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