Repository URL to install this package:
Version:
6.1.1 ▾
|
#------------------------------------------------------------------------------
#
# Copyright (c) 2006, Enthought, Inc.
# All rights reserved.
#
# This software is provided without warranty under the terms of the BSD
# license included in enthought/LICENSE.txt and may be redistributed only
# under the conditions described in the aforementioned license. The license
# is also available online at http://www.enthought.com/licenses/BSD.txt
#
# Thanks for using Enthought open source!
#
# Author: David C. Morrill
# Date: 01/27/2006
#
#------------------------------------------------------------------------------
""" Defines a source code editor for the wxPython user interface toolkit,
useful for tools such as debuggers.
"""
#-------------------------------------------------------------------------
# Imports:
#-------------------------------------------------------------------------
from __future__ import absolute_import
import wx
import wx.stc as stc
from traits.api \
import Str, List, Int, Event, Bool, TraitError, on_trait_change
from traits.trait_base \
import SequenceTypes
# FIXME: ToolkitEditorFactory is a proxy class defined here just for backward
# compatibility. The class has been moved to the
# traitsui.editors.code_editor file.
from traitsui.editors.code_editor \
import ToolkitEditorFactory
from pyface.api \
import PythonEditor
from pyface.util.python_stc \
import faces
from .editor \
import Editor
from .constants \
import OKColor, ErrorColor
#-------------------------------------------------------------------------
# Constants:
#-------------------------------------------------------------------------
# Marker line constants:
# Marks a marked line
MARK_MARKER = 0
# Marks a line matching the current search
SEARCH_MARKER = 1
# Marks the currently selected line
SELECTED_MARKER = 2
#-------------------------------------------------------------------------
# 'SourceEditor' class:
#-------------------------------------------------------------------------
class SourceEditor(Editor):
""" Editor for source code, which displays a PyFace PythonEditor.
"""
#-------------------------------------------------------------------------
# Trait definitions:
#-------------------------------------------------------------------------
# The code editor is scrollable. This value overrides the default.
scrollable = True
# Is the editor read only?
readonly = Bool(False)
# The currently selected line
selected_line = Int
# The currently selected text
selected_text = Str
# The list of line numbers to mark
mark_lines = List(Int)
# The current line number
line = Event
# The current column
column = Event
# calltip clicked event
calltip_clicked = Event
# The STC lexer use
lexer = Int
# The lines to be dimmed
dim_lines = List(Int)
dim_color = Str
_dim_style_number = Int(16) # 0-15 are reserved for the python lexer
# The lines to have squiggles drawn under them
squiggle_lines = List(Int)
squiggle_color = Str
#-------------------------------------------------------------------------
# Finishes initializing the editor by creating the underlying toolkit
# widget:
#-------------------------------------------------------------------------
def init(self, parent):
""" Finishes initializing the editor by creating the underlying toolkit
widget.
"""
factory = self.factory
self._editor = editor = PythonEditor(
parent, show_line_numbers=factory.show_line_numbers)
self.control = control = editor.control
# There are a number of events which aren't well documented that look
# to be useful in future implmentations, below are a subset of the
# events that look interesting:
# EVT_STC_AUTOCOMP_SELECTION
# EVT_STC_HOTSPOT_CLICK
# EVT_STC_HOTSPOT_DCLICK
# EVT_STC_DOUBLECLICK
# EVT_STC_MARGINCLICK
control.SetSize(wx.Size(300, 124))
# Clear out the goofy hotkeys for zooming text
control.CmdKeyClear(ord('B'), stc.STC_SCMOD_CTRL)
control.CmdKeyClear(ord('N'), stc.STC_SCMOD_CTRL)
# Set up the events
wx.EVT_KILL_FOCUS(control, self.wx_update_object)
stc.EVT_STC_CALLTIP_CLICK(control, control.GetId(),
self._calltip_clicked)
if factory.auto_scroll and (factory.selected_line != ''):
wx.EVT_SIZE(control, self._update_selected_line)
if factory.auto_set:
editor.on_trait_change(self.update_object, 'changed',
dispatch='ui')
if factory.key_bindings is not None:
editor.on_trait_change(self.key_pressed, 'key_pressed',
dispatch='ui')
if self.readonly:
control.SetReadOnly(True)
# Set up the lexer
control.SetLexer(stc.STC_LEX_CONTAINER)
control.Bind(stc.EVT_STC_STYLENEEDED, self._style_needed)
try:
self.lexer = getattr(stc, 'STC_LEX_' + self.factory.lexer.upper())
except AttributeError:
self.lexer = stc.STC_LEX_NULL
# Define the markers we use:
control.MarkerDefine(MARK_MARKER, stc.STC_MARK_BACKGROUND,
background=factory.mark_color_)
control.MarkerDefine(SEARCH_MARKER, stc.STC_MARK_BACKGROUND,
background=factory.search_color_)
control.MarkerDefine(SELECTED_MARKER, stc.STC_MARK_BACKGROUND,
background=factory.selected_color_)
# Make sure the editor has been initialized:
self.update_editor()
# Set up any event listeners:
self.sync_value(factory.mark_lines, 'mark_lines', 'from',
is_list=True)
self.sync_value(factory.selected_line, 'selected_line', 'from')
self.sync_value(factory.selected_text, 'selected_text', 'to')
self.sync_value(factory.line, 'line')
self.sync_value(factory.column, 'column')
self.sync_value(factory.calltip_clicked, 'calltip_clicked')
self.sync_value(factory.dim_lines, 'dim_lines', 'from', is_list=True)
if self.factory.dim_color == '':
self.dim_color = 'dark grey'
else:
self.sync_value(factory.dim_color, 'dim_color', 'from')
self.sync_value(factory.squiggle_lines, 'squiggle_lines', 'from',
is_list=True)
if factory.squiggle_color == '':
self.squiggle_color = 'red'
else:
self.sync_value(factory.squiggle_color, 'squiggle_color', 'from')
# Check if we need to monitor the line or column position being
# changed:
if (factory.line != '') or (factory.column != '') or \
(factory.selected_text != ''):
stc.EVT_STC_UPDATEUI(control, control.GetId(),
self._position_changed)
self.set_tooltip()
#-------------------------------------------------------------------------
# Handles the user entering input data in the edit control:
#-------------------------------------------------------------------------
def wx_update_object(self, event):
""" Handles the user entering input data in the edit control.
"""
self.update_object()
event.Skip()
def update_object(self):
""" Handles the user entering input data in the edit control.
"""
if not self._locked:
try:
value = self.control.GetText()
if isinstance(self.value, SequenceTypes):
value = value.split()
self.value = value
self.control.SetBackgroundColour(OKColor)
self.control.Refresh()
except TraitError as excp:
pass
#-------------------------------------------------------------------------
# Updates the editor when the object trait changes external to the editor:
#-------------------------------------------------------------------------
def update_editor(self):
""" Updates the editor when the object trait changes externally to the
editor.
"""
self._locked = True
new_value = self.value
if isinstance(new_value, SequenceTypes):
new_value = '\n'.join([line.rstrip() for line in new_value])
control = self.control
if control.GetText() != new_value:
readonly = control.GetReadOnly()
control.SetReadOnly(False)
l1 = control.GetFirstVisibleLine()
pos = control.GetCurrentPos()
control.SetText(new_value)
control.GotoPos(pos)
control.ScrollToLine(l1)
control.SetReadOnly(readonly)
self._mark_lines_changed()
self._selected_line_changed()
self._style_document()
self._locked = False
#-------------------------------------------------------------------------
# Handles the calltip being clicked:
#-------------------------------------------------------------------------
def _calltip_clicked(self, event):
self.calltip_clicked = True
#-------------------------------------------------------------------------
# Handles the set of 'marked lines' being changed:
#-------------------------------------------------------------------------
def _mark_lines_changed(self):
""" Handles the set of marked lines being changed.
"""
lines = self.mark_lines
control = self.control
lc = control.GetLineCount()
control.MarkerDeleteAll(MARK_MARKER)
for line in lines:
if 0 < line <= lc:
control.MarkerAdd(line - 1, MARK_MARKER)
control.Refresh()
def _mark_lines_items_changed(self):
self._mark_lines_changed()
#-------------------------------------------------------------------------
# Handles the currently 'selected line' being changed:
#-------------------------------------------------------------------------
def _selected_line_changed(self):
""" Handles a change in which line is currently selected.
"""
line = self.selected_line
control = self.control
line = max(1, min(control.GetLineCount(), line)) - 1
control.MarkerDeleteAll(SELECTED_MARKER)
control.MarkerAdd(line, SELECTED_MARKER)
control.GotoLine(line)
if self.factory.auto_scroll:
control.ScrollToLine(line - (control.LinesOnScreen() / 2))
control.Refresh()
#-------------------------------------------------------------------------
# Handles the 'line' trait being changed:
#-------------------------------------------------------------------------
def _line_changed(self, line):
if not self._locked:
self.control.GotoLine(line - 1)
#-------------------------------------------------------------------------
# Handles the 'column' trait being changed:
#-------------------------------------------------------------------------
def _column_changed(self, column):
if not self._locked:
control = self.control
line = control.LineFromPosition(control.GetCurrentPos())
control.GotoPos(control.PositionFromLine(line) + column - 1)
#-------------------------------------------------------------------------
# Handles the cursor position being changed:
#-------------------------------------------------------------------------
def _position_changed(self, event):
""" Handles the cursor position being changed.
"""
control = self.control
pos = control.GetCurrentPos()
line = control.LineFromPosition(pos)
self._locked = True
self.line = line + 1
self.column = pos - control.PositionFromLine(line) + 1
self._locked = False
self.selected_text = control.GetSelectedText()
#-------------------------------------------------------------------------
# Handles a key being pressed within the editor:
#-------------------------------------------------------------------------
def key_pressed(self, event):
""" Handles a key being pressed within the editor.
"""
self.factory.key_bindings.do(event.event, self.ui.handler,
self.ui.info)
#-------------------------------------------------------------------------
# Handles the styling of the editor:
#-------------------------------------------------------------------------
def _dim_color_changed(self):
self.control.StyleSetForeground(self._dim_style_number, self.dim_color)
self.control.StyleSetFaceName(self._dim_style_number, "courier new")
self.control.StyleSetSize(self._dim_style_number, faces['size'])
self.control.Refresh()
def _squiggle_color_changed(self):
self.control.IndicatorSetStyle(2, stc.STC_INDIC_SQUIGGLE)
self.control.IndicatorSetForeground(2, self.squiggle_color)
self.control.Refresh()
@on_trait_change('dim_lines, squiggle_lines')
def _style_document(self):
""" Force the STC to fire a STC_STYLENEEDED event for the entire
document.
"""
self.control.ClearDocumentStyle()
self.control.Colourise(0, -1)
self.control.Refresh()
def _style_needed(self, event):
""" Handles an STC request for styling for some area.
"""
position = self.control.GetEndStyled()
start_line = self.control.LineFromPosition(position)
end = event.GetPosition()
end_line = self.control.LineFromPosition(end)
# Fixes a strange a bug with the STC widget where creating a new line
# after a dimmed line causes it to mysteriously lose its styling
if start_line in self.dim_lines:
start_line -= 1
# Trying to Colourise only the lines that we want does not seem to work
# so we do the whole area and then override the styling on certain
# lines
if self.lexer != stc.STC_LEX_NULL:
self.control.SetLexer(self.lexer)
self.control.Colourise(position, end)
self.control.SetLexer(stc.STC_LEX_CONTAINER)
for line in range(start_line, end_line + 1):
# We don't use LineLength here because it includes newline
# characters. Styling these leads to strange behavior.
position = self.control.PositionFromLine(line)
style_length = self.control.GetLineEndPosition(line) - position
if line + 1 in self.dim_lines:
# Set styling mask to only style text bits, not indicator bits
self.control.StartStyling(position, 0x1f)
self.control.SetStyling(style_length, self._dim_style_number)
elif self.lexer == stc.STC_LEX_NULL:
self.control.StartStyling(position, 0x1f)
self.control.SetStyling(style_length, stc.STC_STYLE_DEFAULT)
if line + 1 in self.squiggle_lines:
self.control.StartStyling(position, stc.STC_INDIC2_MASK)
self.control.SetStyling(style_length, stc.STC_INDIC2_MASK)
else:
self.control.StartStyling(position, stc.STC_INDIC2_MASK)
self.control.SetStyling(style_length, stc.STC_STYLE_DEFAULT)
#-------------------------------------------------------------------------
# Handles an error that occurs while setting the object's trait value:
#-------------------------------------------------------------------------
def error(self, excp):
""" Handles an error that occurs while setting the object's trait value.
"""
self.control.SetBackgroundColour(ErrorColor)
self.control.Refresh()
#-------------------------------------------------------------------------
# Disposes of the contents of an editor:
#-------------------------------------------------------------------------
def dispose(self):
""" Disposes of the contents of an editor.
"""
if self.factory.auto_set:
self._editor.on_trait_change(self.update_object, 'changed',
remove=True)
if self.factory.key_bindings is not None:
self._editor.on_trait_change(self.key_pressed, 'key_pressed',
remove=True)
wx.EVT_KILL_FOCUS(self.control, None)
super(SourceEditor, self).dispose()
#-- UI preference save/restore interface ---------------------------------
#-------------------------------------------------------------------------
# Restores any saved user preference information associated with the
# editor:
#-------------------------------------------------------------------------
def restore_prefs(self, prefs):
""" Restores any saved user preference information associated with the
editor.
"""
if self.factory.key_bindings is not None:
key_bindings = prefs.get('key_bindings')
if key_bindings is not None:
self.factory.key_bindings.merge(key_bindings)
#-------------------------------------------------------------------------
# Returns any user preference information associated with the editor:
#-------------------------------------------------------------------------
def save_prefs(self):
""" Returns any user preference information associated with the editor.
"""
return {'key_bindings': self.factory.key_bindings}
# Define the simple, custom, text and readonly editors, which will be accessed
# by the editor factory for code editors.
CustomEditor = SimpleEditor = TextEditor = SourceEditor
class ReadonlyEditor(SourceEditor):
# Set the value of the readonly trait.
readonly = True
### EOF ##################################################################