##// END OF EJS Templates
Made IPythonWidget and its subclasses Configurable.
epatters -
Show More
@@ -7,6 +7,9 b' from textwrap import dedent'
7 from PyQt4 import QtCore, QtGui
7 from PyQt4 import QtCore, QtGui
8
8
9 # Local imports
9 # Local imports
10 from IPython.config.configurable import Configurable
11 from IPython.frontend.qt.util import MetaQObjectHasTraits
12 from IPython.utils.traitlets import Bool, Enum, Int
10 from ansi_code_processor import QtAnsiCodeProcessor
13 from ansi_code_processor import QtAnsiCodeProcessor
11 from completion_widget import CompletionWidget
14 from completion_widget import CompletionWidget
12
15
@@ -32,7 +35,7 b' class ConsoleTextEdit(QtGui.QTextEdit):'
32 def dropEvent(self, event): pass
35 def dropEvent(self, event): pass
33
36
34
37
35 class ConsoleWidget(QtGui.QWidget):
38 class ConsoleWidget(Configurable, QtGui.QWidget):
36 """ An abstract base class for console-type widgets. This class has
39 """ An abstract base class for console-type widgets. This class has
37 functionality for:
40 functionality for:
38
41
@@ -45,20 +48,40 b' class ConsoleWidget(QtGui.QWidget):'
45 ConsoleWidget also provides a number of utility methods that will be
48 ConsoleWidget also provides a number of utility methods that will be
46 convenient to implementors of a console-style widget.
49 convenient to implementors of a console-style widget.
47 """
50 """
51 __metaclass__ = MetaQObjectHasTraits
48
52
49 # Whether to process ANSI escape codes.
53 # Whether to process ANSI escape codes.
50 ansi_codes = True
54 ansi_codes = Bool(True, config=True)
51
55
52 # The maximum number of lines of text before truncation.
56 # The maximum number of lines of text before truncation. Specifying a
53 buffer_size = 500
57 # non-positive number disables text truncation (not recommended).
58 buffer_size = Int(500, config=True)
54
59
55 # Whether to use a list widget or plain text output for tab completion.
60 # Whether to use a list widget or plain text output for tab completion.
56 gui_completion = True
61 gui_completion = Bool(True, config=True)
62
63 # The type of underlying text widget to use. Valid values are 'plain', which
64 # specifies a QPlainTextEdit, and 'rich', which specifies a QTextEdit.
65 # NOTE: this value can only be specified during initialization.
66 kind = Enum(['plain', 'rich'], default_value='plain', config=True)
67
68 # The type of paging to use. Valid values are:
69 # 'inside' : The widget pages like a traditional terminal pager.
70 # 'hsplit' : When paging is requested, the widget is split
71 # horizontally. The top pane contains the console, and the
72 # bottom pane contains the paged text.
73 # 'vsplit' : Similar to 'hsplit', except that a vertical splitter used.
74 # 'custom' : No action is taken by the widget beyond emitting a
75 # 'custom_page_requested(str)' signal.
76 # 'none' : The text is written directly to the console.
77 # NOTE: this value can only be specified during initialization.
78 paging = Enum(['inside', 'hsplit', 'vsplit', 'custom', 'none'],
79 default_value='inside', config=True)
57
80
58 # Whether to override ShortcutEvents for the keybindings defined by this
81 # Whether to override ShortcutEvents for the keybindings defined by this
59 # widget (Ctrl+n, Ctrl+a, etc). Enable this if you want this widget to take
82 # widget (Ctrl+n, Ctrl+a, etc). Enable this if you want this widget to take
60 # priority (when it has focus) over, e.g., window-level menu shortcuts.
83 # priority (when it has focus) over, e.g., window-level menu shortcuts.
61 override_shortcuts = False
84 override_shortcuts = Bool(False)
62
85
63 # Signals that indicate ConsoleWidget state.
86 # Signals that indicate ConsoleWidget state.
64 copy_available = QtCore.pyqtSignal(bool)
87 copy_available = QtCore.pyqtSignal(bool)
@@ -84,42 +107,26 b' class ConsoleWidget(QtGui.QWidget):'
84 # 'QObject' interface
107 # 'QObject' interface
85 #---------------------------------------------------------------------------
108 #---------------------------------------------------------------------------
86
109
87 def __init__(self, kind='plain', paging='inside', parent=None):
110 def __init__(self, parent=None, **kw):
88 """ Create a ConsoleWidget.
111 """ Create a ConsoleWidget.
89
90 Parameters
91 ----------
92 kind : str, optional [default 'plain']
93 The type of underlying text widget to use. Valid values are 'plain',
94 which specifies a QPlainTextEdit, and 'rich', which specifies a
95 QTextEdit.
96
97 paging : str, optional [default 'inside']
98 The type of paging to use. Valid values are:
99 'inside' : The widget pages like a traditional terminal pager.
100 'hsplit' : When paging is requested, the widget is split
101 horizontally. The top pane contains the console,
102 and the bottom pane contains the paged text.
103 'vsplit' : Similar to 'hsplit', except that a vertical splitter
104 used.
105 'custom' : No action is taken by the widget beyond emitting a
106 'custom_page_requested(str)' signal.
107 'none' : The text is written directly to the console.
108
112
113 Parameters:
114 -----------
109 parent : QWidget, optional [default None]
115 parent : QWidget, optional [default None]
110 The parent for this widget.
116 The parent for this widget.
111 """
117 """
112 super(ConsoleWidget, self).__init__(parent)
118 QtGui.QWidget.__init__(self, parent)
119 Configurable.__init__(self, **kw)
113
120
114 # Create the layout and underlying text widget.
121 # Create the layout and underlying text widget.
115 layout = QtGui.QStackedLayout(self)
122 layout = QtGui.QStackedLayout(self)
116 layout.setContentsMargins(0, 0, 0, 0)
123 layout.setContentsMargins(0, 0, 0, 0)
117 self._control = self._create_control(kind)
124 self._control = self._create_control()
118 self._page_control = None
125 self._page_control = None
119 self._splitter = None
126 self._splitter = None
120 if paging in ('hsplit', 'vsplit'):
127 if self.paging in ('hsplit', 'vsplit'):
121 self._splitter = QtGui.QSplitter()
128 self._splitter = QtGui.QSplitter()
122 if paging == 'hsplit':
129 if self.paging == 'hsplit':
123 self._splitter.setOrientation(QtCore.Qt.Horizontal)
130 self._splitter.setOrientation(QtCore.Qt.Horizontal)
124 else:
131 else:
125 self._splitter.setOrientation(QtCore.Qt.Vertical)
132 self._splitter.setOrientation(QtCore.Qt.Vertical)
@@ -129,16 +136,13 b' class ConsoleWidget(QtGui.QWidget):'
129 layout.addWidget(self._control)
136 layout.addWidget(self._control)
130
137
131 # Create the paging widget, if necessary.
138 # Create the paging widget, if necessary.
132 self._page_style = paging
139 if self.paging in ('inside', 'hsplit', 'vsplit'):
133 if paging in ('inside', 'hsplit', 'vsplit'):
134 self._page_control = self._create_page_control()
140 self._page_control = self._create_page_control()
135 if self._splitter:
141 if self._splitter:
136 self._page_control.hide()
142 self._page_control.hide()
137 self._splitter.addWidget(self._page_control)
143 self._splitter.addWidget(self._page_control)
138 else:
144 else:
139 layout.addWidget(self._page_control)
145 layout.addWidget(self._page_control)
140 elif paging not in ('custom', 'none'):
141 raise ValueError('Paging style %s unknown.' % repr(paging))
142
146
143 # Initialize protected variables. Some variables contain useful state
147 # Initialize protected variables. Some variables contain useful state
144 # information for subclasses; they should be considered read-only.
148 # information for subclasses; they should be considered read-only.
@@ -210,11 +214,11 b' class ConsoleWidget(QtGui.QWidget):'
210 # factor of one character here.
214 # factor of one character here.
211 width = font_metrics.maxWidth() * 81 + margin
215 width = font_metrics.maxWidth() * 81 + margin
212 width += style.pixelMetric(QtGui.QStyle.PM_ScrollBarExtent)
216 width += style.pixelMetric(QtGui.QStyle.PM_ScrollBarExtent)
213 if self._page_style == 'hsplit':
217 if self.paging == 'hsplit':
214 width = width * 2 + splitwidth
218 width = width * 2 + splitwidth
215
219
216 height = font_metrics.height() * 25 + margin
220 height = font_metrics.height() * 25 + margin
217 if self._page_style == 'vsplit':
221 if self.paging == 'vsplit':
218 height = height * 2 + splitwidth
222 height = height * 2 + splitwidth
219
223
220 return QtCore.QSize(width, height)
224 return QtCore.QSize(width, height)
@@ -577,16 +581,14 b' class ConsoleWidget(QtGui.QWidget):'
577
581
578 return down
582 return down
579
583
580 def _create_control(self, kind):
584 def _create_control(self):
581 """ Creates and connects the underlying text widget.
585 """ Creates and connects the underlying text widget.
582 """
586 """
583 if kind == 'plain':
587 if self.kind == 'plain':
584 control = ConsolePlainTextEdit()
588 control = ConsolePlainTextEdit()
585 elif kind == 'rich':
589 elif self.kind == 'rich':
586 control = ConsoleTextEdit()
590 control = ConsoleTextEdit()
587 control.setAcceptRichText(False)
591 control.setAcceptRichText(False)
588 else:
589 raise ValueError("Kind %s unknown." % repr(kind))
590 control.installEventFilter(self)
592 control.installEventFilter(self)
591 control.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
593 control.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
592 control.customContextMenuRequested.connect(self._show_context_menu)
594 control.customContextMenuRequested.connect(self._show_context_menu)
@@ -1072,13 +1074,13 b' class ConsoleWidget(QtGui.QWidget):'
1072 """ Displays text using the pager if it exceeds the height of the
1074 """ Displays text using the pager if it exceeds the height of the
1073 visible area.
1075 visible area.
1074 """
1076 """
1075 if self._page_style == 'none':
1077 if self.paging == 'none':
1076 self._append_plain_text(text)
1078 self._append_plain_text(text)
1077 else:
1079 else:
1078 line_height = QtGui.QFontMetrics(self.font).height()
1080 line_height = QtGui.QFontMetrics(self.font).height()
1079 minlines = self._control.viewport().height() / line_height
1081 minlines = self._control.viewport().height() / line_height
1080 if re.match("(?:[^\n]*\n){%i}" % minlines, text):
1082 if re.match("(?:[^\n]*\n){%i}" % minlines, text):
1081 if self._page_style == 'custom':
1083 if self.paging == 'custom':
1082 self.custom_page_requested.emit(text)
1084 self.custom_page_requested.emit(text)
1083 else:
1085 else:
1084 self._page_control.clear()
1086 self._page_control.clear()
@@ -9,6 +9,7 b' from PyQt4 import QtCore, QtGui'
9 # Local imports
9 # Local imports
10 from IPython.core.inputsplitter import InputSplitter
10 from IPython.core.inputsplitter import InputSplitter
11 from IPython.frontend.qt.base_frontend_mixin import BaseFrontendMixin
11 from IPython.frontend.qt.base_frontend_mixin import BaseFrontendMixin
12 from IPython.utils.traitlets import Bool, Type
12 from call_tip_widget import CallTipWidget
13 from call_tip_widget import CallTipWidget
13 from completion_lexer import CompletionLexer
14 from completion_lexer import CompletionLexer
14 from console_widget import HistoryConsoleWidget
15 from console_widget import HistoryConsoleWidget
@@ -74,12 +75,12 b' class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):'
74
75
75 # An option and corresponding signal for overriding the default kernel
76 # An option and corresponding signal for overriding the default kernel
76 # interrupt behavior.
77 # interrupt behavior.
77 custom_interrupt = False
78 custom_interrupt = Bool(False)
78 custom_interrupt_requested = QtCore.pyqtSignal()
79 custom_interrupt_requested = QtCore.pyqtSignal()
79
80
80 # An option and corresponding signal for overriding the default kernel
81 # An option and corresponding signal for overriding the default kernel
81 # restart behavior.
82 # restart behavior.
82 custom_restart = False
83 custom_restart = Bool(False)
83 custom_restart_requested = QtCore.pyqtSignal()
84 custom_restart_requested = QtCore.pyqtSignal()
84
85
85 # Emitted when an 'execute_reply' has been received from the kernel and
86 # Emitted when an 'execute_reply' has been received from the kernel and
@@ -87,8 +88,8 b' class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):'
87 executed = QtCore.pyqtSignal(object)
88 executed = QtCore.pyqtSignal(object)
88
89
89 # Protected class variables.
90 # Protected class variables.
90 _highlighter_class = FrontendHighlighter
91 _highlighter_class = Type(FrontendHighlighter)
91 _input_splitter_class = InputSplitter
92 _input_splitter_class = Type(InputSplitter)
92
93
93 #---------------------------------------------------------------------------
94 #---------------------------------------------------------------------------
94 # 'object' interface
95 # 'object' interface
@@ -16,49 +16,76 b' from PyQt4 import QtCore, QtGui'
16 # Local imports
16 # Local imports
17 from IPython.core.inputsplitter import IPythonInputSplitter
17 from IPython.core.inputsplitter import IPythonInputSplitter
18 from IPython.core.usage import default_banner
18 from IPython.core.usage import default_banner
19 from IPython.utils.traitlets import Bool, Str
19 from frontend_widget import FrontendWidget
20 from frontend_widget import FrontendWidget
20
21
22 # The default style sheet: black text on a white background.
23 default_style_sheet = '''
24 .error { color: red; }
25 .in-prompt { color: navy; }
26 .in-prompt-number { font-weight: bold; }
27 .out-prompt { color: darkred; }
28 .out-prompt-number { font-weight: bold; }
29 '''
30 default_syntax_style = 'default'
31
32 # A dark style sheet: white text on a black background.
33 dark_style_sheet = '''
34 QPlainTextEdit, QTextEdit { background-color: black; color: white }
35 QFrame { border: 1px solid grey; }
36 .error { color: red; }
37 .in-prompt { color: lime; }
38 .in-prompt-number { color: lime; font-weight: bold; }
39 .out-prompt { color: red; }
40 .out-prompt-number { color: red; font-weight: bold; }
41 '''
42 dark_syntax_style = 'monokai'
43
44 # Default prompts.
45 default_in_prompt = 'In [<span class="in-prompt-number">%i</span>]: '
46 default_out_prompt = 'Out[<span class="out-prompt-number">%i</span>]: '
47
21
48
22 class IPythonWidget(FrontendWidget):
49 class IPythonWidget(FrontendWidget):
23 """ A FrontendWidget for an IPython kernel.
50 """ A FrontendWidget for an IPython kernel.
24 """
51 """
25
52
26 # Signal emitted when an editor is needed for a file and the editor has been
53 # If set, the 'custom_edit_requested(str, int)' signal will be emitted when
27 # specified as 'custom'. See 'set_editor' for more information.
54 # an editor is needed for a file. This overrides 'editor' and 'editor_line'
55 # settings.
56 custom_edit = Bool(False)
28 custom_edit_requested = QtCore.pyqtSignal(object, object)
57 custom_edit_requested = QtCore.pyqtSignal(object, object)
29
58
30 # The default stylesheet: black text on a white background.
59 # A command for invoking a system text editor. If the string contains a
31 default_stylesheet = """
60 # {filename} format specifier, it will be used. Otherwise, the filename will
32 .error { color: red; }
61 # be appended to the end the command.
33 .in-prompt { color: navy; }
62 editor = Str('default', config=True)
34 .in-prompt-number { font-weight: bold; }
63
35 .out-prompt { color: darkred; }
64 # The editor command to use when a specific line number is requested. The
36 .out-prompt-number { font-weight: bold; }
65 # string should contain two format specifiers: {line} and {filename}. If
37 """
66 # this parameter is not specified, the line number option to the %edit magic
38
67 # will be ignored.
39 # A dark stylesheet: white text on a black background.
68 editor_line = Str(config=True)
40 dark_stylesheet = """
69
41 QPlainTextEdit, QTextEdit { background-color: black; color: white }
70 # A CSS stylesheet. The stylesheet can contain classes for:
42 QFrame { border: 1px solid grey; }
71 # 1. Qt: QPlainTextEdit, QFrame, QWidget, etc
43 .error { color: red; }
72 # 2. Pygments: .c, .k, .o, etc (see PygmentsHighlighter)
44 .in-prompt { color: lime; }
73 # 3. IPython: .error, .in-prompt, .out-prompt, etc
45 .in-prompt-number { color: lime; font-weight: bold; }
74 style_sheet = Str(default_style_sheet, config=True)
46 .out-prompt { color: red; }
75
47 .out-prompt-number { color: red; font-weight: bold; }
76 # If not empty, use this Pygments style for syntax highlighting. Otherwise,
48 """
77 # the style sheet is queried for Pygments style information.
49
78 syntax_style = Str(default_syntax_style, config=True)
50 # Default prompts.
51 in_prompt = 'In [<span class="in-prompt-number">%i</span>]: '
52 out_prompt = 'Out[<span class="out-prompt-number">%i</span>]: '
53
79
54 # A type used internally by IPythonWidget for storing prompt information.
80 # Prompts.
55 _PromptBlock = namedtuple('_PromptBlock',
81 in_prompt = Str(default_in_prompt, config=True)
56 ['block', 'length', 'number'])
82 out_prompt = Str(default_out_prompt, config=True)
57
83
58 # FrontendWidget protected class variables.
84 # FrontendWidget protected class variables.
59 _input_splitter_class = IPythonInputSplitter
85 _input_splitter_class = IPythonInputSplitter
60
86
61 # IPythonWidget protected class variables.
87 # IPythonWidget protected class variables.
88 _PromptBlock = namedtuple('_PromptBlock', ['block', 'length', 'number'])
62 _payload_source_edit = 'IPython.zmq.zmqshell.ZMQInteractiveShell.edit_magic'
89 _payload_source_edit = 'IPython.zmq.zmqshell.ZMQInteractiveShell.edit_magic'
63 _payload_source_page = 'IPython.zmq.page.page'
90 _payload_source_page = 'IPython.zmq.page.page'
64
91
@@ -72,9 +99,9 b' class IPythonWidget(FrontendWidget):'
72 # IPythonWidget protected variables.
99 # IPythonWidget protected variables.
73 self._previous_prompt_obj = None
100 self._previous_prompt_obj = None
74
101
75 # Set a default editor and stylesheet.
102 # Initialize widget styling.
76 self.set_editor('default')
103 self._style_sheet_changed()
77 self.reset_styling()
104 self._syntax_style_changed()
78
105
79 #---------------------------------------------------------------------------
106 #---------------------------------------------------------------------------
80 # 'BaseFrontendMixin' abstract interface
107 # 'BaseFrontendMixin' abstract interface
@@ -248,67 +275,6 b' class IPythonWidget(FrontendWidget):'
248 next_prompt['input_sep'])
275 next_prompt['input_sep'])
249
276
250 #---------------------------------------------------------------------------
277 #---------------------------------------------------------------------------
251 # 'IPythonWidget' interface
252 #---------------------------------------------------------------------------
253
254 def reset_styling(self):
255 """ Restores the default IPythonWidget styling.
256 """
257 self.set_styling(self.default_stylesheet, syntax_style='default')
258 #self.set_styling(self.dark_stylesheet, syntax_style='monokai')
259
260 def set_editor(self, editor, line_editor=None):
261 """ Sets the editor to use with the %edit magic.
262
263 Parameters:
264 -----------
265 editor : str
266 A command for invoking a system text editor. If the string contains
267 a {filename} format specifier, it will be used. Otherwise, the
268 filename will be appended to the end the command.
269
270 This parameter also takes a special value:
271 'custom' : Emit a 'custom_edit_requested(str, int)' signal
272 instead of opening an editor.
273
274 line_editor : str, optional
275 The editor command to use when a specific line number is
276 requested. The string should contain two format specifiers: {line}
277 and {filename}. If this parameter is not specified, the line number
278 option to the %edit magic will be ignored.
279 """
280 self._editor = editor
281 self._editor_line = line_editor
282
283 def set_styling(self, stylesheet, syntax_style=None):
284 """ Sets the IPythonWidget styling.
285
286 Parameters:
287 -----------
288 stylesheet : str
289 A CSS stylesheet. The stylesheet can contain classes for:
290 1. Qt: QPlainTextEdit, QFrame, QWidget, etc
291 2. Pygments: .c, .k, .o, etc (see PygmentsHighlighter)
292 3. IPython: .error, .in-prompt, .out-prompt, etc.
293
294 syntax_style : str or None [default None]
295 If specified, use the Pygments style with given name. Otherwise,
296 the stylesheet is queried for Pygments style information.
297 """
298 self.setStyleSheet(stylesheet)
299 self._control.document().setDefaultStyleSheet(stylesheet)
300 if self._page_control:
301 self._page_control.document().setDefaultStyleSheet(stylesheet)
302
303 if syntax_style is None:
304 self._highlighter.set_style_sheet(stylesheet)
305 else:
306 self._highlighter.set_style(syntax_style)
307
308 bg_color = self._control.palette().background().color()
309 self._ansi_processor.set_background_color(bg_color)
310
311 #---------------------------------------------------------------------------
312 # 'IPythonWidget' protected interface
278 # 'IPythonWidget' protected interface
313 #---------------------------------------------------------------------------
279 #---------------------------------------------------------------------------
314
280
@@ -323,21 +289,21 b' class IPythonWidget(FrontendWidget):'
323 line : int, optional
289 line : int, optional
324 A line of interest in the file.
290 A line of interest in the file.
325 """
291 """
326 if self._editor == 'custom':
292 if self.custom_edit:
327 self.custom_edit_requested.emit(filename, line)
293 self.custom_edit_requested.emit(filename, line)
328 elif self._editor == 'default':
294 elif self.editor == 'default':
329 self._append_plain_text('No default editor available.\n')
295 self._append_plain_text('No default editor available.\n')
330 else:
296 else:
331 try:
297 try:
332 filename = '"%s"' % filename
298 filename = '"%s"' % filename
333 if line and self._editor_line:
299 if line and self.editor_line:
334 command = self._editor_line.format(filename=filename,
300 command = self.editor_line.format(filename=filename,
335 line=line)
301 line=line)
336 else:
302 else:
337 try:
303 try:
338 command = self._editor.format()
304 command = self.editor.format()
339 except KeyError:
305 except KeyError:
340 command = self._editor.format(filename=filename)
306 command = self.editor.format(filename=filename)
341 else:
307 else:
342 command += ' ' + filename
308 command += ' ' + filename
343 except KeyError:
309 except KeyError:
@@ -369,3 +335,25 b' class IPythonWidget(FrontendWidget):'
369 """
335 """
370 body = self.out_prompt % number
336 body = self.out_prompt % number
371 return '<span class="out-prompt">%s</span>' % body
337 return '<span class="out-prompt">%s</span>' % body
338
339 #------ Trait change handlers ---------------------------------------------
340
341 def _style_sheet_changed(self):
342 """ Set the style sheets of the underlying widgets.
343 """
344 self.setStyleSheet(self.style_sheet)
345 self._control.document().setDefaultStyleSheet(self.style_sheet)
346 if self._page_control:
347 self._page_control.document().setDefaultStyleSheet(self.style_sheet)
348
349 bg_color = self._control.palette().background().color()
350 self._ansi_processor.set_background_color(bg_color)
351
352 def _syntax_style_changed(self):
353 """ Set the style for the syntax highlighter.
354 """
355 if self.syntax_style:
356 self._highlighter.set_style(self.syntax_style)
357 else:
358 self._highlighter.set_style_sheet(self.style_sheet)
359
General Comments 0
You need to be logged in to leave comments. Login now