diff --git a/IPython/frontend/qt/console/console_widget.py b/IPython/frontend/qt/console/console_widget.py
index e49dda2..e8461bc 100644
--- a/IPython/frontend/qt/console/console_widget.py
+++ b/IPython/frontend/qt/console/console_widget.py
@@ -7,6 +7,9 @@ from textwrap import dedent
from PyQt4 import QtCore, QtGui
# Local imports
+from IPython.config.configurable import Configurable
+from IPython.frontend.qt.util import MetaQObjectHasTraits
+from IPython.utils.traitlets import Bool, Enum, Int
from ansi_code_processor import QtAnsiCodeProcessor
from completion_widget import CompletionWidget
@@ -32,7 +35,7 @@ class ConsoleTextEdit(QtGui.QTextEdit):
def dropEvent(self, event): pass
-class ConsoleWidget(QtGui.QWidget):
+class ConsoleWidget(Configurable, QtGui.QWidget):
""" An abstract base class for console-type widgets. This class has
functionality for:
@@ -45,20 +48,40 @@ class ConsoleWidget(QtGui.QWidget):
ConsoleWidget also provides a number of utility methods that will be
convenient to implementors of a console-style widget.
"""
+ __metaclass__ = MetaQObjectHasTraits
# Whether to process ANSI escape codes.
- ansi_codes = True
+ ansi_codes = Bool(True, config=True)
- # The maximum number of lines of text before truncation.
- buffer_size = 500
+ # The maximum number of lines of text before truncation. Specifying a
+ # non-positive number disables text truncation (not recommended).
+ buffer_size = Int(500, config=True)
# Whether to use a list widget or plain text output for tab completion.
- gui_completion = True
+ gui_completion = Bool(True, config=True)
+
+ # The type of underlying text widget to use. Valid values are 'plain', which
+ # specifies a QPlainTextEdit, and 'rich', which specifies a QTextEdit.
+ # NOTE: this value can only be specified during initialization.
+ kind = Enum(['plain', 'rich'], default_value='plain', config=True)
+
+ # The type of paging to use. Valid values are:
+ # 'inside' : The widget pages like a traditional terminal pager.
+ # 'hsplit' : When paging is requested, the widget is split
+ # horizontally. The top pane contains the console, and the
+ # bottom pane contains the paged text.
+ # 'vsplit' : Similar to 'hsplit', except that a vertical splitter used.
+ # 'custom' : No action is taken by the widget beyond emitting a
+ # 'custom_page_requested(str)' signal.
+ # 'none' : The text is written directly to the console.
+ # NOTE: this value can only be specified during initialization.
+ paging = Enum(['inside', 'hsplit', 'vsplit', 'custom', 'none'],
+ default_value='inside', config=True)
# Whether to override ShortcutEvents for the keybindings defined by this
# widget (Ctrl+n, Ctrl+a, etc). Enable this if you want this widget to take
# priority (when it has focus) over, e.g., window-level menu shortcuts.
- override_shortcuts = False
+ override_shortcuts = Bool(False)
# Signals that indicate ConsoleWidget state.
copy_available = QtCore.pyqtSignal(bool)
@@ -84,42 +107,26 @@ class ConsoleWidget(QtGui.QWidget):
# 'QObject' interface
#---------------------------------------------------------------------------
- def __init__(self, kind='plain', paging='inside', parent=None):
+ def __init__(self, parent=None, **kw):
""" Create a ConsoleWidget.
-
- Parameters
- ----------
- kind : str, optional [default 'plain']
- The type of underlying text widget to use. Valid values are 'plain',
- which specifies a QPlainTextEdit, and 'rich', which specifies a
- QTextEdit.
-
- paging : str, optional [default 'inside']
- The type of paging to use. Valid values are:
- 'inside' : The widget pages like a traditional terminal pager.
- 'hsplit' : When paging is requested, the widget is split
- horizontally. The top pane contains the console,
- and the bottom pane contains the paged text.
- 'vsplit' : Similar to 'hsplit', except that a vertical splitter
- used.
- 'custom' : No action is taken by the widget beyond emitting a
- 'custom_page_requested(str)' signal.
- 'none' : The text is written directly to the console.
+ Parameters:
+ -----------
parent : QWidget, optional [default None]
The parent for this widget.
"""
- super(ConsoleWidget, self).__init__(parent)
+ QtGui.QWidget.__init__(self, parent)
+ Configurable.__init__(self, **kw)
# Create the layout and underlying text widget.
layout = QtGui.QStackedLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
- self._control = self._create_control(kind)
+ self._control = self._create_control()
self._page_control = None
self._splitter = None
- if paging in ('hsplit', 'vsplit'):
+ if self.paging in ('hsplit', 'vsplit'):
self._splitter = QtGui.QSplitter()
- if paging == 'hsplit':
+ if self.paging == 'hsplit':
self._splitter.setOrientation(QtCore.Qt.Horizontal)
else:
self._splitter.setOrientation(QtCore.Qt.Vertical)
@@ -129,16 +136,13 @@ class ConsoleWidget(QtGui.QWidget):
layout.addWidget(self._control)
# Create the paging widget, if necessary.
- self._page_style = paging
- if paging in ('inside', 'hsplit', 'vsplit'):
+ if self.paging in ('inside', 'hsplit', 'vsplit'):
self._page_control = self._create_page_control()
if self._splitter:
self._page_control.hide()
self._splitter.addWidget(self._page_control)
else:
layout.addWidget(self._page_control)
- elif paging not in ('custom', 'none'):
- raise ValueError('Paging style %s unknown.' % repr(paging))
# Initialize protected variables. Some variables contain useful state
# information for subclasses; they should be considered read-only.
@@ -210,11 +214,11 @@ class ConsoleWidget(QtGui.QWidget):
# factor of one character here.
width = font_metrics.maxWidth() * 81 + margin
width += style.pixelMetric(QtGui.QStyle.PM_ScrollBarExtent)
- if self._page_style == 'hsplit':
+ if self.paging == 'hsplit':
width = width * 2 + splitwidth
height = font_metrics.height() * 25 + margin
- if self._page_style == 'vsplit':
+ if self.paging == 'vsplit':
height = height * 2 + splitwidth
return QtCore.QSize(width, height)
@@ -577,16 +581,14 @@ class ConsoleWidget(QtGui.QWidget):
return down
- def _create_control(self, kind):
+ def _create_control(self):
""" Creates and connects the underlying text widget.
"""
- if kind == 'plain':
+ if self.kind == 'plain':
control = ConsolePlainTextEdit()
- elif kind == 'rich':
+ elif self.kind == 'rich':
control = ConsoleTextEdit()
control.setAcceptRichText(False)
- else:
- raise ValueError("Kind %s unknown." % repr(kind))
control.installEventFilter(self)
control.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
control.customContextMenuRequested.connect(self._show_context_menu)
@@ -1072,13 +1074,13 @@ class ConsoleWidget(QtGui.QWidget):
""" Displays text using the pager if it exceeds the height of the
visible area.
"""
- if self._page_style == 'none':
+ if self.paging == 'none':
self._append_plain_text(text)
else:
line_height = QtGui.QFontMetrics(self.font).height()
minlines = self._control.viewport().height() / line_height
if re.match("(?:[^\n]*\n){%i}" % minlines, text):
- if self._page_style == 'custom':
+ if self.paging == 'custom':
self.custom_page_requested.emit(text)
else:
self._page_control.clear()
diff --git a/IPython/frontend/qt/console/frontend_widget.py b/IPython/frontend/qt/console/frontend_widget.py
index e2aaab0..658f861 100644
--- a/IPython/frontend/qt/console/frontend_widget.py
+++ b/IPython/frontend/qt/console/frontend_widget.py
@@ -9,6 +9,7 @@ from PyQt4 import QtCore, QtGui
# Local imports
from IPython.core.inputsplitter import InputSplitter
from IPython.frontend.qt.base_frontend_mixin import BaseFrontendMixin
+from IPython.utils.traitlets import Bool, Type
from call_tip_widget import CallTipWidget
from completion_lexer import CompletionLexer
from console_widget import HistoryConsoleWidget
@@ -74,12 +75,12 @@ class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):
# An option and corresponding signal for overriding the default kernel
# interrupt behavior.
- custom_interrupt = False
+ custom_interrupt = Bool(False)
custom_interrupt_requested = QtCore.pyqtSignal()
# An option and corresponding signal for overriding the default kernel
# restart behavior.
- custom_restart = False
+ custom_restart = Bool(False)
custom_restart_requested = QtCore.pyqtSignal()
# Emitted when an 'execute_reply' has been received from the kernel and
@@ -87,8 +88,8 @@ class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):
executed = QtCore.pyqtSignal(object)
# Protected class variables.
- _highlighter_class = FrontendHighlighter
- _input_splitter_class = InputSplitter
+ _highlighter_class = Type(FrontendHighlighter)
+ _input_splitter_class = Type(InputSplitter)
#---------------------------------------------------------------------------
# 'object' interface
diff --git a/IPython/frontend/qt/console/ipython_widget.py b/IPython/frontend/qt/console/ipython_widget.py
index fda71f9..0ee71c2 100644
--- a/IPython/frontend/qt/console/ipython_widget.py
+++ b/IPython/frontend/qt/console/ipython_widget.py
@@ -16,49 +16,76 @@ from PyQt4 import QtCore, QtGui
# Local imports
from IPython.core.inputsplitter import IPythonInputSplitter
from IPython.core.usage import default_banner
+from IPython.utils.traitlets import Bool, Str
from frontend_widget import FrontendWidget
+# The default style sheet: black text on a white background.
+default_style_sheet = '''
+ .error { color: red; }
+ .in-prompt { color: navy; }
+ .in-prompt-number { font-weight: bold; }
+ .out-prompt { color: darkred; }
+ .out-prompt-number { font-weight: bold; }
+'''
+default_syntax_style = 'default'
+
+# A dark style sheet: white text on a black background.
+dark_style_sheet = '''
+ QPlainTextEdit, QTextEdit { background-color: black; color: white }
+ QFrame { border: 1px solid grey; }
+ .error { color: red; }
+ .in-prompt { color: lime; }
+ .in-prompt-number { color: lime; font-weight: bold; }
+ .out-prompt { color: red; }
+ .out-prompt-number { color: red; font-weight: bold; }
+'''
+dark_syntax_style = 'monokai'
+
+# Default prompts.
+default_in_prompt = 'In [%i]: '
+default_out_prompt = 'Out[%i]: '
+
class IPythonWidget(FrontendWidget):
""" A FrontendWidget for an IPython kernel.
"""
- # Signal emitted when an editor is needed for a file and the editor has been
- # specified as 'custom'. See 'set_editor' for more information.
+ # If set, the 'custom_edit_requested(str, int)' signal will be emitted when
+ # an editor is needed for a file. This overrides 'editor' and 'editor_line'
+ # settings.
+ custom_edit = Bool(False)
custom_edit_requested = QtCore.pyqtSignal(object, object)
- # The default stylesheet: black text on a white background.
- default_stylesheet = """
- .error { color: red; }
- .in-prompt { color: navy; }
- .in-prompt-number { font-weight: bold; }
- .out-prompt { color: darkred; }
- .out-prompt-number { font-weight: bold; }
- """
-
- # A dark stylesheet: white text on a black background.
- dark_stylesheet = """
- QPlainTextEdit, QTextEdit { background-color: black; color: white }
- QFrame { border: 1px solid grey; }
- .error { color: red; }
- .in-prompt { color: lime; }
- .in-prompt-number { color: lime; font-weight: bold; }
- .out-prompt { color: red; }
- .out-prompt-number { color: red; font-weight: bold; }
- """
-
- # Default prompts.
- in_prompt = 'In [%i]: '
- out_prompt = 'Out[%i]: '
+ # A command for invoking a system text editor. If the string contains a
+ # {filename} format specifier, it will be used. Otherwise, the filename will
+ # be appended to the end the command.
+ editor = Str('default', config=True)
+
+ # The editor command to use when a specific line number is requested. The
+ # string should contain two format specifiers: {line} and {filename}. If
+ # this parameter is not specified, the line number option to the %edit magic
+ # will be ignored.
+ editor_line = Str(config=True)
+
+ # A CSS stylesheet. The stylesheet can contain classes for:
+ # 1. Qt: QPlainTextEdit, QFrame, QWidget, etc
+ # 2. Pygments: .c, .k, .o, etc (see PygmentsHighlighter)
+ # 3. IPython: .error, .in-prompt, .out-prompt, etc
+ style_sheet = Str(default_style_sheet, config=True)
+
+ # If not empty, use this Pygments style for syntax highlighting. Otherwise,
+ # the style sheet is queried for Pygments style information.
+ syntax_style = Str(default_syntax_style, config=True)
- # A type used internally by IPythonWidget for storing prompt information.
- _PromptBlock = namedtuple('_PromptBlock',
- ['block', 'length', 'number'])
+ # Prompts.
+ in_prompt = Str(default_in_prompt, config=True)
+ out_prompt = Str(default_out_prompt, config=True)
# FrontendWidget protected class variables.
_input_splitter_class = IPythonInputSplitter
# IPythonWidget protected class variables.
+ _PromptBlock = namedtuple('_PromptBlock', ['block', 'length', 'number'])
_payload_source_edit = 'IPython.zmq.zmqshell.ZMQInteractiveShell.edit_magic'
_payload_source_page = 'IPython.zmq.page.page'
@@ -72,9 +99,9 @@ class IPythonWidget(FrontendWidget):
# IPythonWidget protected variables.
self._previous_prompt_obj = None
- # Set a default editor and stylesheet.
- self.set_editor('default')
- self.reset_styling()
+ # Initialize widget styling.
+ self._style_sheet_changed()
+ self._syntax_style_changed()
#---------------------------------------------------------------------------
# 'BaseFrontendMixin' abstract interface
@@ -248,67 +275,6 @@ class IPythonWidget(FrontendWidget):
next_prompt['input_sep'])
#---------------------------------------------------------------------------
- # 'IPythonWidget' interface
- #---------------------------------------------------------------------------
-
- def reset_styling(self):
- """ Restores the default IPythonWidget styling.
- """
- self.set_styling(self.default_stylesheet, syntax_style='default')
- #self.set_styling(self.dark_stylesheet, syntax_style='monokai')
-
- def set_editor(self, editor, line_editor=None):
- """ Sets the editor to use with the %edit magic.
-
- Parameters:
- -----------
- editor : str
- A command for invoking a system text editor. If the string contains
- a {filename} format specifier, it will be used. Otherwise, the
- filename will be appended to the end the command.
-
- This parameter also takes a special value:
- 'custom' : Emit a 'custom_edit_requested(str, int)' signal
- instead of opening an editor.
-
- line_editor : str, optional
- The editor command to use when a specific line number is
- requested. The string should contain two format specifiers: {line}
- and {filename}. If this parameter is not specified, the line number
- option to the %edit magic will be ignored.
- """
- self._editor = editor
- self._editor_line = line_editor
-
- def set_styling(self, stylesheet, syntax_style=None):
- """ Sets the IPythonWidget styling.
-
- Parameters:
- -----------
- stylesheet : str
- A CSS stylesheet. The stylesheet can contain classes for:
- 1. Qt: QPlainTextEdit, QFrame, QWidget, etc
- 2. Pygments: .c, .k, .o, etc (see PygmentsHighlighter)
- 3. IPython: .error, .in-prompt, .out-prompt, etc.
-
- syntax_style : str or None [default None]
- If specified, use the Pygments style with given name. Otherwise,
- the stylesheet is queried for Pygments style information.
- """
- self.setStyleSheet(stylesheet)
- self._control.document().setDefaultStyleSheet(stylesheet)
- if self._page_control:
- self._page_control.document().setDefaultStyleSheet(stylesheet)
-
- if syntax_style is None:
- self._highlighter.set_style_sheet(stylesheet)
- else:
- self._highlighter.set_style(syntax_style)
-
- bg_color = self._control.palette().background().color()
- self._ansi_processor.set_background_color(bg_color)
-
- #---------------------------------------------------------------------------
# 'IPythonWidget' protected interface
#---------------------------------------------------------------------------
@@ -323,21 +289,21 @@ class IPythonWidget(FrontendWidget):
line : int, optional
A line of interest in the file.
"""
- if self._editor == 'custom':
+ if self.custom_edit:
self.custom_edit_requested.emit(filename, line)
- elif self._editor == 'default':
+ elif self.editor == 'default':
self._append_plain_text('No default editor available.\n')
else:
try:
filename = '"%s"' % filename
- if line and self._editor_line:
- command = self._editor_line.format(filename=filename,
- line=line)
+ if line and self.editor_line:
+ command = self.editor_line.format(filename=filename,
+ line=line)
else:
try:
- command = self._editor.format()
+ command = self.editor.format()
except KeyError:
- command = self._editor.format(filename=filename)
+ command = self.editor.format(filename=filename)
else:
command += ' ' + filename
except KeyError:
@@ -369,3 +335,25 @@ class IPythonWidget(FrontendWidget):
"""
body = self.out_prompt % number
return '%s' % body
+
+ #------ Trait change handlers ---------------------------------------------
+
+ def _style_sheet_changed(self):
+ """ Set the style sheets of the underlying widgets.
+ """
+ self.setStyleSheet(self.style_sheet)
+ self._control.document().setDefaultStyleSheet(self.style_sheet)
+ if self._page_control:
+ self._page_control.document().setDefaultStyleSheet(self.style_sheet)
+
+ bg_color = self._control.palette().background().color()
+ self._ansi_processor.set_background_color(bg_color)
+
+ def _syntax_style_changed(self):
+ """ Set the style for the syntax highlighter.
+ """
+ if self.syntax_style:
+ self._highlighter.set_style(self.syntax_style)
+ else:
+ self._highlighter.set_style_sheet(self.style_sheet)
+