##// END OF EJS Templates
remove again a few other occurences
Matthias BUSSONNIER -
Show More
@@ -1,725 +1,724 b''
1 # coding: utf-8
1 # coding: utf-8
2 """A tornado based IPython notebook server.
2 """A tornado based IPython notebook server.
3
3
4 Authors:
4 Authors:
5
5
6 * Brian Granger
6 * Brian Granger
7 """
7 """
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2013 The IPython Development Team
9 # Copyright (C) 2013 The IPython Development Team
10 #
10 #
11 # Distributed under the terms of the BSD License. The full license is in
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
12 # the file COPYING, distributed as part of this software.
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 # Imports
16 # Imports
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19 # stdlib
19 # stdlib
20 import errno
20 import errno
21 import logging
21 import logging
22 import os
22 import os
23 import random
23 import random
24 import select
24 import select
25 import signal
25 import signal
26 import socket
26 import socket
27 import sys
27 import sys
28 import threading
28 import threading
29 import time
29 import time
30 import webbrowser
30 import webbrowser
31
31
32
32
33 # Third party
33 # Third party
34 # check for pyzmq 2.1.11
34 # check for pyzmq 2.1.11
35 from IPython.utils.zmqrelated import check_for_zmq
35 from IPython.utils.zmqrelated import check_for_zmq
36 check_for_zmq('2.1.11', 'IPython.html')
36 check_for_zmq('2.1.11', 'IPython.html')
37
37
38 from jinja2 import Environment, FileSystemLoader
38 from jinja2 import Environment, FileSystemLoader
39
39
40 # Install the pyzmq ioloop. This has to be done before anything else from
40 # Install the pyzmq ioloop. This has to be done before anything else from
41 # tornado is imported.
41 # tornado is imported.
42 from zmq.eventloop import ioloop
42 from zmq.eventloop import ioloop
43 ioloop.install()
43 ioloop.install()
44
44
45 # check for tornado 2.1.0
45 # check for tornado 2.1.0
46 msg = "The IPython Notebook requires tornado >= 2.1.0"
46 msg = "The IPython Notebook requires tornado >= 2.1.0"
47 try:
47 try:
48 import tornado
48 import tornado
49 except ImportError:
49 except ImportError:
50 raise ImportError(msg)
50 raise ImportError(msg)
51 try:
51 try:
52 version_info = tornado.version_info
52 version_info = tornado.version_info
53 except AttributeError:
53 except AttributeError:
54 raise ImportError(msg + ", but you have < 1.1.0")
54 raise ImportError(msg + ", but you have < 1.1.0")
55 if version_info < (2,1,0):
55 if version_info < (2,1,0):
56 raise ImportError(msg + ", but you have %s" % tornado.version)
56 raise ImportError(msg + ", but you have %s" % tornado.version)
57
57
58 from tornado import httpserver
58 from tornado import httpserver
59 from tornado import web
59 from tornado import web
60
60
61 # Our own libraries
61 # Our own libraries
62 from IPython.html import DEFAULT_STATIC_FILES_PATH
62 from IPython.html import DEFAULT_STATIC_FILES_PATH
63
63
64 from .services.kernels.kernelmanager import MappingKernelManager
64 from .services.kernels.kernelmanager import MappingKernelManager
65 from .services.notebooks.nbmanager import NotebookManager
65 from .services.notebooks.nbmanager import NotebookManager
66 from .services.notebooks.filenbmanager import FileNotebookManager
66 from .services.notebooks.filenbmanager import FileNotebookManager
67 from .services.clusters.clustermanager import ClusterManager
67 from .services.clusters.clustermanager import ClusterManager
68
68
69 from .base.handlers import AuthenticatedFileHandler, FileFindHandler
69 from .base.handlers import AuthenticatedFileHandler, FileFindHandler
70
70
71 from IPython.config.application import catch_config_error, boolean_flag
71 from IPython.config.application import catch_config_error, boolean_flag
72 from IPython.core.application import BaseIPythonApplication
72 from IPython.core.application import BaseIPythonApplication
73 from IPython.consoleapp import IPythonConsoleApp
73 from IPython.consoleapp import IPythonConsoleApp
74 from IPython.kernel import swallow_argv
74 from IPython.kernel import swallow_argv
75 from IPython.kernel.zmq.session import default_secure
75 from IPython.kernel.zmq.session import default_secure
76 from IPython.kernel.zmq.kernelapp import (
76 from IPython.kernel.zmq.kernelapp import (
77 kernel_flags,
77 kernel_flags,
78 kernel_aliases,
78 kernel_aliases,
79 )
79 )
80 from IPython.utils.importstring import import_item
80 from IPython.utils.importstring import import_item
81 from IPython.utils.localinterfaces import LOCALHOST
81 from IPython.utils.localinterfaces import LOCALHOST
82 from IPython.utils import submodule
82 from IPython.utils import submodule
83 from IPython.utils.traitlets import (
83 from IPython.utils.traitlets import (
84 Dict, Unicode, Integer, List, Bool, Bytes,
84 Dict, Unicode, Integer, List, Bool, Bytes,
85 DottedObjectName
85 DottedObjectName
86 )
86 )
87 from IPython.utils import py3compat
87 from IPython.utils import py3compat
88 from IPython.utils.path import filefind
88 from IPython.utils.path import filefind
89
89
90 from .utils import url_path_join
90 from .utils import url_path_join
91
91
92 #-----------------------------------------------------------------------------
92 #-----------------------------------------------------------------------------
93 # Module globals
93 # Module globals
94 #-----------------------------------------------------------------------------
94 #-----------------------------------------------------------------------------
95
95
96 _examples = """
96 _examples = """
97 ipython notebook # start the notebook
97 ipython notebook # start the notebook
98 ipython notebook --profile=sympy # use the sympy profile
98 ipython notebook --profile=sympy # use the sympy profile
99 ipython notebook --pylab=inline # pylab in inline plotting mode
99 ipython notebook --matplotlib=inline # inline plotting mode
100 ipython notebook --certfile=mycert.pem # use SSL/TLS certificate
100 ipython notebook --certfile=mycert.pem # use SSL/TLS certificate
101 ipython notebook --port=5555 --ip=* # Listen on port 5555, all interfaces
102 """
101 """
103
102
104 #-----------------------------------------------------------------------------
103 #-----------------------------------------------------------------------------
105 # Helper functions
104 # Helper functions
106 #-----------------------------------------------------------------------------
105 #-----------------------------------------------------------------------------
107
106
108 def random_ports(port, n):
107 def random_ports(port, n):
109 """Generate a list of n random ports near the given port.
108 """Generate a list of n random ports near the given port.
110
109
111 The first 5 ports will be sequential, and the remaining n-5 will be
110 The first 5 ports will be sequential, and the remaining n-5 will be
112 randomly selected in the range [port-2*n, port+2*n].
111 randomly selected in the range [port-2*n, port+2*n].
113 """
112 """
114 for i in range(min(5, n)):
113 for i in range(min(5, n)):
115 yield port + i
114 yield port + i
116 for i in range(n-5):
115 for i in range(n-5):
117 yield port + random.randint(-2*n, 2*n)
116 yield port + random.randint(-2*n, 2*n)
118
117
119 def load_handlers(name):
118 def load_handlers(name):
120 """Load the (URL pattern, handler) tuples for each component."""
119 """Load the (URL pattern, handler) tuples for each component."""
121 name = 'IPython.html.' + name
120 name = 'IPython.html.' + name
122 mod = __import__(name, fromlist=['default_handlers'])
121 mod = __import__(name, fromlist=['default_handlers'])
123 return mod.default_handlers
122 return mod.default_handlers
124
123
125 #-----------------------------------------------------------------------------
124 #-----------------------------------------------------------------------------
126 # The Tornado web application
125 # The Tornado web application
127 #-----------------------------------------------------------------------------
126 #-----------------------------------------------------------------------------
128
127
129 class NotebookWebApplication(web.Application):
128 class NotebookWebApplication(web.Application):
130
129
131 def __init__(self, ipython_app, kernel_manager, notebook_manager,
130 def __init__(self, ipython_app, kernel_manager, notebook_manager,
132 cluster_manager, log,
131 cluster_manager, log,
133 base_project_url, settings_overrides):
132 base_project_url, settings_overrides):
134
133
135 settings = self.init_settings(
134 settings = self.init_settings(
136 ipython_app, kernel_manager, notebook_manager, cluster_manager,
135 ipython_app, kernel_manager, notebook_manager, cluster_manager,
137 log, base_project_url, settings_overrides)
136 log, base_project_url, settings_overrides)
138 handlers = self.init_handlers(settings)
137 handlers = self.init_handlers(settings)
139
138
140 super(NotebookWebApplication, self).__init__(handlers, **settings)
139 super(NotebookWebApplication, self).__init__(handlers, **settings)
141
140
142 def init_settings(self, ipython_app, kernel_manager, notebook_manager,
141 def init_settings(self, ipython_app, kernel_manager, notebook_manager,
143 cluster_manager, log,
142 cluster_manager, log,
144 base_project_url, settings_overrides):
143 base_project_url, settings_overrides):
145 # Python < 2.6.5 doesn't accept unicode keys in f(**kwargs), and
144 # Python < 2.6.5 doesn't accept unicode keys in f(**kwargs), and
146 # base_project_url will always be unicode, which will in turn
145 # base_project_url will always be unicode, which will in turn
147 # make the patterns unicode, and ultimately result in unicode
146 # make the patterns unicode, and ultimately result in unicode
148 # keys in kwargs to handler._execute(**kwargs) in tornado.
147 # keys in kwargs to handler._execute(**kwargs) in tornado.
149 # This enforces that base_project_url be ascii in that situation.
148 # This enforces that base_project_url be ascii in that situation.
150 #
149 #
151 # Note that the URLs these patterns check against are escaped,
150 # Note that the URLs these patterns check against are escaped,
152 # and thus guaranteed to be ASCII: 'hΓ©llo' is really 'h%C3%A9llo'.
151 # and thus guaranteed to be ASCII: 'hΓ©llo' is really 'h%C3%A9llo'.
153 base_project_url = py3compat.unicode_to_str(base_project_url, 'ascii')
152 base_project_url = py3compat.unicode_to_str(base_project_url, 'ascii')
154 template_path = os.path.join(os.path.dirname(__file__), "templates")
153 template_path = os.path.join(os.path.dirname(__file__), "templates")
155 settings = dict(
154 settings = dict(
156 # basics
155 # basics
157 base_project_url=base_project_url,
156 base_project_url=base_project_url,
158 base_kernel_url=ipython_app.base_kernel_url,
157 base_kernel_url=ipython_app.base_kernel_url,
159 template_path=template_path,
158 template_path=template_path,
160 static_path=ipython_app.static_file_path,
159 static_path=ipython_app.static_file_path,
161 static_handler_class = FileFindHandler,
160 static_handler_class = FileFindHandler,
162 static_url_prefix = url_path_join(base_project_url,'/static/'),
161 static_url_prefix = url_path_join(base_project_url,'/static/'),
163
162
164 # authentication
163 # authentication
165 cookie_secret=ipython_app.cookie_secret,
164 cookie_secret=ipython_app.cookie_secret,
166 login_url=url_path_join(base_project_url,'/login'),
165 login_url=url_path_join(base_project_url,'/login'),
167 password=ipython_app.password,
166 password=ipython_app.password,
168
167
169 # managers
168 # managers
170 kernel_manager=kernel_manager,
169 kernel_manager=kernel_manager,
171 notebook_manager=notebook_manager,
170 notebook_manager=notebook_manager,
172 cluster_manager=cluster_manager,
171 cluster_manager=cluster_manager,
173
172
174 # IPython stuff
173 # IPython stuff
175 mathjax_url=ipython_app.mathjax_url,
174 mathjax_url=ipython_app.mathjax_url,
176 config=ipython_app.config,
175 config=ipython_app.config,
177 use_less=ipython_app.use_less,
176 use_less=ipython_app.use_less,
178 jinja2_env=Environment(loader=FileSystemLoader(template_path)),
177 jinja2_env=Environment(loader=FileSystemLoader(template_path)),
179 )
178 )
180
179
181 # allow custom overrides for the tornado web app.
180 # allow custom overrides for the tornado web app.
182 settings.update(settings_overrides)
181 settings.update(settings_overrides)
183 return settings
182 return settings
184
183
185 def init_handlers(self, settings):
184 def init_handlers(self, settings):
186 # Load the (URL pattern, handler) tuples for each component.
185 # Load the (URL pattern, handler) tuples for each component.
187 handlers = []
186 handlers = []
188 handlers.extend(load_handlers('base.handlers'))
187 handlers.extend(load_handlers('base.handlers'))
189 handlers.extend(load_handlers('tree.handlers'))
188 handlers.extend(load_handlers('tree.handlers'))
190 handlers.extend(load_handlers('auth.login'))
189 handlers.extend(load_handlers('auth.login'))
191 handlers.extend(load_handlers('auth.logout'))
190 handlers.extend(load_handlers('auth.logout'))
192 handlers.extend(load_handlers('notebook.handlers'))
191 handlers.extend(load_handlers('notebook.handlers'))
193 handlers.extend(load_handlers('services.kernels.handlers'))
192 handlers.extend(load_handlers('services.kernels.handlers'))
194 handlers.extend(load_handlers('services.notebooks.handlers'))
193 handlers.extend(load_handlers('services.notebooks.handlers'))
195 handlers.extend(load_handlers('services.clusters.handlers'))
194 handlers.extend(load_handlers('services.clusters.handlers'))
196 handlers.extend([
195 handlers.extend([
197 (r"/files/(.*)", AuthenticatedFileHandler, {'path' : settings['notebook_manager'].notebook_dir}),
196 (r"/files/(.*)", AuthenticatedFileHandler, {'path' : settings['notebook_manager'].notebook_dir}),
198 ])
197 ])
199 # prepend base_project_url onto the patterns that we match
198 # prepend base_project_url onto the patterns that we match
200 new_handlers = []
199 new_handlers = []
201 for handler in handlers:
200 for handler in handlers:
202 pattern = url_path_join(settings['base_project_url'], handler[0])
201 pattern = url_path_join(settings['base_project_url'], handler[0])
203 new_handler = tuple([pattern] + list(handler[1:]))
202 new_handler = tuple([pattern] + list(handler[1:]))
204 new_handlers.append(new_handler)
203 new_handlers.append(new_handler)
205 return new_handlers
204 return new_handlers
206
205
207
206
208
207
209 #-----------------------------------------------------------------------------
208 #-----------------------------------------------------------------------------
210 # Aliases and Flags
209 # Aliases and Flags
211 #-----------------------------------------------------------------------------
210 #-----------------------------------------------------------------------------
212
211
213 flags = dict(kernel_flags)
212 flags = dict(kernel_flags)
214 flags['no-browser']=(
213 flags['no-browser']=(
215 {'NotebookApp' : {'open_browser' : False}},
214 {'NotebookApp' : {'open_browser' : False}},
216 "Don't open the notebook in a browser after startup."
215 "Don't open the notebook in a browser after startup."
217 )
216 )
218 flags['no-mathjax']=(
217 flags['no-mathjax']=(
219 {'NotebookApp' : {'enable_mathjax' : False}},
218 {'NotebookApp' : {'enable_mathjax' : False}},
220 """Disable MathJax
219 """Disable MathJax
221
220
222 MathJax is the javascript library IPython uses to render math/LaTeX. It is
221 MathJax is the javascript library IPython uses to render math/LaTeX. It is
223 very large, so you may want to disable it if you have a slow internet
222 very large, so you may want to disable it if you have a slow internet
224 connection, or for offline use of the notebook.
223 connection, or for offline use of the notebook.
225
224
226 When disabled, equations etc. will appear as their untransformed TeX source.
225 When disabled, equations etc. will appear as their untransformed TeX source.
227 """
226 """
228 )
227 )
229
228
230 # Add notebook manager flags
229 # Add notebook manager flags
231 flags.update(boolean_flag('script', 'FileNotebookManager.save_script',
230 flags.update(boolean_flag('script', 'FileNotebookManager.save_script',
232 'Auto-save a .py script everytime the .ipynb notebook is saved',
231 'Auto-save a .py script everytime the .ipynb notebook is saved',
233 'Do not auto-save .py scripts for every notebook'))
232 'Do not auto-save .py scripts for every notebook'))
234
233
235 # the flags that are specific to the frontend
234 # the flags that are specific to the frontend
236 # these must be scrubbed before being passed to the kernel,
235 # these must be scrubbed before being passed to the kernel,
237 # or it will raise an error on unrecognized flags
236 # or it will raise an error on unrecognized flags
238 notebook_flags = ['no-browser', 'no-mathjax', 'script', 'no-script']
237 notebook_flags = ['no-browser', 'no-mathjax', 'script', 'no-script']
239
238
240 aliases = dict(kernel_aliases)
239 aliases = dict(kernel_aliases)
241
240
242 aliases.update({
241 aliases.update({
243 'ip': 'NotebookApp.ip',
242 'ip': 'NotebookApp.ip',
244 'port': 'NotebookApp.port',
243 'port': 'NotebookApp.port',
245 'port-retries': 'NotebookApp.port_retries',
244 'port-retries': 'NotebookApp.port_retries',
246 'transport': 'KernelManager.transport',
245 'transport': 'KernelManager.transport',
247 'keyfile': 'NotebookApp.keyfile',
246 'keyfile': 'NotebookApp.keyfile',
248 'certfile': 'NotebookApp.certfile',
247 'certfile': 'NotebookApp.certfile',
249 'notebook-dir': 'NotebookManager.notebook_dir',
248 'notebook-dir': 'NotebookManager.notebook_dir',
250 'browser': 'NotebookApp.browser',
249 'browser': 'NotebookApp.browser',
251 })
250 })
252
251
253 # remove ipkernel flags that are singletons, and don't make sense in
252 # remove ipkernel flags that are singletons, and don't make sense in
254 # multi-kernel evironment:
253 # multi-kernel evironment:
255 aliases.pop('f', None)
254 aliases.pop('f', None)
256
255
257 notebook_aliases = [u'port', u'port-retries', u'ip', u'keyfile', u'certfile',
256 notebook_aliases = [u'port', u'port-retries', u'ip', u'keyfile', u'certfile',
258 u'notebook-dir']
257 u'notebook-dir']
259
258
260 #-----------------------------------------------------------------------------
259 #-----------------------------------------------------------------------------
261 # NotebookApp
260 # NotebookApp
262 #-----------------------------------------------------------------------------
261 #-----------------------------------------------------------------------------
263
262
264 class NotebookApp(BaseIPythonApplication):
263 class NotebookApp(BaseIPythonApplication):
265
264
266 name = 'ipython-notebook'
265 name = 'ipython-notebook'
267
266
268 description = """
267 description = """
269 The IPython HTML Notebook.
268 The IPython HTML Notebook.
270
269
271 This launches a Tornado based HTML Notebook Server that serves up an
270 This launches a Tornado based HTML Notebook Server that serves up an
272 HTML5/Javascript Notebook client.
271 HTML5/Javascript Notebook client.
273 """
272 """
274 examples = _examples
273 examples = _examples
275
274
276 classes = IPythonConsoleApp.classes + [MappingKernelManager, NotebookManager,
275 classes = IPythonConsoleApp.classes + [MappingKernelManager, NotebookManager,
277 FileNotebookManager]
276 FileNotebookManager]
278 flags = Dict(flags)
277 flags = Dict(flags)
279 aliases = Dict(aliases)
278 aliases = Dict(aliases)
280
279
281 kernel_argv = List(Unicode)
280 kernel_argv = List(Unicode)
282
281
283 def _log_level_default(self):
282 def _log_level_default(self):
284 return logging.INFO
283 return logging.INFO
285
284
286 def _log_format_default(self):
285 def _log_format_default(self):
287 """override default log format to include time"""
286 """override default log format to include time"""
288 return u"%(asctime)s.%(msecs).03d [%(name)s]%(highlevel)s %(message)s"
287 return u"%(asctime)s.%(msecs).03d [%(name)s]%(highlevel)s %(message)s"
289
288
290 # create requested profiles by default, if they don't exist:
289 # create requested profiles by default, if they don't exist:
291 auto_create = Bool(True)
290 auto_create = Bool(True)
292
291
293 # file to be opened in the notebook server
292 # file to be opened in the notebook server
294 file_to_run = Unicode('')
293 file_to_run = Unicode('')
295
294
296 # Network related information.
295 # Network related information.
297
296
298 ip = Unicode(LOCALHOST, config=True,
297 ip = Unicode(LOCALHOST, config=True,
299 help="The IP address the notebook server will listen on."
298 help="The IP address the notebook server will listen on."
300 )
299 )
301
300
302 def _ip_changed(self, name, old, new):
301 def _ip_changed(self, name, old, new):
303 if new == u'*': self.ip = u''
302 if new == u'*': self.ip = u''
304
303
305 port = Integer(8888, config=True,
304 port = Integer(8888, config=True,
306 help="The port the notebook server will listen on."
305 help="The port the notebook server will listen on."
307 )
306 )
308 port_retries = Integer(50, config=True,
307 port_retries = Integer(50, config=True,
309 help="The number of additional ports to try if the specified port is not available."
308 help="The number of additional ports to try if the specified port is not available."
310 )
309 )
311
310
312 certfile = Unicode(u'', config=True,
311 certfile = Unicode(u'', config=True,
313 help="""The full path to an SSL/TLS certificate file."""
312 help="""The full path to an SSL/TLS certificate file."""
314 )
313 )
315
314
316 keyfile = Unicode(u'', config=True,
315 keyfile = Unicode(u'', config=True,
317 help="""The full path to a private key file for usage with SSL/TLS."""
316 help="""The full path to a private key file for usage with SSL/TLS."""
318 )
317 )
319
318
320 cookie_secret = Bytes(b'', config=True,
319 cookie_secret = Bytes(b'', config=True,
321 help="""The random bytes used to secure cookies.
320 help="""The random bytes used to secure cookies.
322 By default this is a new random number every time you start the Notebook.
321 By default this is a new random number every time you start the Notebook.
323 Set it to a value in a config file to enable logins to persist across server sessions.
322 Set it to a value in a config file to enable logins to persist across server sessions.
324
323
325 Note: Cookie secrets should be kept private, do not share config files with
324 Note: Cookie secrets should be kept private, do not share config files with
326 cookie_secret stored in plaintext (you can read the value from a file).
325 cookie_secret stored in plaintext (you can read the value from a file).
327 """
326 """
328 )
327 )
329 def _cookie_secret_default(self):
328 def _cookie_secret_default(self):
330 return os.urandom(1024)
329 return os.urandom(1024)
331
330
332 password = Unicode(u'', config=True,
331 password = Unicode(u'', config=True,
333 help="""Hashed password to use for web authentication.
332 help="""Hashed password to use for web authentication.
334
333
335 To generate, type in a python/IPython shell:
334 To generate, type in a python/IPython shell:
336
335
337 from IPython.lib import passwd; passwd()
336 from IPython.lib import passwd; passwd()
338
337
339 The string should be of the form type:salt:hashed-password.
338 The string should be of the form type:salt:hashed-password.
340 """
339 """
341 )
340 )
342
341
343 open_browser = Bool(True, config=True,
342 open_browser = Bool(True, config=True,
344 help="""Whether to open in a browser after starting.
343 help="""Whether to open in a browser after starting.
345 The specific browser used is platform dependent and
344 The specific browser used is platform dependent and
346 determined by the python standard library `webbrowser`
345 determined by the python standard library `webbrowser`
347 module, unless it is overridden using the --browser
346 module, unless it is overridden using the --browser
348 (NotebookApp.browser) configuration option.
347 (NotebookApp.browser) configuration option.
349 """)
348 """)
350
349
351 browser = Unicode(u'', config=True,
350 browser = Unicode(u'', config=True,
352 help="""Specify what command to use to invoke a web
351 help="""Specify what command to use to invoke a web
353 browser when opening the notebook. If not specified, the
352 browser when opening the notebook. If not specified, the
354 default browser will be determined by the `webbrowser`
353 default browser will be determined by the `webbrowser`
355 standard library module, which allows setting of the
354 standard library module, which allows setting of the
356 BROWSER environment variable to override it.
355 BROWSER environment variable to override it.
357 """)
356 """)
358
357
359 use_less = Bool(False, config=True,
358 use_less = Bool(False, config=True,
360 help="""Wether to use Browser Side less-css parsing
359 help="""Wether to use Browser Side less-css parsing
361 instead of compiled css version in templates that allows
360 instead of compiled css version in templates that allows
362 it. This is mainly convenient when working on the less
361 it. This is mainly convenient when working on the less
363 file to avoid a build step, or if user want to overwrite
362 file to avoid a build step, or if user want to overwrite
364 some of the less variables without having to recompile
363 some of the less variables without having to recompile
365 everything.
364 everything.
366
365
367 You will need to install the less.js component in the static directory
366 You will need to install the less.js component in the static directory
368 either in the source tree or in your profile folder.
367 either in the source tree or in your profile folder.
369 """)
368 """)
370
369
371 webapp_settings = Dict(config=True,
370 webapp_settings = Dict(config=True,
372 help="Supply overrides for the tornado.web.Application that the "
371 help="Supply overrides for the tornado.web.Application that the "
373 "IPython notebook uses.")
372 "IPython notebook uses.")
374
373
375 enable_mathjax = Bool(True, config=True,
374 enable_mathjax = Bool(True, config=True,
376 help="""Whether to enable MathJax for typesetting math/TeX
375 help="""Whether to enable MathJax for typesetting math/TeX
377
376
378 MathJax is the javascript library IPython uses to render math/LaTeX. It is
377 MathJax is the javascript library IPython uses to render math/LaTeX. It is
379 very large, so you may want to disable it if you have a slow internet
378 very large, so you may want to disable it if you have a slow internet
380 connection, or for offline use of the notebook.
379 connection, or for offline use of the notebook.
381
380
382 When disabled, equations etc. will appear as their untransformed TeX source.
381 When disabled, equations etc. will appear as their untransformed TeX source.
383 """
382 """
384 )
383 )
385 def _enable_mathjax_changed(self, name, old, new):
384 def _enable_mathjax_changed(self, name, old, new):
386 """set mathjax url to empty if mathjax is disabled"""
385 """set mathjax url to empty if mathjax is disabled"""
387 if not new:
386 if not new:
388 self.mathjax_url = u''
387 self.mathjax_url = u''
389
388
390 base_project_url = Unicode('/', config=True,
389 base_project_url = Unicode('/', config=True,
391 help='''The base URL for the notebook server.
390 help='''The base URL for the notebook server.
392
391
393 Leading and trailing slashes can be omitted,
392 Leading and trailing slashes can be omitted,
394 and will automatically be added.
393 and will automatically be added.
395 ''')
394 ''')
396 def _base_project_url_changed(self, name, old, new):
395 def _base_project_url_changed(self, name, old, new):
397 if not new.startswith('/'):
396 if not new.startswith('/'):
398 self.base_project_url = '/'+new
397 self.base_project_url = '/'+new
399 elif not new.endswith('/'):
398 elif not new.endswith('/'):
400 self.base_project_url = new+'/'
399 self.base_project_url = new+'/'
401
400
402 base_kernel_url = Unicode('/', config=True,
401 base_kernel_url = Unicode('/', config=True,
403 help='''The base URL for the kernel server
402 help='''The base URL for the kernel server
404
403
405 Leading and trailing slashes can be omitted,
404 Leading and trailing slashes can be omitted,
406 and will automatically be added.
405 and will automatically be added.
407 ''')
406 ''')
408 def _base_kernel_url_changed(self, name, old, new):
407 def _base_kernel_url_changed(self, name, old, new):
409 if not new.startswith('/'):
408 if not new.startswith('/'):
410 self.base_kernel_url = '/'+new
409 self.base_kernel_url = '/'+new
411 elif not new.endswith('/'):
410 elif not new.endswith('/'):
412 self.base_kernel_url = new+'/'
411 self.base_kernel_url = new+'/'
413
412
414 websocket_url = Unicode("", config=True,
413 websocket_url = Unicode("", config=True,
415 help="""The base URL for the websocket server,
414 help="""The base URL for the websocket server,
416 if it differs from the HTTP server (hint: it almost certainly doesn't).
415 if it differs from the HTTP server (hint: it almost certainly doesn't).
417
416
418 Should be in the form of an HTTP origin: ws[s]://hostname[:port]
417 Should be in the form of an HTTP origin: ws[s]://hostname[:port]
419 """
418 """
420 )
419 )
421
420
422 extra_static_paths = List(Unicode, config=True,
421 extra_static_paths = List(Unicode, config=True,
423 help="""Extra paths to search for serving static files.
422 help="""Extra paths to search for serving static files.
424
423
425 This allows adding javascript/css to be available from the notebook server machine,
424 This allows adding javascript/css to be available from the notebook server machine,
426 or overriding individual files in the IPython"""
425 or overriding individual files in the IPython"""
427 )
426 )
428 def _extra_static_paths_default(self):
427 def _extra_static_paths_default(self):
429 return [os.path.join(self.profile_dir.location, 'static')]
428 return [os.path.join(self.profile_dir.location, 'static')]
430
429
431 @property
430 @property
432 def static_file_path(self):
431 def static_file_path(self):
433 """return extra paths + the default location"""
432 """return extra paths + the default location"""
434 return self.extra_static_paths + [DEFAULT_STATIC_FILES_PATH]
433 return self.extra_static_paths + [DEFAULT_STATIC_FILES_PATH]
435
434
436 mathjax_url = Unicode("", config=True,
435 mathjax_url = Unicode("", config=True,
437 help="""The url for MathJax.js."""
436 help="""The url for MathJax.js."""
438 )
437 )
439 def _mathjax_url_default(self):
438 def _mathjax_url_default(self):
440 if not self.enable_mathjax:
439 if not self.enable_mathjax:
441 return u''
440 return u''
442 static_url_prefix = self.webapp_settings.get("static_url_prefix",
441 static_url_prefix = self.webapp_settings.get("static_url_prefix",
443 url_path_join(self.base_project_url, "static")
442 url_path_join(self.base_project_url, "static")
444 )
443 )
445 try:
444 try:
446 mathjax = filefind(os.path.join('mathjax', 'MathJax.js'), self.static_file_path)
445 mathjax = filefind(os.path.join('mathjax', 'MathJax.js'), self.static_file_path)
447 except IOError:
446 except IOError:
448 if self.certfile:
447 if self.certfile:
449 # HTTPS: load from Rackspace CDN, because SSL certificate requires it
448 # HTTPS: load from Rackspace CDN, because SSL certificate requires it
450 base = u"https://c328740.ssl.cf1.rackcdn.com"
449 base = u"https://c328740.ssl.cf1.rackcdn.com"
451 else:
450 else:
452 base = u"http://cdn.mathjax.org"
451 base = u"http://cdn.mathjax.org"
453
452
454 url = base + u"/mathjax/latest/MathJax.js"
453 url = base + u"/mathjax/latest/MathJax.js"
455 self.log.info("Using MathJax from CDN: %s", url)
454 self.log.info("Using MathJax from CDN: %s", url)
456 return url
455 return url
457 else:
456 else:
458 self.log.info("Using local MathJax from %s" % mathjax)
457 self.log.info("Using local MathJax from %s" % mathjax)
459 return url_path_join(static_url_prefix, u"mathjax/MathJax.js")
458 return url_path_join(static_url_prefix, u"mathjax/MathJax.js")
460
459
461 def _mathjax_url_changed(self, name, old, new):
460 def _mathjax_url_changed(self, name, old, new):
462 if new and not self.enable_mathjax:
461 if new and not self.enable_mathjax:
463 # enable_mathjax=False overrides mathjax_url
462 # enable_mathjax=False overrides mathjax_url
464 self.mathjax_url = u''
463 self.mathjax_url = u''
465 else:
464 else:
466 self.log.info("Using MathJax: %s", new)
465 self.log.info("Using MathJax: %s", new)
467
466
468 notebook_manager_class = DottedObjectName('IPython.html.services.notebooks.filenbmanager.FileNotebookManager',
467 notebook_manager_class = DottedObjectName('IPython.html.services.notebooks.filenbmanager.FileNotebookManager',
469 config=True,
468 config=True,
470 help='The notebook manager class to use.')
469 help='The notebook manager class to use.')
471
470
472 trust_xheaders = Bool(False, config=True,
471 trust_xheaders = Bool(False, config=True,
473 help=("Whether to trust or not X-Scheme/X-Forwarded-Proto and X-Real-Ip/X-Forwarded-For headers"
472 help=("Whether to trust or not X-Scheme/X-Forwarded-Proto and X-Real-Ip/X-Forwarded-For headers"
474 "sent by the upstream reverse proxy. Neccesary if the proxy handles SSL")
473 "sent by the upstream reverse proxy. Neccesary if the proxy handles SSL")
475 )
474 )
476
475
477 def parse_command_line(self, argv=None):
476 def parse_command_line(self, argv=None):
478 super(NotebookApp, self).parse_command_line(argv)
477 super(NotebookApp, self).parse_command_line(argv)
479 if argv is None:
478 if argv is None:
480 argv = sys.argv[1:]
479 argv = sys.argv[1:]
481
480
482 # Scrub frontend-specific flags
481 # Scrub frontend-specific flags
483 self.kernel_argv = swallow_argv(argv, notebook_aliases, notebook_flags)
482 self.kernel_argv = swallow_argv(argv, notebook_aliases, notebook_flags)
484 # Kernel should inherit default config file from frontend
483 # Kernel should inherit default config file from frontend
485 self.kernel_argv.append("--IPKernelApp.parent_appname='%s'" % self.name)
484 self.kernel_argv.append("--IPKernelApp.parent_appname='%s'" % self.name)
486
485
487 if self.extra_args:
486 if self.extra_args:
488 f = os.path.abspath(self.extra_args[0])
487 f = os.path.abspath(self.extra_args[0])
489 if os.path.isdir(f):
488 if os.path.isdir(f):
490 nbdir = f
489 nbdir = f
491 else:
490 else:
492 self.file_to_run = f
491 self.file_to_run = f
493 nbdir = os.path.dirname(f)
492 nbdir = os.path.dirname(f)
494 self.config.NotebookManager.notebook_dir = nbdir
493 self.config.NotebookManager.notebook_dir = nbdir
495
494
496 def init_configurables(self):
495 def init_configurables(self):
497 # force Session default to be secure
496 # force Session default to be secure
498 default_secure(self.config)
497 default_secure(self.config)
499 self.kernel_manager = MappingKernelManager(
498 self.kernel_manager = MappingKernelManager(
500 parent=self, log=self.log, kernel_argv=self.kernel_argv,
499 parent=self, log=self.log, kernel_argv=self.kernel_argv,
501 connection_dir = self.profile_dir.security_dir,
500 connection_dir = self.profile_dir.security_dir,
502 )
501 )
503 kls = import_item(self.notebook_manager_class)
502 kls = import_item(self.notebook_manager_class)
504 self.notebook_manager = kls(parent=self, log=self.log)
503 self.notebook_manager = kls(parent=self, log=self.log)
505 self.notebook_manager.load_notebook_names()
504 self.notebook_manager.load_notebook_names()
506 self.cluster_manager = ClusterManager(parent=self, log=self.log)
505 self.cluster_manager = ClusterManager(parent=self, log=self.log)
507 self.cluster_manager.update_profiles()
506 self.cluster_manager.update_profiles()
508
507
509 def init_logging(self):
508 def init_logging(self):
510 # This prevents double log messages because tornado use a root logger that
509 # This prevents double log messages because tornado use a root logger that
511 # self.log is a child of. The logging module dipatches log messages to a log
510 # self.log is a child of. The logging module dipatches log messages to a log
512 # and all of its ancenstors until propagate is set to False.
511 # and all of its ancenstors until propagate is set to False.
513 self.log.propagate = False
512 self.log.propagate = False
514
513
515 # hook up tornado 3's loggers to our app handlers
514 # hook up tornado 3's loggers to our app handlers
516 for name in ('access', 'application', 'general'):
515 for name in ('access', 'application', 'general'):
517 logging.getLogger('tornado.%s' % name).handlers = self.log.handlers
516 logging.getLogger('tornado.%s' % name).handlers = self.log.handlers
518
517
519 def init_webapp(self):
518 def init_webapp(self):
520 """initialize tornado webapp and httpserver"""
519 """initialize tornado webapp and httpserver"""
521 self.web_app = NotebookWebApplication(
520 self.web_app = NotebookWebApplication(
522 self, self.kernel_manager, self.notebook_manager,
521 self, self.kernel_manager, self.notebook_manager,
523 self.cluster_manager, self.log,
522 self.cluster_manager, self.log,
524 self.base_project_url, self.webapp_settings
523 self.base_project_url, self.webapp_settings
525 )
524 )
526 if self.certfile:
525 if self.certfile:
527 ssl_options = dict(certfile=self.certfile)
526 ssl_options = dict(certfile=self.certfile)
528 if self.keyfile:
527 if self.keyfile:
529 ssl_options['keyfile'] = self.keyfile
528 ssl_options['keyfile'] = self.keyfile
530 else:
529 else:
531 ssl_options = None
530 ssl_options = None
532 self.web_app.password = self.password
531 self.web_app.password = self.password
533 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options,
532 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options,
534 xheaders=self.trust_xheaders)
533 xheaders=self.trust_xheaders)
535 if not self.ip:
534 if not self.ip:
536 warning = "WARNING: The notebook server is listening on all IP addresses"
535 warning = "WARNING: The notebook server is listening on all IP addresses"
537 if ssl_options is None:
536 if ssl_options is None:
538 self.log.critical(warning + " and not using encryption. This "
537 self.log.critical(warning + " and not using encryption. This "
539 "is not recommended.")
538 "is not recommended.")
540 if not self.password:
539 if not self.password:
541 self.log.critical(warning + " and not using authentication. "
540 self.log.critical(warning + " and not using authentication. "
542 "This is highly insecure and not recommended.")
541 "This is highly insecure and not recommended.")
543 success = None
542 success = None
544 for port in random_ports(self.port, self.port_retries+1):
543 for port in random_ports(self.port, self.port_retries+1):
545 try:
544 try:
546 self.http_server.listen(port, self.ip)
545 self.http_server.listen(port, self.ip)
547 except socket.error as e:
546 except socket.error as e:
548 # XXX: remove the e.errno == -9 block when we require
547 # XXX: remove the e.errno == -9 block when we require
549 # tornado >= 3.0
548 # tornado >= 3.0
550 if e.errno == -9 and tornado.version_info[0] < 3:
549 if e.errno == -9 and tornado.version_info[0] < 3:
551 # The flags passed to socket.getaddrinfo from
550 # The flags passed to socket.getaddrinfo from
552 # tornado.netutils.bind_sockets can cause "gaierror:
551 # tornado.netutils.bind_sockets can cause "gaierror:
553 # [Errno -9] Address family for hostname not supported"
552 # [Errno -9] Address family for hostname not supported"
554 # when the interface is not associated, for example.
553 # when the interface is not associated, for example.
555 # Changing the flags to exclude socket.AI_ADDRCONFIG does
554 # Changing the flags to exclude socket.AI_ADDRCONFIG does
556 # not cause this error, but the only way to do this is to
555 # not cause this error, but the only way to do this is to
557 # monkeypatch socket to remove the AI_ADDRCONFIG attribute
556 # monkeypatch socket to remove the AI_ADDRCONFIG attribute
558 saved_AI_ADDRCONFIG = socket.AI_ADDRCONFIG
557 saved_AI_ADDRCONFIG = socket.AI_ADDRCONFIG
559 self.log.warn('Monkeypatching socket to fix tornado bug')
558 self.log.warn('Monkeypatching socket to fix tornado bug')
560 del(socket.AI_ADDRCONFIG)
559 del(socket.AI_ADDRCONFIG)
561 try:
560 try:
562 # retry the tornado call without AI_ADDRCONFIG flags
561 # retry the tornado call without AI_ADDRCONFIG flags
563 self.http_server.listen(port, self.ip)
562 self.http_server.listen(port, self.ip)
564 except socket.error as e2:
563 except socket.error as e2:
565 e = e2
564 e = e2
566 else:
565 else:
567 self.port = port
566 self.port = port
568 success = True
567 success = True
569 break
568 break
570 # restore the monekypatch
569 # restore the monekypatch
571 socket.AI_ADDRCONFIG = saved_AI_ADDRCONFIG
570 socket.AI_ADDRCONFIG = saved_AI_ADDRCONFIG
572 if e.errno != errno.EADDRINUSE:
571 if e.errno != errno.EADDRINUSE:
573 raise
572 raise
574 self.log.info('The port %i is already in use, trying another random port.' % port)
573 self.log.info('The port %i is already in use, trying another random port.' % port)
575 else:
574 else:
576 self.port = port
575 self.port = port
577 success = True
576 success = True
578 break
577 break
579 if not success:
578 if not success:
580 self.log.critical('ERROR: the notebook server could not be started because '
579 self.log.critical('ERROR: the notebook server could not be started because '
581 'no available port could be found.')
580 'no available port could be found.')
582 self.exit(1)
581 self.exit(1)
583
582
584 def init_signal(self):
583 def init_signal(self):
585 if not sys.platform.startswith('win'):
584 if not sys.platform.startswith('win'):
586 signal.signal(signal.SIGINT, self._handle_sigint)
585 signal.signal(signal.SIGINT, self._handle_sigint)
587 signal.signal(signal.SIGTERM, self._signal_stop)
586 signal.signal(signal.SIGTERM, self._signal_stop)
588 if hasattr(signal, 'SIGUSR1'):
587 if hasattr(signal, 'SIGUSR1'):
589 # Windows doesn't support SIGUSR1
588 # Windows doesn't support SIGUSR1
590 signal.signal(signal.SIGUSR1, self._signal_info)
589 signal.signal(signal.SIGUSR1, self._signal_info)
591 if hasattr(signal, 'SIGINFO'):
590 if hasattr(signal, 'SIGINFO'):
592 # only on BSD-based systems
591 # only on BSD-based systems
593 signal.signal(signal.SIGINFO, self._signal_info)
592 signal.signal(signal.SIGINFO, self._signal_info)
594
593
595 def _handle_sigint(self, sig, frame):
594 def _handle_sigint(self, sig, frame):
596 """SIGINT handler spawns confirmation dialog"""
595 """SIGINT handler spawns confirmation dialog"""
597 # register more forceful signal handler for ^C^C case
596 # register more forceful signal handler for ^C^C case
598 signal.signal(signal.SIGINT, self._signal_stop)
597 signal.signal(signal.SIGINT, self._signal_stop)
599 # request confirmation dialog in bg thread, to avoid
598 # request confirmation dialog in bg thread, to avoid
600 # blocking the App
599 # blocking the App
601 thread = threading.Thread(target=self._confirm_exit)
600 thread = threading.Thread(target=self._confirm_exit)
602 thread.daemon = True
601 thread.daemon = True
603 thread.start()
602 thread.start()
604
603
605 def _restore_sigint_handler(self):
604 def _restore_sigint_handler(self):
606 """callback for restoring original SIGINT handler"""
605 """callback for restoring original SIGINT handler"""
607 signal.signal(signal.SIGINT, self._handle_sigint)
606 signal.signal(signal.SIGINT, self._handle_sigint)
608
607
609 def _confirm_exit(self):
608 def _confirm_exit(self):
610 """confirm shutdown on ^C
609 """confirm shutdown on ^C
611
610
612 A second ^C, or answering 'y' within 5s will cause shutdown,
611 A second ^C, or answering 'y' within 5s will cause shutdown,
613 otherwise original SIGINT handler will be restored.
612 otherwise original SIGINT handler will be restored.
614
613
615 This doesn't work on Windows.
614 This doesn't work on Windows.
616 """
615 """
617 # FIXME: remove this delay when pyzmq dependency is >= 2.1.11
616 # FIXME: remove this delay when pyzmq dependency is >= 2.1.11
618 time.sleep(0.1)
617 time.sleep(0.1)
619 info = self.log.info
618 info = self.log.info
620 info('interrupted')
619 info('interrupted')
621 print self.notebook_info()
620 print self.notebook_info()
622 sys.stdout.write("Shutdown this notebook server (y/[n])? ")
621 sys.stdout.write("Shutdown this notebook server (y/[n])? ")
623 sys.stdout.flush()
622 sys.stdout.flush()
624 r,w,x = select.select([sys.stdin], [], [], 5)
623 r,w,x = select.select([sys.stdin], [], [], 5)
625 if r:
624 if r:
626 line = sys.stdin.readline()
625 line = sys.stdin.readline()
627 if line.lower().startswith('y'):
626 if line.lower().startswith('y'):
628 self.log.critical("Shutdown confirmed")
627 self.log.critical("Shutdown confirmed")
629 ioloop.IOLoop.instance().stop()
628 ioloop.IOLoop.instance().stop()
630 return
629 return
631 else:
630 else:
632 print "No answer for 5s:",
631 print "No answer for 5s:",
633 print "resuming operation..."
632 print "resuming operation..."
634 # no answer, or answer is no:
633 # no answer, or answer is no:
635 # set it back to original SIGINT handler
634 # set it back to original SIGINT handler
636 # use IOLoop.add_callback because signal.signal must be called
635 # use IOLoop.add_callback because signal.signal must be called
637 # from main thread
636 # from main thread
638 ioloop.IOLoop.instance().add_callback(self._restore_sigint_handler)
637 ioloop.IOLoop.instance().add_callback(self._restore_sigint_handler)
639
638
640 def _signal_stop(self, sig, frame):
639 def _signal_stop(self, sig, frame):
641 self.log.critical("received signal %s, stopping", sig)
640 self.log.critical("received signal %s, stopping", sig)
642 ioloop.IOLoop.instance().stop()
641 ioloop.IOLoop.instance().stop()
643
642
644 def _signal_info(self, sig, frame):
643 def _signal_info(self, sig, frame):
645 print self.notebook_info()
644 print self.notebook_info()
646
645
647 def init_components(self):
646 def init_components(self):
648 """Check the components submodule, and warn if it's unclean"""
647 """Check the components submodule, and warn if it's unclean"""
649 status = submodule.check_submodule_status()
648 status = submodule.check_submodule_status()
650 if status == 'missing':
649 if status == 'missing':
651 self.log.warn("components submodule missing, running `git submodule update`")
650 self.log.warn("components submodule missing, running `git submodule update`")
652 submodule.update_submodules(submodule.ipython_parent())
651 submodule.update_submodules(submodule.ipython_parent())
653 elif status == 'unclean':
652 elif status == 'unclean':
654 self.log.warn("components submodule unclean, you may see 404s on static/components")
653 self.log.warn("components submodule unclean, you may see 404s on static/components")
655 self.log.warn("run `setup.py submodule` or `git submodule update` to update")
654 self.log.warn("run `setup.py submodule` or `git submodule update` to update")
656
655
657
656
658 @catch_config_error
657 @catch_config_error
659 def initialize(self, argv=None):
658 def initialize(self, argv=None):
660 self.init_logging()
659 self.init_logging()
661 super(NotebookApp, self).initialize(argv)
660 super(NotebookApp, self).initialize(argv)
662 self.init_configurables()
661 self.init_configurables()
663 self.init_components()
662 self.init_components()
664 self.init_webapp()
663 self.init_webapp()
665 self.init_signal()
664 self.init_signal()
666
665
667 def cleanup_kernels(self):
666 def cleanup_kernels(self):
668 """Shutdown all kernels.
667 """Shutdown all kernels.
669
668
670 The kernels will shutdown themselves when this process no longer exists,
669 The kernels will shutdown themselves when this process no longer exists,
671 but explicit shutdown allows the KernelManagers to cleanup the connection files.
670 but explicit shutdown allows the KernelManagers to cleanup the connection files.
672 """
671 """
673 self.log.info('Shutting down kernels')
672 self.log.info('Shutting down kernels')
674 self.kernel_manager.shutdown_all()
673 self.kernel_manager.shutdown_all()
675
674
676 def notebook_info(self):
675 def notebook_info(self):
677 "Return the current working directory and the server url information"
676 "Return the current working directory and the server url information"
678 mgr_info = self.notebook_manager.info_string() + "\n"
677 mgr_info = self.notebook_manager.info_string() + "\n"
679 return mgr_info +"The IPython Notebook is running at: %s" % self._url
678 return mgr_info +"The IPython Notebook is running at: %s" % self._url
680
679
681 def start(self):
680 def start(self):
682 """ Start the IPython Notebook server app, after initialization
681 """ Start the IPython Notebook server app, after initialization
683
682
684 This method takes no arguments so all configuration and initialization
683 This method takes no arguments so all configuration and initialization
685 must be done prior to calling this method."""
684 must be done prior to calling this method."""
686 ip = self.ip if self.ip else '[all ip addresses on your system]'
685 ip = self.ip if self.ip else '[all ip addresses on your system]'
687 proto = 'https' if self.certfile else 'http'
686 proto = 'https' if self.certfile else 'http'
688 info = self.log.info
687 info = self.log.info
689 self._url = "%s://%s:%i%s" % (proto, ip, self.port,
688 self._url = "%s://%s:%i%s" % (proto, ip, self.port,
690 self.base_project_url)
689 self.base_project_url)
691 for line in self.notebook_info().split("\n"):
690 for line in self.notebook_info().split("\n"):
692 info(line)
691 info(line)
693 info("Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).")
692 info("Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).")
694
693
695 if self.open_browser or self.file_to_run:
694 if self.open_browser or self.file_to_run:
696 ip = self.ip or LOCALHOST
695 ip = self.ip or LOCALHOST
697 try:
696 try:
698 browser = webbrowser.get(self.browser or None)
697 browser = webbrowser.get(self.browser or None)
699 except webbrowser.Error as e:
698 except webbrowser.Error as e:
700 self.log.warn('No web browser found: %s.' % e)
699 self.log.warn('No web browser found: %s.' % e)
701 browser = None
700 browser = None
702
701
703 if self.file_to_run:
702 if self.file_to_run:
704 name, _ = os.path.splitext(os.path.basename(self.file_to_run))
703 name, _ = os.path.splitext(os.path.basename(self.file_to_run))
705 url = self.notebook_manager.rev_mapping.get(name, '')
704 url = self.notebook_manager.rev_mapping.get(name, '')
706 else:
705 else:
707 url = ''
706 url = ''
708 if browser:
707 if browser:
709 b = lambda : browser.open("%s://%s:%i%s%s" % (proto, ip,
708 b = lambda : browser.open("%s://%s:%i%s%s" % (proto, ip,
710 self.port, self.base_project_url, url), new=2)
709 self.port, self.base_project_url, url), new=2)
711 threading.Thread(target=b).start()
710 threading.Thread(target=b).start()
712 try:
711 try:
713 ioloop.IOLoop.instance().start()
712 ioloop.IOLoop.instance().start()
714 except KeyboardInterrupt:
713 except KeyboardInterrupt:
715 info("Interrupted...")
714 info("Interrupted...")
716 finally:
715 finally:
717 self.cleanup_kernels()
716 self.cleanup_kernels()
718
717
719
718
720 #-----------------------------------------------------------------------------
719 #-----------------------------------------------------------------------------
721 # Main entry point
720 # Main entry point
722 #-----------------------------------------------------------------------------
721 #-----------------------------------------------------------------------------
723
722
724 launch_new_instance = NotebookApp.launch_instance
723 launch_new_instance = NotebookApp.launch_instance
725
724
@@ -1,385 +1,385 b''
1 """ A minimal application using the Qt console-style IPython frontend.
1 """ A minimal application using the Qt console-style IPython frontend.
2
2
3 This is not a complete console app, as subprocess will not be able to receive
3 This is not a complete console app, as subprocess will not be able to receive
4 input, there is no real readline support, among other limitations.
4 input, there is no real readline support, among other limitations.
5
5
6 Authors:
6 Authors:
7
7
8 * Evan Patterson
8 * Evan Patterson
9 * Min RK
9 * Min RK
10 * Erik Tollerud
10 * Erik Tollerud
11 * Fernando Perez
11 * Fernando Perez
12 * Bussonnier Matthias
12 * Bussonnier Matthias
13 * Thomas Kluyver
13 * Thomas Kluyver
14 * Paul Ivanov
14 * Paul Ivanov
15
15
16 """
16 """
17
17
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19 # Imports
19 # Imports
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21
21
22 # stdlib imports
22 # stdlib imports
23 import os
23 import os
24 import signal
24 import signal
25 import sys
25 import sys
26
26
27 # If run on Windows, install an exception hook which pops up a
27 # If run on Windows, install an exception hook which pops up a
28 # message box. Pythonw.exe hides the console, so without this
28 # message box. Pythonw.exe hides the console, so without this
29 # the application silently fails to load.
29 # the application silently fails to load.
30 #
30 #
31 # We always install this handler, because the expectation is for
31 # We always install this handler, because the expectation is for
32 # qtconsole to bring up a GUI even if called from the console.
32 # qtconsole to bring up a GUI even if called from the console.
33 # The old handler is called, so the exception is printed as well.
33 # The old handler is called, so the exception is printed as well.
34 # If desired, check for pythonw with an additional condition
34 # If desired, check for pythonw with an additional condition
35 # (sys.executable.lower().find('pythonw.exe') >= 0).
35 # (sys.executable.lower().find('pythonw.exe') >= 0).
36 if os.name == 'nt':
36 if os.name == 'nt':
37 old_excepthook = sys.excepthook
37 old_excepthook = sys.excepthook
38
38
39 def gui_excepthook(exctype, value, tb):
39 def gui_excepthook(exctype, value, tb):
40 try:
40 try:
41 import ctypes, traceback
41 import ctypes, traceback
42 MB_ICONERROR = 0x00000010L
42 MB_ICONERROR = 0x00000010L
43 title = u'Error starting IPython QtConsole'
43 title = u'Error starting IPython QtConsole'
44 msg = u''.join(traceback.format_exception(exctype, value, tb))
44 msg = u''.join(traceback.format_exception(exctype, value, tb))
45 ctypes.windll.user32.MessageBoxW(0, msg, title, MB_ICONERROR)
45 ctypes.windll.user32.MessageBoxW(0, msg, title, MB_ICONERROR)
46 finally:
46 finally:
47 # Also call the old exception hook to let it do
47 # Also call the old exception hook to let it do
48 # its thing too.
48 # its thing too.
49 old_excepthook(exctype, value, tb)
49 old_excepthook(exctype, value, tb)
50
50
51 sys.excepthook = gui_excepthook
51 sys.excepthook = gui_excepthook
52
52
53 # System library imports
53 # System library imports
54 from IPython.external.qt import QtCore, QtGui
54 from IPython.external.qt import QtCore, QtGui
55
55
56 # Local imports
56 # Local imports
57 from IPython.config.application import catch_config_error
57 from IPython.config.application import catch_config_error
58 from IPython.core.application import BaseIPythonApplication
58 from IPython.core.application import BaseIPythonApplication
59 from IPython.qt.console.ipython_widget import IPythonWidget
59 from IPython.qt.console.ipython_widget import IPythonWidget
60 from IPython.qt.console.rich_ipython_widget import RichIPythonWidget
60 from IPython.qt.console.rich_ipython_widget import RichIPythonWidget
61 from IPython.qt.console import styles
61 from IPython.qt.console import styles
62 from IPython.qt.console.mainwindow import MainWindow
62 from IPython.qt.console.mainwindow import MainWindow
63 from IPython.qt.client import QtKernelClient
63 from IPython.qt.client import QtKernelClient
64 from IPython.qt.manager import QtKernelManager
64 from IPython.qt.manager import QtKernelManager
65 from IPython.utils.traitlets import (
65 from IPython.utils.traitlets import (
66 Dict, Unicode, CBool, Any
66 Dict, Unicode, CBool, Any
67 )
67 )
68
68
69 from IPython.consoleapp import (
69 from IPython.consoleapp import (
70 IPythonConsoleApp, app_aliases, app_flags, flags, aliases
70 IPythonConsoleApp, app_aliases, app_flags, flags, aliases
71 )
71 )
72
72
73 #-----------------------------------------------------------------------------
73 #-----------------------------------------------------------------------------
74 # Network Constants
74 # Network Constants
75 #-----------------------------------------------------------------------------
75 #-----------------------------------------------------------------------------
76
76
77 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
77 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
78
78
79 #-----------------------------------------------------------------------------
79 #-----------------------------------------------------------------------------
80 # Globals
80 # Globals
81 #-----------------------------------------------------------------------------
81 #-----------------------------------------------------------------------------
82
82
83 _examples = """
83 _examples = """
84 ipython qtconsole # start the qtconsole
84 ipython qtconsole # start the qtconsole
85 ipython qtconsole --pylab=inline # start with pylab in inline plotting mode
85 ipython qtconsole --matplotlib=inline # start with matplotlib inline plotting mode
86 """
86 """
87
87
88 #-----------------------------------------------------------------------------
88 #-----------------------------------------------------------------------------
89 # Aliases and Flags
89 # Aliases and Flags
90 #-----------------------------------------------------------------------------
90 #-----------------------------------------------------------------------------
91
91
92 # start with copy of flags
92 # start with copy of flags
93 flags = dict(flags)
93 flags = dict(flags)
94 qt_flags = {
94 qt_flags = {
95 'plain' : ({'IPythonQtConsoleApp' : {'plain' : True}},
95 'plain' : ({'IPythonQtConsoleApp' : {'plain' : True}},
96 "Disable rich text support."),
96 "Disable rich text support."),
97 }
97 }
98
98
99 # and app_flags from the Console Mixin
99 # and app_flags from the Console Mixin
100 qt_flags.update(app_flags)
100 qt_flags.update(app_flags)
101 # add frontend flags to the full set
101 # add frontend flags to the full set
102 flags.update(qt_flags)
102 flags.update(qt_flags)
103
103
104 # start with copy of front&backend aliases list
104 # start with copy of front&backend aliases list
105 aliases = dict(aliases)
105 aliases = dict(aliases)
106 qt_aliases = dict(
106 qt_aliases = dict(
107 style = 'IPythonWidget.syntax_style',
107 style = 'IPythonWidget.syntax_style',
108 stylesheet = 'IPythonQtConsoleApp.stylesheet',
108 stylesheet = 'IPythonQtConsoleApp.stylesheet',
109 colors = 'ZMQInteractiveShell.colors',
109 colors = 'ZMQInteractiveShell.colors',
110
110
111 editor = 'IPythonWidget.editor',
111 editor = 'IPythonWidget.editor',
112 paging = 'ConsoleWidget.paging',
112 paging = 'ConsoleWidget.paging',
113 )
113 )
114 # and app_aliases from the Console Mixin
114 # and app_aliases from the Console Mixin
115 qt_aliases.update(app_aliases)
115 qt_aliases.update(app_aliases)
116 qt_aliases.update({'gui-completion':'ConsoleWidget.gui_completion'})
116 qt_aliases.update({'gui-completion':'ConsoleWidget.gui_completion'})
117 # add frontend aliases to the full set
117 # add frontend aliases to the full set
118 aliases.update(qt_aliases)
118 aliases.update(qt_aliases)
119
119
120 # get flags&aliases into sets, and remove a couple that
120 # get flags&aliases into sets, and remove a couple that
121 # shouldn't be scrubbed from backend flags:
121 # shouldn't be scrubbed from backend flags:
122 qt_aliases = set(qt_aliases.keys())
122 qt_aliases = set(qt_aliases.keys())
123 qt_aliases.remove('colors')
123 qt_aliases.remove('colors')
124 qt_flags = set(qt_flags.keys())
124 qt_flags = set(qt_flags.keys())
125
125
126 #-----------------------------------------------------------------------------
126 #-----------------------------------------------------------------------------
127 # Classes
127 # Classes
128 #-----------------------------------------------------------------------------
128 #-----------------------------------------------------------------------------
129
129
130 #-----------------------------------------------------------------------------
130 #-----------------------------------------------------------------------------
131 # IPythonQtConsole
131 # IPythonQtConsole
132 #-----------------------------------------------------------------------------
132 #-----------------------------------------------------------------------------
133
133
134
134
135 class IPythonQtConsoleApp(BaseIPythonApplication, IPythonConsoleApp):
135 class IPythonQtConsoleApp(BaseIPythonApplication, IPythonConsoleApp):
136 name = 'ipython-qtconsole'
136 name = 'ipython-qtconsole'
137
137
138 description = """
138 description = """
139 The IPython QtConsole.
139 The IPython QtConsole.
140
140
141 This launches a Console-style application using Qt. It is not a full
141 This launches a Console-style application using Qt. It is not a full
142 console, in that launched terminal subprocesses will not be able to accept
142 console, in that launched terminal subprocesses will not be able to accept
143 input.
143 input.
144
144
145 The QtConsole supports various extra features beyond the Terminal IPython
145 The QtConsole supports various extra features beyond the Terminal IPython
146 shell, such as inline plotting with matplotlib, via:
146 shell, such as inline plotting with matplotlib, via:
147
147
148 ipython qtconsole --pylab=inline
148 ipython qtconsole --matplotlib=inline
149
149
150 as well as saving your session as HTML, and printing the output.
150 as well as saving your session as HTML, and printing the output.
151
151
152 """
152 """
153 examples = _examples
153 examples = _examples
154
154
155 classes = [IPythonWidget] + IPythonConsoleApp.classes
155 classes = [IPythonWidget] + IPythonConsoleApp.classes
156 flags = Dict(flags)
156 flags = Dict(flags)
157 aliases = Dict(aliases)
157 aliases = Dict(aliases)
158 frontend_flags = Any(qt_flags)
158 frontend_flags = Any(qt_flags)
159 frontend_aliases = Any(qt_aliases)
159 frontend_aliases = Any(qt_aliases)
160 kernel_client_class = QtKernelClient
160 kernel_client_class = QtKernelClient
161 kernel_manager_class = QtKernelManager
161 kernel_manager_class = QtKernelManager
162
162
163 stylesheet = Unicode('', config=True,
163 stylesheet = Unicode('', config=True,
164 help="path to a custom CSS stylesheet")
164 help="path to a custom CSS stylesheet")
165
165
166 hide_menubar = CBool(False, config=True,
166 hide_menubar = CBool(False, config=True,
167 help="Start the console window with the menu bar hidden.")
167 help="Start the console window with the menu bar hidden.")
168
168
169 maximize = CBool(False, config=True,
169 maximize = CBool(False, config=True,
170 help="Start the console window maximized.")
170 help="Start the console window maximized.")
171
171
172 plain = CBool(False, config=True,
172 plain = CBool(False, config=True,
173 help="Use a plaintext widget instead of rich text (plain can't print/save).")
173 help="Use a plaintext widget instead of rich text (plain can't print/save).")
174
174
175 def _plain_changed(self, name, old, new):
175 def _plain_changed(self, name, old, new):
176 kind = 'plain' if new else 'rich'
176 kind = 'plain' if new else 'rich'
177 self.config.ConsoleWidget.kind = kind
177 self.config.ConsoleWidget.kind = kind
178 if new:
178 if new:
179 self.widget_factory = IPythonWidget
179 self.widget_factory = IPythonWidget
180 else:
180 else:
181 self.widget_factory = RichIPythonWidget
181 self.widget_factory = RichIPythonWidget
182
182
183 # the factory for creating a widget
183 # the factory for creating a widget
184 widget_factory = Any(RichIPythonWidget)
184 widget_factory = Any(RichIPythonWidget)
185
185
186 def parse_command_line(self, argv=None):
186 def parse_command_line(self, argv=None):
187 super(IPythonQtConsoleApp, self).parse_command_line(argv)
187 super(IPythonQtConsoleApp, self).parse_command_line(argv)
188 self.build_kernel_argv(argv)
188 self.build_kernel_argv(argv)
189
189
190
190
191 def new_frontend_master(self):
191 def new_frontend_master(self):
192 """ Create and return new frontend attached to new kernel, launched on localhost.
192 """ Create and return new frontend attached to new kernel, launched on localhost.
193 """
193 """
194 kernel_manager = self.kernel_manager_class(
194 kernel_manager = self.kernel_manager_class(
195 connection_file=self._new_connection_file(),
195 connection_file=self._new_connection_file(),
196 parent=self,
196 parent=self,
197 autorestart=True,
197 autorestart=True,
198 )
198 )
199 # start the kernel
199 # start the kernel
200 kwargs = dict()
200 kwargs = dict()
201 kwargs['extra_arguments'] = self.kernel_argv
201 kwargs['extra_arguments'] = self.kernel_argv
202 kernel_manager.start_kernel(**kwargs)
202 kernel_manager.start_kernel(**kwargs)
203 kernel_manager.client_factory = self.kernel_client_class
203 kernel_manager.client_factory = self.kernel_client_class
204 kernel_client = kernel_manager.client()
204 kernel_client = kernel_manager.client()
205 kernel_client.start_channels(shell=True, iopub=True)
205 kernel_client.start_channels(shell=True, iopub=True)
206 widget = self.widget_factory(config=self.config,
206 widget = self.widget_factory(config=self.config,
207 local_kernel=True)
207 local_kernel=True)
208 self.init_colors(widget)
208 self.init_colors(widget)
209 widget.kernel_manager = kernel_manager
209 widget.kernel_manager = kernel_manager
210 widget.kernel_client = kernel_client
210 widget.kernel_client = kernel_client
211 widget._existing = False
211 widget._existing = False
212 widget._may_close = True
212 widget._may_close = True
213 widget._confirm_exit = self.confirm_exit
213 widget._confirm_exit = self.confirm_exit
214 return widget
214 return widget
215
215
216 def new_frontend_slave(self, current_widget):
216 def new_frontend_slave(self, current_widget):
217 """Create and return a new frontend attached to an existing kernel.
217 """Create and return a new frontend attached to an existing kernel.
218
218
219 Parameters
219 Parameters
220 ----------
220 ----------
221 current_widget : IPythonWidget
221 current_widget : IPythonWidget
222 The IPythonWidget whose kernel this frontend is to share
222 The IPythonWidget whose kernel this frontend is to share
223 """
223 """
224 kernel_client = self.kernel_client_class(
224 kernel_client = self.kernel_client_class(
225 connection_file=current_widget.kernel_client.connection_file,
225 connection_file=current_widget.kernel_client.connection_file,
226 config = self.config,
226 config = self.config,
227 )
227 )
228 kernel_client.load_connection_file()
228 kernel_client.load_connection_file()
229 kernel_client.start_channels()
229 kernel_client.start_channels()
230 widget = self.widget_factory(config=self.config,
230 widget = self.widget_factory(config=self.config,
231 local_kernel=False)
231 local_kernel=False)
232 self.init_colors(widget)
232 self.init_colors(widget)
233 widget._existing = True
233 widget._existing = True
234 widget._may_close = False
234 widget._may_close = False
235 widget._confirm_exit = False
235 widget._confirm_exit = False
236 widget.kernel_client = kernel_client
236 widget.kernel_client = kernel_client
237 widget.kernel_manager = current_widget.kernel_manager
237 widget.kernel_manager = current_widget.kernel_manager
238 return widget
238 return widget
239
239
240 def init_qt_app(self):
240 def init_qt_app(self):
241 # separate from qt_elements, because it must run first
241 # separate from qt_elements, because it must run first
242 self.app = QtGui.QApplication([])
242 self.app = QtGui.QApplication([])
243
243
244 def init_qt_elements(self):
244 def init_qt_elements(self):
245 # Create the widget.
245 # Create the widget.
246
246
247 base_path = os.path.abspath(os.path.dirname(__file__))
247 base_path = os.path.abspath(os.path.dirname(__file__))
248 icon_path = os.path.join(base_path, 'resources', 'icon', 'IPythonConsole.svg')
248 icon_path = os.path.join(base_path, 'resources', 'icon', 'IPythonConsole.svg')
249 self.app.icon = QtGui.QIcon(icon_path)
249 self.app.icon = QtGui.QIcon(icon_path)
250 QtGui.QApplication.setWindowIcon(self.app.icon)
250 QtGui.QApplication.setWindowIcon(self.app.icon)
251
251
252 try:
252 try:
253 ip = self.config.KernelManager.ip
253 ip = self.config.KernelManager.ip
254 except AttributeError:
254 except AttributeError:
255 ip = LOCALHOST
255 ip = LOCALHOST
256 local_kernel = (not self.existing) or ip in LOCAL_IPS
256 local_kernel = (not self.existing) or ip in LOCAL_IPS
257 self.widget = self.widget_factory(config=self.config,
257 self.widget = self.widget_factory(config=self.config,
258 local_kernel=local_kernel)
258 local_kernel=local_kernel)
259 self.init_colors(self.widget)
259 self.init_colors(self.widget)
260 self.widget._existing = self.existing
260 self.widget._existing = self.existing
261 self.widget._may_close = not self.existing
261 self.widget._may_close = not self.existing
262 self.widget._confirm_exit = self.confirm_exit
262 self.widget._confirm_exit = self.confirm_exit
263
263
264 self.widget.kernel_manager = self.kernel_manager
264 self.widget.kernel_manager = self.kernel_manager
265 self.widget.kernel_client = self.kernel_client
265 self.widget.kernel_client = self.kernel_client
266 self.window = MainWindow(self.app,
266 self.window = MainWindow(self.app,
267 confirm_exit=self.confirm_exit,
267 confirm_exit=self.confirm_exit,
268 new_frontend_factory=self.new_frontend_master,
268 new_frontend_factory=self.new_frontend_master,
269 slave_frontend_factory=self.new_frontend_slave,
269 slave_frontend_factory=self.new_frontend_slave,
270 )
270 )
271 self.window.log = self.log
271 self.window.log = self.log
272 self.window.add_tab_with_frontend(self.widget)
272 self.window.add_tab_with_frontend(self.widget)
273 self.window.init_menu_bar()
273 self.window.init_menu_bar()
274
274
275 # Ignore on OSX, where there is always a menu bar
275 # Ignore on OSX, where there is always a menu bar
276 if sys.platform != 'darwin' and self.hide_menubar:
276 if sys.platform != 'darwin' and self.hide_menubar:
277 self.window.menuBar().setVisible(False)
277 self.window.menuBar().setVisible(False)
278
278
279 self.window.setWindowTitle('IPython')
279 self.window.setWindowTitle('IPython')
280
280
281 def init_colors(self, widget):
281 def init_colors(self, widget):
282 """Configure the coloring of the widget"""
282 """Configure the coloring of the widget"""
283 # Note: This will be dramatically simplified when colors
283 # Note: This will be dramatically simplified when colors
284 # are removed from the backend.
284 # are removed from the backend.
285
285
286 # parse the colors arg down to current known labels
286 # parse the colors arg down to current known labels
287 try:
287 try:
288 colors = self.config.ZMQInteractiveShell.colors
288 colors = self.config.ZMQInteractiveShell.colors
289 except AttributeError:
289 except AttributeError:
290 colors = None
290 colors = None
291 try:
291 try:
292 style = self.config.IPythonWidget.syntax_style
292 style = self.config.IPythonWidget.syntax_style
293 except AttributeError:
293 except AttributeError:
294 style = None
294 style = None
295 try:
295 try:
296 sheet = self.config.IPythonWidget.style_sheet
296 sheet = self.config.IPythonWidget.style_sheet
297 except AttributeError:
297 except AttributeError:
298 sheet = None
298 sheet = None
299
299
300 # find the value for colors:
300 # find the value for colors:
301 if colors:
301 if colors:
302 colors=colors.lower()
302 colors=colors.lower()
303 if colors in ('lightbg', 'light'):
303 if colors in ('lightbg', 'light'):
304 colors='lightbg'
304 colors='lightbg'
305 elif colors in ('dark', 'linux'):
305 elif colors in ('dark', 'linux'):
306 colors='linux'
306 colors='linux'
307 else:
307 else:
308 colors='nocolor'
308 colors='nocolor'
309 elif style:
309 elif style:
310 if style=='bw':
310 if style=='bw':
311 colors='nocolor'
311 colors='nocolor'
312 elif styles.dark_style(style):
312 elif styles.dark_style(style):
313 colors='linux'
313 colors='linux'
314 else:
314 else:
315 colors='lightbg'
315 colors='lightbg'
316 else:
316 else:
317 colors=None
317 colors=None
318
318
319 # Configure the style
319 # Configure the style
320 if style:
320 if style:
321 widget.style_sheet = styles.sheet_from_template(style, colors)
321 widget.style_sheet = styles.sheet_from_template(style, colors)
322 widget.syntax_style = style
322 widget.syntax_style = style
323 widget._syntax_style_changed()
323 widget._syntax_style_changed()
324 widget._style_sheet_changed()
324 widget._style_sheet_changed()
325 elif colors:
325 elif colors:
326 # use a default dark/light/bw style
326 # use a default dark/light/bw style
327 widget.set_default_style(colors=colors)
327 widget.set_default_style(colors=colors)
328
328
329 if self.stylesheet:
329 if self.stylesheet:
330 # we got an explicit stylesheet
330 # we got an explicit stylesheet
331 if os.path.isfile(self.stylesheet):
331 if os.path.isfile(self.stylesheet):
332 with open(self.stylesheet) as f:
332 with open(self.stylesheet) as f:
333 sheet = f.read()
333 sheet = f.read()
334 else:
334 else:
335 raise IOError("Stylesheet %r not found." % self.stylesheet)
335 raise IOError("Stylesheet %r not found." % self.stylesheet)
336 if sheet:
336 if sheet:
337 widget.style_sheet = sheet
337 widget.style_sheet = sheet
338 widget._style_sheet_changed()
338 widget._style_sheet_changed()
339
339
340
340
341 def init_signal(self):
341 def init_signal(self):
342 """allow clean shutdown on sigint"""
342 """allow clean shutdown on sigint"""
343 signal.signal(signal.SIGINT, lambda sig, frame: self.exit(-2))
343 signal.signal(signal.SIGINT, lambda sig, frame: self.exit(-2))
344 # need a timer, so that QApplication doesn't block until a real
344 # need a timer, so that QApplication doesn't block until a real
345 # Qt event fires (can require mouse movement)
345 # Qt event fires (can require mouse movement)
346 # timer trick from http://stackoverflow.com/q/4938723/938949
346 # timer trick from http://stackoverflow.com/q/4938723/938949
347 timer = QtCore.QTimer()
347 timer = QtCore.QTimer()
348 # Let the interpreter run each 200 ms:
348 # Let the interpreter run each 200 ms:
349 timer.timeout.connect(lambda: None)
349 timer.timeout.connect(lambda: None)
350 timer.start(200)
350 timer.start(200)
351 # hold onto ref, so the timer doesn't get cleaned up
351 # hold onto ref, so the timer doesn't get cleaned up
352 self._sigint_timer = timer
352 self._sigint_timer = timer
353
353
354 @catch_config_error
354 @catch_config_error
355 def initialize(self, argv=None):
355 def initialize(self, argv=None):
356 self.init_qt_app()
356 self.init_qt_app()
357 super(IPythonQtConsoleApp, self).initialize(argv)
357 super(IPythonQtConsoleApp, self).initialize(argv)
358 IPythonConsoleApp.initialize(self,argv)
358 IPythonConsoleApp.initialize(self,argv)
359 self.init_qt_elements()
359 self.init_qt_elements()
360 self.init_signal()
360 self.init_signal()
361
361
362 def start(self):
362 def start(self):
363
363
364 # draw the window
364 # draw the window
365 if self.maximize:
365 if self.maximize:
366 self.window.showMaximized()
366 self.window.showMaximized()
367 else:
367 else:
368 self.window.show()
368 self.window.show()
369 self.window.raise_()
369 self.window.raise_()
370
370
371 # Start the application main loop.
371 # Start the application main loop.
372 self.app.exec_()
372 self.app.exec_()
373
373
374 #-----------------------------------------------------------------------------
374 #-----------------------------------------------------------------------------
375 # Main entry point
375 # Main entry point
376 #-----------------------------------------------------------------------------
376 #-----------------------------------------------------------------------------
377
377
378 def main():
378 def main():
379 app = IPythonQtConsoleApp()
379 app = IPythonQtConsoleApp()
380 app.initialize()
380 app.initialize()
381 app.start()
381 app.start()
382
382
383
383
384 if __name__ == '__main__':
384 if __name__ == '__main__':
385 main()
385 main()
General Comments 0
You need to be logged in to leave comments. Login now