def test_extension_point_changed(self): """ extension point changed """ registry = self.registry # Add an extension point. registry.add_extension_point(self._create_extension_point('my.ep')) # Declare a class that consumes the extension. class Foo(TestBase): x = ExtensionPoint(id='my.ep') def _x_changed(self): """ Static trait change handler. """ self.x_changed_called = True return f = Foo() # Connect the extension points on the object so that it can listen # for changes. ExtensionPoint.connect_extension_point_traits(f) # Set the extensions. registry.set_extensions('my.ep', [42, 'a string', True]) # Make sure that instances of the class pick up the extensions. self.assertEqual(3, len(f.x)) self.assertEqual([42, 'a string', True], f.x) # Make sure the trait change handler was called. self.assert_(f.x_changed_called) # Reset the change handler flag. f.x_changed_called = False # Disconnect the extension points on the object. ExtensionPoint.disconnect_extension_point_traits(f) # Set the extensions. registry.set_extensions('my.ep', [98, 99, 100]) # Make sure the trait change handler was *not* called. self.assertEqual(False, f.x_changed_called) return
class PluginA(Plugin): id = "A" x = ExtensionPoint(List(Int), id="x") def _x_items_changed(self, event): self.added = event.added self.removed = event.removed
class HelloWorld(Plugin): """ The 'Hello World' plugin. This plugin offers a single extension point 'greetings' which is a list of greetings, one of which is used to produce the '<greeting> World' message when the plugin is started. """ # This tells us that the plugin offers the 'greetings' extension point, # and that plugins that want to contribute to it must each provide a list # of strings (Str). greetings = ExtensionPoint(List(Str), id="greetings", desc='Greetings for "Hello World"') # Plugin's have two important lifecyle methods, 'start' and 'stop'. These # methods are called automatically by Envisage when the application is # started and stopped respectively. def start(self): """ Start the plugin. """ # Standard library imports. # # We put this import here just to emphasize that it is only used in # this specific plugin. import random print("{} World!".format(random.choice(self.greetings)))
def test_invalid_extension_point_type(self): """ invalid extension point type """ # Extension points currently have to be 'List's of something! The # default is a list of anything. with self.assertRaises(TypeError): ExtensionPoint(Int, "my.ep")
class Foo(TestBase): x = ExtensionPoint(id="my.ep") def _x_changed(self): """ Static trait change handler. """ self.x_changed_called = True
class FlowTaskPlugin(Plugin): """ An Envisage plugin wrapping FlowTask """ # Extension point IDs. PREFERENCES = 'envisage.preferences' PREFERENCES_PANES = 'envisage.ui.tasks.preferences_panes' TASKS = 'envisage.ui.tasks.tasks' # these need to be declared in a Plugin instance; we pass them to # the task instance thru its factory, below. op_plugins = ExtensionPoint(List(IOperationPlugin), OP_PLUGIN_EXT) view_plugins = ExtensionPoint(List(IViewPlugin), VIEW_PLUGIN_EXT) #### 'IPlugin' interface ################################################## # The plugin's unique identifier. id = 'edu.mit.synbio.cytoflow' # The plugin's name (suitable for displaying to the user). name = 'Cytoflow' ########################################################################### # Protected interface. ########################################################################### @contributes_to(PREFERENCES) def _get_preferences(self): filename = os.path.join(os.path.dirname(__file__), 'preferences.ini') return [ 'file://' + filename ] @contributes_to(PREFERENCES_PANES) def _get_preferences_panes(self): from .preferences import CytoflowPreferencesPane return [CytoflowPreferencesPane] @contributes_to(TASKS) def _get_tasks(self): return [TaskFactory(id = 'edu.mit.synbio.cytoflowgui.flow_task', name = 'Cytometry analysis', factory = lambda **x: FlowTask(application = self.application, op_plugins = self.op_plugins, view_plugins = self.view_plugins, model = self.application.model, filename = self.application.filename, **x))]
class FileTypePlugin(Plugin): """ Plugin for identifying file types """ # The Ids of the extension points that this plugin offers. RECOGNIZER = 'omnivore.file_recognizer' # Extension point IDs. SERVICE_OFFERS = 'envisage.service_offers' #### 'IPlugin' interface ################################################## # The plugin's unique identifier. id = 'omnivore.file_type.plugin' # The plugin's name (suitable for displaying to the user). name = 'File Type' #### Extension points offered by this plugin ############################## recognizers = ExtensionPoint(List(Instance(IFileRecognizer)), id=RECOGNIZER, desc=""" This extension point allows you to contribute file scanners that determine MIME types from a byte stream. """) #### Contributions to extension points made by this plugin ################ service_offers = List(contributes_to=SERVICE_OFFERS) ########################################################################### # Protected interface. ########################################################################### def _service_offers_default(self): """ Trait initializer. """ log.debug("in _service_offers_default") offer1 = ServiceOffer( protocol= 'omnivore.file_type.i_file_recognizer.IFileRecognizerDriver', factory=self._create_file_recognizer_driver_service) return [offer1] def _create_file_recognizer_driver_service(self): """ Factory method for the File Recognizer Driver service. """ log.debug("known recognizers: %s" % str(self.recognizers)) # Lazy importing, even though this is a fundamental service and # therefore doesn't buy us anything. But as an example it's useful. from .driver import FileRecognizerDriver return FileRecognizerDriver(recognizers=self.recognizers, application=self.application)
def test_extension_point_changed(self): """ extension point changed """ registry = self.registry # Add an extension point. registry.add_extension_point(self._create_extension_point("my.ep")) # Declare a class that consumes the extension. class Foo(TestBase): x = ExtensionPoint(id="my.ep") def _x_changed(self): """ Static trait change handler. """ self.x_changed_called = True f = Foo() # Connect the extension points on the object so that it can listen # for changes. ExtensionPoint.connect_extension_point_traits(f) # Set the extensions. registry.set_extensions("my.ep", [42, "a string", True]) # Make sure that instances of the class pick up the extensions. self.assertEqual(3, len(f.x)) self.assertEqual([42, "a string", True], f.x) # Make sure the trait change handler was called. self.assertTrue(f.x_changed_called) # Reset the change handler flag. f.x_changed_called = False # Disconnect the extension points on the object. ExtensionPoint.disconnect_extension_point_traits(f) # Set the extensions. registry.set_extensions("my.ep", [98, 99, 100]) # Make sure the trait change handler was *not* called. self.assertEqual(False, f.x_changed_called)
def setUp(self): # We do all of the testing via the application to make sure it offers # the same interface! registry = Application(extension_registry=ExtensionRegistry()) extension_point = ExtensionPoint(id="my.ep", trait_type=List()) registry.add_extension_point(extension_point) self.registry = registry # A place to record events that listeners receive. self.events = []
class CorePyFibrePlugin(CorePlugin): """Inherits from the Envisage CorePlugin to include extra extension points for classes that fulfil the IMultiImageFactory interface""" id = 'pyfibre.core.pyfibre_plugin' #: Extension points for IMultiImageFactory multi_image_factories = ExtensionPoint(List(IMultiImageFactory), id=MULTI_IMAGE_FACTORIES)
class IPythonKernelPlugin(Plugin): """ An IPython kernel plugin. """ # Extension point IDs. SERVICE_OFFERS = 'envisage.service_offers' #### 'IPlugin' interface ################################################## # The plugin unique identifier. id = 'envisage.plugins.ipython_kernel' # The plugin name (suitable for displaying to the user). name = 'IPython embedded kernel plugin' def stop(self): logger.info('Shutting down the embedded ipython kernel') self.kernel.shutdown() #### Extension points offered by this plugin ############################## kernel_namespace = ExtensionPoint(List, id=IPYTHON_NAMESPACE, desc=""" Variables to add to the IPython kernel namespace. This is a list of tuples (name, value). """) #### Contributions to extension points made by this plugin ################ kernel = Instance(IPYTHON_KERNEL_PROTOCOL) service_offers = List(contributes_to=SERVICE_OFFERS) def _service_offers_default(self): ipython_kernel_service_offer = ServiceOffer( protocol=IPYTHON_KERNEL_PROTOCOL, factory=self._create_kernel, ) return [ipython_kernel_service_offer] def _create_kernel(self): return self.kernel #### Trait initializers ################################################### def _kernel_default(self): from .internal_ipkernel import InternalIPKernel kernel = InternalIPKernel() bind_extension_point(kernel, 'initial_namespace', IPYTHON_NAMESPACE, self.application) return kernel
class EnvisagePythonShellTask(PythonShellTask): """ Subclass of PythonShellTask that gets its bindings and commands from an Envisage ExtensionPoint """ id='envisage.plugins.tasks.python_shell_task' # ExtensionPointUser interface extension_registry = Property(Instance(IExtensionRegistry)) # The list of bindings for the shell bindings = ExtensionPoint(id=BINDINGS) # The list of commands to run on shell startup commands = ExtensionPoint(id=COMMANDS) # property getter/setters def _get_extension_registry(self): if self.window is not None: return self.window.application return None
class ByteViewersPlugin(FrameworkPlugin): """ Plugin containing all the viewers for byte data """ # Extension point IDs. VIEWERS = 'omnivore8bit.viewers' #### 'IPlugin' interface ################################################## # The plugin's unique identifier. id = 'omnivore8bit.viewer.plugin' # The plugin's name (suitable for displaying to the user). name = 'Omnivore Byte Viewers' #### Contributions to extension points made by this plugin ################ viewers = List(contributes_to=VIEWERS) segment_viewers = ExtensionPoint( List(Instance(SegmentViewer)), id="omnivore8bit.viewers", desc="A list of SegmentViewers that can display the data in a segment") def _viewers_default(self): # from omnivore8bit.viewers.bitmap import MemoryMapViewer from omnivore8bit.viewers.bitmap2 import BitmapViewer from omnivore8bit.viewers.char2 import CharViewer from omnivore8bit.viewers.cpu2 import DisassemblyViewer from omnivore8bit.viewers.hex2 import HexEditViewer from omnivore8bit.viewers.info import CommentsViewer, UndoViewer, SegmentListViewer from omnivore8bit.viewers.map2 import MapViewer from omnivore8bit.viewers.tile import TileViewer from omnivore8bit.viewers.jumpman2 import JumpmanViewer, TriggerPaintingViewer, LevelSummaryViewer from omnivore8bit.viewers.emulator import Atari800Viewer, CPU6502Viewer, ANTICViewer, POKEYViewer, GTIAViewer, PIAViewer return [ BitmapViewer, CharViewer, DisassemblyViewer, HexEditViewer, CommentsViewer, UndoViewer, SegmentListViewer, MapViewer, TileViewer, JumpmanViewer, TriggerPaintingViewer, LevelSummaryViewer, Atari800Viewer, CPU6502Viewer, ANTICViewer, POKEYViewer, GTIAViewer, PIAViewer ]
class PluginE(Plugin): """ Another plugin that expects to be started before contributing to extension points. """ id = "E" x = ExtensionPoint(List, id="e.x") y = List(Int, contributes_to="d.x") started = Bool(False) def start(self): self.started = True def _y_default(self): if self.started: return [1, 2, 3] else: return []
class IPythonKernelPlugin(Plugin): """ An IPython kernel plugin. """ #: The plugin unique identifier. id = 'envisage.plugins.ipython_kernel' #: The plugin name (suitable for displaying to the user). name = 'IPython embedded kernel plugin' #: Extension point for objects contributed to the IPython kernel namespace. kernel_namespace = ExtensionPoint(List, id=IPYTHON_NAMESPACE, desc=""" Variables to add to the IPython kernel namespace. This is a list of tuples (name, value). """) #: Service offers contributed by this plugin. service_offers = List(contributes_to=SERVICE_OFFERS) #: Whether to initialize the kernel when the service is created. #: The default is ``False```, for backwards compatibility. It will change #: to ``True`` in a future version of Envisage. External users wanting #: to use the future behaviour now should pass ``init_ipkernel=True`` #: when creating the plugin. init_ipkernel = Bool(False) def stop(self): """ Stop the plugin. """ self._destroy_kernel() # Private traits and methods #: The InternalIPKernel instance provided by the service. _kernel = Instance(IPYTHON_KERNEL_PROTOCOL) def _create_kernel(self): from .internal_ipkernel import InternalIPKernel # This shouldn't happen with a normal lifecycle, but add a warning # just in case. if self._kernel is not None: warnings.warn( "A kernel already exists. " "No new kernel will be created.", RuntimeWarning, ) return logger.debug("Creating the embedded IPython kernel") kernel = self._kernel = InternalIPKernel() bind_extension_point(kernel, 'initial_namespace', IPYTHON_NAMESPACE, self.application) if self.init_ipkernel: kernel.init_ipkernel() else: warnings.warn( ("In the future, the IPython kernel will be initialized " "automatically at creation time. To enable this " "future behaviour now, create the plugin using " "IPythonKernelPlugin(init_ipkernel=True)"), DeprecationWarning, ) return kernel def _destroy_kernel(self): """ Destroy any existing kernel. """ if self._kernel is None: return logger.debug("Shutting down the embedded IPython kernel") self._kernel.shutdown() self._kernel = None def _service_offers_default(self): ipython_kernel_service_offer = ServiceOffer( protocol=IPYTHON_KERNEL_PROTOCOL, factory=self._create_kernel, ) return [ipython_kernel_service_offer]
class PythonShellView(View): """ A view containing an interactive Python shell. """ #### 'IView' interface #################################################### # The part's globally unique identifier. id = 'envisage.plugins.python_shell_view' # The part's name (displayed to the user). name = 'Python' # The default position of the view relative to the item specified in the # 'relative_to' trait. position = 'bottom' #### 'PythonShellView' interface ########################################## # The interpreter's namespace. namespace = Property(DictStrAny) # The names bound in the interpreter's namespace. names = Property # Original value for 'sys.stdout': original_stdout = Any # Stdout text is posted to this event stdout_text = Event #### 'IExtensionPointUser' interface ###################################### # The extension registry that the object's extension points are stored in. extension_registry = Property(Instance(IExtensionRegistry)) #### Private interface #################################################### # Bindings. _bindings = ExtensionPoint(id='envisage.plugins.python_shell.bindings') # Commands. _commands = ExtensionPoint(id='envisage.plugins.python_shell.commands') ########################################################################### # 'IExtensionPointUser' interface. ########################################################################### def _get_extension_registry(self): """ Trait property getter. """ return self.window.application ########################################################################### # 'View' interface. ########################################################################### def create_control(self, parent): """ Creates the toolkit-specific control that represents the view. """ self.shell = shell = PythonShell(parent) shell.on_trait_change(self._on_key_pressed, 'key_pressed') shell.on_trait_change(self._on_command_executed, 'command_executed') # Write application standard out to this shell instead of to DOS window self.on_trait_change( self._on_write_stdout, 'stdout_text', dispatch='ui' ) self.original_stdout = sys.stdout sys.stdout = PseudoFile(self._write_stdout) # Namespace contributions. for bindings in self._bindings: for name, value in bindings.items(): self.bind(name, value) for command in self._commands: self.execute_command(command) # We take note of the starting set of names and types bound in the # interpreter's namespace so that we can show the user what they have # added or removed in the namespace view. self._namespace_types = set((name, type(value)) for name, value in \ self.namespace.items()) # Register the view as a service. app = self.window.application self._service_id = app.register_service(IPythonShell, self) return self.shell.control def destroy_control(self): """ Destroys the toolkit-specific control that represents the view. """ super(PythonShellView, self).destroy_control() # Unregister the view as a service. self.window.application.unregister_service(self._service_id) # Remove the sys.stdout handlers. self.on_trait_change( self._on_write_stdout, 'stdout_text', remove=True ) # Restore the original stdout. sys.stdout = self.original_stdout return ########################################################################### # 'PythonShellView' interface. ########################################################################### #### Properties ########################################################### def _get_namespace(self): """ Property getter. """ return self.shell.interpreter().locals def _get_names(self): """ Property getter. """ return list(self.shell.interpreter().locals.keys()) #### Methods ############################################################## def bind(self, name, value): """ Binds a name to a value in the interpreter's namespace. """ self.shell.bind(name, value) return def execute_command(self, command, hidden=True): """ Execute a command in the interpreter. """ return self.shell.execute_command(command, hidden) def execute_file(self, path, hidden=True): """ Execute a command in the interpreter. """ return self.shell.execute_file(path, hidden) def lookup(self, name): """ Returns the value bound to a name in the interpreter's namespace. """ return self.shell.interpreter().locals[name] ########################################################################### # Private interface. ########################################################################### def _write_stdout(self, text): """ Handles text written to stdout. """ self.stdout_text = text return #### Trait change handlers ################################################ def _on_command_executed(self, shell): """ Dynamic trait change handler. """ if self.control is not None: # Get the set of tuples of names and types in the current namespace. namespace_types = set((name, type(value)) for name, value in \ self.namespace.items()) # Figure out the changes in the namespace, if any. added = namespace_types.difference(self._namespace_types) removed = self._namespace_types.difference(namespace_types) # Cache the new list, to use for comparison next time. self._namespace_types = namespace_types # Fire events if there are change. if len(added) > 0 or len(removed) > 0: self.trait_property_changed('namespace', {}, self.namespace) self.trait_property_changed('names', [], self.names) return def _on_key_pressed(self, event): """ Dynamic trait change handler. """ if event.alt_down and event.key_code == 317: zoom = self.shell.control.GetZoom() if zoom != 20: self.shell.control.SetZoom(zoom+1) elif event.alt_down and event.key_code == 319: zoom = self.shell.control.GetZoom() if zoom != -10: self.shell.control.SetZoom(zoom-1) return def _on_write_stdout(self, text): """ Dynamic trait change handler. """ self.shell.control.write(text) return
class LoggerPlugin(Plugin): """Logger plugin.""" id = ID name = "Logger plugin" #### Extension points for this plugin ##################################### MAIL_FILES = "apptools.logger.plugin.mail_files" mail_files = ExtensionPoint( List(Callable), id=MAIL_FILES, desc=""" This extension point allows you to contribute functions which will be called to add project files to the zip file that the user mails back with bug reports from the Quality Agent. The function will be passed a zipfile.ZipFile object. """, ) #### Contributions to extension points made by this plugin ################ PREFERENCES = "envisage.preferences" PREFERENCES_PAGES = "envisage.ui.workbench.preferences_pages" VIEWS = "envisage.ui.workbench.views" preferences = List(contributes_to=PREFERENCES) preferences_pages = List(contributes_to=PREFERENCES_PAGES) views = List(contributes_to=VIEWS) def _preferences_default(self): return ["pkgfile://%s/plugin/preferences.ini" % ID] def _preferences_pages_default(self): from apptools.logger.plugin.view.logger_preferences_page import ( LoggerPreferencesPage, ) return [LoggerPreferencesPage] def _views_default(self): return [self._logger_view_factory] #### Plugin interface ##################################################### def start(self): """Starts the plugin.""" preferences = LoggerPreferences() service = LoggerService(application=self.application, preferences=preferences) formatter = logging.Formatter("%(levelname)s|%(asctime)s|%(message)s") handler = LogQueueHandler() handler.setLevel(preferences.level_) handler.setFormatter(formatter) root_logger = logging.getLogger() root_logger.addHandler(handler) root_logger.setLevel(preferences.level_) service.handler = handler self.application.register_service(ILOGGER, service) def stop(self): """Stops the plugin.""" service = self.application.get_service(ILOGGER) service.save_preferences() #### LoggerPlugin private interface ####################################### def _logger_view_factory(self, **traits): from apptools.logger.plugin.view.logger_view import LoggerView service = self.application.get_service(ILOGGER) view = LoggerView(service=service, **traits) # Record the created view on the service. service.plugin_view = view return view
class Foo(TestBase): x = ExtensionPoint(id="my.ep")
class PythonShellPlugin(Plugin): """ A tasks plugin to display a simple Python shell to the user. """ # Extension point IDs. BINDINGS = BINDINGS COMMANDS = COMMANDS TASKS = "envisage.ui.tasks.tasks" #### 'IPlugin' interface ################################################## # The plugin's unique identifier. id = "envisage.plugins.tasks.python_shell_plugin" # The plugin's name (suitable for displaying to the user). name = "Python Shell" #### Extension points exposed by this plugin ############################## bindings = ExtensionPoint( List(Dict), id=BINDINGS, desc=""" This extension point allows you to contribute name/value pairs that will be bound when the interactive Python shell is started. e.g. Each item in the list is a dictionary of name/value pairs:: {'x' : 10, 'y' : ['a', 'b', 'c']} """, ) commands = ExtensionPoint( List(Str), id=COMMANDS, desc=""" This extension point allows you to contribute commands that are executed when the interactive Python shell is started. e.g. Each item in the list is a string of arbitrary Python code:: 'import os, sys' 'from traits.api import *' Yes, I know this is insecure but it follows the usual Python rule of 'we are all consenting adults'. """, ) #### Contributions to extension points made by this plugin ################ # Bindings. contributed_bindings = List(contributes_to=BINDINGS) tasks = List(contributes_to=TASKS) ########################################################################### # Protected interface. ########################################################################### def start(self): logger.debug("started python shell plugin") def _contributed_bindings_default(self): """ By default, expose the Envisage application object to the namespace """ return [{"application": self.application}] def _tasks_default(self): return [ TaskFactory( id="envisage.plugins.tasks.python_shell_task", name="Python Shell", factory=EnvisagePythonShellTask, ), ]
class Foo(HasTraits): x = ExtensionPoint(List(Int), id="my.ep")
class MOTDPlugin(Plugin): """ The 'Message of the Day' plugin. This plugin simply prints the 'Message of the Day' to stdout. """ # The Ids of the extension points that this plugin offers. MESSAGES = "acme.motd.messages" # The Ids of the extension points that this plugin contributes to. SERVICE_OFFERS = "envisage.service_offers" #### 'IPlugin' interface ################################################## # The plugin's unique identifier. id = "acme.motd" # The plugin's name (suitable for displaying to the user). name = "MOTD" #### Extension points offered by this plugin ############################## # The messages extension point. # # Notice that we use the string name of the 'IMessage' interface rather # than actually importing it. This makes sure that the import only happens # when somebody actually gets the contributions to the extension point. messages = ExtensionPoint( List(Instance("acme.motd.api.IMessage")), id=MESSAGES, desc=""" This extension point allows you to contribute messages to the 'Message Of The Day'. """, ) #### Contributions to extension points made by this plugin ################ service_offers = List(contributes_to=SERVICE_OFFERS) def _service_offers_default(self): """ Trait initializer. """ # Register the protocol as a string containing the actual module path # and *not* a module path that goes via an 'api.py' file as this does # not match what Python thinks the module is! This allows the service # to be looked up by passing either the exact same string, or the # actual protocol object itself (the latter is the preferred way of # doing it!). motd_service_offer = ServiceOffer( protocol="acme.motd.i_motd.IMOTD", factory=self._create_motd_service, ) return [motd_service_offer] ########################################################################### # Private interface. ########################################################################### def _create_motd_service(self): """ Factory method for the 'MOTD' service. """ # Only do imports when you need to! This makes sure that the import # only happens when somebody needs an 'IMOTD' service. from acme.motd.motd import MOTD return MOTD(messages=self.messages) # This plugin does all of its work in this method which gets called when # the application has started all of its plugins. @on_trait_change("application:started") def _print_motd(self): """ Print the 'Message of the Day' to stdout! """ # Note that we always offer the service via its name, but look it up # via the actual protocol. from acme.motd.api import IMOTD # Lookup the MOTD service. motd = self.application.get_service(IMOTD) # Get the message of the day... message = motd.motd() # ... and print it. print('\n"%s"\n\n- %s' % (message.text, message.author))
class Foo(TestBase): x = ExtensionPoint(List(Int), id="my.ep")
class ConnectomeFile2Plugin(Plugin): # Extension points we contribute to. VIEWS = 'envisage.ui.workbench.views' #- for application scope and SERVICE_OFFERS = 'envisage.service_offers' #### Extension points offered by this plugin ############################## CFILE = 'cviewer.plugins.cff2.cfile' # The connectome file extension point. cfiles = ExtensionPoint( List( ), id=CFILE, desc=""" This extension point allows you to contribute files in the connectome file format, e.g. adding template atlases """ ) # The plugin's unique identifier. id = 'connectome.file' # The plugin's name (suitable for displaying to the user). name = 'Connectome File' # Services we contribute. service_offers = List(contributes_to=SERVICE_OFFERS) # Views. views = List(contributes_to=VIEWS) # it contributes at the same time one cfile to the extension point! cfile = List(contributes_to=CFILE) ##################################################################### # Private methods ##################################################################### def _cfile_default(self): """ Creates the dummy cfile contributed """ return self._get_cff_service() # a service offer looks up a corresponding # file from the extension point, and then can be used # below in the cfile_view_factory to display it. # ...e.g. open a cff just changes its contents. # and this makes also window available in the cfile for service registry lookup def _service_offers_default(self): """ Trait initializer. """ cfile_service_offer = ServiceOffer( protocol = 'cviewer.plugins.cff2.cfile.CFile', factory = 'cviewer.plugins.cff2.cfile.CFile' ) return [cfile_service_offer] def _get_cff_service(self): """ Looks up a cfile from the application service offers to be used by the view factory """ return self.application.get_service('cviewer.plugins.cff2.cfile.CFile') def _views_default(self): """ Trait initializer. """ return [self._cfile_view_factory] def _cfile_view_factory(self, window, **traits): """ Factory method for connectome file views. """ from pyface.workbench.traits_ui_view import \ TraitsUIView from cviewer.plugins.cff2.ui.cff_view import CFFView cfile = self._get_cff_service() cfile._workbenchwin = window cff_view = CFFView(cfile=cfile) tui_engine_view = TraitsUIView(obj=cff_view, id=CFFVIEW, name='Connectome File View', window=window, position='left', **traits ) return tui_engine_view
class WorkbenchPlugin(Plugin): """ The Envisage workbench plugin. The workbench plugin uses the PyFace workbench to provide the basis of an IDE-like user interface. The interface is made up of perspectives, views and editors. Note that this is not intended to be a 'general-purpose' plugin for user interfaces - it provides an IDE-like style and that is all. If your application requires another style of interface then write another plugin (you can still re-use all the menu, group and action contribution stuff!). """ # The Ids of the extension points that this plugin offers. ACTION_SETS = PKG + '.action_sets' PERSPECTIVES = PKG + '.perspectives' PREFERENCES_PAGES = PKG + '.preferences_pages' WORKBENCH_SERVICE_OFFERS = PKG + '.service_offers' VIEWS = PKG + '.views' # The Ids of the extension points that this plugin contributes to. PREFERENCES = 'envisage.preferences' SERVICE_OFFERS = 'envisage.service_offers' #### 'IPlugin' interface ################################################## # The plugin's unique identifier. id = 'envisage.ui.workbench' # The plugin's name (suitable for displaying to the user). name = 'Workbench' #### Extension points offered by this plugin ############################## action_sets = ExtensionPoint(List(Callable), id=ACTION_SETS, desc=""" An action set contains the toobars, menus, groups and actions that you would like to add to top-level workbench windows (i.e. the main application window). You can create new toolbars, menus and groups and/or add to existing ones. Each contribution to this extension point must be a factory that creates an action set, where 'factory' means any callable with the following signature:: callable(**traits) -> IActionSet The easiest way to contribute such a factory is to create a class that derives from 'envisage.ui.action.api.ActionSet'. """) perspectives = ExtensionPoint(List(Callable), id=PERSPECTIVES, desc=""" A perspective is simply an arrangment of views around the (optionally hidden) editor area. Each contribution to this extension point must be a factory that creates a perspective, where 'factory' means any callable with the following signature:: callable(**traits) -> IPerspective The easiest way to contribute such a factory is to create a class that derives from 'pyface.workbench.api.IPerspective'. """) preferences_pages = ExtensionPoint(List(Callable), id=PREFERENCES_PAGES, desc=""" A preferences page appears in the preferences dialog to allow the user to manipulate some preference values. Each contribution to this extension point must be a factory that creates a preferences page, where 'factory' means any callable with the following signature:: callable(**traits) -> IPreferencesPage The easiest way to contribute such a factory is to create a class that derives from 'apptools.preferences.ui.api.IPreferencesPage'. """) service_offers = ExtensionPoint(List(ServiceOffer), id=WORKBENCH_SERVICE_OFFERS, desc=""" Services are simply objects that a plugin wants to make available to other plugins. This extension point allows you to offer 'per window' services that are created 'on-demand' (where 'on demand' means the first time somebody looks up a service of the appropriate protocol). . e.g. my_service_offer = ServiceOffer( protocol = 'acme.IMyService', factory = an_object_or_a_callable_that_creates_one, properties = {'a dictionary' : 'that is passed to the factory'} ) Any properties specified are passed as keywrod arguments to the factory, i.e. the factory signature is:: callable(**properties) """) views = ExtensionPoint(List(Callable), id=VIEWS, desc=""" A view provides information to the user to support their current task. Views can contain anything you like(!) and are arranged around the (optionally hidden) editor area. The user can re-arrange views as he/she sees fit. Each contribution to this extension point must be a factory that creates a view, where 'factory' means any callable with the following signature:: callable(**traits) -> IView The easiest way to contribute such a factory is to create a class that derives from 'pyface.workbench.api.View'. It is also common to use a simple function (especially when a view is a representation of a service) e.g:: def foo_view_factory(**traits): ' Create a view that is a representation of a service. ' foo = self.application.get_service('IFoo') return FooView(foo=foo, **traits) """) #### Contributions to extension points made by this plugin ################ my_action_sets = List(contributes_to=ACTION_SETS) def _my_action_sets_default(self): """ Trait initializer. """ from .default_action_set import DefaultActionSet return [DefaultActionSet] my_preferences = List(contributes_to=PREFERENCES) def _my_preferences_default(self): """ Trait initializer. """ return ['pkgfile://envisage.ui.workbench/preferences.ini'] my_preferences_pages = List(contributes_to=PREFERENCES_PAGES) def _my_preferences_pages_default(self): """ Trait initializer. """ from .workbench_preferences_page import WorkbenchPreferencesPage return [WorkbenchPreferencesPage] my_service_offers = List(contributes_to=SERVICE_OFFERS) def _my_service_offers_default(self): """ Trait initializer. """ preferences_manager_service_offer = ServiceOffer( protocol='apptools.preferences.ui.preferences_manager' '.PreferencesManager', factory=self._create_preferences_manager_service) workbench_service_offer = ServiceOffer( protocol='envisage.ui.workbench.workbench.Workbench', factory=self._create_workbench_service) return [preferences_manager_service_offer, workbench_service_offer] ########################################################################### # Private interface. ########################################################################### def _create_preferences_manager_service(self, **properties): """ Factory method for the preferences manager service. """ from apptools.preferences.ui.api import PreferencesManager preferences_manager = PreferencesManager( pages=[factory() for factory in self.preferences_pages]) return preferences_manager def _create_workbench_service(self, **properties): """ Factory method for the workbench service. """ # We don't actually create the workbench here, we just return a # reference to it. # # fixme: This guard is really just for testing when we have the # workbench plugin as a source egg (i.e. if the egg is on our path # then we get the plugin for any egg-based application, even if it is # not a workbench application!). return getattr(self.application, 'workbench', None)
class TasksPlugin(Plugin): """ The Envisage Tasks plugin. The Tasks plugin uses Pyface Tasks to provide an extensible framework for building user interfaces. For more information, see the Tasks User Manual. """ # The IDs of the extension point that this plugin offers. PREFERENCES_CATEGORIES = PKG + '.preferences_categories' PREFERENCES_PANES = PKG + '.preferences_panes' TASKS = PKG + '.tasks' TASK_EXTENSIONS = PKG + '.task_extensions' # The IDs of the extension points that this plugin contributes to. SERVICE_OFFERS = 'envisage.service_offers' #### 'IPlugin' interface ################################################## #: The plugin's unique identifier. id = 'envisage.ui.tasks' #: The plugin's name (suitable for displaying to the user). name = 'Tasks' #### Extension points offered by this plugin ############################## #: Contributed preference categories. Contributions to this extension #: point must have type ``PreferencesCategory``. Preference categories #: will be created automatically if necessary; this extension point is #: useful when ensuring that a category is inserted at a specific location. preferences_categories = ExtensionPoint(List(PreferencesCategory), id=PREFERENCES_CATEGORIES, desc=""" This extension point makes preference categories available to the application. Note that preference categories will be created automatically if necessary; this extension point is useful when one wants to ensure that a category is inserted at a specific location. """) #: Contributed preference pane factories. Each contribution to this #: extension point must be a callable with the signature #: ``callable(**traits) -> PreferencePane``. preferences_panes = ExtensionPoint(List(Callable), id=PREFERENCES_PANES, desc=""" A preferences pane appears in the preferences dialog to allow the user manipulate certain preference values. Each contribution to this extension point must be a factory that creates a preferences pane, where 'factory' means any callable with the following signature:: callable(**traits) -> PreferencesPane The easiest way to contribute such a factory is to create a class that derives from 'envisage.ui.tasks.api.PreferencesPane'. """) #: Contributed task factories. Contributions to this extension point #: must have type ``TaskFactory``. tasks = ExtensionPoint(List( Instance('envisage.ui.tasks.task_factory.TaskFactory')), id=TASKS, desc=""" This extension point makes tasks avaiable to the application. Each contribution to the extension point must be an instance of 'envisage.tasks.api.TaskFactory. """) #: Contributed task extensions. Contributions to this extension point #: must have type ``TaskExtension``. task_extensions = ExtensionPoint(List( Instance('envisage.ui.tasks.task_extension.TaskExtension')), id=TASK_EXTENSIONS, desc=""" This extension point permits the contribution of new actions and panes to existing tasks (without creating a new task). Each contribution to the extension point must be an instance of 'envisage.tasks.api.TaskExtension'. """) #### Contributions to extension points made by this plugin ################ my_service_offers = List(contributes_to=SERVICE_OFFERS) def _my_service_offers_default(self): preferences_dialog_service_offer = ServiceOffer( protocol='envisage.ui.tasks.preferences_dialog.PreferencesDialog', factory=self._create_preferences_dialog_service) return [preferences_dialog_service_offer] my_task_extensions = List(contributes_to=TASK_EXTENSIONS) def _my_task_extensions_default(self): from .action.exit_action import ExitAction from .action.preferences_action import PreferencesGroup from .task_extension import TaskExtension from pyface.tasks.action.api import DockPaneToggleGroup, SchemaAddition actions = [ SchemaAddition(id='Exit', factory=ExitAction, path='MenuBar/File'), SchemaAddition(id='Preferences', factory=PreferencesGroup, path='MenuBar/Edit'), SchemaAddition(id='DockPaneToggleGroup', factory=DockPaneToggleGroup, path='MenuBar/View') ] return [TaskExtension(actions=actions)] ########################################################################### # Private interface. ########################################################################### def _create_preferences_dialog_service(self): """ Factory method for preferences dialog service. """ from .preferences_dialog import PreferencesDialog dialog = PreferencesDialog(application=self.application) dialog.trait_set(categories=self.preferences_categories, panes=[ factory(dialog=dialog) for factory in self.preferences_panes ]) return dialog
class WorkbenchWindow(pyface.WorkbenchWindow): """ An extensible workbench window. """ # Extension point Ids. ACTION_SETS = "envisage.ui.workbench.action_sets" VIEWS = "envisage.ui.workbench.views" PERSPECTIVES = "envisage.ui.workbench.perspectives" SERVICE_OFFERS = "envisage.ui.workbench.service_offers" #### 'WorkbenchWindow' interface ########################################## # The application that the window is part of. # # This is equivalent to 'self.workbench.application', and is provided just # as a convenience since windows often want access to the application. application = Delegate("workbench", modify=True) # The action sets that provide the toolbars, menus groups and actions # used in the window. action_sets = List(Instance(ActionSet)) # The service registry for 'per window' services. service_registry = Instance(IServiceRegistry, factory=ServiceRegistry) #### 'IExtensionPointUser' interface ###################################### # The extension registry that the object's extension points are stored in. extension_registry = Property(Instance(IExtensionRegistry)) #### Private interface #################################################### # The workbench menu and tool bar builder. # # The builder is used to create the window's tool bar and menu bar by # combining all of the contributed action sets. _action_manager_builder = Instance(WorkbenchActionManagerBuilder) # Contributed action sets (each contribution is actually a factory). _action_sets = ExtensionPoint(id=ACTION_SETS) # Contributed views (each contribution is actually a factory). _views = ExtensionPoint(id=VIEWS) # Contributed perspectives (each contribution is actually a factory). _perspectives = ExtensionPoint(id=PERSPECTIVES) # Contributed service offers. _service_offers = ExtensionPoint(id=SERVICE_OFFERS) # The Ids of the services that were automatically registered. _service_ids = List ########################################################################### # 'IExtensionPointUser' interface. ########################################################################### def _get_extension_registry(self): """ Trait property getter. """ return self.application ########################################################################### # 'pyface.Window' interface. ########################################################################### #### Trait initializers ################################################### def _menu_bar_manager_default(self): """ Trait initializer. """ return self._action_manager_builder.create_menu_bar_manager("MenuBar") def _status_bar_manager_default(self): """ Trait initializer. """ return StatusBarManager() def _tool_bar_managers_default(self): """ Trait initializer. """ return self._action_manager_builder.create_tool_bar_managers("ToolBar") #### Trait change handlers ################################################ def _opening_changed(self): """ Static trait change handler. """ self._service_ids = self._register_service_offers(self._service_offers) return def _closed_changed(self): """ Static trait change handler. """ self._unregister_service_offers(self._service_ids) return ########################################################################### # 'pyface.WorkbenchWindow' interface. ########################################################################### #### Trait initializers ################################################### def _editor_manager_default(self): """ Trait initializer. """ return WorkbenchEditorManager(window=self) def _icon_default(self): """ Trait initializer. """ return self.workbench.application.icon def _perspectives_default(self): """ Trait initializer. """ return [factory() for factory in self._perspectives] def _title_default(self): """ Trait initializer. """ return self.workbench.application.name def _views_default(self): """ Trait initializer. """ return [factory(window=self) for factory in self._views] ########################################################################### # 'WorkbenchWindow' interface. ########################################################################### def _action_sets_default(self): """ Trait initializer. """ return [factory(window=self) for factory in self._action_sets] ########################################################################### # 'IServiceRegistry' interface. ########################################################################### def get_service(self, protocol, query="", minimize="", maximize=""): """ Return at most one service that matches the specified query. """ service = self.service_registry.get_service(protocol, query, minimize, maximize) return service def get_service_properties(self, service_id): """ Return the dictionary of properties associated with a service. """ return self.service_registry.get_service_properties(service_id) def get_services(self, protocol, query="", minimize="", maximize=""): """ Return all services that match the specified query. """ services = self.service_registry.get_services(protocol, query, minimize, maximize) return services def register_service(self, protocol, obj, properties=None): """ Register a service. """ service_id = self.service_registry.register_service( protocol, obj, properties) return service_id def set_service_properties(self, service_id, properties): """ Set the dictionary of properties associated with a service. """ self.service_registry.set_service_properties(service_id, properties) return def unregister_service(self, service_id): """ Unregister a service. """ self.service_registry.unregister_service(service_id) return ########################################################################### # Private interface. ########################################################################### def __action_manager_builder_default(self): """ Trait initializer. """ action_manager_builder = WorkbenchActionManagerBuilder( window=self, action_sets=self.action_sets) return action_manager_builder def _register_service_offers(self, service_offers): """ Register all service offers. """ return list(map(self._register_service_offer, service_offers)) def _register_service_offer(self, service_offer): """ Register a service offer. """ # Add the window to the service offer properties (this is so that it # is available to the factory when it is called to create the actual # service). service_offer.properties["window"] = self service_id = self.register_service( protocol=service_offer.protocol, obj=service_offer.factory, properties=service_offer.properties, ) return service_id def _unregister_service_offers(self, service_ids): """ Unregister all service offers. """ # Unregister the services in the reverse order that we registered # them. service_ids_copy = service_ids[:] service_ids_copy.reverse() for service_id in service_ids_copy: self.unregister_service(service_id) return
class Foo(TestBase): x = ExtensionPoint(List(Int))
class PluginA(Plugin): id = "A" x = ExtensionPoint(List, id="bob")
def _create_extension_point(self, id, trait_type=List, desc=''): """ Create an extension point. """ return ExtensionPoint(id=id, trait_type=trait_type, desc=desc)
class IPythonShellPlugin(Plugin): """ An IPython shell plugin. """ # Extension point Ids. BANNER = 'envisage.plugins.ipython_shell.banner' BINDINGS = 'envisage.plugins.python_shell.bindings' COMMANDS = 'envisage.plugins.python_shell.commands' VIEWS = 'envisage.ui.workbench.views' ACTION_SETS = 'envisage.ui.workbench.action_sets' #### 'IPlugin' interface ################################################## # The plugin's unique identifier. id = 'envisage.plugins.python_shell' # The plugin's name (suitable for displaying to the user). name = 'Python Shell' #### Extension points offered by this plugin ############################## banner = ExtensionPoint(List(Str), id=BANNER, desc=""" This extension point allows you to contribute a string that is printed as a banner when the IPython shell is started. """) bindings = ExtensionPoint(List(Dict), id=BINDINGS, desc=""" This extension point allows you to contribute name/value pairs that will be bound when the interactive Python shell is started. e.g. Each item in the list is a dictionary of name/value pairs:: {'x' : 10, 'y' : ['a', 'b', 'c']} """) commands = ExtensionPoint(List(Str), id=COMMANDS, desc=""" This extension point allows you to contribute commands that are executed when the interactive Python shell is started. e.g. Each item in the list is a string of arbitrary Python code:: 'import os, sys' 'from traits.api import *' Yes, I know this is insecure but it follows the usual Python rule of 'we are all consenting adults'. """) #### Contributions to extension points made by this plugin ################ # Our action sets. action_sets = List(contributes_to=ACTION_SETS) def _action_sets_default(self): """ Trait initializer. """ from envisage.plugins.ipython_shell.actions.ipython_shell_actions \ import IPythonShellActionSet return [IPythonShellActionSet] # Bindings. contributed_bindings = List(contributes_to=BINDINGS) def _contributed_bindings_default(self): """ Trait initializer. """ return [{'application': self.application}] # Views. contributed_views = List(contributes_to=VIEWS) def _contributed_views_default(self): """ Trait initializer. """ # Local imports. from view.ipython_shell_view import IPythonShellView from view.namespace_view \ import NamespaceView return [IPythonShellView, NamespaceView]
class ProjectPlugin(Plugin): """ The single-project plugin. """ # The Ids of the extension points that this plugin offers. ACTION_SETS = 'envisage.ui.workbench.action_sets' FACTORY_DEFINITIONS = 'envisage.ui.single_project.factory_definitions' UI_SERVICE_FACTORY = 'envisage.ui.single_project.ui_service_factory' # The Ids of the extension points that this plugin contributes to. PERSPECTIVES = 'envisage.ui.workbench.perspectives' PREFERENCES = 'envisage.preferences' PREFERENCES_PAGES = 'envisage.ui.workbench.preferences_pages' SERVICE_OFFERS = 'envisage.service_offers' VIEWS = 'envisage.ui.workbench.views' #### 'IPlugin' interface ################################################## # The plugin's unique identifier. id = 'envisage.ui.single_project' # The plugin's name (suitable for displaying to the user). name = 'Single Project' #### Extension points offered by this plugin ############################## # Factory definitions. factory_definitions = ExtensionPoint(List(Callable), id=FACTORY_DEFINITIONS, desc=""" A project factory definition. An instance of the specified class is used to open and/or create new projects. The extension with the highest priority wins! In the event of a tie, the first instance wins. """) # Ui service factories. ui_service_factory = ExtensionPoint(List(Callable), id=UI_SERVICE_FACTORY, desc=""" A ui service factory definition. """) #### Contributions to extension points made by this plugin ################ # Action sets. action_sets = List(contributes_to=ACTION_SETS) def _action_sets_default(self): """ Default project actions. """ return [ProjectActionSet] # Factory definitions. my_factory_definitions = List(contributes_to=FACTORY_DEFINITIONS) def _my_factory_definitions_default(self): """ Default factory definition. """ factory_definition = FactoryDefinition( class_name=PKG + '.project_factory.ProjectFactory', priority=0, ) return [factory_definition] # Perspectives. perspectives = List(contributes_to=PERSPECTIVES) def _perspectives_default(self): """ Default project perspective. """ return [ProjectPerspective] # Service offers. service_offers = List(contributes_to=SERVICE_OFFERS) def _service_offers_default(self): """ Our service contributions. """ model_service = ServiceOffer(protocol=IPROJECT_MODEL, factory=self._create_model_service) ui_service = ServiceOffer(protocol=IPROJECT_UI, factory=self._create_ui_service) # FIXME: Eventually we will register the services here intead # of in the plugin's start() method. #return [model_service, ui_service] return [] # Ui service factories. my_ui_service_factory = List(contributes_to=UI_SERVICE_FACTORY) def _my_ui_service_factory_default(self): """ Default ui service factory. """ ui_service_factory = UIServiceFactory( class_name=PKG + '.ui_service_factory.UIServiceFactory', priority=0, ) return [ui_service_factory] # Preferences. my_preferences = List(contributes_to=PREFERENCES) def _my_preferences_default(self): """ Default preferences. """ return ['pkgfile://%s/preferences.ini' % PKG] # Preference pages. my_preferences_pages = List(contributes_to=PREFERENCES_PAGES) def _my_preferences_pages_default(self): """ Default preference page. """ from .default_path_preference_page import DefaultPathPreferencePage return [DefaultPathPreferencePage] # Views. views = List(contributes_to=VIEWS) def _views_default(self): """ Add our project views. """ return [self._project_view_factory] ### protected interface ################################################## def start(self): """ Starts the plugin. Overridden here to start up our services and load the project that was open when we were last shut-down. """ super(ProjectPlugin, self).start() # FIXME: We eventually won't have to explicitly register the # services ourselves, since we contribute them as service offers # so they are instantiated when they are invoked, but since they are # not used anywhere else yet, I had to use this same old approach # just to test and make sure they were working correctly. # Create and register the model service we offer model_service = self._create_model_service() self.application.register_service(IPROJECT_MODEL, model_service) # Create and register the ui service we offer ui_service = self._create_ui_service(model_service) self.application.register_service(IPROJECT_UI, ui_service) # Set up any listeners interested in the current project selection # FIXME: Register the selection listeners for the current project selection. #self._register_selection_listeners(model_service) return ###################################################################### # Private methods. def _project_view_factory(self, window, **traits): """ Factory method for project views. """ from pyface.workbench.traits_ui_view import \ TraitsUIView from envisage.ui.single_project.api import \ ProjectView project_view = ProjectView(application=window.application) tui_project_view = TraitsUIView( obj=project_view, id='envisage.ui.single_project.view.project_view.ProjectView', name='Project View', window=window, position='left', **traits) return tui_project_view def _create_model_service(self): """ Creates a model service for this plugin. """ # Determine which contributed project factory to use. factory = self._get_contributed_project_factory() # Make sure the factory has a reference to our Envisage application. factory.application = self.application # Create the project service instance. result = ModelService(self.application, factory) return result def _create_ui_service(self, model_service): """ Creates a UI service for this plugin. """ # Create the menu manager representing the context menu we show when # nothing is selected in the project view. menu_manager = self._get_no_selection_context_menu_manager() # Get the UI service factory. ui_service_factory = self._get_contributed_ui_service_factory() # Create the ui service instance ui_service = ui_service_factory.create_ui_service( model_service, menu_manager) return ui_service def _get_contributed_project_factory(self): """ Retrieves the instance of the project factory to use with this plugin. The instance is generated from the contributed factory definition that was the first one with the highest priority. """ # Retrieve all the factory definition contributions extensions = self.application.get_extensions( 'envisage.ui.single_project.factory_definitions') # Find the winning contribution definition = None for extension in extensions: if not definition or extension.priority > definition.priority: definition = extension # Create an instance of the winning project factory logger.info("Using ProjectFactory [%s]", definition.class_name) klass = self.application.import_symbol(definition.class_name) factory = klass() return factory def _get_contributed_ui_service_factory(self): """ Retrieves the instance of the UiService factory to use with this plugin. The instance is generated from the contributed factory definition that was the first one with the highest priority. """ # Retrieve all the factory definition contributions extensions = self.get_extensions( 'envisage.ui.single_project.ui_service_factory') # Find the winning contribution definition = None for extension in extensions: if not definition or extension.priority > definition.priority: definition = extension # Create an instance of the winning factory logger.info("Using UiService Factory [%s]", definition.class_name) class_name = definition.class_name klass = self.application.import_symbol(class_name) factory = klass() return factory def _get_no_selection_context_menu_manager(self): """ Generates a menu manager representing the context menu shown when nothing is selected within the project view. That is, when the user right clicks on any empty space within our associated UI. """ # Retrieve all contributions for the no-selection context menu. extensions = self.get_extensions(ProjectActionSet) # Populate a menu manager from the extensions. menu_manager = MenuManager() if len(extensions) > 0: action_set_manager = ActionSetManager(action_sets=extensions) menu_builder = DefaultMenuBuilder(application=self.application) menu_builder.initialize_menu_manager(menu_manager, action_set_manager, NO_SELECTION_MENU_ID) return menu_manager def _register_selection_listeners(self, model_service): """ Registers any extension-requested listeners on the project selection. """ for sps in self.get_extensions(SyncProjectSelection): object = self.application.lookup_application_object(sps.uol) if object is not None: name = sps.name self._register_selection_handler(object, name, model_service) else: logger.error('Could not resolve the SyncProjectSelection ' + \ 'UOL: "%s"', sps.uol ) return def _register_selection_handler(self, object, name, model_service): """ Creates a handler and registers it. """ def handler(): # The key to this method is to realize that our goal is to # make it as easy as possible to create recipients for # notification. Using traits as the recipients makes # creation very simple because we can rely on the type # knowledge within that trait to ensure only valid values # get assigned to the recipient. That is the recipient # doesn't need to do anything complex to validate the # values they get assigned. This method also works if the # recipient isn't a trait, but in that case, they will # have to handle multiple selection of the project # bindings. # # First, try to provide the recipient with a multiple # selection type value i.e. a list of bindings. try: setattr(object, name, model_service.selection) return except: pass # If that didn't work, remove the binding wrappers and try # notification of the resulting list. selection = [s.obj for s in model_service.selection] try: setattr(object, name, selection) return except: pass # If that didn't work, and only a single item is selected, # then try to provide that item to the recipient. if len(selection) == 1: try: setattr(object, name, selection[0]) return except: pass # The recipient must not be accepting the type of the # current selection, so let's clear its current selection # instead. If this fails, then something has gone wrong # with the declaration of the recipient. try: setattr(object, name, None) except: logger.debug( 'Error informing object [%s] of project ' 'selection change via attribute [%s]', object, name) model_service.on_trait_change(handler, 'selection') model_service.on_trait_change(handler, 'selection_items') return