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