##// END OF EJS Templates
add confirm_exit option to qtconsole to suppress exit dialog
MinRK -
Show More
@@ -1,372 +1,402 b''
1 1 """ A minimal application using the Qt console-style IPython frontend.
2 2 """
3 3
4 4 #-----------------------------------------------------------------------------
5 5 # Imports
6 6 #-----------------------------------------------------------------------------
7 7
8 8 # stdlib imports
9 9 import os
10 10 import signal
11 11 import sys
12 12
13 13 # System library imports
14 14 from IPython.external.qt import QtGui
15 15 from pygments.styles import get_all_styles
16 16
17 17 # Local imports
18 from IPython.config.application import boolean_flag
18 19 from IPython.core.newapplication import ProfileDir, BaseIPythonApplication
19 20 from IPython.frontend.qt.console.frontend_widget import FrontendWidget
20 21 from IPython.frontend.qt.console.ipython_widget import IPythonWidget
21 22 from IPython.frontend.qt.console.rich_ipython_widget import RichIPythonWidget
22 23 from IPython.frontend.qt.console import styles
23 24 from IPython.frontend.qt.kernelmanager import QtKernelManager
24 25 from IPython.utils.traitlets import (
25 Dict, List, Unicode, Int, CaselessStrEnum, Bool, Any
26 Dict, List, Unicode, Int, CaselessStrEnum, CBool, Any
26 27 )
27 28 from IPython.zmq.ipkernel import (
28 29 flags as ipkernel_flags,
29 30 aliases as ipkernel_aliases,
30 31 IPKernelApp
31 32 )
32 33 from IPython.zmq.zmqshell import ZMQInteractiveShell
33 34
34 35
35 36 #-----------------------------------------------------------------------------
36 37 # Network Constants
37 38 #-----------------------------------------------------------------------------
38 39
39 40 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
40 41
41 42 #-----------------------------------------------------------------------------
42 43 # Classes
43 44 #-----------------------------------------------------------------------------
44 45
45 46 class MainWindow(QtGui.QMainWindow):
46 47
47 48 #---------------------------------------------------------------------------
48 49 # 'object' interface
49 50 #---------------------------------------------------------------------------
50 51
51 def __init__(self, app, frontend, existing=False, may_close=True):
52 def __init__(self, app, frontend, existing=False, may_close=True,
53 confirm_exit=True):
52 54 """ Create a MainWindow for the specified FrontendWidget.
53 55
54 56 The app is passed as an argument to allow for different
55 57 closing behavior depending on whether we are the Kernel's parent.
56 58
57 59 If existing is True, then this Console does not own the Kernel.
58 60
59 61 If may_close is True, then this Console is permitted to close the kernel
60 62 """
61 63 super(MainWindow, self).__init__()
62 64 self._app = app
63 65 self._frontend = frontend
64 66 self._existing = existing
65 67 if existing:
66 68 self._may_close = may_close
67 69 else:
68 70 self._may_close = True
69 71 self._frontend.exit_requested.connect(self.close)
72 self._confirm_exit = confirm_exit
70 73 self.setCentralWidget(frontend)
71 74
72 75 #---------------------------------------------------------------------------
73 76 # QWidget interface
74 77 #---------------------------------------------------------------------------
75 78
76 79 def closeEvent(self, event):
77 80 """ Close the window and the kernel (if necessary).
78 81
79 82 This will prompt the user if they are finished with the kernel, and if
80 83 so, closes the kernel cleanly. Alternatively, if the exit magic is used,
81 84 it closes without prompt.
82 85 """
83 86 keepkernel = None #Use the prompt by default
84 87 if hasattr(self._frontend,'_keep_kernel_on_exit'): #set by exit magic
85 88 keepkernel = self._frontend._keep_kernel_on_exit
86 89
87 90 kernel_manager = self._frontend.kernel_manager
88 91
92 if keepkernel is None and not self._confirm_exit:
93 # don't prompt, just terminate the kernel if we own it
94 # or leave it alone if we don't
95 keepkernel = not self._existing
96
89 97 if keepkernel is None: #show prompt
90 98 if kernel_manager and kernel_manager.channels_running:
91 99 title = self.window().windowTitle()
92 100 cancel = QtGui.QMessageBox.Cancel
93 101 okay = QtGui.QMessageBox.Ok
94 102 if self._may_close:
95 103 msg = "You are closing this Console window."
96 104 info = "Would you like to quit the Kernel and all attached Consoles as well?"
97 105 justthis = QtGui.QPushButton("&No, just this Console", self)
98 106 justthis.setShortcut('N')
99 107 closeall = QtGui.QPushButton("&Yes, quit everything", self)
100 108 closeall.setShortcut('Y')
101 109 box = QtGui.QMessageBox(QtGui.QMessageBox.Question,
102 110 title, msg)
103 111 box.setInformativeText(info)
104 112 box.addButton(cancel)
105 113 box.addButton(justthis, QtGui.QMessageBox.NoRole)
106 114 box.addButton(closeall, QtGui.QMessageBox.YesRole)
107 115 box.setDefaultButton(closeall)
108 116 box.setEscapeButton(cancel)
109 117 reply = box.exec_()
110 118 if reply == 1: # close All
111 119 kernel_manager.shutdown_kernel()
112 120 #kernel_manager.stop_channels()
113 121 event.accept()
114 122 elif reply == 0: # close Console
115 123 if not self._existing:
116 124 # Have kernel: don't quit, just close the window
117 125 self._app.setQuitOnLastWindowClosed(False)
118 126 self.deleteLater()
119 127 event.accept()
120 128 else:
121 129 event.ignore()
122 130 else:
123 131 reply = QtGui.QMessageBox.question(self, title,
124 132 "Are you sure you want to close this Console?"+
125 133 "\nThe Kernel and other Consoles will remain active.",
126 134 okay|cancel,
127 135 defaultButton=okay
128 136 )
129 137 if reply == okay:
130 138 event.accept()
131 139 else:
132 140 event.ignore()
133 141 elif keepkernel: #close console but leave kernel running (no prompt)
134 142 if kernel_manager and kernel_manager.channels_running:
135 143 if not self._existing:
136 144 # I have the kernel: don't quit, just close the window
137 145 self._app.setQuitOnLastWindowClosed(False)
138 146 event.accept()
139 147 else: #close console and kernel (no prompt)
140 148 if kernel_manager and kernel_manager.channels_running:
141 149 kernel_manager.shutdown_kernel()
142 150 event.accept()
143 151
144 152 #-----------------------------------------------------------------------------
145 153 # Aliases and Flags
146 154 #-----------------------------------------------------------------------------
147 155
148 156 flags = dict(ipkernel_flags)
149 157
150 158 flags.update({
151 159 'existing' : ({'IPythonQtConsoleApp' : {'existing' : True}},
152 160 "Connect to an existing kernel."),
153 161 'pure' : ({'IPythonQtConsoleApp' : {'pure' : True}},
154 162 "Use a pure Python kernel instead of an IPython kernel."),
155 163 'plain' : ({'ConsoleWidget' : {'kind' : 'plain'}},
156 164 "Disable rich text support."),
157 'gui-completion' : ({'FrontendWidget' : {'gui_completion' : True}},
158 "use a GUI widget for tab completion"),
159 165 })
160
161 qt_flags = ['existing', 'pure', 'plain', 'gui-completion']
166 flags.update(boolean_flag(
167 'gui-completion', 'ConsoleWidget.gui_completion',
168 "use a GUI widget for tab completion",
169 "use plaintext output for completion"
170 ))
171 flags.update(boolean_flag(
172 'confirm-exit', 'IPythonQtConsoleApp.confirm_exit',
173 """Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
174 to force a direct exit without any confirmation.
175 """,
176 """Don't prompt the user when exiting. This will terminate the kernel
177 if it is owned by the frontend, and leave it alive if it is external.
178 """
179 ))
180 # the flags that are specific to the frontend
181 # these must be scrubbed before being passed to the kernel,
182 # or it will raise an error on unrecognized flags
183 qt_flags = ['existing', 'pure', 'plain', 'gui-completion', 'no-gui-completion',
184 'confirm-exit', 'no-confirm-exit']
162 185
163 186 aliases = dict(ipkernel_aliases)
164 187
165 188 aliases.update(dict(
166 189 hb = 'IPythonQtConsoleApp.hb_port',
167 190 shell = 'IPythonQtConsoleApp.shell_port',
168 191 iopub = 'IPythonQtConsoleApp.iopub_port',
169 192 stdin = 'IPythonQtConsoleApp.stdin_port',
170 193 ip = 'IPythonQtConsoleApp.ip',
171 194
172 195 plain = 'IPythonQtConsoleApp.plain',
173 196 pure = 'IPythonQtConsoleApp.pure',
174 gui_completion = 'FrontendWidget.gui_completion',
197 gui_completion = 'ConsoleWidget.gui_completion',
175 198 style = 'IPythonWidget.syntax_style',
176 199 stylesheet = 'IPythonQtConsoleApp.stylesheet',
177 200 colors = 'ZMQInteractiveShell.colors',
178 201
179 202 editor = 'IPythonWidget.editor',
180 203 pi = 'IPythonWidget.in_prompt',
181 204 po = 'IPythonWidget.out_prompt',
182 205 si = 'IPythonWidget.input_sep',
183 206 so = 'IPythonWidget.output_sep',
184 207 so2 = 'IPythonWidget.output_sep2',
185 208 ))
186 209
187 210 #-----------------------------------------------------------------------------
188 211 # IPythonQtConsole
189 212 #-----------------------------------------------------------------------------
190 213
191 214 class IPythonQtConsoleApp(BaseIPythonApplication):
192 215 name = 'ipython-qtconsole'
193 216 default_config_file_name='ipython_config.py'
194 217 classes = [IPKernelApp, IPythonWidget, ZMQInteractiveShell, ProfileDir]
195 218 flags = Dict(flags)
196 219 aliases = Dict(aliases)
197 220
198 221 kernel_argv = List(Unicode)
199 222
200 223 # connection info:
201 224 ip = Unicode(LOCALHOST, config=True,
202 225 help="""Set the kernel\'s IP address [default localhost].
203 226 If the IP address is something other than localhost, then
204 227 Consoles on other machines will be able to connect
205 228 to the Kernel, so be careful!"""
206 229 )
207 230 hb_port = Int(0, config=True,
208 231 help="set the heartbeat port [default: random]")
209 232 shell_port = Int(0, config=True,
210 233 help="set the shell (XREP) port [default: random]")
211 234 iopub_port = Int(0, config=True,
212 235 help="set the iopub (PUB) port [default: random]")
213 236 stdin_port = Int(0, config=True,
214 237 help="set the stdin (XREQ) port [default: random]")
215 238
216 existing = Bool(False, config=True,
239 existing = CBool(False, config=True,
217 240 help="Whether to connect to an already running Kernel.")
218 241
219 242 stylesheet = Unicode('', config=True,
220 243 help="path to a custom CSS stylesheet")
221 244
222 pure = Bool(False, config=True,
245 pure = CBool(False, config=True,
223 246 help="Use a pure Python kernel instead of an IPython kernel.")
224 plain = Bool(False, config=True,
247 plain = CBool(False, config=True,
225 248 help="Use a plaintext widget instead of rich text (plain can't print/save).")
226 249
227 250 def _pure_changed(self, name, old, new):
228 251 kind = 'plain' if self.plain else 'rich'
229 252 self.config.ConsoleWidget.kind = kind
230 253 if self.pure:
231 254 self.widget_factory = FrontendWidget
232 255 elif self.plain:
233 256 self.widget_factory = IPythonWidget
234 257 else:
235 258 self.widget_factory = RichIPythonWidget
236 259
237 260 _plain_changed = _pure_changed
238 261
262 confirm_exit = CBool(True, config=True,
263 help="""
264 Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
265 to force a direct exit without any confirmation.""",
266 )
267
239 268 # the factory for creating a widget
240 269 widget_factory = Any(RichIPythonWidget)
241 270
242 271 def parse_command_line(self, argv=None):
243 272 super(IPythonQtConsoleApp, self).parse_command_line(argv)
244 273 if argv is None:
245 274 argv = sys.argv[1:]
246 275
247 276 self.kernel_argv = list(argv) # copy
248 277
249 278 # scrub frontend-specific flags
250 279 for a in argv:
251 280 if a.startswith('--') and a[2:] in qt_flags:
252 281 self.kernel_argv.remove(a)
253 282
254 283 def init_kernel_manager(self):
255 284 # Don't let Qt or ZMQ swallow KeyboardInterupts.
256 285 signal.signal(signal.SIGINT, signal.SIG_DFL)
257 286
258 287 # Create a KernelManager and start a kernel.
259 288 self.kernel_manager = QtKernelManager(
260 289 shell_address=(self.ip, self.shell_port),
261 290 sub_address=(self.ip, self.iopub_port),
262 291 stdin_address=(self.ip, self.stdin_port),
263 292 hb_address=(self.ip, self.hb_port)
264 293 )
265 294 # start the kernel
266 295 if not self.existing:
267 296 kwargs = dict(ip=self.ip, ipython=not self.pure)
268 297 kwargs['extra_arguments'] = self.kernel_argv
269 298 self.kernel_manager.start_kernel(**kwargs)
270 299 self.kernel_manager.start_channels()
271 300
272 301
273 302 def init_qt_elements(self):
274 303 # Create the widget.
275 304 self.app = QtGui.QApplication([])
276 305 local_kernel = (not self.existing) or self.ip in LOCAL_IPS
277 306 self.widget = self.widget_factory(config=self.config,
278 307 local_kernel=local_kernel)
279 308 self.widget.kernel_manager = self.kernel_manager
280 309 self.window = MainWindow(self.app, self.widget, self.existing,
281 may_close=local_kernel)
310 may_close=local_kernel,
311 confirm_exit=self.confirm_exit)
282 312 self.window.setWindowTitle('Python' if self.pure else 'IPython')
283 313
284 314 def init_colors(self):
285 315 """Configure the coloring of the widget"""
286 316 # Note: This will be dramatically simplified when colors
287 317 # are removed from the backend.
288 318
289 319 if self.pure:
290 320 # only IPythonWidget supports styling
291 321 return
292 322
293 323 # parse the colors arg down to current known labels
294 324 try:
295 325 colors = self.config.ZMQInteractiveShell.colors
296 326 except AttributeError:
297 327 colors = None
298 328 try:
299 329 style = self.config.IPythonWidget.colors
300 330 except AttributeError:
301 331 style = None
302 332
303 333 # find the value for colors:
304 334 if colors:
305 335 colors=colors.lower()
306 336 if colors in ('lightbg', 'light'):
307 337 colors='lightbg'
308 338 elif colors in ('dark', 'linux'):
309 339 colors='linux'
310 340 else:
311 341 colors='nocolor'
312 342 elif style:
313 343 if style=='bw':
314 344 colors='nocolor'
315 345 elif styles.dark_style(style):
316 346 colors='linux'
317 347 else:
318 348 colors='lightbg'
319 349 else:
320 350 colors=None
321 351
322 352 # Configure the style.
323 353 widget = self.widget
324 354 if style:
325 355 widget.style_sheet = styles.sheet_from_template(style, colors)
326 356 widget.syntax_style = style
327 357 widget._syntax_style_changed()
328 358 widget._style_sheet_changed()
329 359 elif colors:
330 360 # use a default style
331 361 widget.set_default_style(colors=colors)
332 362 else:
333 363 # this is redundant for now, but allows the widget's
334 364 # defaults to change
335 365 widget.set_default_style()
336 366
337 367 if self.stylesheet:
338 368 # we got an expicit stylesheet
339 369 if os.path.isfile(self.stylesheet):
340 370 with open(self.stylesheet) as f:
341 371 sheet = f.read()
342 372 widget.style_sheet = sheet
343 373 widget._style_sheet_changed()
344 374 else:
345 375 raise IOError("Stylesheet %r not found."%self.stylesheet)
346 376
347 377 def initialize(self, argv=None):
348 378 super(IPythonQtConsoleApp, self).initialize(argv)
349 379 self.init_kernel_manager()
350 380 self.init_qt_elements()
351 381 self.init_colors()
352 382
353 383 def start(self):
354 384
355 385 # draw the window
356 386 self.window.show()
357 387
358 388 # Start the application main loop.
359 389 self.app.exec_()
360 390
361 391 #-----------------------------------------------------------------------------
362 392 # Main entry point
363 393 #-----------------------------------------------------------------------------
364 394
365 395 def main():
366 396 app = IPythonQtConsoleApp()
367 397 app.initialize()
368 398 app.start()
369 399
370 400
371 401 if __name__ == '__main__':
372 402 main()
General Comments 0
You need to be logged in to leave comments. Login now