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