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