##// END OF EJS Templates
add missing kernel_manager in mainwindow.close_tab
MinRK -
Show More
@@ -1,990 +1,992 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 * Paul Ivanov
15 15
16 16 """
17 17
18 18 #-----------------------------------------------------------------------------
19 19 # Imports
20 20 #-----------------------------------------------------------------------------
21 21
22 22 # stdlib imports
23 23 import sys
24 24 import re
25 25 import webbrowser
26 26 import ast
27 27 from threading import Thread
28 28
29 29 # System library imports
30 30 from IPython.external.qt import QtGui,QtCore
31 31
32 32 def background(f):
33 33 """call a function in a simple thread, to prevent blocking"""
34 34 t = Thread(target=f)
35 35 t.start()
36 36 return t
37 37
38 38 #-----------------------------------------------------------------------------
39 39 # Classes
40 40 #-----------------------------------------------------------------------------
41 41
42 42 class MainWindow(QtGui.QMainWindow):
43 43
44 44 #---------------------------------------------------------------------------
45 45 # 'object' interface
46 46 #---------------------------------------------------------------------------
47 47
48 48 _magic_menu_dict = {}
49 49
50 50 def __init__(self, app,
51 51 confirm_exit=True,
52 52 new_frontend_factory=None, slave_frontend_factory=None,
53 53 ):
54 54 """ Create a tabbed MainWindow for managing IPython FrontendWidgets
55 55
56 56 Parameters
57 57 ----------
58 58
59 59 app : reference to QApplication parent
60 60 confirm_exit : bool, optional
61 61 Whether we should prompt on close of tabs
62 62 new_frontend_factory : callable
63 63 A callable that returns a new IPythonWidget instance, attached to
64 64 its own running kernel.
65 65 slave_frontend_factory : callable
66 66 A callable that takes an existing IPythonWidget, and returns a new
67 67 IPythonWidget instance, attached to the same kernel.
68 68 """
69 69
70 70 super(MainWindow, self).__init__()
71 71 self._kernel_counter = 0
72 72 self._app = app
73 73 self.confirm_exit = confirm_exit
74 74 self.new_frontend_factory = new_frontend_factory
75 75 self.slave_frontend_factory = slave_frontend_factory
76 76
77 77 self.tab_widget = QtGui.QTabWidget(self)
78 78 self.tab_widget.setDocumentMode(True)
79 79 self.tab_widget.setTabsClosable(True)
80 80 self.tab_widget.tabCloseRequested[int].connect(self.close_tab)
81 81
82 82 self.setCentralWidget(self.tab_widget)
83 83 # hide tab bar at first, since we have no tabs:
84 84 self.tab_widget.tabBar().setVisible(False)
85 85 # prevent focus in tab bar
86 86 self.tab_widget.setFocusPolicy(QtCore.Qt.NoFocus)
87 87
88 88 def update_tab_bar_visibility(self):
89 89 """ update visibility of the tabBar depending of the number of tab
90 90
91 91 0 or 1 tab, tabBar hidden
92 92 2+ tabs, tabBar visible
93 93
94 94 send a self.close if number of tab ==0
95 95
96 96 need to be called explicitly, or be connected to tabInserted/tabRemoved
97 97 """
98 98 if self.tab_widget.count() <= 1:
99 99 self.tab_widget.tabBar().setVisible(False)
100 100 else:
101 101 self.tab_widget.tabBar().setVisible(True)
102 102 if self.tab_widget.count()==0 :
103 103 self.close()
104 104
105 105 @property
106 106 def next_kernel_id(self):
107 107 """constantly increasing counter for kernel IDs"""
108 108 c = self._kernel_counter
109 109 self._kernel_counter += 1
110 110 return c
111 111
112 112 @property
113 113 def active_frontend(self):
114 114 return self.tab_widget.currentWidget()
115 115
116 116 def create_tab_with_new_frontend(self):
117 117 """create a new frontend and attach it to a new tab"""
118 118 widget = self.new_frontend_factory()
119 119 self.add_tab_with_frontend(widget)
120 120
121 121 def create_tab_with_current_kernel(self):
122 122 """create a new frontend attached to the same kernel as the current tab"""
123 123 current_widget = self.tab_widget.currentWidget()
124 124 current_widget_index = self.tab_widget.indexOf(current_widget)
125 125 current_widget_name = self.tab_widget.tabText(current_widget_index)
126 126 widget = self.slave_frontend_factory(current_widget)
127 127 if 'slave' in current_widget_name:
128 128 # don't keep stacking slaves
129 129 name = current_widget_name
130 130 else:
131 131 name = '(%s) slave' % current_widget_name
132 132 self.add_tab_with_frontend(widget,name=name)
133 133
134 134 def close_tab(self,current_tab):
135 135 """ Called when you need to try to close a tab.
136 136
137 137 It takes the number of the tab to be closed as argument, or a reference
138 138 to the widget inside this tab
139 139 """
140 140
141 141 # let's be sure "tab" and "closing widget" are respectively the index
142 142 # of the tab to close and a reference to the frontend to close
143 143 if type(current_tab) is not int :
144 144 current_tab = self.tab_widget.indexOf(current_tab)
145 145 closing_widget=self.tab_widget.widget(current_tab)
146 146
147 147
148 148 # when trying to be closed, widget might re-send a request to be
149 149 # closed again, but will be deleted when event will be processed. So
150 150 # need to check that widget still exists and skip if not. One example
151 151 # of this is when 'exit' is sent in a slave tab. 'exit' will be
152 152 # re-sent by this function on the master widget, which ask all slave
153 153 # widgets to exit
154 154 if closing_widget==None:
155 155 return
156 156
157 157 #get a list of all slave widgets on the same kernel.
158 158 slave_tabs = self.find_slave_widgets(closing_widget)
159 159
160 160 keepkernel = None #Use the prompt by default
161 161 if hasattr(closing_widget,'_keep_kernel_on_exit'): #set by exit magic
162 162 keepkernel = closing_widget._keep_kernel_on_exit
163 163 # If signal sent by exit magic (_keep_kernel_on_exit, exist and not None)
164 164 # we set local slave tabs._hidden to True to avoid prompting for kernel
165 165 # restart when they get the signal. and then "forward" the 'exit'
166 166 # to the main window
167 167 if keepkernel is not None:
168 168 for tab in slave_tabs:
169 169 tab._hidden = True
170 170 if closing_widget in slave_tabs:
171 171 try :
172 172 self.find_master_tab(closing_widget).execute('exit')
173 173 except AttributeError:
174 174 self.log.info("Master already closed or not local, closing only current tab")
175 175 self.tab_widget.removeTab(current_tab)
176 176 self.update_tab_bar_visibility()
177 177 return
178 178
179 179 kernel_client = closing_widget.kernel_client
180 kernel_manager = closing_widget.kernel_manager
180 181
181 182 if keepkernel is None and not closing_widget._confirm_exit:
182 183 # don't prompt, just terminate the kernel if we own it
183 184 # or leave it alone if we don't
184 185 keepkernel = closing_widget._existing
185 186 if keepkernel is None: #show prompt
186 187 if kernel_client and kernel_client.channels_running:
187 188 title = self.window().windowTitle()
188 189 cancel = QtGui.QMessageBox.Cancel
189 190 okay = QtGui.QMessageBox.Ok
190 191 if closing_widget._may_close:
191 192 msg = "You are closing the tab : "+'"'+self.tab_widget.tabText(current_tab)+'"'
192 193 info = "Would you like to quit the Kernel and close all attached Consoles as well?"
193 194 justthis = QtGui.QPushButton("&No, just this Tab", self)
194 195 justthis.setShortcut('N')
195 196 closeall = QtGui.QPushButton("&Yes, close all", self)
196 197 closeall.setShortcut('Y')
197 198 # allow ctrl-d ctrl-d exit, like in terminal
198 199 closeall.setShortcut('Ctrl+D')
199 200 box = QtGui.QMessageBox(QtGui.QMessageBox.Question,
200 201 title, msg)
201 202 box.setInformativeText(info)
202 203 box.addButton(cancel)
203 204 box.addButton(justthis, QtGui.QMessageBox.NoRole)
204 205 box.addButton(closeall, QtGui.QMessageBox.YesRole)
205 206 box.setDefaultButton(closeall)
206 207 box.setEscapeButton(cancel)
207 208 pixmap = QtGui.QPixmap(self._app.icon.pixmap(QtCore.QSize(64,64)))
208 209 box.setIconPixmap(pixmap)
209 210 reply = box.exec_()
210 211 if reply == 1: # close All
211 212 for slave in slave_tabs:
212 213 background(slave.kernel_client.stop_channels)
213 214 self.tab_widget.removeTab(self.tab_widget.indexOf(slave))
214 215 closing_widget.execute("exit")
215 216 self.tab_widget.removeTab(current_tab)
216 217 background(kernel_client.stop_channels)
217 218 elif reply == 0: # close Console
218 219 if not closing_widget._existing:
219 220 # Have kernel: don't quit, just close the tab
220 221 closing_widget.execute("exit True")
221 222 self.tab_widget.removeTab(current_tab)
222 223 background(kernel_client.stop_channels)
223 224 else:
224 225 reply = QtGui.QMessageBox.question(self, title,
225 226 "Are you sure you want to close this Console?"+
226 227 "\nThe Kernel and other Consoles will remain active.",
227 228 okay|cancel,
228 229 defaultButton=okay
229 230 )
230 231 if reply == okay:
231 232 self.tab_widget.removeTab(current_tab)
232 233 elif keepkernel: #close console but leave kernel running (no prompt)
233 234 self.tab_widget.removeTab(current_tab)
234 235 background(kernel_client.stop_channels)
235 236 else: #close console and kernel (no prompt)
236 237 self.tab_widget.removeTab(current_tab)
237 238 if kernel_client and kernel_client.channels_running:
238 239 for slave in slave_tabs:
239 240 background(slave.kernel_client.stop_channels)
240 241 self.tab_widget.removeTab(self.tab_widget.indexOf(slave))
241 kernel_manager.shutdown_kernel()
242 if kernel_manager:
243 kernel_manager.shutdown_kernel()
242 244 background(kernel_client.stop_channels)
243 245
244 246 self.update_tab_bar_visibility()
245 247
246 248 def add_tab_with_frontend(self,frontend,name=None):
247 249 """ insert a tab with a given frontend in the tab bar, and give it a name
248 250
249 251 """
250 252 if not name:
251 253 name = 'kernel %i' % self.next_kernel_id
252 254 self.tab_widget.addTab(frontend,name)
253 255 self.update_tab_bar_visibility()
254 256 self.make_frontend_visible(frontend)
255 257 frontend.exit_requested.connect(self.close_tab)
256 258
257 259 def next_tab(self):
258 260 self.tab_widget.setCurrentIndex((self.tab_widget.currentIndex()+1))
259 261
260 262 def prev_tab(self):
261 263 self.tab_widget.setCurrentIndex((self.tab_widget.currentIndex()-1))
262 264
263 265 def make_frontend_visible(self,frontend):
264 266 widget_index=self.tab_widget.indexOf(frontend)
265 267 if widget_index > 0 :
266 268 self.tab_widget.setCurrentIndex(widget_index)
267 269
268 270 def find_master_tab(self,tab,as_list=False):
269 271 """
270 272 Try to return the frontend that owns the kernel attached to the given widget/tab.
271 273
272 274 Only finds frontend owned by the current application. Selection
273 275 based on port of the kernel might be inaccurate if several kernel
274 276 on different ip use same port number.
275 277
276 278 This function does the conversion tabNumber/widget if needed.
277 279 Might return None if no master widget (non local kernel)
278 280 Will crash IPython if more than 1 masterWidget
279 281
280 282 When asList set to True, always return a list of widget(s) owning
281 283 the kernel. The list might be empty or containing several Widget.
282 284 """
283 285
284 286 #convert from/to int/richIpythonWidget if needed
285 287 if isinstance(tab, int):
286 288 tab = self.tab_widget.widget(tab)
287 289 km=tab.kernel_client
288 290
289 291 #build list of all widgets
290 292 widget_list = [self.tab_widget.widget(i) for i in range(self.tab_widget.count())]
291 293
292 294 # widget that are candidate to be the owner of the kernel does have all the same port of the curent widget
293 295 # And should have a _may_close attribute
294 296 filtered_widget_list = [ widget for widget in widget_list if
295 297 widget.kernel_client.connection_file == km.connection_file and
296 298 hasattr(widget,'_may_close') ]
297 299 # the master widget is the one that may close the kernel
298 300 master_widget= [ widget for widget in filtered_widget_list if widget._may_close]
299 301 if as_list:
300 302 return master_widget
301 303 assert(len(master_widget)<=1 )
302 304 if len(master_widget)==0:
303 305 return None
304 306
305 307 return master_widget[0]
306 308
307 309 def find_slave_widgets(self,tab):
308 310 """return all the frontends that do not own the kernel attached to the given widget/tab.
309 311
310 312 Only find frontends owned by the current application. Selection
311 313 based on connection file of the kernel.
312 314
313 315 This function does the conversion tabNumber/widget if needed.
314 316 """
315 317 #convert from/to int/richIpythonWidget if needed
316 318 if isinstance(tab, int):
317 319 tab = self.tab_widget.widget(tab)
318 320 km=tab.kernel_client
319 321
320 322 #build list of all widgets
321 323 widget_list = [self.tab_widget.widget(i) for i in range(self.tab_widget.count())]
322 324
323 325 # widget that are candidate not to be the owner of the kernel does have all the same port of the curent widget
324 326 filtered_widget_list = ( widget for widget in widget_list if
325 327 widget.kernel_client.connection_file == km.connection_file)
326 328 # Get a list of all widget owning the same kernel and removed it from
327 329 # the previous cadidate. (better using sets ?)
328 330 master_widget_list = self.find_master_tab(tab, as_list=True)
329 331 slave_list = [widget for widget in filtered_widget_list if widget not in master_widget_list]
330 332
331 333 return slave_list
332 334
333 335 # Populate the menu bar with common actions and shortcuts
334 336 def add_menu_action(self, menu, action, defer_shortcut=False):
335 337 """Add action to menu as well as self
336 338
337 339 So that when the menu bar is invisible, its actions are still available.
338 340
339 341 If defer_shortcut is True, set the shortcut context to widget-only,
340 342 where it will avoid conflict with shortcuts already bound to the
341 343 widgets themselves.
342 344 """
343 345 menu.addAction(action)
344 346 self.addAction(action)
345 347
346 348 if defer_shortcut:
347 349 action.setShortcutContext(QtCore.Qt.WidgetShortcut)
348 350
349 351 def init_menu_bar(self):
350 352 #create menu in the order they should appear in the menu bar
351 353 self.init_file_menu()
352 354 self.init_edit_menu()
353 355 self.init_view_menu()
354 356 self.init_kernel_menu()
355 357 self.init_magic_menu()
356 358 self.init_window_menu()
357 359 self.init_help_menu()
358 360
359 361 def init_file_menu(self):
360 362 self.file_menu = self.menuBar().addMenu("&File")
361 363
362 364 self.new_kernel_tab_act = QtGui.QAction("New Tab with &New kernel",
363 365 self,
364 366 shortcut="Ctrl+T",
365 367 triggered=self.create_tab_with_new_frontend)
366 368 self.add_menu_action(self.file_menu, self.new_kernel_tab_act)
367 369
368 370 self.slave_kernel_tab_act = QtGui.QAction("New Tab with Sa&me kernel",
369 371 self,
370 372 shortcut="Ctrl+Shift+T",
371 373 triggered=self.create_tab_with_current_kernel)
372 374 self.add_menu_action(self.file_menu, self.slave_kernel_tab_act)
373 375
374 376 self.file_menu.addSeparator()
375 377
376 378 self.close_action=QtGui.QAction("&Close Tab",
377 379 self,
378 380 shortcut=QtGui.QKeySequence.Close,
379 381 triggered=self.close_active_frontend
380 382 )
381 383 self.add_menu_action(self.file_menu, self.close_action)
382 384
383 385 self.export_action=QtGui.QAction("&Save to HTML/XHTML",
384 386 self,
385 387 shortcut=QtGui.QKeySequence.Save,
386 388 triggered=self.export_action_active_frontend
387 389 )
388 390 self.add_menu_action(self.file_menu, self.export_action, True)
389 391
390 392 self.file_menu.addSeparator()
391 393
392 394 printkey = QtGui.QKeySequence(QtGui.QKeySequence.Print)
393 395 if printkey.matches("Ctrl+P") and sys.platform != 'darwin':
394 396 # Only override the default if there is a collision.
395 397 # Qt ctrl = cmd on OSX, so the match gets a false positive on OSX.
396 398 printkey = "Ctrl+Shift+P"
397 399 self.print_action = QtGui.QAction("&Print",
398 400 self,
399 401 shortcut=printkey,
400 402 triggered=self.print_action_active_frontend)
401 403 self.add_menu_action(self.file_menu, self.print_action, True)
402 404
403 405 if sys.platform != 'darwin':
404 406 # OSX always has Quit in the Application menu, only add it
405 407 # to the File menu elsewhere.
406 408
407 409 self.file_menu.addSeparator()
408 410
409 411 self.quit_action = QtGui.QAction("&Quit",
410 412 self,
411 413 shortcut=QtGui.QKeySequence.Quit,
412 414 triggered=self.close,
413 415 )
414 416 self.add_menu_action(self.file_menu, self.quit_action)
415 417
416 418
417 419 def init_edit_menu(self):
418 420 self.edit_menu = self.menuBar().addMenu("&Edit")
419 421
420 422 self.undo_action = QtGui.QAction("&Undo",
421 423 self,
422 424 shortcut=QtGui.QKeySequence.Undo,
423 425 statusTip="Undo last action if possible",
424 426 triggered=self.undo_active_frontend
425 427 )
426 428 self.add_menu_action(self.edit_menu, self.undo_action)
427 429
428 430 self.redo_action = QtGui.QAction("&Redo",
429 431 self,
430 432 shortcut=QtGui.QKeySequence.Redo,
431 433 statusTip="Redo last action if possible",
432 434 triggered=self.redo_active_frontend)
433 435 self.add_menu_action(self.edit_menu, self.redo_action)
434 436
435 437 self.edit_menu.addSeparator()
436 438
437 439 self.cut_action = QtGui.QAction("&Cut",
438 440 self,
439 441 shortcut=QtGui.QKeySequence.Cut,
440 442 triggered=self.cut_active_frontend
441 443 )
442 444 self.add_menu_action(self.edit_menu, self.cut_action, True)
443 445
444 446 self.copy_action = QtGui.QAction("&Copy",
445 447 self,
446 448 shortcut=QtGui.QKeySequence.Copy,
447 449 triggered=self.copy_active_frontend
448 450 )
449 451 self.add_menu_action(self.edit_menu, self.copy_action, True)
450 452
451 453 self.copy_raw_action = QtGui.QAction("Copy (&Raw Text)",
452 454 self,
453 455 shortcut="Ctrl+Shift+C",
454 456 triggered=self.copy_raw_active_frontend
455 457 )
456 458 self.add_menu_action(self.edit_menu, self.copy_raw_action, True)
457 459
458 460 self.paste_action = QtGui.QAction("&Paste",
459 461 self,
460 462 shortcut=QtGui.QKeySequence.Paste,
461 463 triggered=self.paste_active_frontend
462 464 )
463 465 self.add_menu_action(self.edit_menu, self.paste_action, True)
464 466
465 467 self.edit_menu.addSeparator()
466 468
467 469 selectall = QtGui.QKeySequence(QtGui.QKeySequence.SelectAll)
468 470 if selectall.matches("Ctrl+A") and sys.platform != 'darwin':
469 471 # Only override the default if there is a collision.
470 472 # Qt ctrl = cmd on OSX, so the match gets a false positive on OSX.
471 473 selectall = "Ctrl+Shift+A"
472 474 self.select_all_action = QtGui.QAction("Select &All",
473 475 self,
474 476 shortcut=selectall,
475 477 triggered=self.select_all_active_frontend
476 478 )
477 479 self.add_menu_action(self.edit_menu, self.select_all_action, True)
478 480
479 481
480 482 def init_view_menu(self):
481 483 self.view_menu = self.menuBar().addMenu("&View")
482 484
483 485 if sys.platform != 'darwin':
484 486 # disable on OSX, where there is always a menu bar
485 487 self.toggle_menu_bar_act = QtGui.QAction("Toggle &Menu Bar",
486 488 self,
487 489 shortcut="Ctrl+Shift+M",
488 490 statusTip="Toggle visibility of menubar",
489 491 triggered=self.toggle_menu_bar)
490 492 self.add_menu_action(self.view_menu, self.toggle_menu_bar_act)
491 493
492 494 fs_key = "Ctrl+Meta+F" if sys.platform == 'darwin' else "F11"
493 495 self.full_screen_act = QtGui.QAction("&Full Screen",
494 496 self,
495 497 shortcut=fs_key,
496 498 statusTip="Toggle between Fullscreen and Normal Size",
497 499 triggered=self.toggleFullScreen)
498 500 self.add_menu_action(self.view_menu, self.full_screen_act)
499 501
500 502 self.view_menu.addSeparator()
501 503
502 504 self.increase_font_size = QtGui.QAction("Zoom &In",
503 505 self,
504 506 shortcut=QtGui.QKeySequence.ZoomIn,
505 507 triggered=self.increase_font_size_active_frontend
506 508 )
507 509 self.add_menu_action(self.view_menu, self.increase_font_size, True)
508 510
509 511 self.decrease_font_size = QtGui.QAction("Zoom &Out",
510 512 self,
511 513 shortcut=QtGui.QKeySequence.ZoomOut,
512 514 triggered=self.decrease_font_size_active_frontend
513 515 )
514 516 self.add_menu_action(self.view_menu, self.decrease_font_size, True)
515 517
516 518 self.reset_font_size = QtGui.QAction("Zoom &Reset",
517 519 self,
518 520 shortcut="Ctrl+0",
519 521 triggered=self.reset_font_size_active_frontend
520 522 )
521 523 self.add_menu_action(self.view_menu, self.reset_font_size, True)
522 524
523 525 self.view_menu.addSeparator()
524 526
525 527 self.clear_action = QtGui.QAction("&Clear Screen",
526 528 self,
527 529 shortcut='Ctrl+L',
528 530 statusTip="Clear the console",
529 531 triggered=self.clear_magic_active_frontend)
530 532 self.add_menu_action(self.view_menu, self.clear_action)
531 533
532 534 self.pager_menu = self.view_menu.addMenu("&Pager")
533 535
534 536 hsplit_action = QtGui.QAction(".. &Horizontal Split",
535 537 self,
536 538 triggered=lambda: self.set_paging_active_frontend('hsplit'))
537 539
538 540 vsplit_action = QtGui.QAction(" : &Vertical Split",
539 541 self,
540 542 triggered=lambda: self.set_paging_active_frontend('vsplit'))
541 543
542 544 inside_action = QtGui.QAction(" &Inside Pager",
543 545 self,
544 546 triggered=lambda: self.set_paging_active_frontend('inside'))
545 547
546 548 self.pager_menu.addAction(hsplit_action)
547 549 self.pager_menu.addAction(vsplit_action)
548 550 self.pager_menu.addAction(inside_action)
549 551
550 552 def init_kernel_menu(self):
551 553 self.kernel_menu = self.menuBar().addMenu("&Kernel")
552 554 # Qt on OSX maps Ctrl to Cmd, and Meta to Ctrl
553 555 # keep the signal shortcuts to ctrl, rather than
554 556 # platform-default like we do elsewhere.
555 557
556 558 ctrl = "Meta" if sys.platform == 'darwin' else "Ctrl"
557 559
558 560 self.interrupt_kernel_action = QtGui.QAction("&Interrupt current Kernel",
559 561 self,
560 562 triggered=self.interrupt_kernel_active_frontend,
561 563 shortcut=ctrl+"+C",
562 564 )
563 565 self.add_menu_action(self.kernel_menu, self.interrupt_kernel_action)
564 566
565 567 self.restart_kernel_action = QtGui.QAction("&Restart current Kernel",
566 568 self,
567 569 triggered=self.restart_kernel_active_frontend,
568 570 shortcut=ctrl+"+.",
569 571 )
570 572 self.add_menu_action(self.kernel_menu, self.restart_kernel_action)
571 573
572 574 self.kernel_menu.addSeparator()
573 575
574 576 self.confirm_restart_kernel_action = QtGui.QAction("&Confirm kernel restart",
575 577 self,
576 578 checkable=True,
577 579 checked=self.active_frontend.confirm_restart,
578 580 triggered=self.toggle_confirm_restart_active_frontend
579 581 )
580 582
581 583 self.add_menu_action(self.kernel_menu, self.confirm_restart_kernel_action)
582 584 self.tab_widget.currentChanged.connect(self.update_restart_checkbox)
583 585
584 586 def _make_dynamic_magic(self,magic):
585 587 """Return a function `fun` that will execute `magic` on active frontend.
586 588
587 589 Parameters
588 590 ----------
589 591 magic : string
590 592 string that will be executed as is when the returned function is called
591 593
592 594 Returns
593 595 -------
594 596 fun : function
595 597 function with no parameters, when called will execute `magic` on the
596 598 current active frontend at call time
597 599
598 600 See Also
599 601 --------
600 602 populate_all_magic_menu : generate the "All Magics..." menu
601 603
602 604 Notes
603 605 -----
604 606 `fun` executes `magic` in active frontend at the moment it is triggered,
605 607 not the active frontend at the moment it was created.
606 608
607 609 This function is mostly used to create the "All Magics..." Menu at run time.
608 610 """
609 611 # need two level nested function to be sure to pass magic
610 612 # to active frontend **at run time**.
611 613 def inner_dynamic_magic():
612 614 self.active_frontend.execute(magic)
613 615 inner_dynamic_magic.__name__ = "dynamics_magic_s"
614 616 return inner_dynamic_magic
615 617
616 618 def populate_all_magic_menu(self, listofmagic=None):
617 619 """Clean "All Magics..." menu and repopulate it with `listofmagic`
618 620
619 621 Parameters
620 622 ----------
621 623 listofmagic : string,
622 624 repr() of a list of strings, send back by the kernel
623 625
624 626 Notes
625 627 -----
626 628 `listofmagic`is a repr() of list because it is fed with the result of
627 629 a 'user_expression'
628 630 """
629 631 for k,v in self._magic_menu_dict.items():
630 632 v.clear()
631 633 self.all_magic_menu.clear()
632 634
633 635
634 636 mlist=ast.literal_eval(listofmagic)
635 637 for magic in mlist:
636 638 cell = (magic['type'] == 'cell')
637 639 name = magic['name']
638 640 mclass = magic['class']
639 641 if cell :
640 642 prefix='%%'
641 643 else :
642 644 prefix='%'
643 645 magic_menu = self._get_magic_menu(mclass)
644 646
645 647 pmagic = '%s%s'%(prefix,name)
646 648
647 649 xaction = QtGui.QAction(pmagic,
648 650 self,
649 651 triggered=self._make_dynamic_magic(pmagic)
650 652 )
651 653 magic_menu.addAction(xaction)
652 654 self.all_magic_menu.addAction(xaction)
653 655
654 656 def update_all_magic_menu(self):
655 657 """ Update the list of magics in the "All Magics..." Menu
656 658
657 659 Request the kernel with the list of available magics and populate the
658 660 menu with the list received back
659 661
660 662 """
661 663 self.active_frontend._silent_exec_callback('get_ipython().magics_manager.lsmagic_info()',
662 664 self.populate_all_magic_menu)
663 665
664 666 def _get_magic_menu(self,menuidentifier, menulabel=None):
665 667 """return a submagic menu by name, and create it if needed
666 668
667 669 parameters:
668 670 -----------
669 671
670 672 menulabel : str
671 673 Label for the menu
672 674
673 675 Will infere the menu name from the identifier at creation if menulabel not given.
674 676 To do so you have too give menuidentifier as a CamelCassedString
675 677 """
676 678 menu = self._magic_menu_dict.get(menuidentifier,None)
677 679 if not menu :
678 680 if not menulabel:
679 681 menulabel = re.sub("([a-zA-Z]+)([A-Z][a-z])","\g<1> \g<2>",menuidentifier)
680 682 menu = QtGui.QMenu(menulabel,self.magic_menu)
681 683 self._magic_menu_dict[menuidentifier]=menu
682 684 self.magic_menu.insertMenu(self.magic_menu_separator,menu)
683 685 return menu
684 686
685 687
686 688
687 689 def init_magic_menu(self):
688 690 self.magic_menu = self.menuBar().addMenu("&Magic")
689 691 self.magic_menu_separator = self.magic_menu.addSeparator()
690 692
691 693 self.all_magic_menu = self._get_magic_menu("AllMagics", menulabel="&All Magics...")
692 694
693 695 # This action should usually not appear as it will be cleared when menu
694 696 # is updated at first kernel response. Though, it is necessary when
695 697 # connecting through X-forwarding, as in this case, the menu is not
696 698 # auto updated, SO DO NOT DELETE.
697 699 self.pop = QtGui.QAction("&Update All Magic Menu ",
698 700 self, triggered=self.update_all_magic_menu)
699 701 self.add_menu_action(self.all_magic_menu, self.pop)
700 702 # we need to populate the 'Magic Menu' once the kernel has answer at
701 703 # least once let's do it immediately, but it's assured to works
702 704 self.pop.trigger()
703 705
704 706 self.reset_action = QtGui.QAction("&Reset",
705 707 self,
706 708 statusTip="Clear all variables from workspace",
707 709 triggered=self.reset_magic_active_frontend)
708 710 self.add_menu_action(self.magic_menu, self.reset_action)
709 711
710 712 self.history_action = QtGui.QAction("&History",
711 713 self,
712 714 statusTip="show command history",
713 715 triggered=self.history_magic_active_frontend)
714 716 self.add_menu_action(self.magic_menu, self.history_action)
715 717
716 718 self.save_action = QtGui.QAction("E&xport History ",
717 719 self,
718 720 statusTip="Export History as Python File",
719 721 triggered=self.save_magic_active_frontend)
720 722 self.add_menu_action(self.magic_menu, self.save_action)
721 723
722 724 self.who_action = QtGui.QAction("&Who",
723 725 self,
724 726 statusTip="List interactive variables",
725 727 triggered=self.who_magic_active_frontend)
726 728 self.add_menu_action(self.magic_menu, self.who_action)
727 729
728 730 self.who_ls_action = QtGui.QAction("Wh&o ls",
729 731 self,
730 732 statusTip="Return a list of interactive variables",
731 733 triggered=self.who_ls_magic_active_frontend)
732 734 self.add_menu_action(self.magic_menu, self.who_ls_action)
733 735
734 736 self.whos_action = QtGui.QAction("Who&s",
735 737 self,
736 738 statusTip="List interactive variables with details",
737 739 triggered=self.whos_magic_active_frontend)
738 740 self.add_menu_action(self.magic_menu, self.whos_action)
739 741
740 742 def init_window_menu(self):
741 743 self.window_menu = self.menuBar().addMenu("&Window")
742 744 if sys.platform == 'darwin':
743 745 # add min/maximize actions to OSX, which lacks default bindings.
744 746 self.minimizeAct = QtGui.QAction("Mini&mize",
745 747 self,
746 748 shortcut="Ctrl+m",
747 749 statusTip="Minimize the window/Restore Normal Size",
748 750 triggered=self.toggleMinimized)
749 751 # maximize is called 'Zoom' on OSX for some reason
750 752 self.maximizeAct = QtGui.QAction("&Zoom",
751 753 self,
752 754 shortcut="Ctrl+Shift+M",
753 755 statusTip="Maximize the window/Restore Normal Size",
754 756 triggered=self.toggleMaximized)
755 757
756 758 self.add_menu_action(self.window_menu, self.minimizeAct)
757 759 self.add_menu_action(self.window_menu, self.maximizeAct)
758 760 self.window_menu.addSeparator()
759 761
760 762 prev_key = "Ctrl+Shift+Left" if sys.platform == 'darwin' else "Ctrl+PgUp"
761 763 self.prev_tab_act = QtGui.QAction("Pre&vious Tab",
762 764 self,
763 765 shortcut=prev_key,
764 766 statusTip="Select previous tab",
765 767 triggered=self.prev_tab)
766 768 self.add_menu_action(self.window_menu, self.prev_tab_act)
767 769
768 770 next_key = "Ctrl+Shift+Right" if sys.platform == 'darwin' else "Ctrl+PgDown"
769 771 self.next_tab_act = QtGui.QAction("Ne&xt Tab",
770 772 self,
771 773 shortcut=next_key,
772 774 statusTip="Select next tab",
773 775 triggered=self.next_tab)
774 776 self.add_menu_action(self.window_menu, self.next_tab_act)
775 777
776 778 def init_help_menu(self):
777 779 # please keep the Help menu in Mac Os even if empty. It will
778 780 # automatically contain a search field to search inside menus and
779 781 # please keep it spelled in English, as long as Qt Doesn't support
780 782 # a QAction.MenuRole like HelpMenuRole otherwise it will lose
781 783 # this search field functionality
782 784
783 785 self.help_menu = self.menuBar().addMenu("&Help")
784 786
785 787
786 788 # Help Menu
787 789
788 790 self.intro_active_frontend_action = QtGui.QAction("&Intro to IPython",
789 791 self,
790 792 triggered=self.intro_active_frontend
791 793 )
792 794 self.add_menu_action(self.help_menu, self.intro_active_frontend_action)
793 795
794 796 self.quickref_active_frontend_action = QtGui.QAction("IPython &Cheat Sheet",
795 797 self,
796 798 triggered=self.quickref_active_frontend
797 799 )
798 800 self.add_menu_action(self.help_menu, self.quickref_active_frontend_action)
799 801
800 802 self.guiref_active_frontend_action = QtGui.QAction("&Qt Console",
801 803 self,
802 804 triggered=self.guiref_active_frontend
803 805 )
804 806 self.add_menu_action(self.help_menu, self.guiref_active_frontend_action)
805 807
806 808 self.onlineHelpAct = QtGui.QAction("Open Online &Help",
807 809 self,
808 810 triggered=self._open_online_help)
809 811 self.add_menu_action(self.help_menu, self.onlineHelpAct)
810 812
811 813 # minimize/maximize/fullscreen actions:
812 814
813 815 def toggle_menu_bar(self):
814 816 menu_bar = self.menuBar()
815 817 if menu_bar.isVisible():
816 818 menu_bar.setVisible(False)
817 819 else:
818 820 menu_bar.setVisible(True)
819 821
820 822 def toggleMinimized(self):
821 823 if not self.isMinimized():
822 824 self.showMinimized()
823 825 else:
824 826 self.showNormal()
825 827
826 828 def _open_online_help(self):
827 829 filename="http://ipython.org/ipython-doc/stable/index.html"
828 830 webbrowser.open(filename, new=1, autoraise=True)
829 831
830 832 def toggleMaximized(self):
831 833 if not self.isMaximized():
832 834 self.showMaximized()
833 835 else:
834 836 self.showNormal()
835 837
836 838 # Min/Max imizing while in full screen give a bug
837 839 # when going out of full screen, at least on OSX
838 840 def toggleFullScreen(self):
839 841 if not self.isFullScreen():
840 842 self.showFullScreen()
841 843 if sys.platform == 'darwin':
842 844 self.maximizeAct.setEnabled(False)
843 845 self.minimizeAct.setEnabled(False)
844 846 else:
845 847 self.showNormal()
846 848 if sys.platform == 'darwin':
847 849 self.maximizeAct.setEnabled(True)
848 850 self.minimizeAct.setEnabled(True)
849 851
850 852 def set_paging_active_frontend(self, paging):
851 853 self.active_frontend._set_paging(paging)
852 854
853 855 def close_active_frontend(self):
854 856 self.close_tab(self.active_frontend)
855 857
856 858 def restart_kernel_active_frontend(self):
857 859 self.active_frontend.request_restart_kernel()
858 860
859 861 def interrupt_kernel_active_frontend(self):
860 862 self.active_frontend.request_interrupt_kernel()
861 863
862 864 def toggle_confirm_restart_active_frontend(self):
863 865 widget = self.active_frontend
864 866 widget.confirm_restart = not widget.confirm_restart
865 867 self.confirm_restart_kernel_action.setChecked(widget.confirm_restart)
866 868
867 869 def update_restart_checkbox(self):
868 870 if self.active_frontend is None:
869 871 return
870 872 widget = self.active_frontend
871 873 self.confirm_restart_kernel_action.setChecked(widget.confirm_restart)
872 874
873 875 def cut_active_frontend(self):
874 876 widget = self.active_frontend
875 877 if widget.can_cut():
876 878 widget.cut()
877 879
878 880 def copy_active_frontend(self):
879 881 widget = self.active_frontend
880 882 widget.copy()
881 883
882 884 def copy_raw_active_frontend(self):
883 885 self.active_frontend._copy_raw_action.trigger()
884 886
885 887 def paste_active_frontend(self):
886 888 widget = self.active_frontend
887 889 if widget.can_paste():
888 890 widget.paste()
889 891
890 892 def undo_active_frontend(self):
891 893 self.active_frontend.undo()
892 894
893 895 def redo_active_frontend(self):
894 896 self.active_frontend.redo()
895 897
896 898 def reset_magic_active_frontend(self):
897 899 self.active_frontend.execute("%reset")
898 900
899 901 def history_magic_active_frontend(self):
900 902 self.active_frontend.execute("%history")
901 903
902 904 def save_magic_active_frontend(self):
903 905 self.active_frontend.save_magic()
904 906
905 907 def clear_magic_active_frontend(self):
906 908 self.active_frontend.execute("%clear")
907 909
908 910 def who_magic_active_frontend(self):
909 911 self.active_frontend.execute("%who")
910 912
911 913 def who_ls_magic_active_frontend(self):
912 914 self.active_frontend.execute("%who_ls")
913 915
914 916 def whos_magic_active_frontend(self):
915 917 self.active_frontend.execute("%whos")
916 918
917 919 def print_action_active_frontend(self):
918 920 self.active_frontend.print_action.trigger()
919 921
920 922 def export_action_active_frontend(self):
921 923 self.active_frontend.export_action.trigger()
922 924
923 925 def select_all_active_frontend(self):
924 926 self.active_frontend.select_all_action.trigger()
925 927
926 928 def increase_font_size_active_frontend(self):
927 929 self.active_frontend.increase_font_size.trigger()
928 930
929 931 def decrease_font_size_active_frontend(self):
930 932 self.active_frontend.decrease_font_size.trigger()
931 933
932 934 def reset_font_size_active_frontend(self):
933 935 self.active_frontend.reset_font_size.trigger()
934 936
935 937 def guiref_active_frontend(self):
936 938 self.active_frontend.execute("%guiref")
937 939
938 940 def intro_active_frontend(self):
939 941 self.active_frontend.execute("?")
940 942
941 943 def quickref_active_frontend(self):
942 944 self.active_frontend.execute("%quickref")
943 945 #---------------------------------------------------------------------------
944 946 # QWidget interface
945 947 #---------------------------------------------------------------------------
946 948
947 949 def closeEvent(self, event):
948 950 """ Forward the close event to every tabs contained by the windows
949 951 """
950 952 if self.tab_widget.count() == 0:
951 953 # no tabs, just close
952 954 event.accept()
953 955 return
954 956 # Do Not loop on the widget count as it change while closing
955 957 title = self.window().windowTitle()
956 958 cancel = QtGui.QMessageBox.Cancel
957 959 okay = QtGui.QMessageBox.Ok
958 960
959 961 if self.confirm_exit:
960 962 if self.tab_widget.count() > 1:
961 963 msg = "Close all tabs, stop all kernels, and Quit?"
962 964 else:
963 965 msg = "Close console, stop kernel, and Quit?"
964 966 info = "Kernels not started here (e.g. notebooks) will be left alone."
965 967 closeall = QtGui.QPushButton("&Quit", self)
966 968 closeall.setShortcut('Q')
967 969 box = QtGui.QMessageBox(QtGui.QMessageBox.Question,
968 970 title, msg)
969 971 box.setInformativeText(info)
970 972 box.addButton(cancel)
971 973 box.addButton(closeall, QtGui.QMessageBox.YesRole)
972 974 box.setDefaultButton(closeall)
973 975 box.setEscapeButton(cancel)
974 976 pixmap = QtGui.QPixmap(self._app.icon.pixmap(QtCore.QSize(64,64)))
975 977 box.setIconPixmap(pixmap)
976 978 reply = box.exec_()
977 979 else:
978 980 reply = okay
979 981
980 982 if reply == cancel:
981 983 event.ignore()
982 984 return
983 985 if reply == okay:
984 986 while self.tab_widget.count() >= 1:
985 987 # prevent further confirmations:
986 988 widget = self.active_frontend
987 989 widget._confirm_exit = False
988 990 self.close_tab(widget)
989 991 event.accept()
990 992
General Comments 0
You need to be logged in to leave comments. Login now