1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168
|
# (C) Copyright 2005-2025 Enthought, Inc., Austin, TX
# All rights reserved.
#
# This software is provided without warranty under the terms of the BSD
# license included in 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!
""" A binding between a trait on an object and a preference value. """
from ast import literal_eval
# Enthought library imports.
from traits.api import Any, HasTraits, Instance, Str, Undefined
# Local imports.
from .i_preferences import IPreferences
from .package_globals import get_default_preferences
class PreferenceBinding(HasTraits):
""" A binding between a trait on an object and a preference value. """
#### 'PreferenceBinding' interface ########################################
# The object that we are binding the preference to.
obj = Any
# The preferences node used by the binding. If this trait is not set then
# the package-global default preferences node is used (and if that is not
# set then the binding won't work ;^)
preferences = Instance(IPreferences)
# The path to the preference value.
preference_path = Str
# The name of the trait that we are binding the preference to.
trait_name = Str
###########################################################################
# 'object' interface.
###########################################################################
def __init__(self, **traits):
""" Constructor. """
super(PreferenceBinding, self).__init__(**traits)
# Initialize the object's trait from the preference value.
self._set_trait(notify=False)
# Wire-up trait change handlers etc.
self._initialize()
###########################################################################
# 'PreferenceBinding' interface.
###########################################################################
#### Trait initializers ###################################################
def _preferences_default(self):
""" Trait initializer. """
return get_default_preferences()
###########################################################################
# Private interface.
###########################################################################
#### Trait change handlers ################################################
def _on_trait_changed(self, event):
""" Dynamic trait change handler. """
self.preferences.set(self.preference_path, event.new)
#### Other observer pattern listeners #####################################
def _preferences_listener(self, node, key, old, new):
""" Listener called when a preference value is changed. """
components = self.preference_path.split(".")
if key == components[-1]:
self._set_trait()
#### Methods ##############################################################
# fixme: This method is mostly duplicated in 'PreferencesHelper' (the only
# difference is the line that gets the handler).
def _get_value(self, trait_name, value):
"""Get the actual value to set.
This method makes sure that any required work is done to convert the
preference value from a string.
"""
handler = self.obj.trait(trait_name).handler
# If the trait type is 'Str' then we just take the raw value.
if type(handler) is Str:
pass
# Otherwise, we literal_eval it! This is safe against arbitrary code
# execution, but it does limit values to core Python data types.
else:
try:
value = literal_eval(value)
# If the eval fails then there is probably a syntax error, but
# we will let the handler validation throw the exception.
except Exception:
pass
return handler.validate(self, trait_name, value)
def _initialize(self):
""" Wire-up trait change handlers etc. """
# Listen for the object's trait being changed.
self.obj.observe(self._on_trait_changed, self.trait_name)
# Listen for the preference value being changed.
components = self.preference_path.split(".")
node = ".".join(components[:-1])
self.preferences.add_preferences_listener(
self._preferences_listener, node
)
def _set_trait(self, notify=True):
""" Set the object's trait to the value of the preference. """
value = self.preferences.get(self.preference_path, Undefined)
if value is not Undefined:
trait_value = self._get_value(self.trait_name, value)
traits = {self.trait_name: trait_value}
self.obj.trait_set(trait_change_notify=notify, **traits)
# Factory function for creating bindings.
def bind_preference(obj, trait_name, preference_path, preferences=None):
""" Create a new preference binding. """
# This may seem a bit wierd, but we manually build up a dictionary of
# the traits that need to be set at the time the 'PreferenceBinding'
# instance is created.
#
# This is because we only want to set the 'preferences' trait iff one
# is explicitly specified. If we passed it in with the default argument
# value of 'None' then it counts as 'setting' the trait which prevents
# the binding instance from defaulting to the package-global preferences.
# Also, if we try to set the 'preferences' trait *after* construction time
# then it is too late as the binding initialization is done in the
# constructor (we could of course split that out, which may be the 'right'
# way to do it ;^).
traits = {
"obj": obj,
"trait_name": trait_name,
"preference_path": preference_path,
}
if preferences is not None:
traits["preferences"] = preferences
return PreferenceBinding(**traits)
|