##// END OF EJS Templates
Made IPythonWidget and its subclasses Configurable.
epatters -
Show More
@@ -7,6 +7,9 b' from textwrap import dedent'
7 7 from PyQt4 import QtCore, QtGui
8 8
9 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 13 from ansi_code_processor import QtAnsiCodeProcessor
11 14 from completion_widget import CompletionWidget
12 15
@@ -32,7 +35,7 b' class ConsoleTextEdit(QtGui.QTextEdit):'
32 35 def dropEvent(self, event): pass
33 36
34 37
35 class ConsoleWidget(QtGui.QWidget):
38 class ConsoleWidget(Configurable, QtGui.QWidget):
36 39 """ An abstract base class for console-type widgets. This class has
37 40 functionality for:
38 41
@@ -45,20 +48,40 b' class ConsoleWidget(QtGui.QWidget):'
45 48 ConsoleWidget also provides a number of utility methods that will be
46 49 convenient to implementors of a console-style widget.
47 50 """
51 __metaclass__ = MetaQObjectHasTraits
48 52
49 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.
53 buffer_size = 500
56 # The maximum number of lines of text before truncation. Specifying a
57 # non-positive number disables text truncation (not recommended).
58 buffer_size = Int(500, config=True)
54 59
55 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 81 # Whether to override ShortcutEvents for the keybindings defined by this
59 82 # widget (Ctrl+n, Ctrl+a, etc). Enable this if you want this widget to take
60 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 86 # Signals that indicate ConsoleWidget state.
64 87 copy_available = QtCore.pyqtSignal(bool)
@@ -84,42 +107,26 b' class ConsoleWidget(QtGui.QWidget):'
84 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 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 115 parent : QWidget, optional [default None]
110 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 121 # Create the layout and underlying text widget.
115 122 layout = QtGui.QStackedLayout(self)
116 123 layout.setContentsMargins(0, 0, 0, 0)
117 self._control = self._create_control(kind)
124 self._control = self._create_control()
118 125 self._page_control = None
119 126 self._splitter = None
120 if paging in ('hsplit', 'vsplit'):
127 if self.paging in ('hsplit', 'vsplit'):
121 128 self._splitter = QtGui.QSplitter()
122 if paging == 'hsplit':
129 if self.paging == 'hsplit':
123 130 self._splitter.setOrientation(QtCore.Qt.Horizontal)
124 131 else:
125 132 self._splitter.setOrientation(QtCore.Qt.Vertical)
@@ -129,16 +136,13 b' class ConsoleWidget(QtGui.QWidget):'
129 136 layout.addWidget(self._control)
130 137
131 138 # Create the paging widget, if necessary.
132 self._page_style = paging
133 if paging in ('inside', 'hsplit', 'vsplit'):
139 if self.paging in ('inside', 'hsplit', 'vsplit'):
134 140 self._page_control = self._create_page_control()
135 141 if self._splitter:
136 142 self._page_control.hide()
137 143 self._splitter.addWidget(self._page_control)
138 144 else:
139 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 147 # Initialize protected variables. Some variables contain useful state
144 148 # information for subclasses; they should be considered read-only.
@@ -210,11 +214,11 b' class ConsoleWidget(QtGui.QWidget):'
210 214 # factor of one character here.
211 215 width = font_metrics.maxWidth() * 81 + margin
212 216 width += style.pixelMetric(QtGui.QStyle.PM_ScrollBarExtent)
213 if self._page_style == 'hsplit':
217 if self.paging == 'hsplit':
214 218 width = width * 2 + splitwidth
215 219
216 220 height = font_metrics.height() * 25 + margin
217 if self._page_style == 'vsplit':
221 if self.paging == 'vsplit':
218 222 height = height * 2 + splitwidth
219 223
220 224 return QtCore.QSize(width, height)
@@ -577,16 +581,14 b' class ConsoleWidget(QtGui.QWidget):'
577 581
578 582 return down
579 583
580 def _create_control(self, kind):
584 def _create_control(self):
581 585 """ Creates and connects the underlying text widget.
582 586 """
583 if kind == 'plain':
587 if self.kind == 'plain':
584 588 control = ConsolePlainTextEdit()
585 elif kind == 'rich':
589 elif self.kind == 'rich':
586 590 control = ConsoleTextEdit()
587 591 control.setAcceptRichText(False)
588 else:
589 raise ValueError("Kind %s unknown." % repr(kind))
590 592 control.installEventFilter(self)
591 593 control.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
592 594 control.customContextMenuRequested.connect(self._show_context_menu)
@@ -1072,13 +1074,13 b' class ConsoleWidget(QtGui.QWidget):'
1072 1074 """ Displays text using the pager if it exceeds the height of the
1073 1075 visible area.
1074 1076 """
1075 if self._page_style == 'none':
1077 if self.paging == 'none':
1076 1078 self._append_plain_text(text)
1077 1079 else:
1078 1080 line_height = QtGui.QFontMetrics(self.font).height()
1079 1081 minlines = self._control.viewport().height() / line_height
1080 1082 if re.match("(?:[^\n]*\n){%i}" % minlines, text):
1081 if self._page_style == 'custom':
1083 if self.paging == 'custom':
1082 1084 self.custom_page_requested.emit(text)
1083 1085 else:
1084 1086 self._page_control.clear()
@@ -9,6 +9,7 b' from PyQt4 import QtCore, QtGui'
9 9 # Local imports
10 10 from IPython.core.inputsplitter import InputSplitter
11 11 from IPython.frontend.qt.base_frontend_mixin import BaseFrontendMixin
12 from IPython.utils.traitlets import Bool, Type
12 13 from call_tip_widget import CallTipWidget
13 14 from completion_lexer import CompletionLexer
14 15 from console_widget import HistoryConsoleWidget
@@ -74,12 +75,12 b' class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):'
74 75
75 76 # An option and corresponding signal for overriding the default kernel
76 77 # interrupt behavior.
77 custom_interrupt = False
78 custom_interrupt = Bool(False)
78 79 custom_interrupt_requested = QtCore.pyqtSignal()
79 80
80 81 # An option and corresponding signal for overriding the default kernel
81 82 # restart behavior.
82 custom_restart = False
83 custom_restart = Bool(False)
83 84 custom_restart_requested = QtCore.pyqtSignal()
84 85
85 86 # Emitted when an 'execute_reply' has been received from the kernel and
@@ -87,8 +88,8 b' class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):'
87 88 executed = QtCore.pyqtSignal(object)
88 89
89 90 # Protected class variables.
90 _highlighter_class = FrontendHighlighter
91 _input_splitter_class = InputSplitter
91 _highlighter_class = Type(FrontendHighlighter)
92 _input_splitter_class = Type(InputSplitter)
92 93
93 94 #---------------------------------------------------------------------------
94 95 # 'object' interface
@@ -16,49 +16,76 b' from PyQt4 import QtCore, QtGui'
16 16 # Local imports
17 17 from IPython.core.inputsplitter import IPythonInputSplitter
18 18 from IPython.core.usage import default_banner
19 from IPython.utils.traitlets import Bool, Str
19 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 49 class IPythonWidget(FrontendWidget):
23 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
27 # specified as 'custom'. See 'set_editor' for more information.
53 # If set, the 'custom_edit_requested(str, int)' signal will be emitted when
54 # an editor is needed for a file. This overrides 'editor' and 'editor_line'
55 # settings.
56 custom_edit = Bool(False)
28 57 custom_edit_requested = QtCore.pyqtSignal(object, object)
29 58
30 # The default stylesheet: black text on a white background.
31 default_stylesheet = """
32 .error { color: red; }
33 .in-prompt { color: navy; }
34 .in-prompt-number { font-weight: bold; }
35 .out-prompt { color: darkred; }
36 .out-prompt-number { font-weight: bold; }
37 """
38
39 # A dark stylesheet: white text on a black background.
40 dark_stylesheet = """
41 QPlainTextEdit, QTextEdit { background-color: black; color: white }
42 QFrame { border: 1px solid grey; }
43 .error { color: red; }
44 .in-prompt { color: lime; }
45 .in-prompt-number { color: lime; font-weight: bold; }
46 .out-prompt { color: red; }
47 .out-prompt-number { color: red; font-weight: bold; }
48 """
49
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>]: '
59 # A command for invoking a system text editor. If the string contains a
60 # {filename} format specifier, it will be used. Otherwise, the filename will
61 # be appended to the end the command.
62 editor = Str('default', config=True)
63
64 # The editor command to use when a specific line number is requested. The
65 # string should contain two format specifiers: {line} and {filename}. If
66 # this parameter is not specified, the line number option to the %edit magic
67 # will be ignored.
68 editor_line = Str(config=True)
69
70 # A CSS stylesheet. The stylesheet can contain classes for:
71 # 1. Qt: QPlainTextEdit, QFrame, QWidget, etc
72 # 2. Pygments: .c, .k, .o, etc (see PygmentsHighlighter)
73 # 3. IPython: .error, .in-prompt, .out-prompt, etc
74 style_sheet = Str(default_style_sheet, config=True)
75
76 # If not empty, use this Pygments style for syntax highlighting. Otherwise,
77 # the style sheet is queried for Pygments style information.
78 syntax_style = Str(default_syntax_style, config=True)
53 79
54 # A type used internally by IPythonWidget for storing prompt information.
55 _PromptBlock = namedtuple('_PromptBlock',
56 ['block', 'length', 'number'])
80 # Prompts.
81 in_prompt = Str(default_in_prompt, config=True)
82 out_prompt = Str(default_out_prompt, config=True)
57 83
58 84 # FrontendWidget protected class variables.
59 85 _input_splitter_class = IPythonInputSplitter
60 86
61 87 # IPythonWidget protected class variables.
88 _PromptBlock = namedtuple('_PromptBlock', ['block', 'length', 'number'])
62 89 _payload_source_edit = 'IPython.zmq.zmqshell.ZMQInteractiveShell.edit_magic'
63 90 _payload_source_page = 'IPython.zmq.page.page'
64 91
@@ -72,9 +99,9 b' class IPythonWidget(FrontendWidget):'
72 99 # IPythonWidget protected variables.
73 100 self._previous_prompt_obj = None
74 101
75 # Set a default editor and stylesheet.
76 self.set_editor('default')
77 self.reset_styling()
102 # Initialize widget styling.
103 self._style_sheet_changed()
104 self._syntax_style_changed()
78 105
79 106 #---------------------------------------------------------------------------
80 107 # 'BaseFrontendMixin' abstract interface
@@ -248,67 +275,6 b' class IPythonWidget(FrontendWidget):'
248 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 278 # 'IPythonWidget' protected interface
313 279 #---------------------------------------------------------------------------
314 280
@@ -323,21 +289,21 b' class IPythonWidget(FrontendWidget):'
323 289 line : int, optional
324 290 A line of interest in the file.
325 291 """
326 if self._editor == 'custom':
292 if self.custom_edit:
327 293 self.custom_edit_requested.emit(filename, line)
328 elif self._editor == 'default':
294 elif self.editor == 'default':
329 295 self._append_plain_text('No default editor available.\n')
330 296 else:
331 297 try:
332 298 filename = '"%s"' % filename
333 if line and self._editor_line:
334 command = self._editor_line.format(filename=filename,
335 line=line)
299 if line and self.editor_line:
300 command = self.editor_line.format(filename=filename,
301 line=line)
336 302 else:
337 303 try:
338 command = self._editor.format()
304 command = self.editor.format()
339 305 except KeyError:
340 command = self._editor.format(filename=filename)
306 command = self.editor.format(filename=filename)
341 307 else:
342 308 command += ' ' + filename
343 309 except KeyError:
@@ -369,3 +335,25 b' class IPythonWidget(FrontendWidget):'
369 335 """
370 336 body = self.out_prompt % number
371 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