##// END OF EJS Templates
Remove "all magics" menu stuff.
Dimitry Kloper -
Show More
@@ -1,1151 +1,1034
1 1 """The Qt MainWindow for the QtConsole
2 2
3 3 This is a tabbed pseudo-terminal of IPython sessions, with a menu bar for
4 4 common actions.
5 5
6 6 Authors:
7 7
8 8 * Evan Patterson
9 9 * Min RK
10 10 * Erik Tollerud
11 11 * Fernando Perez
12 12 * Bussonnier Matthias
13 13 * Thomas Kluyver
14 14 * Paul Ivanov
15 15
16 16 """
17 17
18 18 #-----------------------------------------------------------------------------
19 19 # Imports
20 20 #-----------------------------------------------------------------------------
21 21
22 22 # stdlib imports
23 23 import json
24 24 import re
25 25 import sys
26 26 import webbrowser
27 27 from threading import Thread
28 28
29 29 # System library imports
30 30 from IPython.external.qt import QtGui,QtCore
31 31
32 32 from IPython.core.magic import magic_escapes
33 33
34 34 def background(f):
35 35 """call a function in a simple thread, to prevent blocking"""
36 36 t = Thread(target=f)
37 37 t.start()
38 38 return t
39 39
40 40 #-----------------------------------------------------------------------------
41 41 # Classes
42 42 #-----------------------------------------------------------------------------
43 43
44 44 class MainWindow(QtGui.QMainWindow):
45 45
46 46 #---------------------------------------------------------------------------
47 47 # 'object' interface
48 48 #---------------------------------------------------------------------------
49 49
50 _magic_menu_dict = {}
51
52 50 def __init__(self, app,
53 51 confirm_exit=True,
54 52 new_frontend_factory=None, slave_frontend_factory=None,
55 53 ):
56 54 """ Create a tabbed MainWindow for managing IPython FrontendWidgets
57 55
58 56 Parameters
59 57 ----------
60 58
61 59 app : reference to QApplication parent
62 60 confirm_exit : bool, optional
63 61 Whether we should prompt on close of tabs
64 62 new_frontend_factory : callable
65 63 A callable that returns a new IPythonWidget instance, attached to
66 64 its own running kernel.
67 65 slave_frontend_factory : callable
68 66 A callable that takes an existing IPythonWidget, and returns a new
69 67 IPythonWidget instance, attached to the same kernel.
70 68 """
71 69
72 70 super(MainWindow, self).__init__()
73 71 self._kernel_counter = 0
74 72 self._app = app
75 73 self.confirm_exit = confirm_exit
76 74 self.new_frontend_factory = new_frontend_factory
77 75 self.slave_frontend_factory = slave_frontend_factory
78 76
79 77 self.tab_widget = QtGui.QTabWidget(self)
80 78 self.tab_widget.setDocumentMode(True)
81 79 self.tab_widget.setTabsClosable(True)
82 80 self.tab_widget.tabCloseRequested[int].connect(self.close_tab)
83 81
84 82 self.setCentralWidget(self.tab_widget)
85 83 # hide tab bar at first, since we have no tabs:
86 84 self.tab_widget.tabBar().setVisible(False)
87 85 # prevent focus in tab bar
88 86 self.tab_widget.setFocusPolicy(QtCore.Qt.NoFocus)
89 87
90 88 def update_tab_bar_visibility(self):
91 89 """ update visibility of the tabBar depending of the number of tab
92 90
93 91 0 or 1 tab, tabBar hidden
94 92 2+ tabs, tabBar visible
95 93
96 94 send a self.close if number of tab ==0
97 95
98 96 need to be called explicitly, or be connected to tabInserted/tabRemoved
99 97 """
100 98 if self.tab_widget.count() <= 1:
101 99 self.tab_widget.tabBar().setVisible(False)
102 100 else:
103 101 self.tab_widget.tabBar().setVisible(True)
104 102 if self.tab_widget.count()==0 :
105 103 self.close()
106 104
107 105 @property
108 106 def next_kernel_id(self):
109 107 """constantly increasing counter for kernel IDs"""
110 108 c = self._kernel_counter
111 109 self._kernel_counter += 1
112 110 return c
113 111
114 112 @property
115 113 def active_frontend(self):
116 114 return self.tab_widget.currentWidget()
117 115
118 116 def create_tab_with_new_frontend(self):
119 117 """create a new frontend and attach it to a new tab"""
120 118 widget = self.new_frontend_factory()
121 119 self.add_tab_with_frontend(widget)
122 120
123 121 def create_tab_with_current_kernel(self):
124 122 """create a new frontend attached to the same kernel as the current tab"""
125 123 current_widget = self.tab_widget.currentWidget()
126 124 current_widget_index = self.tab_widget.indexOf(current_widget)
127 125 current_widget_name = self.tab_widget.tabText(current_widget_index)
128 126 widget = self.slave_frontend_factory(current_widget)
129 127 if 'slave' in current_widget_name:
130 128 # don't keep stacking slaves
131 129 name = current_widget_name
132 130 else:
133 131 name = '(%s) slave' % current_widget_name
134 132 self.add_tab_with_frontend(widget,name=name)
135 133
136 134 def close_tab(self,current_tab):
137 135 """ Called when you need to try to close a tab.
138 136
139 137 It takes the number of the tab to be closed as argument, or a reference
140 138 to the widget inside this tab
141 139 """
142 140
143 141 # let's be sure "tab" and "closing widget" are respectively the index
144 142 # of the tab to close and a reference to the frontend to close
145 143 if type(current_tab) is not int :
146 144 current_tab = self.tab_widget.indexOf(current_tab)
147 145 closing_widget=self.tab_widget.widget(current_tab)
148 146
149 147
150 148 # when trying to be closed, widget might re-send a request to be
151 149 # closed again, but will be deleted when event will be processed. So
152 150 # need to check that widget still exists and skip if not. One example
153 151 # of this is when 'exit' is sent in a slave tab. 'exit' will be
154 152 # re-sent by this function on the master widget, which ask all slave
155 153 # widgets to exit
156 154 if closing_widget==None:
157 155 return
158 156
159 157 #get a list of all slave widgets on the same kernel.
160 158 slave_tabs = self.find_slave_widgets(closing_widget)
161 159
162 160 keepkernel = None #Use the prompt by default
163 161 if hasattr(closing_widget,'_keep_kernel_on_exit'): #set by exit magic
164 162 keepkernel = closing_widget._keep_kernel_on_exit
165 163 # If signal sent by exit magic (_keep_kernel_on_exit, exist and not None)
166 164 # we set local slave tabs._hidden to True to avoid prompting for kernel
167 165 # restart when they get the signal. and then "forward" the 'exit'
168 166 # to the main window
169 167 if keepkernel is not None:
170 168 for tab in slave_tabs:
171 169 tab._hidden = True
172 170 if closing_widget in slave_tabs:
173 171 try :
174 172 self.find_master_tab(closing_widget).execute('exit')
175 173 except AttributeError:
176 174 self.log.info("Master already closed or not local, closing only current tab")
177 175 self.tab_widget.removeTab(current_tab)
178 176 self.update_tab_bar_visibility()
179 177 return
180 178
181 179 kernel_client = closing_widget.kernel_client
182 180 kernel_manager = closing_widget.kernel_manager
183 181
184 182 if keepkernel is None and not closing_widget._confirm_exit:
185 183 # don't prompt, just terminate the kernel if we own it
186 184 # or leave it alone if we don't
187 185 keepkernel = closing_widget._existing
188 186 if keepkernel is None: #show prompt
189 187 if kernel_client and kernel_client.channels_running:
190 188 title = self.window().windowTitle()
191 189 cancel = QtGui.QMessageBox.Cancel
192 190 okay = QtGui.QMessageBox.Ok
193 191 if closing_widget._may_close:
194 192 msg = "You are closing the tab : "+'"'+self.tab_widget.tabText(current_tab)+'"'
195 193 info = "Would you like to quit the Kernel and close all attached Consoles as well?"
196 194 justthis = QtGui.QPushButton("&No, just this Tab", self)
197 195 justthis.setShortcut('N')
198 196 closeall = QtGui.QPushButton("&Yes, close all", self)
199 197 closeall.setShortcut('Y')
200 198 # allow ctrl-d ctrl-d exit, like in terminal
201 199 closeall.setShortcut('Ctrl+D')
202 200 box = QtGui.QMessageBox(QtGui.QMessageBox.Question,
203 201 title, msg)
204 202 box.setInformativeText(info)
205 203 box.addButton(cancel)
206 204 box.addButton(justthis, QtGui.QMessageBox.NoRole)
207 205 box.addButton(closeall, QtGui.QMessageBox.YesRole)
208 206 box.setDefaultButton(closeall)
209 207 box.setEscapeButton(cancel)
210 208 pixmap = QtGui.QPixmap(self._app.icon.pixmap(QtCore.QSize(64,64)))
211 209 box.setIconPixmap(pixmap)
212 210 reply = box.exec_()
213 211 if reply == 1: # close All
214 212 for slave in slave_tabs:
215 213 background(slave.kernel_client.stop_channels)
216 214 self.tab_widget.removeTab(self.tab_widget.indexOf(slave))
217 215 closing_widget.execute("exit")
218 216 self.tab_widget.removeTab(current_tab)
219 217 background(kernel_client.stop_channels)
220 218 elif reply == 0: # close Console
221 219 if not closing_widget._existing:
222 220 # Have kernel: don't quit, just close the tab
223 221 closing_widget.execute("exit True")
224 222 self.tab_widget.removeTab(current_tab)
225 223 background(kernel_client.stop_channels)
226 224 else:
227 225 reply = QtGui.QMessageBox.question(self, title,
228 226 "Are you sure you want to close this Console?"+
229 227 "\nThe Kernel and other Consoles will remain active.",
230 228 okay|cancel,
231 229 defaultButton=okay
232 230 )
233 231 if reply == okay:
234 232 self.tab_widget.removeTab(current_tab)
235 233 elif keepkernel: #close console but leave kernel running (no prompt)
236 234 self.tab_widget.removeTab(current_tab)
237 235 background(kernel_client.stop_channels)
238 236 else: #close console and kernel (no prompt)
239 237 self.tab_widget.removeTab(current_tab)
240 238 if kernel_client and kernel_client.channels_running:
241 239 for slave in slave_tabs:
242 240 background(slave.kernel_client.stop_channels)
243 241 self.tab_widget.removeTab(self.tab_widget.indexOf(slave))
244 242 if kernel_manager:
245 243 kernel_manager.shutdown_kernel()
246 244 background(kernel_client.stop_channels)
247 245
248 246 self.update_tab_bar_visibility()
249 247
250 248 def add_tab_with_frontend(self,frontend,name=None):
251 249 """ insert a tab with a given frontend in the tab bar, and give it a name
252 250
253 251 """
254 252 if not name:
255 253 name = 'kernel %i' % self.next_kernel_id
256 254 self.tab_widget.addTab(frontend,name)
257 255 self.update_tab_bar_visibility()
258 256 self.make_frontend_visible(frontend)
259 257 frontend.exit_requested.connect(self.close_tab)
260 258
261 259 def next_tab(self):
262 260 self.tab_widget.setCurrentIndex((self.tab_widget.currentIndex()+1))
263 261
264 262 def prev_tab(self):
265 263 self.tab_widget.setCurrentIndex((self.tab_widget.currentIndex()-1))
266 264
267 265 def make_frontend_visible(self,frontend):
268 266 widget_index=self.tab_widget.indexOf(frontend)
269 267 if widget_index > 0 :
270 268 self.tab_widget.setCurrentIndex(widget_index)
271 269
272 270 def find_master_tab(self,tab,as_list=False):
273 271 """
274 272 Try to return the frontend that owns the kernel attached to the given widget/tab.
275 273
276 274 Only finds frontend owned by the current application. Selection
277 275 based on port of the kernel might be inaccurate if several kernel
278 276 on different ip use same port number.
279 277
280 278 This function does the conversion tabNumber/widget if needed.
281 279 Might return None if no master widget (non local kernel)
282 280 Will crash IPython if more than 1 masterWidget
283 281
284 282 When asList set to True, always return a list of widget(s) owning
285 283 the kernel. The list might be empty or containing several Widget.
286 284 """
287 285
288 286 #convert from/to int/richIpythonWidget if needed
289 287 if isinstance(tab, int):
290 288 tab = self.tab_widget.widget(tab)
291 289 km=tab.kernel_client
292 290
293 291 #build list of all widgets
294 292 widget_list = [self.tab_widget.widget(i) for i in range(self.tab_widget.count())]
295 293
296 294 # widget that are candidate to be the owner of the kernel does have all the same port of the curent widget
297 295 # And should have a _may_close attribute
298 296 filtered_widget_list = [ widget for widget in widget_list if
299 297 widget.kernel_client.connection_file == km.connection_file and
300 298 hasattr(widget,'_may_close') ]
301 299 # the master widget is the one that may close the kernel
302 300 master_widget= [ widget for widget in filtered_widget_list if widget._may_close]
303 301 if as_list:
304 302 return master_widget
305 303 assert(len(master_widget)<=1 )
306 304 if len(master_widget)==0:
307 305 return None
308 306
309 307 return master_widget[0]
310 308
311 309 def find_slave_widgets(self,tab):
312 310 """return all the frontends that do not own the kernel attached to the given widget/tab.
313 311
314 312 Only find frontends owned by the current application. Selection
315 313 based on connection file of the kernel.
316 314
317 315 This function does the conversion tabNumber/widget if needed.
318 316 """
319 317 #convert from/to int/richIpythonWidget if needed
320 318 if isinstance(tab, int):
321 319 tab = self.tab_widget.widget(tab)
322 320 km=tab.kernel_client
323 321
324 322 #build list of all widgets
325 323 widget_list = [self.tab_widget.widget(i) for i in range(self.tab_widget.count())]
326 324
327 325 # widget that are candidate not to be the owner of the kernel does have all the same port of the curent widget
328 326 filtered_widget_list = ( widget for widget in widget_list if
329 327 widget.kernel_client.connection_file == km.connection_file)
330 328 # Get a list of all widget owning the same kernel and removed it from
331 329 # the previous cadidate. (better using sets ?)
332 330 master_widget_list = self.find_master_tab(tab, as_list=True)
333 331 slave_list = [widget for widget in filtered_widget_list if widget not in master_widget_list]
334 332
335 333 return slave_list
336 334
337 335 # Populate the menu bar with common actions and shortcuts
338 336 def add_menu_action(self, menu, action, defer_shortcut=False):
339 337 """Add action to menu as well as self
340 338
341 339 So that when the menu bar is invisible, its actions are still available.
342 340
343 341 If defer_shortcut is True, set the shortcut context to widget-only,
344 342 where it will avoid conflict with shortcuts already bound to the
345 343 widgets themselves.
346 344 """
347 345 menu.addAction(action)
348 346 self.addAction(action)
349 347
350 348 if defer_shortcut:
351 349 action.setShortcutContext(QtCore.Qt.WidgetShortcut)
352 350
353 351 def init_menu_bar(self):
354 352 #create menu in the order they should appear in the menu bar
355 353 self.init_file_menu()
356 354 self.init_edit_menu()
357 355 self.init_view_menu()
358 356 self.init_kernel_menu()
359 357 self.init_magic_menu()
360 358 self.init_window_menu()
361 359 self.init_help_menu()
362 360
363 361 def init_file_menu(self):
364 362 self.file_menu = self.menuBar().addMenu("&File")
365 363
366 364 self.new_kernel_tab_act = QtGui.QAction("New Tab with &New kernel",
367 365 self,
368 366 shortcut="Ctrl+T",
369 367 triggered=self.create_tab_with_new_frontend)
370 368 self.add_menu_action(self.file_menu, self.new_kernel_tab_act)
371 369
372 370 self.slave_kernel_tab_act = QtGui.QAction("New Tab with Sa&me kernel",
373 371 self,
374 372 shortcut="Ctrl+Shift+T",
375 373 triggered=self.create_tab_with_current_kernel)
376 374 self.add_menu_action(self.file_menu, self.slave_kernel_tab_act)
377 375
378 376 self.file_menu.addSeparator()
379 377
380 378 self.close_action=QtGui.QAction("&Close Tab",
381 379 self,
382 380 shortcut=QtGui.QKeySequence.Close,
383 381 triggered=self.close_active_frontend
384 382 )
385 383 self.add_menu_action(self.file_menu, self.close_action)
386 384
387 385 self.export_action=QtGui.QAction("&Save to HTML/XHTML",
388 386 self,
389 387 shortcut=QtGui.QKeySequence.Save,
390 388 triggered=self.export_action_active_frontend
391 389 )
392 390 self.add_menu_action(self.file_menu, self.export_action, True)
393 391
394 392 self.file_menu.addSeparator()
395 393
396 394 printkey = QtGui.QKeySequence(QtGui.QKeySequence.Print)
397 395 if printkey.matches("Ctrl+P") and sys.platform != 'darwin':
398 396 # Only override the default if there is a collision.
399 397 # Qt ctrl = cmd on OSX, so the match gets a false positive on OSX.
400 398 printkey = "Ctrl+Shift+P"
401 399 self.print_action = QtGui.QAction("&Print",
402 400 self,
403 401 shortcut=printkey,
404 402 triggered=self.print_action_active_frontend)
405 403 self.add_menu_action(self.file_menu, self.print_action, True)
406 404
407 405 if sys.platform != 'darwin':
408 406 # OSX always has Quit in the Application menu, only add it
409 407 # to the File menu elsewhere.
410 408
411 409 self.file_menu.addSeparator()
412 410
413 411 self.quit_action = QtGui.QAction("&Quit",
414 412 self,
415 413 shortcut=QtGui.QKeySequence.Quit,
416 414 triggered=self.close,
417 415 )
418 416 self.add_menu_action(self.file_menu, self.quit_action)
419 417
420 418
421 419 def init_edit_menu(self):
422 420 self.edit_menu = self.menuBar().addMenu("&Edit")
423 421
424 422 self.undo_action = QtGui.QAction("&Undo",
425 423 self,
426 424 shortcut=QtGui.QKeySequence.Undo,
427 425 statusTip="Undo last action if possible",
428 426 triggered=self.undo_active_frontend
429 427 )
430 428 self.add_menu_action(self.edit_menu, self.undo_action)
431 429
432 430 self.redo_action = QtGui.QAction("&Redo",
433 431 self,
434 432 shortcut=QtGui.QKeySequence.Redo,
435 433 statusTip="Redo last action if possible",
436 434 triggered=self.redo_active_frontend)
437 435 self.add_menu_action(self.edit_menu, self.redo_action)
438 436
439 437 self.edit_menu.addSeparator()
440 438
441 439 self.cut_action = QtGui.QAction("&Cut",
442 440 self,
443 441 shortcut=QtGui.QKeySequence.Cut,
444 442 triggered=self.cut_active_frontend
445 443 )
446 444 self.add_menu_action(self.edit_menu, self.cut_action, True)
447 445
448 446 self.copy_action = QtGui.QAction("&Copy",
449 447 self,
450 448 shortcut=QtGui.QKeySequence.Copy,
451 449 triggered=self.copy_active_frontend
452 450 )
453 451 self.add_menu_action(self.edit_menu, self.copy_action, True)
454 452
455 453 self.copy_raw_action = QtGui.QAction("Copy (&Raw Text)",
456 454 self,
457 455 shortcut="Ctrl+Shift+C",
458 456 triggered=self.copy_raw_active_frontend
459 457 )
460 458 self.add_menu_action(self.edit_menu, self.copy_raw_action, True)
461 459
462 460 self.paste_action = QtGui.QAction("&Paste",
463 461 self,
464 462 shortcut=QtGui.QKeySequence.Paste,
465 463 triggered=self.paste_active_frontend
466 464 )
467 465 self.add_menu_action(self.edit_menu, self.paste_action, True)
468 466
469 467 self.edit_menu.addSeparator()
470 468
471 469 selectall = QtGui.QKeySequence(QtGui.QKeySequence.SelectAll)
472 470 if selectall.matches("Ctrl+A") and sys.platform != 'darwin':
473 471 # Only override the default if there is a collision.
474 472 # Qt ctrl = cmd on OSX, so the match gets a false positive on OSX.
475 473 selectall = "Ctrl+Shift+A"
476 474 self.select_all_action = QtGui.QAction("Select &All",
477 475 self,
478 476 shortcut=selectall,
479 477 triggered=self.select_all_active_frontend
480 478 )
481 479 self.add_menu_action(self.edit_menu, self.select_all_action, True)
482 480
483 481
484 482 def init_view_menu(self):
485 483 self.view_menu = self.menuBar().addMenu("&View")
486 484
487 485 if sys.platform != 'darwin':
488 486 # disable on OSX, where there is always a menu bar
489 487 self.toggle_menu_bar_act = QtGui.QAction("Toggle &Menu Bar",
490 488 self,
491 489 shortcut="Ctrl+Shift+M",
492 490 statusTip="Toggle visibility of menubar",
493 491 triggered=self.toggle_menu_bar)
494 492 self.add_menu_action(self.view_menu, self.toggle_menu_bar_act)
495 493
496 494 fs_key = "Ctrl+Meta+F" if sys.platform == 'darwin' else "F11"
497 495 self.full_screen_act = QtGui.QAction("&Full Screen",
498 496 self,
499 497 shortcut=fs_key,
500 498 statusTip="Toggle between Fullscreen and Normal Size",
501 499 triggered=self.toggleFullScreen)
502 500 self.add_menu_action(self.view_menu, self.full_screen_act)
503 501
504 502 self.view_menu.addSeparator()
505 503
506 504 self.increase_font_size = QtGui.QAction("Zoom &In",
507 505 self,
508 506 shortcut=QtGui.QKeySequence.ZoomIn,
509 507 triggered=self.increase_font_size_active_frontend
510 508 )
511 509 self.add_menu_action(self.view_menu, self.increase_font_size, True)
512 510
513 511 self.decrease_font_size = QtGui.QAction("Zoom &Out",
514 512 self,
515 513 shortcut=QtGui.QKeySequence.ZoomOut,
516 514 triggered=self.decrease_font_size_active_frontend
517 515 )
518 516 self.add_menu_action(self.view_menu, self.decrease_font_size, True)
519 517
520 518 self.reset_font_size = QtGui.QAction("Zoom &Reset",
521 519 self,
522 520 shortcut="Ctrl+0",
523 521 triggered=self.reset_font_size_active_frontend
524 522 )
525 523 self.add_menu_action(self.view_menu, self.reset_font_size, True)
526 524
527 525 self.view_menu.addSeparator()
528 526
529 527 self.clear_action = QtGui.QAction("&Clear Screen",
530 528 self,
531 529 shortcut='Ctrl+L',
532 530 statusTip="Clear the console",
533 531 triggered=self.clear_magic_active_frontend)
534 532 self.add_menu_action(self.view_menu, self.clear_action)
535 533
536 534 self.pager_menu = self.view_menu.addMenu("&Pager")
537 535
538 536 hsplit_action = QtGui.QAction(".. &Horizontal Split",
539 537 self,
540 538 triggered=lambda: self.set_paging_active_frontend('hsplit'))
541 539
542 540 vsplit_action = QtGui.QAction(" : &Vertical Split",
543 541 self,
544 542 triggered=lambda: self.set_paging_active_frontend('vsplit'))
545 543
546 544 inside_action = QtGui.QAction(" &Inside Pager",
547 545 self,
548 546 triggered=lambda: self.set_paging_active_frontend('inside'))
549 547
550 548 self.pager_menu.addAction(hsplit_action)
551 549 self.pager_menu.addAction(vsplit_action)
552 550 self.pager_menu.addAction(inside_action)
553 551
554 552 def init_kernel_menu(self):
555 553 self.kernel_menu = self.menuBar().addMenu("&Kernel")
556 554 # Qt on OSX maps Ctrl to Cmd, and Meta to Ctrl
557 555 # keep the signal shortcuts to ctrl, rather than
558 556 # platform-default like we do elsewhere.
559 557
560 558 ctrl = "Meta" if sys.platform == 'darwin' else "Ctrl"
561 559
562 560 self.interrupt_kernel_action = QtGui.QAction("&Interrupt current Kernel",
563 561 self,
564 562 triggered=self.interrupt_kernel_active_frontend,
565 563 shortcut=ctrl+"+C",
566 564 )
567 565 self.add_menu_action(self.kernel_menu, self.interrupt_kernel_action)
568 566
569 567 self.restart_kernel_action = QtGui.QAction("&Restart current Kernel",
570 568 self,
571 569 triggered=self.restart_kernel_active_frontend,
572 570 shortcut=ctrl+"+.",
573 571 )
574 572 self.add_menu_action(self.kernel_menu, self.restart_kernel_action)
575 573
576 574 self.kernel_menu.addSeparator()
577 575
578 576 self.confirm_restart_kernel_action = QtGui.QAction("&Confirm kernel restart",
579 577 self,
580 578 checkable=True,
581 579 checked=self.active_frontend.confirm_restart,
582 580 triggered=self.toggle_confirm_restart_active_frontend
583 581 )
584 582
585 583 self.add_menu_action(self.kernel_menu, self.confirm_restart_kernel_action)
586 584 self.tab_widget.currentChanged.connect(self.update_restart_checkbox)
587 585
588 def _make_dynamic_magic(self,magic):
589 """Return a function `fun` that will execute `magic` on active frontend.
590
591 Parameters
592 ----------
593 magic : string
594 string that will be executed as is when the returned function is called
595
596 Returns
597 -------
598 fun : function
599 function with no parameters, when called will execute `magic` on the
600 current active frontend at call time
601
602 See Also
603 --------
604 populate_all_magic_menu : generate the "All Magics..." menu
605
606 Notes
607 -----
608 `fun` executes `magic` in active frontend at the moment it is triggered,
609 not the active frontend at the moment it was created.
610
611 This function is mostly used to create the "All Magics..." Menu at run time.
612 """
613 # need two level nested function to be sure to pass magic
614 # to active frontend **at run time**.
615 def inner_dynamic_magic():
616 self.active_frontend.execute(magic)
617 inner_dynamic_magic.__name__ = "dynamics_magic_s"
618 return inner_dynamic_magic
619
620 def populate_all_magic_menu(self, display_data=None):
621 """Clean "All Magics..." menu and repopulate it with `display_data`
622
623 Parameters
624 ----------
625 display_data : dict,
626 dict of display_data for the magics dict of a MagicsManager.
627 Expects json data, as the result of %lsmagic
628
629 """
630 for k,v in self._magic_menu_dict.items():
631 v.clear()
632 self.all_magic_menu.clear()
633
634 if not display_data:
635 return
636
637 if display_data['status'] != 'ok':
638 self.log.warn("%%lsmagic user-expression failed: %s" % display_data)
639 return
640
641 mdict = json.loads(display_data['data'].get('application/json', {}))
642
643 for mtype in sorted(mdict):
644 subdict = mdict[mtype]
645 prefix = magic_escapes[mtype]
646 for name in sorted(subdict):
647 mclass = subdict[name]
648 magic_menu = self._get_magic_menu(mclass)
649 pmagic = prefix + name
650
651 # Adding seperate QActions is needed for some window managers
652 xaction = QtGui.QAction(pmagic,
653 self,
654 triggered=self._make_dynamic_magic(pmagic)
655 )
656 magic_menu.addAction(xaction)
657 self.all_magic_menu.addAction(xaction)
658
659 def update_all_magic_menu(self):
660 """ Update the list of magics in the "All Magics..." Menu
661
662 Request the kernel with the list of available magics and populate the
663 menu with the list received back
664
665 """
666 self.active_frontend._silent_exec_callback('get_ipython().magic("lsmagic")',
667 self.populate_all_magic_menu)
668
669 def _get_magic_menu(self,menuidentifier, menulabel=None):
670 """return a submagic menu by name, and create it if needed
671
672 Parameters
673 ----------
674
675 menulabel : str
676 Label for the menu
677
678 Will infere the menu name from the identifier at creation if menulabel not given.
679 To do so you have too give menuidentifier as a CamelCassedString
680 """
681 menu = self._magic_menu_dict.get(menuidentifier,None)
682 if not menu :
683 if not menulabel:
684 menulabel = re.sub("([a-zA-Z]+)([A-Z][a-z])","\g<1> \g<2>",menuidentifier)
685 menu = QtGui.QMenu(menulabel,self.magic_menu)
686 self._magic_menu_dict[menuidentifier]=menu
687 self.magic_menu.insertMenu(self.magic_menu_separator,menu)
688 return menu
689
690
691
692 586 def init_magic_menu(self):
693 587 self.magic_menu = self.menuBar().addMenu("&Magic")
694 self.magic_menu_separator = self.magic_menu.addSeparator()
695 588
696 self.all_magic_menu = self._get_magic_menu("AllMagics", menulabel="&All Magics...")
589 self.add_menu_action(self.magic_menu,
590 self.magic_helper.toggleViewAction())
697 591
698 # This action should usually not appear as it will be cleared when menu
699 # is updated at first kernel response. Though, it is necessary when
700 # connecting through X-forwarding, as in this case, the menu is not
701 # auto updated, SO DO NOT DELETE.
702 self.pop = QtGui.QAction("&Update All Magic Menu ",
703 self, triggered=self.update_all_magic_menu)
704 self.add_menu_action(self.all_magic_menu, self.pop)
705 # we need to populate the 'Magic Menu' once the kernel has answer at
706 # least once let's do it immediately, but it's assured to works
707 self.pop.trigger()
592 self.magic_menu_separator = self.magic_menu.addSeparator()
708 593
709 594 self.reset_action = QtGui.QAction("&Reset",
710 595 self,
711 596 statusTip="Clear all variables from workspace",
712 597 triggered=self.reset_magic_active_frontend)
713 598 self.add_menu_action(self.magic_menu, self.reset_action)
714 599
715 600 self.history_action = QtGui.QAction("&History",
716 601 self,
717 602 statusTip="show command history",
718 603 triggered=self.history_magic_active_frontend)
719 604 self.add_menu_action(self.magic_menu, self.history_action)
720 605
721 606 self.save_action = QtGui.QAction("E&xport History ",
722 607 self,
723 608 statusTip="Export History as Python File",
724 609 triggered=self.save_magic_active_frontend)
725 610 self.add_menu_action(self.magic_menu, self.save_action)
726 611
727 612 self.who_action = QtGui.QAction("&Who",
728 613 self,
729 614 statusTip="List interactive variables",
730 615 triggered=self.who_magic_active_frontend)
731 616 self.add_menu_action(self.magic_menu, self.who_action)
732 617
733 618 self.who_ls_action = QtGui.QAction("Wh&o ls",
734 619 self,
735 620 statusTip="Return a list of interactive variables",
736 621 triggered=self.who_ls_magic_active_frontend)
737 622 self.add_menu_action(self.magic_menu, self.who_ls_action)
738 623
739 624 self.whos_action = QtGui.QAction("Who&s",
740 625 self,
741 626 statusTip="List interactive variables with details",
742 627 triggered=self.whos_magic_active_frontend)
743 628 self.add_menu_action(self.magic_menu, self.whos_action)
744 629
745 630 def init_window_menu(self):
746 631 self.window_menu = self.menuBar().addMenu("&Window")
747 632 if sys.platform == 'darwin':
748 633 # add min/maximize actions to OSX, which lacks default bindings.
749 634 self.minimizeAct = QtGui.QAction("Mini&mize",
750 635 self,
751 636 shortcut="Ctrl+m",
752 637 statusTip="Minimize the window/Restore Normal Size",
753 638 triggered=self.toggleMinimized)
754 639 # maximize is called 'Zoom' on OSX for some reason
755 640 self.maximizeAct = QtGui.QAction("&Zoom",
756 641 self,
757 642 shortcut="Ctrl+Shift+M",
758 643 statusTip="Maximize the window/Restore Normal Size",
759 644 triggered=self.toggleMaximized)
760 645
761 646 self.add_menu_action(self.window_menu, self.minimizeAct)
762 647 self.add_menu_action(self.window_menu, self.maximizeAct)
763 648 self.window_menu.addSeparator()
764 649
765 650 prev_key = "Ctrl+Shift+Left" if sys.platform == 'darwin' else "Ctrl+PgUp"
766 651 self.prev_tab_act = QtGui.QAction("Pre&vious Tab",
767 652 self,
768 653 shortcut=prev_key,
769 654 statusTip="Select previous tab",
770 655 triggered=self.prev_tab)
771 656 self.add_menu_action(self.window_menu, self.prev_tab_act)
772 657
773 658 next_key = "Ctrl+Shift+Right" if sys.platform == 'darwin' else "Ctrl+PgDown"
774 659 self.next_tab_act = QtGui.QAction("Ne&xt Tab",
775 660 self,
776 661 shortcut=next_key,
777 662 statusTip="Select next tab",
778 663 triggered=self.next_tab)
779 664 self.add_menu_action(self.window_menu, self.next_tab_act)
780 665
781 666 def init_help_menu(self):
782 667 # please keep the Help menu in Mac Os even if empty. It will
783 668 # automatically contain a search field to search inside menus and
784 669 # please keep it spelled in English, as long as Qt Doesn't support
785 670 # a QAction.MenuRole like HelpMenuRole otherwise it will lose
786 671 # this search field functionality
787 672
788 673 self.help_menu = self.menuBar().addMenu("&Help")
789 674
790 675
791 676 # Help Menu
792 677
793 678 self.intro_active_frontend_action = QtGui.QAction("&Intro to IPython",
794 679 self,
795 680 triggered=self.intro_active_frontend
796 681 )
797 682 self.add_menu_action(self.help_menu, self.intro_active_frontend_action)
798 683
799 684 self.quickref_active_frontend_action = QtGui.QAction("IPython &Cheat Sheet",
800 685 self,
801 686 triggered=self.quickref_active_frontend
802 687 )
803 688 self.add_menu_action(self.help_menu, self.quickref_active_frontend_action)
804 689
805 690 self.guiref_active_frontend_action = QtGui.QAction("&Qt Console",
806 691 self,
807 692 triggered=self.guiref_active_frontend
808 693 )
809 694 self.add_menu_action(self.help_menu, self.guiref_active_frontend_action)
810 695
811 696 self.onlineHelpAct = QtGui.QAction("Open Online &Help",
812 697 self,
813 698 triggered=self._open_online_help)
814 699 self.add_menu_action(self.help_menu, self.onlineHelpAct)
815 700
816 701 def init_magic_helper(self):
817 702 self.magic_helper_data = None
818 self.magic_helper = QtGui.QDockWidget("Magics", self)
703 self.magic_helper = QtGui.QDockWidget("Show Magics", self)
819 704 self.magic_helper.setAllowedAreas(QtCore.Qt.LeftDockWidgetArea |
820 705 QtCore.Qt.RightDockWidgetArea)
821 706 self.magic_helper.setVisible(False)
822 707
823 708 class MinListWidget(QtGui.QListWidget):
824 709 def sizeHint(self):
825 710 s = QtCore.QSize()
826 711 s.setHeight(super(MinListWidget,self).sizeHint().height())
827 712 s.setWidth(self.sizeHintForColumn(0))
828 713 return s
829 714
830 715 self.magic_helper_frame = QtGui.QFrame()
831 716 self.magic_helper_searchl = QtGui.QLabel("Search:")
832 717 self.magic_helper_search = QtGui.QLineEdit()
833 718 self.magic_helper_class = QtGui.QComboBox()
834 719 self.magic_helper_list = MinListWidget()
835 720 self.magic_helper_paste = QtGui.QPushButton("Paste")
836 721 self.magic_helper_run = QtGui.QPushButton("Run")
837 722
838 723 main_layout = QtGui.QVBoxLayout()
839 724 search_layout = QtGui.QHBoxLayout()
840 725 search_layout.addWidget(self.magic_helper_searchl)
841 726 search_layout.addWidget(self.magic_helper_search, 10)
842 727 main_layout.addLayout(search_layout)
843 728 main_layout.addWidget(self.magic_helper_class)
844 729 main_layout.addWidget(self.magic_helper_list, 10)
845 730 action_layout = QtGui.QHBoxLayout()
846 731 action_layout.addWidget(self.magic_helper_paste)
847 732 action_layout.addWidget(self.magic_helper_run)
848 733 main_layout.addLayout(action_layout)
849 734
850 735 self.magic_helper_frame.setLayout(main_layout)
851 736 self.magic_helper.setWidget(self.magic_helper_frame)
852 737
853 738 self.magic_helper.visibilityChanged[bool].connect(
854 739 self.update_magic_helper
855 740 )
856 741 self.magic_helper_class.activated[int].connect(
857 742 self.magic_helper_class_selected
858 743 )
859 744 self.magic_helper_search.textChanged[str].connect(
860 745 self.magic_helper_search_changed
861 746 )
862 747 self.magic_helper_list.itemDoubleClicked[QtGui.QListWidgetItem].connect(
863 748 self.magic_helper_paste_requested
864 749 )
865 750 self.magic_helper_paste.clicked[bool].connect(
866 751 self.magic_helper_paste_requested
867 752 )
868 753 self.magic_helper_run.clicked[bool].connect(
869 754 self.magic_helper_run_requested
870 755 )
871 756
872 757 self.addDockWidget(QtCore.Qt.RightDockWidgetArea, self.magic_helper)
873 self.add_menu_action(self.magic_menu,
874 self.magic_helper.toggleViewAction())
875 758
876 759 def update_magic_helper(self, visible):
877 760 if not visible or self.magic_helper_data != None:
878 761 return
879 762 self.magic_helper_data = {}
880 763 self.magic_helper_class.clear()
881 764 self.magic_helper_class.addItem("Populating...")
882 765 self.active_frontend._silent_exec_callback(
883 766 'get_ipython().magic("lsmagic")',
884 767 self.populate_magic_helper
885 768 )
886 769
887 770 def populate_magic_helper(self, data):
888 771 if not data:
889 772 return
890 773
891 774 if data['status'] != 'ok':
892 775 self.log.warn("%%lsmagic user-expression failed: {}".format(data))
893 776 return
894 777
895 778 self.magic_helper_class.clear()
896 779 self.magic_helper_list.clear()
897 780
898 781 self.magic_helper_data = json.loads(
899 782 data['data'].get('application/json', {})
900 783 )
901 784
902 785 self.magic_helper_class.addItem('All Magics', 'any')
903 786 classes = set()
904 787
905 788 for mtype in sorted(self.magic_helper_data):
906 789 subdict = self.magic_helper_data[mtype]
907 790 for name in sorted(subdict):
908 791 classes.add(subdict[name])
909 792
910 793 for cls in sorted(classes):
911 794 label = re.sub("([a-zA-Z]+)([A-Z][a-z])","\g<1> \g<2>", cls)
912 795 self.magic_helper_class.addItem(label, cls)
913 796
914 797 self.filter_magic_helper('.', 'any')
915 798
916 799 def magic_helper_class_selected(self, index):
917 800 item = self.magic_helper_class.itemData(index)
918 801 regex = self.magic_helper_search.text()
919 802 self.filter_magic_helper(regex = regex, cls = item)
920 803
921 804 def magic_helper_search_changed(self, search_string):
922 805 item = self.magic_helper_class.itemData(
923 806 self.magic_helper_class.currentIndex()
924 807 )
925 808 self.filter_magic_helper(regex = search_string, cls = item)
926 809
927 810 def _magic_helper_get_current(self, item = None):
928 811 text = None
929 812 if not isinstance(item, QtGui.QListWidgetItem):
930 813 item = self.magic_helper_list.currentItem()
931 814 text = item.text()
932 815 return text
933 816
934 817 def _set_active_frontend_focus(self):
935 818 # this is a hack, self.active_frontend._control seems to be
936 819 # a private member. Unfortunately this is the only method
937 820 # to set focus reliably
938 821 QtCore.QTimer.singleShot(200, self.active_frontend._control.setFocus)
939 822
940 823 def magic_helper_paste_requested(self, item = None):
941 824 text = self._magic_helper_get_current(item)
942 825 if text != None:
943 826 self.active_frontend.input_buffer = text
944 827 self._set_active_frontend_focus()
945 828
946 829 def magic_helper_run_requested(self, item = None):
947 830 text = self._magic_helper_get_current(item)
948 831 if text != None:
949 832 self.active_frontend.execute(text)
950 833 self._set_active_frontend_focus()
951 834
952 835 def filter_magic_helper(self, regex, cls):
953 836 if regex == "" or regex == None:
954 837 regex = '.'
955 838 if cls == None:
956 839 cls = 'any'
957 840
958 841 self.magic_helper_list.clear()
959 842 for mtype in sorted(self.magic_helper_data):
960 843 subdict = self.magic_helper_data[mtype]
961 844 prefix = magic_escapes[mtype]
962 845
963 846 for name in sorted(subdict):
964 847 mclass = subdict[name]
965 848 pmagic = prefix + name
966 849
967 850 if (re.match(regex, name) or re.match(regex, pmagic)) and \
968 851 (cls == 'any' or cls == mclass):
969 852 self.magic_helper_list.addItem(pmagic)
970 853
971 854
972 855 # minimize/maximize/fullscreen actions:
973 856
974 857 def toggle_menu_bar(self):
975 858 menu_bar = self.menuBar()
976 859 if menu_bar.isVisible():
977 860 menu_bar.setVisible(False)
978 861 else:
979 862 menu_bar.setVisible(True)
980 863
981 864 def toggleMinimized(self):
982 865 if not self.isMinimized():
983 866 self.showMinimized()
984 867 else:
985 868 self.showNormal()
986 869
987 870 def _open_online_help(self):
988 871 filename="http://ipython.org/ipython-doc/stable/index.html"
989 872 webbrowser.open(filename, new=1, autoraise=True)
990 873
991 874 def toggleMaximized(self):
992 875 if not self.isMaximized():
993 876 self.showMaximized()
994 877 else:
995 878 self.showNormal()
996 879
997 880 # Min/Max imizing while in full screen give a bug
998 881 # when going out of full screen, at least on OSX
999 882 def toggleFullScreen(self):
1000 883 if not self.isFullScreen():
1001 884 self.showFullScreen()
1002 885 if sys.platform == 'darwin':
1003 886 self.maximizeAct.setEnabled(False)
1004 887 self.minimizeAct.setEnabled(False)
1005 888 else:
1006 889 self.showNormal()
1007 890 if sys.platform == 'darwin':
1008 891 self.maximizeAct.setEnabled(True)
1009 892 self.minimizeAct.setEnabled(True)
1010 893
1011 894 def set_paging_active_frontend(self, paging):
1012 895 self.active_frontend._set_paging(paging)
1013 896
1014 897 def close_active_frontend(self):
1015 898 self.close_tab(self.active_frontend)
1016 899
1017 900 def restart_kernel_active_frontend(self):
1018 901 self.active_frontend.request_restart_kernel()
1019 902
1020 903 def interrupt_kernel_active_frontend(self):
1021 904 self.active_frontend.request_interrupt_kernel()
1022 905
1023 906 def toggle_confirm_restart_active_frontend(self):
1024 907 widget = self.active_frontend
1025 908 widget.confirm_restart = not widget.confirm_restart
1026 909 self.confirm_restart_kernel_action.setChecked(widget.confirm_restart)
1027 910
1028 911 def update_restart_checkbox(self):
1029 912 if self.active_frontend is None:
1030 913 return
1031 914 widget = self.active_frontend
1032 915 self.confirm_restart_kernel_action.setChecked(widget.confirm_restart)
1033 916
1034 917 def cut_active_frontend(self):
1035 918 widget = self.active_frontend
1036 919 if widget.can_cut():
1037 920 widget.cut()
1038 921
1039 922 def copy_active_frontend(self):
1040 923 widget = self.active_frontend
1041 924 widget.copy()
1042 925
1043 926 def copy_raw_active_frontend(self):
1044 927 self.active_frontend._copy_raw_action.trigger()
1045 928
1046 929 def paste_active_frontend(self):
1047 930 widget = self.active_frontend
1048 931 if widget.can_paste():
1049 932 widget.paste()
1050 933
1051 934 def undo_active_frontend(self):
1052 935 self.active_frontend.undo()
1053 936
1054 937 def redo_active_frontend(self):
1055 938 self.active_frontend.redo()
1056 939
1057 940 def reset_magic_active_frontend(self):
1058 941 self.active_frontend.execute("%reset")
1059 942
1060 943 def history_magic_active_frontend(self):
1061 944 self.active_frontend.execute("%history")
1062 945
1063 946 def save_magic_active_frontend(self):
1064 947 self.active_frontend.save_magic()
1065 948
1066 949 def clear_magic_active_frontend(self):
1067 950 self.active_frontend.execute("%clear")
1068 951
1069 952 def who_magic_active_frontend(self):
1070 953 self.active_frontend.execute("%who")
1071 954
1072 955 def who_ls_magic_active_frontend(self):
1073 956 self.active_frontend.execute("%who_ls")
1074 957
1075 958 def whos_magic_active_frontend(self):
1076 959 self.active_frontend.execute("%whos")
1077 960
1078 961 def print_action_active_frontend(self):
1079 962 self.active_frontend.print_action.trigger()
1080 963
1081 964 def export_action_active_frontend(self):
1082 965 self.active_frontend.export_action.trigger()
1083 966
1084 967 def select_all_active_frontend(self):
1085 968 self.active_frontend.select_all_action.trigger()
1086 969
1087 970 def increase_font_size_active_frontend(self):
1088 971 self.active_frontend.increase_font_size.trigger()
1089 972
1090 973 def decrease_font_size_active_frontend(self):
1091 974 self.active_frontend.decrease_font_size.trigger()
1092 975
1093 976 def reset_font_size_active_frontend(self):
1094 977 self.active_frontend.reset_font_size.trigger()
1095 978
1096 979 def guiref_active_frontend(self):
1097 980 self.active_frontend.execute("%guiref")
1098 981
1099 982 def intro_active_frontend(self):
1100 983 self.active_frontend.execute("?")
1101 984
1102 985 def quickref_active_frontend(self):
1103 986 self.active_frontend.execute("%quickref")
1104 987 #---------------------------------------------------------------------------
1105 988 # QWidget interface
1106 989 #---------------------------------------------------------------------------
1107 990
1108 991 def closeEvent(self, event):
1109 992 """ Forward the close event to every tabs contained by the windows
1110 993 """
1111 994 if self.tab_widget.count() == 0:
1112 995 # no tabs, just close
1113 996 event.accept()
1114 997 return
1115 998 # Do Not loop on the widget count as it change while closing
1116 999 title = self.window().windowTitle()
1117 1000 cancel = QtGui.QMessageBox.Cancel
1118 1001 okay = QtGui.QMessageBox.Ok
1119 1002
1120 1003 if self.confirm_exit:
1121 1004 if self.tab_widget.count() > 1:
1122 1005 msg = "Close all tabs, stop all kernels, and Quit?"
1123 1006 else:
1124 1007 msg = "Close console, stop kernel, and Quit?"
1125 1008 info = "Kernels not started here (e.g. notebooks) will be left alone."
1126 1009 closeall = QtGui.QPushButton("&Quit", self)
1127 1010 closeall.setShortcut('Q')
1128 1011 box = QtGui.QMessageBox(QtGui.QMessageBox.Question,
1129 1012 title, msg)
1130 1013 box.setInformativeText(info)
1131 1014 box.addButton(cancel)
1132 1015 box.addButton(closeall, QtGui.QMessageBox.YesRole)
1133 1016 box.setDefaultButton(closeall)
1134 1017 box.setEscapeButton(cancel)
1135 1018 pixmap = QtGui.QPixmap(self._app.icon.pixmap(QtCore.QSize(64,64)))
1136 1019 box.setIconPixmap(pixmap)
1137 1020 reply = box.exec_()
1138 1021 else:
1139 1022 reply = okay
1140 1023
1141 1024 if reply == cancel:
1142 1025 event.ignore()
1143 1026 return
1144 1027 if reply == okay:
1145 1028 while self.tab_widget.count() >= 1:
1146 1029 # prevent further confirmations:
1147 1030 widget = self.active_frontend
1148 1031 widget._confirm_exit = False
1149 1032 self.close_tab(widget)
1150 1033 event.accept()
1151 1034
@@ -1,379 +1,379
1 1 """ A minimal application using the Qt console-style IPython frontend.
2 2
3 3 This is not a complete console app, as subprocess will not be able to receive
4 4 input, there is no real readline support, among other limitations.
5 5
6 6 Authors:
7 7
8 8 * Evan Patterson
9 9 * Min RK
10 10 * Erik Tollerud
11 11 * Fernando Perez
12 12 * Bussonnier Matthias
13 13 * Thomas Kluyver
14 14 * Paul Ivanov
15 15
16 16 """
17 17
18 18 #-----------------------------------------------------------------------------
19 19 # Imports
20 20 #-----------------------------------------------------------------------------
21 21
22 22 # stdlib imports
23 23 import os
24 24 import signal
25 25 import sys
26 26
27 27 # If run on Windows, install an exception hook which pops up a
28 28 # message box. Pythonw.exe hides the console, so without this
29 29 # the application silently fails to load.
30 30 #
31 31 # We always install this handler, because the expectation is for
32 32 # qtconsole to bring up a GUI even if called from the console.
33 33 # The old handler is called, so the exception is printed as well.
34 34 # If desired, check for pythonw with an additional condition
35 35 # (sys.executable.lower().find('pythonw.exe') >= 0).
36 36 if os.name == 'nt':
37 37 old_excepthook = sys.excepthook
38 38
39 39 # Exclude this from our autogenerated API docs.
40 40 undoc = lambda func: func
41 41
42 42 @undoc
43 43 def gui_excepthook(exctype, value, tb):
44 44 try:
45 45 import ctypes, traceback
46 46 MB_ICONERROR = 0x00000010
47 47 title = u'Error starting IPython QtConsole'
48 48 msg = u''.join(traceback.format_exception(exctype, value, tb))
49 49 ctypes.windll.user32.MessageBoxW(0, msg, title, MB_ICONERROR)
50 50 finally:
51 51 # Also call the old exception hook to let it do
52 52 # its thing too.
53 53 old_excepthook(exctype, value, tb)
54 54
55 55 sys.excepthook = gui_excepthook
56 56
57 57 # System library imports
58 58 from IPython.external.qt import QtCore, QtGui
59 59
60 60 # Local imports
61 61 from IPython.config.application import catch_config_error
62 62 from IPython.core.application import BaseIPythonApplication
63 63 from IPython.qt.console.ipython_widget import IPythonWidget
64 64 from IPython.qt.console.rich_ipython_widget import RichIPythonWidget
65 65 from IPython.qt.console import styles
66 66 from IPython.qt.console.mainwindow import MainWindow
67 67 from IPython.qt.client import QtKernelClient
68 68 from IPython.qt.manager import QtKernelManager
69 69 from IPython.utils.traitlets import (
70 70 Dict, Unicode, CBool, Any
71 71 )
72 72
73 73 from IPython.consoleapp import (
74 74 IPythonConsoleApp, app_aliases, app_flags, flags, aliases
75 75 )
76 76
77 77 #-----------------------------------------------------------------------------
78 78 # Network Constants
79 79 #-----------------------------------------------------------------------------
80 80
81 81 from IPython.utils.localinterfaces import is_local_ip
82 82
83 83 #-----------------------------------------------------------------------------
84 84 # Globals
85 85 #-----------------------------------------------------------------------------
86 86
87 87 _examples = """
88 88 ipython qtconsole # start the qtconsole
89 89 ipython qtconsole --matplotlib=inline # start with matplotlib inline plotting mode
90 90 """
91 91
92 92 #-----------------------------------------------------------------------------
93 93 # Aliases and Flags
94 94 #-----------------------------------------------------------------------------
95 95
96 96 # start with copy of flags
97 97 flags = dict(flags)
98 98 qt_flags = {
99 99 'plain' : ({'IPythonQtConsoleApp' : {'plain' : True}},
100 100 "Disable rich text support."),
101 101 }
102 102
103 103 # and app_flags from the Console Mixin
104 104 qt_flags.update(app_flags)
105 105 # add frontend flags to the full set
106 106 flags.update(qt_flags)
107 107
108 108 # start with copy of front&backend aliases list
109 109 aliases = dict(aliases)
110 110 qt_aliases = dict(
111 111 style = 'IPythonWidget.syntax_style',
112 112 stylesheet = 'IPythonQtConsoleApp.stylesheet',
113 113 colors = 'ZMQInteractiveShell.colors',
114 114
115 115 editor = 'IPythonWidget.editor',
116 116 paging = 'ConsoleWidget.paging',
117 117 )
118 118 # and app_aliases from the Console Mixin
119 119 qt_aliases.update(app_aliases)
120 120 qt_aliases.update({'gui-completion':'ConsoleWidget.gui_completion'})
121 121 # add frontend aliases to the full set
122 122 aliases.update(qt_aliases)
123 123
124 124 # get flags&aliases into sets, and remove a couple that
125 125 # shouldn't be scrubbed from backend flags:
126 126 qt_aliases = set(qt_aliases.keys())
127 127 qt_aliases.remove('colors')
128 128 qt_flags = set(qt_flags.keys())
129 129
130 130 #-----------------------------------------------------------------------------
131 131 # Classes
132 132 #-----------------------------------------------------------------------------
133 133
134 134 #-----------------------------------------------------------------------------
135 135 # IPythonQtConsole
136 136 #-----------------------------------------------------------------------------
137 137
138 138
139 139 class IPythonQtConsoleApp(BaseIPythonApplication, IPythonConsoleApp):
140 140 name = 'ipython-qtconsole'
141 141
142 142 description = """
143 143 The IPython QtConsole.
144 144
145 145 This launches a Console-style application using Qt. It is not a full
146 146 console, in that launched terminal subprocesses will not be able to accept
147 147 input.
148 148
149 149 The QtConsole supports various extra features beyond the Terminal IPython
150 150 shell, such as inline plotting with matplotlib, via:
151 151
152 152 ipython qtconsole --matplotlib=inline
153 153
154 154 as well as saving your session as HTML, and printing the output.
155 155
156 156 """
157 157 examples = _examples
158 158
159 159 classes = [IPythonWidget] + IPythonConsoleApp.classes
160 160 flags = Dict(flags)
161 161 aliases = Dict(aliases)
162 162 frontend_flags = Any(qt_flags)
163 163 frontend_aliases = Any(qt_aliases)
164 164 kernel_client_class = QtKernelClient
165 165 kernel_manager_class = QtKernelManager
166 166
167 167 stylesheet = Unicode('', config=True,
168 168 help="path to a custom CSS stylesheet")
169 169
170 170 hide_menubar = CBool(False, config=True,
171 171 help="Start the console window with the menu bar hidden.")
172 172
173 173 maximize = CBool(False, config=True,
174 174 help="Start the console window maximized.")
175 175
176 176 plain = CBool(False, config=True,
177 177 help="Use a plaintext widget instead of rich text (plain can't print/save).")
178 178
179 179 def _plain_changed(self, name, old, new):
180 180 kind = 'plain' if new else 'rich'
181 181 self.config.ConsoleWidget.kind = kind
182 182 if new:
183 183 self.widget_factory = IPythonWidget
184 184 else:
185 185 self.widget_factory = RichIPythonWidget
186 186
187 187 # the factory for creating a widget
188 188 widget_factory = Any(RichIPythonWidget)
189 189
190 190 def parse_command_line(self, argv=None):
191 191 super(IPythonQtConsoleApp, self).parse_command_line(argv)
192 192 self.build_kernel_argv(argv)
193 193
194 194
195 195 def new_frontend_master(self):
196 196 """ Create and return new frontend attached to new kernel, launched on localhost.
197 197 """
198 198 kernel_manager = self.kernel_manager_class(
199 199 connection_file=self._new_connection_file(),
200 200 parent=self,
201 201 autorestart=True,
202 202 )
203 203 # start the kernel
204 204 kwargs = dict()
205 205 kwargs['extra_arguments'] = self.kernel_argv
206 206 kernel_manager.start_kernel(**kwargs)
207 207 kernel_manager.client_factory = self.kernel_client_class
208 208 kernel_client = kernel_manager.client()
209 209 kernel_client.start_channels(shell=True, iopub=True)
210 210 widget = self.widget_factory(config=self.config,
211 211 local_kernel=True)
212 212 self.init_colors(widget)
213 213 widget.kernel_manager = kernel_manager
214 214 widget.kernel_client = kernel_client
215 215 widget._existing = False
216 216 widget._may_close = True
217 217 widget._confirm_exit = self.confirm_exit
218 218 return widget
219 219
220 220 def new_frontend_slave(self, current_widget):
221 221 """Create and return a new frontend attached to an existing kernel.
222 222
223 223 Parameters
224 224 ----------
225 225 current_widget : IPythonWidget
226 226 The IPythonWidget whose kernel this frontend is to share
227 227 """
228 228 kernel_client = self.kernel_client_class(
229 229 connection_file=current_widget.kernel_client.connection_file,
230 230 config = self.config,
231 231 )
232 232 kernel_client.load_connection_file()
233 233 kernel_client.start_channels()
234 234 widget = self.widget_factory(config=self.config,
235 235 local_kernel=False)
236 236 self.init_colors(widget)
237 237 widget._existing = True
238 238 widget._may_close = False
239 239 widget._confirm_exit = False
240 240 widget.kernel_client = kernel_client
241 241 widget.kernel_manager = current_widget.kernel_manager
242 242 return widget
243 243
244 244 def init_qt_app(self):
245 245 # separate from qt_elements, because it must run first
246 246 self.app = QtGui.QApplication([])
247 247
248 248 def init_qt_elements(self):
249 249 # Create the widget.
250 250
251 251 base_path = os.path.abspath(os.path.dirname(__file__))
252 252 icon_path = os.path.join(base_path, 'resources', 'icon', 'IPythonConsole.svg')
253 253 self.app.icon = QtGui.QIcon(icon_path)
254 254 QtGui.QApplication.setWindowIcon(self.app.icon)
255 255
256 256 ip = self.ip
257 257 local_kernel = (not self.existing) or is_local_ip(ip)
258 258 self.widget = self.widget_factory(config=self.config,
259 259 local_kernel=local_kernel)
260 260 self.init_colors(self.widget)
261 261 self.widget._existing = self.existing
262 262 self.widget._may_close = not self.existing
263 263 self.widget._confirm_exit = self.confirm_exit
264 264
265 265 self.widget.kernel_manager = self.kernel_manager
266 266 self.widget.kernel_client = self.kernel_client
267 267 self.window = MainWindow(self.app,
268 268 confirm_exit=self.confirm_exit,
269 269 new_frontend_factory=self.new_frontend_master,
270 270 slave_frontend_factory=self.new_frontend_slave,
271 271 )
272 272 self.window.log = self.log
273 273 self.window.add_tab_with_frontend(self.widget)
274 self.window.init_menu_bar()
275 274 self.window.init_magic_helper()
275 self.window.init_menu_bar()
276 276
277 277 # Ignore on OSX, where there is always a menu bar
278 278 if sys.platform != 'darwin' and self.hide_menubar:
279 279 self.window.menuBar().setVisible(False)
280 280
281 281 self.window.setWindowTitle('IPython')
282 282
283 283 def init_colors(self, widget):
284 284 """Configure the coloring of the widget"""
285 285 # Note: This will be dramatically simplified when colors
286 286 # are removed from the backend.
287 287
288 288 # parse the colors arg down to current known labels
289 289 cfg = self.config
290 290 colors = cfg.ZMQInteractiveShell.colors if 'ZMQInteractiveShell.colors' in cfg else None
291 291 style = cfg.IPythonWidget.syntax_style if 'IPythonWidget.syntax_style' in cfg else None
292 292 sheet = cfg.IPythonWidget.style_sheet if 'IPythonWidget.style_sheet' in cfg else None
293 293
294 294 # find the value for colors:
295 295 if colors:
296 296 colors=colors.lower()
297 297 if colors in ('lightbg', 'light'):
298 298 colors='lightbg'
299 299 elif colors in ('dark', 'linux'):
300 300 colors='linux'
301 301 else:
302 302 colors='nocolor'
303 303 elif style:
304 304 if style=='bw':
305 305 colors='nocolor'
306 306 elif styles.dark_style(style):
307 307 colors='linux'
308 308 else:
309 309 colors='lightbg'
310 310 else:
311 311 colors=None
312 312
313 313 # Configure the style
314 314 if style:
315 315 widget.style_sheet = styles.sheet_from_template(style, colors)
316 316 widget.syntax_style = style
317 317 widget._syntax_style_changed()
318 318 widget._style_sheet_changed()
319 319 elif colors:
320 320 # use a default dark/light/bw style
321 321 widget.set_default_style(colors=colors)
322 322
323 323 if self.stylesheet:
324 324 # we got an explicit stylesheet
325 325 if os.path.isfile(self.stylesheet):
326 326 with open(self.stylesheet) as f:
327 327 sheet = f.read()
328 328 else:
329 329 raise IOError("Stylesheet %r not found." % self.stylesheet)
330 330 if sheet:
331 331 widget.style_sheet = sheet
332 332 widget._style_sheet_changed()
333 333
334 334
335 335 def init_signal(self):
336 336 """allow clean shutdown on sigint"""
337 337 signal.signal(signal.SIGINT, lambda sig, frame: self.exit(-2))
338 338 # need a timer, so that QApplication doesn't block until a real
339 339 # Qt event fires (can require mouse movement)
340 340 # timer trick from http://stackoverflow.com/q/4938723/938949
341 341 timer = QtCore.QTimer()
342 342 # Let the interpreter run each 200 ms:
343 343 timer.timeout.connect(lambda: None)
344 344 timer.start(200)
345 345 # hold onto ref, so the timer doesn't get cleaned up
346 346 self._sigint_timer = timer
347 347
348 348 @catch_config_error
349 349 def initialize(self, argv=None):
350 350 self.init_qt_app()
351 351 super(IPythonQtConsoleApp, self).initialize(argv)
352 352 IPythonConsoleApp.initialize(self,argv)
353 353 self.init_qt_elements()
354 354 self.init_signal()
355 355
356 356 def start(self):
357 357
358 358 # draw the window
359 359 if self.maximize:
360 360 self.window.showMaximized()
361 361 else:
362 362 self.window.show()
363 363 self.window.raise_()
364 364
365 365 # Start the application main loop.
366 366 self.app.exec_()
367 367
368 368 #-----------------------------------------------------------------------------
369 369 # Main entry point
370 370 #-----------------------------------------------------------------------------
371 371
372 372 def main():
373 373 app = IPythonQtConsoleApp()
374 374 app.initialize()
375 375 app.start()
376 376
377 377
378 378 if __name__ == '__main__':
379 379 main()
General Comments 0
You need to be logged in to leave comments. Login now