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