Source code for apptools.preferences.preference_binding

# (C) Copyright 2005-2022 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


[docs]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.
[docs]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)