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