##// END OF EJS Templates
Create all_magic_menu. use webbrowser to open url
Matthias BUSSONNIER -
Show More
@@ -1,1076 +1,1111 b''
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
13 13 """
14 14
15 15 #-----------------------------------------------------------------------------
16 16 # Imports
17 17 #-----------------------------------------------------------------------------
18 18
19 19 # stdlib imports
20 20 import json
21 21 import os
22 22 import signal
23 23 import sys
24 import webbrowser
25 from getpass import getpass
24 26
25 27 # System library imports
26 28 from IPython.external.qt import QtGui,QtCore
27 29 from pygments.styles import get_all_styles
28 30
29 31 # Local imports
30 32 from IPython.config.application import boolean_flag
31 33 from IPython.core.application import BaseIPythonApplication
32 34 from IPython.core.profiledir import ProfileDir
33 35 from IPython.lib.kernel import tunnel_to_kernel, find_connection_file
34 36 from IPython.frontend.qt.console.frontend_widget import FrontendWidget
35 37 from IPython.frontend.qt.console.ipython_widget import IPythonWidget
36 38 from IPython.frontend.qt.console.rich_ipython_widget import RichIPythonWidget
37 39 from IPython.frontend.qt.console import styles
38 40 from IPython.frontend.qt.kernelmanager import QtKernelManager
39 41 from IPython.utils.path import filefind
40 42 from IPython.utils.py3compat import str_to_bytes
41 43 from IPython.utils.traitlets import (
42 44 Dict, List, Unicode, Int, CaselessStrEnum, CBool, Any
43 45 )
44 46 from IPython.zmq.ipkernel import (
45 47 flags as ipkernel_flags,
46 48 aliases as ipkernel_aliases,
47 49 IPKernelApp
48 50 )
49 51 from IPython.zmq.session import Session, default_secure
50 52 from IPython.zmq.zmqshell import ZMQInteractiveShell
51 53
52 54 #-----------------------------------------------------------------------------
53 55 # Network Constants
54 56 #-----------------------------------------------------------------------------
55 57
56 58 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
57 59
58 60 #-----------------------------------------------------------------------------
59 61 # Globals
60 62 #-----------------------------------------------------------------------------
61 63
62 64 _examples = """
63 65 ipython qtconsole # start the qtconsole
64 66 ipython qtconsole --pylab=inline # start with pylab in inline plotting mode
65 67 """
66 68
67 69 #-----------------------------------------------------------------------------
68 70 # Classes
69 71 #-----------------------------------------------------------------------------
70 72
71 73 class MainWindow(QtGui.QMainWindow):
72 74
73 75 #---------------------------------------------------------------------------
74 76 # 'object' interface
75 77 #---------------------------------------------------------------------------
76 78
77 79 def __init__(self, app, frontend, existing=False, may_close=True,
78 80 confirm_exit=True):
79 81 """ Create a MainWindow for the specified FrontendWidget.
80 82
81 83 The app is passed as an argument to allow for different
82 84 closing behavior depending on whether we are the Kernel's parent.
83 85
84 86 If existing is True, then this Console does not own the Kernel.
85 87
86 88 If may_close is True, then this Console is permitted to close the kernel
87 89 """
88 90
89 91 super(MainWindow, self).__init__()
90 92 self._app = app
91 93
92 94 self.tab_widget = QtGui.QTabWidget(self)
93 95 self.tab_widget.setDocumentMode(True)
94 96 self.tab_widget.setTabsClosable(True)
95 97 self.tab_widget.tabCloseRequested[int].connect(self.close_tab)
96 98
97 99 self.setCentralWidget(self.tab_widget)
98 100 self.update_tab_bar_visibility()
99 101
100 102 def update_tab_bar_visibility(self):
101 103 """ update visibility of the tabBar depending of the number of tab
102 104
103 105 0 or 1 tab, tabBar hidden
104 106 2+ tabs, tabBar visible
105 107
106 108 send a self.close if number of tab ==0
107 109
108 110 need to be called explicitely, or be connected to tabInserted/tabRemoved
109 111 """
110 112 if self.tab_widget.count() <= 1:
111 113 self.tab_widget.tabBar().setVisible(False)
112 114 else:
113 115 self.tab_widget.tabBar().setVisible(True)
114 116 if self.tab_widget.count()==0 :
115 117 self.close()
116 118
117 119 @property
118 120 def active_frontend(self):
119 121 return self.tab_widget.currentWidget()
120 122
121 123 def close_tab(self,current_tab):
122 124 """ Called when you need to try to close a tab.
123 125
124 126 It takes the number of the tab to be closed as argument, or a referece
125 127 to the wiget insite this tab
126 128 """
127 129
128 130 # let's be sure "tab" and "closing widget are respectivey the index of the tab to close
129 131 # and a reference to the trontend to close
130 132 if type(current_tab) is not int :
131 133 current_tab = self.tab_widget.indexOf(current_tab)
132 134 closing_widget=self.tab_widget.widget(current_tab)
133 135
134 136
135 137 # when trying to be closed, widget might re-send a request to be closed again, but will
136 138 # be deleted when event will be processed. So need to check that widget still exist and
137 139 # skip if not. One example of this is when 'exit' is send in a slave tab. 'exit' will be
138 140 # re-send by this fonction on the master widget, which ask all slaves widget to exit
139 141 if closing_widget==None:
140 142 return
141 143
142 144 #get a list of all wwidget not owning the kernel.
143 145 slave_tabs=self.find_slaves_tabs(closing_widget)
144 146
145 147 keepkernel = None #Use the prompt by default
146 148 if hasattr(closing_widget,'_keep_kernel_on_exit'): #set by exit magic
147 149 keepkernel = closing_widget._keep_kernel_on_exit
148 150 # If signal sent by exist magic (_keep_kernel_on_exit, exist and not None)
149 151 # we set local slave tabs._hidden to True to avoit prompting for kernel
150 152 # restart when they litt get the signal. and the "forward" the 'exit'
151 153 # to the main win
152 154 if keepkernel is not None:
153 155 for tab in slave_tabs:
154 156 tab._hidden = True
155 157 if closing_widget in slave_tabs :
156 158 try :
157 159 self.find_master_tab(closing_widget).execute('exit')
158 160 except AttributeError:
159 161 self.log.info("Master already closed or not local, closing only current tab")
160 162 self.tab_widget.removeTab(current_tab)
161 163 return
162 164
163 165 kernel_manager = closing_widget.kernel_manager
164 166
165 167 if keepkernel is None and not closing_widget._confirm_exit:
166 168 # don't prompt, just terminate the kernel if we own it
167 169 # or leave it alone if we don't
168 170 keepkernel = not closing_widget._existing
169 171
170 172 if keepkernel is None: #show prompt
171 173 if kernel_manager and kernel_manager.channels_running:
172 174 title = self.window().windowTitle()
173 175 cancel = QtGui.QMessageBox.Cancel
174 176 okay = QtGui.QMessageBox.Ok
175 177 if closing_widget._may_close:
176 178 msg = "You are closing the tab : "+'"'+self.tab_widget.tabText(current_tab)+'"'
177 179 info = "Would you like to quit the Kernel and all attached Consoles as well?"
178 180 justthis = QtGui.QPushButton("&No, just this Console", self)
179 181 justthis.setShortcut('N')
180 182 closeall = QtGui.QPushButton("&Yes, quit everything", self)
181 183 closeall.setShortcut('Y')
182 184 box = QtGui.QMessageBox(QtGui.QMessageBox.Question,
183 185 title, msg)
184 186 box.setInformativeText(info)
185 187 box.addButton(cancel)
186 188 box.addButton(justthis, QtGui.QMessageBox.NoRole)
187 189 box.addButton(closeall, QtGui.QMessageBox.YesRole)
188 190 box.setDefaultButton(closeall)
189 191 box.setEscapeButton(cancel)
190 192 pixmap = QtGui.QPixmap(self._app.icon.pixmap(QtCore.QSize(64,64)))
191 193 box.setIconPixmap(pixmap)
192 194 reply = box.exec_()
193 195 if reply == 1: # close All
194 196 for slave in slave_tabs:
195 197 self.tab_widget.removeTab(self.tab_widget.indexOf(slave))
196 198 closing_widget.execute("exit")
197 199 self.tab_widget.removeTab(current_tab)
198 200 elif reply == 0: # close Console
199 201 if not closing_widget._existing:
200 202 # Have kernel: don't quit, just close the window
201 203 self._app.setQuitOnLastWindowClosed(False)
202 204 closing_widget.execute("exit True")
203 205 else:
204 206 reply = QtGui.QMessageBox.question(self, title,
205 207 "Are you sure you want to close this Console?"+
206 208 "\nThe Kernel and other Consoles will remain active.",
207 209 okay|cancel,
208 210 defaultButton=okay
209 211 )
210 212 if reply == okay:
211 213 self.tab_widget.removeTab(current_tab)
212 214 elif keepkernel: #close console but leave kernel running (no prompt)
213 215 if kernel_manager and kernel_manager.channels_running:
214 216 if not closing_widget._existing:
215 217 # I have the kernel: don't quit, just close the window
216 218 self.tab_widget.removeTab(current_tab)
217 219 else: #close console and kernel (no prompt)
218 220 if kernel_manager and kernel_manager.channels_running:
219 221 for slave in slave_tabs:
220 222 self.tab_widget.removeTab(self.tab_widget.indexOf(slave))
221 223 self.tab_widget.removeTab(current_tab)
222 224 kernel_manager.shutdown_kernel()
223 225 self.update_tab_bar_visibility()
224 226
225 227 def add_tab_with_frontend(self,frontend,name=None):
226 228 """ insert a tab with a given frontend in the tab bar, and give it a name
227 229
228 230 """
229 231 if not name:
230 232 name=str('kernel '+str(self.tab_widget.count()))
231 233 self.tab_widget.addTab(frontend,name)
232 234 self.update_tab_bar_visibility()
233 235 self.make_frontend_visible(frontend)
234 236 frontend.exit_requested.connect(self.close_tab)
235 237
236 238 def next_tab(self):
237 239 self.tab_widget.setCurrentIndex((self.tab_widget.currentIndex()+1))
238 240
239 241 def prev_tab(self):
240 242 self.tab_widget.setCurrentIndex((self.tab_widget.currentIndex()-1))
241 243
242 244 def make_frontend_visible(self,frontend):
243 245 widget_index=self.tab_widget.indexOf(frontend)
244 246 if widget_index > 0 :
245 247 self.tab_widget.setCurrentIndex(widget_index)
246 248
247 249 def find_master_tab(self,tab,as_list=False):
248 250 """
249 251 Try to return the frontend that own the kernel attached to the given widget/tab.
250 252
251 253 Only find frontend owed by the current application. Selection
252 254 based on port of the kernel, might be inacurate if several kernel
253 255 on different ip use same port number.
254 256
255 257 This fonction does the conversion tabNumber/widget if needed.
256 258 Might return None if no master widget (non local kernel)
257 259 Will crash IPython if more than 1 masterWidget
258 260
259 261 When asList set to True, always return a list of widget(s) owning
260 262 the kernel. The list might be empty or containing several Widget.
261 263 """
262 264
263 265 #convert from/to int/richIpythonWidget if needed
264 266 if type(tab) == int:
265 267 tab = self.tab_widget.widget(tab)
266 268 km=tab.kernel_manager;
267 269
268 270 #build list of all widgets
269 271 widget_list = [self.tab_widget.widget(i) for i in range(self.tab_widget.count())]
270 272
271 273 # widget that are candidate to be the owner of the kernel does have all the same port of the curent widget
272 274 # And should have a _may_close attribute
273 275 filtred_widget_list = [ widget for widget in widget_list if
274 276 widget.kernel_manager.shell_address == km.shell_address and
275 277 widget.kernel_manager.sub_address == km.sub_address and
276 278 widget.kernel_manager.stdin_address == km.stdin_address and
277 279 widget.kernel_manager.hb_address == km.hb_address and
278 280 hasattr(widget,'_may_close') ]
279 281 # the master widget is the one that may close the kernel
280 282 master_widget= [ widget for widget in filtred_widget_list if widget._may_close]
281 283 if as_list:
282 284 return master_widget
283 285 assert(len(master_widget)<=1 )
284 286 if len(master_widget)==0:
285 287 return None
286 288
287 289 return master_widget[0]
288 290
289 291 def find_slaves_tabs(self,tab):
290 292 """
291 293 Try to return all the frontend that do not own the kernel attached to the given widget/tab.
292 294
293 295 Only find frontend owed by the current application. Selection
294 296 based on port of the kernel, might be innacurate if several kernel
295 297 on different ip use same port number.
296 298
297 299 This fonction does the conversion tabNumber/widget if needed.
298 300 """
299 301 #convert from/to int/richIpythonWidget if needed
300 302 if type(tab) == int:
301 303 tab = self.tab_widget.widget(tab)
302 304 km=tab.kernel_manager;
303 305
304 306 #build list of all widgets
305 307 widget_list = [self.tab_widget.widget(i) for i in range(self.tab_widget.count())]
306 308
307 309 # widget that are candidate not to be the owner of the kernel does have all the same port of the curent widget
308 310 filtered_widget_list = ( widget for widget in widget_list if
309 311 widget.kernel_manager.shell_address == km.shell_address and
310 312 widget.kernel_manager.sub_address == km.sub_address and
311 313 widget.kernel_manager.stdin_address == km.stdin_address and
312 314 widget.kernel_manager.hb_address == km.hb_address)
313 315 # Get a list of all widget owning the same kernel and removed it from
314 316 # the previous cadidate. (better using sets ?)
315 317 master_widget_list = self.find_master_tab(tab,as_list=True)
316 318 slave_list = [widget for widget in filtered_widget_list if widget not in master_widget_list]
317 319
318 320 return slave_list
319 321
320 322 # MenuBar is always present on Mac Os, so let's populate it with possible
321 323 # action, don't do it on other platform as some user might not want the
322 324 # menu bar, or give them an option to remove it
323 325 def init_menu_bar(self):
324 326 #create menu in the order they should appear in the menu bar
325 327 self.file_menu = self.menuBar().addMenu("&File")
326 328 self.edit_menu = self.menuBar().addMenu("&Edit")
327 329 self.font_menu = self.menuBar().addMenu("F&ont")
328 330 self.window_menu = self.menuBar().addMenu("&Window")
329 331 self.magic_menu = self.menuBar().addMenu("&Magic")
332 self.all_magic_menu = self.magic_menu.addMenu("&All Magic")
330 333
331 334 # please keep the Help menu in Mac Os even if empty. It will
332 335 # automatically contain a search field to search inside menus and
333 336 # please keep it spelled in English, as long as Qt Doesn't support
334 337 # a QAction.MenuRole like HelpMenuRole otherwise it will loose
335 338 # this search field fonctionnality
336 339
337 340 self.help_menu = self.menuBar().addMenu("&Help")
338 341
339 342 # sould wrap every line of the following block into a try/except,
340 343 # as we are not sure of instanciating a _frontend which support all
341 344 # theses actions, but there might be a better way
342 345 self.print_action = QtGui.QAction("&Print",
343 346 self,
344 347 shortcut="Ctrl+P",
345 348 triggered=self.print_action_active_frontend)
346 349 self.file_menu.addAction(self.print_action)
347 350
348 351 self.export_action=QtGui.QAction("E&xport",
349 352 self,
350 353 shortcut="Ctrl+S",
351 354 triggered=self.export_action_active_frontend
352 355 )
353 356 self.file_menu.addAction(self.export_action)
354 357
355 358 self.select_all_action = QtGui.QAction("Select &All",
356 359 self,
357 360 shortcut="Ctrl+A",
358 361 triggered=self.select_all_active_frontend
359 362 )
360 363 self.file_menu.addAction(self.select_all_action)
361 364
362 365 self.undo_action = QtGui.QAction("&Undo",
363 366 self,
364 367 shortcut="Ctrl+Z",
365 368 statusTip="Undo last action if possible",
366 369 triggered=self.undo_active_frontend
367 370 )
368 371 self.edit_menu.addAction(self.undo_action)
369 372
370 373 self.redo_action = QtGui.QAction("&Redo",
371 374 self,
372 375 shortcut="Ctrl+Shift+Z",
373 376 statusTip="Redo last action if possible",
374 377 triggered=self.redo_active_frontend)
375 378 self.edit_menu.addAction(self.redo_action)
376 379
377 380 self.increase_font_size = QtGui.QAction("&Increase Font Size",
378 381 self,
379 382 shortcut="Ctrl++",
380 383 triggered=self.increase_font_size_active_frontend
381 384 )
382 385 self.font_menu.addAction(self.increase_font_size)
383 386
384 387 self.decrease_font_size = QtGui.QAction("&Decrease Font Size",
385 388 self,
386 389 shortcut="Ctrl+-",
387 390 triggered=self.decrease_font_size_active_frontend
388 391 )
389 392 self.font_menu.addAction(self.decrease_font_size)
390 393
391 394 self.reset_font_size = QtGui.QAction("&Reset Font Size",
392 395 self,
393 396 shortcut="Ctrl+0",
394 397 triggered=self.reset_font_size_active_frontend
395 398 )
396 399 self.font_menu.addAction(self.reset_font_size)
397 400
398 401 self.reset_action = QtGui.QAction("&Reset",
399 402 self,
400 403 statusTip="Clear all varible from workspace",
401 404 triggered=self.reset_magic_active_frontend)
402 405 self.magic_menu.addAction(self.reset_action)
403 406
404 407 self.history_action = QtGui.QAction("&History",
405 408 self,
406 409 statusTip="show command history",
407 410 triggered=self.history_magic_active_frontend)
408 411 self.magic_menu.addAction(self.history_action)
409 412
410 413 self.save_action = QtGui.QAction("E&xport History ",
411 414 self,
412 415 statusTip="Export History as Python File",
413 416 triggered=self.save_magic_active_frontend)
414 417 self.magic_menu.addAction(self.save_action)
415 418
416 419 self.clear_action = QtGui.QAction("&Clear",
417 420 self,
418 421 statusTip="Clear the console",
419 422 triggered=self.clear_magic_active_frontend)
420 423 self.magic_menu.addAction(self.clear_action)
421 424
422 425 self.who_action = QtGui.QAction("&Who",
423 426 self,
424 427 statusTip="List interactive variable",
425 428 triggered=self.who_magic_active_frontend)
426 429 self.magic_menu.addAction(self.who_action)
427 430
428 431 self.who_ls_action = QtGui.QAction("Wh&o ls",
429 432 self,
430 433 statusTip="Return a list of interactive variable",
431 434 triggered=self.who_ls_magic_active_frontend)
432 435 self.magic_menu.addAction(self.who_ls_action)
433 436
434 437 self.whos_action = QtGui.QAction("Who&s",
435 438 self,
436 439 statusTip="List interactive variable with detail",
437 440 triggered=self.whos_magic_active_frontend)
438 441 self.magic_menu.addAction(self.whos_action)
439 442
440 self.intro_active_frontend_action = QtGui.QAction("intro",
443 self.intro_active_frontend_action = QtGui.QAction("Intro",
441 444 self,
442 445 triggered=self.intro_active_frontend
443 446 )
444 447 self.help_menu.addAction(self.intro_active_frontend_action)
445 448
446 self.guiref_active_frontend_action = QtGui.QAction("guiref",
449 self.guiref_active_frontend_action = QtGui.QAction("Gui references",
447 450 self,
448 451 triggered=self.guiref_active_frontend
449 452 )
450 453 self.help_menu.addAction(self.guiref_active_frontend_action)
451 454
455 self.quickref_active_frontend_action = QtGui.QAction("Quick references",
456 self,
457 triggered=self.quickref_active_frontend
458 )
459 self.help_menu.addAction(self.quickref_active_frontend_action)
460
461 magiclist=["%alias", "%autocall", "%automagic", "%bookmark", "%cd", "%clear",
462 "%colors", "%debug", "%dhist", "%dirs", "%doctest_mode", "%ed", "%edit", "%env", "%gui",
463 "%guiref", "%hist", "%history", "%install_default_config", "%install_profiles",
464 "%less", "%load_ext", "%loadpy", "%logoff", "%logon", "%logstart", "%logstate",
465 "%logstop", "%lsmagic", "%macro", "%magic", "%man", "%more", "%notebook", "%page",
466 "%pastebin", "%pdb", "%pdef", "%pdoc", "%pfile", "%pinfo", "%pinfo2", "%popd", "%pprint",
467 "%precision", "%profile", "%prun", "%psearch", "%psource", "%pushd", "%pwd", "%pycat",
468 "%pylab", "%quickref", "%recall", "%rehashx", "%reload_ext", "%rep", "%rerun",
469 "%reset", "%reset_selective", "%run", "%save", "%sc", "%sx", "%tb", "%time", "%timeit",
470 "%unalias", "%unload_ext", "%who", "%who_ls", "%whos", "%xdel", "%xmode"]
471
472 def make_dynamic_magic(i):
473 def inner_dynamic_magic():
474 self.active_frontend.execute(i)
475 inner_dynamic_magic.__name__ = "dynamics_magic_%s" % i
476 return inner_dynamic_magic
477
478 for magic in magiclist:
479 xaction = QtGui.QAction(magic,
480 self,
481 triggered=make_dynamic_magic(magic)
482 )
483 self.all_magic_menu.addAction(xaction)
484
485
452 486 def undo_active_frontend(self):
453 487 self.active_frontend.undo()
454 488
455 489 def redo_active_frontend(self):
456 490 self.active_frontend.redo()
457 491
458 492 def reset_magic_active_frontend(self):
459 493 self.active_frontend.execute("%reset")
460 494
461 495 def history_magic_active_frontend(self):
462 496 self.active_frontend.history_magic()
463 497
464 498 def save_magic_active_frontend(self):
465 499 self.active_frontend.save_magic()
466 500
467 501 def clear_magic_active_frontend(self):
468 502 self.active_frontend.execute("%clear")
469 503
470 504 def who_magic_active_frontend(self):
471 505 self.active_frontend.execute("%who")
472 506
473 507 def who_ls_magic_active_frontend(self):
474 508 self.active_frontend.execute("%who_ls")
475 509
476 510 def whos_magic_active_frontend(self):
477 511 self.active_frontend.execute("%whos")
478 512
479 513 def print_action_active_frontend(self):
480 514 self.active_frontend.print_action.trigger()
481 515
482 516 def export_action_active_frontend(self):
483 517 self.active_frontend.export_action.trigger()
484 518
485 519 def select_all_active_frontend(self):
486 520 self.active_frontend.select_all_action.trigger()
487 521
488 522 def increase_font_size_active_frontend(self):
489 523 self.active_frontend.increase_font_size.trigger()
490 524
491 525 def decrease_font_size_active_frontend(self):
492 526 self.active_frontend.decrease_font_size.trigger()
493 527
494 528 def reset_font_size_active_frontend(self):
495 529 self.active_frontend.reset_font_size.trigger()
496 530
497 531 def guiref_active_frontend(self):
498 532 self.active_frontend.execute("%guiref")
499 533
500 534 def intro_active_frontend(self):
501 535 self.active_frontend.execute("?")
536
537 def quickref_active_frontend(self):
538 self.active_frontend.execute("%quickref")
502 539 #---------------------------------------------------------------------------
503 540 # QWidget interface
504 541 #---------------------------------------------------------------------------
505 542
506 543 def closeEvent(self, event):
507 544 """ Forward the close event to every tabs contained by the windows
508 545 """
509 546 # Do Not loop on the widget count as it change while closing
510 547 widget_list=[ self.tab_widget.widget(i) for i in range(self.tab_widget.count())]
511 548 for widget in widget_list:
512 549 self.close_tab(widget)
513 550 event.accept()
514 551
515 552 #-----------------------------------------------------------------------------
516 553 # Aliases and Flags
517 554 #-----------------------------------------------------------------------------
518 555
519 556 flags = dict(ipkernel_flags)
520 557 qt_flags = {
521 558 'existing' : ({'IPythonQtConsoleApp' : {'existing' : 'kernel*.json'}},
522 559 "Connect to an existing kernel. If no argument specified, guess most recent"),
523 560 'pure' : ({'IPythonQtConsoleApp' : {'pure' : True}},
524 561 "Use a pure Python kernel instead of an IPython kernel."),
525 562 'plain' : ({'ConsoleWidget' : {'kind' : 'plain'}},
526 563 "Disable rich text support."),
527 564 }
528 565 qt_flags.update(boolean_flag(
529 566 'gui-completion', 'ConsoleWidget.gui_completion',
530 567 "use a GUI widget for tab completion",
531 568 "use plaintext output for completion"
532 569 ))
533 570 qt_flags.update(boolean_flag(
534 571 'confirm-exit', 'IPythonQtConsoleApp.confirm_exit',
535 572 """Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
536 573 to force a direct exit without any confirmation.
537 574 """,
538 575 """Don't prompt the user when exiting. This will terminate the kernel
539 576 if it is owned by the frontend, and leave it alive if it is external.
540 577 """
541 578 ))
542 579 flags.update(qt_flags)
543 580
544 581 aliases = dict(ipkernel_aliases)
545 582
546 583 qt_aliases = dict(
547 584 hb = 'IPythonQtConsoleApp.hb_port',
548 585 shell = 'IPythonQtConsoleApp.shell_port',
549 586 iopub = 'IPythonQtConsoleApp.iopub_port',
550 587 stdin = 'IPythonQtConsoleApp.stdin_port',
551 588 ip = 'IPythonQtConsoleApp.ip',
552 589 existing = 'IPythonQtConsoleApp.existing',
553 590 f = 'IPythonQtConsoleApp.connection_file',
554 591
555 592 style = 'IPythonWidget.syntax_style',
556 593 stylesheet = 'IPythonQtConsoleApp.stylesheet',
557 594 colors = 'ZMQInteractiveShell.colors',
558 595
559 596 editor = 'IPythonWidget.editor',
560 597 paging = 'ConsoleWidget.paging',
561 598 ssh = 'IPythonQtConsoleApp.sshserver',
562 599 )
563 600 aliases.update(qt_aliases)
564 601
565 602
566 603 #-----------------------------------------------------------------------------
567 604 # IPythonQtConsole
568 605 #-----------------------------------------------------------------------------
569 606
570 607
571 608 class IPythonQtConsoleApp(BaseIPythonApplication):
572 609 name = 'ipython-qtconsole'
573 610 default_config_file_name='ipython_config.py'
574 611
575 612 description = """
576 613 The IPython QtConsole.
577 614
578 615 This launches a Console-style application using Qt. It is not a full
579 616 console, in that launched terminal subprocesses will not be able to accept
580 617 input.
581 618
582 619 The QtConsole supports various extra features beyond the Terminal IPython
583 620 shell, such as inline plotting with matplotlib, via:
584 621
585 622 ipython qtconsole --pylab=inline
586 623
587 624 as well as saving your session as HTML, and printing the output.
588 625
589 626 """
590 627 examples = _examples
591 628
592 629 classes = [IPKernelApp, IPythonWidget, ZMQInteractiveShell, ProfileDir, Session]
593 630 flags = Dict(flags)
594 631 aliases = Dict(aliases)
595 632
596 633 kernel_argv = List(Unicode)
597 634
598 635 # create requested profiles by default, if they don't exist:
599 636 auto_create = CBool(True)
600 637 # connection info:
601 638 ip = Unicode(LOCALHOST, config=True,
602 639 help="""Set the kernel\'s IP address [default localhost].
603 640 If the IP address is something other than localhost, then
604 641 Consoles on other machines will be able to connect
605 642 to the Kernel, so be careful!"""
606 643 )
607 644
608 645 sshserver = Unicode('', config=True,
609 646 help="""The SSH server to use to connect to the kernel.""")
610 647 sshkey = Unicode('', config=True,
611 648 help="""Path to the ssh key to use for logging in to the ssh server.""")
612 649
613 650 hb_port = Int(0, config=True,
614 651 help="set the heartbeat port [default: random]")
615 652 shell_port = Int(0, config=True,
616 653 help="set the shell (XREP) port [default: random]")
617 654 iopub_port = Int(0, config=True,
618 655 help="set the iopub (PUB) port [default: random]")
619 656 stdin_port = Int(0, config=True,
620 657 help="set the stdin (XREQ) port [default: random]")
621 658 connection_file = Unicode('', config=True,
622 659 help="""JSON file in which to store connection info [default: kernel-<pid>.json]
623 660
624 661 This file will contain the IP, ports, and authentication key needed to connect
625 662 clients to this kernel. By default, this file will be created in the security-dir
626 663 of the current profile, but can be specified by absolute path.
627 664 """)
628 665 def _connection_file_default(self):
629 666 return 'kernel-%i.json' % os.getpid()
630 667
631 668 existing = Unicode('', config=True,
632 669 help="""Connect to an already running kernel""")
633 670
634 671 stylesheet = Unicode('', config=True,
635 672 help="path to a custom CSS stylesheet")
636 673
637 674 pure = CBool(False, config=True,
638 675 help="Use a pure Python kernel instead of an IPython kernel.")
639 676 plain = CBool(False, config=True,
640 677 help="Use a plaintext widget instead of rich text (plain can't print/save).")
641 678
642 679 def _pure_changed(self, name, old, new):
643 680 kind = 'plain' if self.plain else 'rich'
644 681 self.config.ConsoleWidget.kind = kind
645 682 if self.pure:
646 683 self.widget_factory = FrontendWidget
647 684 elif self.plain:
648 685 self.widget_factory = IPythonWidget
649 686 else:
650 687 self.widget_factory = RichIPythonWidget
651 688
652 689 _plain_changed = _pure_changed
653 690
654 691 confirm_exit = CBool(True, config=True,
655 692 help="""
656 693 Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
657 694 to force a direct exit without any confirmation.""",
658 695 )
659 696
660 697 # the factory for creating a widget
661 698 widget_factory = Any(RichIPythonWidget)
662 699
663 700 def parse_command_line(self, argv=None):
664 701 super(IPythonQtConsoleApp, self).parse_command_line(argv)
665 702 if argv is None:
666 703 argv = sys.argv[1:]
667 704
668 705 self.kernel_argv = list(argv) # copy
669 706 # kernel should inherit default config file from frontend
670 707 self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name)
671 708 # Scrub frontend-specific flags
672 709 for a in argv:
673 710 if a.startswith('-') and a.lstrip('-') in qt_flags:
674 711 self.kernel_argv.remove(a)
675 712 swallow_next = False
676 713 for a in argv:
677 714 if swallow_next:
678 715 self.kernel_argv.remove(a)
679 716 swallow_next = False
680 717 continue
681 718 if a.startswith('-'):
682 719 split = a.lstrip('-').split('=')
683 720 alias = split[0]
684 721 if alias in qt_aliases:
685 722 self.kernel_argv.remove(a)
686 723 if len(split) == 1:
687 724 # alias passed with arg via space
688 725 swallow_next = True
689 726
690 727 def init_connection_file(self):
691 728 """find the connection file, and load the info if found.
692 729
693 730 The current working directory and the current profile's security
694 731 directory will be searched for the file if it is not given by
695 732 absolute path.
696 733
697 734 When attempting to connect to an existing kernel and the `--existing`
698 735 argument does not match an existing file, it will be interpreted as a
699 736 fileglob, and the matching file in the current profile's security dir
700 737 with the latest access time will be used.
701 738 """
702 739 if self.existing:
703 740 try:
704 741 cf = find_connection_file(self.existing)
705 742 except Exception:
706 743 self.log.critical("Could not find existing kernel connection file %s", self.existing)
707 744 self.exit(1)
708 745 self.log.info("Connecting to existing kernel: %s" % cf)
709 746 self.connection_file = cf
710 747 # should load_connection_file only be used for existing?
711 748 # as it is now, this allows reusing ports if an existing
712 749 # file is requested
713 750 try:
714 751 self.load_connection_file()
715 752 except Exception:
716 753 self.log.error("Failed to load connection file: %r", self.connection_file, exc_info=True)
717 754 self.exit(1)
718 755
719 756 def load_connection_file(self):
720 757 """load ip/port/hmac config from JSON connection file"""
721 758 # this is identical to KernelApp.load_connection_file
722 759 # perhaps it can be centralized somewhere?
723 760 try:
724 761 fname = filefind(self.connection_file, ['.', self.profile_dir.security_dir])
725 762 except IOError:
726 763 self.log.debug("Connection File not found: %s", self.connection_file)
727 764 return
728 765 self.log.debug(u"Loading connection file %s", fname)
729 766 with open(fname) as f:
730 767 s = f.read()
731 768 cfg = json.loads(s)
732 769 if self.ip == LOCALHOST and 'ip' in cfg:
733 770 # not overridden by config or cl_args
734 771 self.ip = cfg['ip']
735 772 for channel in ('hb', 'shell', 'iopub', 'stdin'):
736 773 name = channel + '_port'
737 774 if getattr(self, name) == 0 and name in cfg:
738 775 # not overridden by config or cl_args
739 776 setattr(self, name, cfg[name])
740 777 if 'key' in cfg:
741 778 self.config.Session.key = str_to_bytes(cfg['key'])
742 779
743 780 def init_ssh(self):
744 781 """set up ssh tunnels, if needed."""
745 782 if not self.sshserver and not self.sshkey:
746 783 return
747 784
748 785 if self.sshkey and not self.sshserver:
749 786 # specifying just the key implies that we are connecting directly
750 787 self.sshserver = self.ip
751 788 self.ip = LOCALHOST
752 789
753 790 # build connection dict for tunnels:
754 791 info = dict(ip=self.ip,
755 792 shell_port=self.shell_port,
756 793 iopub_port=self.iopub_port,
757 794 stdin_port=self.stdin_port,
758 795 hb_port=self.hb_port
759 796 )
760 797
761 798 self.log.info("Forwarding connections to %s via %s"%(self.ip, self.sshserver))
762 799
763 800 # tunnels return a new set of ports, which will be on localhost:
764 801 self.ip = LOCALHOST
765 802 try:
766 803 newports = tunnel_to_kernel(info, self.sshserver, self.sshkey)
767 804 except:
768 805 # even catch KeyboardInterrupt
769 806 self.log.error("Could not setup tunnels", exc_info=True)
770 807 self.exit(1)
771 808
772 809 self.shell_port, self.iopub_port, self.stdin_port, self.hb_port = newports
773 810
774 811 cf = self.connection_file
775 812 base,ext = os.path.splitext(cf)
776 813 base = os.path.basename(base)
777 814 self.connection_file = os.path.basename(base)+'-ssh'+ext
778 815 self.log.critical("To connect another client via this tunnel, use:")
779 816 self.log.critical("--existing %s" % self.connection_file)
780 817
781 818 def init_kernel_manager(self):
782 819 # Don't let Qt or ZMQ swallow KeyboardInterupts.
783 820 signal.signal(signal.SIGINT, signal.SIG_DFL)
784 821 sec = self.profile_dir.security_dir
785 822 try:
786 823 cf = filefind(self.connection_file, ['.', sec])
787 824 except IOError:
788 825 # file might not exist
789 826 if self.connection_file == os.path.basename(self.connection_file):
790 827 # just shortname, put it in security dir
791 828 cf = os.path.join(sec, self.connection_file)
792 829 else:
793 830 cf = self.connection_file
794 831
795 832 # Create a KernelManager and start a kernel.
796 833 self.kernel_manager = QtKernelManager(
797 834 ip=self.ip,
798 835 shell_port=self.shell_port,
799 836 iopub_port=self.iopub_port,
800 837 stdin_port=self.stdin_port,
801 838 hb_port=self.hb_port,
802 839 connection_file=cf,
803 840 config=self.config,
804 841 )
805 842 # start the kernel
806 843 if not self.existing:
807 844 kwargs = dict(ipython=not self.pure)
808 845 kwargs['extra_arguments'] = self.kernel_argv
809 846 self.kernel_manager.start_kernel(**kwargs)
810 847 elif self.sshserver:
811 848 # ssh, write new connection file
812 849 self.kernel_manager.write_connection_file()
813 850 self.kernel_manager.start_channels()
814 851
815 852 def create_tab_with_new_frontend(self):
816 853 """ Create new tab attached to new kernel, launched on localhost.
817 854 """
818 855 kernel_manager = QtKernelManager(
819 856 shell_address=(LOCALHOST,0 ),
820 857 sub_address=(LOCALHOST, 0),
821 858 stdin_address=(LOCALHOST, 0),
822 859 hb_address=(LOCALHOST, 0),
823 860 config=self.config
824 861 )
825 862 # start the kernel
826 863 kwargs = dict(ip=LOCALHOST, ipython=not self.pure)
827 864 kwargs['extra_arguments'] = self.kernel_argv
828 865 kernel_manager.start_kernel(**kwargs)
829 866 kernel_manager.start_channels()
830 867 local_kernel = (not False) or self.ip in LOCAL_IPS
831 868 widget = self.widget_factory(config=self.config,
832 869 local_kernel=local_kernel)
833 870 widget.kernel_manager = kernel_manager
834 871 widget._existing=False;
835 872 widget._confirm_exit=True;
836 873 widget._may_close=True;
837 874 self.window.add_tab_with_frontend(widget)
838 875
839 876 def create_tab_attached_to_current_tab_kernel(self):
840 877 current_widget = self.window.tab_widget.currentWidget()
841 878 current_widget_index = self.window.tab_widget.indexOf(current_widget)
842 879 current_widget.kernel_manager = current_widget.kernel_manager;
843 880 current_widget_name = self.window.tab_widget.tabText(current_widget_index);
844 881 kernel_manager = QtKernelManager(
845 882 shell_address = current_widget.kernel_manager.shell_address,
846 883 sub_address = current_widget.kernel_manager.sub_address,
847 884 stdin_address = current_widget.kernel_manager.stdin_address,
848 885 hb_address = current_widget.kernel_manager.hb_address,
849 886 config = self.config
850 887 )
851 888 kernel_manager.start_channels()
852 889 local_kernel = (not self.existing) or self.ip in LOCAL_IPS
853 890 widget = self.widget_factory(config=self.config,
854 891 local_kernel=False)
855 892 widget._confirm_exit=True;
856 893 widget._may_close=False;
857 894 widget.kernel_manager = kernel_manager
858 895 self.window.add_tab_with_frontend(widget,name=str('('+current_widget_name+') slave'))
859 896
860 897 def init_qt_elements(self):
861 898 # Create the widget.
862 899 self.app = QtGui.QApplication([])
863 900
864 901 base_path = os.path.abspath(os.path.dirname(__file__))
865 902 icon_path = os.path.join(base_path, 'resources', 'icon', 'IPythonConsole.svg')
866 903 self.app.icon = QtGui.QIcon(icon_path)
867 904 QtGui.QApplication.setWindowIcon(self.app.icon)
868 905
869 906 local_kernel = (not self.existing) or self.ip in LOCAL_IPS
870 907 self.widget = self.widget_factory(config=self.config,
871 908 local_kernel=local_kernel)
872 909 self.widget._existing = self.existing;
873 910 self.widget._may_close = not self.existing;
874 911 self.widget._confirm_exit = not self.existing;
875 912
876 913 self.widget.kernel_manager = self.kernel_manager
877 914 self.window = MainWindow(self.app, self.widget, self.existing,
878 915 may_close=local_kernel,
879 916 confirm_exit=self.confirm_exit)
880 917 self.window.log = self.log
881 918 self.window.add_tab_with_frontend(self.widget)
882 919 self.window.init_menu_bar()
883 920 self.window.setWindowTitle('Python' if self.pure else 'IPython')
884 921
885 922 def init_colors(self):
886 923 """Configure the coloring of the widget"""
887 924 # Note: This will be dramatically simplified when colors
888 925 # are removed from the backend.
889 926
890 927 if self.pure:
891 928 # only IPythonWidget supports styling
892 929 return
893 930
894 931 # parse the colors arg down to current known labels
895 932 try:
896 933 colors = self.config.ZMQInteractiveShell.colors
897 934 except AttributeError:
898 935 colors = None
899 936 try:
900 937 style = self.config.IPythonWidget.colors
901 938 except AttributeError:
902 939 style = None
903 940
904 941 # find the value for colors:
905 942 if colors:
906 943 colors=colors.lower()
907 944 if colors in ('lightbg', 'light'):
908 945 colors='lightbg'
909 946 elif colors in ('dark', 'linux'):
910 947 colors='linux'
911 948 else:
912 949 colors='nocolor'
913 950 elif style:
914 951 if style=='bw':
915 952 colors='nocolor'
916 953 elif styles.dark_style(style):
917 954 colors='linux'
918 955 else:
919 956 colors='lightbg'
920 957 else:
921 958 colors=None
922 959
923 960 # Configure the style.
924 961 widget = self.widget
925 962 if style:
926 963 widget.style_sheet = styles.sheet_from_template(style, colors)
927 964 widget.syntax_style = style
928 965 widget._syntax_style_changed()
929 966 widget._style_sheet_changed()
930 967 elif colors:
931 968 # use a default style
932 969 widget.set_default_style(colors=colors)
933 970 else:
934 971 # this is redundant for now, but allows the widget's
935 972 # defaults to change
936 973 widget.set_default_style()
937 974
938 975 if self.stylesheet:
939 976 # we got an expicit stylesheet
940 977 if os.path.isfile(self.stylesheet):
941 978 with open(self.stylesheet) as f:
942 979 sheet = f.read()
943 980 widget.style_sheet = sheet
944 981 widget._style_sheet_changed()
945 982 else:
946 983 raise IOError("Stylesheet %r not found."%self.stylesheet)
947 984
948 985 def initialize(self, argv=None):
949 986 super(IPythonQtConsoleApp, self).initialize(argv)
950 987 self.init_connection_file()
951 988 default_secure(self.config)
952 989 self.init_ssh()
953 990 self.init_kernel_manager()
954 991 self.init_qt_elements()
955 992 self.init_colors()
956 993 self.init_window_shortcut()
957 994
958 995 def init_window_shortcut(self):
959 996
960 997 self.prev_tab_act = QtGui.QAction("Pre&vious Tab",
961 998 self.window,
962 999 shortcut="Ctrl+PgDown",
963 1000 statusTip="Cahange to next tab",
964 1001 triggered=self.window.prev_tab)
965 1002
966 1003 self.next_tab_act = QtGui.QAction("Ne&xt Tab",
967 1004 self.window,
968 1005 shortcut="Ctrl+PgUp",
969 1006 statusTip="Cahange to next tab",
970 1007 triggered=self.window.next_tab)
971 1008
972 1009 self.fullScreenAct = QtGui.QAction("&Full Screen",
973 1010 self.window,
974 1011 shortcut="Ctrl+Meta+Space",
975 1012 statusTip="Toggle between Fullscreen and Normal Size",
976 1013 triggered=self.toggleFullScreen)
977 1014
978 1015 self.tabAndNewKernelAct =QtGui.QAction("Tab with &New kernel",
979 1016 self.window,
980 1017 shortcut="Ctrl+T",
981 1018 triggered=self.create_tab_with_new_frontend)
982 1019 self.window.window_menu.addAction(self.tabAndNewKernelAct)
983 1020 self.tabSameKernalAct =QtGui.QAction("Tab with Sa&me kernel",
984 1021 self.window,
985 1022 shortcut="Ctrl+Shift+T",
986 1023 triggered=self.create_tab_attached_to_current_tab_kernel)
987 1024 self.window.window_menu.addAction(self.tabSameKernalAct)
988 1025 self.window.window_menu.addSeparator()
989 1026
990 1027 self.onlineHelpAct = QtGui.QAction("Open Online &Help",
991 1028 self.window,
992 1029 triggered=self._open_online_help)
993 1030 self.window.help_menu.addAction(self.onlineHelpAct)
994 1031 # creating shortcut in menubar only for Mac OS as I don't
995 1032 # know the shortcut or if the windows manager assign it in
996 1033 # other platform.
997 1034 if sys.platform == 'darwin':
998 1035 self.minimizeAct = QtGui.QAction("Mini&mize",
999 1036 self.window,
1000 1037 shortcut="Ctrl+m",
1001 1038 statusTip="Minimize the window/Restore Normal Size",
1002 1039 triggered=self.toggleMinimized)
1003 1040 self.maximizeAct = QtGui.QAction("Ma&ximize",
1004 1041 self.window,
1005 1042 shortcut="Ctrl+Shift+M",
1006 1043 statusTip="Maximize the window/Restore Normal Size",
1007 1044 triggered=self.toggleMaximized)
1008 1045
1009 1046
1010 1047 self.window_menu = self.window.window_menu
1011 1048
1012 1049 self.window_menu.addAction(self.next_tab_act)
1013 1050 self.window_menu.addAction(self.prev_tab_act)
1014 1051 self.window_menu.addSeparator()
1015 1052 self.window_menu.addAction(self.minimizeAct)
1016 1053 self.window_menu.addAction(self.maximizeAct)
1017 1054 self.window_menu.addSeparator()
1018 1055 self.window_menu.addAction(self.fullScreenAct)
1019 1056
1020 1057 else:
1021 1058 # if we don't put it in a menu, we add it to the window so
1022 1059 # that it can still be triggerd by shortcut
1023 1060 self.window.addAction(self.fullScreenAct)
1024 1061
1025 1062 def toggleMinimized(self):
1026 1063 if not self.window.isMinimized():
1027 1064 self.window.showMinimized()
1028 1065 else:
1029 1066 self.window.showNormal()
1030 1067
1031 1068 def _open_online_help(self):
1032 QtGui.QDesktopServices.openUrl(
1033 QtCore.QUrl("http://ipython.org/documentation.html",
1034 QtCore.QUrl.TolerantMode)
1035 )
1069 filename="http://ipython.org/ipython-doc/stable/index.html"
1070 webbrowser.open(filename, new=1, autoraise=True)
1036 1071
1037 1072 def toggleMaximized(self):
1038 1073 if not self.window.isMaximized():
1039 1074 self.window.showMaximized()
1040 1075 else:
1041 1076 self.window.showNormal()
1042 1077
1043 1078 # Min/Max imizing while in full screen give a bug
1044 1079 # when going out of full screen, at least on OSX
1045 1080 def toggleFullScreen(self):
1046 1081 if not self.window.isFullScreen():
1047 1082 self.window.showFullScreen()
1048 1083 if sys.platform == 'darwin':
1049 1084 self.maximizeAct.setEnabled(False)
1050 1085 self.minimizeAct.setEnabled(False)
1051 1086 else:
1052 1087 self.window.showNormal()
1053 1088 if sys.platform == 'darwin':
1054 1089 self.maximizeAct.setEnabled(True)
1055 1090 self.minimizeAct.setEnabled(True)
1056 1091
1057 1092 def start(self):
1058 1093
1059 1094 # draw the window
1060 1095 self.window.show()
1061 1096
1062 1097 # Start the application main loop.
1063 1098 self.app.exec_()
1064 1099
1065 1100 #-----------------------------------------------------------------------------
1066 1101 # Main entry point
1067 1102 #-----------------------------------------------------------------------------
1068 1103
1069 1104 def main():
1070 1105 app = IPythonQtConsoleApp()
1071 1106 app.initialize()
1072 1107 app.start()
1073 1108
1074 1109
1075 1110 if __name__ == '__main__':
1076 1111 main()
General Comments 0
You need to be logged in to leave comments. Login now