Repository URL to install this package:
Version:
6.1.1 ▾
|
#------------------------------------------------------------------------------
#
# Copyright (c) 2005, 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: 10/21/2004
#
#------------------------------------------------------------------------------
""" Defines the various editors for single-selection enumerations, for the
wxPython user interface toolkit.
"""
#-------------------------------------------------------------------------
# Imports:
#-------------------------------------------------------------------------
from __future__ import absolute_import
import wx
from traits.api \
import Property
# FIXME: ToolkitEditorFactory is a proxy class defined here just for backward
# compatibility. The class has been moved to the
# traitsui.editors.drop_editor file.
from traitsui.editors.enum_editor \
import ToolkitEditorFactory
from .editor \
import Editor
from .constants \
import OKColor, ErrorColor
from .helper \
import enum_values_changed, TraitsUIPanel, disconnect, disconnect_no_id
from functools import reduce
# default formatting function (would import from string, but not in Python 3)
capitalize = lambda s: s.capitalize()
#-------------------------------------------------------------------------
# 'BaseEditor' class:
#-------------------------------------------------------------------------
class BaseEditor(Editor):
""" Base class for enumeration editors.
"""
#-------------------------------------------------------------------------
# Trait definitions:
#-------------------------------------------------------------------------
# Current set of enumeration names:
names = Property
# Current mapping from names to values:
mapping = Property
# Current inverse mapping from values to names:
inverse_mapping = Property
#-------------------------------------------------------------------------
# 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
if factory.name != '':
self._object, self._name, self._value = \
self.parse_extended_name(factory.name)
self.values_changed()
self._object.on_trait_change(self._values_changed,
' ' + self._name, dispatch='ui')
else:
factory.on_trait_change(self.rebuild_editor, 'values_modified',
dispatch='ui')
#-------------------------------------------------------------------------
# Gets the current set of enumeration names:
#-------------------------------------------------------------------------
def _get_names(self):
""" Gets the current set of enumeration names.
"""
if self._object is None:
return self.factory._names
return self._names
#-------------------------------------------------------------------------
# Gets the current mapping:
#-------------------------------------------------------------------------
def _get_mapping(self):
""" Gets the current mapping.
"""
if self._object is None:
return self.factory._mapping
return self._mapping
#-------------------------------------------------------------------------
# Gets the current inverse mapping:
#-------------------------------------------------------------------------
def _get_inverse_mapping(self):
""" Gets the current inverse mapping.
"""
if self._object is None:
return self.factory._inverse_mapping
return self._inverse_mapping
#-------------------------------------------------------------------------
# Rebuilds the contents of the editor whenever the original factory
# object's 'values' trait changes:
#-------------------------------------------------------------------------
def rebuild_editor(self):
""" Rebuilds the contents of the editor whenever the original factory
object's **values** trait changes.
"""
raise NotImplementedError
#-------------------------------------------------------------------------
# Recomputes the cached data based on the underlying enumeration model:
#-------------------------------------------------------------------------
def values_changed(self):
""" Recomputes the cached data based on the underlying enumeration model.
"""
self._names, self._mapping, self._inverse_mapping = \
enum_values_changed(self._value())
#-------------------------------------------------------------------------
# Handles the underlying object model's enumeration set being changed:
#-------------------------------------------------------------------------
def _values_changed(self):
""" Handles the underlying object model's enumeration set being changed.
"""
self.values_changed()
self.rebuild_editor()
#-------------------------------------------------------------------------
# Disposes of the contents of an editor:
#-------------------------------------------------------------------------
def dispose(self):
""" Disposes of the contents of an editor.
"""
if self._object is not None:
self._object.on_trait_change(self._values_changed,
' ' + self._name, remove=True)
else:
self.factory.on_trait_change(self.rebuild_editor,
'values_modified', remove=True)
super(BaseEditor, self).dispose()
#-------------------------------------------------------------------------
# 'SimpleEditor' class:
#-------------------------------------------------------------------------
class SimpleEditor(BaseEditor):
""" Simple style of enumeration editor, which displays a combo box.
"""
#-------------------------------------------------------------------------
# Finishes initializing the editor by creating the underlying toolkit
# widget:
#-------------------------------------------------------------------------
def init(self, parent):
""" Finishes initializing the editor by creating the underlying toolkit
widget.
"""
super(SimpleEditor, self).init(parent)
factory = self.factory
if factory.evaluate is None:
self.control = control = wx.Choice(parent, -1, wx.Point(0, 0),
wx.Size(-1, -1), self.names)
wx.EVT_CHOICE(parent, self.control.GetId(), self.update_object)
else:
self.control = control = wx.ComboBox(parent, -1, '',
wx.Point(0, 0), wx.Size(-1, -1), self.names,
style=wx.CB_DROPDOWN)
wx.EVT_COMBOBOX(parent, control.GetId(), self.update_object)
wx.EVT_TEXT_ENTER(parent, control.GetId(),
self.update_text_object)
wx.EVT_KILL_FOCUS(control, self.on_kill_focus)
if (not factory.is_grid_cell) and factory.auto_set:
wx.EVT_TEXT(parent, control.GetId(), self.update_text_object)
self._no_enum_update = 0
self.set_tooltip()
def dispose(self):
""" Disposes of the contents of an editor.
"""
disconnect(self.control,
wx.EVT_COMBOBOX, wx.EVT_TEXT_ENTER, wx.EVT_TEXT)
disconnect_no_id(self.control, wx.EVT_KILL_FOCUS)
super(SimpleEditor, self).dispose()
#-------------------------------------------------------------------------
# Handles the user selecting a new value from the combo box:
#-------------------------------------------------------------------------
def update_object(self, event):
""" Handles the user selecting a new value from the combo box.
"""
self._no_enum_update += 1
try:
new_value = self.mapping[event.GetString()]
if new_value == self.value and self.factory.is_grid_cell:
# If the enum editor is in a grid cell and the value did not
# change, we want the enum editor to go away, reverting back to
# the normal cell appearance. This is for 2 reasons:
# 1. it looks nicer
# 2. if the grid id suddenly closed, wx freaks & causes a
# segfault
grid = self.control.Parent.Parent
grid.EnableEditing(False)
grid.EnableEditing(True)
self.value = new_value
except:
from traitsui.api import raise_to_debug
raise_to_debug()
self._no_enum_update -= 1
#-------------------------------------------------------------------------
# Handles the user typing text into the combo box text entry field:
#-------------------------------------------------------------------------
def update_text_object(self, event):
""" Handles the user typing text into the combo box text entry field.
"""
if self._no_enum_update == 0:
value = self.control.GetValue()
try:
value = self.mapping[value]
except:
try:
value = self.factory.evaluate(value)
except Exception as excp:
self.error(excp)
return
self._no_enum_update += 1
try:
self.value = value
self.control.SetBackgroundColour(OKColor)
self.control.Refresh()
except:
pass
self._no_enum_update -= 1
#-------------------------------------------------------------------------
# Handles the control losing the keyboard focus:
#-------------------------------------------------------------------------
def on_kill_focus(self, event):
""" Handles the control losing the keyboard focus.
"""
self.update_text_object(event)
event.Skip()
#-------------------------------------------------------------------------
# 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.
"""
if self._no_enum_update == 0:
if self.factory.evaluate is None:
try:
self.control.SetStringSelection(
self.inverse_mapping[self.value])
except:
pass
else:
try:
self.control.SetValue(self.str_value)
except:
pass
#-------------------------------------------------------------------------
# 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()
#-------------------------------------------------------------------------
# Rebuilds the contents of the editor whenever the original factory
# object's 'values' trait changes:
#-------------------------------------------------------------------------
def rebuild_editor(self):
""" Rebuilds the contents of the editor whenever the original factory
object's **values** trait changes.
"""
# Note: This code is unnecessarily complex due to a strange bug in
# wxWidgets implementation of the wx.Combobox control that has strange
# behavior when the current text field value is one of the selection
# values when 'Clear' is called. In this case, even saving and
# restoring the text field value does not work, so we go to great
# lengths to detect this case and avoid using 'Clear', but still get
# the equivalent visual results. Modify this code at your own risk...
control = self.control
clear = True
cur_name = None
if self.factory.evaluate is not None:
n = control.GetCount()
cur_names = [control.GetString(i) for i in range(n)]
cur_name = control.GetValue()
if cur_name in self.names:
clear = False
include = True
for i in range(n - 1, -1, -1):
if cur_name == cur_names[i]:
include = False
else:
control.Delete(i)
for name in self.names:
if include or (name != cur_name):
control.Append(name)
cur_name = None
else:
point = control.GetInsertionPoint()
if clear:
control.Clear()
control.AppendItems(self.names)
if cur_name is not None:
self._no_enum_update += 1
control.SetValue(cur_name)
control.SetInsertionPoint(point)
self._no_enum_update -= 1
self.update_editor()
#-------------------------------------------------------------------------
# 'RadioEditor' class:
#-------------------------------------------------------------------------
class RadioEditor(BaseEditor):
""" Enumeration editor, used for the "custom" style, that displays radio
buttons.
"""
#-------------------------------------------------------------------------
# Finishes initializing the editor by creating the underlying toolkit
# widget:
#-------------------------------------------------------------------------
def init(self, parent):
""" Finishes initializing the editor by creating the underlying toolkit
widget.
"""
super(RadioEditor, self).init(parent)
# Create a panel to hold all of the radio buttons:
self.control = TraitsUIPanel(parent, -1)
self.rebuild_editor()
#-------------------------------------------------------------------------
# Handles the user clicking one of the 'custom' radio buttons:
#-------------------------------------------------------------------------
def update_object(self, event):
""" Handles the user clicking one of the custom radio buttons.
"""
try:
self.value = event.GetEventObject().value
except:
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.
"""
value = self.value
for button in self.control.GetChildren():
state = (button.value == value)
button.SetValue(state)
if state:
button.SetFocus()
#-------------------------------------------------------------------------
# Rebuilds the contents of the editor whenever the original factory
# object's 'values' trait changes:
#-------------------------------------------------------------------------
def rebuild_editor(self):
""" Rebuilds the contents of the editor whenever the original factory
object's **values** trait changes.
"""
# Clear any existing content:
panel = self.control
panel.SetSizer(None)
panel.DestroyChildren()
# Get the current trait value:
cur_name = self.str_value
# Create a sizer to manage the radio buttons:
names = self.names
mapping = self.mapping
n = len(names)
cols = self.factory.cols
rows = (n + cols - 1) / cols
incr = [n / cols] * cols
rem = n % cols
for i in range(cols):
incr[i] += (rem > i)
incr[-1] = -(reduce(lambda x, y: x + y, incr[:-1], 0) - 1)
if cols > 1:
sizer = wx.GridSizer(0, cols, 2, 4)
else:
sizer = wx.BoxSizer(wx.VERTICAL)
# Add the set of all possible choices:
style = wx.RB_GROUP
index = 0
for i in range(rows):
for j in range(cols):
if n > 0:
name = label = names[index]
label = self.string_value(label, capitalize)
control = wx.RadioButton(panel, -1, label, style=style)
control.value = mapping[name]
style = 0
control.SetValue(name == cur_name)
wx.EVT_RADIOBUTTON(panel, control.GetId(),
self.update_object)
self.set_tooltip(control)
index += incr[j]
n -= 1
else:
control = wx.RadioButton(panel, -1, '')
control.value = ''
control.Show(False)
sizer.Add(control, 0, wx.NORTH, 5)
# Set-up the layout:
panel.SetSizerAndFit(sizer)
#-------------------------------------------------------------------------
# 'ListEditor' class:
#-------------------------------------------------------------------------
class ListEditor(BaseEditor):
""" Enumeration editor, used for the "custom" style, that displays a list
box.
"""
#-------------------------------------------------------------------------
# Finishes initializing the editor by creating the underlying toolkit
# widget:
#-------------------------------------------------------------------------
def init(self, parent):
""" Finishes initializing the editor by creating the underlying toolkit
widget.
"""
super(ListEditor, self).init(parent)
# Create a panel to hold all of the radio buttons:
self.control = wx.ListBox(parent, -1, wx.Point(0, 0),
wx.Size(-1, -1), self.names,
style=wx.LB_SINGLE | wx.LB_NEEDED_SB)
wx.EVT_LISTBOX(parent, self.control.GetId(), self.update_object)
self.set_tooltip()
def dispose(self):
""" Disposes of the contents of an editor.
"""
disconnect(self.control, wx.EVT_LISTBOX)
super(ListEditor, self).dispose()
#-------------------------------------------------------------------------
# Handles the user selecting a list box item:
#-------------------------------------------------------------------------
def update_object(self, event):
""" Handles the user selecting a list box item.
"""
if not self._ignore_update:
value = self.control.GetStringSelection()
try:
value = self.mapping[value]
except:
try:
value = self.factory.evaluate(value)
except:
pass
try:
self.value = value
except:
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.
"""
control = self.control
try:
index = control.FindString(self.inverse_mapping[self.value])
if index >= 0:
control.SetSelection(index)
except:
pass
#-------------------------------------------------------------------------
# Rebuilds the contents of the editor whenever the original factory
# object's 'values' trait changes:
#-------------------------------------------------------------------------
def rebuild_editor(self):
""" Rebuilds the contents of the editor whenever the original factory
object's **values** trait changes.
"""
self._ignore_update = True
self.control.Clear()
self.control.AppendItems(self.names)
self._ignore_update = False
# fixme: Is this line necessary?
self.update_editor()
### EOF #######################################################################