Source code for apptools.selection.selection_service

# (C) Copyright 2005-2024 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!
from traits.api import Dict, HasTraits

from apptools.selection.errors import (
    ProviderNotRegisteredError,
    IDConflictError,
    ListenerNotConnectedError,
)


[docs]class SelectionService(HasTraits): """The selection service connects selection providers and listeners. The selection service is a register of selection providers, i.e., objects that publish their current selection. Selections can be requested actively, by explicitly requesting the current selection in a provider (:meth:`get_selection(id)`), or passively by connecting selection listeners. """ #### 'SelectionService' protocol ##########################################
[docs] def add_selection_provider(self, provider): """Add a selection provider. The provider is identified by its ID. If a provider with the same ID has been already registered, an :class:`~.IDConflictError` is raised. Arguments: provider -- ISelectionProvider The selection provider added to the internal registry. """ provider_id = provider.provider_id if self.has_selection_provider(provider_id): raise IDConflictError(provider_id=provider_id) self._providers[provider_id] = provider if provider_id in self._listeners: self._connect_all_listeners(provider_id)
[docs] def has_selection_provider(self, provider_id): """ Has a provider with the given ID been registered? """ return provider_id in self._providers
[docs] def remove_selection_provider(self, provider): """Remove a selection provider. If the provider has not been registered, a :class:`~.ProviderNotRegisteredError` is raised. Arguments: provider -- ISelectionProvider The selection provider added to the internal registry. """ provider_id = provider.provider_id self._raise_if_not_registered(provider_id) if provider_id in self._listeners: self._disconnect_all_listeners(provider_id) del self._providers[provider_id]
[docs] def get_selection(self, provider_id): """Return the current selection of the provider with the given ID. If a provider with that ID has not been registered, a :class:`~.ProviderNotRegisteredError` is raised. Arguments: provider_id -- str The selection provider ID. Returns: selection -- ISelection The current selection of the provider. """ self._raise_if_not_registered(provider_id) provider = self._providers[provider_id] return provider.get_selection()
[docs] def set_selection(self, provider_id, items, ignore_missing=False): """Set the current selection in a provider to the given items. If a provider with the given ID has not been registered, a :class:`~.ProviderNotRegisteredError` is raised. If ``ignore_missing`` is ``True``, items that are not available in the selection provider are silently ignored. If it is ``False`` (default), a :class:`ValueError` should be raised. Arguments: provider_id -- str The selection provider ID. items -- list List of items to be selected. ignore_missing -- bool If ``False`` (default), the provider raises an exception if any of the items in ``items`` is not available to be selected. Otherwise, missing elements are silently ignored, and the rest is selected. """ self._raise_if_not_registered(provider_id) provider = self._providers[provider_id] return provider.set_selection(items, ignore_missing=ignore_missing)
[docs] def connect_selection_listener(self, provider_id, func): """Connect a listener to selection events from a specific provider. The signature if the listener callback is ``func(i_selection)``. The listener is called: 1) When a provider with the given ID is registered, with its initial selection as argument, or 2) whenever the provider fires a selection event. It is perfectly valid to connect a listener before a provider with the given ID is registered. The listener will remain connected even if the provider is repeatedly connected and disconnected. Arguments: provider_id -- str The selection provider ID. func -- callable(i_selection) A callable object that is notified when the selection changes. """ self._listeners.setdefault(provider_id, []) self._listeners[provider_id].append(func) if self.has_selection_provider(provider_id): self._toggle_listener(provider_id, func, remove=False)
[docs] def disconnect_selection_listener(self, provider_id, func): """Disconnect a listener from a specific provider. Arguments: provider_id -- str The selection provider ID. func -- callable(provider_id, i_selection) A callable object that is notified when the selection changes. """ if self.has_selection_provider(provider_id): self._toggle_listener(provider_id, func, remove=True) try: self._listeners[provider_id].remove(func) except (ValueError, KeyError): raise ListenerNotConnectedError( provider_id=provider_id, listener=func )
#### Private protocol ##################################################### _listeners = Dict() _providers = Dict() def _toggle_listener(self, provider_id, func, remove): provider = self._providers[provider_id] provider.on_trait_change(func, "selection", remove=remove) def _connect_all_listeners(self, provider_id): """Connect all listeners connected to a provider. As soon as they are connected, they receive the initial selection. """ provider = self._providers[provider_id] selection = provider.get_selection() for func in self._listeners[provider_id]: self._toggle_listener(provider_id, func, remove=False) # FIXME: make this robust to notifications that raise exceptions. # Can we send the error to the traits exception hook? func(selection) def _disconnect_all_listeners(self, provider_id): for func in self._listeners[provider_id]: self._toggle_listener(provider_id, func, remove=True) def _raise_if_not_registered(self, provider_id): if not self.has_selection_provider(provider_id): raise ProviderNotRegisteredError(provider_id=provider_id)