##// END OF EJS Templates
add keyboard accelerators to Kernel menu
Paul Ivanov -
Show More
@@ -1,971 +1,971 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 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 self.interrupt_kernel_action = QtGui.QAction("Interrupt current Kernel",
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 self.restart_kernel_action = QtGui.QAction("Restart current Kernel",
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",
555 self.confirm_restart_kernel_action = QtGui.QAction("&Confirm kernel restart",
556 556 self,
557 557 checkable=True,
558 558 checked=self.active_frontend.confirm_restart,
559 559 triggered=self.toggle_confirm_restart_active_frontend
560 560 )
561 561
562 562 self.add_menu_action(self.kernel_menu, self.confirm_restart_kernel_action)
563 563
564 564 def _make_dynamic_magic(self,magic):
565 565 """Return a function `fun` that will execute `magic` on active frontend.
566 566
567 567 Parameters
568 568 ----------
569 569 magic : string
570 570 string that will be executed as is when the returned function is called
571 571
572 572 Returns
573 573 -------
574 574 fun : function
575 575 function with no parameters, when called will execute `magic` on the
576 576 current active frontend at call time
577 577
578 578 See Also
579 579 --------
580 580 populate_all_magic_menu : generate the "All Magics..." menu
581 581
582 582 Notes
583 583 -----
584 584 `fun` executes `magic` in active frontend at the moment it is triggered,
585 585 not the active frontend at the moment it was created.
586 586
587 587 This function is mostly used to create the "All Magics..." Menu at run time.
588 588 """
589 589 # need two level nested function to be sure to pass magic
590 590 # to active frontend **at run time**.
591 591 def inner_dynamic_magic():
592 592 self.active_frontend.execute(magic)
593 593 inner_dynamic_magic.__name__ = "dynamics_magic_s"
594 594 return inner_dynamic_magic
595 595
596 596 def populate_all_magic_menu(self, listofmagic=None):
597 597 """Clean "All Magics..." menu and repopulate it with `listofmagic`
598 598
599 599 Parameters
600 600 ----------
601 601 listofmagic : string,
602 602 repr() of a list of strings, send back by the kernel
603 603
604 604 Notes
605 605 -----
606 606 `listofmagic`is a repr() of list because it is fed with the result of
607 607 a 'user_expression'
608 608 """
609 609 for k,v in self._magic_menu_dict.items():
610 610 v.clear()
611 611 self.all_magic_menu.clear()
612 612
613 613
614 614 protected_magic = set(["more","less","load_ext","pycat","loadpy","load","save","psource"])
615 615 mlist=ast.literal_eval(listofmagic)
616 616 for magic in mlist:
617 617 cell = (magic['type'] == 'cell')
618 618 name = magic['name']
619 619 mclass = magic['class']
620 620 if cell :
621 621 prefix='%%'
622 622 else :
623 623 prefix='%'
624 624 magic_menu = self._get_magic_menu(mclass)
625 625
626 626 if name in protected_magic:
627 627 suffix = '?'
628 628 else :
629 629 suffix = ''
630 630 pmagic = '%s%s%s'%(prefix,name,suffix)
631 631
632 632 xaction = QtGui.QAction(pmagic,
633 633 self,
634 634 triggered=self._make_dynamic_magic(pmagic)
635 635 )
636 636 magic_menu.addAction(xaction)
637 637 self.all_magic_menu.addAction(xaction)
638 638
639 639 def update_all_magic_menu(self):
640 640 """ Update the list of magics in the "All Magics..." Menu
641 641
642 642 Request the kernel with the list of available magics and populate the
643 643 menu with the list received back
644 644
645 645 """
646 646 self.active_frontend._silent_exec_callback('get_ipython().magics_manager.lsmagic_info()',
647 647 self.populate_all_magic_menu)
648 648
649 649 def _get_magic_menu(self,menuidentifier, menulabel=None):
650 650 """return a submagic menu by name, and create it if needed
651 651
652 652 parameters:
653 653 -----------
654 654
655 655 menulabel : str
656 656 Label for the menu
657 657
658 658 Will infere the menu name from the identifier at creation if menulabel not given.
659 659 To do so you have too give menuidentifier as a CamelCassedString
660 660 """
661 661 menu = self._magic_menu_dict.get(menuidentifier,None)
662 662 if not menu :
663 663 if not menulabel:
664 664 menulabel = re.sub("([a-zA-Z]+)([A-Z][a-z])","\g<1> \g<2>",menuidentifier)
665 665 menu = QtGui.QMenu(menulabel,self.magic_menu)
666 666 self._magic_menu_dict[menuidentifier]=menu
667 667 self.magic_menu.insertMenu(self.magic_menu_separator,menu)
668 668 return menu
669 669
670 670
671 671
672 672 def init_magic_menu(self):
673 673 self.magic_menu = self.menuBar().addMenu("&Magic")
674 674 self.magic_menu_separator = self.magic_menu.addSeparator()
675 675
676 676 self.all_magic_menu = self._get_magic_menu("AllMagics", menulabel="&All Magics...")
677 677
678 678 # This action should usually not appear as it will be cleared when menu
679 679 # is updated at first kernel response. Though, it is necessary when
680 680 # connecting through X-forwarding, as in this case, the menu is not
681 681 # auto updated, SO DO NOT DELETE.
682 682 self.pop = QtGui.QAction("&Update All Magic Menu ",
683 683 self, triggered=self.update_all_magic_menu)
684 684 self.add_menu_action(self.all_magic_menu, self.pop)
685 685 # we need to populate the 'Magic Menu' once the kernel has answer at
686 686 # least once let's do it immediately, but it's assured to works
687 687 self.pop.trigger()
688 688
689 689 self.reset_action = QtGui.QAction("&Reset",
690 690 self,
691 691 statusTip="Clear all variables from workspace",
692 692 triggered=self.reset_magic_active_frontend)
693 693 self.add_menu_action(self.magic_menu, self.reset_action)
694 694
695 695 self.history_action = QtGui.QAction("&History",
696 696 self,
697 697 statusTip="show command history",
698 698 triggered=self.history_magic_active_frontend)
699 699 self.add_menu_action(self.magic_menu, self.history_action)
700 700
701 701 self.save_action = QtGui.QAction("E&xport History ",
702 702 self,
703 703 statusTip="Export History as Python File",
704 704 triggered=self.save_magic_active_frontend)
705 705 self.add_menu_action(self.magic_menu, self.save_action)
706 706
707 707 self.who_action = QtGui.QAction("&Who",
708 708 self,
709 709 statusTip="List interactive variables",
710 710 triggered=self.who_magic_active_frontend)
711 711 self.add_menu_action(self.magic_menu, self.who_action)
712 712
713 713 self.who_ls_action = QtGui.QAction("Wh&o ls",
714 714 self,
715 715 statusTip="Return a list of interactive variables",
716 716 triggered=self.who_ls_magic_active_frontend)
717 717 self.add_menu_action(self.magic_menu, self.who_ls_action)
718 718
719 719 self.whos_action = QtGui.QAction("Who&s",
720 720 self,
721 721 statusTip="List interactive variables with details",
722 722 triggered=self.whos_magic_active_frontend)
723 723 self.add_menu_action(self.magic_menu, self.whos_action)
724 724
725 725 def init_window_menu(self):
726 726 self.window_menu = self.menuBar().addMenu("&Window")
727 727 if sys.platform == 'darwin':
728 728 # add min/maximize actions to OSX, which lacks default bindings.
729 729 self.minimizeAct = QtGui.QAction("Mini&mize",
730 730 self,
731 731 shortcut="Ctrl+m",
732 732 statusTip="Minimize the window/Restore Normal Size",
733 733 triggered=self.toggleMinimized)
734 734 # maximize is called 'Zoom' on OSX for some reason
735 735 self.maximizeAct = QtGui.QAction("&Zoom",
736 736 self,
737 737 shortcut="Ctrl+Shift+M",
738 738 statusTip="Maximize the window/Restore Normal Size",
739 739 triggered=self.toggleMaximized)
740 740
741 741 self.add_menu_action(self.window_menu, self.minimizeAct)
742 742 self.add_menu_action(self.window_menu, self.maximizeAct)
743 743 self.window_menu.addSeparator()
744 744
745 745 prev_key = "Ctrl+Shift+Left" if sys.platform == 'darwin' else "Ctrl+PgUp"
746 746 self.prev_tab_act = QtGui.QAction("Pre&vious Tab",
747 747 self,
748 748 shortcut=prev_key,
749 749 statusTip="Select previous tab",
750 750 triggered=self.prev_tab)
751 751 self.add_menu_action(self.window_menu, self.prev_tab_act)
752 752
753 753 next_key = "Ctrl+Shift+Right" if sys.platform == 'darwin' else "Ctrl+PgDown"
754 754 self.next_tab_act = QtGui.QAction("Ne&xt Tab",
755 755 self,
756 756 shortcut=next_key,
757 757 statusTip="Select next tab",
758 758 triggered=self.next_tab)
759 759 self.add_menu_action(self.window_menu, self.next_tab_act)
760 760
761 761 def init_help_menu(self):
762 762 # please keep the Help menu in Mac Os even if empty. It will
763 763 # automatically contain a search field to search inside menus and
764 764 # please keep it spelled in English, as long as Qt Doesn't support
765 765 # a QAction.MenuRole like HelpMenuRole otherwise it will lose
766 766 # this search field functionality
767 767
768 768 self.help_menu = self.menuBar().addMenu("&Help")
769 769
770 770
771 771 # Help Menu
772 772
773 773 self.intro_active_frontend_action = QtGui.QAction("&Intro to IPython",
774 774 self,
775 775 triggered=self.intro_active_frontend
776 776 )
777 777 self.add_menu_action(self.help_menu, self.intro_active_frontend_action)
778 778
779 779 self.quickref_active_frontend_action = QtGui.QAction("IPython &Cheat Sheet",
780 780 self,
781 781 triggered=self.quickref_active_frontend
782 782 )
783 783 self.add_menu_action(self.help_menu, self.quickref_active_frontend_action)
784 784
785 785 self.guiref_active_frontend_action = QtGui.QAction("&Qt Console",
786 786 self,
787 787 triggered=self.guiref_active_frontend
788 788 )
789 789 self.add_menu_action(self.help_menu, self.guiref_active_frontend_action)
790 790
791 791 self.onlineHelpAct = QtGui.QAction("Open Online &Help",
792 792 self,
793 793 triggered=self._open_online_help)
794 794 self.add_menu_action(self.help_menu, self.onlineHelpAct)
795 795
796 796 # minimize/maximize/fullscreen actions:
797 797
798 798 def toggle_menu_bar(self):
799 799 menu_bar = self.menuBar()
800 800 if menu_bar.isVisible():
801 801 menu_bar.setVisible(False)
802 802 else:
803 803 menu_bar.setVisible(True)
804 804
805 805 def toggleMinimized(self):
806 806 if not self.isMinimized():
807 807 self.showMinimized()
808 808 else:
809 809 self.showNormal()
810 810
811 811 def _open_online_help(self):
812 812 filename="http://ipython.org/ipython-doc/stable/index.html"
813 813 webbrowser.open(filename, new=1, autoraise=True)
814 814
815 815 def toggleMaximized(self):
816 816 if not self.isMaximized():
817 817 self.showMaximized()
818 818 else:
819 819 self.showNormal()
820 820
821 821 # Min/Max imizing while in full screen give a bug
822 822 # when going out of full screen, at least on OSX
823 823 def toggleFullScreen(self):
824 824 if not self.isFullScreen():
825 825 self.showFullScreen()
826 826 if sys.platform == 'darwin':
827 827 self.maximizeAct.setEnabled(False)
828 828 self.minimizeAct.setEnabled(False)
829 829 else:
830 830 self.showNormal()
831 831 if sys.platform == 'darwin':
832 832 self.maximizeAct.setEnabled(True)
833 833 self.minimizeAct.setEnabled(True)
834 834
835 835 def close_active_frontend(self):
836 836 self.close_tab(self.active_frontend)
837 837
838 838 def restart_kernel_active_frontend(self):
839 839 self.active_frontend.request_restart_kernel()
840 840
841 841 def interrupt_kernel_active_frontend(self):
842 842 self.active_frontend.request_interrupt_kernel()
843 843
844 844 def toggle_confirm_restart_active_frontend(self):
845 845 widget = self.active_frontend
846 846 widget.confirm_restart = not widget.confirm_restart
847 847 # XXX: whenever tabs are switched, the checkbox may not be
848 848 # representative of the state of the active widget. The next line
849 849 # ensures that at least after toggling, the checkbox represents the
850 850 # state this widget is in.
851 851 self.confirm_restart_kernel_action.setChecked(widget.confirm_restart)
852 852
853 853
854 854 def cut_active_frontend(self):
855 855 widget = self.active_frontend
856 856 if widget.can_cut():
857 857 widget.cut()
858 858
859 859 def copy_active_frontend(self):
860 860 widget = self.active_frontend
861 861 widget.copy()
862 862
863 863 def copy_raw_active_frontend(self):
864 864 self.active_frontend._copy_raw_action.trigger()
865 865
866 866 def paste_active_frontend(self):
867 867 widget = self.active_frontend
868 868 if widget.can_paste():
869 869 widget.paste()
870 870
871 871 def undo_active_frontend(self):
872 872 self.active_frontend.undo()
873 873
874 874 def redo_active_frontend(self):
875 875 self.active_frontend.redo()
876 876
877 877 def reset_magic_active_frontend(self):
878 878 self.active_frontend.execute("%reset")
879 879
880 880 def history_magic_active_frontend(self):
881 881 self.active_frontend.execute("%history")
882 882
883 883 def save_magic_active_frontend(self):
884 884 self.active_frontend.save_magic()
885 885
886 886 def clear_magic_active_frontend(self):
887 887 self.active_frontend.execute("%clear")
888 888
889 889 def who_magic_active_frontend(self):
890 890 self.active_frontend.execute("%who")
891 891
892 892 def who_ls_magic_active_frontend(self):
893 893 self.active_frontend.execute("%who_ls")
894 894
895 895 def whos_magic_active_frontend(self):
896 896 self.active_frontend.execute("%whos")
897 897
898 898 def print_action_active_frontend(self):
899 899 self.active_frontend.print_action.trigger()
900 900
901 901 def export_action_active_frontend(self):
902 902 self.active_frontend.export_action.trigger()
903 903
904 904 def select_all_active_frontend(self):
905 905 self.active_frontend.select_all_action.trigger()
906 906
907 907 def increase_font_size_active_frontend(self):
908 908 self.active_frontend.increase_font_size.trigger()
909 909
910 910 def decrease_font_size_active_frontend(self):
911 911 self.active_frontend.decrease_font_size.trigger()
912 912
913 913 def reset_font_size_active_frontend(self):
914 914 self.active_frontend.reset_font_size.trigger()
915 915
916 916 def guiref_active_frontend(self):
917 917 self.active_frontend.execute("%guiref")
918 918
919 919 def intro_active_frontend(self):
920 920 self.active_frontend.execute("?")
921 921
922 922 def quickref_active_frontend(self):
923 923 self.active_frontend.execute("%quickref")
924 924 #---------------------------------------------------------------------------
925 925 # QWidget interface
926 926 #---------------------------------------------------------------------------
927 927
928 928 def closeEvent(self, event):
929 929 """ Forward the close event to every tabs contained by the windows
930 930 """
931 931 if self.tab_widget.count() == 0:
932 932 # no tabs, just close
933 933 event.accept()
934 934 return
935 935 # Do Not loop on the widget count as it change while closing
936 936 title = self.window().windowTitle()
937 937 cancel = QtGui.QMessageBox.Cancel
938 938 okay = QtGui.QMessageBox.Ok
939 939
940 940 if self.confirm_exit:
941 941 if self.tab_widget.count() > 1:
942 942 msg = "Close all tabs, stop all kernels, and Quit?"
943 943 else:
944 944 msg = "Close console, stop kernel, and Quit?"
945 945 info = "Kernels not started here (e.g. notebooks) will be left alone."
946 946 closeall = QtGui.QPushButton("&Quit", self)
947 947 closeall.setShortcut('Q')
948 948 box = QtGui.QMessageBox(QtGui.QMessageBox.Question,
949 949 title, msg)
950 950 box.setInformativeText(info)
951 951 box.addButton(cancel)
952 952 box.addButton(closeall, QtGui.QMessageBox.YesRole)
953 953 box.setDefaultButton(closeall)
954 954 box.setEscapeButton(cancel)
955 955 pixmap = QtGui.QPixmap(self._app.icon.pixmap(QtCore.QSize(64,64)))
956 956 box.setIconPixmap(pixmap)
957 957 reply = box.exec_()
958 958 else:
959 959 reply = okay
960 960
961 961 if reply == cancel:
962 962 event.ignore()
963 963 return
964 964 if reply == okay:
965 965 while self.tab_widget.count() >= 1:
966 966 # prevent further confirmations:
967 967 widget = self.active_frontend
968 968 widget._confirm_exit = False
969 969 self.close_tab(widget)
970 970 event.accept()
971 971
General Comments 0
You need to be logged in to leave comments. Login now