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