##// END OF EJS Templates
move %history and %save action into mainwindow
Matthias BUSSONNIER -
Show More
@@ -1,300 +1,286 b''
1 # System library imports
1 # System library imports
2 from IPython.external.qt import QtGui
2 from IPython.external.qt import QtGui
3
3
4 # Local imports
4 # Local imports
5 from IPython.utils.traitlets import Bool
5 from IPython.utils.traitlets import Bool
6 from console_widget import ConsoleWidget
6 from console_widget import ConsoleWidget
7
7
8
8
9 class HistoryConsoleWidget(ConsoleWidget):
9 class HistoryConsoleWidget(ConsoleWidget):
10 """ A ConsoleWidget that keeps a history of the commands that have been
10 """ A ConsoleWidget that keeps a history of the commands that have been
11 executed and provides a readline-esque interface to this history.
11 executed and provides a readline-esque interface to this history.
12 """
12 """
13
13
14 #------ Configuration ------------------------------------------------------
14 #------ Configuration ------------------------------------------------------
15
15
16 # If enabled, the input buffer will become "locked" to history movement when
16 # If enabled, the input buffer will become "locked" to history movement when
17 # an edit is made to a multi-line input buffer. To override the lock, use
17 # an edit is made to a multi-line input buffer. To override the lock, use
18 # Shift in conjunction with the standard history cycling keys.
18 # Shift in conjunction with the standard history cycling keys.
19 history_lock = Bool(False, config=True)
19 history_lock = Bool(False, config=True)
20
20
21 #---------------------------------------------------------------------------
21 #---------------------------------------------------------------------------
22 # 'object' interface
22 # 'object' interface
23 #---------------------------------------------------------------------------
23 #---------------------------------------------------------------------------
24
24
25 def __init__(self, *args, **kw):
25 def __init__(self, *args, **kw):
26 super(HistoryConsoleWidget, self).__init__(*args, **kw)
26 super(HistoryConsoleWidget, self).__init__(*args, **kw)
27
27
28 # HistoryConsoleWidget protected variables.
28 # HistoryConsoleWidget protected variables.
29 self._history = []
29 self._history = []
30 self._history_edits = {}
30 self._history_edits = {}
31 self._history_index = 0
31 self._history_index = 0
32 self._history_prefix = ''
32 self._history_prefix = ''
33
33
34 self.history_action = QtGui.QAction("History",
35 self,
36 statusTip="show command history",
37 triggered=self.history_magic)
38 self.history_action.setDisabled(True)
39 self.addAction(self.history_action)
40
41 self.save_action = QtGui.QAction("Export History ",
42 self,
43 statusTip="Export History as Python File",
44 triggered=self.save_magic)
45 self.save_action.setDisabled(True)
46 self.addAction(self.save_action)
47
48 #---------------------------------------------------------------------------
34 #---------------------------------------------------------------------------
49 # 'ConsoleWidget' public interface
35 # 'ConsoleWidget' public interface
50 #---------------------------------------------------------------------------
36 #---------------------------------------------------------------------------
51
37
52 def execute(self, source=None, hidden=False, interactive=False):
38 def execute(self, source=None, hidden=False, interactive=False):
53 """ Reimplemented to the store history.
39 """ Reimplemented to the store history.
54 """
40 """
55 if not hidden:
41 if not hidden:
56 history = self.input_buffer if source is None else source
42 history = self.input_buffer if source is None else source
57
43
58 executed = super(HistoryConsoleWidget, self).execute(
44 executed = super(HistoryConsoleWidget, self).execute(
59 source, hidden, interactive)
45 source, hidden, interactive)
60
46
61 if executed and not hidden:
47 if executed and not hidden:
62 # Save the command unless it was an empty string or was identical
48 # Save the command unless it was an empty string or was identical
63 # to the previous command.
49 # to the previous command.
64 history = history.rstrip()
50 history = history.rstrip()
65 if history and (not self._history or self._history[-1] != history):
51 if history and (not self._history or self._history[-1] != history):
66 self._history.append(history)
52 self._history.append(history)
67
53
68 # Emulate readline: reset all history edits.
54 # Emulate readline: reset all history edits.
69 self._history_edits = {}
55 self._history_edits = {}
70
56
71 # Move the history index to the most recent item.
57 # Move the history index to the most recent item.
72 self._history_index = len(self._history)
58 self._history_index = len(self._history)
73
59
74 return executed
60 return executed
75
61
76 #---------------------------------------------------------------------------
62 #---------------------------------------------------------------------------
77 # 'ConsoleWidget' abstract interface
63 # 'ConsoleWidget' abstract interface
78 #---------------------------------------------------------------------------
64 #---------------------------------------------------------------------------
79
65
80 def _up_pressed(self, shift_modifier):
66 def _up_pressed(self, shift_modifier):
81 """ Called when the up key is pressed. Returns whether to continue
67 """ Called when the up key is pressed. Returns whether to continue
82 processing the event.
68 processing the event.
83 """
69 """
84 prompt_cursor = self._get_prompt_cursor()
70 prompt_cursor = self._get_prompt_cursor()
85 if self._get_cursor().blockNumber() == prompt_cursor.blockNumber():
71 if self._get_cursor().blockNumber() == prompt_cursor.blockNumber():
86 # Bail out if we're locked.
72 # Bail out if we're locked.
87 if self._history_locked() and not shift_modifier:
73 if self._history_locked() and not shift_modifier:
88 return False
74 return False
89
75
90 # Set a search prefix based on the cursor position.
76 # Set a search prefix based on the cursor position.
91 col = self._get_input_buffer_cursor_column()
77 col = self._get_input_buffer_cursor_column()
92 input_buffer = self.input_buffer
78 input_buffer = self.input_buffer
93 if self._history_index == len(self._history) or \
79 if self._history_index == len(self._history) or \
94 (self._history_prefix and col != len(self._history_prefix)):
80 (self._history_prefix and col != len(self._history_prefix)):
95 self._history_index = len(self._history)
81 self._history_index = len(self._history)
96 self._history_prefix = input_buffer[:col]
82 self._history_prefix = input_buffer[:col]
97
83
98 # Perform the search.
84 # Perform the search.
99 self.history_previous(self._history_prefix,
85 self.history_previous(self._history_prefix,
100 as_prefix=not shift_modifier)
86 as_prefix=not shift_modifier)
101
87
102 # Go to the first line of the prompt for seemless history scrolling.
88 # Go to the first line of the prompt for seemless history scrolling.
103 # Emulate readline: keep the cursor position fixed for a prefix
89 # Emulate readline: keep the cursor position fixed for a prefix
104 # search.
90 # search.
105 cursor = self._get_prompt_cursor()
91 cursor = self._get_prompt_cursor()
106 if self._history_prefix:
92 if self._history_prefix:
107 cursor.movePosition(QtGui.QTextCursor.Right,
93 cursor.movePosition(QtGui.QTextCursor.Right,
108 n=len(self._history_prefix))
94 n=len(self._history_prefix))
109 else:
95 else:
110 cursor.movePosition(QtGui.QTextCursor.EndOfLine)
96 cursor.movePosition(QtGui.QTextCursor.EndOfLine)
111 self._set_cursor(cursor)
97 self._set_cursor(cursor)
112
98
113 return False
99 return False
114
100
115 return True
101 return True
116
102
117 def _down_pressed(self, shift_modifier):
103 def _down_pressed(self, shift_modifier):
118 """ Called when the down key is pressed. Returns whether to continue
104 """ Called when the down key is pressed. Returns whether to continue
119 processing the event.
105 processing the event.
120 """
106 """
121 end_cursor = self._get_end_cursor()
107 end_cursor = self._get_end_cursor()
122 if self._get_cursor().blockNumber() == end_cursor.blockNumber():
108 if self._get_cursor().blockNumber() == end_cursor.blockNumber():
123 # Bail out if we're locked.
109 # Bail out if we're locked.
124 if self._history_locked() and not shift_modifier:
110 if self._history_locked() and not shift_modifier:
125 return False
111 return False
126
112
127 # Perform the search.
113 # Perform the search.
128 replaced = self.history_next(self._history_prefix,
114 replaced = self.history_next(self._history_prefix,
129 as_prefix=not shift_modifier)
115 as_prefix=not shift_modifier)
130
116
131 # Emulate readline: keep the cursor position fixed for a prefix
117 # Emulate readline: keep the cursor position fixed for a prefix
132 # search. (We don't need to move the cursor to the end of the buffer
118 # search. (We don't need to move the cursor to the end of the buffer
133 # in the other case because this happens automatically when the
119 # in the other case because this happens automatically when the
134 # input buffer is set.)
120 # input buffer is set.)
135 if self._history_prefix and replaced:
121 if self._history_prefix and replaced:
136 cursor = self._get_prompt_cursor()
122 cursor = self._get_prompt_cursor()
137 cursor.movePosition(QtGui.QTextCursor.Right,
123 cursor.movePosition(QtGui.QTextCursor.Right,
138 n=len(self._history_prefix))
124 n=len(self._history_prefix))
139 self._set_cursor(cursor)
125 self._set_cursor(cursor)
140
126
141 return False
127 return False
142
128
143 return True
129 return True
144
130
145 #---------------------------------------------------------------------------
131 #---------------------------------------------------------------------------
146 # 'HistoryConsoleWidget' public interface
132 # 'HistoryConsoleWidget' public interface
147 #---------------------------------------------------------------------------
133 #---------------------------------------------------------------------------
148
134
149 def history_previous(self, substring='', as_prefix=True):
135 def history_previous(self, substring='', as_prefix=True):
150 """ If possible, set the input buffer to a previous history item.
136 """ If possible, set the input buffer to a previous history item.
151
137
152 Parameters:
138 Parameters:
153 -----------
139 -----------
154 substring : str, optional
140 substring : str, optional
155 If specified, search for an item with this substring.
141 If specified, search for an item with this substring.
156 as_prefix : bool, optional
142 as_prefix : bool, optional
157 If True, the substring must match at the beginning (default).
143 If True, the substring must match at the beginning (default).
158
144
159 Returns:
145 Returns:
160 --------
146 --------
161 Whether the input buffer was changed.
147 Whether the input buffer was changed.
162 """
148 """
163 index = self._history_index
149 index = self._history_index
164 replace = False
150 replace = False
165 while index > 0:
151 while index > 0:
166 index -= 1
152 index -= 1
167 history = self._get_edited_history(index)
153 history = self._get_edited_history(index)
168 if (as_prefix and history.startswith(substring)) \
154 if (as_prefix and history.startswith(substring)) \
169 or (not as_prefix and substring in history):
155 or (not as_prefix and substring in history):
170 replace = True
156 replace = True
171 break
157 break
172
158
173 if replace:
159 if replace:
174 self._store_edits()
160 self._store_edits()
175 self._history_index = index
161 self._history_index = index
176 self.input_buffer = history
162 self.input_buffer = history
177
163
178 return replace
164 return replace
179
165
180 def history_next(self, substring='', as_prefix=True):
166 def history_next(self, substring='', as_prefix=True):
181 """ If possible, set the input buffer to a subsequent history item.
167 """ If possible, set the input buffer to a subsequent history item.
182
168
183 Parameters:
169 Parameters:
184 -----------
170 -----------
185 substring : str, optional
171 substring : str, optional
186 If specified, search for an item with this substring.
172 If specified, search for an item with this substring.
187 as_prefix : bool, optional
173 as_prefix : bool, optional
188 If True, the substring must match at the beginning (default).
174 If True, the substring must match at the beginning (default).
189
175
190 Returns:
176 Returns:
191 --------
177 --------
192 Whether the input buffer was changed.
178 Whether the input buffer was changed.
193 """
179 """
194 index = self._history_index
180 index = self._history_index
195 replace = False
181 replace = False
196 while self._history_index < len(self._history):
182 while self._history_index < len(self._history):
197 index += 1
183 index += 1
198 history = self._get_edited_history(index)
184 history = self._get_edited_history(index)
199 if (as_prefix and history.startswith(substring)) \
185 if (as_prefix and history.startswith(substring)) \
200 or (not as_prefix and substring in history):
186 or (not as_prefix and substring in history):
201 replace = True
187 replace = True
202 break
188 break
203
189
204 if replace:
190 if replace:
205 self._store_edits()
191 self._store_edits()
206 self._history_index = index
192 self._history_index = index
207 self.input_buffer = history
193 self.input_buffer = history
208
194
209 return replace
195 return replace
210
196
211 def history_tail(self, n=10):
197 def history_tail(self, n=10):
212 """ Get the local history list.
198 """ Get the local history list.
213
199
214 Parameters:
200 Parameters:
215 -----------
201 -----------
216 n : int
202 n : int
217 The (maximum) number of history items to get.
203 The (maximum) number of history items to get.
218 """
204 """
219 return self._history[-n:]
205 return self._history[-n:]
220
206
221 def history_magic(self):
207 def history_magic(self):
222 self.pasteMagic("%history")
208 self.pasteMagic("%history")
223
209
224 def _request_update_session_history_length(self):
210 def _request_update_session_history_length(self):
225 msg_id = self.kernel_manager.shell_channel.execute('',
211 msg_id = self.kernel_manager.shell_channel.execute('',
226 silent=True,
212 silent=True,
227 user_expressions={
213 user_expressions={
228 'hlen':'len(get_ipython().history_manager.input_hist_raw)',
214 'hlen':'len(get_ipython().history_manager.input_hist_raw)',
229 }
215 }
230 )
216 )
231 self._request_info['execute'] = self._ExecutionRequest(msg_id, 'save_magic')
217 self._request_info['execute'] = self._ExecutionRequest(msg_id, 'save_magic')
232
218
233 def _handle_execute_reply(self, msg):
219 def _handle_execute_reply(self, msg):
234 """ Handles replies for code execution, here only session history length
220 """ Handles replies for code execution, here only session history length
235 """
221 """
236 info = self._request_info.get('execute')
222 info = self._request_info.get('execute')
237 if info and info.id == msg['parent_header']['msg_id'] and \
223 if info and info.id == msg['parent_header']['msg_id'] and \
238 info.kind == 'save_magic' and not self._hidden:
224 info.kind == 'save_magic' and not self._hidden:
239 content = msg['content']
225 content = msg['content']
240 status = content['status']
226 status = content['status']
241 if status == 'ok':
227 if status == 'ok':
242 self._max_session_history=(int(content['user_expressions']['hlen']))
228 self._max_session_history=(int(content['user_expressions']['hlen']))
243
229
244 def save_magic(self):
230 def save_magic(self):
245 # update the session history length
231 # update the session history length
246 self._request_update_session_history_length()
232 self._request_update_session_history_length()
247
233
248 file_name,extFilter = QtGui.QFileDialog.getSaveFileName(self,
234 file_name,extFilter = QtGui.QFileDialog.getSaveFileName(self,
249 "Enter A filename",
235 "Enter A filename",
250 filter='Python File (*.py);; All files (*.*)'
236 filter='Python File (*.py);; All files (*.*)'
251 )
237 )
252
238
253 # let's the user search/type for a file name, while the history length
239 # let's the user search/type for a file name, while the history length
254 # is fetched
240 # is fetched
255
241
256 if file_name:
242 if file_name:
257 hist_range, ok = QtGui.QInputDialog.getText(self,
243 hist_range, ok = QtGui.QInputDialog.getText(self,
258 'Please enter an interval of command to save',
244 'Please enter an interval of command to save',
259 'Saving commands:',
245 'Saving commands:',
260 text=str('1-'+str(self._max_session_history))
246 text=str('1-'+str(self._max_session_history))
261 )
247 )
262 if ok:
248 if ok:
263 self.pasteMagic("%save"+" "+file_name+" "+str(hist_range))
249 self.pasteMagic("%save"+" "+file_name+" "+str(hist_range))
264
250
265 #---------------------------------------------------------------------------
251 #---------------------------------------------------------------------------
266 # 'HistoryConsoleWidget' protected interface
252 # 'HistoryConsoleWidget' protected interface
267 #---------------------------------------------------------------------------
253 #---------------------------------------------------------------------------
268
254
269 def _history_locked(self):
255 def _history_locked(self):
270 """ Returns whether history movement is locked.
256 """ Returns whether history movement is locked.
271 """
257 """
272 return (self.history_lock and
258 return (self.history_lock and
273 (self._get_edited_history(self._history_index) !=
259 (self._get_edited_history(self._history_index) !=
274 self.input_buffer) and
260 self.input_buffer) and
275 (self._get_prompt_cursor().blockNumber() !=
261 (self._get_prompt_cursor().blockNumber() !=
276 self._get_end_cursor().blockNumber()))
262 self._get_end_cursor().blockNumber()))
277
263
278 def _get_edited_history(self, index):
264 def _get_edited_history(self, index):
279 """ Retrieves a history item, possibly with temporary edits.
265 """ Retrieves a history item, possibly with temporary edits.
280 """
266 """
281 if index in self._history_edits:
267 if index in self._history_edits:
282 return self._history_edits[index]
268 return self._history_edits[index]
283 elif index == len(self._history):
269 elif index == len(self._history):
284 return unicode()
270 return unicode()
285 return self._history[index]
271 return self._history[index]
286
272
287 def _set_history(self, history):
273 def _set_history(self, history):
288 """ Replace the current history with a sequence of history items.
274 """ Replace the current history with a sequence of history items.
289 """
275 """
290 self._history = list(history)
276 self._history = list(history)
291 self._history_edits = {}
277 self._history_edits = {}
292 self._history_index = len(self._history)
278 self._history_index = len(self._history)
293
279
294 def _store_edits(self):
280 def _store_edits(self):
295 """ If there are edits to the current input buffer, store them.
281 """ If there are edits to the current input buffer, store them.
296 """
282 """
297 current = self.input_buffer
283 current = self.input_buffer
298 if self._history_index == len(self._history) or \
284 if self._history_index == len(self._history) or \
299 self._history[self._history_index] != current:
285 self._history[self._history_index] != current:
300 self._history_edits[self._history_index] = current
286 self._history_edits[self._history_index] = current
@@ -1,798 +1,803 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
12
13 """
13 """
14
14
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 # Imports
16 # Imports
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19 # stdlib imports
19 # stdlib imports
20 import json
20 import json
21 import os
21 import os
22 import signal
22 import signal
23 import sys
23 import sys
24
24
25 # System library imports
25 # System library imports
26 from IPython.external.qt import QtGui,QtCore
26 from IPython.external.qt import QtGui,QtCore
27 from pygments.styles import get_all_styles
27 from pygments.styles import get_all_styles
28
28
29 # Local imports
29 # Local imports
30 from IPython.config.application import boolean_flag
30 from IPython.config.application import boolean_flag
31 from IPython.core.application import BaseIPythonApplication
31 from IPython.core.application import BaseIPythonApplication
32 from IPython.core.profiledir import ProfileDir
32 from IPython.core.profiledir import ProfileDir
33 from IPython.lib.kernel import tunnel_to_kernel, find_connection_file
33 from IPython.lib.kernel import tunnel_to_kernel, find_connection_file
34 from IPython.frontend.qt.console.frontend_widget import FrontendWidget
34 from IPython.frontend.qt.console.frontend_widget import FrontendWidget
35 from IPython.frontend.qt.console.ipython_widget import IPythonWidget
35 from IPython.frontend.qt.console.ipython_widget import IPythonWidget
36 from IPython.frontend.qt.console.rich_ipython_widget import RichIPythonWidget
36 from IPython.frontend.qt.console.rich_ipython_widget import RichIPythonWidget
37 from IPython.frontend.qt.console import styles
37 from IPython.frontend.qt.console import styles
38 from IPython.frontend.qt.kernelmanager import QtKernelManager
38 from IPython.frontend.qt.kernelmanager import QtKernelManager
39 from IPython.utils.path import filefind
39 from IPython.utils.path import filefind
40 from IPython.utils.py3compat import str_to_bytes
40 from IPython.utils.py3compat import str_to_bytes
41 from IPython.utils.traitlets import (
41 from IPython.utils.traitlets import (
42 Dict, List, Unicode, Int, CaselessStrEnum, CBool, Any
42 Dict, List, Unicode, Int, CaselessStrEnum, CBool, Any
43 )
43 )
44 from IPython.zmq.ipkernel import (
44 from IPython.zmq.ipkernel import (
45 flags as ipkernel_flags,
45 flags as ipkernel_flags,
46 aliases as ipkernel_aliases,
46 aliases as ipkernel_aliases,
47 IPKernelApp
47 IPKernelApp
48 )
48 )
49 from IPython.zmq.session import Session, default_secure
49 from IPython.zmq.session import Session, default_secure
50 from IPython.zmq.zmqshell import ZMQInteractiveShell
50 from IPython.zmq.zmqshell import ZMQInteractiveShell
51
51
52 import application_rc
52 import application_rc
53
53
54 #-----------------------------------------------------------------------------
54 #-----------------------------------------------------------------------------
55 # Network Constants
55 # Network Constants
56 #-----------------------------------------------------------------------------
56 #-----------------------------------------------------------------------------
57
57
58 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
58 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
59
59
60 #-----------------------------------------------------------------------------
60 #-----------------------------------------------------------------------------
61 # Globals
61 # Globals
62 #-----------------------------------------------------------------------------
62 #-----------------------------------------------------------------------------
63
63
64 _examples = """
64 _examples = """
65 ipython qtconsole # start the qtconsole
65 ipython qtconsole # start the qtconsole
66 ipython qtconsole --pylab=inline # start with pylab in inline plotting mode
66 ipython qtconsole --pylab=inline # start with pylab in inline plotting mode
67 """
67 """
68
68
69 #-----------------------------------------------------------------------------
69 #-----------------------------------------------------------------------------
70 # Classes
70 # Classes
71 #-----------------------------------------------------------------------------
71 #-----------------------------------------------------------------------------
72
72
73 class MainWindow(QtGui.QMainWindow):
73 class MainWindow(QtGui.QMainWindow):
74
74
75 #---------------------------------------------------------------------------
75 #---------------------------------------------------------------------------
76 # 'object' interface
76 # 'object' interface
77 #---------------------------------------------------------------------------
77 #---------------------------------------------------------------------------
78
78
79 def __init__(self, app, frontend, existing=False, may_close=True,
79 def __init__(self, app, frontend, existing=False, may_close=True,
80 confirm_exit=True):
80 confirm_exit=True):
81 """ Create a MainWindow for the specified FrontendWidget.
81 """ Create a MainWindow for the specified FrontendWidget.
82
82
83 The app is passed as an argument to allow for different
83 The app is passed as an argument to allow for different
84 closing behavior depending on whether we are the Kernel's parent.
84 closing behavior depending on whether we are the Kernel's parent.
85
85
86 If existing is True, then this Console does not own the Kernel.
86 If existing is True, then this Console does not own the Kernel.
87
87
88 If may_close is True, then this Console is permitted to close the kernel
88 If may_close is True, then this Console is permitted to close the kernel
89 """
89 """
90 super(MainWindow, self).__init__()
90 super(MainWindow, self).__init__()
91 self._app = app
91 self._app = app
92 self._frontend = frontend
92 self._frontend = frontend
93 self._existing = existing
93 self._existing = existing
94 if existing:
94 if existing:
95 self._may_close = may_close
95 self._may_close = may_close
96 else:
96 else:
97 self._may_close = True
97 self._may_close = True
98 self._frontend.exit_requested.connect(self.close)
98 self._frontend.exit_requested.connect(self.close)
99 self._confirm_exit = confirm_exit
99 self._confirm_exit = confirm_exit
100 self.setCentralWidget(frontend)
100 self.setCentralWidget(frontend)
101
101
102 # MenuBar is always present on Mac Os, so let's populate
102 # MenuBar is always present on Mac Os, so let's populate
103 # it with possible action, don't do it on other platform
103 # it with possible action, don't do it on other platform
104 # as some user might not want the menu bar, or give them
104 # as some user might not want the menu bar, or give them
105 # an option to remove it
105 # an option to remove it
106
106
107 def initMenuBar(self):
107 def initMenuBar(self):
108 #create menu in the order they should appear in the menu bar
108 #create menu in the order they should appear in the menu bar
109 self.fileMenu = self.menuBar().addMenu("File")
109 self.fileMenu = self.menuBar().addMenu("File")
110 self.editMenu = self.menuBar().addMenu("Edit")
110 self.editMenu = self.menuBar().addMenu("Edit")
111 self.fontMenu = self.menuBar().addMenu("Font")
111 self.fontMenu = self.menuBar().addMenu("Font")
112 self.windowMenu = self.menuBar().addMenu("Window")
112 self.windowMenu = self.menuBar().addMenu("Window")
113 self.magicMenu = self.menuBar().addMenu("Magic")
113 self.magicMenu = self.menuBar().addMenu("Magic")
114
114
115 # please keep the Help menu in Mac Os even if empty. It will
115 # please keep the Help menu in Mac Os even if empty. It will
116 # automatically contain a search field to search inside menus and
116 # automatically contain a search field to search inside menus and
117 # please keep it spelled in English, as long as Qt Doesn't support
117 # please keep it spelled in English, as long as Qt Doesn't support
118 # a QAction.MenuRole like HelpMenuRole otherwise it will loose
118 # a QAction.MenuRole like HelpMenuRole otherwise it will loose
119 # this search field fonctionnality
119 # this search field fonctionnality
120
120
121 self.helpMenu = self.menuBar().addMenu("Help")
121 self.helpMenu = self.menuBar().addMenu("Help")
122
122
123 # sould wrap every line of the following block into a try/except,
123 # sould wrap every line of the following block into a try/except,
124 # as we are not sure of instanciating a _frontend which support all
124 # as we are not sure of instanciating a _frontend which support all
125 # theses actions, but there might be a better way
125 # theses actions, but there might be a better way
126 try:
126 try:
127 self.fileMenu.addAction(self._frontend.print_action)
127 self.fileMenu.addAction(self._frontend.print_action)
128 except AttributeError:
128 except AttributeError:
129 print "trying to add unexisting action, skipping"
129 print "trying to add unexisting action, skipping"
130
130
131 try:
131 try:
132 self.fileMenu.addAction(self._frontend.export_action)
132 self.fileMenu.addAction(self._frontend.export_action)
133 except AttributeError:
133 except AttributeError:
134 print "trying to add unexisting action, skipping"
134 print "trying to add unexisting action, skipping"
135
135
136 try:
136 try:
137 self.fileMenu.addAction(self._frontend.select_all_action)
137 self.fileMenu.addAction(self._frontend.select_all_action)
138 except AttributeError:
138 except AttributeError:
139 print "trying to add unexisting action, skipping"
139 print "trying to add unexisting action, skipping"
140
140
141 try:
141 try:
142 self.undo_action = QtGui.QAction("Undo",
142 self.undo_action = QtGui.QAction("Undo",
143 self,
143 self,
144 shortcut="Ctrl+Z",
144 shortcut="Ctrl+Z",
145 statusTip="Undo last action if possible",
145 statusTip="Undo last action if possible",
146 triggered=self._frontend.undo)
146 triggered=self._frontend.undo)
147
147
148 self.editMenu.addAction(self.undo_action)
148 self.editMenu.addAction(self.undo_action)
149 except AttributeError:
149 except AttributeError:
150 print "trying to add unexisting action, skipping"
150 print "trying to add unexisting action, skipping"
151
151
152 try:
152 try:
153 self.redo_action = QtGui.QAction("Redo",
153 self.redo_action = QtGui.QAction("Redo",
154 self,
154 self,
155 shortcut="Ctrl+Shift+Z",
155 shortcut="Ctrl+Shift+Z",
156 statusTip="Redo last action if possible",
156 statusTip="Redo last action if possible",
157 triggered=self._frontend.redo)
157 triggered=self._frontend.redo)
158 self.editMenu.addAction(self.redo_action)
158 self.editMenu.addAction(self.redo_action)
159 except AttributeError:
159 except AttributeError:
160 print "trying to add unexisting action, skipping"
160 print "trying to add unexisting action, skipping"
161
161
162 try:
162 try:
163 self.fontMenu.addAction(self._frontend.increase_font_size)
163 self.fontMenu.addAction(self._frontend.increase_font_size)
164 except AttributeError:
164 except AttributeError:
165 print "trying to add unexisting action, skipping"
165 print "trying to add unexisting action, skipping"
166
166
167 try:
167 try:
168 self.fontMenu.addAction(self._frontend.decrease_font_size)
168 self.fontMenu.addAction(self._frontend.decrease_font_size)
169 except AttributeError:
169 except AttributeError:
170 print "trying to add unexisting action, skipping"
170 print "trying to add unexisting action, skipping"
171
171
172 try:
172 try:
173 self.fontMenu.addAction(self._frontend.reset_font_size)
173 self.fontMenu.addAction(self._frontend.reset_font_size)
174 except AttributeError:
174 except AttributeError:
175 print "trying to add unexisting action, skipping"
175 print "trying to add unexisting action, skipping"
176
176
177 try:
177 try:
178 self.reset_action = QtGui.QAction("Reset",
178 self.reset_action = QtGui.QAction("Reset",
179 self,
179 self,
180 statusTip="Clear all varible from workspace",
180 statusTip="Clear all varible from workspace",
181 triggered=self._frontend.reset_magic)
181 triggered=self._frontend.reset_magic)
182 self.magicMenu.addAction(self.reset_action)
182 self.magicMenu.addAction(self.reset_action)
183 except AttributeError:
183 except AttributeError:
184 print "trying to add unexisting action (reset), skipping"
184 print "trying to add unexisting action (reset), skipping"
185
185
186 try:
186 try:
187 self.magicMenu.addAction(self._frontend.history_action)
187 self.history_action = QtGui.QAction("History",
188 self._frontend.history_action.setEnabled(True)
188 self,
189 statusTip="show command history",
190 triggered=self._frontend.history_magic)
191 self.magicMenu.addAction(self.history_action)
189 except AttributeError:
192 except AttributeError:
190 print "trying to add unexisting action (history), skipping"
193 print "trying to add unexisting action (history), skipping"
191
194
192 try:
195 try:
193 self.magicMenu.addAction(self._frontend.save_action)
196 self.save_action = QtGui.QAction("Export History ",
194 self._frontend.save_action.setEnabled(True)
197 self,
198 statusTip="Export History as Python File",
199 triggered=self._frontend.save_magic)
200 self.magicMenu.addAction(self.save_action)
195 except AttributeError:
201 except AttributeError:
196 print "trying to add unexisting action (save), skipping"
202 print "trying to add unexisting action (save), skipping"
197 self._frontend.reset_action.setEnabled(True)
198
203
199 try:
204 try:
200 self.clear_action = QtGui.QAction("Clear",
205 self.clear_action = QtGui.QAction("Clear",
201 self,
206 self,
202 statusTip="Clear the console",
207 statusTip="Clear the console",
203 triggered=self._frontend.clear_magic)
208 triggered=self._frontend.clear_magic)
204 self.magicMenu.addAction(self.clear_action)
209 self.magicMenu.addAction(self.clear_action)
205 except AttributeError:
210 except AttributeError:
206 print "trying to add unexisting action, skipping"
211 print "trying to add unexisting action, skipping"
207
212
208 try:
213 try:
209 self.who_action = QtGui.QAction("Who",
214 self.who_action = QtGui.QAction("Who",
210 self,
215 self,
211 statusTip="List interactive variable",
216 statusTip="List interactive variable",
212 triggered=self._frontend.who_magic)
217 triggered=self._frontend.who_magic)
213 self.magicMenu.addAction(self.who_action)
218 self.magicMenu.addAction(self.who_action)
214 except AttributeError:
219 except AttributeError:
215 print "trying to add unexisting action (who), skipping"
220 print "trying to add unexisting action (who), skipping"
216
221
217 try:
222 try:
218 self.who_ls_action = QtGui.QAction("Who ls",
223 self.who_ls_action = QtGui.QAction("Who ls",
219 self,
224 self,
220 statusTip="Return a list of interactive variable",
225 statusTip="Return a list of interactive variable",
221 triggered=self._frontend.who_ls_magic)
226 triggered=self._frontend.who_ls_magic)
222 self.magicMenu.addAction(self.who_ls_action)
227 self.magicMenu.addAction(self.who_ls_action)
223 except AttributeError:
228 except AttributeError:
224 print "trying to add unexisting action (who_ls), skipping"
229 print "trying to add unexisting action (who_ls), skipping"
225
230
226 try:
231 try:
227 self.whos_action = QtGui.QAction("Whos",
232 self.whos_action = QtGui.QAction("Whos",
228 self,
233 self,
229 statusTip="List interactive variable with detail",
234 statusTip="List interactive variable with detail",
230 triggered=self._frontend.whos_magic)
235 triggered=self._frontend.whos_magic)
231 self.magicMenu.addAction(self.whos_action)
236 self.magicMenu.addAction(self.whos_action)
232 except AttributeError:
237 except AttributeError:
233 print "trying to add unexisting action (whos), skipping"
238 print "trying to add unexisting action (whos), skipping"
234
239
235
240
236 #---------------------------------------------------------------------------
241 #---------------------------------------------------------------------------
237 # QWidget interface
242 # QWidget interface
238 #---------------------------------------------------------------------------
243 #---------------------------------------------------------------------------
239
244
240 def closeEvent(self, event):
245 def closeEvent(self, event):
241 """ Close the window and the kernel (if necessary).
246 """ Close the window and the kernel (if necessary).
242
247
243 This will prompt the user if they are finished with the kernel, and if
248 This will prompt the user if they are finished with the kernel, and if
244 so, closes the kernel cleanly. Alternatively, if the exit magic is used,
249 so, closes the kernel cleanly. Alternatively, if the exit magic is used,
245 it closes without prompt.
250 it closes without prompt.
246 """
251 """
247 keepkernel = None #Use the prompt by default
252 keepkernel = None #Use the prompt by default
248 if hasattr(self._frontend,'_keep_kernel_on_exit'): #set by exit magic
253 if hasattr(self._frontend,'_keep_kernel_on_exit'): #set by exit magic
249 keepkernel = self._frontend._keep_kernel_on_exit
254 keepkernel = self._frontend._keep_kernel_on_exit
250
255
251 kernel_manager = self._frontend.kernel_manager
256 kernel_manager = self._frontend.kernel_manager
252
257
253 if keepkernel is None and not self._confirm_exit:
258 if keepkernel is None and not self._confirm_exit:
254 # don't prompt, just terminate the kernel if we own it
259 # don't prompt, just terminate the kernel if we own it
255 # or leave it alone if we don't
260 # or leave it alone if we don't
256 keepkernel = not self._existing
261 keepkernel = not self._existing
257
262
258 if keepkernel is None: #show prompt
263 if keepkernel is None: #show prompt
259 if kernel_manager and kernel_manager.channels_running:
264 if kernel_manager and kernel_manager.channels_running:
260 title = self.window().windowTitle()
265 title = self.window().windowTitle()
261 cancel = QtGui.QMessageBox.Cancel
266 cancel = QtGui.QMessageBox.Cancel
262 okay = QtGui.QMessageBox.Ok
267 okay = QtGui.QMessageBox.Ok
263 if self._may_close:
268 if self._may_close:
264 msg = "You are closing this Console window."
269 msg = "You are closing this Console window."
265 info = "Would you like to quit the Kernel and all attached Consoles as well?"
270 info = "Would you like to quit the Kernel and all attached Consoles as well?"
266 justthis = QtGui.QPushButton("&No, just this Console", self)
271 justthis = QtGui.QPushButton("&No, just this Console", self)
267 justthis.setShortcut('N')
272 justthis.setShortcut('N')
268 closeall = QtGui.QPushButton("&Yes, quit everything", self)
273 closeall = QtGui.QPushButton("&Yes, quit everything", self)
269 closeall.setShortcut('Y')
274 closeall.setShortcut('Y')
270 box = QtGui.QMessageBox(QtGui.QMessageBox.Question,
275 box = QtGui.QMessageBox(QtGui.QMessageBox.Question,
271 title, msg)
276 title, msg)
272 box.setInformativeText(info)
277 box.setInformativeText(info)
273 box.addButton(cancel)
278 box.addButton(cancel)
274 box.addButton(justthis, QtGui.QMessageBox.NoRole)
279 box.addButton(justthis, QtGui.QMessageBox.NoRole)
275 box.addButton(closeall, QtGui.QMessageBox.YesRole)
280 box.addButton(closeall, QtGui.QMessageBox.YesRole)
276 box.setDefaultButton(closeall)
281 box.setDefaultButton(closeall)
277 box.setEscapeButton(cancel)
282 box.setEscapeButton(cancel)
278 pixmap = QtGui.QPixmap(':/icon/IPythonConsole.png')
283 pixmap = QtGui.QPixmap(':/icon/IPythonConsole.png')
279 scaledpixmap = pixmap.scaledToWidth(64,mode=QtCore.Qt.SmoothTransformation)
284 scaledpixmap = pixmap.scaledToWidth(64,mode=QtCore.Qt.SmoothTransformation)
280 box.setIconPixmap(scaledpixmap)
285 box.setIconPixmap(scaledpixmap)
281 reply = box.exec_()
286 reply = box.exec_()
282 if reply == 1: # close All
287 if reply == 1: # close All
283 kernel_manager.shutdown_kernel()
288 kernel_manager.shutdown_kernel()
284 #kernel_manager.stop_channels()
289 #kernel_manager.stop_channels()
285 event.accept()
290 event.accept()
286 elif reply == 0: # close Console
291 elif reply == 0: # close Console
287 if not self._existing:
292 if not self._existing:
288 # Have kernel: don't quit, just close the window
293 # Have kernel: don't quit, just close the window
289 self._app.setQuitOnLastWindowClosed(False)
294 self._app.setQuitOnLastWindowClosed(False)
290 self.deleteLater()
295 self.deleteLater()
291 event.accept()
296 event.accept()
292 else:
297 else:
293 event.ignore()
298 event.ignore()
294 else:
299 else:
295 reply = QtGui.QMessageBox.question(self, title,
300 reply = QtGui.QMessageBox.question(self, title,
296 "Are you sure you want to close this Console?"+
301 "Are you sure you want to close this Console?"+
297 "\nThe Kernel and other Consoles will remain active.",
302 "\nThe Kernel and other Consoles will remain active.",
298 okay|cancel,
303 okay|cancel,
299 defaultButton=okay
304 defaultButton=okay
300 )
305 )
301 if reply == okay:
306 if reply == okay:
302 event.accept()
307 event.accept()
303 else:
308 else:
304 event.ignore()
309 event.ignore()
305 elif keepkernel: #close console but leave kernel running (no prompt)
310 elif keepkernel: #close console but leave kernel running (no prompt)
306 if kernel_manager and kernel_manager.channels_running:
311 if kernel_manager and kernel_manager.channels_running:
307 if not self._existing:
312 if not self._existing:
308 # I have the kernel: don't quit, just close the window
313 # I have the kernel: don't quit, just close the window
309 self._app.setQuitOnLastWindowClosed(False)
314 self._app.setQuitOnLastWindowClosed(False)
310 event.accept()
315 event.accept()
311 else: #close console and kernel (no prompt)
316 else: #close console and kernel (no prompt)
312 if kernel_manager and kernel_manager.channels_running:
317 if kernel_manager and kernel_manager.channels_running:
313 kernel_manager.shutdown_kernel()
318 kernel_manager.shutdown_kernel()
314 event.accept()
319 event.accept()
315
320
316 #-----------------------------------------------------------------------------
321 #-----------------------------------------------------------------------------
317 # Aliases and Flags
322 # Aliases and Flags
318 #-----------------------------------------------------------------------------
323 #-----------------------------------------------------------------------------
319
324
320 flags = dict(ipkernel_flags)
325 flags = dict(ipkernel_flags)
321 qt_flags = {
326 qt_flags = {
322 'existing' : ({'IPythonQtConsoleApp' : {'existing' : 'kernel*.json'}},
327 'existing' : ({'IPythonQtConsoleApp' : {'existing' : 'kernel*.json'}},
323 "Connect to an existing kernel. If no argument specified, guess most recent"),
328 "Connect to an existing kernel. If no argument specified, guess most recent"),
324 'pure' : ({'IPythonQtConsoleApp' : {'pure' : True}},
329 'pure' : ({'IPythonQtConsoleApp' : {'pure' : True}},
325 "Use a pure Python kernel instead of an IPython kernel."),
330 "Use a pure Python kernel instead of an IPython kernel."),
326 'plain' : ({'ConsoleWidget' : {'kind' : 'plain'}},
331 'plain' : ({'ConsoleWidget' : {'kind' : 'plain'}},
327 "Disable rich text support."),
332 "Disable rich text support."),
328 }
333 }
329 qt_flags.update(boolean_flag(
334 qt_flags.update(boolean_flag(
330 'gui-completion', 'ConsoleWidget.gui_completion',
335 'gui-completion', 'ConsoleWidget.gui_completion',
331 "use a GUI widget for tab completion",
336 "use a GUI widget for tab completion",
332 "use plaintext output for completion"
337 "use plaintext output for completion"
333 ))
338 ))
334 qt_flags.update(boolean_flag(
339 qt_flags.update(boolean_flag(
335 'confirm-exit', 'IPythonQtConsoleApp.confirm_exit',
340 'confirm-exit', 'IPythonQtConsoleApp.confirm_exit',
336 """Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
341 """Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
337 to force a direct exit without any confirmation.
342 to force a direct exit without any confirmation.
338 """,
343 """,
339 """Don't prompt the user when exiting. This will terminate the kernel
344 """Don't prompt the user when exiting. This will terminate the kernel
340 if it is owned by the frontend, and leave it alive if it is external.
345 if it is owned by the frontend, and leave it alive if it is external.
341 """
346 """
342 ))
347 ))
343 flags.update(qt_flags)
348 flags.update(qt_flags)
344
349
345 aliases = dict(ipkernel_aliases)
350 aliases = dict(ipkernel_aliases)
346
351
347 qt_aliases = dict(
352 qt_aliases = dict(
348 hb = 'IPythonQtConsoleApp.hb_port',
353 hb = 'IPythonQtConsoleApp.hb_port',
349 shell = 'IPythonQtConsoleApp.shell_port',
354 shell = 'IPythonQtConsoleApp.shell_port',
350 iopub = 'IPythonQtConsoleApp.iopub_port',
355 iopub = 'IPythonQtConsoleApp.iopub_port',
351 stdin = 'IPythonQtConsoleApp.stdin_port',
356 stdin = 'IPythonQtConsoleApp.stdin_port',
352 ip = 'IPythonQtConsoleApp.ip',
357 ip = 'IPythonQtConsoleApp.ip',
353 existing = 'IPythonQtConsoleApp.existing',
358 existing = 'IPythonQtConsoleApp.existing',
354 f = 'IPythonQtConsoleApp.connection_file',
359 f = 'IPythonQtConsoleApp.connection_file',
355
360
356 style = 'IPythonWidget.syntax_style',
361 style = 'IPythonWidget.syntax_style',
357 stylesheet = 'IPythonQtConsoleApp.stylesheet',
362 stylesheet = 'IPythonQtConsoleApp.stylesheet',
358 colors = 'ZMQInteractiveShell.colors',
363 colors = 'ZMQInteractiveShell.colors',
359
364
360 editor = 'IPythonWidget.editor',
365 editor = 'IPythonWidget.editor',
361 paging = 'ConsoleWidget.paging',
366 paging = 'ConsoleWidget.paging',
362 ssh = 'IPythonQtConsoleApp.sshserver',
367 ssh = 'IPythonQtConsoleApp.sshserver',
363 )
368 )
364 aliases.update(qt_aliases)
369 aliases.update(qt_aliases)
365
370
366
371
367 #-----------------------------------------------------------------------------
372 #-----------------------------------------------------------------------------
368 # IPythonQtConsole
373 # IPythonQtConsole
369 #-----------------------------------------------------------------------------
374 #-----------------------------------------------------------------------------
370
375
371
376
372 class IPythonQtConsoleApp(BaseIPythonApplication):
377 class IPythonQtConsoleApp(BaseIPythonApplication):
373 name = 'ipython-qtconsole'
378 name = 'ipython-qtconsole'
374 default_config_file_name='ipython_config.py'
379 default_config_file_name='ipython_config.py'
375
380
376 description = """
381 description = """
377 The IPython QtConsole.
382 The IPython QtConsole.
378
383
379 This launches a Console-style application using Qt. It is not a full
384 This launches a Console-style application using Qt. It is not a full
380 console, in that launched terminal subprocesses will not be able to accept
385 console, in that launched terminal subprocesses will not be able to accept
381 input.
386 input.
382
387
383 The QtConsole supports various extra features beyond the Terminal IPython
388 The QtConsole supports various extra features beyond the Terminal IPython
384 shell, such as inline plotting with matplotlib, via:
389 shell, such as inline plotting with matplotlib, via:
385
390
386 ipython qtconsole --pylab=inline
391 ipython qtconsole --pylab=inline
387
392
388 as well as saving your session as HTML, and printing the output.
393 as well as saving your session as HTML, and printing the output.
389
394
390 """
395 """
391 examples = _examples
396 examples = _examples
392
397
393 classes = [IPKernelApp, IPythonWidget, ZMQInteractiveShell, ProfileDir, Session]
398 classes = [IPKernelApp, IPythonWidget, ZMQInteractiveShell, ProfileDir, Session]
394 flags = Dict(flags)
399 flags = Dict(flags)
395 aliases = Dict(aliases)
400 aliases = Dict(aliases)
396
401
397 kernel_argv = List(Unicode)
402 kernel_argv = List(Unicode)
398
403
399 # create requested profiles by default, if they don't exist:
404 # create requested profiles by default, if they don't exist:
400 auto_create = CBool(True)
405 auto_create = CBool(True)
401 # connection info:
406 # connection info:
402 ip = Unicode(LOCALHOST, config=True,
407 ip = Unicode(LOCALHOST, config=True,
403 help="""Set the kernel\'s IP address [default localhost].
408 help="""Set the kernel\'s IP address [default localhost].
404 If the IP address is something other than localhost, then
409 If the IP address is something other than localhost, then
405 Consoles on other machines will be able to connect
410 Consoles on other machines will be able to connect
406 to the Kernel, so be careful!"""
411 to the Kernel, so be careful!"""
407 )
412 )
408
413
409 sshserver = Unicode('', config=True,
414 sshserver = Unicode('', config=True,
410 help="""The SSH server to use to connect to the kernel.""")
415 help="""The SSH server to use to connect to the kernel.""")
411 sshkey = Unicode('', config=True,
416 sshkey = Unicode('', config=True,
412 help="""Path to the ssh key to use for logging in to the ssh server.""")
417 help="""Path to the ssh key to use for logging in to the ssh server.""")
413
418
414 hb_port = Int(0, config=True,
419 hb_port = Int(0, config=True,
415 help="set the heartbeat port [default: random]")
420 help="set the heartbeat port [default: random]")
416 shell_port = Int(0, config=True,
421 shell_port = Int(0, config=True,
417 help="set the shell (XREP) port [default: random]")
422 help="set the shell (XREP) port [default: random]")
418 iopub_port = Int(0, config=True,
423 iopub_port = Int(0, config=True,
419 help="set the iopub (PUB) port [default: random]")
424 help="set the iopub (PUB) port [default: random]")
420 stdin_port = Int(0, config=True,
425 stdin_port = Int(0, config=True,
421 help="set the stdin (XREQ) port [default: random]")
426 help="set the stdin (XREQ) port [default: random]")
422 connection_file = Unicode('', config=True,
427 connection_file = Unicode('', config=True,
423 help="""JSON file in which to store connection info [default: kernel-<pid>.json]
428 help="""JSON file in which to store connection info [default: kernel-<pid>.json]
424
429
425 This file will contain the IP, ports, and authentication key needed to connect
430 This file will contain the IP, ports, and authentication key needed to connect
426 clients to this kernel. By default, this file will be created in the security-dir
431 clients to this kernel. By default, this file will be created in the security-dir
427 of the current profile, but can be specified by absolute path.
432 of the current profile, but can be specified by absolute path.
428 """)
433 """)
429 def _connection_file_default(self):
434 def _connection_file_default(self):
430 return 'kernel-%i.json' % os.getpid()
435 return 'kernel-%i.json' % os.getpid()
431
436
432 existing = Unicode('', config=True,
437 existing = Unicode('', config=True,
433 help="""Connect to an already running kernel""")
438 help="""Connect to an already running kernel""")
434
439
435 stylesheet = Unicode('', config=True,
440 stylesheet = Unicode('', config=True,
436 help="path to a custom CSS stylesheet")
441 help="path to a custom CSS stylesheet")
437
442
438 pure = CBool(False, config=True,
443 pure = CBool(False, config=True,
439 help="Use a pure Python kernel instead of an IPython kernel.")
444 help="Use a pure Python kernel instead of an IPython kernel.")
440 plain = CBool(False, config=True,
445 plain = CBool(False, config=True,
441 help="Use a plaintext widget instead of rich text (plain can't print/save).")
446 help="Use a plaintext widget instead of rich text (plain can't print/save).")
442
447
443 def _pure_changed(self, name, old, new):
448 def _pure_changed(self, name, old, new):
444 kind = 'plain' if self.plain else 'rich'
449 kind = 'plain' if self.plain else 'rich'
445 self.config.ConsoleWidget.kind = kind
450 self.config.ConsoleWidget.kind = kind
446 if self.pure:
451 if self.pure:
447 self.widget_factory = FrontendWidget
452 self.widget_factory = FrontendWidget
448 elif self.plain:
453 elif self.plain:
449 self.widget_factory = IPythonWidget
454 self.widget_factory = IPythonWidget
450 else:
455 else:
451 self.widget_factory = RichIPythonWidget
456 self.widget_factory = RichIPythonWidget
452
457
453 _plain_changed = _pure_changed
458 _plain_changed = _pure_changed
454
459
455 confirm_exit = CBool(True, config=True,
460 confirm_exit = CBool(True, config=True,
456 help="""
461 help="""
457 Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
462 Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
458 to force a direct exit without any confirmation.""",
463 to force a direct exit without any confirmation.""",
459 )
464 )
460
465
461 # the factory for creating a widget
466 # the factory for creating a widget
462 widget_factory = Any(RichIPythonWidget)
467 widget_factory = Any(RichIPythonWidget)
463
468
464 def parse_command_line(self, argv=None):
469 def parse_command_line(self, argv=None):
465 super(IPythonQtConsoleApp, self).parse_command_line(argv)
470 super(IPythonQtConsoleApp, self).parse_command_line(argv)
466 if argv is None:
471 if argv is None:
467 argv = sys.argv[1:]
472 argv = sys.argv[1:]
468
473
469 self.kernel_argv = list(argv) # copy
474 self.kernel_argv = list(argv) # copy
470 # kernel should inherit default config file from frontend
475 # kernel should inherit default config file from frontend
471 self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name)
476 self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name)
472 # Scrub frontend-specific flags
477 # Scrub frontend-specific flags
473 for a in argv:
478 for a in argv:
474 if a.startswith('-') and a.lstrip('-') in qt_flags:
479 if a.startswith('-') and a.lstrip('-') in qt_flags:
475 self.kernel_argv.remove(a)
480 self.kernel_argv.remove(a)
476 swallow_next = False
481 swallow_next = False
477 for a in argv:
482 for a in argv:
478 if swallow_next:
483 if swallow_next:
479 self.kernel_argv.remove(a)
484 self.kernel_argv.remove(a)
480 swallow_next = False
485 swallow_next = False
481 continue
486 continue
482 if a.startswith('-'):
487 if a.startswith('-'):
483 split = a.lstrip('-').split('=')
488 split = a.lstrip('-').split('=')
484 alias = split[0]
489 alias = split[0]
485 if alias in qt_aliases:
490 if alias in qt_aliases:
486 self.kernel_argv.remove(a)
491 self.kernel_argv.remove(a)
487 if len(split) == 1:
492 if len(split) == 1:
488 # alias passed with arg via space
493 # alias passed with arg via space
489 swallow_next = True
494 swallow_next = True
490
495
491 def init_connection_file(self):
496 def init_connection_file(self):
492 """find the connection file, and load the info if found.
497 """find the connection file, and load the info if found.
493
498
494 The current working directory and the current profile's security
499 The current working directory and the current profile's security
495 directory will be searched for the file if it is not given by
500 directory will be searched for the file if it is not given by
496 absolute path.
501 absolute path.
497
502
498 When attempting to connect to an existing kernel and the `--existing`
503 When attempting to connect to an existing kernel and the `--existing`
499 argument does not match an existing file, it will be interpreted as a
504 argument does not match an existing file, it will be interpreted as a
500 fileglob, and the matching file in the current profile's security dir
505 fileglob, and the matching file in the current profile's security dir
501 with the latest access time will be used.
506 with the latest access time will be used.
502 """
507 """
503 if self.existing:
508 if self.existing:
504 try:
509 try:
505 cf = find_connection_file(self.existing)
510 cf = find_connection_file(self.existing)
506 except Exception:
511 except Exception:
507 self.log.critical("Could not find existing kernel connection file %s", self.existing)
512 self.log.critical("Could not find existing kernel connection file %s", self.existing)
508 self.exit(1)
513 self.exit(1)
509 self.log.info("Connecting to existing kernel: %s" % cf)
514 self.log.info("Connecting to existing kernel: %s" % cf)
510 self.connection_file = cf
515 self.connection_file = cf
511 # should load_connection_file only be used for existing?
516 # should load_connection_file only be used for existing?
512 # as it is now, this allows reusing ports if an existing
517 # as it is now, this allows reusing ports if an existing
513 # file is requested
518 # file is requested
514 try:
519 try:
515 self.load_connection_file()
520 self.load_connection_file()
516 except Exception:
521 except Exception:
517 self.log.error("Failed to load connection file: %r", self.connection_file, exc_info=True)
522 self.log.error("Failed to load connection file: %r", self.connection_file, exc_info=True)
518 self.exit(1)
523 self.exit(1)
519
524
520 def load_connection_file(self):
525 def load_connection_file(self):
521 """load ip/port/hmac config from JSON connection file"""
526 """load ip/port/hmac config from JSON connection file"""
522 # this is identical to KernelApp.load_connection_file
527 # this is identical to KernelApp.load_connection_file
523 # perhaps it can be centralized somewhere?
528 # perhaps it can be centralized somewhere?
524 try:
529 try:
525 fname = filefind(self.connection_file, ['.', self.profile_dir.security_dir])
530 fname = filefind(self.connection_file, ['.', self.profile_dir.security_dir])
526 except IOError:
531 except IOError:
527 self.log.debug("Connection File not found: %s", self.connection_file)
532 self.log.debug("Connection File not found: %s", self.connection_file)
528 return
533 return
529 self.log.debug(u"Loading connection file %s", fname)
534 self.log.debug(u"Loading connection file %s", fname)
530 with open(fname) as f:
535 with open(fname) as f:
531 s = f.read()
536 s = f.read()
532 cfg = json.loads(s)
537 cfg = json.loads(s)
533 if self.ip == LOCALHOST and 'ip' in cfg:
538 if self.ip == LOCALHOST and 'ip' in cfg:
534 # not overridden by config or cl_args
539 # not overridden by config or cl_args
535 self.ip = cfg['ip']
540 self.ip = cfg['ip']
536 for channel in ('hb', 'shell', 'iopub', 'stdin'):
541 for channel in ('hb', 'shell', 'iopub', 'stdin'):
537 name = channel + '_port'
542 name = channel + '_port'
538 if getattr(self, name) == 0 and name in cfg:
543 if getattr(self, name) == 0 and name in cfg:
539 # not overridden by config or cl_args
544 # not overridden by config or cl_args
540 setattr(self, name, cfg[name])
545 setattr(self, name, cfg[name])
541 if 'key' in cfg:
546 if 'key' in cfg:
542 self.config.Session.key = str_to_bytes(cfg['key'])
547 self.config.Session.key = str_to_bytes(cfg['key'])
543
548
544 def init_ssh(self):
549 def init_ssh(self):
545 """set up ssh tunnels, if needed."""
550 """set up ssh tunnels, if needed."""
546 if not self.sshserver and not self.sshkey:
551 if not self.sshserver and not self.sshkey:
547 return
552 return
548
553
549 if self.sshkey and not self.sshserver:
554 if self.sshkey and not self.sshserver:
550 # specifying just the key implies that we are connecting directly
555 # specifying just the key implies that we are connecting directly
551 self.sshserver = self.ip
556 self.sshserver = self.ip
552 self.ip = LOCALHOST
557 self.ip = LOCALHOST
553
558
554 # build connection dict for tunnels:
559 # build connection dict for tunnels:
555 info = dict(ip=self.ip,
560 info = dict(ip=self.ip,
556 shell_port=self.shell_port,
561 shell_port=self.shell_port,
557 iopub_port=self.iopub_port,
562 iopub_port=self.iopub_port,
558 stdin_port=self.stdin_port,
563 stdin_port=self.stdin_port,
559 hb_port=self.hb_port
564 hb_port=self.hb_port
560 )
565 )
561
566
562 self.log.info("Forwarding connections to %s via %s"%(self.ip, self.sshserver))
567 self.log.info("Forwarding connections to %s via %s"%(self.ip, self.sshserver))
563
568
564 # tunnels return a new set of ports, which will be on localhost:
569 # tunnels return a new set of ports, which will be on localhost:
565 self.ip = LOCALHOST
570 self.ip = LOCALHOST
566 try:
571 try:
567 newports = tunnel_to_kernel(info, self.sshserver, self.sshkey)
572 newports = tunnel_to_kernel(info, self.sshserver, self.sshkey)
568 except:
573 except:
569 # even catch KeyboardInterrupt
574 # even catch KeyboardInterrupt
570 self.log.error("Could not setup tunnels", exc_info=True)
575 self.log.error("Could not setup tunnels", exc_info=True)
571 self.exit(1)
576 self.exit(1)
572
577
573 self.shell_port, self.iopub_port, self.stdin_port, self.hb_port = newports
578 self.shell_port, self.iopub_port, self.stdin_port, self.hb_port = newports
574
579
575 cf = self.connection_file
580 cf = self.connection_file
576 base,ext = os.path.splitext(cf)
581 base,ext = os.path.splitext(cf)
577 base = os.path.basename(base)
582 base = os.path.basename(base)
578 self.connection_file = os.path.basename(base)+'-ssh'+ext
583 self.connection_file = os.path.basename(base)+'-ssh'+ext
579 self.log.critical("To connect another client via this tunnel, use:")
584 self.log.critical("To connect another client via this tunnel, use:")
580 self.log.critical("--existing %s" % self.connection_file)
585 self.log.critical("--existing %s" % self.connection_file)
581
586
582 def init_kernel_manager(self):
587 def init_kernel_manager(self):
583 # Don't let Qt or ZMQ swallow KeyboardInterupts.
588 # Don't let Qt or ZMQ swallow KeyboardInterupts.
584 signal.signal(signal.SIGINT, signal.SIG_DFL)
589 signal.signal(signal.SIGINT, signal.SIG_DFL)
585 sec = self.profile_dir.security_dir
590 sec = self.profile_dir.security_dir
586 try:
591 try:
587 cf = filefind(self.connection_file, ['.', sec])
592 cf = filefind(self.connection_file, ['.', sec])
588 except IOError:
593 except IOError:
589 # file might not exist
594 # file might not exist
590 if self.connection_file == os.path.basename(self.connection_file):
595 if self.connection_file == os.path.basename(self.connection_file):
591 # just shortname, put it in security dir
596 # just shortname, put it in security dir
592 cf = os.path.join(sec, self.connection_file)
597 cf = os.path.join(sec, self.connection_file)
593 else:
598 else:
594 cf = self.connection_file
599 cf = self.connection_file
595
600
596 # Create a KernelManager and start a kernel.
601 # Create a KernelManager and start a kernel.
597 self.kernel_manager = QtKernelManager(
602 self.kernel_manager = QtKernelManager(
598 ip=self.ip,
603 ip=self.ip,
599 shell_port=self.shell_port,
604 shell_port=self.shell_port,
600 iopub_port=self.iopub_port,
605 iopub_port=self.iopub_port,
601 stdin_port=self.stdin_port,
606 stdin_port=self.stdin_port,
602 hb_port=self.hb_port,
607 hb_port=self.hb_port,
603 connection_file=cf,
608 connection_file=cf,
604 config=self.config,
609 config=self.config,
605 )
610 )
606 # start the kernel
611 # start the kernel
607 if not self.existing:
612 if not self.existing:
608 kwargs = dict(ipython=not self.pure)
613 kwargs = dict(ipython=not self.pure)
609 kwargs['extra_arguments'] = self.kernel_argv
614 kwargs['extra_arguments'] = self.kernel_argv
610 self.kernel_manager.start_kernel(**kwargs)
615 self.kernel_manager.start_kernel(**kwargs)
611 elif self.sshserver:
616 elif self.sshserver:
612 # ssh, write new connection file
617 # ssh, write new connection file
613 self.kernel_manager.write_connection_file()
618 self.kernel_manager.write_connection_file()
614 self.kernel_manager.start_channels()
619 self.kernel_manager.start_channels()
615
620
616
621
617 def init_qt_elements(self):
622 def init_qt_elements(self):
618 # Create the widget.
623 # Create the widget.
619 self.app = QtGui.QApplication([])
624 self.app = QtGui.QApplication([])
620 pixmap=QtGui.QPixmap(':/icon/IPythonConsole.png')
625 pixmap=QtGui.QPixmap(':/icon/IPythonConsole.png')
621 icon=QtGui.QIcon(pixmap)
626 icon=QtGui.QIcon(pixmap)
622 QtGui.QApplication.setWindowIcon(icon)
627 QtGui.QApplication.setWindowIcon(icon)
623
628
624 local_kernel = (not self.existing) or self.ip in LOCAL_IPS
629 local_kernel = (not self.existing) or self.ip in LOCAL_IPS
625 self.widget = self.widget_factory(config=self.config,
630 self.widget = self.widget_factory(config=self.config,
626 local_kernel=local_kernel)
631 local_kernel=local_kernel)
627 self.widget.kernel_manager = self.kernel_manager
632 self.widget.kernel_manager = self.kernel_manager
628 self.window = MainWindow(self.app, self.widget, self.existing,
633 self.window = MainWindow(self.app, self.widget, self.existing,
629 may_close=local_kernel,
634 may_close=local_kernel,
630 confirm_exit=self.confirm_exit)
635 confirm_exit=self.confirm_exit)
631 self.window.initMenuBar()
636 self.window.initMenuBar()
632 self.window.setWindowTitle('Python' if self.pure else 'IPython')
637 self.window.setWindowTitle('Python' if self.pure else 'IPython')
633
638
634 def init_colors(self):
639 def init_colors(self):
635 """Configure the coloring of the widget"""
640 """Configure the coloring of the widget"""
636 # Note: This will be dramatically simplified when colors
641 # Note: This will be dramatically simplified when colors
637 # are removed from the backend.
642 # are removed from the backend.
638
643
639 if self.pure:
644 if self.pure:
640 # only IPythonWidget supports styling
645 # only IPythonWidget supports styling
641 return
646 return
642
647
643 # parse the colors arg down to current known labels
648 # parse the colors arg down to current known labels
644 try:
649 try:
645 colors = self.config.ZMQInteractiveShell.colors
650 colors = self.config.ZMQInteractiveShell.colors
646 except AttributeError:
651 except AttributeError:
647 colors = None
652 colors = None
648 try:
653 try:
649 style = self.config.IPythonWidget.colors
654 style = self.config.IPythonWidget.colors
650 except AttributeError:
655 except AttributeError:
651 style = None
656 style = None
652
657
653 # find the value for colors:
658 # find the value for colors:
654 if colors:
659 if colors:
655 colors=colors.lower()
660 colors=colors.lower()
656 if colors in ('lightbg', 'light'):
661 if colors in ('lightbg', 'light'):
657 colors='lightbg'
662 colors='lightbg'
658 elif colors in ('dark', 'linux'):
663 elif colors in ('dark', 'linux'):
659 colors='linux'
664 colors='linux'
660 else:
665 else:
661 colors='nocolor'
666 colors='nocolor'
662 elif style:
667 elif style:
663 if style=='bw':
668 if style=='bw':
664 colors='nocolor'
669 colors='nocolor'
665 elif styles.dark_style(style):
670 elif styles.dark_style(style):
666 colors='linux'
671 colors='linux'
667 else:
672 else:
668 colors='lightbg'
673 colors='lightbg'
669 else:
674 else:
670 colors=None
675 colors=None
671
676
672 # Configure the style.
677 # Configure the style.
673 widget = self.widget
678 widget = self.widget
674 if style:
679 if style:
675 widget.style_sheet = styles.sheet_from_template(style, colors)
680 widget.style_sheet = styles.sheet_from_template(style, colors)
676 widget.syntax_style = style
681 widget.syntax_style = style
677 widget._syntax_style_changed()
682 widget._syntax_style_changed()
678 widget._style_sheet_changed()
683 widget._style_sheet_changed()
679 elif colors:
684 elif colors:
680 # use a default style
685 # use a default style
681 widget.set_default_style(colors=colors)
686 widget.set_default_style(colors=colors)
682 else:
687 else:
683 # this is redundant for now, but allows the widget's
688 # this is redundant for now, but allows the widget's
684 # defaults to change
689 # defaults to change
685 widget.set_default_style()
690 widget.set_default_style()
686
691
687 if self.stylesheet:
692 if self.stylesheet:
688 # we got an expicit stylesheet
693 # we got an expicit stylesheet
689 if os.path.isfile(self.stylesheet):
694 if os.path.isfile(self.stylesheet):
690 with open(self.stylesheet) as f:
695 with open(self.stylesheet) as f:
691 sheet = f.read()
696 sheet = f.read()
692 widget.style_sheet = sheet
697 widget.style_sheet = sheet
693 widget._style_sheet_changed()
698 widget._style_sheet_changed()
694 else:
699 else:
695 raise IOError("Stylesheet %r not found."%self.stylesheet)
700 raise IOError("Stylesheet %r not found."%self.stylesheet)
696
701
697 def initialize(self, argv=None):
702 def initialize(self, argv=None):
698 super(IPythonQtConsoleApp, self).initialize(argv)
703 super(IPythonQtConsoleApp, self).initialize(argv)
699 self.init_connection_file()
704 self.init_connection_file()
700 default_secure(self.config)
705 default_secure(self.config)
701 self.init_ssh()
706 self.init_ssh()
702 self.init_kernel_manager()
707 self.init_kernel_manager()
703 self.init_qt_elements()
708 self.init_qt_elements()
704 self.init_colors()
709 self.init_colors()
705 self.init_window_shortcut()
710 self.init_window_shortcut()
706
711
707 def init_window_shortcut(self):
712 def init_window_shortcut(self):
708
713
709 self.fullScreenAct = QtGui.QAction("Full Screen",
714 self.fullScreenAct = QtGui.QAction("Full Screen",
710 self.window,
715 self.window,
711 shortcut="Ctrl+Meta+Space",
716 shortcut="Ctrl+Meta+Space",
712 statusTip="Toggle between Fullscreen and Normal Size",
717 statusTip="Toggle between Fullscreen and Normal Size",
713 triggered=self.toggleFullScreen)
718 triggered=self.toggleFullScreen)
714
719
715
720
716 # creating shortcut in menubar only for Mac OS as I don't
721 # creating shortcut in menubar only for Mac OS as I don't
717 # know the shortcut or if the windows manager assign it in
722 # know the shortcut or if the windows manager assign it in
718 # other platform.
723 # other platform.
719 if sys.platform == 'darwin':
724 if sys.platform == 'darwin':
720 self.minimizeAct = QtGui.QAction("Minimize",
725 self.minimizeAct = QtGui.QAction("Minimize",
721 self.window,
726 self.window,
722 shortcut="Ctrl+m",
727 shortcut="Ctrl+m",
723 statusTip="Minimize the window/Restore Normal Size",
728 statusTip="Minimize the window/Restore Normal Size",
724 triggered=self.toggleMinimized)
729 triggered=self.toggleMinimized)
725 self.maximizeAct = QtGui.QAction("Maximize",
730 self.maximizeAct = QtGui.QAction("Maximize",
726 self.window,
731 self.window,
727 shortcut="Ctrl+Shift+M",
732 shortcut="Ctrl+Shift+M",
728 statusTip="Maximize the window/Restore Normal Size",
733 statusTip="Maximize the window/Restore Normal Size",
729 triggered=self.toggleMaximized)
734 triggered=self.toggleMaximized)
730
735
731 self.onlineHelpAct = QtGui.QAction("Open Online Help",
736 self.onlineHelpAct = QtGui.QAction("Open Online Help",
732 self.window,
737 self.window,
733 triggered=self._open_online_help)
738 triggered=self._open_online_help)
734
739
735 self.windowMenu = self.window.windowMenu
740 self.windowMenu = self.window.windowMenu
736 self.windowMenu.addAction(self.minimizeAct)
741 self.windowMenu.addAction(self.minimizeAct)
737 self.windowMenu.addAction(self.maximizeAct)
742 self.windowMenu.addAction(self.maximizeAct)
738 self.windowMenu.addSeparator()
743 self.windowMenu.addSeparator()
739 self.windowMenu.addAction(self.fullScreenAct)
744 self.windowMenu.addAction(self.fullScreenAct)
740
745
741 self.window.helpMenu.addAction(self.onlineHelpAct)
746 self.window.helpMenu.addAction(self.onlineHelpAct)
742 else:
747 else:
743 # if we don't put it in a menu, we add it to the window so
748 # if we don't put it in a menu, we add it to the window so
744 # that it can still be triggerd by shortcut
749 # that it can still be triggerd by shortcut
745 self.window.addAction(self.fullScreenAct)
750 self.window.addAction(self.fullScreenAct)
746
751
747 def toggleMinimized(self):
752 def toggleMinimized(self):
748 if not self.window.isMinimized():
753 if not self.window.isMinimized():
749 self.window.showMinimized()
754 self.window.showMinimized()
750 else:
755 else:
751 self.window.showNormal()
756 self.window.showNormal()
752
757
753 def _open_online_help(self):
758 def _open_online_help(self):
754 QtGui.QDesktopServices.openUrl(
759 QtGui.QDesktopServices.openUrl(
755 QtCore.QUrl("http://ipython.org/documentation.html",
760 QtCore.QUrl("http://ipython.org/documentation.html",
756 QtCore.QUrl.TolerantMode)
761 QtCore.QUrl.TolerantMode)
757 )
762 )
758
763
759 def toggleMaximized(self):
764 def toggleMaximized(self):
760 if not self.window.isMaximized():
765 if not self.window.isMaximized():
761 self.window.showMaximized()
766 self.window.showMaximized()
762 else:
767 else:
763 self.window.showNormal()
768 self.window.showNormal()
764
769
765 # Min/Max imizing while in full screen give a bug
770 # Min/Max imizing while in full screen give a bug
766 # when going out of full screen, at least on OSX
771 # when going out of full screen, at least on OSX
767 def toggleFullScreen(self):
772 def toggleFullScreen(self):
768 if not self.window.isFullScreen():
773 if not self.window.isFullScreen():
769 self.window.showFullScreen()
774 self.window.showFullScreen()
770 if sys.platform == 'darwin':
775 if sys.platform == 'darwin':
771 self.maximizeAct.setEnabled(False)
776 self.maximizeAct.setEnabled(False)
772 self.minimizeAct.setEnabled(False)
777 self.minimizeAct.setEnabled(False)
773 else:
778 else:
774 self.window.showNormal()
779 self.window.showNormal()
775 if sys.platform == 'darwin':
780 if sys.platform == 'darwin':
776 self.maximizeAct.setEnabled(True)
781 self.maximizeAct.setEnabled(True)
777 self.minimizeAct.setEnabled(True)
782 self.minimizeAct.setEnabled(True)
778
783
779 def start(self):
784 def start(self):
780
785
781 # draw the window
786 # draw the window
782 self.window.show()
787 self.window.show()
783
788
784 # Start the application main loop.
789 # Start the application main loop.
785 self.app.exec_()
790 self.app.exec_()
786
791
787 #-----------------------------------------------------------------------------
792 #-----------------------------------------------------------------------------
788 # Main entry point
793 # Main entry point
789 #-----------------------------------------------------------------------------
794 #-----------------------------------------------------------------------------
790
795
791 def main():
796 def main():
792 app = IPythonQtConsoleApp()
797 app = IPythonQtConsoleApp()
793 app.initialize()
798 app.initialize()
794 app.start()
799 app.start()
795
800
796
801
797 if __name__ == '__main__':
802 if __name__ == '__main__':
798 main()
803 main()
General Comments 0
You need to be logged in to leave comments. Login now