envisage-4.4.0/0000755000175000017500000000000012252662115014030 5ustar davidcdavidc00000000000000envisage-4.4.0/setup.cfg0000644000175000017500000000007312252662115015651 0ustar davidcdavidc00000000000000[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 envisage-4.4.0/envisage/0000755000175000017500000000000012252662115015631 5ustar davidcdavidc00000000000000envisage-4.4.0/envisage/unknown_extension_point.py0000644000175000017500000000043012252661404023204 0ustar davidcdavidc00000000000000""" The exception raised when an unknown extension point is referenced. """ class UnknownExtensionPoint(Exception): """ The exception raised when an unknown extension point is referenced. """ #### EOF ###################################################################### envisage-4.4.0/envisage/egg_utils.py0000644000175000017500000000571212252661404020172 0ustar davidcdavidc00000000000000""" Utility functions for working with Python Eggs. """ # Standard library imports. import pkg_resources # Enthought library imports. from traits.util.toposort import topological_sort def add_eggs_on_path(working_set, path): """ Add all eggs found on the path to a working set. """ environment = pkg_resources.Environment(path) # 'find_plugins' identifies those distributions that *could* be added # to the working set without version conflicts or missing requirements. distributions, errors = working_set.find_plugins(environment) if len(errors) > 0: raise SystemError('Cannot find eggs %s' % errors) # Add the distributions to the working set (this makes any Python # modules in the eggs available for importing). map(working_set.add, distributions) return def get_entry_points_in_egg_order(working_set, entry_point_name): """ Return entry points in Egg dependency order. """ # Find all distributions that actually contain contributions to the # entry point. distributions = get_distributions_with_entry_point( working_set, entry_point_name ) # Order them in dependency order (i.e. ordered by their requirements). distributions = get_distributions_in_egg_order(working_set, distributions) entry_points = [] for distribution in distributions: map = distribution.get_entry_map(entry_point_name) entry_points.extend(map.values()) return entry_points def get_distributions_with_entry_point(working_set, entry_point_name): """ Return all distributions that contribute to an entry point. """ distributions = [] for distribution in working_set: if len(distribution.get_entry_map(entry_point_name)) > 0: distributions.append(distribution) return distributions def get_distributions_in_egg_order(working_set, distributions=None): """ Return all distributions in Egg dependency order. """ # If no specific list of distributions is specified then use all # distributions in the working set. if distributions is None: distributions = working_set # Build a dependency graph. graph = {} for distribution in distributions: arcs = graph.setdefault(distribution, []) arcs.extend(get_requires(working_set, distribution)) distributions = topological_sort(graph) distributions.reverse() return distributions def get_requires(working_set, distribution): """ Return all of the other distributions that a distribution requires. """ requires = [] for requirement in distribution.requires(): required = working_set.find(requirement) # fixme: For some reason, the resolution of requirements sometimes # results in 'None' being returned instead of a distribution. if required is not None: requires.append(working_set.find(requirement)) return requires #### EOF ###################################################################### envisage-4.4.0/envisage/core_plugin.py0000644000175000017500000002244712252661404020522 0ustar davidcdavidc00000000000000""" The Envisage core plugin. """ # Enthought library imports. from envisage.api import ExtensionPoint, Plugin, ServiceOffer from traits.api import List, Instance, on_trait_change, Str class CorePlugin(Plugin): """ The Envisage core plugin. The core plugin offers facilities that are generally useful when building extensible applications such as adapters, categories and hooks etc. It does not contain anything to do with user interfaces! The core plugin should be started before any other plugin. It is up to the plugin manager to do this. """ # Extension point Ids. CATEGORIES = 'envisage.categories' CLASS_LOAD_HOOKS = 'envisage.class_load_hooks' PREFERENCES = 'envisage.preferences' SERVICE_OFFERS = 'envisage.service_offers' #### 'IPlugin' interface ################################################## # The plugin's unique identifier. id = 'envisage.core' # The plugin's name (suitable for displaying to the user). name = 'Core' #### Extension points offered by this plugin ############################## # Categories are actually implemented via standard 'ClassLoadHooks', but # for (hopefully) readability and convenience we have a specific extension # point. categories = ExtensionPoint( List(Instance('envisage.category.Category')), id = CATEGORIES, desc = """ Traits categories allow you to dynamically extend a Python class with extra attributes, methods and events. Contributions to this extension point allow you to import categories *lazily* when the class to be extended is imported or created. Each contribution contains the name of the category class that you want to add (the 'class_name') and the name of the class that you want to extend (the 'target_class_name'). e.g. To add the 'FooCategory' category to the 'Foo' class:: Category( class_name = 'foo_category.FooCategory', target_class_name = 'foo.Foo' ) """ ) @on_trait_change('categories_items') def _categories_items_changed(self, event): """ React to new categories being *added*. Note that we don't currently do anything if categories are *removed*. """ self._add_category_class_load_hooks(event.added) return class_load_hooks = ExtensionPoint( List(Instance('envisage.class_load_hook.ClassLoadHook')), id = CLASS_LOAD_HOOKS, desc = """ Class load hooks allow you to be notified when any 'HasTraits' class is imported or created. See the documentation for 'ClassLoadHook' for more details. """ ) @on_trait_change('class_load_hooks_items') def _class_load_hooks_changed(self, event): """ React to new class load hooks being *added*. Note that we don't currently do anything if class load hooks are *removed*. """ self._connect_class_load_hooks(event.added) return preferences = ExtensionPoint( List(Str), id = PREFERENCES, desc = """ Preferences files allow plugins to contribute default values for user preferences. Each contributed string must be the URL of a file-like object that contains preferences values. e.g. 'pkgfile://envisage/preferences.ini' - this looks for the 'preferences.ini' file in the 'envisage' package. 'file://C:/tmp/preferences.ini' - this looks for the 'preferences.ini' file in 'C:/tmp' 'http://some.website/preferences.ini' - this looks for the 'preferences.ini' document on the 'some.website' web site! The files themselves are parsed using the excellent 'ConfigObj' package. For detailed documentation please go to:- http://www.voidspace.org.uk/python/configobj.html """ ) @on_trait_change('preferences_items') def _preferences_changed(self, event): """ React to new preferencess being *added*. Note that we don't currently do anything if preferences are *removed*. """ self._load_preferences(event.added) return service_offers = ExtensionPoint( List(ServiceOffer), id = SERVICE_OFFERS, desc = """ Services are simply objects that a plugin wants to make available to other plugins. This extension point allows you to offer services that are created 'on-demand'. 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'} ) See the documentation for 'ServiceOffer' for more details. """ ) @on_trait_change('service_offers_items') def _service_offers_changed(self, event): """ React to new service offers being *added*. Note that we don't currently do anything if services are *removed* as we have no facility to let users of the service know that the offer has been retracted. """ map(self._register_service_offer, event.added) return #### Contributions to extension points made by this plugin ################ # None. ########################################################################### # 'IPlugin' interface. ########################################################################### def start(self): """ Start the plugin. """ # Load all contributed preferences files into the application's root # preferences node. self._load_preferences(self.preferences) # Connect all class load hooks. self._connect_class_load_hooks(self.class_load_hooks) # Add class load hooks for all of the contributed categories. The # category will be imported and added when the associated target class # is imported/created. self._add_category_class_load_hooks(self.categories) # Register all service offers. # # These services are unregistered by the default plugin activation # strategy (due to the fact that we store the service ids in this # specific trait!). self._service_ids = self._register_service_offers(self.service_offers) return ########################################################################### # Private interface. ########################################################################### def _add_category_class_load_hooks(self, categories): """ Add class load hooks for a list of categories. """ for category in categories: class_load_hook = self._create_category_class_load_hook(category) class_load_hook.connect() return def _connect_class_load_hooks(self, class_load_hooks): """ Connect all class load hooks. """ for class_load_hook in class_load_hooks: class_load_hook.connect() return def _create_category_class_load_hook(self, category): """ Create a category class load hook. """ # Local imports. from class_load_hook import ClassLoadHook def import_and_add_category(cls): """ Import a category and add it to a class. This is a closure that binds 'self' and 'category'. """ category_cls = self.application.import_symbol(category.class_name) cls.add_trait_category(category_cls) return category_class_load_hook = ClassLoadHook( class_name = category.target_class_name, on_load = import_and_add_category ) return category_class_load_hook def _load_preferences(self, preferences): """ Load all contributed preferences into a preferences node. """ # Enthought library imports. from envisage.resource.api import ResourceManager # We add the plugin preferences to the default scope. The default scope # is a transient scope which means that (quite nicely ;^) we never # save the actual default plugin preference values. They will only get # saved if a value has been set in another (persistent) scope - which # is exactly what happens in the preferences UI. default = self.application.preferences.node('default/') # The resource manager is used to find the preferences files. resource_manager = ResourceManager() for resource_name in preferences: f = resource_manager.file(resource_name) try: default.load(f) finally: f.close() return def _register_service_offers(self, service_offers): """ Register a list of service offers. """ return map(self._register_service_offer, service_offers) def _register_service_offer(self, service_offer): """ Register a service offer. """ service_id = self.application.register_service( protocol = service_offer.protocol, obj = service_offer.factory, properties = service_offer.properties ) return service_id ### EOF ###################################################################### envisage-4.4.0/envisage/i_plugin_activator.py0000644000175000017500000000172112252661404022066 0ustar davidcdavidc00000000000000""" The plugin activator interface. """ # Enthought library imports. from traits.api import Interface class IPluginActivator(Interface): """ The plugin activator interface. A plugin activator is really just a collection of two strategies - one to start the plugin and one to stop it. We use an activator so that the framework can implement default start and stop strategies without forcing the plugin writer to call 'super' if they override the 'start' and 'stop' methods on 'IPlugin'. I'm not sure that having to call 'super' is such a burden, but some people seem to like it this way, and it does mean one less thing for a plugin writer to have to remember to do! """ def start_plugin(self, plugin): """ Start the specified plugin. """ def stop_plugin(self, plugin): """ Stop the specified plugin. """ #### EOF ###################################################################### envisage-4.4.0/envisage/provider_extension_registry.py0000644000175000017500000002520712252661404024067 0ustar davidcdavidc00000000000000""" An extension registry implementation with multiple providers. """ # Standard library imports. import logging # Enthought library imports. from traits.api import List, implements, on_trait_change # Local imports. from extension_registry import ExtensionRegistry from i_extension_provider import IExtensionProvider from i_provider_extension_registry import IProviderExtensionRegistry # Logging. logger = logging.getLogger(__name__) class ProviderExtensionRegistry(ExtensionRegistry): """ An extension registry implementation with multiple providers. """ implements(IProviderExtensionRegistry) #### Protected 'ProviderExtensionRegistry' interface ###################### # The extension providers that populate the registry. _providers = List(IExtensionProvider) ########################################################################### # 'IExtensionRegistry' interface. ########################################################################### def set_extensions(self, extension_point_id, extensions): """ Set the extensions to an extension point. """ raise SystemError('extension points cannot be set') ########################################################################### # 'ProviderExtensionRegistry' interface. ########################################################################### def add_provider(self, provider): """ Add an extension provider. """ events = self._add_provider(provider) for extension_point_id, (refs, added, index) in events.items(): self._call_listeners(refs, extension_point_id, added, [], index) return def get_providers(self): """ Return all of the providers in the registry. """ return self._providers[:] def remove_provider(self, provider): """ Remove an extension provider. Raise a 'ValueError' if the provider is not in the registry. """ events = self._remove_provider(provider) for extension_point_id, (refs, removed, index) in events.items(): self._call_listeners(refs, extension_point_id, [], removed, index) return ########################################################################### # Protected 'ExtensionRegistry' interface. ########################################################################### def _get_extensions(self, extension_point_id): """ Return the extensions for the given extension point. """ # If we don't know about the extension point then it sure ain't got # any extensions! if not extension_point_id in self._extension_points: logger.warn( 'getting extensions of unknown extension point <%s>' \ % extension_point_id ) extensions = [] # Has this extension point already been accessed? elif extension_point_id in self._extensions: extensions = self._extensions[extension_point_id] # If not, then ask each provider for its contributions to the extension # point. else: extensions = self._initialize_extensions(extension_point_id) self._extensions[extension_point_id] = extensions # We store the extensions as a list of lists, with each inner list # containing the contributions from a single provider. Here we just # concatenate them into a single list. # # You could use a list comprehension, here:- # # all = [x for y in extensions for x in y] # # But I'm sure sure that that makes it any clearer ;^) all = [] map(all.extend, extensions) return all ########################################################################### # Protected 'ProviderExtensionRegistry' interface. ########################################################################### def _add_provider(self, provider): """ Add a new provider. """ # Add the provider's extension points. self._add_provider_extension_points(provider) # Add the provider's extensions. events = self._add_provider_extensions(provider) # And finally, tag it into the list of providers. self._providers.append(provider) return events def _add_provider_extensions(self, provider): """ Add a provider's extensions to the registry. """ # Each provider can contribute to multiple extension points, so we # build up a dictionary of the 'ExtensionPointChanged' events that we # need to fire. events = {} # Does the provider contribute any extensions to an extension point # that has already been accessed? for extension_point_id, extensions in self._extensions.items(): new = provider.get_extensions(extension_point_id) # We only need fire an event for this extension point if the # provider contributes any extensions. if len(new) > 0: index = sum(map(len, extensions)) refs = self._get_listener_refs(extension_point_id) events[extension_point_id] = (refs, new[:], index) extensions.append(new) return events def _add_provider_extension_points(self, provider): """ Add a provider's extension points to the registry. """ for extension_point in provider.get_extension_points(): self._extension_points[extension_point.id] = extension_point return def _remove_provider(self, provider): """ Remove a provider. """ # Remove the provider's extensions. events = self._remove_provider_extensions(provider) # Remove the provider's extension points. self._remove_provider_extension_points(provider, events) # And finally take it out of the list of providers. self._providers.remove(provider) return events def _remove_provider_extensions(self, provider): """ Remove a provider's extensions from the registry. """ # Each provider can contribute to multiple extension points, so we # build up a dictionary of the 'ExtensionPointChanged' events that we # need to fire. events = {} # Find the index of the provider in the provider list. Its # contributions are at the same index in the extensions list of lists. index = self._providers.index(provider) # Does the provider contribute any extensions to an extension point # that has already been accessed? for extension_point_id, extensions in self._extensions.items(): old = extensions[index] # We only need fire an event for this extension point if the # provider contributed any extensions. if len(old) > 0: offset = sum(map(len, extensions[:index])) refs = self._get_listener_refs(extension_point_id) events[extension_point_id] = (refs, old[:], offset) del extensions[index] return events def _remove_provider_extension_points(self, provider, events): """ Remove a provider's extension points from the registry. """ for extension_point in provider.get_extension_points(): # Remove the extension point. del self._extension_points[extension_point.id] return ########################################################################### # Private interface. ########################################################################### #### Trait change handlers ################################################ @on_trait_change('_providers:extension_point_changed') def _providers_extension_point_changed(self, obj, trait_name, old, event): """ Dynamic trait change handler. """ logger.debug('provider <%s> extension point changed', obj) extension_point_id = event.extension_point_id # If the extension point has not yet been accessed then we don't fire a # changed event. # # This is because we only access extension points lazily and so we # can't tell what has actually changed because we have nothing to # compare it to! if not extension_point_id in self._extensions: return # This is a list of lists where each inner list contains the # contributions made to the extension point by a single provider. # # fixme: This causes a problem if the extension point has not yet been # accessed! The tricky thing is that if it hasn't been accessed yet # how do we know what has changed?!? Maybe we should just return an # empty list instead of barfing! extensions = self._extensions[extension_point_id] # Find the index of the provider in the provider list. Its # contributions are at the same index in the extensions list of lists. provider_index = self._providers.index(obj) # Get the updated list from the provider. extensions[provider_index] = obj.get_extensions(extension_point_id) # Find where the provider's contributions are in the whole 'list'. offset = sum(map(len, extensions[:provider_index])) # Translate the event index from one that refers to the list of # contributions from the provider, to the list of contributions from # all providers. index = self._translate_index(event.index, offset) # Find out who is listening. refs = self._get_listener_refs(extension_point_id) # Let any listeners know that the extensions have been added. self._call_listeners( refs, extension_point_id, event.added, event.removed, index ) return #### Methods ############################################################## def _initialize_extensions(self, extension_point_id): """ Initialize the extensions to an extension point. """ # We store the extensions as a list of lists, with each inner list # containing the contributions from a single provider. extensions = [] for provider in self._providers: extensions.append(provider.get_extensions(extension_point_id)[:]) logger.debug('extensions to <%s> <%s>', extension_point_id, extensions) return extensions def _translate_index(self, index, offset): """ Translate an event index by the given offset. """ if isinstance(index, slice): index = slice(index.start+offset, index.stop+offset, index.step) else: index = index + offset return index #### EOF ###################################################################### envisage-4.4.0/envisage/api.py0000644000175000017500000000314212252661404016754 0ustar davidcdavidc00000000000000""" Envisage package Copyright 2003-2007 Enthought, Inc. """ from i_application import IApplication from i_extension_point import IExtensionPoint from i_extension_point_user import IExtensionPointUser from i_extension_provider import IExtensionProvider from i_extension_registry import IExtensionRegistry from i_import_manager import IImportManager from i_plugin import IPlugin from i_plugin_activator import IPluginActivator from i_plugin_manager import IPluginManager from i_service_registry import IServiceRegistry from application import Application from category import Category from class_load_hook import ClassLoadHook from egg_plugin_manager import EggPluginManager from extension_registry import ExtensionRegistry from extension_point import ExtensionPoint, contributes_to from extension_point_binding import ExtensionPointBinding, bind_extension_point from extension_provider import ExtensionProvider from extension_point_changed_event import ExtensionPointChangedEvent from import_manager import ImportManager from plugin import Plugin from plugin_activator import PluginActivator from plugin_extension_registry import PluginExtensionRegistry from plugin_manager import PluginManager from provider_extension_registry import ProviderExtensionRegistry from service import Service from service_offer import ServiceOffer from service_registry import NoSuchServiceError, ServiceRegistry from twisted_application import TwistedApplication from unknown_extension import UnknownExtension from unknown_extension_point import UnknownExtensionPoint #### EOF ###################################################################### envisage-4.4.0/envisage/i_application.py0000644000175000017500000000353412252661404021023 0ustar davidcdavidc00000000000000""" The application interface. """ # Enthought library imports. from apptools.preferences.api import IPreferences from traits.api import Event, Instance, Str, VetoableEvent # Local imports. from i_extension_registry import IExtensionRegistry from i_import_manager import IImportManager from i_plugin_manager import IPluginManager from i_service_registry import IServiceRegistry from application_event import ApplicationEvent class IApplication( IExtensionRegistry, IImportManager, IPluginManager, IServiceRegistry ): """ The application interface. """ # The application's globally unique identifier. id = Str # The name of a directory (created for you) to which the application can # read and write non-user accessible data, i.e. configuration information, # preferences, etc. home = Str # The name of a directory (created for you upon access) to which the # application can read and write user-accessible data, e.g. projects created # by the user. user_data = Str # The root preferences node. preferences = Instance(IPreferences) #### Events #### # Fired when the application is starting. This is the first thing that # happens when the 'start' method is called. starting = VetoableEvent(ApplicationEvent) # Fired when all plugins have been started. started = Event(ApplicationEvent) # Fired when the plugin manager is stopping. This is the first thing that # happens when the 'stop' method is called. stopping = VetoableEvent(ApplicationEvent) # Fired when all plugins have been stopped. stopped = Event(ApplicationEvent) def run(self): """ Run the application. The same as:: if application.start(): application.stop() """ #### EOF ###################################################################### envisage-4.4.0/envisage/safeweakref.py0000644000175000017500000000561412252661404020474 0ustar davidcdavidc00000000000000""" An implementation of weak references that works for bound methods. This code is based on the code in the Python Cookbook, but you can call `ref` for objects that are *not* bound methods too, in which case it just returns a standard `weakref.ref`. Weak references to bound methods are cached so that `ref(x) is ref(x)` as for standard weakrefs, and the `ref` class defined here is therefore intended to be used as a drop-in replacement for 'weakref.ref'. """ # Standard library imports. import new, weakref # Because this module is intended as a drop-in replacement for weakref, we # import everything from that module here (so the user can do things like # "import safeweakref as weakref" etc). from weakref import * class ref(object): """ An implementation of weak references that works for bound methods. """ # A cache containing the weak references we have already created. # # We cache the weak references by the object containing the associated # bound methods, hence this is a dictionary of dictionaries in the form:- # # { bound_method.im_self : { bound_method.im_func : ref } } # # This makes sure that when the object is garbage collected, any cached # weak references are garbage collected too. _cache = weakref.WeakKeyDictionary() def __new__(cls, obj, *args, **kw): """ Create a new instance of the class. """ # If the object is a bound method then either get from the cache, or # create an instance of *this* class. if hasattr(obj, 'im_self'): func_cache = ref._cache.setdefault(obj.im_self, {}) # If we haven't created a weakref to this bound method before, then # create one and cache it. self = func_cache.get(obj.im_func) if self is None: self = object.__new__(cls, obj, *args, **kw) func_cache[obj.im_func] = self # Otherwise, just return a regular weakref (because we aren't # returning an instance of *this* class our constructor does not get # called). else: self = weakref.ref(obj) return self def __init__(self, obj): """ Create a weak reference to a bound method object. 'obj' is *always* a bound method because in the '__new__' method we don't return an instance of this class if it is not, and hence this constructor doesn't get called. """ self._cls = obj.im_class self._fn = obj.im_func self._ref = weakref.ref(obj.im_self) return def __call__(self): """ Return a strong reference to the object. Return None if the object has been garbage collected. """ obj = self._ref() if obj is not None: obj = new.instancemethod(self._fn, obj, self._cls) return obj #### EOF ###################################################################### envisage-4.4.0/envisage/i_extension_provider.py0000644000175000017500000000175612252661404022452 0ustar davidcdavidc00000000000000""" The interface for extension providers. """ # Enthought library imports. from traits.api import Event, Interface # Local imports. from extension_point_changed_event import ExtensionPointChangedEvent class IExtensionProvider(Interface): """ The interface for extension providers. """ # The event fired when one of the provider's extension points has changed. extension_point_changed = Event(ExtensionPointChangedEvent) def get_extension_points(self): """ Return the extension points offered by the provider. Return an empty list if the provider does not offer any extension points. """ def get_extensions(self, extension_point_id): """ Return the provider's extensions to an extension point. The return value *must* be a list. Return an empty list if the provider does not contribute any extensions to the extension point. """ #### EOF ###################################################################### envisage-4.4.0/envisage/plugins/0000755000175000017500000000000012252662115017312 5ustar davidcdavidc00000000000000envisage-4.4.0/envisage/plugins/ipython_shell/0000755000175000017500000000000012252662115022173 5ustar davidcdavidc00000000000000envisage-4.4.0/envisage/plugins/ipython_shell/actions/0000755000175000017500000000000012252662115023633 5ustar davidcdavidc00000000000000envisage-4.4.0/envisage/plugins/ipython_shell/actions/ipython_shell_actions.py0000644000175000017500000000355512252661404030616 0ustar davidcdavidc00000000000000from envisage.ui.action.api import Action, ActionSet, Group from pyface.action.api import Action as PyfaceAction from envisage.plugins.python_shell.api import IPythonShell def get_shell(window): """ Given an application window, retrieve the ipython shell. """ return window.application.get_service(IPythonShell) ################################################################################ # Groups ################################################################################ ipython_shell_group = Group( id='IPythonShellGroup', path='MenuBar/Tools', #before='ExitGroup' ) ################################################################################ # `ClearScreen` class. ################################################################################ class ClearScreen(PyfaceAction): """ An action that clears the IPython screen. """ tooltip = "Clear the IPython screen." description = "Clear the IPython screen." ########################################################################### # 'Action' interface. ########################################################################### def perform(self, event): """ Performs the action. """ shell = get_shell(self.window) if shell is not None: shell.control.clear_screen() clear_screen = Action( path = "MenuBar/Tools", class_name = __name__ + '.ClearScreen', name = "Clear IPython screen", group = "IPythonShellGroup", ) ################################################################################ # `IPythonShellActionSet` class. ################################################################################ class IPythonShellActionSet(ActionSet): """ The default action set for the IPython shell plugin. """ groups = [ipython_shell_group, ] actions = [clear_screen] envisage-4.4.0/envisage/plugins/ipython_shell/actions/__init__.py0000644000175000017500000000000012252661404025732 0ustar davidcdavidc00000000000000envisage-4.4.0/envisage/plugins/ipython_shell/api.py0000644000175000017500000000016212252661404023315 0ustar davidcdavidc00000000000000from envisage.plugins.python_shell.i_python_shell import IPythonShell from i_namespace_view import INamespaceView envisage-4.4.0/envisage/plugins/ipython_shell/__init__.py0000644000175000017500000000000012252661404024272 0ustar davidcdavidc00000000000000envisage-4.4.0/envisage/plugins/ipython_shell/i_namespace_view.py0000644000175000017500000000062412252661404026045 0ustar davidcdavidc00000000000000""" Interface definition for the Namespace view """ # Enthought library imports. from traits.api import Interface class INamespaceView(Interface): """ Interface definition for the Namespace view """ def _on_names_changed(self, new): """ Handler to track the changes in the namespace viewed. """ #### EOF ###################################################################### envisage-4.4.0/envisage/plugins/ipython_shell/view/0000755000175000017500000000000012252662115023145 5ustar davidcdavidc00000000000000envisage-4.4.0/envisage/plugins/ipython_shell/view/api.py0000644000175000017500000000006012252661404024264 0ustar davidcdavidc00000000000000from ipython_shell_view import IPythonShellView envisage-4.4.0/envisage/plugins/ipython_shell/view/namespace_view.py0000644000175000017500000002017712252661404026514 0ustar davidcdavidc00000000000000""" A view containing the contents of a Python shell namespace. """ # Enthought library imports. from envisage.plugins.python_shell.api import IPythonShell from envisage.plugins.ipython_shell.api import INamespaceView from pyface.workbench.api import View from traits.api import Property, implements, Instance, \ Str, HasTraits from traitsui.api import Item, TreeEditor, Group from traitsui.api import View as TraitsView from traitsui.value_tree import DictNode, StringNode, \ value_tree_nodes from pyface.timer.api import Timer from pyface.api import GUI def search_namespace(namespace, string, depth=3): """ Iterator on a dictionnary-like object. Given a namespace, search recursively for a name containing the string in the enclosed modules and classes. """ if depth==0: raise StopIteration for child_name in namespace: child = namespace[child_name] if string in child_name: yield child_name, child if hasattr(child, '__dict__'): for suitable_child_name, suitable_child in \ search_namespace(child.__dict__, string, depth=depth-1): yield ('%s.%s' % (child_name, suitable_child_name), suitable_child) def filter_namespace(namespace, string, depth=3): """ Return a flattened dictionnary to the depth given, with only the keys matching the given name. """ out_dict = dict() for key, item in search_namespace(namespace, string, depth=depth): out_dict[key] = item return out_dict def explore(node): """ Small helper function to graphically edit an object. """ # FIXME: This is really very dumb. The view used should be made # better. The logics should probably be put in the nodes themselves, # subclassing them. name = node.name obj = node.value class MyClass(HasTraits): the_object = obj view = TraitsView(Item('the_object', style='custom', show_label=False), resizable=True, title=name, width=600, ) return MyClass().edit_traits() class NamespaceNode(DictNode): """ Subclass of the DictNode for the namespace purposes. """ def tno_get_icon ( self, node, is_expanded ): """ Returns the icon for a specified object. We overwrite this method because we don't have a default icon for this object. """ return ('@icons:dict_node') def tno_get_children ( self, node ): """ Gets the object's children. We overwrite this method for a nicer label on the objects. """ node_for = self.node_for items = self.value.items() items.sort( lambda l, r: cmp( l[0], r[0] ) ) if len( items ) > 500: return ([ self.node_for( k, v ) for k, v in items[: 250 ] ] + [ StringNode( value = '...', readonly = True ) ] + [ self.node_for( k, v ) for k, v in items[ -250: ] ]) return [ self.node_for( k, v ) for k, v in items ] ################################################################################ class NamespaceView(View): """ A view containing the contents of the Python shell namespace. """ implements(INamespaceView) #### 'IView' interface #################################################### # The part's globally unique identifier. id = 'envisage.plugins.ipython_shell.namespace_view' # The view's name. name = 'Namespace' # The default position of the view relative to the item specified in the # 'relative_to' trait. position = 'left' #### 'NamespaceView' interface ############################################ # The different tree nodes tree_nodes = Property(depends_on='search_text') # Search text search_text = Str tree_editor = Property(depends_on="ui") # The timer used to refresh the ui _refresh_tree_nodes_timer = Instance(Timer) def __refresh_tree_nodes_timer_default(self): return Timer(100, self._refresh_tree_nodes) ########################################################################### # 'View' interface. ########################################################################### traits_view = TraitsView( Group(Item('search_text', label='Search')), Item( 'tree_nodes', id = 'table', editor = TreeEditor( auto_open=1, hide_root=True, editable=False, nodes=value_tree_nodes, on_dclick='object._explore', ), springy = True, resizable = True, show_label = False ), resizable = True, ) def create_control(self, parent): """ Creates the toolkit-specific control that represents the view. 'parent' is the toolkit-specific control that is the view's parent. """ self.ui = self.edit_traits(parent=parent, kind='subpanel') # Register the view as a service. self.window.application.register_service(INamespaceView, self) shell = self.window.application.get_service(IPythonShell) if shell is not None: shell.on_trait_change(self._on_names_changed, 'names') self._on_names_changed(shell.names) return self.ui.control def destroy_control(self): """ Destroys the toolkit-specific control that represents the view. """ super(NamespaceView, self).destroy_control() # Remove the namespace change handler shell= self.window.application.get_service(IPythonShell) if shell is not None: shell.on_trait_change( self._on_names_changed, 'names', remove=True ) ########################################################################### # 'NamespaceView' interface. ########################################################################### #### Properties ########################################################### def _get_tree_nodes(self): """ Property getter. """ shell = self.window.application.get_service(IPythonShell) # Cater for an un-initialized python shell view if shell is None: return NamespaceNode(value={}, readonly=True) filtered_namespace = dict() for name in shell.names: filtered_namespace[name] = shell.lookup(name) if not self.search_text == '': filtered_namespace = filter_namespace(filtered_namespace, self.search_text) return NamespaceNode(value=filtered_namespace, readonly=True) def _get_tree_editor(self): """ Walk the editor list to retrieve the instance of the tree editor currently used. """ for editor in self.ui._editors: print editor return self.ui._editors[-1] def _refresh_tree_nodes(self): """ Callback called by a timer to refresh the UI. The UI is refreshed by a timer to buffer the refreshes, in order not to slow down the execution engine. """ self.trait_property_changed('tree_nodes', None) self._refresh_tree_nodes_timer.Stop() ########################################################################### # Private interface. ########################################################################### #### Trait change handlers ################################################ def _on_names_changed(self, new): """ Dynamic trait change handler. """ if not self._refresh_tree_nodes_timer.IsRunning(): GUI.invoke_later(self._refresh_tree_nodes_timer.Start) def _explore(self, object): """ Displays a view of the object. """ explore(object) #### EOF ###################################################################### envisage-4.4.0/envisage/plugins/ipython_shell/view/__init__.py0000644000175000017500000000000012252661404025244 0ustar davidcdavidc00000000000000envisage-4.4.0/envisage/plugins/ipython_shell/view/ipython_shell_view.py0000644000175000017500000001355412252661404027442 0ustar davidcdavidc00000000000000""" A view containing an interactive Python shell. """ # Standard library imports. import logging import traceback # Major library imports from IPython.kernel.core.interpreter import Interpreter # Enthought library imports. from envisage.api import IExtensionRegistry from envisage.api import ExtensionPoint from envisage.plugins.python_shell.api import IPythonShell from envisage.plugins.ipython_shell.api import INamespaceView from pyface.workbench.api import View from pyface.ipython_widget import IPythonWidget from pyface.api import GUI from traits.api import Instance, Property, implements, Dict # Setup a logger for this module. logger = logging.getLogger(__name__) class IPythonShellView(View): """ A view containing an IPython shell. """ implements(IPythonShell) #### 'IView' interface #################################################### # The part's globally unique identifier. id = 'envisage.plugins.python_shell_view' # The part's name (displayed to the user). name = 'IPython' # 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 = Dict # The names bound in the interpreter's namespace. names = Property(depends_on="namespace") #### 'IPythonShellView' interface ######################################### # The interpreter interpreter = Instance(Interpreter) def _interpreter_default(self): # Create an interpreter that has a reference to our namespace. return Interpreter(user_ns=self.namespace) #### 'IExtensionPointUser' interface ###################################### # The extension registry that the object's extension points are stored in. extension_registry = Property(Instance(IExtensionRegistry)) #### Private interface #################################################### # Banner. _banner = ExtensionPoint(id='envisage.plugins.ipython_shell.banner') # 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 = IPythonWidget(parent, banner='\n'.join(self._banner), interp=self.interpreter) # Namespace contributions. for bindings in self._bindings: for name, value in bindings.items(): self.bind(name, value) for command in self._commands: try: self.execute_command(command) except Exception, e: logger.exception( "The command '%s' supplied to the Ipython shell " "plugin has raised an exception:\n%s" % (command, traceback.format_exc())) # Register the view as a service. self.window.application.register_service(IPythonShell, self) ns_view = self.window.application.get_service(INamespaceView) if ns_view is not None: self.on_trait_change(ns_view._on_names_changed, 'names') def try_set_focus(): try: self.shell.control.SetFocus() except: # The window may not have been created yet. pass def set_focus(): self.window.application.gui.invoke_later(try_set_focus) GUI.invoke_later(set_focus) return self.shell.control def destroy_control(self): """ Destroys the toolkit-specific control that represents the view. """ super(IPythonShellView, self).destroy_control() # Remove the namespace change handler ns_view = self.window.application.get_service(INamespaceView) if ns_view is not None: self.on_trait_change( ns_view._on_names_changed, 'names', remove=True ) ########################################################################### # 'PythonShellView' interface. ########################################################################### #### Properties ########################################################### def _get_names(self): """ Property getter. """ return self.control.ipython0.magic_who_ls() #### Methods ############################################################## def bind(self, name, value): """ Binds a name to a value in the interpreter's namespace. """ self.namespace[name] = value return def execute_command(self, command, hidden=True): """ Execute a command in the interpreter. """ self.shell.execute_command(command, hidden) self.trait_property_changed('namespace', [], self.namespace) def execute_file(self, path, hidden=True): """ Execute a command in the interpreter. """ self.shell.execute_file(path, hidden) self.trait_property_changed('namespace', [], self.namespace) def lookup(self, name): """ Returns the value bound to a name in the interpreter's namespace.""" return self.namespace[name] #### EOF ###################################################################### envisage-4.4.0/envisage/plugins/ipython_shell/ipython_shell_plugin.py0000644000175000017500000000563712252661404027017 0ustar davidcdavidc00000000000000""" An IPython shell plugin. """ # Enthought library imports. from envisage.api import ExtensionPoint, Plugin from traits.api import Dict, List, Str 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] #### EOF ###################################################################### envisage-4.4.0/envisage/plugins/text_editor/0000755000175000017500000000000012252662115021644 5ustar davidcdavidc00000000000000envisage-4.4.0/envisage/plugins/text_editor/text_editor_action_set.py0000644000175000017500000000147412252661404026766 0ustar davidcdavidc00000000000000 from envisage.ui.action.api import Action, ActionSet, Group class TextEditorActionSet(ActionSet): """ The default action set for the Text Editor plugin. """ groups = [ Group( id = "TextFileGroup", path = "MenuBar/File", before = "ExitGroup", ) ] actions = [ Action( id = "NewFileAction", name = "New Text File", class_name='envisage.plugins.text_editor.actions.NewFileAction', group='TextFileGroup', path="MenuBar/File", ), Action( id = 'OpenFile', name = "Open Text File...", class_name='envisage.plugins.text_editor.actions.OpenFileAction', group='TextFileGroup', path="MenuBar/File", ), ] envisage-4.4.0/envisage/plugins/text_editor/api.py0000644000175000017500000000014112252661404022763 0ustar davidcdavidc00000000000000from text_editor_action_set import TextEditorActionSet from editor.text_editor import TextEditor envisage-4.4.0/envisage/plugins/text_editor/editor/0000755000175000017500000000000012252662115023132 5ustar davidcdavidc00000000000000envisage-4.4.0/envisage/plugins/text_editor/editor/text_editor.py0000644000175000017500000001446712252661404026052 0ustar davidcdavidc00000000000000""" A text editor. """ # Standard library imports. from os.path import basename # Enthought library imports. from pyface.workbench.api import TraitsUIEditor from pyface.api import FileDialog, CANCEL from traits.api import Code, Instance from traitsui.api import CodeEditor, Group, Item, View from traitsui.key_bindings import KeyBinding, KeyBindings from traitsui.menu import NoButtons # Local imports. from text_editor_handler import TextEditorHandler def _id_generator(): """ A generator that returns the next number for untitled files. """ i = 1 while True: yield(i) i += 1 return _id_generator = _id_generator() class TextEditor(TraitsUIEditor): """ A text editor. """ #### 'TextEditor' interface ############################################### # The key bindings used by the editor. key_bindings = Instance(KeyBindings) # The text being edited. text = Code ########################################################################### # 'IEditor' interface. ########################################################################### def save(self): """ Saves the text to disk. """ # If the file has not yet been saved then prompt for the file name. if len(self.obj.path) == 0: self.save_as() else: f = file(self.obj.path, 'w') f.write(self.text) f.close() # We have just saved the file so we ain't dirty no more! self.dirty = False return def save_as(self): """ Saves the text to disk after prompting for the file name. """ dialog = FileDialog( parent = self.window.control, action = 'save as', default_filename = self.name, wildcard = FileDialog.WILDCARD_PY ) if dialog.open() != CANCEL: # Update the editor. self.id = dialog.path self.name = basename(dialog.path) # Update the resource. self.obj.path = dialog.path # Save it! self.save() return ########################################################################### # 'TraitsUIEditor' interface. ########################################################################### def create_ui(self, parent): """ Creates the traits UI that represents the editor. """ ui = self.edit_traits( parent=parent, view=self._create_traits_ui_view(), kind='subpanel' ) return ui ########################################################################### # 'TextEditor' interface. ########################################################################### def run(self): """ Runs the file as Python. """ # The file must be saved first! self.save() # Execute the code. if len(self.obj.path) > 0: view = self.window.get_view_by_id( 'envisage.plugins.python_shell_view' ) if view is not None: view.execute_command( 'execfile(r"%s")' % self.obj.path, hidden=False ) return def select_line(self, lineno): """ Selects the specified line. """ self.ui.info.text.selected_line = lineno return ########################################################################### # Private interface. ########################################################################### #### Trait initializers ################################################### def _key_bindings_default(self): """ Trait initializer. """ key_bindings = KeyBindings( KeyBinding( binding1 = 'Ctrl-s', description = 'Save the file', method_name = 'save' ), KeyBinding( binding1 = 'Ctrl-r', description = 'Run the file', method_name = 'run' ) ) return key_bindings #### Trait change handlers ################################################ def _obj_changed(self, new): """ Static trait change handler. """ # The path will be the empty string if we are editing a file that has # not yet been saved. if len(new.path) == 0: self.id = self._get_unique_id() self.name = self.id else: self.id = new.path self.name = basename(new.path) f = file(new.path, 'r') self.text = f.read() f.close() return def _text_changed(self, trait_name, old, new): """ Static trait change handler. """ if self.traits_inited(): self.dirty = True return def _dirty_changed(self, dirty): """ Static trait change handler. """ if len(self.obj.path) > 0: if dirty: self.name = basename(self.obj.path) + '*' else: self.name = basename(self.obj.path) return #### Methods ############################################################## def _create_traits_ui_view(self): """ Create the traits UI view used by the editor. fixme: We create the view dynamically to allow the key bindings to be created dynamically (we don't use this just yet, but obviously plugins need to be able to contribute new bindings). """ view = View( Group( Item( 'text', editor=CodeEditor(key_bindings=self.key_bindings) ), show_labels = False ), id = 'envisage.editor.text_editor', handler = TextEditorHandler(), kind = 'live', resizable = True, width = 1.0, height = 1.0, buttons = NoButtons, ) return view def _get_unique_id(self, prefix='Untitled '): """ Return a unique id for a new file. """ id = prefix + str(_id_generator.next()) while self.window.get_editor_by_id(id) is not None: id = prefix + str(_id_generator.next()) return id #### EOF ###################################################################### envisage-4.4.0/envisage/plugins/text_editor/editor/text_editor_handler.py0000644000175000017500000000156212252661404027537 0ustar davidcdavidc00000000000000""" The traits UI handler for the text editor. """ # Enthought library imports. from traitsui.api import Handler class TextEditorHandler(Handler): """ The traits UI handler for the text editor. """ ########################################################################### # 'TextEditorHandler' interface. ########################################################################### # fixme: We need to work out how to create these 'dispatch' methods # dynamically! Plugins will want to add bindings to the editor to bind # a key to an action. def run(self, info): """ Run the text as Python code. """ info.object.run() return def save(self, info): """ Save the text to disk. """ info.object.save() return #### EOF ###################################################################### envisage-4.4.0/envisage/plugins/text_editor/editor/__init__.py0000644000175000017500000000000012252661404025231 0ustar davidcdavidc00000000000000envisage-4.4.0/envisage/plugins/text_editor/actions.py0000644000175000017500000000213112252661404023653 0ustar davidcdavidc00000000000000import logging from apptools.io.api import File from pyface.api import FileDialog, OK from pyface.action.api import Action from traits.api import Any from editor.text_editor import TextEditor logger = logging.getLogger(__name__) class NewFileAction(Action): """ Open a new file in the text editor. """ tooltip = "Create a new file for editing" description = "Create a new file for editing" # The WorkbenchWindow the action is attached to. window = Any() def perform(self, event=None): logger.info('NewFileAction.perform()') self.window.workbench.edit(File(''), kind=TextEditor, use_existing=False) class OpenFileAction(Action): """ Open an existing file in the text editor. """ tooltip = "Open a file for editing" description = "Open a file for editing" def perform(self, event=None): logger.info('OpenFileAction.perform()') dialog = FileDialog(parent=self.window.control, title='Open File') if dialog.open() == OK: self.window.workbench.edit(File(dialog.path), kind=TextEditor) envisage-4.4.0/envisage/plugins/text_editor/__init__.py0000644000175000017500000000000012252661404023743 0ustar davidcdavidc00000000000000envisage-4.4.0/envisage/plugins/text_editor/text_editor_plugin.py0000644000175000017500000000142112252661404026124 0ustar davidcdavidc00000000000000""" Text Editor plugin for the Workbench UI. """ # Enthought library imports. from traits.api import List from envisage.api import Plugin # The plugin's globally unique identifier (also used as the prefix for all # identifiers defined in this module). ID = "envisage.plugins.text_editor" class TextEditorPlugin(Plugin): """ Text Editor plugin for the Workbench UI. """ name = 'Text Editor plugin' #### Contributions made by this plugin ##################################### ACTION_SETS = 'envisage.ui.workbench.action_sets' action_sets = List(contributes_to=ACTION_SETS) def _action_sets_default(self): from envisage.plugins.text_editor.text_editor_action_set import \ TextEditorActionSet return [TextEditorActionSet] envisage-4.4.0/envisage/plugins/tasks/0000755000175000017500000000000012252662115020437 5ustar davidcdavidc00000000000000envisage-4.4.0/envisage/plugins/tasks/__init__.py0000644000175000017500000000000012252661404022536 0ustar davidcdavidc00000000000000envisage-4.4.0/envisage/plugins/tasks/python_shell_plugin.py0000644000175000017500000000721112252661404025100 0ustar davidcdavidc00000000000000""" Module defining a simple Python shell Envisage tasks plugin. This plugin provides a task with a simple Python shell. This shouldn't be confused with a more full-featured shell, such as those provided by IPython. """ # Standard library imports. import logging # Enthought library imports. from traits.api import Str, List, Dict, Instance, Property from pyface.tasks.contrib.python_shell import PythonShellTask from envisage.api import Plugin, ExtensionPoint, IExtensionRegistry from envisage.ui.tasks.api import TaskFactory logger = logging.getLogger() BINDINGS = 'envisage.plugins.python_shell.bindings' COMMANDS = 'envisage.plugins.python_shell.commands' 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 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 we 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 ), ] envisage-4.4.0/envisage/plugins/refresh_code/0000755000175000017500000000000012252662115021742 5ustar davidcdavidc00000000000000envisage-4.4.0/envisage/plugins/refresh_code/refresh_code_plugin_definition.py0000644000175000017500000000731412252661404030537 0ustar davidcdavidc00000000000000""" Text Editor plugin definition. """ # Plugin extension-point imports. from envisage import PluginDefinition, get_using_workbench # Are we using the old UI plugin, or the shiny new Workbench plugin? USING_WORKBENCH = get_using_workbench() # Enthought plugin definition imports. if USING_WORKBENCH: from envisage.workbench.action.action_plugin_definition import \ Action, Group, Location, Menu, WorkbenchActionSet else: from envisage.ui.ui_plugin_definition import \ Action, Group, Menu, UIActions # The plugin's globally unique identifier (also used as the prefix for all # identifiers defined in this module). ID = "envisage.plugins.refresh_code" ############################################################################### # Extensions. ############################################################################### if USING_WORKBENCH: refresh_code = Action( name = "Refresh Code", description = "Refresh application to reflect python code changes", accelerator = "Ctrl+Shift+R", function_name = "traits.util.refresh.refresh", locations = [ Location(path="MenuBar/FileMenu/ExitGroup") ] ) actions = WorkbenchActionSet( id = ID + ".refresh_code_action_set", name = "Refresh Code", # fixme: This menus stuff should go away once we get ticket:312 # resolved. #groups = [ # Group( # id = "ToolsMenuGroup", # location = Location(path="MenuBar") # ), # Group( # id = "RefreshGroup", # location = Location(path="MenuBar/ToolsMenu") # ), #], #menus = [ # Menu( # id = "ToolMenu", # name = "&Tools", # location = Location(path="MenuBar/ToolsMenuGroup"), # groups = [] # ), #], actions = [refresh_code] ) requires = "envisage.workbench.action" else: refresh_code = Action( name = "Refresh Code", # fixme: this should change description = "Refresh application to reflect python code changes", menu_bar_path = "ToolsMenu/additions", # fixme: this should change accelerator = "Ctrl+Shift+R", function_name = "traits.util.refresh.refresh" ) actions = UIActions( # fixme: This menus stuff should go away once we get ticket:312 # resolved. menus = [ Menu( id = "ToolsMenu", name = "&Tools", path = "ToolsGroup", groups = [ Group(id = "Start"), Group(id = "End"), ] ), ], actions = [refresh_code] ) requires = "envisage.ui" ############################################################################### # The plugin definition! ############################################################################### class RefreshCodePluginDefinition(PluginDefinition): # The plugin's globally unique identifier. id = ID # General information about the plugin. name = "Refresh Code Plugin" version = "1.0.0" provider_name = "Enthought Inc" provider_url = "www.enthought.com" enabled = True # The Id's of the plugins that this plugin requires. requires = [requires] # The extension points offered by this plugin, extension_points = [] # The contributions that this plugin makes to extension points offered by # either itself or other plugins. extensions = [actions] #### EOF ###################################################################### envisage-4.4.0/envisage/plugins/refresh_code/actions.py0000644000175000017500000000120112252661404023746 0ustar davidcdavidc00000000000000""" Actions for the refresh code plugin. """ # Enthought library imports. from pyface.action.api import Action class RefreshCode(Action): """ Invoke the 'refresh code' function. """ #### 'Action' interface ################################################### name = 'Refresh Code' description = 'Refresh application to reflect python code changes' accelerator = 'Ctrl+Shift+R' def perform(self, event): """ Perform the action. """ from traits.util.refresh import refresh refresh() return #### EOF ###################################################################### envisage-4.4.0/envisage/plugins/refresh_code/__init__.py0000644000175000017500000000000012252661404024041 0ustar davidcdavidc00000000000000envisage-4.4.0/envisage/plugins/refresh_code/refresh_code_action_set.py0000644000175000017500000000104012252661404027147 0ustar davidcdavidc00000000000000""" The default action set for the refresh code plugin. """ # Enthought library imports. from envisage.ui.action.api import Action, ActionSet # This package PKG = '.'.join(__name__.split('.')[:-1]) refresh_code = Action( class_name = PKG + '.actions.RefreshCode', path = 'MenuBar/Tools', group='additions' ) class RefreshCodeActionSet(ActionSet): """ The default action set for the refresh code plugin. """ actions = [refresh_code] #### EOF ###################################################################### envisage-4.4.0/envisage/plugins/refresh_code/refresh_code_plugin.py0000644000175000017500000000155512252661404026330 0ustar davidcdavidc00000000000000""" A plugin that offers the 'refresh code' functionality. """ # Enthought library imports. from envisage.api import Plugin from traits.api import List class RefreshCodePlugin(Plugin): """ A plugin that offers the 'refresh code' functionality. """ # Extension point Ids. ACTION_SETS = 'envisage.ui.workbench.action_sets' #### Extension points offered by this plugin ############################## # None. #### Contributions to extension points made by this plugin ################ action_sets = List(contributes_to=ACTION_SETS) def _action_sets_default(self): """ Trait initializer. """ from envisage.plugins.refresh_code.refresh_code_action_set import ( RefreshCodeActionSet ) return [RefreshCodeActionSet] #### EOF ###################################################################### envisage-4.4.0/envisage/plugins/__init__.py0000644000175000017500000000000012252661404021411 0ustar davidcdavidc00000000000000envisage-4.4.0/envisage/plugins/event_manager/0000755000175000017500000000000012252662115022125 5ustar davidcdavidc00000000000000envisage-4.4.0/envisage/plugins/event_manager/__init__.py0000644000175000017500000000000012252661404024224 0ustar davidcdavidc00000000000000envisage-4.4.0/envisage/plugins/event_manager/plugin.py0000644000175000017500000000221212252661404023772 0ustar davidcdavidc00000000000000# # # (C) Copyright 2011 Enthought, Inc., Austin, TX # All right reserved. # # This file is open source software distributed according to the terms in LICENSE.txt # """ This module provides a plugin which adds an EventManager to application. If the application does not already have an evt_mgr attribute which is an instance of EventManager, the plugin creates a new EventManager instance, creates a service to offer the event manager and sets the evt_mgr instance of the application to the created event manager. """ # Enthought library imports. from envisage.api import Plugin, ServiceOffer from traits.api import List, Any class EventManagerPlugin(Plugin): """ Plugin to add event manager to the application. """ id = 'envisage.event_manager' SERVICE_OFFERS = 'envisage.service_offers' service_offers = List(contributes_to=SERVICE_OFFERS) def _service_offers_default(self): from encore.events.api import BaseEventManager, get_event_manager evt_mgr_service_offer = ServiceOffer( protocol = BaseEventManager, factory = get_event_manager, ) return [evt_mgr_service_offer] envisage-4.4.0/envisage/plugins/update_checker/0000755000175000017500000000000012252662115022260 5ustar davidcdavidc00000000000000envisage-4.4.0/envisage/plugins/update_checker/update_checker_plugin.py0000644000175000017500000000411212252661404027154 0ustar davidcdavidc00000000000000 # Enthought library imports from envisage.api import Plugin from traits.api import Bool, Callable, Event, Int, Instance, Str, Trait # Local, relative imports from update_info import UpdateInfo # The globally unique ID of this plugin ID = "envisage.plugins.update_checker" class UpdateCheckerPlugin(Plugin): name = "Update Checker Plugin" # The URI to an updates.xml file. Although this can be a local path, # typically it will be a URL. location = Str("localhost") # Update # Should the plugin automatically check for updates? check_automatically = Bool(True) # The frequency with which to check for updates # Can be "startup", which means at application startup, or an integer number # of seconds # TODO: Contribute a preference for this! check_frequency = Trait("startup", "startup", Int) # Whether or not to display a dialog informing the user of available # updates display_dialog = Bool(True) # The UpdateInfo object that contains the actual update information. # Regardless of whether or not a dialog is displayed to the user, this # attribute will be populated with the appropriate information. update_info = Instance(UpdateInfo) # This event fires every time the UpdateCheckerPlugin is invoked and # determines that an application update is available. update_needed = Event() #Event(ApplicationEvent) #======================================================================== # Plugin interface #======================================================================== def start(self): """ Make the appropriate contributions """ self.application.on_trait_change("started", self._check_for_update) def stop(self): """ Clean up """ self.application.on_trait_change("started", self._check_for_update, remove=True) #======================================================================== # Public methods #======================================================================== def _check_for_update(self): pass envisage-4.4.0/envisage/plugins/update_checker/update_checker_dialog.py0000644000175000017500000000017012252661404027115 0ustar davidcdavidc00000000000000 """ This module is a UI for informing the user that an update has been found, and prompting them to download it. """ envisage-4.4.0/envisage/plugins/update_checker/__init__.py0000644000175000017500000000000012252661404024357 0ustar davidcdavidc00000000000000envisage-4.4.0/envisage/plugins/update_checker/tools.py0000644000175000017500000000142612252661404023775 0ustar davidcdavidc00000000000000""" A collection of command-line tools for building encoded update.xml files. """ class InfoFile: update_file = "" version = None checksum = None # A multi-line HTML document describing the changes between # this version and the previous version description = "" @classmethod def from_info_file(filename): return def files2xml(filenames): """ Given a list of filenames, extracts the app version and log information from accompanying files produces an output xml file. There are no constraints or restrictions on the names or extensions of the input files. They just need to be accompanied by a sidecar file named similarly, but with a ".info" extension, that can be loaded by the InfoFile class. """ return envisage-4.4.0/envisage/plugins/update_checker/update_info_view.py0000644000175000017500000000047512252661404026167 0ustar davidcdavidc00000000000000 from traitsui.api import View class UpdateInfoView(View): """ A View for UpdateInfo objects that displays them in a nice HTML widget. Note that this view does NOT include any UI widgets for interacting with the user or prompting for a download; for that, use the UpdateCheckerDialog. """ pass envisage-4.4.0/envisage/plugins/update_checker/update_info.py0000644000175000017500000000332012252661404025125 0ustar davidcdavidc00000000000000 from traits.api import Callable, HasTraits, List, Str class VersionInfo(HasTraits): """ Represents the information about a particular version of an application. """ # The version string that this version = Str() # Customer-facing notes about this version. Typically this is an # HTML document containing the changelog between this version and # the previous version notes = Str() # The location of where to obtain this version. Typically this will # be an HTTP URL, but this can be a URI for a local or LAN item, or # it can be a location = Str() # A function that takes a string (self.version) and returns something # that can be used to compare against the version-parsed version of # another VersionInfo object. version_parser = Callable() def __cmp__(self, other): """ Allows for comparing two VersionInfo objects so they can be presented in version-sorted order. This is where we parse and interpretation of the **version** string attribute. """ # TODO: Do something more intelligent here if self.version_parser is not None: self_ver = self.version_parser(self.version) else: self_ver = self.version if other.version_parser is not None: other_ver = other.version_parser(other.version) else: other_ver = other.version return self_ver < other_ver class UpdateInfo(HasTraits): """ Encapsulates the information about the available update or updates. An update can consist of multiple versions, with each version containing its own information and download URL. """ updates = List(VersionInfo) pass envisage-4.4.0/envisage/plugins/python_shell/0000755000175000017500000000000012252662115022022 5ustar davidcdavidc00000000000000envisage-4.4.0/envisage/plugins/python_shell/api.py0000644000175000017500000000005012252661404023140 0ustar davidcdavidc00000000000000from i_python_shell import IPythonShell envisage-4.4.0/envisage/plugins/python_shell/__init__.py0000644000175000017500000000000012252661404024121 0ustar davidcdavidc00000000000000envisage-4.4.0/envisage/plugins/python_shell/view/0000755000175000017500000000000012252662115022774 5ustar davidcdavidc00000000000000envisage-4.4.0/envisage/plugins/python_shell/view/api.py0000644000175000017500000000005612252661404024120 0ustar davidcdavidc00000000000000from python_shell_view import PythonShellView envisage-4.4.0/envisage/plugins/python_shell/view/namespace_view.py0000644000175000017500000001057512252661404026344 0ustar davidcdavidc00000000000000""" A view containing the contents of a Python shell namespace. """ import types # Enthought library imports. from envisage.plugins.python_shell.api import IPythonShell from envisage.plugins.python_shell.view.python_shell_view import PythonShellView from pyface.workbench.api import View from traits.api import HasTraits, Str, Property, List, Instance, \ DelegatesTo, cached_property from traitsui.api import Item, TableEditor, VGroup from traitsui.api import View as TraitsView from traitsui.table_column import ObjectColumn from traitsui.table_filter import RuleTableFilter from traitsui.table_filter import MenuFilterTemplate from traitsui.table_filter import EvalFilterTemplate from traitsui.table_filter import RuleFilterTemplate # Table editor definition: filters = [EvalFilterTemplate, MenuFilterTemplate, RuleFilterTemplate] table_editor = TableEditor( columns = [ ObjectColumn(name='name'), ObjectColumn(name='type'), ObjectColumn(name='module'), ], editable = False, deletable = False, sortable = True, sort_model = False, filters = filters, search = RuleTableFilter(), ) def type_to_str(obj): """ Make a string out `obj`'s type robustly. """ typ = type(obj) if typ.__name__ == 'vtkobject' or typ is types.InstanceType: typ = obj.__class__ if type.__module__ == '__builtin__': # Make things like int and str easier to read. return typ.__name__ else: name = '%s.%s' % (typ.__module__, typ.__name__) return name def module_to_str(obj): """ Return the string representation of `obj`s __module__ attribute, or an empty string if there is no such attribute. """ if hasattr(obj, '__module__'): return str(obj.__module__) else: return '' class NamespaceView(View): """ A view containing the contents of the Python shell namespace. """ #### 'IView' interface #################################################### # The part's globally unique identifier. id = 'enthought.plugins.python_shell.view.namespace_view' # The view's name. name = 'Namespace' # The default position of the view relative to the item specified in the # 'relative_to' trait. position = 'left' #### 'NamespaceView' interface ############################################ # The bindings in the namespace. This is a list of HasTraits objects with # 'name', 'type' and 'module' string attributes. bindings = Property(List, depends_on=['namespace']) shell_view = Instance(PythonShellView) namespace = DelegatesTo('shell_view') # The default traits UI view. traits_view = TraitsView( VGroup( Item( 'bindings', id = 'table', editor = table_editor, springy = True, resizable = True, ), show_border = True, show_labels = False ), resizable = True, ) ########################################################################### # 'View' interface. ########################################################################### def create_control(self, parent): """ Creates the toolkit-specific control that represents the view. 'parent' is the toolkit-specific control that is the view's parent. """ self.ui = self.edit_traits(parent=parent, kind='subpanel') self.shell_view = self.window.application.get_service(IPythonShell) # 'shell_view' is an instance of the class PythonShellView from the module # envisage.plugins.python_shell.view.python_shell_view. return self.ui.control ########################################################################### # 'NamespaceView' interface. ########################################################################### #### Properties ########################################################### @cached_property def _get_bindings(self): """ Property getter. """ if self.shell_view is None: return [] class item(HasTraits): name = Str type = Str module = Str data = [item(name=name, type=type_to_str(value), module=module_to_str(value)) for name, value in self.shell_view.namespace.items()] return data envisage-4.4.0/envisage/plugins/python_shell/view/python_shell_view.py0000644000175000017500000001723312252661404027116 0ustar davidcdavidc00000000000000""" A view containing an interactive Python shell. """ # Standard library imports. import logging, sys # Enthought library imports. from envisage.api import IExtensionRegistry from envisage.api import ExtensionPoint from envisage.plugins.python_shell.api import IPythonShell from pyface.api import PythonShell from pyface.workbench.api import View from traits.api import Any, Event, Instance, Property, DictStrAny, implements # Setup a logger for this module. logger = logging.getLogger(__name__) class PseudoFile ( object ): """ Simulates a normal File object. """ def __init__(self, write): self.write = write def readline(self): pass def writelines(self, l): map(self.write, l) def flush(self): pass def isatty(self): return 1 class PythonShellView(View): """ A view containing an interactive Python shell. """ implements(IPythonShell) #### '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 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 #### EOF ###################################################################### envisage-4.4.0/envisage/plugins/python_shell/view/__init__.py0000644000175000017500000000005612252661404025106 0ustar davidcdavidc00000000000000from python_shell_view import PythonShellView envisage-4.4.0/envisage/plugins/python_shell/i_python_shell.py0000644000175000017500000000134412252661404025416 0ustar davidcdavidc00000000000000""" A simple interface for the Python shell. """ # Enthought library imports. from traits.api import Interface class IPythonShell(Interface): """ A simple interface for the Python shell. """ def bind(self, name, value): """ Binds a name to a value in the interpreter's namespace. """ def execute_command(self, command, hidden=True): """ Execute a command in the interpreter. """ def execute_file(self, path, hidden=True): """ Execute a file in the interpreter. """ def lookup(self, name): """ Returns the value bound to a name in the interpreter's namespace. """ #### EOF ###################################################################### envisage-4.4.0/envisage/plugins/python_shell/python_shell_plugin.py0000644000175000017500000000440212252661404026462 0ustar davidcdavidc00000000000000""" The interactive Python shell plugin. """ # Enthought library imports. from envisage.api import ExtensionPoint, Plugin from traits.api import Dict, List, Str class PythonShellPlugin(Plugin): """ The interactive Python shell plugin. """ # Extension point Ids. BINDINGS = 'envisage.plugins.python_shell.bindings' COMMANDS = 'envisage.plugins.python_shell.commands' VIEWS = 'envisage.ui.workbench.views' #### '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 ############################## 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) 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.python_shell_view import PythonShellView from view.namespace_view import NamespaceView return [PythonShellView, NamespaceView] #### EOF ###################################################################### envisage-4.4.0/envisage/plugins/remote_editor/0000755000175000017500000000000012252662115022153 5ustar davidcdavidc00000000000000envisage-4.4.0/envisage/plugins/remote_editor/communication/0000755000175000017500000000000012252662115025020 5ustar davidcdavidc00000000000000envisage-4.4.0/envisage/plugins/remote_editor/communication/client.py0000644000175000017500000002276612252661404026665 0ustar davidcdavidc00000000000000# Standard library imports import logging import os import select import socket import sys from threading import Thread # ETS imports from traits.api import HasTraits, Int, Str, Bool, Instance, List, \ Tuple, Enum from envisage.plugins import remote_editor # Local imports from server import Server from util import accept_no_intr, get_server_port, receive, send_port, \ spawn_independent, MESSAGE_SEP logger = logging.getLogger(__name__) class ClientThread(Thread): """ A thread for listening for commands from the server. """ def __init__(self, client): Thread.__init__(self) self.client = client self._finished = False def run(self): # Get the server port, spawning it if necessary server_port = get_server_port() if server_port == -1 or not Server.ping(server_port, timeout=5): if len(self.client.server_prefs): # Spawn the server logger.info("Client spawning Server...") sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.settimeout(5) sock.bind(('localhost', 0)) sock.listen(1) port = sock.getsockname()[1] args = self.client.server_prefs + ( port, ) code = "from envisage.plugins.remote_editor.communication." \ "server import main; main(r'%s', '%s', %i)" % args spawn_independent([sys.executable, '-c', code]) # Await a reponse from the server try: server, address = accept_no_intr(sock) try: command, arguments = receive(server) if command == "__port__": self.client._server_port = int(arguments) else: raise socket.error finally: # Use try...except to handle timeouts try: server.shutdown(socket.SHUT_RD) except: pass except socket.error, e: logger.error(repr(e)) logger.error("Client spawned a non-responsive Server! " \ "Unregistering...") self.client.error = True self.client.unregister() return finally: sock.close() else: logger.error("Client could not contact the Server and no " \ "spawn command is defined. Unregistering...") self.client.error = True self.client.unregister() return else: self.client._server_port = server_port # Create the socket that will receive commands from the server sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.bind(('localhost', 0)) sock.listen(1) self.client._port = sock.getsockname()[1] # Register with the server port = str(self.client._port) arguments = MESSAGE_SEP.join((port, self.client.self_type, self.client.other_type)) self.client.error = not send_port(self.client._server_port, 'register', arguments) self.client.registered = True # Send queued commands (these can only exist if we spawned the server) for command, args in self.client._queue: arguments = MESSAGE_SEP.join((port, command, args)) self.client.error = not send_port(self.client._server_port, 'send', arguments) self.client._queue = [] # Start the loop to listen for commands from the Server logger.info("Client listening on port %i..." % self.client._port) try: while not self._finished: server, address = accept_no_intr(sock) # Reject non-local connections if address[0] != '127.0.0.1': msg = "Client on port %s received connection from a " \ "non-local party (port %s). Ignoring..." logger.warning(msg % (port, address[0])) continue # Receive the command from the server self.client.error = False command, arguments = receive(server) msg = r"Client on port %s received: %s %s" logger.debug(msg, port, command, arguments) # Handle special commands from the server if command == "__orphaned__": self.client.orphaned = bool(int(arguments)) elif command == "__error__": error_status = arguments[0] error_message = '' if len(arguments) > 0: error_message = arguments[1:] logger.warning("Error status received from the server: " \ "%s\n%s" % (error_status, error_message)) self.client.error = bool(int(error_status)) # Handle other commands through Client interface else: if self.client.ui_dispatch == 'off': self.client.handle_command(command, arguments) else: if self.client.ui_dispatch == 'auto': from pyface.gui import GUI else: exec('from pyface.ui.%s.gui import GUI' % self.client.ui_dispatch) GUI.invoke_later(self.client.handle_command, command, arguments) finally: self.client.unregister() def stop(self): self._finished = True class Client(HasTraits): """ An object that communicates with another object through a Server. """ # The preferences file path and node path to use for spawning a Server. If # this is not specified it will not be possible for this Client to spawn # the server. server_prefs = Tuple((os.path.join(remote_editor.__path__[0], "preferences.ini"), "enthought.remote_editor"), Str, Str) # The type of this object and the type of the desired object, respectively self_type = Str other_type = Str # Specifies how 'handle_command' should be called. If 'auto', use the # dispatch method appropriate for the toolkit Traits is using. Failure to # set this variable as appropriate will likely result in crashes. ui_dispatch = Enum('off', 'auto', 'wx', 'qt4') # Whether this client has been registered with the Server. Note that this is # *not* set after the 'register' method is called--it is set when the Server # actually receives the register command, which happens asynchronously. registered = Bool(False) # The client's orphaned status orphaned = Bool(True) # The client's error state. This is set to True after a failure to # communicate with the server or a failure by the server to communicate with # this object's counterpart. error = Bool(False) # Protected traits _port = Int _server_port = Int _communication_thread = Instance(ClientThread) _queue = List(Tuple(Str, Str)) def register(self): """ Inform the server that this Client is available to receive commands. """ if self._communication_thread is not None: raise RuntimeError, "'register' has already been called on Client!" self._communication_thread = ClientThread(self) self._communication_thread.setDaemon(True) self._communication_thread.start() def unregister(self): """ Inform the server that this Client is no longer available to receive commands. Note that it is poor etiquette not to communicate when a Client is becoming unavailable. Calling 'unregister' when a Client is not registered has no effect. """ if self._communication_thread is not None: self._communication_thread.stop() self._communication_thread = None send_port(self._server_port, 'unregister', str(self._port)) self._port = 0 self._server_port = 0 self.registered = False self.orphaned = True def send_command(self, command, arguments=''): """ Send a command to the server which is to be passed to an object of the appropriate type. """ if self._communication_thread is None: raise RuntimeError, "Client is not registered. Cannot send command." msg = r"Client on port %i sending: %s %s" logger.debug(msg, self._port, command, arguments) args = MESSAGE_SEP.join((str(self._port), command, arguments)) if self.registered: self.error = not send_port(self._server_port, 'send', args) else: self._queue.append((command, arguments)) def handle_command(self, command, arguments): """ This function should take a command string and an arguments string and do something with them. It should return True if the command given was understood; otherwise, False. """ raise NotImplementedError envisage-4.4.0/envisage/plugins/remote_editor/communication/server.py0000644000175000017500000003055512252661404026710 0ustar davidcdavidc00000000000000# Standard library imports import os, sys import logging import socket # ETS imports from apptools.preferences.api import Preferences from traits.api import HasTraits, HasStrictTraits, Int, Str, List, \ Dict, Tuple, Instance # Local imports from util import accept_no_intr, receive, send_port, spawn_independent, \ MESSAGE_SEP, LOCK_PATH, LOG_PATH logger = logging.getLogger("communication") logger.setLevel(logging.DEBUG) logging.basicConfig(filename=LOG_PATH) class PortInfo(HasStrictTraits): """ Object to store information on how a port is being used. """ port = Int type = Str other_type = Str def __str__(self): return "Port: %i, Type: %s, Other type: %s" % \ (self.port, self.type, self.other_type) class Server(HasTraits): """ A socket protocal that facilates two objects communicating with each other. The only instance methods that should be called in ordinary use are 'init' and 'main'. Communication should be done through the methods on 'Client' objects. """ spawn_commands = Dict(Str, Str) _port = Int _sock = Instance(socket.socket) _port_map = Dict(Int, Instance(PortInfo)) _orphans = List(Instance(PortInfo)) _pairs = Dict(Instance(PortInfo), Instance(PortInfo)) # Commands that have been queued for spawned process # desired_type -> list of (command, arguments) _queue = Dict(Str, List(Tuple(Str, Str))) def init(self, pref_path='', pref_node=''): """ Read a configuration file and attempt to bind the server to the specified port. """ # Bind to port and write port to lock file self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self._sock.bind(('localhost', 0)) self._port = self._sock.getsockname()[1] f = open(LOCK_PATH, 'w') f.write(str(self._port)) f.close() # Read configuration file if not pref_path: return if os.path.exists(pref_path): prefs = Preferences(filename=pref_path) if prefs.node_exists(pref_node): spawn_node = prefs.node(pref_node) for key in spawn_node.keys(): cmd = spawn_node.get(key).strip() if cmd.startswith('python '): cmd = cmd.replace('python', sys.executable, 1) self.spawn_commands[key] = cmd else: msg = "Server could not locate preference node '%s.'" logger.error(msg % pref_node) else: msg = "Server given non-existent preference path '%s'." logger.error(msg % pref_path) def main(self, port=0): """ Starts the server mainloop. If 'port' is specified, the assumption is that this Server was spawned from an object on said port. The Server will inform the Client that it has been created, and if fails to contact the client, this function will return. """ try: # Start listening *before* we (potentially) inform the spawner that # we are working. This means that if the spawner tries to register, # we will be ready. logger.info("Server listening on port %i..." % self._port) self._sock.listen(5) self._sock.settimeout(300) # If necessary, inform the launcher that we have initialized # correctly by telling it our port if port: if not send_port(port, "__port__", str(self._port), timeout=5): msg = "Server could not contact spawner. Shutting down..." logger.warning(msg) return # Start the mainloop while True: try: client, address = accept_no_intr(self._sock) except socket.timeout: # Every 5 minutes of inactivity, we trigger a garbage # collection. We do this to make sure the server doesn't # stay on, with dead process as zombies. self._gc() continue try: if address[0] != '127.0.0.1': msg = "Server received connection from a non-local " \ "party (port %s). Ignoring..." logger.warning(msg, address[0]) continue command, arguments = receive(client) logger.debug("Server received: %s %s", command, arguments) if command == "send": port, command, arguments = arguments.split(MESSAGE_SEP) self._send_from(int(port), command, arguments) elif command == "register": port, type, other_type = arguments.split(MESSAGE_SEP) self._register(int(port), type, other_type) elif command == "unregister": self._unregister(int(arguments)) elif command == "ping": self._send_to(int(arguments), "__status__", "1") elif command == "spawn": self._spawn(arguments) else: logger.error("Server received unknown command: %s %s", command, arguments) finally: client.close() finally: self._sock.close() @staticmethod def ping(server_port, timeout=1, error_only=False): """ Returns whether the server is running on 'server_port'. If error_only, return False only if there was an error (ie the socket is closed). """ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.settimeout(timeout) sock.bind(('localhost', 0)) sock.listen(1) try: port = str(sock.getsockname()[1]) send_port(server_port, "ping", port, timeout=timeout) try: server, address = accept_no_intr(sock) try: command, arguments = receive(server) return command == "__status__" and bool(int(arguments)) finally: try: server.shutdown(socket.SHUT_RD) except: pass except socket.error: # 'sock' may have timed out, so use try..except. try: sock.shutdown(socket.SHUT_RD) except: pass return False except: try: sock.shutdown(socket.SHUT_RD) except: pass def _spawn(self, object_type): """ Attempt to spawn an process according the specified type. Returns whether this was sucessful. """ try: command = self.spawn_commands[object_type] except KeyError: msg = "No spawn command is defined for object type '%s'." \ % object_type logger.warning(msg) return msg try: logging.info("Server spawning Client of type '%s.'" % object_type) spawn_independent(command, shell=True) except OSError: msg = "Error spawning process for '%s' with command '%s'." \ % (object_type, command) logger.error(msg) return msg return False def _match(self, port): """ Attempt to match a registered client on 'port' to another registered client of the same type. """ # Try to find a compatible orphan info = self._port_map[port] for orphan in self._orphans: if info.other_type == orphan.type: # Ping the orphan to see if its alive: if not self._send_to(orphan.port, '__keepalive__', ''): continue # Save information about the matching self._orphans.remove(orphan) self._pairs[info] = orphan self._pairs[orphan] = info # Dispatch orphaned status to clients self._send_to(port, "__orphaned__", "0") self._send_to(orphan.port, "__orphaned__", "0") # Check command queue and dispatch, if necessary for command, arguments in self._queue.pop(info.type, []): self._send_to(port, command, arguments) return orphan # Otherwise, the object becomes an orphan self._orphans.append(info) return None def _register(self, port, object_type, other_type): """ Register a port of 'object_type' that wants to be paired with another object of 'other_type'. These types are simply strings. Calling 'register' on an already registered port has no effect. """ # Only continue if this object is not already registered if port in self._port_map: return info = PortInfo(port=port, type=object_type, other_type=other_type) self._port_map[port] = info self._match(port) def _unregister(self, port): """ Unregister a port. Calling 'unregister' on a port that is not registered has no effect. """ try: info = self._port_map.pop(port) except KeyError: return if info in self._pairs: other = self._pairs.pop(info) if other in self._pairs: self._pairs.pop(other) self._orphans.append(other) self._send_to(other.port, "__orphaned__", "1") else: self._orphans.remove(info) # If we have nobody registered, terminate the server. if len(self._port_map) == 0: logger.info("No registered Clients left. Server shutting down...") sys.exit(0) def _gc(self): """ Garbage collection of the processes. Check that all the orphaned processes are still responsive, and if not, unregister them. """ for object_info in self._orphans: self._send_to(object_info.port, '__keepalive__', '') # _send_to will automatically unregister the port. def _send_from(self, port, command, arguments): """ Send a command from an object on the specified port. """ try: object_info = self._port_map[port] except KeyError: msg = "Server received a 'send' command from an unregistered " \ "object (port %s)." logger.warning(msg % port) return try_spawn = False if object_info in self._pairs: other = self._pairs[object_info] while not self._send_to(other.port, command, arguments): # The object will automatically be orphaned by _send_to in the # event of a communication failure. Try to find another match. if object_info in self._orphans: self._orphans.remove(object_info) other = self._match(port) if other is None: try_spawn = True break else: try_spawn = True if try_spawn: queue = self._queue.get(object_info.other_type) if queue: queue.append((command, arguments)) else: error_msg = self._spawn(object_info.other_type) if error_msg: self._send_to(port, '__error__', '1' + error_msg) else: self._queue[object_info.other_type] = [(command, arguments)] self._match(port) def _send_to(self, port, command, arguments): """ Send a command to an object on the specified port. Returns whether the command was sent sucessfully. """ status = send_port(port, command, arguments) if not status: msg = "Server failed to communicate with client on port %i. " \ "Unregistering..." logger.warning(msg % port) self._unregister(port) return status def main(pref_path, pref_node, *arg, **kw): server = Server() server.init(pref_path, pref_node) server.main(*arg, **kw) envisage-4.4.0/envisage/plugins/remote_editor/communication/__init__.py0000644000175000017500000000000012252661404027117 0ustar davidcdavidc00000000000000envisage-4.4.0/envisage/plugins/remote_editor/communication/tests/0000755000175000017500000000000012252662115026162 5ustar davidcdavidc00000000000000envisage-4.4.0/envisage/plugins/remote_editor/communication/tests/test_communication.py0000644000175000017500000000662412252661404032450 0ustar davidcdavidc00000000000000# Standard library imports import os import unittest from time import sleep from threading import Thread # ETS imports from traits.api import Int, Str # Local imports from envisage.plugins.remote_editor.communication.client import Client from envisage.plugins.remote_editor.communication.server import Server from envisage.plugins.remote_editor.communication.util import \ get_server_port, LOCK_PATH class TestClient(Client): command = Str arguments = Str error_count = Int(0) def handle_command(self, command, arguments): self.command = command self.arguments = arguments def _error_changed(self, error): if error: self.error_count += 1 class TestThread(Thread): def run(self): self.server = Server() self.server.init() self.server.main() class CommunicationTestCase(unittest.TestCase): def setUp(self): """ Make sure no old lock files exist prior to run. """ if os.path.exists(LOCK_PATH): os.remove(LOCK_PATH) def tearDown(self): """ Make sure no old lock files are left around. """ if os.path.exists(LOCK_PATH): os.remove(LOCK_PATH) def testCommunication(self): """ Can the Server communicate with Clients and handle errors appropriately? """ # Test server set up # Does the ping operation work when the Server is not running? self.assert_(not Server.ping(get_server_port())) # Set up server thread serverThread = TestThread() serverThread.setDaemon(True) serverThread.start() sleep(.5) self.assert_(os.path.exists(LOCK_PATH)) # Test normal operation self.assert_(Server.ping(get_server_port())) client1 = TestClient(self_type='client1', other_type='client2') client1.register() client2 = TestClient(self_type='client2', other_type='client1') client2.register() sleep(.5) self.assert_(not(client1.orphaned or client2.orphaned)) client1.send_command("foo", "bar") sleep(.1) self.assertEqual(client2.command, "foo") self.assertEqual(client2.arguments, "bar") client1.unregister() sleep(.1) self.assert_(client1.orphaned and client2.orphaned) client1.register() sleep(.1) self.assert_(not(client1.orphaned or client2.orphaned)) # Simulated breakage -- does the Server handle unexpected communication # failure? # Have client1 'die'. We send the dummy command to force its connection # loop to terminate after the call to 'stop'. (In Python we can't just # kill the thread, which is really what we want to do in this case.) client1._communication_thread.stop() sleep(.1) serverThread.server._send_to(client1._port, "dummy", "") sleep(.1) # The Server should inform client1 that it could not complete its # request client2.send_command("foo", "bar") sleep(.1) self.assert_(client2.orphaned) self.assertEqual(client2.error_count, 1) if __name__ == '__main__': """ Run the unittest, but redirect the log to stderr for convenience. """ import logging console = logging.StreamHandler() console.setLevel(logging.DEBUG) logging.getLogger("communication").addHandler(console) unittest.main() envisage-4.4.0/envisage/plugins/remote_editor/communication/tests/__init__.py0000644000175000017500000000000012252661404030261 0ustar davidcdavidc00000000000000envisage-4.4.0/envisage/plugins/remote_editor/communication/util.py0000644000175000017500000001135012252661404026347 0ustar davidcdavidc00000000000000# Standard library imports from errno import EINTR import os import select import socket from subprocess import Popen import sys import csv import StringIO # ETS imports from traits.etsconfig.api import ETSConfig # An obscure ASCII character that we used as separators in socket streams MESSAGE_SEP = chr(7) # 'bell' character # The location of the server lock file and the communication log LOCK_PATH = os.path.join(ETSConfig.application_data, 'remote_editor_server.lock') LOG_PATH = os.path.join(ETSConfig.application_data, 'remote_editor_server.log') def quoted_split(s): f = StringIO.StringIO(s) split = csv.reader(f, delimiter=' ', quotechar='"').next() return split def spawn_independent(command, shell=False): """ Given a command suitable for 'Popen', open the process such that if this process is killed, the spawned process survives. `command` is either a list of strings, with the first item in the list being the executable and the rest being its arguments, or a single string containing the executable and its arguments. In the latter case, any argument that contains spaces must be delimited with double-quotes. """ if sys.platform == 'win32': if isinstance(command, basestring): command = 'start /b ' + command else: command.insert(0, 'start') command.insert(1, '/b') Popen(command, shell=True) elif sys.platform == 'darwin': pid = os.fork() if pid: return else: os.setpgrp() if isinstance(command, basestring): tmp = quoted_split(command) else: tmp = command os.execv(tmp[0], tmp) else: pid = os.fork() if pid: return else: os.setpgrp() Popen(command, shell=shell) sys.exit(0) def get_server_port(): """ Reads the server port from the lock file. If the file does not exist returns -1. """ if os.path.exists(LOCK_PATH): f = open(LOCK_PATH, 'r') try: return int(f.read().strip()) finally: f.close() else: return -1 def accept_no_intr(sock): """ Call sock.accept such that if it is interrupted by an EINTR ("Interrupted system call") signal or a KeyboardInterrupt exception, it is re-called. """ while True: try: return sock.accept() except socket.error, err: if err[0] != EINTR: raise except KeyboardInterrupt: pass def send(sock, command, arguments=''): """ Send a command with arguments (both strings) through a socket. This information is encoded with length information to ensure that everything is received. """ sent, total = 0, 0 msg = command + MESSAGE_SEP + arguments msg = str(len(msg)) + MESSAGE_SEP + msg length = len(msg) while total < length: msg = msg[sent:] sent = sock.send(msg) if not sent: raise socket.error total += sent def send_port(port, command, arguments='', timeout=None): """ A send a command to a port. Convenience function that uses 'send'. """ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) if timeout is not None: sock.settimeout(timeout) errno = sock.connect_ex(('localhost', port)) if errno: sock.close() return False try: send(sock, command, arguments) except socket.error: return False finally: try: sock.shutdown(socket.SHUT_WR) except: pass return True def receive(sock): """ Receive a command with arguments from a socket that was previously sent information with 'send'. """ index = -1 chunk, length = '', '' received = False while not received: # Use select to query the socket for activity before calling sock.recv. # Choose a timeout of 60 seconds for now. readers, writers, in_error = select.select([sock], [], [], 60) for rsock in readers: while index == -1: length += chunk chunk = rsock.recv(4096) if not chunk: raise socket.error index = chunk.find(MESSAGE_SEP) length = int(length + chunk[:index]) msg = chunk[index+1:] while len(msg) < length: chunk = rsock.recv(length - len(msg)) if not chunk: raise socket.error msg += chunk received = True command, sep, arguments = msg.partition(MESSAGE_SEP) return command, arguments envisage-4.4.0/envisage/plugins/remote_editor/envisage_remote_editor.py0000644000175000017500000000410512252661404027247 0ustar davidcdavidc00000000000000""" An implementation of a client controlling a remote editor, but also using envisage to retrieve an execution engine, and run the commands from the editor. """ # Standard library imports import logging # Enthought library imports from traits.api import Instance from envisage.api import Application from envisage.plugins.python_shell.api import IPythonShell from pyface.api import GUI # Local imports from envisage.plugins.remote_editor.remote_editor_controller import \ RemoteEditorController logger = logging.getLogger(__name__) class EnvisageRemoteEditorController(RemoteEditorController): """ An implementation of a client controlling a remote editor, but also using envisage to retrieve an execution engine, and run the commands from the editor. """ # Tell the Client code to play well with the wx or Qt4 event loop ui_dispatch = 'auto' # A reference to the Envisage application. application = Instance(Application) ########################################################################### # EnvisageRemoteEditorController interface ########################################################################### def run_file(self, path): """ Called by the server to execute a file. """ shell = self.application.get_service(IPythonShell) shell.execute_file(path, hidden=False) def run_text(self, text): """ Called by the server to execute text. """ shell = self.application.get_service(IPythonShell) shell.execute_command(text, hidden=False) ########################################################################### # Enshell Client interface ########################################################################### def handle_command(self, command, arguments): """ Hande commands coming in from the server. """ if command == "run_file": self.run_file(arguments) return True elif command == "run_text": self.run_text(arguments) return True return False envisage-4.4.0/envisage/plugins/remote_editor/api.py0000644000175000017500000000021712252661404023276 0ustar davidcdavidc00000000000000from i_remote_editor import IRemoteEditor from i_remote_shell import IRemoteShell from remote_editor_controller import RemoteEditorController envisage-4.4.0/envisage/plugins/remote_editor/i_remote_shell.py0000644000175000017500000000071512252661404025522 0ustar davidcdavidc00000000000000""" Interface definition for the Remote shell """ # Enthought library imports. from traits.api import Interface class IRemoteShell(Interface): """ Interface definition for the remote shell """ def run_file(self, path): """ Runs a file in the remote shell. """ def run_text(self, text): """ Runs a string in the remote shell. """ #### EOF ###################################################################### envisage-4.4.0/envisage/plugins/remote_editor/remote_shell_controller.py0000644000175000017500000000154512252661404027457 0ustar davidcdavidc00000000000000""" A client controlling a remote shell. """ # Enthought library imports from traits.api import implements # Local imports from envisage.plugins.remote_editor.communication.client import Client from i_remote_shell import IRemoteShell class RemoteShellController(Client): """ A Client used to control a remote shell. """ implements(IRemoteShell) #------------------------------------------------------ # Client interface #------------------------------------------------------ self_type = "python_editor" other_type = "python_shell" #------------------------------------------------------ # RemoteShell interface #------------------------------------------------------ def run_file(self, path): self.send_command('run_file', path) def run_text(self, text): self.send_command('run_text', text) envisage-4.4.0/envisage/plugins/remote_editor/i_remote_editor.py0000644000175000017500000000073012252661404025676 0ustar davidcdavidc00000000000000""" Interface definition for the Remote editor """ # Enthought library imports. from traits.api import Interface class IRemoteEditor(Interface): """ Interface definition for the Namespace view """ def new_file(self): """ Creates a new file in the remote editor. """ def open_file(self, filename): """ Opens a file in the remote editor. """ #### EOF ###################################################################### envisage-4.4.0/envisage/plugins/remote_editor/editor_plugins/0000755000175000017500000000000012252662115025202 5ustar davidcdavidc00000000000000envisage-4.4.0/envisage/plugins/remote_editor/editor_plugins/editra/0000755000175000017500000000000012252662115026452 5ustar davidcdavidc00000000000000envisage-4.4.0/envisage/plugins/remote_editor/editor_plugins/editra/images/0000755000175000017500000000000012252662115027717 5ustar davidcdavidc00000000000000envisage-4.4.0/envisage/plugins/remote_editor/editor_plugins/editra/images/image_LICENSE.txt0000644000175000017500000000135412252661404032707 0ustar davidcdavidc00000000000000The icons are mostly derived work from other icons. As such they are licensed accordingly to the original license: Enthought: BSD-like Unless stated in this file, icons are work of enthought, and are released under BSD-like license. Files and orginal authors: ---------------------------------------------------------------- python_run_16x16.png | Enthought python_run_24x24.png | Enthought python_run_32x32.png | Enthought python_run_48x48.png | Enthought python_runsel_16x16.png | Enthought python_runsel_24x24.png | Enthought python_runsel_32x32.png | Enthought python_runsel_48x48.png | Enthought envisage-4.4.0/envisage/plugins/remote_editor/editor_plugins/editra/images/python_runsel_24x24.png0000644000175000017500000000305112252661404034200 0ustar davidcdavidc00000000000000‰PNG  IHDRàw=øsRGB®ÎébKGDÿÿÿ ½§“ pHYs  šœtIMEØ (Э%­©IDATHǵU]lUþîÜéììnÛÝÒh)mÑÚ–Vm¡€Í&µÚˆ¶¨Äú€Š} >˜(‘G¬ 11Ä Á´ 1 ¤.±€LL)»[[+SJm»?uwfgî½>ìöG(‰/žädæÞïÜóïœ*€,þ“dßó¸m÷įF[è&þý¯—;Fc±µ,K@)ªª —Ë%ššš·îÅ-k…;r£ûæ'~ÿP×1 @¿°ßï'œó¥@`e(Ê@0Ãår {<žqI’¢,-ʙغkGö3o<ýjõKK(¬WŸ—Êð6BÈ„‚ÊÊÊËš¦Ô4í„×ëýFÓ´MÓÎUUUõI’”+„×ôVù•Úm÷æÏX“X¡zÐÿ[_¼ë›®kÃ?ÿ½gìªõ€)|6@OOÂ9¯Våbyyùmƒ0 £ŠRêÿWac,‚þØ%¸=²ýÙ7Ÿ[[ÿrõñÊç{é˰€köœeYÜ0Œ±öööÇZ[[ƒÁ9àÖÖÖwÛÚÚ6%‰°eY·S „ÀtbL rSYVu}Ͷ{x°{äƒß;ôO¡cL×u=Œx<V\\\´ð~cc〦iý~¿¼¬¬LȇcÑB20Œè½H“Tòè®M¹¡‘Èž3…gZÆ{þ~³¡¡á‚×ëÚ¿¿®(Jbá½ôôt½¤¤$ …Ì 6p E)rÀ¹H:ãsn˜1ôG¯@ÏšPv¼Ör÷#¯Ô¾g»ÜîO\Üx¨ýÐ˲È„¢ªªÈÍÍ%É>ÈI–sf¥ê(n#ak‘x²«òÏÖìòž;éûòb íTiwIpcEÝÂà ŒR*@vä: ÀÀ-‘Äbñ ã370EGQÓ\½dmÃúí_?5F!9%ÄqØ;K).ÀYè„À™cB`Α¢Lˆä\@À@8tnG¶ôÄ Oº†þDy³‚Á©ëPlª¼*k•5?*ŠÄ(SžÔÁ9‡àI5-L„€"D")…Û½f‚a0p3SªÝ—ÍTg 9@8̃ip˜CB¿³ç8òñÀªM˜ÇNùª÷©ªÝºd¡ÐU·ê6Sƒ“ÌÕcQ!À,Îļóù,ÜK°®t#¦þš4ÛŽ¹™¾dù;»·PJm¥x”ƒ$1çÀ“èWÎ,ÿœ 0&À-™*X[Q‹L5SøNŸžøKŸþȵ¹êóŽ]Âcó„P lÕjܵ¬—~î ÷Ýèó©¼uï}ZýL}¸6R»|‘=â ‹TêÉ>HK‚Ëi˜$ QœŒ<¬)¬ÁÀ@_¬íÄá^¹fÙëY;×r‘;þù"±ïÇ}Š‘aEQ(!dᨡ¦iÊ333dvဦ¥”¡HÈÓ±fÅzÌDÂÖ‰SGG̺/kO­/ŽøD'éŒ@WW—<00 8ÎŒ‚‚{ffæ\€h4ª Ù#‘HÚùóç™ $ °) Ö¬\§ä¹_:&FâcŸe?ÿÇñ 6éƒod~\Ûl6ZRR’öìÙÊÞÞÞò-[¶Ìðù|e–eyã”ÒaâÞ<÷ññ+›ë¶åÿtµ#|Më¾`óí“W/½9KÇ­ ÝÝÝ­2ÆÖÛíöÎ;íƒx<~Ÿ$I×d™Ì?Òq°Ç*v¿ázic`!‹™$IœR€½{÷ZZZpôèQ,ÈÒdŒ R/ê ô®þÊŒºŠ~zè.ü‹Ž;ìdÊ9_  ¦§§g•4«&ÃívVTT B¦drÔQWêwÃ=²‹Y"‘ࣣ£cŒ±iY––eÍ6— ”Z¦iš×¯_·š››Å?[\Í0X0X5IEND®B`‚envisage-4.4.0/envisage/plugins/remote_editor/editor_plugins/editra/images/python_runsel_16x16.png0000644000175000017500000000156612252661404034213 0ustar davidcdavidc00000000000000‰PNG  IHDRóÿasRGB®ÎébKGDÿÿÿ ½§“ pHYs  šœtIMEØ ,³¬$°öIDAT8Ë]“_h›UÆßù¾|iÒ&$u«%¬[MÖg§q󹽈E¶‚ ìBa0Ä AZodà 2±Þtié"þ©ÐÉì¤Ùpk Æ–lËÚt¶_šïß9^4©YŸ›—÷=ïyxž÷=Gc! Ü|Ú0==-,ËꙚšê/•J‰………Àðð0étúz>Ÿ¿¥5ûâÙw×þ]¶NÇïŸj€˜™™Ñ¥”C†as¹Üq¡PÀ÷ý¢™«g^zV¼þþËï~ýÔùôáΣ@'€B3 Ãl]jº®E»ÜÛêo}÷sñ½GNæ¿ÉLÝñD 366h*:΃8¬ŠmEÖÜVCÿ>–=öyþ'/³ôåg_ŒuÚ¶}[ë PÝ$èoúP %¾”,;7©ï,Çro=ÿÆÀ¨yvü©üšµÖNàÒˆˆ¡¤Äs$JµÎ.’[Ú¡}]‰Gû^+ü~šH_€†Ó ÃìСô¾ØÛ1˜ê=QÖÂÒ—ø¾Âwž#ñÆ5û®ØùH7Oæö3÷GW3/v;Š{6w¶r·Žô%JnÚÐ4 M€išˆpËÕñ®^bч¡P¨i¡?†ÒÁ­ûø®Ä÷ÒW(BÒ»‰¨¨S½¸n~÷ñ¯L~û:ºþ¢P @)Ráy’x¨‡Ç÷òÊ×Ê‹^%:þjêm슇ŽB[C¿a 4 ºBRïSÕÊí›çÿšûjôà’÷’Y¡Øz°Ëheš¡ÙÛËÆš½zîÊì÷\âÓWªïÔª¶½ƒ+[”ÂдT÷þËKþ2¹:±ԉپK¥¡ÊPöí@ë/´¢ã8ö–‚¹«?Wé‰|<2ðã9fWÐJÀu]ÏuÝ“““/d³ÙÄüü¼žÉd˜˜˜¨ŒŒŒ” ×¾ùôá(Ñg´3N»Îõõu·X,–-ËZ®×뗓ɤnÛ¶´mÛ]\\tþàHoc\owIEND®B`‚envisage-4.4.0/envisage/plugins/remote_editor/editor_plugins/editra/images/python_run_24x24.png0000644000175000017500000000244112252661404033476 0ustar davidcdavidc00000000000000‰PNG  IHDRàw=øsRGB®ÎébKGDÿÿÿ ½§“ pHYs  šœtIMEØ $.Ý=â9¡IDATHǵ•YlTeÇw›¥í´ÓMºÉN¶‚ -ÅFÄDmˆ‘h‚‰QÔø€õI ¾ˆ¸! 1Ä…–”F"È"‹P)BºÍ̽û->ÌТ†¤x’/¹÷þÿsÎwÏ(Lþ§°¦­~¬ò†7HHßwÀ”E…»¦.®X(vóŠKBàâ¾êŸÿÚó«_7qvÕÜdàúsFXÞè¿"zPcT/)Y›?ÃŽ$œnkÞ‚¦‚ÒIOøy=k_^Jö¨,H PYWéó»é׈–å;-ZRaEÅÓfaß´T·>d )ûðd‚>qÉ3ªÃõóëÒx‹õìxLĤL&!â¤Ì^cÎÜù¹Sž2ßs:Ÿqrô_éž@¯ïû÷Ü[eQ*[f=Rî!™¢Ý?N^aaàÅwVMùãè™ÏOÔv{¡àKN0\™é }r=ßõ>ûuãš²=“Ã(¥BݶWhúEœ÷ ų*rWÍiÎ=±gçÏåcÛK0ÉE)3¤ õ°¸ $‡’ „Îhë¹0†¦'q•[VÓ›g2}¾Cë¶Í öÍÆ0Â4Âå@ü.(©‘R¡5Ã¥QJ£µÎ–O£•F3Dß1¢9Å´¼¾Œ+c´nY·†@Y 8Ý•¶îè¦Æ’!úãJ)´­õ‰†a‚aØ–E4Z†Ÿ–´ŸŽaæÍÀp‚ȤL뎖Nå'=á%> ÖîÿÄÆ¥ÀOK”̸Ì8ÿg@eÉxj*ê9{ê<{~Ù‹a,Åq@ú»å L"R˜Ò Ùxo[l¬Œc)Jê‘3 bÑX=›=·ØöÅdº ÛZˆö‚žr½}'¿úpÓÍßš›.ˆ¦r‹€rgäæÜ®¿R)5Jhl+@Ã̹D‚ùìݹŸÞÞòöþΪ?míp¯ºƒÙ¯Óšk sÅÛHd D l›6&˜¦Ãí11,˜>©†Éãª9|ø0±XÓ|²­½mÓ×ß½¼,}í:Sº¡a1‘ä¶{„‹ž6NFÜvL¤©1-MI¤œºñs¸|ùbêÛïw›áÀªPØ)¢cïέ]?¬^™’Ë£œfE‡Ÿ¥G*¥iÛr²7#`±ó¨«|„Ä@¿øq×ÖN¿ÄZ—Wº¶Ê½«R„bûwÞÌ ½¹|Dp”è(q€“¼=À m8 ¨{°‘\³€Ÿ¶Þèt»·¿R¿ÙÈø¾†÷’œàK -ÖÝÕ»ÝR®Îdâ‚t¸x%}já«·Ž7mƒ’Ü*ní?ûý@pÁ„u‘šúk”ö´ÛÓÙ}Ý z4`\T¶ H„ÑÐÕ$7û~ÜhØÖPàŒ˜}?ñ´s.îƒÆÁÁ›Žòò ×”©ÐõŸ}u¼Ê³¯-Ú³ãB¶g?•ûèÌv¯¯¶8ÆáÄ@KØJdéT­©Ü"…¤â¡SCßdwE*›AæjÒMŠ)îÜž)ǽD(Ê€àdwà РtgG6Ì÷…z–ŽÍIEND®B`‚envisage-4.4.0/envisage/plugins/remote_editor/editor_plugins/editra/images/python_runsel_32x32.png0000644000175000017500000000374612252661404034211 0ustar davidcdavidc00000000000000‰PNG  IHDR szzôsRGB®ÎébKGDÿÿÿ ½§“ pHYs  šœtIMEØ )1Ãt3fIDATXÃ¥—klTÇÇsïÝõÝõú±Æ66ÔÆ”ðˆÁp“€1‚ŠhTUIZ•T¤Rª–*i>Ti”Uó!R*UU›&UM•|pÕ&4ª ¸5nR †%â‚ÝðŽ©ñÛ^Ûû¼3ý°³öz±CÒŒtvvvïó?9ó?ÈÑ_`„B!0´- € HÇqfèÀ" ôÿ‡bX€È׳ñHˆcÚиã8ý’gé½9`¶ôžŽïîL^úøçÀ`¹ÀÍÀ J•ép8 \…BQÇq”$LKLlÙ±eÓ?rÛæÝ{áÊßc¯Äǵën4P,^¾Á³÷jOÇe¥^ÉU«̺UNîÑÖ¶¶çßîIþôãÖÄ! bd®üÐî÷y:œ8Ž“þYÖ5@»öš´27ë ÿ“ú +sê×®.=øÆÛ?,ì>?øaìñÞÓî9–ñ9¬2¯éÔH+Î-‚ì¢2L×ø1ò=Åܳk›¯ïÚÕú/h*\<Þüßc‘gF»éÕ@bY›JýÛà§Xžý:)UùŒ¡”b4ÞÏ™ÑÜ‚°ØóØ^ÿ»î¸«js µökö¯—`~¼>VR{çàyhÙã$ð,p%ó¤seŠT.×¢—9;ö7,)³ö>ýp ážU?þò6_KåÏWu¦edzDoÞ¥çìq èÐsTƒÆÀþ41.Ož¦kò(k·®³xrÏ‚Å*ž_¾Ãÿji½å`S½}ûvŸ0¬Ã06ËVãÀžcSì Î‡ì`̈;Æùñw0.qß÷wú¶=ü­†ŠÛó,½Ë·oÐÿÁûËÎtžqµòØ,&um‰nº"vÙtÁ’R!]©EáfI2)Š\åÔð;Œ?xêA{óŽ÷-\m´&æ_ÜÞt¨i~ûÙv;žŒÏ ®A¸™GÚÂÇÔZº )dz•í¤WÇ»˜ì¦zÅ-žºz§àð›ýIçÉ#»ÛþÍ+¹y¹vEI³œ”D†[u–¥³€ëªÔ÷LIÎ\»®"šœ¤s¸s£ÇØü;|;ùNå5:}ýÝg6]¸ˆ™k"L‘™m›å ú ´r©®«y*c-Dfý($c‰!B“‡X·˜?z §ë?¿Ùÿ(¥Ë º o¬@NÀôçø¯@!p©(©ROèÇ”TH¥P2•#JJ¤Jÿ.B !ø(ò!ÝCXV~«ØóÄ^ÚâÄá“ô†?Áˆx•ÁJ#ß›/2jÙA5 ˜K’HÆ´"…” ¸Þ#BL)„!0„ (PNÐ_FwÏeÎÐEd A„a&ò½ùÉìİì2;U$ã.ñ„‹Ò*•ªŒ™„þÈ´\P”WÂêªuȤâ/oü)LzÎ5Ò@>ÓÎÜb:AVJ+ÕT"N¹\¥€¤@(3­Ïµ¬¬º²Â…¼×Öûà_ïG¾RÿÍÐÖõÛ¶~îëÈq‰Ïô‘Á’˜‚B›„gªþêZ çL RÍiÔÕÔ³¼jçNË·^ýc4iñû­w¯u]ÁúMÅFñ–3Mݘ3sÀWî#ieœ}Å”ÅH w˜jÞÄÚe\èåw/ÿ:ž·k‹Ÿ®]â 7Ä‹+ʉ9®á=gÏ´e ¤aLÃ5åÍ£±n#–éåà;oE.]þ¨[Õæ=‘{gÝû¦0GvíN–åó´¢ìáÕ<Ñ“yûXÁ°´S`èÂ!™¿åceõmT–Ôp4Ô?uòxÄ,ËýYþcÎÛË3Ñïëï{íÔk%@¡fGÙÃ4w …B Çq¤eÛ69©Ä2Li¥²ZI¥ fþ2n©tè¼tV½øú/# ¯Ü_ôÝÏYUc"ýGÄ‘p(2´ÒR (ž@æEúVœœbDÂÔî° \e`(EiÞ—XQ±†ÑñaþðçßF†'†NØ>U¸¾º'Jtä-ƒd+Îj@õ,*5kîzfrB–G`{ Y^v;>O€#'F:/vôš‹ žœ·m]»‹;YHa_“hʦì–ö@-°gjQ§å‚Û$RÐÌpiÙ­Ì,"Ôõ^âx¨-J‘ÿÙàC M–׊ûñ÷Âs°bS7#%³‘Ñ,nX¢“јŠX$¦^jùE4bÄšüwßô+Mù¨…5²†5ƒûÄ>ùú•n6v,3¯d À%Y°¿å…èÐÄÀ{EùãE5›˜³¯Y4Çši¾‘bWÍžYXpöº[·gIK"…ð΋¾YøíÆVHzð 4‹æ±ÏÒ“9Ž£B¡ÔÍË9àMXmš4˜Ô­Ùy} RrȱŠv5>”ªÞáÏáîl¾7¦7ÔG-?£ïˆi«t[N7§b½Z´±ƒ<=Í¢9öZótÇã×ǧ+bšŠMj“º;VÿˆÐj˜ ÚÞIEND®B`‚envisage-4.4.0/envisage/plugins/remote_editor/editor_plugins/editra/images/python_run_48x48.png0000644000175000017500000000651312252661404033516 0ustar davidcdavidc00000000000000‰PNG  IHDR00Wù‡sRGB®ÎébKGDÿÿÿ ½§“ pHYs  šœtIMEØ !fFÏÎ ËIDAThÞÕš{pT×}Ç?çÞ»{wWZ­´+„ I ƒ$0 ƒ×6´c»vR¿pfêf¦iÚ&S§i§™6ãzú˜Ä“´é´“Ç´ýî·8ŽÇlcž1!$øX&PB/Ð =VÚû8çô½»Zñ‡ƒÏÌ™½w÷®öûýý¾ßßïÜseõ@@òf $ Èú£B µäSáÿ­Xný}Q…q`´S ¥ç#A |¹õw†F¨HÜ•j²®3MÑ‘PPø€{M˜³v÷Ítÿ_ç«Ø¸´ÞŒË1ÙáL¢×ª?L U±ÌÚ´pÉü²­7rýÍkŒ¢*Ûè:rª->ßüd¼Ú87Ò-O óþpu͘¿¤¦¬vQ#ƒÎ)jZYyëJ1¥Æ#C§n/o Ý)3ŽuË³Ñ Œ~íèÛÁÛE"\AKs»Xº®‘þÓ}eŽ7q_²ÉªZ¼?9¤äµâ ôNÆSCnþs‹êXÙ¾Ö¨\’]G»êí2ÿáÄ"+<1(É Ñß´?.J@- #Óô;'Ú¡®¼…ÕëÛ ;išÝG»V%ë­ûbs=©: úLJîY d‡fÂfÐ9IȈ²¤v%­ë[ÄÄԹع¡³Ë›Í[BÅtLôê±ß„?®€@vH|F¼>†½^’v­-7ˆ†5‹è;Õ].Eæ²sžÖâPæ¬" ò¡øãР䆧S¤åóâ‹YµnQ¾8!ºŽv-‰¤ÔɆŸ>-;¤K ( dåÿúؤ*š­M󛮌@nLÉq2'P(W¶²jýaùÖé½ëJ­»‹ªÄéÑ^Õ…E›0™_‡?L»ÌN%ëÅUȺC3î1àv1‹h®_Íò[–‰ÑѳÅc##/o ­‰ÄÍCΘ•¶Š¬ÒXiÌpÇÝ_éBÑ´*­T²Flªiª)[´¨‘žÉÃh­ Q^vJå1ìö2âöQ[@k[»¨]QC÷±®*efŠ×èr2N9¾U„Jü„¯ùÕ,­p< xù˜h¥ÑZç±_M¬ÆÔ0ï}ƒr»šº9+ù£¿þ¼yèýý¼ñßÛÙ|ª~åƒÅe·;B„!LB¡"0,”ïe¤’§´/OKéÿçî¯>´9 êò’3í;•(U›jªËÕ5Ò5ÑÒ­­¯(…S£Iûcœ™<4ÎocåúUÂ5'Ì΃;â™ôÛef¨a&ñ½ |ßAˈr­©pïükï /柕„Y²¬$Uñ6Õ4Æ?ÈJHø/˜ú¼s1ó<ÈšÒŠsN?“…´.½æ—0t¦“ÓÇ^Gz]„ A‡ð½4ZI@€Òµó®ÿí=?{ygPŠ/©;5“Ö(•99i5=•â¼Ypmþšé9åOðÁð^ î +æÁÏ<Êï?v?áðIÎö<ÁÄÈ‘*ïãºCø2V Ã0廒[ÝK{ÀŽÛ?=M@Èç*|0ã"÷¾€f8ÓÏþ¾W©Ž7Q·p9ü·M¼óî>v~'Nz?ÑÄï`EW ¥‹ÖCG#@9pH_’qà\`]@"M_äE†`ZfJÙÓ¥TžÕÑÉ÷è9AÓœU¬Yu3K—µ°}ËëtìÚŒ~;þ ÌÐ||=J¬1V7ytòȬ ô¹é7r©?;h=­¥Q´T( J*йï‰é "w®óÇcj”·ºw*ªdEõ­Üsïý´ß²–ï}í)&Ï~—HÙC¡ˆ ìF;âuf—Pà‚ì*lBây.J(´´@)•-­ú2’ €æô#„@'"D–„Pˆ%iHµa›6Ããýì~y;w #Ç Wgñh¤Óí¸—ÍÖ4&Ï“¸ž_P…¦¥•+“³ÀŽ#a›æªëYX¾×›bÇ®-ì{e/Â,%’zËZŠÒ áYL~0Ù¯ºÿ°åò¾“ñostéKŸ¦i…Ãa„AþGµÌU•p¾lÎ$’[Jdq‹iϾf¦AÃÜå4ÏkÃ0LtüŒíÏmÃÉxDJ6`GoDi–Ã!´¯•‡¡e$lÉGFß½u<±bçßc% BÙÙ&&™¡÷Âú_˜]Pƒrš/”N.úUÉVÖ¯#-ãTßqÞxa =gGWSR¹AZiDÆFxf>XY@YðÈ,”G4,þp-;n#Í‚ ¨€„ºu"… A.ð¥ñRV/¾‰y©ù ñ?/=ÅñCG°Â HÌý<¬¥ÐN+ø­™W«iàùL #^X¾±õžCÏd=P@ ×Ng€W: 5“È SÈÅÛ´Õ¯¢ya+®ï°kÏVöíxŒ8%åŸÆ²[ÐJ¡=™"ð(‰VÓà¥ôÞ¹q-Ñ×ʇ`V&i^´ì¸Í¤©óÊucÎo¹¨’ˆå†ÁuµË¸¾©°esðð[ìzuSSSÄw-¾ T%5"ÇÌQUy­Uf´óýo×U‡?›œ?‰XX6Evp;nT!²u?wž#! ø(›1B ´ÐÌKÕ°nùÍ”'*è:}’m¯o¡¿¯—Hl©Ê»1ŒD¶»!˜ Ôh-Q:ˆ|4å9ï w~ú¯Ÿ¾Ý2hBz•‘©½" ’íÄ… Œ§#X\#08 dB@¼¸„ö¥£¶²Žsã#üàåg8ÚÑA(TC²ò1¬Ðb2þÀkÃ=»vûÞsGå°œõ†æÈÎkk«ÃŸ <Òò[á’0SVFö82g Be£­µÀ Y,[´’ë¶¡¤dÏþíüdÏÐÚ¦¬â°£íh CgöþÓw>éX«k«ÿÔ|ⱚéJQNz3ÏÕåÏ¥ëF¶›Ó2„À0E>ÂZ,•E¶3/šÛÈŠºv¢áœ<ÈŽí¯‘N+Ù@¼äwAGÐR㎾øìï)µ´¾êKeåÊÀç˦ñëµ ¬`Qí–€a %¦ç4¯ÉÄV,ZKy|.g†{xþ•§UwO—´2wÞ&L£­$Ú5ÐãaFÞÑô'©¯\5ø‚jƒr >Ÿ6ôȨwð-a꼄LÓÀT"¸QÑhC`[QZç·S“ªgÊI³eÏsúз…°ŒSeóî?Td~ü.”DI ãQô”‰V’ŽçŸúîL2žxñ͉­@: P !S`謄 ,ê+–ÑXÙÀÛ‡÷°ûÇÛ”¯¼)«ªø©M-ßçhq…þ©¿QOÚ6á`á—¯éþ4øóJaŒ?S&òüLœ½‡ë:G>÷¤ÓŒZÁ’.ëÀ4ÁÔ•¥ i®\C$£³ÿ[w¿èŒŽFqèÙÄ­ ÿj·ThÀŸw¸‹³ë¿n(ûËZ« ®«\GÍvBå?îæ£­ €_­÷pŸFñ|Œ ‰X9‹S­$£• §ûye÷³êD×1C„¬w‹ÖT?^|Çâc2icn¯Ú>»ÿáæ¿|jÔ²"*­*„ÖÉ|W½˜Lä%ÀÍbhå»ÃÒ÷|(óô†?Ïì îÔ&³0X]³ןb×Á—ØÿÞO4†è ×&¾šx íéí<óÜ:Ö =.ϵÏ̾öéoÏ) „€s—®.æâ—&®pU[-i`ÜUHjÉÏ{ßfÇÞ×dÆË¸F2ò­Ò{ZþËHF¼`Ùœ êU¼êleëù<d0oÞl²ð~Û3yYÉ™™ò=çHt]×Ý`?h~2 ‘ÉíVX†¥9Õw’ÎþoúƒgLµ~XrSÝ¿„Ûjsr10†¶‰m£—‰Pæ‚­õó:è̵M!x†¡•ô1 Œ_úŽ JÖ ŒõaXFG´¹ê‰¢Û›Þ/¸f4Ejh³Øü‹=Q—¨6ÊAƘ÷EŒ9 z¿‰ì~¤OÁƒf%Z˜üŒ–*VrOËVCçäâãì»~©‡Ê÷‡~òBƒú3÷t®ÖÏ&%ñ!=‹3; R¼Fƒ÷Ì@&NP2Ç±àØ¹œþ*¬v¸æÓþIEND®B`‚envisage-4.4.0/envisage/plugins/remote_editor/editor_plugins/editra/images/python_run_32x32.png0000644000175000017500000000375712252661404033507 0ustar davidcdavidc00000000000000‰PNG  IHDR szzôsRGB®ÎébKGDÿÿÿ ½§“ pHYs  šœtIMEØ "7ï íoIDATXÃÅ—[Œ]UÇk_ÎÙg®gÎ8íL§´ÓbÛZ 7;­g @SÄ€Mª™ 6&¾ Žá…ȃ1Æ„FD°öÁR%S•¤€„4J”N¡t¦Ã´Óv¦=÷½÷ÙëâÃÙs-bÕW²NvvNöÿ÷}ëÿ}k-€$ÐþÖé"ŠçÇ Ð²ö É“-ÝÉ.%͉`Zk ÔÇPßz«»{àáþÕ¹âÙ/ÖÝ€[œPcª‚À\o€ô’ î7>ÿ•‡R›ú¶$¬Z½1pÎ?˜js£úR ^_€õî`Ï];¼w /ñÉ›nq6nßZ›/]ìK,ówÚ–~·8i$ Êu÷ßbº2Nd•Ù¶¥ßí\ßÑz~êÌýõ«Ìª(/O„y¬kí炌1„ªÌ…p”T­'vöÝã6.«¹ùriâáºåVT“JaÇ úÚÄž3J2ËTå í­«¬;ûûÚõ·Vj§w¥Òb,7¦ ñ7‚ÿ À#½¤ÛìX03´Qd£IòêÝk7;·öÞV);ywjy¸UØæd)« ò¿÷‡íµxéÌÖ`ÏÀöE A¤ ™ŽÎ ÉÛîvWt·/ŸÈ2–ö3@›ãy¥dÕX–-°ª«8:—®£{ÅFFN¿e~ñôOý(¡÷ev¯ÿ¹Óј÷ñ/š\ S©¦ZEA1;i¯l|´ùžWªœ&}a¿Ø?ÛÛ•”£ZFk”Œ8{ôÏÏ–Æ÷ì®ñÔ½Èy‘Æ¢³Ï* \6GJ5qƒ\Ûº‰¥u+yýäËÑ‘×_ ÈÔ<Ñ´gÛ~'áTj¨¹xP,,n¥*(=©´y\©ÈûàØsÙï›÷Î Î'ðÆHp(T\@B?4¿|þ'o…ûkî»ég5m9'»•­ÓCbèC÷÷žøÒ^à ЄóÅÎO^ÞokßT3áƒòxw´r|û×/¦…lÜ÷ü“Á¥ÒÔß¼õm?ÈôvŽÙؾ}aX ‡Ã Ô–^&<`æ,M«]H *!6MöEÀÿ?íh´°êÜC—ëƒé‡{ „tq§†Åpþ*OU:þ  £r`©rÕ|3sÃIôkËgnYçâv,é$I:™¯õîH¸üQ龚QÌçv¥ìÊmÊ]–ö=d™èȲ‘WއOÇßò ,€¸ÃÜÑäá5¹¸ÃbøZ\Á< hêãÓ@!Žþüü£ü?ŠÄRçÝIEND®B`‚envisage-4.4.0/envisage/plugins/remote_editor/editor_plugins/editra/images/python_runsel_48x48.png0000644000175000017500000000553412252661404034224 0ustar davidcdavidc00000000000000‰PNG  IHDR00Wù‡sRGB®ÎébKGDÿÿÿ ½§“ pHYs  šœtIMEØ ¨½ ÜIDAThÞÕš{pTÕÇ?çÜ»{7›ìnžB$$„CxÊ(j)¡ã(`¡jǶÖÇ´µÕvšéŒÓqª…–Ž:m§3N§Õ?l;µJ¥ÛJ¨‰AQy†&$›°Ù×½÷ô½›lB Y\;õ7s'gïc÷÷=¿ï÷÷;÷ü :_PÓ€  ðQÀú¢(rÆn àD#¨/ 0åkî—¯¾Ë8Zv³k ”y€ÈÄØ¶-2íx08VM¹Ëh‘BhJ)ì8]Z¿lß<ÄàB¦~xݺuŸ)²+V¬4:€âļ%s+Ê«+hx~ÃMšë‚@…|±}ì¹àY\@Ÿ$š ·ß~û=·aÆá)  IEé4úY½Vw_nøõûÆ/ðl©¨s/ÇMPg2c†1èéüpv‘3çc§™]{#WO›!vnÞ’wh{óš)K=÷^hµŸ<ÝÛø€N +ÓB_»vmÚÏ\àxßAÎF>¤<{&·-¹SÌ»i> ~­òŒlýSÕW-ÖÓ玘ÒÉXÕÇã?~eYh¨…­^>è}“#½oàËÉã[?øž¶âÇË…¯(û+cjµ-•·?rçáJ€RÀ¸‡£ÑhÿÑÔÔÔ¾©©iе´"4¥]±6ºã[ë)grY ¯œªØ·GÛùÒî‡*{V„:̵'v˜ €ç3]1^JÔÉvÔ€Xœ ã|ä4¥Þé\ÝB¦ÕÖŠÆ×6ÞóÁ3S¾&¿¯¯4|Æ?laq&|„wº74Û¸é¦[yèˆê9“J¼ùòwS–x^,cTJ—Ì1ŠŒr_‰¯ðJ¢~yû“³šm"¥L÷ÈéÐîã#sgÃ1Éw-+¾y¯<µèC^áõ9BëÚU¬^êlÕ~{¡=& qâïiæ?¡)÷Ï`ñâ;Äìëç±õoË>}ïøKÇÇwDÂÆÚ˜ãTÛ)zB=ƒ¿FˆÑg!…" X)åDAõÓ)u–“Ñ8Rîí¿gà›héÜË¡ŽF¼Þ¾þàòÎú»ðçgßœållúè5BÑ^z£½hÙ•^JÔ Ÿ0ClLš:PEÁ9/ èŒ´±¿uã}ÕT”]ÃC?«ÖÞ9ø;ÿº“‡Ÿ^Ê-5ËQºBh‚¶ž6±ð(³èNqX¥€Hº¦F—DJ³-'zJaÛv?ªûÞ¥µëªÇÌaîœ\=½†ÆM[øÇÎ?0ñŽ ‹³Ýgéuôí¥)ä|µŸ*…2Ê9oÙX¦³ˆE-¢}q¡8¡ž(¡`„ž®0½Ý.ôD ‡bDÂqbQ ÓTX¦m*,SÑ ràT#o܌ەÅÒ;îâþ'¾C (‹Ò¹’#§ß JS”¬,É‘BŽ ˆ\°ˆÇcØÂFÙ€ض¶RŽ€“üB Dâsr,„@Øðæ3¹ C3èìmã†F"±0– WW"¤@“­‡[£#2}À§xÜ"7Φµ„.P/@¤p€3ö¸ ¦Ž›MYábñ0»6òVÃ^òr imVôµïû¹ø ?y®{QÙïÉÏ+©LÙ'Ïýûº÷èë>Ûk‚Žá¼jÈ”L"(‡ "­,!‹ä5Š’‚Ræ_³€Â@'Ï~Êö­ VÛ™VMxôÍ®¹ãŸvÏÓ*”P1W¬û`åÁóì̼ø†PÈž ¤HT1)ŽÀA … ¾?󮾑‰c+èîíâÕ†¿¨£ïB—ÇôjÿSÆ’IûK îìël?6ùب·'G³"ÕÝ~7aÝq21îw2) ÂN̶RÝ¥3½|ÓÊj±-‹=ûwð¯=»m[Y=®ñ9¿ÊºwÊzÍ­ÙØ˜q+~n_Á¾žtgw4BNTbm€BR¤&úgX‰dAKŒQ‚òâ*fVÌ#Ëí¥åÓfwl4C¡^¸_̺cÂsîꢉT&f°Žºs+ÅÊŒÐåÒûB2Ám© ¤-Or^ òc˜Y~=…¾bþÓyšWþdŸ:}RJCoòÞj0@@éBSýÒ4‰f ”‰l#†žÅŒ«æQZ0‰p4ÄÆ=ëÔ{‡ÞB—'<Õ¹«߬yө̖ѱIlD—L;TD*…4T It&M§jl o°‡ÝMÛmÓŽ‡õq9Ï|£æ¯*ÇeI¤ˆà|æH—tCï¶Áîd"@Ó@S’±¹eL;ËËñ¶£lÝýšÙì’2ÇõRà–É¿5jÆu©!tÙÊÖŒ48B¡Ð°÷ ·WÚéD à-¤²`ùYcé µÑ°û%û““Ǥpé³çŽ_™³¨òØåè’nƒc8«««»hûò"Ö8¸¶´Ž˜fWóö¿û/…gÜkw×nhmjÝ£¡Kº¶jÕª´ûƒ²¥,Žœy›Æ½›­H<“ùžçr—Õ¼ ó=qgÙqájK‡.騣>ze¤®8Ñú)ÇÛ~cvœo×D–¾ÞCůݵ¥IºHä¹íb{0“_Š#55.`ÙVE{O+R—‡³¦Ž[•ýåê÷SSnç^¯d¬ƒ?šÆÅ¨«õBµ°*¸¡ùFeÙ^ÿ²š­©’t11Ûw‰]þM,T «†l.tɤ=ûì³®úúúøp Ž`Ç3íüúõë3ú¯õõõñ5kÖ$^þ«ÅùQ¢n ûÿ.ÃÙd:†½'#oIEND®B`‚envisage-4.4.0/envisage/plugins/remote_editor/editor_plugins/editra/images/python_run_16x16.png0000644000175000017500000000132012252661404033473 0ustar davidcdavidc00000000000000‰PNG  IHDRóÿasRGB®ÎébKGDÿÿÿ ½§“ pHYs  šœtIMEØ % #žþPIDAT8Ë•“ÍkQÅÏ›N“É8F©Ij[:µ©55´ DPDh•¢¸‰î\¸pÁ…Ý©‚Kÿ‚n\ Õ Ú‚Uk‚AD¥ ô‹I3M2“ùx﹈ͦŠí]ÝÍýqϹç­ ˆèÀ!ko@=?^Іmó³ó@ó°éæ“é{©+‰ËÝò&ª•Ê7ïïÀ€ôíDvGù©Dôc]És³¡¸}ÉÜ­¯XëÌ@ðTSv™ §³*êÉÁÞdz$Ã4cØXm,û5Ôð¿ú!¥¯'²®R•ÁÎ9lº ª4¤ñÉTR?Í8áJpËp 0áìh5)5ÕŸµƒU™RF9eð}†5 EÄpêìä…ã=â¬ÐÕÜàGÕ²õËrÛ€ØLLÑcY+¸#3Ê@)õ8|—Áw}ØM;Í-áÄ`o4qúÔ5«Qž!SJÞ|_7D@_K\eË£ œµd!DY®iÚMî¬°î €‹ õkà€gQPú-‚ àdϨÅ×Ko ÉÈô•Ö&?x þ1¨p´ ãð}†.5ŠØ€_*Kkþö#íêèü—±\(·Mlm0¡AXÄ€B%¤BŽòíõòBñÕ3ùîØóO( ›ûÎ(î5D$å zµaØ5§š/ä^tNÇŸ6z"«K$çþ+Hm€ ¢·—K‹ïÌ!òwôbŽäkÿ‹rðöëü6"êãà­Á—yä* ßÙAž‰@š§UJ4Œðê™sóοWR44¼#=IEND®B`‚envisage-4.4.0/envisage/plugins/remote_editor/editor_plugins/editra/__init__.py0000644000175000017500000000000012252661404030551 0ustar davidcdavidc00000000000000envisage-4.4.0/envisage/plugins/remote_editor/editor_plugins/editra/editra_plugin.py0000644000175000017500000001545012252661404031657 0ustar davidcdavidc00000000000000""" Integrates Editra with the an envisage applciation with the remote editor plugin. """ __author__ = "Enthought" __version__ = "0.1" # Standard library imports import os # System library imports import wx # ETS imports from traits.api import Any, Bool from envisage.plugins.remote_editor.editor_plugins.editor_plugin \ import EditorPlugin # Editra namespace imports from wx.tools.Editra.src import ed_menu from profiler import Profile_Get # Constants _ = wx.GetTranslation ID_RUN_SCRIPT = wx.NewId() ID_RUN_TEXT = wx.NewId() class EditraEditorPlugin(EditorPlugin): # Client interface # Dispatch all command events in same thread as the wx event loop ui_dispatch = 'wx' # EditraEditorPlugin interface # A reference to the Editra mainWindow main_window = Any # Whether the window should be given focus when it recieves a command raise_on_command = Bool(True) #-------------------------------------------------------------------------- # 'EditorPlugin' interface #-------------------------------------------------------------------------- def new(self): """ Open a new file in the main window. """ notebook = self.main_window.GetNotebook() current = notebook.GetCurrentCtrl() if current.GetFileName() != "" or str(current.GetText()) != "": notebook.NewPage() notebook.GoCurrentPage() current.FindLexer('py') current.BackSpaceUnIndents = True if self.raise_on_command: self.main_window.Raise() def open(self, filename): """ Open the given filename in the main window. """ self.main_window.DoOpen(None, filename) if self.raise_on_command: self.main_window.Raise() class RemoteEditorPlugin(object): """ Editor plugin for communicating with shells programs, acting as a remote editor. """ def __init__(self, Editra=None): self.Editra = Editra def do_PlugIt(self): self.mainWindow = mainWindow = wx.GetApp().GetMainWindow() self.log = wx.GetApp().GetLog() self.log("[remote editor][info] Installing remote editor plugin") menuBar= mainWindow.GetMenuBar() # Register the EditorPlugin with the Enthought remote_editor server self.client = EditraEditorPlugin(main_window=self.mainWindow) self.client.register() # Set up a handler for keybindings. Ideally, we would do this through an # interface provided by Editra, but since this seems to change with # every single Editra release, we take the brute force approach. mainWindow.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown) # Insert menu items toolsMenu = menuBar.GetMenuByName("tools") mnu_run_text = wx.MenuItem(toolsMenu, ID_RUN_TEXT, _('Execute selection\tShift+Enter'), _('Execute selected text in a python shell'), wx.ITEM_NORMAL) mnu_run_text.SetBitmap(wx.Bitmap(os.path.join( os.path.dirname(__file__), 'images', 'python_runsel_16x16.png'))) toolsMenu.AppendItem(mnu_run_text, use_bmp=False) mnu_run_script = wx.MenuItem(toolsMenu, ID_RUN_SCRIPT, _('Execute script\tCtrl+Enter'), _('Execute file in a python shell')) mnu_run_script.SetBitmap(wx.Bitmap(os.path.join( os.path.dirname(__file__), 'images', 'python_run_16x16.png'))) toolsMenu.AppendItem(mnu_run_script, use_bmp=False) # Bind the events. self.mainWindow._handlers['menu'].extend( [(ID_RUN_SCRIPT, self.OnRunScript), (ID_RUN_TEXT, self.OnRunText) ]) # The enable/disable callback for the toolbar button (we need to insert # this callback at the front of the stack). self.mainWindow._handlers['ui'].insert(0, (ID_RUN_TEXT, self.EnableSelection), ) # Insert toolbar items toolBar = mainWindow.GetToolBar() self.run_sel_tb = toolBar.AddLabelTool( ID_RUN_TEXT, 'Execute selection', wx.Bitmap(os.path.join(os.path.dirname(__file__), 'images', 'python_runsel_24x24.png')), shortHelp='Execute selection', longHelp='Execute selection in shell') self.run_file_tb = toolBar.AddLabelTool( ID_RUN_SCRIPT, 'Execute', wx.Bitmap(os.path.join(os.path.dirname(__file__), 'images', 'python_run_24x24.png')), shortHelp='Execute script', longHelp='Execute whole file in shell') # Just calling AddLabelTool is not displaying the new tools in the # toolbar (for Win XP and OS-X at least). Need to call Realize. toolBar.Realize() def OnRunScript(self, event): """ Run the script, prompting for a save if necessary. """ notebook = self.mainWindow.GetNotebook() currentTab = notebook.GetCurrentCtrl() filename = currentTab.GetFileName() if filename == "": if self.mainWindow.OnSaveAs(None, page=currentTab): filename = currentTab.GetFileName() else: return else: if currentTab.GetModify(): result = notebook.frame.ModifySave() if result == wx.ID_CANCEL: return self.client.send_command('run_file', filename) def OnRunText(self, event): """ Run the selected text. """ currentTab = self.mainWindow.GetNotebook().GetCurrentCtrl() text = str(currentTab.GetSelectedText()) # Convert from unicode if text == "": msg = _("Cannot execute. No text is selected.") self.mainWindow.PushStatusText(msg) else: self.client.send_command('run_text', text) def OnKeyDown(self, event): """ Handles key presses when the version of Editra is less than 0.3 (no KeyBindings support). """ if event.GetKeyCode() == wx.WXK_RETURN: if event.ShiftDown(): self.OnRunText(event) elif event.ControlDown(): self.OnRunScript(event) else: event.Skip() def EnableSelection(self, evt): if not self.mainWindow.IsActive(): return e_id = evt.GetId() evt.SetMode(wx.UPDATE_UI_PROCESS_SPECIFIED) # Slow the update interval to reduce overhead evt.SetUpdateInterval(200) ctrl = self.mainWindow.nb.GetCurrentCtrl() if e_id == ID_RUN_TEXT: evt.Enable(ctrl.GetSelectionStart() != ctrl.GetSelectionEnd()) else: evt.Skip() envisage-4.4.0/envisage/plugins/remote_editor/editor_plugins/editra/start_editra.py0000644000175000017500000000250412252661404031512 0ustar davidcdavidc00000000000000""" Provides a platform indepedent way to launch Editra. Code mostly copied from Editra run file (/usr/bin/editra) in Ubuntu wx package. """ import sys import os try: import Editra as Editra_root sys.path.insert(0, os.path.join(os.path.dirname(Editra_root.__file__), 'src')) # Really ugly: we need to remove the Editra name from the list of # imported modules sys.modules.pop('Editra') except ImportError: import wx.tools # Editra needs its src package to be on the sys.path for plugins and # such to work right, so put it there before we do the first import of # any Editra package or module. sys.path.insert(0, os.path.join(os.path.dirname(wx.tools.__file__), 'Editra', 'src')) import Editra # XXX: Super ugly: monkey-patch the main Editra class to add our plugins old_MainLoop = Editra.Editra.MainLoop def my_MainLoop(self, *args, **kwargs): from envisage.plugins.remote_editor.editor_plugins.editra.editra_plugin \ import RemoteEditorPlugin plugin = RemoteEditorPlugin(Editra=self) try: plugin.do_PlugIt() old_MainLoop(self, *args, **kwargs) finally: plugin.client.unregister() Editra.Editra.MainLoop = my_MainLoop def main(): Editra.Main() if __name__ == '__main__': main() envisage-4.4.0/envisage/plugins/remote_editor/editor_plugins/__init__.py0000644000175000017500000000000012252661404027301 0ustar davidcdavidc00000000000000envisage-4.4.0/envisage/plugins/remote_editor/editor_plugins/editor_plugin.py0000644000175000017500000000175112252661404030424 0ustar davidcdavidc00000000000000# Standard library imports import os import logging # Local imports from envisage.plugins.remote_editor.communication.client import Client class EditorPlugin(Client): # EditorPlugin interface def new(self): raise NotImplementedError def open(self, filename): raise NotImplementedError def run_file(self, path): self.send_command('run_file', path) def run_text(self, text): self.send_command('run_text', text) # Client interface self_type = "python_editor" other_type = "python_shell" def handle_command(self, command, arguments): if command == "new": self.new() return True elif command == "open": if os.path.exists(arguments): self.open(arguments) else: logging.warning("EditorPlugin recieved invalid path '%s' for " "'open' command", arguments) return True return False envisage-4.4.0/envisage/plugins/remote_editor/actions.py0000644000175000017500000000617212252661404024173 0ustar davidcdavidc00000000000000from envisage.ui.action.api import Action, ActionSet, Group from pyface.api import FileDialog, OK from pyface.action.api import Action as PyfaceAction from envisage.plugins.remote_editor.api import IRemoteEditor def get_server(window): """ Given an application window, retrieve the communication server. """ return window.application.get_service(IRemoteEditor) ################################################################################ # Groups ################################################################################ file_group = Group( id='RemoteEditorFileGroup', path='MenuBar/File', before='ExitGroup' ) ################################################################################ # `OpenScript` class. ################################################################################ class OpenScript(PyfaceAction): """ An action that opens a Python file in a remote editor. """ tooltip = "Open a Python script in separate editor." description = "Open a Python script in separate editor." ########################################################################### # 'Action' interface. ########################################################################### def perform(self, event): """ Performs the action. """ server = get_server(self.window) wildcard = 'Python files (*.py)|*.py' parent = self.window.control dialog = FileDialog(parent=parent, title='Open Python script in separate editor', action='open', wildcard=wildcard ) if dialog.open() == OK: server.open_file(dialog.path) open_script = Action( path = "MenuBar/File", class_name = __name__ + '.OpenScript', name = "Open script in editor", group = "RemoteEditorFileGroup", ) ################################################################################ # `NewScript` class. ################################################################################ class NewScript(PyfaceAction): """ An action that opens a new file in a remote editor. """ tooltip = "Open a new file in separate editor." description = "Open a new file in separate editor." ########################################################################### # 'Action' interface. ########################################################################### def perform(self, event): """ Performs the action. """ server = get_server(self.window) server.new_file() new_script = Action( path = "MenuBar/File", class_name = __name__ + '.NewScript', name = "New script in editor", group = "RemoteEditorFileGroup", ) ################################################################################ # `RemoteEditorActionSet` class. ################################################################################ class RemoteEditorActionSet(ActionSet): """ The default action set for the remote editor plugin. """ groups = [file_group, ] actions = [open_script, new_script] envisage-4.4.0/envisage/plugins/remote_editor/preferences.ini0000644000175000017500000000034312252661404025155 0ustar davidcdavidc00000000000000[enthought.remote_editor] python_shell = python -c "from enthought.epdlab.app.epdlab import main; main()" python_editor = python -c "from enthought.plugins.remote_editor.editor_plugins.editra.start_editra import main; main()" envisage-4.4.0/envisage/plugins/remote_editor/remote_editor_controller.py0000644000175000017500000000112412252661404027627 0ustar davidcdavidc00000000000000""" A client controlling a remote editor. """ # Enthought library imports from traits.api import implements # Local imports from envisage.plugins.remote_editor.communication.client import Client from i_remote_editor import IRemoteEditor class RemoteEditorController(Client): """ A Client used to control a remote editor. """ implements(IRemoteEditor) # Client interface self_type = "python_shell" other_type = "python_editor" def new_file(self): self.send_command('new') def open_file(self, filename): self.send_command('open', filename) envisage-4.4.0/envisage/plugins/remote_editor/__init__.py0000644000175000017500000000000012252661404024252 0ustar davidcdavidc00000000000000envisage-4.4.0/envisage/plugins/remote_editor/enshell_client.py0000644000175000017500000000071412252661404025517 0ustar davidcdavidc00000000000000# Standard library imports import os # Local imports from envisage.plugins import remote_editor from envisage.plugins.remote_editor.communication.client import Client class EnshellClient(Client): """ A Client that is configured to spawn servers using the config file for this application. """ server_prefs = (os.path.join(remote_editor.__path__[0], "preferences.ini"), "enthought.remote_editor") envisage-4.4.0/envisage/plugins/remote_editor/remote_editor_plugin.py0000644000175000017500000000643412252661404026753 0ustar davidcdavidc00000000000000""" A plugin for controlling a remote editor. """ # Enthought library imports. from envisage.api import Plugin from envisage.plugins.remote_editor.api import IRemoteEditor from traits.api import List, Instance, Any, on_trait_change # Local imports from envisage_remote_editor import EnvisageRemoteEditorController \ as RemoteEditorController ID = 'envisage.plugins.remote_editor' class RemoteEditorPlugin(Plugin): """ A plugin for controlling a remote editor. """ # Extension point Ids. REMOTE_EDITOR = ID ACTION_SETS = 'envisage.ui.workbench.action_sets' PREFERENCES = 'envisage.preferences' PREFERENCES_PAGES = 'envisage.ui.workbench.preferences_pages' # Our remote controller for the editor remote_controller = Instance(RemoteEditorController) # The shell and editor commands server_prefs = Any #### 'IPlugin' interface ################################################## # The plugin's unique identifier. id = 'envisage.plugins.remote_editor' # The plugin's name (suitable for displaying to the user). name = 'Remote editor' #### Extension points offered by this plugin ############################## #### Contributions to extension points made by this plugin ################ # Our action sets. action_sets = List(contributes_to=ACTION_SETS) # Preferences pages. # FIXME: Create a UI for remote editor preferences #preferences_pages = List(contributes_to=PREFERENCES_PAGES) # Preferences. preferences = List(contributes_to=PREFERENCES) def _action_sets_default(self): """ Trait initializer. """ from envisage.plugins.remote_editor.actions import \ RemoteEditorActionSet return [ RemoteEditorActionSet ] def _preferences_default(self): """ Trait initializer. """ return [ 'pkgfile://%s/preferences.ini' % ID ] def _preferences_pages_default(self): """ Trait initializer. """ from envisage.plugins.remote_editor.preference_pages \ import RemoteEditorPreferencesPage return [ RemoteEditorPreferencesPage ] ########################################################################### # Private interface. ########################################################################### # Create the central server for spawning shells and editors. @on_trait_change('application:started') def _create_server(self): """ Create the central server for spawning shells and editors and register the controller as an envisage service. """ # Register our client to the server. If the server does not exist, this # will create it. self.remote_controller = RemoteEditorController( application=self.application) # XXX I don't like this at all if self.server_prefs: self.remote_controller.server_prefs = self.server_prefs self.remote_controller.register() self.application.register_service(IRemoteEditor, self.remote_controller) @on_trait_change('application:stopping') def _unregister_from_server(self): """ Unregister this client from the server. """ self.remote_controller.unregister() #### EOF ###################################################################### envisage-4.4.0/envisage/plugins/debug/0000755000175000017500000000000012252662115020400 5ustar davidcdavidc00000000000000envisage-4.4.0/envisage/plugins/debug/fbi_plugin_definition.py0000644000175000017500000000243712252661404025306 0ustar davidcdavidc00000000000000#------------------------------------------------------------------------------- # # FBI (Frame Based Inspector) Plugin. # # Written by: David C. Morrill # # Date: 1/4/2006 # # (c) Copyright 2006 by Enthought, Inc. # #------------------------------------------------------------------------------- #------------------------------------------------------------------------------- # Imports: #------------------------------------------------------------------------------- from envisage.core.core_plugin_definition \ import PluginDefinition #------------------------------------------------------------------------------- # The plugin definition: #------------------------------------------------------------------------------- PluginDefinition( # The plugin's globally unique identifier: id = "envisage.plugins.debug.fbi", # The name of the class that implements the plugin: class_name = "envisage.plugins.debug.fbi_plugin.FBIPlugin", # General information about the plugin: name = "FBI Plugin", version = "1.0.0", provider_name = "Enthought Inc", provider_url = "www.enthought.com", enabled = True, autostart = True, # The Id's of the plugins that this plugin requires: requires = [ "envisage.core", ] ) envisage-4.4.0/envisage/plugins/debug/__init__.py0000644000175000017500000000000012252661404022477 0ustar davidcdavidc00000000000000envisage-4.4.0/envisage/plugins/debug/fbi_plugin.py0000644000175000017500000000270312252661404023072 0ustar davidcdavidc00000000000000#------------------------------------------------------------------------------- # # wxPython Test Plugin. # # Written by: David C. Morrill # # Date: 10/18/2005 # # (c) Copyright 2005 by Enthought, Inc. # #------------------------------------------------------------------------------- #------------------------------------------------------------------------------- # Imports: #------------------------------------------------------------------------------- import sys from traits.api import push_exception_handler from envisage.api import Plugin from etsdevtools.debug.fbi import enable_fbi, fbi #------------------------------------------------------------------------------- # 'FBIPlugin' class: #------------------------------------------------------------------------------- class FBIPlugin ( Plugin ): """ FBIPython plugin. """ #--------------------------------------------------------------------------- # 'Plugin' interface: #--------------------------------------------------------------------------- def start ( self, application ): """ Starts the plugin. """ # Tell the FBI to wiretap all unauthorized exceptions: enable_fbi() push_exception_handler( handler = lambda obj, name, old, new: fbi(), locked = False, main = True ) def stop ( self, application ): """ Stops the plugin. """ pass envisage-4.4.0/envisage/i_provider_extension_registry.py0000644000175000017500000000124112252661404024367 0ustar davidcdavidc00000000000000""" The provider extension registry interface. """ # Local imports. from i_extension_registry import IExtensionRegistry class IProviderExtensionRegistry(IExtensionRegistry): """ The provider extension registry interface. """ def add_provider(self, provider): """ Add an extension provider. """ def get_providers(self): """ Return all of the providers in the registry. """ def remove_provider(self, provider): """ Remove an extension provider. Raise a 'ValueError' if the provider is not in the registry. """ #### EOF ###################################################################### envisage-4.4.0/envisage/egg_basket_plugin_manager.py0000644000175000017500000000764112252661404023356 0ustar davidcdavidc00000000000000""" A plugin manager that finds plugins in eggs on the 'plugin_path'. """ import logging, pkg_resources, sys from traits.api import Directory, List, on_trait_change from egg_utils import add_eggs_on_path, get_entry_points_in_egg_order from plugin_manager import PluginManager logger = logging.getLogger(__name__) class EggBasketPluginManager(PluginManager): """ A plugin manager that finds plugins in eggs on the 'plugin_path'. To declare a plugin (or plugins) in your egg use an entry point in your 'setup.py' file, e.g. [envisage.plugins] acme.foo = acme.foo.foo_plugin:FooPlugin acme.foo.fred = acme.foo.fred.fred_plugin:FredPlugin The left hand side of the entry point declaration MUST be the same as the 'id' trait of the plugin (e.g. the 'FooPlugin' would have its 'id' trait set to 'acme.foo'). This allows the plugin manager to filter out plugins using the 'include' and 'exclude' lists (if specified) *without* having to import and instantiate them. """ # Entry point Id. ENVISAGE_PLUGINS_ENTRY_POINT = 'envisage.plugins' #### 'EggBasketPluginManager' protocol ##################################### # A list of directories that will be searched to find plugins. plugin_path = List(Directory) @on_trait_change('plugin_path[]') def _plugin_path_changed(self, obj, trait_name, removed, added): self._update_sys_dot_path(removed, added) self.reset_traits(['_plugins']) # Protected 'PluginManager' protocol ###################################### def __plugins_default(self): """ Trait initializer. """ plugins = self._harvest_plugins_in_eggs(self.application) logger.debug('egg basket plugin manager found plugins <%s>', plugins) return plugins #### Private protocol ##################################################### def _create_plugin_from_entry_point(self, ep, application): """ Create a plugin from an entry point. """ klass = ep.load() plugin = klass(application=application) # Warn if the entry point is an old-style one where the LHS didn't have # to be the same as the plugin Id. if ep.name != plugin.id: logger.warn( 'entry point name <%s> should be the same as the ' 'plugin id <%s>' % (ep.name, plugin.id) ) return plugin def _get_plugin_entry_points(self, working_set): """ Return all plugin entry points in the working set. """ entry_points = get_entry_points_in_egg_order( working_set, self.ENVISAGE_PLUGINS_ENTRY_POINT ) return entry_points def _harvest_plugins_in_eggs(self, application): """ Harvest plugins found in eggs on the plugin path. """ # We first add the eggs to a local working set so that when we get # the plugin entry points we don't pick up any from other eggs # installed on sys.path. plugin_working_set = pkg_resources.WorkingSet(self.plugin_path) add_eggs_on_path(plugin_working_set, self.plugin_path) # We also add the eggs to the global working set as otherwise the # plugin classes can't be imported! add_eggs_on_path(pkg_resources.working_set, self.plugin_path) plugins = [ self._create_plugin_from_entry_point(ep, application) for ep in self._get_plugin_entry_points(plugin_working_set) if self._include_plugin(ep.name) ] return plugins def _update_sys_dot_path(self, removed, added): """ Add/remove the given entries from sys.path. """ for dirname in removed: if dirname in sys.path: sys.path.remove(dirname) for dirname in added: if dirname not in sys.path: sys.path.append(dirname) #### EOF ###################################################################### envisage-4.4.0/envisage/unknown_extension.py0000644000175000017500000000040712252661404021777 0ustar davidcdavidc00000000000000""" The exception raised when an unknown extension is referenced. """ class UnknownExtension(Exception): """ The exception raised when an unknown extension is referenced. """ #### EOF ###################################################################### envisage-4.4.0/envisage/egg_plugin_manager.py0000644000175000017500000001010012252661404022005 0ustar davidcdavidc00000000000000""" A plugin manager that gets its plugins from Eggs. """ # Standard library imports. import logging, pkg_resources, re # Enthought library imports. from traits.api import Instance, List, Str # Local imports. from egg_utils import get_entry_points_in_egg_order from plugin_manager import PluginManager # Logging. logger = logging.getLogger(__name__) class EggPluginManager(PluginManager): """ A plugin manager that gets its plugins from Eggs. To declare a plugin (or plugins) in your egg use an entry point in your 'setup.py' file, e.g. [envisage.plugins] acme.foo = acme.foo.foo_plugin:FooPlugin The left hand side of the entry point declaration must be the same as the 'id' trait of the plugin (e.g. the 'FooPlugin' would have its 'id' trait set to 'acme.foo'). This allows the plugin manager to filter out plugins using the 'include' and 'exclude' lists (if specified) *without* having to import and instantiate them. """ # Entry point Id. PLUGINS = 'envisage.plugins' #### 'EggPluginManager' interface ######################################### # The working set that contains the eggs that contain the plugins that # live in the house that Jack built ;^) By default we use the global # working set. working_set = Instance(pkg_resources.WorkingSet, pkg_resources.working_set) # An optional list of the Ids of the plugins that are to be excluded by # the manager. # # Each item in the list is actually a regular expression as used by the # 're' module. exclude = List(Str) # An optional list of the Ids of the plugins that are to be included by # the manager (i.e. *only* plugins with Ids in this list will be added to # the manager). # # Each item in the list is actually a regular expression as used by the # 're' module. include = List(Str) ########################################################################### # Protected 'PluginManager' interface. ########################################################################### def __plugins_default(self): """ Trait initializer. """ plugins = [] for ep in get_entry_points_in_egg_order(self.working_set,self.PLUGINS): if self._is_included(ep.name) and not self._is_excluded(ep.name): plugin = self._create_plugin_from_ep(ep) plugins.append(plugin) logger.debug('egg plugin manager found plugins <%s>', plugins) return plugins ########################################################################### # Private interface. ########################################################################### def _create_plugin_from_ep(self, ep): """ Create a plugin from an extension point. """ klass = ep.load() plugin = klass(application=self.application) # Warn if the entry point is an old-style one where the LHS didn't have # to be the same as the plugin Id. if ep.name != plugin.id: logger.warn( 'entry point name <%s> should be the same as the ' 'plugin id <%s>' % (ep.name, plugin.id) ) return plugin def _is_excluded(self, plugin_id): """ Return True if the plugin Id is excluded. If no 'exclude' patterns are specified then this method returns False for all plugin Ids. """ if len(self.exclude) == 0: return False for pattern in self.exclude: if re.match(pattern, plugin_id) is not None: return True return False def _is_included(self, plugin_id): """ Return True if the plugin Id is included. If no 'include' patterns are specified then this method returns True for all plugin Ids. """ if len(self.include) == 0: return True for pattern in self.include: if re.match(pattern, plugin_id) is not None: return True return False #### EOF ###################################################################### envisage-4.4.0/envisage/import_manager.py0000644000175000017500000000422012252661404021205 0ustar davidcdavidc00000000000000""" The default import manager implementation. """ # Enthought library imports. from traits.api import HasTraits, implements # Local imports. from i_import_manager import IImportManager class ImportManager(HasTraits): """ The default import manager implementation. Its just a guess, but I think using an import manager to do all imports will make debugging easier (as opposed to just letting imports happen from all over the place). """ implements(IImportManager) ########################################################################### # 'IImportManager' interface. ########################################################################### def import_symbol(self, symbol_path): """ Import the symbol defined by the specified symbol path. """ if ':' in symbol_path: module_name, symbol_name = symbol_path.split(':') module = self._import_module(module_name) symbol = eval(symbol_name, module.__dict__) else: components = symbol_path.split('.') module_name = '.'.join(components[:-1]) symbol_name = components[-1] module = __import__( module_name, globals(), locals(), [symbol_name] ) symbol = getattr(module, symbol_name) # Event notification. self.symbol_imported = symbol return symbol ########################################################################### # Private interface. ########################################################################### def _import_module(self, module_name): """ Import the module with the specified (and possibly dotted) name. Returns the imported module. This method is copied from the documentation of the '__import__' function in the Python Library Reference Manual. """ module = __import__(module_name) components = module_name.split('.') for component in components[1:]: module = getattr(module, component) return module #### EOF ###################################################################### envisage-4.4.0/envisage/ui/0000755000175000017500000000000012252662115016246 5ustar davidcdavidc00000000000000envisage-4.4.0/envisage/ui/workbench/0000755000175000017500000000000012252662115020230 5ustar davidcdavidc00000000000000envisage-4.4.0/envisage/ui/workbench/workbench_editor_manager.py0000644000175000017500000000265712252661404025636 0ustar davidcdavidc00000000000000""" An editor manager that uses contributed editors. """ # Enthought library imports. from pyface.workbench.api import EditorManager, TraitsUIEditor class WorkbenchEditorManager(EditorManager): """ An editor manager that uses contributed editors. """ ########################################################################### # 'IEditorManager' interface. ########################################################################### def create_editor(self, window, obj, kind): """ Create an editor for an object. For now, the 'kind' is actually a factory that produces editors. It should be a callable with the following signature:: callable(window=window, obj=obj) -> IEditor """ if kind is None: kind = TraitsUIEditor editor = kind(window=window, obj=obj) self.add_editor(editor, kind) return editor ########################################################################### # 'Protected' 'EditorManager' interface. ########################################################################### def _is_editing(self, editor, obj, kind): """ Return True if the editor is editing the object. """ if kind is None: kind = TraitsUIEditor return self.get_editor_kind(editor) is kind and editor.obj == obj #### EOF ###################################################################### envisage-4.4.0/envisage/ui/workbench/api.py0000644000175000017500000000052012252661404021350 0ustar davidcdavidc00000000000000""" Envisage package Copyright 2003, 2004, 2005 Enthought, Inc. """ from workbench import Workbench from workbench_action_set import WorkbenchActionSet from workbench_application import WorkbenchApplication from workbench_window import WorkbenchWindow #### EOF ###################################################################### envisage-4.4.0/envisage/ui/workbench/images/0000755000175000017500000000000012252662115021475 5ustar davidcdavidc00000000000000envisage-4.4.0/envisage/ui/workbench/images/about.png0000644000175000017500000002106612252661404023322 0ustar davidcdavidc00000000000000‰PNG  IHDRÈP\m:°sBIT|dˆtEXtSoftwarewww.inkscape.org›î< IDATxœíyx“UÚðï'Oö½Iš¦MÛtoi)…²dw¡¨Œâ6¾Î‡~:Š#^âø¹Í|μ.—Ì«‚£3¯øâ6ਠŸ(²(PZÖR( ”îM×,m¶f_Ÿ<Ï÷GHš¶iš¦;æw]U’g;IÎ}Î}îí ATCŒ1BBšìĈ1•‰ HŒaˆ HŒaˆ HŒaˆ HŒañxn.ÌKÚúõÉ4•Î<虿f*++ãvîÜ™USS#p¹\èd·gª2¡rY®a^jÕôÍçe'À».ƒcoÞ¯ÜWé c?ØÛò«Ÿ—¥áø˜ß:Æ Î„ HUs'ø5—A…wÿ÷rXR l:îZ˜ ¯ýÇ’qyvM[·¡SכǸa™P1XýÔœÒ@§ô·äHP˜?.ϯiïfŽËcܰL¨«×êœÔu@}‡–“êèr:$­VKÕét§Ó‰fffÚÅb±;Òëår9C&“9Édò”1_ã8A (ŠŽ¸M^¯éì줫Õjzbb¢355ÕÍ}|†µZMS«Õ4ÇCâñxžÌÌL;ƒÁˆZ¹žP±»°~3ÖUrxò¶Y@§ö5£Qa€šví¸<_k²OŠ€Z,ôäÉ“ÂÊÊJQOOÏ 5ËåºsrrÌ7nì¢Ñh!̽{÷&^ºtI ×ëéÛ¶mû…ÃáxÃ=ó­·ÞÊV*•,€M›6µX>ðàA©×ë ˜EjkkùO?ýôlÿë­[·Ö°Ùì!ïaRVV&llläj4º^¯§‰Ä1þ|Ãí·ß®Îl\SSÃþþûï¥*•Љa}ý‚L&ãIIIöõë×+ ­ao>;~ü¸°¼¼|8Ñh4ÒT*•,¥Rɪ­­åmÙ²¥e¨¹ÿþ„Hý™N§c‰Ä©T*™‡ÔÙÙÉþàƒrÖ®]«\¿~}O¨{à8§N:t(É`0ÐP%$‰C,;ív;Y­V3¬V+Ån·“øá‡”ŠŠŠ„çŸ¾Q"‘D<[LP“+mZ¸Ò¦ìŸæà8;vìH»|ù²@,;Ö­[§*,,´ €^¯§üýïÏR«ÕÌŽŽöÛo¿óꫯ6D«f Gii©¦´´TsêÔ)Á®]»Ò {Ÿyæ™Öá®mhhà544ð!æÎ«ÏÏÏ7åååÙìv;zöìYAyy¹ ¹¹™ûÃ?H6lØ x?þ8µªª*@ ¸yä‘¶œœ‰DǃÔÔÔpvïÞfµZ)Lîîîf<ùä“íÁ÷hnnf~þùçi†A&“ñ¥K—öÜu×]Ý</b„aràÀñ¡C‡¤A &“‰ºk×®Ô^x¡e$ßפ ÈŠÇã!ù…àõ×_¯ ¥v…BÏsÏ=×üÚk¯å[,ŠJ¥bž>}:nÙ²e† mp„¤§§[|ðÁ®ôôtGðûiiiJAˆãÇ'”••IÖ®]«a2™Á ¾¾žåŽøøxÇ‹/¾ØÜ©) Q\\læóùÍÛ·oÏu:èÅ‹…555úÂÂB‹ÿ¼††¶Fã³Hë÷»ß)¶“L&ëׯïq8¨¿M---\—ËEJ E,¾`‚§“óù|lÞ¼yãAyy¹xB5B233ͯ¼òJÓ@áðsÓM7„Ã0RGGÝÿÇqسgOŠÿõí·ßÞoÄ&##ÃQZZªô¿Þ»wo2¥ëŽ;î¨hA *•jjŽ˜€L.\è\J¥’ÕØØ8åLÒ %l/•ÉdN‡ãñ¿V©T©©©áøl6Û³hÑ¢Þp÷Z²d‰L&ãÝÝÝÌ‹/FåAf³Ù^6›hŽã#ŠÙ˜Pë¯.ì°»°®ÐG‡W¹w•]M8p®eJŽ®£%##â(î_Ìwuu1rssí“Ý®‘"“ɬ׮]‹ð©™þ÷;;;Ö»¤¤$ûpfj6›íMKK³¶´´p|ßÇüùóMÑ´‰F£y­VkTÌ xsTyl5¬isºÃd21‹ÅB0 ÔÉnO4P©Ô³ŒZ­Çó„:g \.7p^ww7=ܹãŘˆ\m¢jPs;µz§ÎB×›Ô€‘˜×}¯ƒÿ?è¼ “¯³Øc#ÐæólèØ&ëÿlj‚ ˜Ùv`Ͱ`¥ñ±&…‚ÅbÄh4NK ­Vèà‘ H°º¦Ñh¦Ÿ€th-ÔÔ&UÔ(}š|ÈŽÉy!Ž^; ÜÝôÁ.,—|ún÷WVÈz³h‰ý £%Øo`±Xn( #…B Ìþ'¢u@°Š6ÔÌ4ÞD½HßsF.|pû±™å5J!A„Ív𾨨pmc>X¯MH£Õj E°zq# ‰Ikfsd©Áƒ„P89IoQ È‘+ Þ{®Ê¼8qc F0^ ZßH"r/v´/$E"ш<¾S„„„@T} þ>œãѮ᱀T˵¬×¿¹˜ß¨³F(œLP|<®Ö3FC V)âãã‡1].×´3ϧ¦¦|']]],‡Ãö3x½^$xÝ’’’Ò÷2ÞŒø‹þðç:©Û‹O»hÔtïIÜ=nƒÂÉ“'^w …‚™ƒÓéô€0Ød-Dp0ÖPXXh‘H$vŸ±ººšîüË—/sý3ˆD"qÌ™3ÇîüñbD½Ie¢_ëêå æh™‚ñX¸…Þܨ/ã ¶Z­è¹sçDþ×%%%ÚѺ)))ŸÈ‰'†M˜Õ:@XãÆ ‰r^»vm 6ëСC‰V«uÈTßŠŠŠÀ÷±nÝ:Õd•ÑS÷œ‘O&ÓÁ¤‘}Îk.[4êB¯¼òJAyy¹``D®ÝnG·mÛ–e2™¨¾Ù£´´tPëìÙ³žçºº:þ®]»’C©)555ì7ß|3W¥R…õÄËd²€Ê¢V«™ÝÝÝãnV.))1¦¦¦Zt:ýÃ?L8pà8Ÿ~úiJcc# ##ÃRRRbï¶ EĦD(¯S Ƴ1£"‚I'[*òu {”a¦j>DT&ôz=ýË/¿Lß¿²T*µÇÇÇ;µZ-].—sü¡æL&Û´iS‹H$dÁZµj•^.—³/\¸ 8uêTÂ¥K—éééV@à6…BÁ2 ƒbôíp8ïŠ+ºËËË%†‘¶oßž3gÎCAA¹  À:#6‰D‚^x¡yÇŽi×®]‹kjjâ½üòË3³³³Íééé6•JÅhnnæ¨Õj&€/ñ±Çë󆌀ˆ¤]k¦ÙœØ´¶Í¤‰}jн‰Õ p; ŽV03GlrܲeKãáÇø¼ÆÆÆ~±EB¡Ð¹yóæ©T:ä½}ôÑN@à.++“`F²ÙlX‡©Tj+..îmllä455ñ†Ê¨»ÿþûU^¯9sæL¼Á` •••%–••%F’-4 ß¼ysë7ß|“TQQ‘`0hUUUñþ(_*•ê]³fz¨\‰$âµ]]§š"°d¬(Sb‚p¶Gh½ÆŒF@òóó­ùùùV…BAohh`i4ºÕj%Ëd2[AA599yX3&™L&6lØ ^±b…®¶¶–í¿àóùž¸¸8L&søÓwßyçÀZ18ä<E‰‡~X±víÚž¦¦&VOOÍår¡t:= P………æÍ›77p8œaC…Ö­[×½xñb=@RRRÈÏD"‘àþûïWmذAÝÖÖÆhnnfuwwÓ“’’9996™Læ7ƒ-\¸ÐèWƒ×RC±qãÆv·ÛM צ¡ˆX@ê”Æ)]:þp×¢.6ƒ†ƒ¡Œ¸#ú:P¶zÀ]a#QÑœœìŒDÂ! =7ß|sØ6èõú€ªÅb±ÂÎB¡Ð³hÑ¢z>ŸÏÇø|¾%Ô±P¤¤¤8SRR"ú|(ŠYYYö¬¬¬eÆÇÇ»ããã#öåååÙFrÿ`"V4[5–i[2gn®ÔxÏÒxŒ((>]A{ó”(p‡ÞÞÞ€€„Ë/žˆg^ëàjˆÂ(-¾+f§ëÿôÛe¾ûηS3îs¸5S>ˆP§ÓQýÖ2‰Db¯ôÝ_ ˆÕé™V tq˵éŽùе r|ªƒú_ `ª½3Oùï¡««+àÎÏÏ*‡"†QÈØ J$ ØÎì$¾-CÂs (BôÝzà3€ö_0%IqÎYib;ŸÃð©.5:¶¦­&j'_?p' ^' Pú”,dj4É_ýu*€Ï¼»`Á‚¨×K1"¯G0ïØ& 9 ÷3¥³»$Y8 ÚØëÈŽv˜+¹Ðýoé¨å¡ðèÈ€&O©`Bǃ”••‰Ž=*1›ÍT€»ï¾»+33sRb˜n"%t êuz¼cÒÑV¥ê^Ú0O —‚öz&¸Ú¾¨Ùà䪳HpîÆ Ä*/®Ø[™€Ûɾûܘê·Ëå">}:®££ƒ©T*™jµšév¤ÓéXii©ê¶ÛnŸ |¿""V±Øtʨ€?ß_"_7?÷.0‹ÍîðZ(ý:þÀ¤©A‰UCýÛí8 E4¬?EQB(:õzý¸d‘Édbß¾})þâp‚b±Ø‘——g^¿~½z¼}¿6F" ˜Îâ•çÎ’Œžuó3Œ€™Qèþg*XªS201$ÉúƒL&[·n­miiaVVŽq`(Š·Ür‹EQ"==Ý–™™éI½§‘±€94w»Öµ@*d9Ÿ[_¬åÛéਟž»>¡#Ëô‹Æ)wÞy礇bÜèDì(ÌJäj±÷ʽ Úé2†¢i+ŒÐEÓbL]Ì6úõ‘*῞ v"žAò¤üë£àÈU".“ê™—™`¯í×É#¾ÁT‚;'â°‹Sƒ—ÿù­ìô/Íq·.š©ÝH®x™™ÂZMÈNŠóÅÂØ˜@Lóýð¸óc2ÍÀ¼¾jŠdÒÈ·X@RE7‡N¾ð[ˆ&ÌHŽó —³eZGÊÆ€=3¦bM3°ëa7(:ް8/!*¯lNÒõÙÇ5ú\êIE°Â0nÛñÆ7¼þ$Š˜´ Ⱥ¹©Qm_FF¯g´Ó¯G?î‰9Þ¦!~ O `~–Ø&á3&¥>ѤÃ̱+ï×ùÙ§9þEG>>øŠÍ·Ï´Yɯ‚äß«&» 1¢Ã‹ûU¬‘Ï #Ý^=+ÙTqM¥;zU1b›ò´…³â–ŒÊz¥é5“Ï\iᨴ½Ôn½‰*òÜsg¤YçäÊì4Jø­4½fòÅúvVM‹’åÁ0¤0+Ù67/Í&Ç rZÚ.Òá³Wù) BWÉÌŒ™tj‘²¯â’à··.Ôñ9ƒÓqk[•Œ+M]Ì»–Íée1ú{çMVZuMήoS3R…®ÙÙ)ötéÐEîü`^©•+Wš»˜µ­*–Éj'ß¶¨Ð°~yñ um]›Š^uMÎñ`^¤ CjŸ“jØŽ˜¬ô|m+«º¾ãö`ˆTçZPn••âÀúf‰ÙÄó¥»çt)ôVz½ÒÈŽæúi5ÁiŒzÖ¼ÔÐÁüø‡“’s×ä|ÿT`ß HKÙwþyc³8ŽÒB¸ëàÑû{Ž¥x°¾M@÷»°qÝå3¬é&þËÎ\¹BÃ\5†>”€”]¨ãþñ½¯³  žÏñÜ»zþ ­ÞöŸüEðõ‘*ɲâ\spÇ|÷‹Ÿw®L$!‘¿^zöŽ¥³5¯>v§‚BÐ=˜ùâðYÑ—?KÐôZYŽ‚[~{«2øÜ6¥–¶éo»²Ô:ÝAˆÇvÿmó½­óóÓC ü…º6ֳ۾̲Ú]d€à¶É…½ÉW€.3oT¦SðOþ°¼ñ㲆„]IvƒVZä-ì…Ì¿v™UàßîCgEÛ¿:’ pÛ™º›‹sMy²D‡XÀÅNÿÒÄyëóƒ²vµŽùÈ~’»ûµß7¸ýsÇ_üàÙO•5"âýý+;WÌa¼^äèù:þ§ûO%}öãii]›Šµãåÿ%÷ ɼif¹Bìªmåa^¨V”¯ãûc9W×óC HåÕ^†4Þ6‘¥ä87…®áîCF󆋆 kýûƒÄ»‘ev¾x%ÃîÓŽ/WJ¼^yâî劧6¬ì7uË™&‰ˆ×ü»¿ìÌWhzé_þtNôô}«ç:s•÷SeˆBFñz¤±0+9Ð1²RzÒ“DΗþñmfÕµVþ·e÷­^`X½ ߸çèy‰Õî"W^ma/“ÓO5'ÈJÛüÂáG–(r'¸®ƒ™¶çèyáKK¿Ûã+ˆÇeaá„£®ME¿Ú¢à¬^?¨-(È0;_'¬¸Ø °Ø¦/šúÛ²jÆ`¦<}ßj%;Ä:…N¥¤ë…óÆÝQ8é`F2Øê™}uCüÕöýYƒþœ#Úát´Tä¡G®ùéf€ƒ™ÖØ®¦\mîbö\ÿá×.ž5ävз-*4èŒVjC›Š@B()Ì4œ¹ÚÒ¯@tYµ/Hô÷¿YÞ(â9Ž_¨ïwÎé+Í<—噓+³¸=RyUÎX9oFÈA0?#É Ð¥1ôË} „x Ó1ž¾" QÉøŠy3©ö¯=0/Žt¨ûÊ:sEà+t9Ôú¤_;¢¨9å ôÃt"Œñ‘'L ø7»ØÙïÈ'ª¹ób˜Å¡Ë èùj½‰:#=ÉY½³ ±ƒIIˆ kUéhE9©v€•óf®¼&ÒÌ´kr%ï·Ÿ¹ÒÌKp]ùéIÎ%E9ÆoÊ.øŒ8Þ’H`u¸H5- Îêù‡p»ZGóŽCg®ŠN\jŒ‚„ €\_÷h¾mãôÆþeú¯ÎA×ÙÝW.õáWÿ'AA@Ï|G:#Åÿy_i£T‰ÐÁc3†Tã¿C˜j(¦—€L3°Tlfßú¬[ï+IÔ¦ÒFâø8Îù'¿(QÄ ¨Nê!<{ô}×f§ôߢìæâ\#@‹BÃRi{)§~iâ±™4laa–íz›`áÌëk•+Í|œ  ª¶•7''ÕlÕÊJîkµ,–Ρƒ.U"t8œžˆïO§RÓ·©PSgwØL×HÛŠG@è©/ÌÜõ·d²[E:ô¬Ne§ú:ypgoîìR@š¾c4 ÏI•ô[JfšüÛì?ùKÜå†î‚ü Sp[V^ŸM4½Ú¿WŠŒ;e霜~…æ2¤ñ.ÉwŸÚVåˆR®#]¤§'Å;ºzô —‹8\Ú¯vµ)µLl 6ÿ EïhÞÑ3Ž¡â²âV(ì"€´Wб© ¬O5jqh¶9Ð3W›ãææ¥™âù¾êéóóÓmÙ) 6€¦Îž'8|¶FpÏÊy=C1D|66#-ѰûðÙD—ë·¶è[«ìø®\ °zAA¿s¨2‘!·|W~qD(E:rçɬf›“ü}Å¥ˆ7)Z»x–ÀåÁHµ­ÊÕc¼8°ÓLý„2|ÌNT°fÐÓú¿G¢×ö*™°­„ƒääå&žÑÒW+/ŽÃ_>ú>Ååöù ž¼g¹:øø£w.UüÒÔÉýæØ…AUQv|[ž 1˜il& {òž!‹7,ã+»jµ»Èd”D¬ òmô­Uüçd¥ˆm‰"Þ £ÀkOü¦ƒBFñ®cëç“B=ëjKãJsW¿Ù.ÒEú²â\Ëš’ÀŽÿW.­kS êì&«=xæ rß»zžI§zÞød¿,ÔìS^Ý%ýþ“‘0±‹tF® Œec_|ˆlàï³ ¢.ƒ?R‚äàé+ñÇ/Ô n.ÎéÍJ;œnŒtòR#¿¹«‡ðÄÝË úÇM­]<ËÔÑ­W|ô]yòßv’5uö0–ÌÎ6[ìN´âbïhU­HÄg»·=û€|(ç-%ƾ«H(ÊN5‡ê Ëçæ+k|~ŽÅEÙ!ëødHOß»J±ý«#©_©JlêìaÞT”eŠçs°s5rNUm+Oo²RçÍH3}òmñ_ç½ntˆÄƒý×߯ïº&W°Õ:ý±×?Ë+]R¤™)µ«tFêÙ+ͼÚVÛ‹ãˆ8ŽÛà÷y°4üÝghÙ²í«¬¦ÎÖc¯š}çÍstÉ wC»Šqä\­ ®MÅž‘–h}î¡[}Ç‘0ÁR0ªÐ!q´ØêX3úÞóÚ GûŸÇ™3>ÏA°€l¾ugC»šy¬ªNøsåµÀû">ÛýÔ†•Š{VÎ é€ÛtÏŠ™DèüïoK¿);/Ù{ì¼€A£zKff_òîŽAè G€¬”W²8ÎiwºÑ «æ…LöZSR`úÛ®CD²Xà\·¤hÈhˆw,Ѳ4ïŽïÊ¥ÚyÚ!+I"¾ó¹‡níxà–’~ u#Iue3hø'~´éí/KË«„{—ì=æ;F!£øÍÅ9†'~³¼{Fzÿ pnš•e}ÿù‡šÿk÷¡”¹’í÷È ¸,O†4Þþ榻åë–ÎŽzC„ ˆêh/ŽŠî¥`üICWIŒ¦²"Y ¼ n€õ2€ö;Ÿàø§ä¸UZH{¥s\?[j‰rÛ3ïÎxÿù‡š–çZŒ;*Whhš^3¥ CêçˆÙæ@¯6w1âã¸Xvj‚“aêog·ž*ǹÑ0àå‘ÂÐ_ÅÉècj19žt2Ï )/·éD/ô|– ^óØï^…²1H|´ DwLŒ ÀdQ‰ ÈôerCMxËŒÀ½É¦Ó<0½Ž7ºbÖ3ß q«ô XmÒämr<ƒDc15˜üX,„B…ø+Œà1Áv• ö:68šYàêb†-„Pq §Ù€™cf¾ØE6 Ædq6Z° ¦h<¸1¦“/ ÁPð—¿¼Ï,絑À£§f î&ÊòÊñ™ë”‹25£e8LºWÄg»uF+5¦bM_&ÞŠõ+'8{¥™]˜•6_!ÆÔ%& 1b„ajê'1bLb#Fb#Fþ?’÷È:åbIEND®B`‚envisage-4.4.0/envisage/ui/workbench/images/application.ico0000644000175000017500000004624612252661404024510 0ustar davidcdavidc00000000000000hV¾ˆ Î ¨V00¨%þ&(  ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿU,O)1I%@C"ÿÿÿs: t;P|?™z>‡s:?s:ÿÿÿÿÿÿÿÿÿe6b3…mEç3xUî5tTó%cDçS,ˇNáJ¶ùLÁ…ÿK»‚ÿA©tó ~CÉs:ÿÿÿÿÿÿ o=¸¶yÿ€HÖ"’Yß3ªnÿ˜YÿTÿ‚Gÿv>ÿD±zÿ>Ò‡ÿ;΃ÿ6È~ÿ½lÿ²_ÿ ­Zÿ §Wÿ)¯jÿ'ŽYå"–[ÞN¼„ÿJ³~ÿH¬yÿD¤sÿ+]ÿ9¥oÿ^Ùšÿ[Ö—ÿWÑ“ÿRÌŽÿBÃÿ²dÿ §Wÿ4³rÿ‡QÝ’RÈZÃÿdÓÿc½ÿ`¶ŠÿY®ƒÿ$YÿfÛ ÿeØžÿbÔšÿ[Ï”ÿUÉŽÿMÇÿ1µrÿNºƒÿz?¿ ‘OO:¬sîrÍŸÿ}̤ÿyÆŸÿs¾˜ÿ?œmÿNºƒÿlÚ¢ÿkÖ ÿdÑ™ÿ]Ë“ÿTÅ‹ÿSÁ‰ÿ.•`ês:*ÿÿÿ”R®C³{ôsÍŸÿ‡Ïªÿɤÿe·ÿ‰SþC°yýlÖŸÿfÒ›ÿ]Ì”ÿ]Æ‘ÿ0˜cìy=uÿÿÿÿÿÿ“P ‘Oy!˜\ã7¤lë:¢nï(Zä t@³y>i‚GÓ'’[å!ŒVá }AÇt:2ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ L ‡I' F6 {Bÿÿÿÿÿÿÿÿÿs:ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÏÀ€€€€à‡ÿÿÿÿÿÿ(, ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿR*N(5J&>E# ÿÿÿÿÿÿÿÿÿs:)s:dx=¦w<¡s:_s:#ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ^15[0Ó Z2÷d?ðb@ñ O,÷E#áI$Vx=©„Mó;¥o÷Q¹„ÿN¶ÿ8 kõ€Hôv<–s:ÿÿÿÿÿÿÿÿÿÿÿÿg6i=óChÿGkÿ7[ÿ4zVÿD€aÿ?|\ÿu>þ@®vý_Ó˜ÿ;Ç€ÿ$¾pÿ&»pÿ;¾{ÿ^Æ‘ÿ8Ÿjöw<Þs:ÿÿÿÿÿÿ n;_{LôNŸuÿNÿo;ÿh7ÿb3ÿ[/ÿr<ÿP½†ÿEЊÿ¿gÿ»dÿ·aÿ ³_ÿ ¯\ÿ¬[ÿG½ÿB¦súv;«ÿÿÿ v? uAðO¦zÿ‡Lÿ {Aÿ u>ÿn:ÿg6ÿh6ÿ)”]ÿZؘÿÃiÿ¿fÿ»dÿ·aÿ ³_ÿ ¯\ÿ «Yÿ §XÿZÁŒÿ‡Qòs:D {B6šhû,dÿ ‡Hÿ Eÿ zAÿ t=ÿm:ÿv<ÿaÌ–ÿÊsÿÃiÿ¿fÿ»dÿ·aÿ ³_ÿ ¯\ÿ «Yÿ §Wÿ«cÿR°€ÿv;¢ €EÁL®|ÿ—Sÿ Lÿ ‡Hÿ €Dÿ zAÿ s=ÿx>ÿhÙ ÿÇlÿÃiÿ¿fÿ»dÿ·aÿ ³_ÿ ¯\ÿ «Yÿ §Wÿ £Uÿb½ÿv<Û „HïUº†ÿ¡_ÿ ›]ÿ •ZÿRÿ Fÿ y@ÿ€IÿiÞ¢ÿKÔÿKÒÿKÏŒÿ:Ç€ÿ#½oÿ´aÿ ¯\ÿ «Yÿ §Wÿ£Tÿ]¿Œÿ w?ú ˆJíX¾‹ÿE´|ÿC¯xÿAªuÿ?¤pÿ;kÿ(Zÿ y@ÿoÜ¥ÿYؘÿWÕ•ÿUÒ’ÿSÎÿOÊŒÿEÅ„ÿ!¶jÿ «Yÿ §Wÿ£Tÿb¿ÿu;å ‹L¾L¸‚ÿXÁ‹ÿW¼ˆÿU¶…ÿR°ÿO«|ÿK¤wÿ‚HÿgКÿbÚÿ_ךÿ]Ô—ÿZЕÿVÌÿQÈŒÿLćÿ:»yÿ«_ÿ¨]ÿW´„ÿv<± M{6ªpú^Ç’ÿiÆ—ÿh”ÿf½ÿb¸Œÿ^±‡ÿ0‘_ÿ<¨qÿjÛ¢ÿgÙŸÿdÖœÿaÒ™ÿ\ΔÿWÊÿRÆ‹ÿLÁ…ÿE¼€ÿWÁŠÿ.“_õt:`NOîTÁ‰ÿuΡÿ{Ì£ÿyÈ ÿuÛÿq¾–ÿhµÿFÿ`Ê“ÿhÙ ÿk× ÿgÔÿbИÿ]Ì”ÿWÈŽÿPÈÿL¿„ÿR²þv;Ñs:ÿÿÿNU ™\ô[ÅÿzϤÿ‹Ó®ÿ‡ÎªÿȤÿ{žÿU¨~ÿ€Hÿ[ÅŽÿkØ¡ÿjÕŸÿgÒœÿ_Í•ÿSÆŒÿbÉ”ÿP³þ yAñs:'ÿÿÿÿÿÿÿÿÿ OoQôEµ}þbÄ“ÿkÆ—ÿhÀ“ÿXµ†ÿH¤vÿwDöw<ð3 i÷S½‡ÿiÌ™ÿeÈ–ÿO¶ÿ-•`õv<Ðs:!ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ŽM* MÈJõUò Wñ|E÷ w@Ò q¥qÿ<Ÿmÿ5˜fÿ‚Lÿ‡QÿiÝ£ÿX×—ÿWÕ•ÿTÒ“ÿRÏÿOËŒÿKȉÿ.¼tÿ°^ÿ ªXÿ ¦Vÿ¦Yÿa»ÿu;ä ŠK»E³|ÿS¿ˆÿT»†ÿR·„ÿO±ÿM¬|ÿK§xÿDžpÿ w?ÿsݧÿ`Ùœÿ^ךÿ\Ô—ÿYÑ”ÿUÍÿQÊÿMƈÿDÁ‚ÿ#³iÿ ¦Wÿ$­gÿL¬{ÿv;° ŒL€3¨lùZÅÿdÅ”ÿdÁ’ÿb½Žÿ`¸‹ÿ[²†ÿX­ÿFÿZÅŽÿhÛ¡ÿeØžÿbÖ›ÿ`Ó˜ÿ[Ï”ÿWÌ‘ÿSÈŒÿMćÿG¿‚ÿ9·wÿYÁŒÿ+‘\÷s:m N!NöTÁŠÿmË›ÿvË ÿtÇœÿq™ÿn¾•ÿh¸ÿJ¢uÿ‚JÿrÚ¥ÿkÚ¢ÿi× ÿfÔœÿaјÿ]Í”ÿWÉÿRÆ‹ÿLÁ…ÿM¿…ÿT³‚ÿu;ãs:ÿÿÿ Mq#›^õ`Ê”ÿzФÿ†Ð«ÿ̦ÿ~Ç¢ÿxœÿr¼—ÿ+Ž\ÿ0fÿpÙ¤ÿi× ÿkÖ ÿfÒ›ÿaÏ—ÿ[Ê’ÿTÇŒÿ\Æÿ`¿ÿ|E÷s:;ÿÿÿÿÿÿ’O NÀ#š^öWÃÿpËÿ~Í¥ÿˆÏ«ÿvÄœÿ^¶‰ÿZ¯„ÿIþ„MÿaÊ”ÿp×¢ÿiÓÿcЙÿbÌ–ÿjÍšÿD«wüw=õt:^ÿÿÿÿÿÿÿÿÿÿÿÿ‘O ŽMxP÷2¦kùB°xÿQ·„ÿH­zÿ6›iý‚Nõ p<Ìq:lu;òJ÷/™cô9¤mú$ŽXó xAûv<Ìs:!ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ M( ‰Ju ˆI· ƒGè €EÒ {B’ v?S r=ÿÿÿs:s:+s:as:}s:Ls:ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿø8àÀÀ€€ÀÀðü?ÿÿÿÿÿÿÿÿÿÿÿÿÿ( @ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿs:s: ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿU,R*NO)„L'žI%nF#9C"ÿÿÿÿÿÿÿÿÿs:/s:t:¹t;ðs:ÿs:ýt;Ös:œs:cs:ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ]1FZ/ßW-ÿT,ÿZ3û_;û P+ýI%ÿF#ûC"¯F#s:~s:ü€Iû-›býD°yÿYÁŒÿO·‚ÿ8£lÿ!ŒUûu<þt:Ós:,ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿe5 b4”_2þ$tJüB‰eÿT“sÿY•vÿX’tÿXsÿK‚fÿ7pSÿ R/üj6ûu<þG±zÿwÙ§ÿiÕžÿSÍÿ?Å€ÿHņÿ]Ë“ÿpПÿfÔÿˆQûs:ús:pÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿk9h7Û j:ü>ŽfÿXŸ{ÿ6ˆ^ÿ!yLÿi<ÿa3ÿe<ÿ&mIÿ?|]ÿwAÿ†NÿeÍ—ÿjØ ÿ'Äuÿ¼dÿ¹bÿ¶`ÿ ³_ÿ ±]ÿ¯]ÿF¿ÿrÌžÿ=£oÿt;ÿs:aÿÿÿÿÿÿÿÿÿÿÿÿ m:¥rBûS£zÿKtÿ~Iÿ q<ÿm9ÿh7ÿd4ÿ_2ÿ[/ÿf4ÿ yAÿnÕ¡ÿTÔ“ÿÂkÿ¾fÿ¼dÿ¹bÿ¶`ÿ ³_ÿ ±]ÿ ®[ÿ «Yÿ'³kÿjÉ™ÿ:žkþt:òs:ÿÿÿÿÿÿ s=L p<ÿGžqÿE¡rÿ Dÿ zAÿ u>ÿq<ÿl9ÿh6ÿc4ÿc3ÿs:ÿNºƒÿeÛŸÿÄjÿÁhÿ¾fÿ¼dÿ¹bÿ¶`ÿ ³_ÿ ±]ÿ ®[ÿ «Yÿ ©Wÿ,³nÿpÈ›ÿIûs:´ÿÿÿ x@ v?è*Œ[ýX¯ƒÿ‹Nÿ ‚Fÿ ~Cÿ y@ÿ u>ÿp;ÿl9ÿg6ÿp9ÿ$Yÿwߪÿ&ÌxÿÄiÿÁhÿ¾fÿ¼dÿ¹bÿ¶`ÿ ³_ÿ ±]ÿ ®[ÿ «Yÿ ©Wÿ ¦VÿQ¾†ÿY¶†ÿs:ÿs:= {BJ yAÿX²„ÿ.žeÿ ‹Jÿ †Hÿ ‚Eÿ }Cÿ y@ÿ t>ÿp;ÿk8ÿt;ÿO¼„ÿSØ•ÿÇkÿÄiÿÁhÿ¾fÿ¼dÿ¹bÿ¶`ÿ ³_ÿ ±]ÿ ®[ÿ «Yÿ ©Wÿ ¦Vÿ¨]ÿrÈÿ|Güs:t }C|ƒNú^ºŒÿ–Sÿ Mÿ ŠJÿ †Gÿ Eÿ }Bÿ x@ÿ t=ÿp;ÿs:ÿeΙÿ=Ó‡ÿÇkÿÄiÿÁhÿ¾fÿ¼dÿ¹bÿ¶`ÿ ³_ÿ ±]ÿ ®[ÿ «Yÿ ©Wÿ ¦Vÿ£TÿhÅ–ÿ)Züs:¦ €E¯*–^üU¹†ÿ—Qÿ “Oÿ ŽLÿ ŠJÿ …Gÿ €Eÿ |Bÿ x?ÿs<ÿu=ÿxݪÿ4Ñÿ"Ëuÿ$Ètÿ&Æuÿ%Äsÿ¾hÿ¹bÿ¶`ÿ ³_ÿ ±]ÿ ®[ÿ «Yÿ ©Wÿ ¦Vÿ£TÿT½ˆÿ>¡nÿt:Ü ƒFä:¥oÿN¹‚ÿ(¦fÿ)¢eÿ)Ÿcÿ)›aÿ&•\ÿŒQÿ Fÿ |Bÿu=ÿIÿyâ­ÿUØ–ÿQÖ’ÿPÔÿOÑÿMÏÿKÍ‹ÿ:Æÿ%½oÿ´aÿ ±]ÿ ®[ÿ «Yÿ ©Wÿ ¦Vÿ£TÿE¸}ÿL«zÿs:ú †Hà9¦pÿT¾ˆÿ>²wÿ=¯uÿ<ªrÿ;§oÿ9¢mÿ8jÿ4™eÿ&ŽYÿ {Bÿu<ÿvÜ©ÿ[ÚšÿVוÿVÕ”ÿTÓ“ÿSБÿPÎŽÿMË‹ÿKɉÿBÄ‚ÿ·hÿ ®[ÿ «Yÿ ©Wÿ ¦Vÿ£TÿV¾‰ÿ;Ÿlÿt:Ö ‡I©)›bû^Ä‘ÿKº‚ÿK¶€ÿJ³}ÿH¯{ÿG«xÿD§uÿB£rÿ@žnÿ4”cÿs:ÿcÌ—ÿbÛžÿ[Ø™ÿ[Ö—ÿYÔ–ÿWÑ”ÿUÏ‘ÿRÍŽÿPÊŒÿMljÿIņÿ2»uÿ­^ÿ ©Wÿ ¦Vÿ£TÿjÆ—ÿ'Xûs:Ÿ ŠKwSû`Ç“ÿZÂÿW¾ŠÿW»ˆÿV·†ÿS´ƒÿR°ÿP¬}ÿL§yÿJ£vÿw>ÿL¹‚ÿlÞ¥ÿaÚœÿ`ךÿ^Õ™ÿ]Ó—ÿZÑ•ÿWΑÿTËŽÿQÈŒÿNƉÿIÃ…ÿD¿€ÿ)´lÿ §YÿªaÿrÈÿ{Cýs:n ŒLE ‰JÿXÂÿ]Æ‘ÿdÅ”ÿdÃ’ÿcÀ‘ÿb½Žÿ`¹Œÿ^µˆÿZ°…ÿW¬ÿƒNÿ‰QÿxÞªÿgÛ ÿfÙžÿd×ÿbÔšÿ_Ò˜ÿ[ДÿYÍ‘ÿUÊÿRÇ‹ÿMćÿJÁ„ÿF¾€ÿ>¹zÿ`ÑÿP¯ÿs:ÿs:2N LÞ)ŸcüaÊ”ÿkË›ÿqÊÿqÇœÿnÄ™ÿmÀ–ÿj½“ÿg¹ÿdµ‹ÿQ§{ÿu<ÿD±zÿtÞ©ÿkÚ¡ÿiØ ÿfÕÿcÓšÿ`јÿ]ΔÿYË‘ÿTÈÿQÅŠÿM†ÿH¿‚ÿU‹ÿmƘÿ|Düs:žÿÿÿÿÿÿ M> ŒLþF·~ÿcÊ–ÿ|Цÿ~Î¥ÿ|Ì£ÿyÈ ÿwÅÿsÁšÿo½–ÿk¸‘ÿ:–gÿv=ÿgЛÿqÜ¥ÿlÙ¢ÿk× ÿgÕÿdÒšÿ`Ï–ÿ]Ì”ÿXÉÿTÆŒÿPÈÿYÅÿqÌžÿ0—cýt:és:ÿÿÿÿÿÿÿÿÿ ŽM•‘SüSÁ‰ÿcÊ–ÿ~ѧÿ‰Ó®ÿ‡Ï«ÿƒÌ§ÿÈ£ÿ{ÄŸÿv¿šÿqº•ÿLÿFÿZÄŽÿuÛ§ÿj× ÿkÖ ÿhÓœÿdЙÿ`Í–ÿ[Ê’ÿVÇÿiË™ÿoÉšÿ/—aýs:ÿs:Lÿÿÿÿÿÿÿÿÿÿÿÿ‘O MÉŒMþ<°vÿaÉ”ÿnË›ÿwË¡ÿƒÎ¨ÿ‰Î«ÿ€È£ÿr¿˜ÿ`µ‰ÿO§zÿwAþs:ÿ8£lþtÖ¤ÿvئÿnÕ¡ÿiÒœÿlÑžÿuÓ£ÿuÑ¢ÿ[»ŠÿHût:îs:QÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿO ŽM| ‹Kü!š^ûA±xÿRº…ÿ^ÁŽÿ_¿Žÿ[¹ˆÿI©yÿ5˜fÿvBü o:àq:‚t;ôu=þ‹Tú6£kÿKµÿ@¬uÿ*–_ü~Gûs:ÿt:¸s:ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ M3 ‹KÇ ‡Iû …Hÿ…IþˆPû }Dÿ yAÿ w?î s>– p< ÿÿÿs:s:as:—t;Ñs:ût;ës:³s:|s:Cÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ŠK ‡I6 „Gl F† ~DV |B!ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿs:ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿŸÀü€ðàÀÀ€€€€€€ÀÀàøüàÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ(0` $ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿs:s:js:s:¦s:»s:¬s:“s:ws:1ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿV-T,cS+«Q*ÌO(èM'ûK&åI%ÊG$¦E#\C"ÿÿÿÿÿÿÿÿÿÿÿÿs:s:ls:És:þs:ÿs:ÿs:ÿs:ÿs:ÿs:ÿs:ÿs:ÿs:Ýs:ˆs:ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ\07Z/¼X.ýV-ÿT,ÿR+ÿP)ÿO(ÿM'ÿK&ÿI%ÿG$ÿE#úC"±A ,ÿÿÿs:Rs:Üs:ÿs:ÿv<ÿ'–]ÿ=©rÿI´}ÿU¼‡ÿLµÿ?ªsÿ/šcÿ {Cÿs:ÿs:ÿs:ñs:vs:ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿb3`2¥^1þ\0ÿ[0ÿiBÿ6|YÿD†dÿM‹kÿRoÿL†hÿC}_ÿ1nOÿW6ÿG$ÿE#ÿD"üe3Õs:ÿs:ÿFÿN¹‚ÿrÕ£ÿwاÿwצÿv×¥ÿvÕ¥ÿuÔ¤ÿuÓ£ÿuÒ£ÿsÏ¡ÿXºˆÿ‰Pÿs:ÿs:ÿs:Æs:ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿe5-c4áa3ÿ_2ÿ$vMÿT˜uÿZ›zÿZ™yÿZ—xÿV“tÿQŽoÿVsÿXsÿXrÿX‹qÿNgÿ[:ÿf3ÿs:ÿt:ÿ,¨iÿo×¢ÿwÛ¨ÿp×£ÿNÍÿ3Ãzÿ&¿pÿºhÿ!ºlÿ-¼sÿC‚ÿf̘ÿtСÿrÏŸÿ<«sÿw=ÿs:ÿs:Þs:)ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿi8Cg6òe5ÿ i:ÿ?gÿ[¡}ÿWzÿ<‹cÿ"yMÿ j:ÿc4ÿ`2ÿ]0ÿ _5ÿ"kFÿ=zZÿUŠnÿyHÿs:ÿ yAÿN‡ÿxݪÿwܨÿE͈ÿ¾gÿ»dÿ¹cÿ¸aÿ¶`ÿ ´_ÿ ²^ÿ °]ÿ ®[ÿ/¹sÿmÍœÿtΠÿ[¿ÿ‚Jÿs:ÿs:ås:ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿl:&j9ñi7ÿzJÿS¡yÿ[¥ÿK™qÿxFÿo:ÿl9ÿh7ÿe5ÿc4ÿ`2ÿ\0ÿY.ÿW-ÿ i:ÿs:ÿ {BÿgÑ›ÿxݪÿjÙ¡ÿ(Çvÿ¿fÿ½eÿ»dÿ¹cÿ¸aÿ¶`ÿ ´_ÿ ²^ÿ °]ÿ ®[ÿ ­Zÿ¯aÿWÄŒÿsÍŸÿnÈšÿˆQÿs:ÿs:Ås:ÿÿÿÿÿÿÿÿÿÿÿÿ p< n;Õl9ÿ!~Nÿ[©‚ÿ[¨ÿ4‘bÿ xAÿ t>ÿq<ÿn:ÿk8ÿh7ÿe5ÿb3ÿ_2ÿ\0ÿ`1ÿs:ÿs:ÿNĈÿxß«ÿUÕ”ÿÄlÿÁgÿ¿fÿ½eÿ»dÿ¹cÿ¸aÿ¶`ÿ ´_ÿ ²^ÿ °]ÿ ®[ÿ ­Zÿ «Yÿ ©Xÿ9¸xÿsÌžÿaÁÿx>ÿs:ÿs:ƒÿÿÿÿÿÿÿÿÿÿÿÿ r=‘ p<ÿtAÿT©~ÿ[¬ƒÿ ‹Uÿ }Bÿ zAÿ w?ÿ t=ÿq<ÿn:ÿk8ÿh6ÿe5ÿb3ÿ_1ÿn8ÿs:ÿ(¥eÿyà«ÿiÛ¢ÿÅlÿÃiÿÁgÿ¿fÿ½eÿ»dÿ¹cÿ¸aÿ¶`ÿ ´_ÿ ²^ÿ °]ÿ ®[ÿ ­Zÿ «Yÿ ©Xÿ §WÿM¾„ÿsËžÿ>«sÿs:ÿs:ôs:ÿÿÿÿÿÿ u? s>ø r=ÿ=mÿ]°†ÿ7›hÿ ƒFÿ Dÿ |Bÿ y@ÿ w?ÿ s=ÿp;ÿm:ÿj8ÿg6ÿd5ÿg5ÿs:ÿ {AÿlÚ¢ÿyà«ÿ+Ì{ÿÄjÿÃiÿÁgÿ¿fÿ½eÿ»dÿ¹cÿ¸aÿ¶`ÿ ´_ÿ ²^ÿ °]ÿ ®[ÿ ­Zÿ «Yÿ ©Xÿ §Wÿ¨[ÿjȘÿqÉœÿ‰Pÿs:ÿs:”ÿÿÿÿÿÿ w@š u?ÿ ‡Sÿ]´ˆÿR¬ÿ‰Kÿ …Gÿ ‚Eÿ Dÿ |Bÿ y@ÿ v?ÿ s=ÿp;ÿm:ÿj8ÿg6ÿo8ÿs:ÿG¶~ÿyá¬ÿOÖ‘ÿÆkÿÄjÿÃiÿÁgÿ¿fÿ½eÿ»dÿ¹cÿ¸aÿ¶`ÿ ´_ÿ ²^ÿ °]ÿ ®[ÿ ­Zÿ «Yÿ ©Xÿ §Wÿ ¥Uÿ-²mÿsÊÿ]ºŠÿt;ÿs:òs: {B yAò w@ÿR­ÿ^¶‰ÿ —[ÿ ‹Jÿ ˆIÿ …Gÿ ‚Eÿ Dÿ |Bÿ y@ÿ v>ÿ s=ÿp;ÿm9ÿj8ÿs:ÿu<ÿtÙ¦ÿqß§ÿÉoÿÆkÿÄjÿÃiÿÁgÿ¿fÿ½eÿ»dÿ¹cÿ¸aÿ¶`ÿ ´_ÿ ²^ÿ °]ÿ ®[ÿ ­Zÿ «Yÿ ©Xÿ §Wÿ ¥Uÿ¤TÿVÀŠÿrÉÿ„Oÿs:ÿs:F |CC zBÿ„Pÿ^¹‹ÿF¬xÿ Nÿ Lÿ ŠJÿ ‡Hÿ „Gÿ Eÿ ~Cÿ {Bÿ x@ÿ u>ÿ r<ÿo;ÿn9ÿs:ÿ#Xÿyâ­ÿUÙ–ÿÈlÿÆkÿÄjÿÃiÿÁgÿ¿fÿ½eÿ»dÿ¹cÿ¸aÿ¶`ÿ ´_ÿ ²^ÿ °]ÿ ®[ÿ ­Zÿ «Yÿ ©Xÿ §Wÿ ¥Uÿ¤Tÿ3²qÿrÉÿ=Ÿmÿs:ÿs:’ ~D |Cÿ4›gÿ^»Œÿ-¤hÿ “Oÿ Mÿ Lÿ ŠJÿ ‡Hÿ „Fÿ Eÿ ~Cÿ {Aÿ x@ÿ u>ÿ r<ÿq:ÿs:ÿB°xÿyâ­ÿ7ÒƒÿÈlÿÆkÿÄjÿÃiÿÁgÿ¿fÿ½eÿ»dÿ¹cÿ¸aÿ¶`ÿ ´_ÿ ²^ÿ °]ÿ ®[ÿ ­Zÿ «Yÿ ©Xÿ §Wÿ ¥Uÿ¤Tÿ¦[ÿrÉÿYµ†ÿs:ÿs: €E· ~DÿG«yÿ^½ÿYÿ –Qÿ “Oÿ Mÿ Kÿ ŠJÿ ‡Hÿ „Fÿ Eÿ ~Cÿ {Aÿ x@ÿ u>ÿs;ÿs:ÿN¼„ÿyâ­ÿ&ÎxÿÈmÿÈpÿ!Èsÿ ÇrÿÄnÿÁkÿ¾fÿ»dÿ¹cÿ¸aÿ¶`ÿ ´_ÿ ²^ÿ °]ÿ ®[ÿ ­Zÿ «Yÿ ©Xÿ §Wÿ ¥Uÿ¤Tÿ¢SÿkƘÿe½ÿs:ÿs:Ú FÐ €EÿP´ÿ]¾ŒÿWÿ›WÿšXÿ˜Yÿ•Vÿ‘SÿPÿˆKÿ ƒFÿ €Dÿ }Cÿ zAÿ w?ÿs;ÿs:ÿZÅÿyâ­ÿMבÿMÖÿMÔÿMÓÿLÒŽÿKÐŒÿJÏ‹ÿF̈ÿ7Æ~ÿ(Àtÿ¹dÿ¶`ÿ ´_ÿ ²^ÿ °]ÿ ®[ÿ ­Zÿ «Yÿ ©Xÿ §Wÿ ¥Uÿ¤Tÿ¢SÿaÑÿoĘÿs:ÿs:ò ƒGç FÿY»ŠÿZ¿‹ÿ1¬mÿ0©lÿ0§jÿ/¤iÿ.¡fÿ.žeÿ,›cÿ+˜aÿ'”]ÿŒTÿ‚Hÿ }Bÿ zAÿt;ÿs:ÿ\È’ÿzâ­ÿSØ•ÿRדÿPÕ’ÿPÔ‘ÿPÓÿOÑŽÿMÏÿLÎŒÿJÌŠÿIʉÿFȆÿ/Àvÿ¶cÿ ²^ÿ °]ÿ ®[ÿ ­Zÿ «Yÿ ©Xÿ §Wÿ ¥Uÿ¤Tÿ¢Sÿ_ÂÿqÅšÿt;ÿs:ù …Hä ƒGÿX¼ŠÿYÀ‹ÿ:²uÿ9¯tÿ9­rÿ9ªqÿ7§nÿ7¥mÿ5¢jÿ4Ÿiÿ3œfÿ1™dÿ/•bÿ$XÿGÿv=ÿs:ÿR¿ˆÿyâ­ÿRØ”ÿUוÿTÖ”ÿTÔ“ÿRÓ’ÿRÒ‘ÿQÐÿPÏŽÿNÍÿLË‹ÿJʉÿJȇÿDŃÿ%ºnÿ °^ÿ ®[ÿ ­Zÿ «Yÿ ©Xÿ §Wÿ ¥Uÿ¤Tÿ¢SÿhÅ–ÿiÀ”ÿs:ÿs:â †IÌ …HÿP¸„ÿ_Ãÿ<´wÿC´{ÿB²zÿA¯wÿ@­uÿ?ªtÿ>§rÿ=¥pÿ<¢nÿ;Ÿlÿ9œjÿ7™gÿ4•dÿ}Eÿs:ÿGµ}ÿyâ­ÿQØ”ÿXØ—ÿX×—ÿXÕ–ÿVÔ•ÿUÓ“ÿSÑ‘ÿSÏÿQÎÿPÌÿMË‹ÿLɉÿJLJÿHÅ…ÿ9¿{ÿ±bÿ ­Zÿ «Yÿ ©Xÿ §Wÿ ¥Uÿ¤Tÿ £VÿqÉœÿ^¸‹ÿs:ÿs:Ê ˆJ´ †IÿF³|ÿ`Æ’ÿA¸|ÿKº‚ÿK¸€ÿJµÿI³}ÿI°{ÿG®zÿG«xÿD¨uÿC¥sÿB£rÿ@Ÿoÿ>œlÿ%ŠVÿs:ÿ/œeÿyâ­ÿ_Ûœÿ\Ùšÿ[Ø™ÿ[Ö˜ÿZÕ—ÿYÔ•ÿWÒ”ÿVÐ’ÿUÏ‘ÿSÍÿQÌÿOÊ‹ÿMȉÿKƇÿHÄ…ÿDÁ‚ÿ*·oÿ¬\ÿ ©Xÿ §Wÿ ¥Uÿ¤Tÿ(®iÿrÉÿG¨wÿs:ÿs:§ ŠKƒ ˆJÿ2£jÿ`È“ÿO¿†ÿT¿‰ÿT½ˆÿS»†ÿR¹„ÿQ¶ƒÿP³ÿO±ÿN®}ÿL¬{ÿK¨yÿH¥vÿF£tÿ=škÿs:ÿ xAÿxÞªÿqà§ÿ`Úœÿ_Ù›ÿ_ךÿ^Ö™ÿ]Õ—ÿ[Ó–ÿZÑ•ÿWГÿVΑÿTÍÿRËÿOÉ‹ÿNljÿKćÿH„ÿFÁ‚ÿ@½}ÿ$²jÿ ¨Xÿ ¥Uÿ¤TÿHº€ÿrÉÿ'Xÿs:ÿs:^ ŒL9 ŠKÿ‘Tÿ`É”ÿ\Æÿ]Äÿ]ÂŽÿ[Àÿ[½Œÿ[»ŠÿY¹ˆÿX·‡ÿV´„ÿU±‚ÿS¯ÿR«~ÿO¨{ÿM¥yÿ{Eÿs:ÿZÅŽÿyá¬ÿeÛŸÿcÙÿbØœÿ`×›ÿ`Öšÿ^Ô˜ÿ]Ò—ÿ[Ñ•ÿYÏ“ÿWÍ‘ÿUËÿRÉÿPÇ‹ÿNʼnÿKÆÿHƒÿF¿ÿD½ÿ:¹yÿ­dÿªaÿpÉ›ÿkÖÿ v>ÿs:ýs: M ‹Lì ‰JÿN¼…ÿ`É”ÿ_Ç“ÿdÇ•ÿdÅ”ÿdÃ’ÿdÁ’ÿb¿ÿa¼Žÿ`ºŒÿ^·Šÿ\´‡ÿZ²…ÿX¯ƒÿU«ÿ.]ÿs:ÿ‰Pÿvàªÿvß©ÿfÚŸÿfÙŸÿdØÿcÖ›ÿbÕšÿaÓ™ÿ^Ñ—ÿ\ЕÿZΓÿXÌ‘ÿUÊÿSÈÿQÆ‹ÿMćÿKÃ…ÿGÀƒÿF¾ÿB»~ÿA¹|ÿiȘÿsÊÿ-™bÿs:ÿs:ºÿÿÿÿÿÿ M† ‹Kÿ˜Yÿ_Ë”ÿaÈ”ÿlÊšÿnÊ›ÿmÈšÿlŘÿk×ÿjÁ•ÿi¿“ÿf¼‘ÿd¹Žÿb·Œÿa´Šÿ_±ˆÿW«€ÿ x@ÿs:ÿ>»|ÿyà«ÿnܤÿhÚ ÿhÙ ÿf×ÿdÖœÿcÔ›ÿaÒ™ÿ_Зÿ\Ï”ÿ[Í“ÿXË‘ÿUÉŽÿSÇŒÿPʼnÿMÇÿJÁ…ÿG¿‚ÿD¼ÿYÂÿsËžÿUº†ÿt:ÿs:ÿs:8ÿÿÿÿÿÿ N Lð ‹Kÿ9´vÿ`Ê•ÿdÊ–ÿv΢ÿvÍ¡ÿuËŸÿtÈÿsÆœÿqÄšÿo˜ÿn¿–ÿk¼“ÿiº‘ÿf¶ÿd´‹ÿ8•fÿs:ÿz@ÿdÔ›ÿxß«ÿjÚ¢ÿkÚ¡ÿjØ ÿgÖžÿfÕÿdÓ›ÿbÑ™ÿ_Жÿ]Δÿ[Ì’ÿXÊÿTÈÿRÆ‹ÿOĈÿL†ÿI¿ƒÿL¿…ÿrËžÿnÉšÿ„Iÿs:ÿs:¯ÿÿÿÿÿÿÿÿÿÿÿÿ M{ LÿŽMÿSÄ‹ÿ`Ê”ÿlÌ›ÿѧÿ~Ϧÿ|Í¥ÿ{Ë£ÿyÉ ÿxÇŸÿvÄÿsšÿq¿—ÿn¼•ÿk¹’ÿfµŒÿ‚Lÿs:ÿRÿvÜ©ÿyݪÿmÚ£ÿnÙ¢ÿk× ÿiÖžÿgÔÿeÒ›ÿbјÿ_Ï–ÿ]Í”ÿZÊ‘ÿWÈÿTÆŒÿQĉÿM‡ÿ^Ç’ÿrÍŸÿsÌžÿ3œfÿs:ÿs:ës:ÿÿÿÿÿÿÿÿÿÿÿÿN ŽMÁ ŒLÿ ™\ÿ^É“ÿ`Ê”ÿdÊ—ÿÒ©ÿ†Ò¬ÿ„Щÿ‚ͧÿË¥ÿ~É£ÿ|Æ ÿxÞÿvÁ›ÿr½—ÿoº”ÿc²ŠÿzCÿs:ÿ!‘XÿlÕ ÿxݪÿtÛ¦ÿmØ¡ÿlÖ ÿiÕžÿgÓœÿdÑ™ÿbÏ—ÿ_Í•ÿ\Ë’ÿYÉÿVÇÿWÇÿpΞÿuΠÿoËœÿ2›eÿs:ÿs:üs:BÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿN ŽMã ŒLÿ—YÿQŠÿ`Ê”ÿaÈ”ÿyÏ£ÿÔ°ÿŠÒ®ÿˆÐ«ÿ…Í©ÿƒË§ÿ€È¤ÿ}Æ¡ÿzÞÿv¿šÿi·ÿR§|ÿ x@ÿs:ÿ DÿOÇŠÿxÜ©ÿxÚ¨ÿjÖŸÿ[Ñ•ÿWÎ’ÿcÑ™ÿdЙÿaΖÿ[Ë’ÿ\Ê“ÿ^Ê”ÿpÏŸÿtÏ¡ÿ\ÃŽÿˆMÿs:ÿs:üs:cÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿN- ŽMä ŒLÿŽNÿ9´vÿ_É”ÿ`È“ÿ^Å‘ÿ^ÑÿfÄ”ÿwÉ ÿ…ΩÿzÇ¡ÿoÁ—ÿj½’ÿ[µˆÿ]´ˆÿ[±…ÿ*Ž[ÿr:ÿs:ÿs:ÿ+žcÿmÒŸÿwÙ§ÿwاÿwצÿnÔ¡ÿkÒÿnÓŸÿsÓ¢ÿuÒ£ÿtÑ¢ÿrΟÿ<§pÿv=ÿs:ÿs:òs:HÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿN ŽMÎ ŒLÿ ŠKÿ ›]ÿP¼…ÿ`Ç“ÿ`Å’ÿ`Ãÿ_Áÿ_¿Žÿ^½ÿ^»Œÿ^¹‹ÿ^¶ŠÿJ¦wÿKÿ o;ÿm:ÿq:ès:þs:ÿv<ÿ'•\ÿK¶€ÿaÆ“ÿmÏÿvÔ¤ÿoÏžÿcÅ“ÿQ¸ƒÿ.™cÿ yAÿs:ÿs:ÿs:Ås:+ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿN M} ‹Lô ŠKÿ ˆIÿŽRÿ1£iÿ>¬uÿF°zÿK²~ÿF¬xÿ<£oÿ,•_ÿ}Gÿ t>ÿ r=ÿ p<î o;pm:s:4s:¼s:þs:ÿs:ÿs:ÿs:ÿt;ÿs:ÿs:ÿs:ÿs:ÿs:ÿs:×s:Wÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ M ‹L” ‰Jê ‡Iÿ …Hÿ „Gÿ ‚Fÿ €Eÿ ~Dÿ |Cÿ zAÿ x@ÿ v?ä t>‰ r=ÿÿÿÿÿÿÿÿÿÿÿÿs:s:ls:ºs:Ýs:ös:ÿs:ús:ãs:Çs:s:-ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ‹K ‰JA ‡I† …H¤ ƒGÀ FÓ E½ }D¢ |B‚ zA9 x@ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿs: s:ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüÿÿðàÿÿ€€ÿþ?üøðàÀÀ€€€€€€Àààðø?üÿÀÿÿ€øÿÿðÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿenvisage-4.4.0/envisage/ui/workbench/images/image_LICENSE.txt0000644000175000017500000000047412252661404024467 0ustar davidcdavidc00000000000000These are my best guesses at where each icon came from, this file should be updated whenever icons change. filename source -------------------------------------------------------------- about.png Python Software Foundation application.ico Gael Varoquaux envisage-4.4.0/envisage/ui/workbench/workbench.py0000644000175000017500000000335612252661404022573 0ustar davidcdavidc00000000000000""" The Envisage workbench. """ # Enthought library imports. import pyface.workbench.api as pyface from envisage.api import IApplication from pyface.api import YES from traits.api import Delegate, Instance # Local imports. from workbench_preferences import WorkbenchPreferences from workbench_window import WorkbenchWindow class Workbench(pyface.Workbench): """ The Envisage workbench. There is (usually) exactly *one* workbench per application. The workbench can create any number of workbench windows. """ #### 'pyface.Workbench' interface ######################################### # The factory that is used to create workbench windows. window_factory = WorkbenchWindow #### 'Workbench' interface ################################################ # The application that the workbench is part of. application = Instance(IApplication) # Should the user be prompted before exiting the workbench? prompt_on_exit = Delegate('_preferences') #### Private interface #################################################### # The workbench preferences. _preferences = Instance(WorkbenchPreferences, ()) ########################################################################### # Private interface. ########################################################################### def _exiting_changed(self, event): """ Called when the workbench is exiting. """ if self.prompt_on_exit: answer = self.active_window.confirm( "Exit %s?" % self.active_window.title, "Confirm Exit" ) if answer != YES: event.veto = True return #### EOF ###################################################################### envisage-4.4.0/envisage/ui/workbench/workbench_preferences.py0000644000175000017500000000130212252661404025141 0ustar davidcdavidc00000000000000""" The workbench preferences. """ # Enthought library imports. from apptools.preferences.api import PreferencesHelper from traits.api import Bool class WorkbenchPreferences(PreferencesHelper): """ Helper for the workbench preferences. """ #### 'PreferencesHelper' interface ######################################## # The path to the preferences node that contains the preferences. preferences_path = 'envisage.ui.workbench' #### Preferences ########################################################## # Should the user be prompted before exiting the workbench? prompt_on_exit = Bool(True) #### EOF ###################################################################### envisage-4.4.0/envisage/ui/workbench/default_action_set.py0000644000175000017500000000243012252661404024435 0ustar davidcdavidc00000000000000""" The default workbench action set. """ # Enthought library imports. from envisage.ui.action.api import Action, ActionSet, Menu # This module's package. PKG = '.'.join(__name__.split('.')[:-1]) class DefaultActionSet(ActionSet): """ The default workbench action set. """ menus = [ Menu( name='&File', path='MenuBar', groups=['OpenGroup', 'SaveGroup', 'ImportGroup', 'ExitGroup'] ), Menu( path='MenuBar', class_name='pyface.workbench.action.api:ViewMenuManager' ), Menu( name='&Tools', path='MenuBar', groups=['PreferencesGroup'] ), Menu( name='&Help', path='MenuBar', groups=['AboutGroup'] ) ] actions = [ Action( path='MenuBar/File', group='ExitGroup', class_name=PKG + '.action.api:ExitAction' ), Action( path='MenuBar/Tools', group='PreferencesGroup', class_name=PKG + '.action.api:EditPreferencesAction' ), Action( path='MenuBar/Help', group='AboutGroup', class_name=PKG + '.action.api:AboutAction' ), ] #### EOF ###################################################################### envisage-4.4.0/envisage/ui/workbench/workbench_action_manager_builder.py0000644000175000017500000001425112252661404027324 0ustar davidcdavidc00000000000000""" The action manager builder used to build the workbench menu/tool bars. """ # Standard library imports. import weakref # Enthought library imports. from envisage.ui.action.api import AbstractActionManagerBuilder from pyface.action.api import Action, Group, MenuManager from pyface.workbench.action.api import MenuBarManager from pyface.workbench.action.api import ToolBarManager from traits.api import Any, Instance class WorkbenchActionManagerBuilder(AbstractActionManagerBuilder): """ The action manager builder used to build the workbench menu/tool bars. """ #### 'WorkbenchActionManagerBuilder' interface ############################ # The workbench window that we build the menu and tool bars for. window = Instance('envisage.ui.workbench.api.WorkbenchWindow') #### Private interface #################################################### # All action implementations. _actions = Any ########################################################################### # Protected 'AbstractActionManagerBuilder' interface. ########################################################################### def _create_action(self, definition): """ Create an action implementation from an action definition. """ traits = {'window' : self.window} # Override any traits that can be set in the definition. if len(definition.name) > 0: traits['name'] = definition.name if len(definition.class_name) > 0: action = self._actions.get(definition.class_name) if action is None: klass = self._import_symbol(definition.class_name) action = klass(**traits) self._actions[definition.class_name] = action # fixme: Do we ever actually do this? It seems that in Envisage 3.x # we always specify an action class!?! else: action = Action(**traits) # fixme: We need to associate the action set with the action to # allow for dynamic enabling/disabling etc. This is a *very* hacky # way to do it! action._action_set_ = definition._action_set_ return action def _create_group(self, definition): """ Create a group implementation from a group definition. """ traits = {} # Override any traits that can be set in the definition. if len(definition.id) > 0: traits['id'] = definition.id if len(definition.class_name) > 0: klass = self._import_symbol(definition.class_name) else: klass = Group group = klass(**traits) # fixme: We need to associate the action set with the action to # allow for dynamic enabling/disabling etc. This is a *very* hacky # way to do it! group._action_set_ = definition._action_set_ return group def _create_menu_manager(self, definition): """ Create a menu manager implementation from a menu definition. """ # fixme: 'window' is not actually a trait on 'MenuManager'! We set # it here to allow the 'View' menu to be created. However, it seems # that menus and actions etc should *always* have a reference to # the window that they are in?!? traits = {'window' : self.window} # Override any traits that can be set in the definition. if len(definition.id) > 0: traits['id'] = definition.id if len(definition.name) > 0: traits['name'] = definition.name if len(definition.class_name) > 0: klass = self._import_symbol(definition.class_name) else: klass = MenuManager menu_manager = klass(**traits) # Add any groups to the menu. for group in definition.groups: group._action_set_ = definition._action_set_ menu_manager.insert(-1, self._create_group(group)) # fixme: We need to associate the action set with the action to # allow for dynamic enabling/disabling etc. This is a *very* hacky # way to do it! menu_manager._action_set_ = definition._action_set_ return menu_manager def _create_menu_bar_manager(self): """ Create a menu bar manager from the builder's action sets. """ return MenuBarManager(window=self.window) def _create_tool_bar_manager(self, definition): """ Create a tool bar manager implementation from a definition. """ traits = { 'window' : self.window, 'show_tool_names' : False } # Override any traits that can be set in the definition. if len(definition.id) > 0: traits['id'] = definition.id if len(definition.name) > 0: traits['name'] = definition.name if len(definition.class_name) > 0: klass = self._import_symbol(definition.class_name) else: klass = ToolBarManager # fixme: 'window' is not actually a trait on 'ToolBarManager'! We # set it here because it is set on the 'MenuManager'! However, it # seems that menus and actions etc should *always* have a reference # to the window that they are in?!? tool_bar_manager = klass(**traits) # Add any groups to the tool bar. for group in definition.groups: group._action_set_ = definition._action_set_ tool_bar_manager.insert(-1, self._create_group(group)) # fixme: We need to associate the action set with the action to # allow for dynamic enabling/disabling etc. This is a *very* hacky # way to do it! tool_bar_manager._action_set_ = definition._action_set_ return tool_bar_manager ########################################################################### # Private interface. ########################################################################### def __actions_default(self): """ Trait initializer. """ return weakref.WeakValueDictionary() def _import_symbol(self, symbol_path): """ Import a symbol. """ return self.window.application.import_symbol(symbol_path) #### EOF ###################################################################### envisage-4.4.0/envisage/ui/workbench/preferences.ini0000644000175000017500000000007012252661404023227 0ustar davidcdavidc00000000000000[enthought.envisage.ui.workbench] prompt_on_exit = True envisage-4.4.0/envisage/ui/workbench/__init__.py0000644000175000017500000000000012252661404022327 0ustar davidcdavidc00000000000000envisage-4.4.0/envisage/ui/workbench/workbench_plugin.py0000644000175000017500000001756012252661404024153 0ustar davidcdavidc00000000000000""" The Envisage workbench plugin. """ # Enthought library imports. from envisage.api import ExtensionPoint, Plugin, ServiceOffer from traits.api import Callable, List # This module's package. PKG = '.'.join(__name__.split('.')[:-1]) 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) ### EOF ###################################################################### envisage-4.4.0/envisage/ui/workbench/workbench_application.py0000644000175000017500000001404412252661404025152 0ustar davidcdavidc00000000000000""" The entry point for an Envisage Workbench application. """ # Standard library imports. import logging # Enthought library imports. # # fixme: The ordering of these imports is critical. We don't use traits UI in # this module, but it must be imported *before* any 'HasTraits' class whose # instances might want to have 'edit_traits' called on them. # # fixme: Just importing the package is enought (see above). import traitsui # Enthought library imports. from envisage.api import Application from pyface.api import AboutDialog, Dialog, GUI, ImageResource from pyface.api import SplashScreen from pyface.workbench.api import IWorkbench from traits.api import Callable, Instance, Str, Tuple # Local imports. from workbench import Workbench # Logging. logger = logging.getLogger(__name__) class WorkbenchApplication(Application): """ The entry point for an Envisage Workbench application. i.e. a GUI application whose user interface is provided by the workbench plugin. This class handles the common case for Workbench applications, and it is intended to be subclassed to change start/stop behaviour etc. In fact, I generally create a subclass for every Workbench application I write since it is a good place to put branding information etc. """ #### 'WorkbenchApplication' interface ##################################### # The PyFace GUI for the application (this is here to make it easy for # parts of the application to get a reference to the GUI so they can get # system metrics, etc. gui = Instance(GUI) # The workbench. workbench = Instance(IWorkbench) # The factory for creating the workbench (used *instead* of providing a # workbench explicitly). workbench_factory = Callable(Workbench) # Branding information. # # The 'About' dialog. about_dialog = Instance(Dialog) # The icon used on window title bars etc. icon = Instance(ImageResource, ImageResource('application.ico')) # The name of the application (also used on window title bars etc). name = Str('Workbench') # The splash screen (None, the default, if no splash screen is required). splash_screen = Instance(SplashScreen) # The default position of the main window. window_position = Tuple((200, 200)) # The default size of the main window. window_size = Tuple((800, 600)) ########################################################################### # 'IApplication' interface. ########################################################################### def run(self): """ Run the application. This does the following (so you don't have to ;^):- 1) Starts the application 2) Creates and opens a workbench window 3) Starts the GUI event loop 4) When the event loop terminates, stops the application """ logger.debug('---------- workbench application ----------') # Make sure the GUI has been created (so that, if required, the splash # screen is shown). gui = self.gui # Start the application. if self.start(): # Create and open the first workbench window. window = self.workbench.create_window( position=self.window_position, size=self.window_size ) window.open() # We stop the application when the workbench has exited. self.workbench.on_trait_change(self._on_workbench_exited, 'exited') # Start the GUI event loop. # # THIS CALL DOES NOT RETURN UNTIL THE GUI IS CLOSED. gui.start_event_loop() return ########################################################################### # 'WorkbenchApplication' interface. ########################################################################### #### Initializers ######################################################### def _about_dialog_default(self): """ Trait initializer. """ return AboutDialog(image=ImageResource('about')) def _gui_default(self): """ Trait initializer. """ return GUI(splash_screen=self.splash_screen) def _workbench_default(self): """ Trait initializer. """ return self.create_workbench() #### Methods ############################################################## def about(self): """ Display the about dialog. """ # fixme: We really need to create a new 'about dialog' every time so # that it can have the active window as its parent. self.about_dialog.open() return # fixme: Is this needed on the public API? Why can't we just do this in # the default initializer (_workbench_default)? def create_workbench(self): """ Create the workbench. """ logger.debug('workbench factory %s', self.workbench_factory) return self.workbench_factory(application=self) def exit(self): """ Exit the application. This closes all open windows and hence exits the GUI event loop. """ self.workbench.exit() return ########################################################################### # Private interface. ########################################################################### def _on_workbench_exited(self): """ Dynamic trait change handler. """ # We don't invoke 'stop' directly because:- # # The workbench is often exited via a user action (either by closing # the last open window, or by choosing 'File/Exit'). If this happens # then the workbench 'exit' method is called from within an event # handler which would cause the 'stop' method to get called *before* # the handling of the window 'closed' event is complete. Hance, this # might mean that somebody listening for the window being closed would # get the event *after* the application had already stopped! self.gui.invoke_later(self.stop) return #### EOF ###################################################################### envisage-4.4.0/envisage/ui/workbench/workbench_preferences_page.py0000644000175000017500000000237112252661404026144 0ustar davidcdavidc00000000000000""" The main preferences page for the workbench. """ # Enthought library imports. from apptools.preferences.ui.api import PreferencesPage from traits.api import Bool from traitsui.api import View class WorkbenchPreferencesPage(PreferencesPage): """ The main preferences page for the workbench. """ #### 'PreferencesPage' interface ########################################## # The page's category (e.g. 'General/Appearance'). The empty string means # that this is a top-level page. category = '' # The page's help identifier (optional). If a help Id *is* provided then # there will be a 'Help' button shown on the preference page. help_id = '' # The page name (this is what is shown in the preferences dialog. name = 'General' # The path to the preferences node that contains the preferences. preferences_path = 'envisage.ui.workbench' #### Preferences ########################################################## # Should the user be prompted before exiting the workbench? prompt_on_exit = Bool(True) #### Traits UI views ###################################################### trait_view = View('prompt_on_exit') #### EOF ###################################################################### envisage-4.4.0/envisage/ui/workbench/workbench_window.py0000644000175000017500000002076412252661404024164 0ustar davidcdavidc00000000000000""" An extensible workbench window. """ # Standard library imports. import logging # Enthought library imports. import pyface.workbench.api as pyface from envisage.api import IExtensionPointUser, IExtensionRegistry from envisage.api import IServiceRegistry from envisage.api import ExtensionPoint, ServiceRegistry from envisage.ui.action.api import ActionSet from pyface.action.api import StatusBarManager from traits.api import Delegate, Instance, List, Property, implements # Local imports. from workbench_action_manager_builder import WorkbenchActionManagerBuilder from workbench_editor_manager import WorkbenchEditorManager # Logging. logger = logging.getLogger(__name__) class WorkbenchWindow(pyface.WorkbenchWindow): """ An extensible workbench window. """ implements(IServiceRegistry, IExtensionPointUser) # 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 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 #### EOF ###################################################################### envisage-4.4.0/envisage/ui/workbench/workbench_action_set.py0000644000175000017500000001553612252661404025006 0ustar davidcdavidc00000000000000""" An action set in a workbench window. """ # Enthought library imports. from envisage.ui.action.api import ActionSet from traits.api import Instance, List, Str class WorkbenchActionSet(ActionSet): """ An action set in a workbench window. This class adds a 'window' trait which is the workbench window that the action set is in. The trait is set by the framework when the action set is added to the window. It also adds a simple way for the action set to be enabled and/or visible in specific perspectives. """ ########################################################################### # 'WorkbenchActionSet' interface. ########################################################################### # It is common for an action set to be enabled and/or visible only in a # particular perspective (or group of perspectives). The following traits # allow you to say which by specifiying a list of the appropriate # perspective *Ids*. # # For finer control over the enablement/visibility simply override the # 'initialize' method. enabled_for_perspectives = List(Str) visible_for_perspectives = List(Str) # It is common for an action set to be enabled and/or visible only when # particular view (or group of views) is visible. The following traits # allow you to say which by specifiying a list of the appropriate view # *Ids*. # # For finer control over the enablement/visibility simply override the # 'initialize' method. enabled_for_views = List(Str) visible_for_views = List(Str) # The workbench window that the action set is in. # # The framework sets this trait when the action set is first added to a # window. window = Instance('envisage.ui.workbench.api.WorkbenchWindow') ########################################################################### # 'ActionSet' interface. ########################################################################### def _enabled_changed(self, trait_name, old, new): """ Static trait change handler. """ if self.window is not None: self._update_tool_bars(self.window, 'enabled', new) self._update_actions(self.window, 'enabled', new) return def _visible_changed(self, trait_name, old, new): """ Static trait change handler. """ if self.window is not None: self._update_tool_bars(self.window, 'visible', new) self._update_actions(self.window, 'visible', new) return ########################################################################### # 'WorkbenchActionSet' interface. ########################################################################### def initialize(self): """ Called when the action set has been added to a window. Use this method to hook up any listeners that you need to control the enabled and/or visible state of the action set. By default, we listen to the window being opened and the active perspective and active view being changed. """ # We use dynamic trait handlers here instead of static handlers (or # @on_trait_change) because sub-classes might have a completely # different way to determine the anabled and/or visible state, hence # we might want to hook up completely different events. self.window.on_trait_change(self._refresh, 'opened') self.window.on_trait_change(self._refresh, 'active_part') self.window.on_trait_change(self._refresh, 'active_perspective') return ########################################################################### # Private interface. ########################################################################### #### Trait change handlers ################################################ def _window_changed(self): """ Static trait change handler. """ # fixme: We put the code into an 'initialize' method because it seems # easier to explain that we expect it to be overridden. It seems a bit # smelly to say that a trait change handfler needs to be overridden. self.initialize() return #### Methods ############################################################## def _refresh(self): """ Refresh the enabled/visible state of the action set. """ window = self.window if len(self.enabled_for_perspectives) > 0: self.enabled = window is not None \ and window.active_perspective is not None \ and window.active_perspective.id in \ self.enabled_for_perspectives if len(self.visible_for_perspectives) > 0: self.visible = window is not None \ and window.active_perspective is not None \ and window.active_perspective.id in \ self.visible_for_perspectives if len(self.enabled_for_views) > 0: self.enabled = window is not None \ and window.active_part is not None \ and window.active_part.id in \ self.enabled_for_views if len(self.visible_for_views) > 0: self.visible = window is not None \ and window.active_part is not None \ and window.active_part.id in \ self.visible_for_views return def _update_actions(self, window, trait_name, value): """ Update the state of the tool bars in the action set. """ def visitor(item): """ Called when we visit each item in an action manager. """ # fixme: The 'additions' group gets created by default and hence # has no '_action_set_' attribute. This smells because of the fact # that we 'tag' the '_action_set_' attribute onto all items to be # ble to find them later. This link should be maintained externally # (maybe in the action set itself?). if hasattr(item, '_action_set_'): if item._action_set_ is self: setattr(item, trait_name, value) return # Update actions on the menu bar. window.menu_bar_manager.walk(visitor) # Update actions on the tool bars. for tool_bar_manager in window.tool_bar_managers: tool_bar_manager.walk(visitor) return def _update_tool_bars(self, window, trait_name, value): """ Update the state of the tool bars in the action set. """ for tool_bar_manager in window.tool_bar_managers: if tool_bar_manager._action_set_ is self: setattr(tool_bar_manager, trait_name, value) return #### EOF ###################################################################### envisage-4.4.0/envisage/ui/workbench/action/0000755000175000017500000000000012252662115021505 5ustar davidcdavidc00000000000000envisage-4.4.0/envisage/ui/workbench/action/exit_action.py0000644000175000017500000000205112252661404024363 0ustar davidcdavidc00000000000000""" An action that exits the workbench. """ # Enthought library imports. from pyface.api import ImageResource from pyface.action.api import Action class ExitAction(Action): """ An action that exits the workbench. """ #### 'Action' interface ################################################### # A longer description of the action. description = 'Exit the application' # The action's image (displayed on tool bar tools etc). image = ImageResource('exit') # The action's name (displayed on menus/tool bar tools etc). name = 'Exit' # A short description of the action used for tooltip text etc. tooltip = 'Exit the application' ########################################################################### # 'Action' interface. ########################################################################### def perform(self, event): """ Perform the action. """ self.window.application.exit() return #### EOF ###################################################################### envisage-4.4.0/envisage/ui/workbench/action/api.py0000644000175000017500000000020212252661404022622 0ustar davidcdavidc00000000000000from about_action import AboutAction from edit_preferences_action import EditPreferencesAction from exit_action import ExitAction envisage-4.4.0/envisage/ui/workbench/action/images/0000755000175000017500000000000012252662115022752 5ustar davidcdavidc00000000000000envisage-4.4.0/envisage/ui/workbench/action/images/preferences.png0000644000175000017500000000150612252661404025763 0ustar davidcdavidc00000000000000‰PNG  IHDRóÿa IDAT8uSMle}ëMœ/^ge“¸l)RDOR„/‰š¨(M`‘HЖƒ›‰8P…Üzì¡(B‚J‰ƒˆÛF­‰*¤@­8 Eijü“¸ÿ5v½»òî&±×ß'Gá‘FšÃ{£yóf@Dø¿;FõÅÅE- n‡ëÀb~~Þ¿¾¾Þˆ¼÷~Xt»;‘N§/ÍÌÌü!˲0==½û"ž#"@,»,x½_jªºëéñ'GF"BW—^¯?¯¬5ð^=r¤/™J}:59ù5€ˆ I’+výFNɨªªTUU*W*¤ìä¨ôìí–ËT3 2-‹ê Z^^ζ$¸ Z­:7®Ç¾'Ǧë¨Õ (O(Š‚R©Ó4ÁƒmÛ`¬‰………›- ®VñΙ3#Žã Q·ñw" €^ŸÏñûN?666P¯7ÀûgϾ}¸„©©©s³³³•d*ÕÌlmÓƒkT(黥%EÅp øxõîÝÊsU¥µµ?©P,Q:“iÎÍÍU†‡‡O»’ÉÔ‰/\ô;ÞÚÛCö±¿€Û·nÏëºþc¹\^ŠÇã?hª††ÝD³i££³“?ᢟç;N¸LÓØ·ëp˜rDŸˆS§FN¶¦zËãñ@ÓTÔ 8F ¦iìóÅb1©©ê›>Q|ùãÇÆ$©º®<|øèóD"q¥«»ûõcÇ^cMˆ¢÷ïß+|þ¾ººzõÅËûÊ´ö(_(Ò/¿þFåJ…=ޤͿž¢ìÐOwîÐæ“e•ýâ_6Àààà|žž—pô¨Œìv¦eÁ0 K%¼Òן(‚çy„B¡Z¼ÃS®Æv·ÛÝ¿gY$IAપŽã ˆˆcÔév»TUÍÚØ€8>>ƒ—dYþ,•Nd•Ê>U(³µeƒÁIY–? ‡Ãqâ!¯Ý—}síZ2ŸÏS.—£‰‰‰{ípmp'E"‘•H$²Âq\o;Ü?âì ³/q¹ IEND®B`‚envisage-4.4.0/envisage/ui/workbench/action/images/image_LICENSE.txt0000644000175000017500000000067212252661404025744 0ustar davidcdavidc00000000000000The icons are mostly derived work from other icons. As such they are licensed accordingly to the original license: CP (Crystal Project Icons): LGPL license as described in image_LICENSE_CP.txt Unless stated in this file, icons are work of enthought, and are released under BSD-like license. Files and orginal authors: -------------------------------------------------------------------- exit.png CP preferences.png CP envisage-4.4.0/envisage/ui/workbench/action/images/exit.png0000644000175000017500000000155412252661404024436 0ustar davidcdavidc00000000000000‰PNG  IHDRóÿa3IDAT8m“_L[ƽ÷¶½í`…RÖBKmˈsƒ,Ù721ËLæpa›JFæ4ÑøïA“ùb‰qÉÌ^M6$²È¦Ƙm:æÀ-˜0aðÈŸB7þHKm¹m)¥ÇÂâˆßË9ßùN¾“ó™D„ÍxJkyºÖgy!àÙáycªf¥w*×=²™kú¯@™z¼òéO_}±êˆgïóvü~À#Ì÷ ¤º®üðÅë'³¹oÇ7f”f‡¥y_÷³c·?Ø¥žôl+¶c-€"7•‚fÇí.¶¿¿[?ñ]õ_¿í0Ýÿ„€G=üÜ3p­B{“ ÿÀä$ÌÍB¹<^x‰IRs1¼,•·»û¯•©'‚N9ÆÎùe¡,²'šH M Û ‘À@ˆµš]ÌEf˜þ;ŽŸEOkÁÈgŠ_=¾½Î<Ù¼˜ÌˆÅÐßzSs +ñ%Ȥ!“fUµí Zëi’‹1bÆ*û´‰—ýJKzSë-‡"Ý òëž™½ü• ÜbüÞ#ÆÛò T.óß_•µ|^~©­–« r£i°6žÒœêråÃlžLv¨ òõ%2†ÁÊ›§A ¹´Dêâ¶=†³þ £ýCdsàT–CÚ*"á5°¨šÉ„ÙbE Á¢ëh¦õ«kšFx´)‹I”¨Å9PÀ£€ÑwoÛ誂MQ°© ºªàm;@â^/…¦õ…1sÑ„6fóõnÁ‘.´¦lÙ¡A¢ÝW(}½ ›«”Ü­› 54Qxè%º:ÉàÕÁŠ53e¯èADxÏ_×1àÛ*„Êe´öI_éÉåä1r9IÓ!CÕÛ¥Çç’»NUÞöìé4€Ž‚Ýgëóñƒ5–å Ūc¾ùÓºáêçÖëð æŸ¯c×uT›Æ ŠtÙ÷Ÿ½¸ñH±áó‘wõ†WF-î°ÏaC ÁYÅN(q«5Äï°1n- ä8Ô>?ýD&î_è;fn¬;—­¼4½¼š`5ä€5Èf˜IfíùªË¯m=|`âÏ/ûþ7(9ܬq*{.{„p4¾Ëßþøñäfî¿3?Z`p]IEND®B`‚envisage-4.4.0/envisage/ui/workbench/action/edit_preferences_action.py0000644000175000017500000000300712252661404026722 0ustar davidcdavidc00000000000000""" An action that displays the preferences dialog. """ # Enthought library imports. from pyface.api import ImageResource from pyface.action.api import Action class EditPreferencesAction(Action): """ An action that displays the preferences dialog. """ #### 'Action' interface ################################################### # A longer description of the action. description = 'Manage Preferences' # The action's image (displayed on tool bar tools etc). image = ImageResource('preferences') # The action's name (displayed on menus/tool bar tools etc). name = 'Preferences' # A short description of the action used for tooltip text etc. tooltip = 'Manage Preferences' ########################################################################### # 'Action' interface. ########################################################################### def perform(self, event): """ Performs the action. """ from apptools.preferences.ui.api import PreferencesManager # Lookup the preferences manager service. manager = event.window.application.get_service(PreferencesManager) ui = manager.edit_traits(parent=event.window.control, kind='modal') # If the user hit the "Ok" button, then save the preferences in case # application crashes before it exits! if ui.result: self.window.application.preferences.save() return #### EOF ###################################################################### envisage-4.4.0/envisage/ui/workbench/action/__init__.py0000644000175000017500000000000012252661404023604 0ustar davidcdavidc00000000000000envisage-4.4.0/envisage/ui/workbench/action/about_action.py0000644000175000017500000000173412252661404024533 0ustar davidcdavidc00000000000000""" An action that shows the 'About' dialog. """ # Enthought library imports. from pyface.action.api import Action class AboutAction(Action): """ An action that shows the 'About' dialog. """ #### 'Action' interface ################################################### # A longer description of the action. description = 'Display information about the application' # The action's name (displayed on menus/tool bar tools etc). name = 'About' # A short description of the action used for tooltip text etc. tooltip = 'Display information about the application' ########################################################################### # 'Action' interface. ########################################################################### def perform(self, event): """ Perform the action. """ self.window.application.about() return #### EOF ###################################################################### envisage-4.4.0/envisage/ui/tasks/0000755000175000017500000000000012252662115017373 5ustar davidcdavidc00000000000000envisage-4.4.0/envisage/ui/tasks/task_extension.py0000644000175000017500000000115312252661404023003 0ustar davidcdavidc00000000000000# Enthought library imports. from pyface.tasks.action.api import SchemaAddition from traits.api import HasStrictTraits, Callable, List, Str class TaskExtension(HasStrictTraits): """ A bundle of items for extending a Task. """ # The ID of the task to extend. If the ID is omitted, the extension applies # to all tasks. task_id = Str # A list of menu bar and tool bar items to add to the set provided # by the task. actions = List(SchemaAddition) # A list of dock pane factories that will extend the dock panes provided by # the task. dock_pane_factories = List(Callable) envisage-4.4.0/envisage/ui/tasks/api.py0000644000175000017500000000040212252661404020512 0ustar davidcdavidc00000000000000from preferences_category import PreferencesCategory from preferences_pane import PreferencesPane from task_extension import TaskExtension from task_factory import TaskFactory from task_window import TaskWindow from tasks_application import TasksApplication envisage-4.4.0/envisage/ui/tasks/task_window.py0000644000175000017500000000335412252661404022303 0ustar davidcdavidc00000000000000# Enthought library imports. from pyface.image_resource import ImageResource from pyface.tasks.api import TaskWindow as PyfaceTaskWindow from traits.api import Instance, Property class TaskWindow(PyfaceTaskWindow): """ A TaskWindow for use with the Envisage Tasks plugin. """ # The application that created and is managing this window. application = Instance('envisage.ui.tasks.api.TasksApplication') # The window's icon. We override it so it can delegate to the application # icon if the window's icon is not set. icon = Property(Instance(ImageResource), depends_on='_icon') #### Protected interface ################################################## _icon = Instance(ImageResource, allow_none=True) ########################################################################### # Protected 'TaskWindow' interface. ########################################################################### def _get_title(self): """ If the application has a name, add it to the title. Otherwise, behave like the base class. """ if self._title or self.active_task is None: return self._title title = self.active_task.name if self.application.name: title = u'%s - %s' % (title, self.application.name) return title def _get_icon(self): """If we have an icon return it, else delegate to the application. """ if self._icon is not None: return self._icon elif self.application is not None: return self.application.icon else: return None def _set_icon(self, icon): """Explicitly set the icon to use. None is allowed. """ self._icon = icon envisage-4.4.0/envisage/ui/tasks/preferences_dialog.py0000644000175000017500000001132112252661404023563 0ustar davidcdavidc00000000000000# Enthought library imports. from traits.api import Bool, HasTraits, Instance, List, Unicode, \ on_trait_change from traitsui.api import Item, Handler, ListEditor, View from pyface.tasks.topological_sort import before_after_sort # Local imports. from preferences_category import PreferencesCategory from preferences_pane import PreferencesPane class PreferencesTab(HasTraits): """ An object used internally by PreferencesDialog. """ name = Unicode panes = List(PreferencesPane) view = View(Item('panes', editor = ListEditor(style = 'custom'), show_label = False, style = 'readonly'), resizable = True) class PreferencesDialog(Handler): """ A dialog for editing preferences. """ #### 'PreferencesDialog' interface ######################################## # The application that created and is managing this dialog. application = Instance('envisage.ui.tasks.api.TasksApplication') # The list of categories to use when building the dialog. categories = List(PreferencesCategory) # The list of panes to use when building the dialog. panes = List(PreferencesPane) # Should the Apply button be shown? show_apply = Bool(False) #### Private interface #################################################### _tabs = List(PreferencesTab) _selected = Instance(PreferencesTab) ########################################################################### # Public interface ########################################################################### def select_pane(self, pane_id): """ Find and activate the notebook tab that contains the given pane id. """ for tab in self._tabs: for pane in tab.panes: if pane.id == pane_id: self._selected = tab return ########################################################################### # 'HasTraits' interface. ########################################################################### def trait_context ( self ): """ Returns the default context to use for editing or configuring traits. """ return { 'object': self, 'handler': self } def traits_view(self): """ Build the dynamic dialog view. """ buttons = ['OK', 'Cancel'] if self.show_apply: buttons = ['Apply'] + buttons # Only show the tab bar if there is more than one category. tabs_style = 'custom' if len(self._tabs) > 1 else 'readonly' return View(Item('_tabs', editor = ListEditor(page_name = '.name', style ='custom', use_notebook = True, selected = '_selected'), show_label = False, style = tabs_style), buttons = buttons, kind = 'livemodal', resizable = True, title = 'Preferences') ########################################################################### # 'Handler' interface. ########################################################################### def apply(self, info=None): """ Handles the Apply button being clicked. """ for tab in self._tabs: for pane in tab.panes: pane.apply() def close(self, info, is_ok): """ Handles the user attempting to close a dialog-based user interface. """ if is_ok: self.apply() return super(PreferencesDialog, self).close(info, is_ok) ########################################################################### # Protected interface. ########################################################################### @on_trait_change('categories, panes') def _update_tabs(self): # Build a { category id -> [ PreferencePane ] } map. categories = self.categories[:] category_map = dict((category.id, []) for category in categories) for pane in self.panes: if pane.category in category_map: category_map[pane.category].append(pane) else: categories.append(PreferencesCategory(id=pane.category)) category_map[pane.category] = [ pane ] # Construct the appropriately sorted list of preference tabs. tabs = [] for category in before_after_sort(categories): panes = before_after_sort(category_map[category.id]) tabs.append(PreferencesTab(name = category.name, panes=panes)) self._tabs = tabs envisage-4.4.0/envisage/ui/tasks/preferences_category.py0000644000175000017500000000141012252661404024137 0ustar davidcdavidc00000000000000# Enthought library imports. from traits.api import HasTraits, Str, Unicode class PreferencesCategory(HasTraits): """ The description for a container of PreferencesPanes. """ # The globally unique identifier for the category. id = Str # The user-visible name of the category. name = Unicode # The category appears after the category with this ID. before = Str # The category appears after the category with this ID. after = Str ########################################################################### # Protected interface. ########################################################################### def _name_default(self): """ By default, use the ID for the name. """ return self.id envisage-4.4.0/envisage/ui/tasks/tasks_plugin.py0000644000175000017500000001152512252661404022454 0ustar davidcdavidc00000000000000# Enthought library imports. from envisage.api import ExtensionPoint, Plugin, ServiceOffer from traits.api import Callable, List # Local imports. from preferences_category import PreferencesCategory from task_factory import TaskFactory from task_extension import TaskExtension # Constants. PKG = '.'.join(__name__.split('.')[:-1]) 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 ############################## 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. """) 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'. """) tasks = ExtensionPoint( List(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. """) task_extensions = ExtensionPoint( List(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 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 envisage-4.4.0/envisage/ui/tasks/__init__.py0000644000175000017500000000000012252661404021472 0ustar davidcdavidc00000000000000envisage-4.4.0/envisage/ui/tasks/preferences_pane.py0000644000175000017500000000553412252661404023260 0ustar davidcdavidc00000000000000# Enthought library imports. from apptools.preferences.api import IPreferences, PreferencesHelper from traits.api import Callable, Dict, HasTraits, Instance, Str from traitsui.api import Controller class PreferencesPane(Controller): """ A panel for configuring application preferences. """ #### 'Controller' interface ############################################### # The preferences helper for which this pane is a view. model = Instance(PreferencesHelper) #### 'PreferencesPane' interface ########################################## # An identifier for the pane (unique within a category). id = Str # The ID of the category in which to place the pane. category = Str('General') # The pane appears after the pane with this ID. before = Str # The pane appears after the pane with this ID. after = Str # The preferences dialog to which the pane belongs. Set by the framework. dialog = Instance( 'envisage.ui.tasks.preferences_dialog.PreferencesDialog') # # The factory to use for creating the preferences model object, of form: # callable(**traits) -> PreferencesHelper # If not specified, the preferences helper must be supplied manually. model_factory = Callable #### Private interface #################################################### _model = Instance(PreferencesHelper) ########################################################################### # 'HasTraits' interface. ########################################################################### def trait_context ( self ): """ Re-implemented to use a copy of the model that is not connected to the preferences node. """ if self.model is None: if self.model_factory is not None: preferences = self.dialog.application.preferences self.model = self.model_factory(preferences = preferences) else: raise ValueError('A preferences pane must have a model!') self._model = self.model.clone_traits() self._model.preferences = None return { 'object': self._model, 'controller': self, 'handler': self } ########################################################################### # 'Handler' interface. ########################################################################### def apply(self, info=None): """ Handles the Apply button being clicked. """ trait_names = filter(self._model._is_preference_trait, self._model.trait_names()) self.model.copy_traits(self._model, trait_names) def close(self, info, is_ok): """ Handles the user attempting to close a dialog-based user interface. """ if is_ok: self.apply() return super(PreferencesPane, self).close(info, is_ok) envisage-4.4.0/envisage/ui/tasks/task_window_event.py0000644000175000017500000000065012252661404023500 0ustar davidcdavidc00000000000000# Enthought library imports. from traits.api import HasTraits, Instance, Vetoable # Local imports. from task_window import TaskWindow class TaskWindowEvent(HasTraits): """ A task window lifecycle event. """ # The window that the event occurred on. window = Instance(TaskWindow) class VetoableTaskWindowEvent(TaskWindowEvent, Vetoable): """ A vetoable task window lifecycle event. """ pass envisage-4.4.0/envisage/ui/tasks/task_factory.py0000644000175000017500000000213412252661404022436 0ustar davidcdavidc00000000000000# Enthought library imports. from traits.api import Callable, HasTraits, Str, Unicode class TaskFactory(HasTraits): """ A factory for creating a Task with some additional metadata. """ # The task factory's unique identifier. This ID is assigned to all tasks # created by the factory. id = Str # The task factory's user-visible name. name = Unicode # A callable with the following signature: # # callable(**traits) -> Task # # Often this attribute will simply be a Task subclass. factory = Callable def create(self, **traits): """ Creates the Task. The default implementation simply calls the 'factory' attribute. """ return self.factory(**traits) def create_with_extensions(self, extensions, **traits): """ Creates the Task using the specified TaskExtensions. """ task = self.create(**traits) for extension in extensions: task.extra_actions.extend(extension.actions) task.extra_dock_pane_factories.extend(extension.dock_pane_factories) return task envisage-4.4.0/envisage/ui/tasks/action/0000755000175000017500000000000012252662115020650 5ustar davidcdavidc00000000000000envisage-4.4.0/envisage/ui/tasks/action/exit_action.py0000644000175000017500000000144312252661404023532 0ustar davidcdavidc00000000000000# Enthought library imports. from pyface.api import ImageResource from pyface.action.api import Action class ExitAction(Action): """ An action that exits the application. """ #### 'Action' interface ################################################### # A longer description of the action. description = 'Exit the application' # The action's name (displayed on menus/tool bar tools etc). name = 'E&xit' # A short description of the action used for tooltip text etc. tooltip = 'Exit the application' ########################################################################### # 'Action' interface. ########################################################################### def perform(self, event): event.task.window.application.exit() envisage-4.4.0/envisage/ui/tasks/action/preferences_action.py0000644000175000017500000000321112252661404025055 0ustar davidcdavidc00000000000000# Enthought library imports. from pyface.action.api import Action, Group class PreferencesAction(Action): """ An action that displays the preferences dialog. """ #### 'Action' interface ################################################### # A longer description of the action. description = 'Open the preferences dialog' # The action's name (displayed on menus/tool bar tools etc). name = 'Prefere&nces...' # A short description of the action used for tooltip text etc. tooltip = 'Open the preferences dialog' ########################################################################### # 'Action' interface. ########################################################################### def perform(self, event): from envisage.ui.tasks.preferences_dialog import \ PreferencesDialog window = event.task.window dialog = window.application.get_service(PreferencesDialog) ui = dialog.edit_traits(parent=window.control, kind='livemodal') if ui.result: window.application.preferences.save() class PreferencesGroup(Group): """ A group that contains the preferences action. """ #### 'Action' interface ################################################### # The group's identifier (unique within action manager). id = 'PreferencesGroup' ########################################################################### # 'object' interface. ########################################################################### def __init__(self, **traits): super(PreferencesGroup, self).__init__(PreferencesAction(), **traits) envisage-4.4.0/envisage/ui/tasks/action/api.py0000644000175000017500000000022512252661404021772 0ustar davidcdavidc00000000000000from task_window_launch_group import TaskWindowLaunchAction, \ TaskWindowLaunchGroup from task_window_toggle_group import TaskWindowToggleGroup envisage-4.4.0/envisage/ui/tasks/action/task_window_toggle_group.py0000644000175000017500000000733612252661404026341 0ustar davidcdavidc00000000000000# Enthought library imports. from pyface.action.api import Action, ActionItem, Group from traits.api import Any, Bool, Instance, List, Property, Unicode, \ on_trait_change class TaskWindowToggleAction(Action): """ An action for activating an application window. """ #### 'Action' interface ################################################### name = Property(Unicode, depends_on='window.active_task.name') style = 'toggle' #### 'TaskWindowToggleAction' interface ################################### # The window to use for this action. window = Instance('envisage.ui.tasks.task_window.TaskWindow') ########################################################################### # 'Action' interface. ########################################################################### def perform(self, event=None): if self.window: self.window.activate() ########################################################################### # Private interface. ########################################################################### def _get_name(self): if self.window.active_task: return self.window.active_task.name return unicode() @on_trait_change('window:activated') def _window_activated(self): self.checked = True @on_trait_change('window:deactivated') def _window_deactivated(self): self.checked = False class TaskWindowToggleGroup(Group): """ A Group for toggling the activation state of an application's windows. """ #### 'Group' interface #################################################### id = 'TaskWindowToggleGroup' items = List #### 'TaskWindowToggleGroup' interface #################################### # The application that contains the group. application = Instance('envisage.ui.tasks.tasks_application.' 'TasksApplication') # The ActionManager to which the group belongs. manager = Any ########################################################################### # 'Group' interface. ########################################################################### def destroy(self): """ Called when the group is no longer required. """ super(TaskWindowToggleGroup, self).destroy() if self.application: self.application.on_trait_change( self._rebuild, 'window_opened, window_closed', remove=True) ########################################################################### # Private interface. ########################################################################### def _get_items(self): items = [] for window in self.application.windows: active = window == self.application.active_window action = TaskWindowToggleAction(window=window, checked=active) items.append(ActionItem(action=action)) return items def _rebuild(self): # Clear out the old group, then build the new one. for item in self.items: item.destroy() self.items = self._get_items() # Inform our manager that it needs to be rebuilt. self.manager.changed = True #### Trait initializers ################################################### def _application_default(self): return self.manager.controller.task.window.application def _items_default(self): self.application.on_trait_change(self._rebuild, 'window_opened, window_closed') return self._get_items() def _manager_default(self): manager = self while isinstance(manager, Group): manager = manager.parent return manager envisage-4.4.0/envisage/ui/tasks/action/__init__.py0000644000175000017500000000000012252661404022747 0ustar davidcdavidc00000000000000envisage-4.4.0/envisage/ui/tasks/action/task_window_launch_group.py0000644000175000017500000000434612252661404026330 0ustar davidcdavidc00000000000000# Enthought library imports. from pyface.action.api import ActionItem, Group from pyface.tasks.api import TaskWindowLayout from pyface.tasks.action.api import TaskAction from traits.api import List, Str class TaskWindowLaunchAction(TaskAction): """ An Action that creates a task window with a single task. """ #### 'TaskWindowLaunchAction' interface ################################### task_id = Str ########################################################################### # 'Action' interface. ########################################################################### def perform(self, event): application = event.task.window.application window = application.create_window(TaskWindowLayout(self.task_id)) window.open() ########################################################################### # Private interface. ########################################################################### #### Trait change handlers ################################################ def _task_changed(self, task): """ Name the action (unless a name has already been assigned). """ if task and not self.name: name = unicode() for factory in task.window.application.task_factories: if factory.id == self.task_id: name = factory.name break self.name = name class TaskWindowLaunchGroup(Group): """ A Group for creating task windows with a single task. """ #### 'Group' interface #################################################### id = 'TaskWindowLaunchGroup' items = List ########################################################################### # Private interface. ########################################################################### def _items_default(self): manager = self while isinstance(manager, Group): manager = manager.parent application = manager.controller.task.window.application items = [] for factory in application.task_factories: action = TaskWindowLaunchAction(task_id=factory.id) items.append(ActionItem(action=action)) return items envisage-4.4.0/envisage/ui/tasks/tasks_application.py0000644000175000017500000004212412252661404023460 0ustar davidcdavidc00000000000000# Standard library imports. import cPickle import logging import os.path # Enthought library imports. from envisage.api import Application, ExtensionPoint from pyface.api import GUI, SplashScreen from pyface.image_resource import ImageResource from pyface.tasks.api import TaskLayout, TaskWindowLayout from traits.api import Bool, Callable, Directory, Event, HasStrictTraits, \ Instance, Int, List, Property, Str, Unicode, Vetoable from traits.etsconfig.api import ETSConfig # Local imports from task_window import TaskWindow from task_window_event import TaskWindowEvent, VetoableTaskWindowEvent # Logging. logger = logging.getLogger(__name__) class TasksApplication(Application): """ The entry point for an Envisage Tasks application. This class handles the common case for Tasks applications and is intended to be subclassed to modify its start/stop behavior, etc. """ # Extension point IDs. TASK_FACTORIES = 'envisage.ui.tasks.tasks' TASK_EXTENSIONS = 'envisage.ui.tasks.task_extensions' #### 'TasksApplication' interface ######################################### # The active task window (the last one to get focus). active_window = Instance(TaskWindow) # The PyFace GUI for the application. gui = Instance(GUI) # Icon for the whole application. Will be used to override all taskWindows # icons to have the same. icon = Instance(ImageResource, allow_none=True) #Any # The name of the application (also used on window title bars). name = Unicode # The splash screen for the application. By default, there is no splash # screen. splash_screen = Instance(SplashScreen) # The directory on the local file system used to persist window layout # information. state_location = Directory # Contributed task factories. This attribute is primarily for run-time # inspection; to instantiate a task, use the 'create_task' method. task_factories = ExtensionPoint(id=TASK_FACTORIES) # Contributed task extensions. task_extensions = ExtensionPoint(id=TASK_EXTENSIONS) # The list of task windows created by the application. windows = List(TaskWindow) # The factory for creating task windows. window_factory = Callable(TaskWindow) #### Application layout ################################################### # The default layout for the application. If not specified, a single window # will be created with the first available task factory. default_layout = List(TaskWindowLayout) # Whether to always apply the default *application level* layout when the # application is started. Even if this is False, the layout state of # individual tasks will be restored. always_use_default_layout = Bool(False) #### Application lifecycle events ######################################### # Fired after the initial windows have been created and the GUI event loop # has been started. application_initialized = Event # Fired immediately before the extant windows are destroyed and the GUI # event loop is terminated. application_exiting = Event # Fired when a task window has been created. window_created = Event(TaskWindowEvent) # Fired when a task window is opening. window_opening = Event(VetoableTaskWindowEvent) # Fired when a task window has been opened. window_opened = Event(TaskWindowEvent) # Fired when a task window is closing. window_closing = Event(VetoableTaskWindowEvent) # Fired when a task window has been closed. window_closed = Event(TaskWindowEvent) #### Protected interface ################################################## # An 'explicit' exit is when the the 'exit' method is called. # An 'implicit' exit is when the user closes the last open window. _explicit_exit = Bool(False) # Application state. _state = Instance('envisage.ui.tasks.tasks_application.' 'TasksApplicationState') ########################################################################### # 'IApplication' interface. ########################################################################### def run(self): """ Run the application. Returns: -------- Whether the application started successfully (i.e., without a veto). """ # Make sure the GUI has been created (so that, if required, the splash # screen is shown). gui = self.gui started = self.start() if started: # Create windows from the default or saved application layout. self._create_windows() # Start the GUI event loop. gui.set_trait_later(self, 'application_initialized', self) gui.start_event_loop() return started ########################################################################### # 'TasksApplication' interface. ########################################################################### def create_task(self, id): """ Creates the Task with the specified ID. Returns: -------- The new Task, or None if there is not a suitable TaskFactory. """ # Get the factory for the task. factory = self._get_task_factory(id) if factory is None: return None # Create the task using suitable task extensions. extensions = [ ext for ext in self.task_extensions if ext.task_id == id or not ext.task_id ] task = factory.create_with_extensions(extensions) task.id = factory.id return task def create_window(self, layout=None, restore=True, **traits): """ Creates a new TaskWindow, possibly with some Tasks. Parameters: ----------- layout : TaskWindowLayout, optional The layout to use for the window. The tasks described in the layout will be created and added to the window automatically. If not specified, the window will contain no tasks. restore : bool, optional (default True) If set, the application will restore old size and positions for the window and its panes, if possible. If a layout is not provided, this parameter has no effect. **traits : dict, optional Additional parameters to pass to ``window_factory()`` when creating the TaskWindow. Returns: -------- The new TaskWindow. """ window = self.window_factory(application=self, **traits) # Listen for the window events. window.on_trait_change(self._on_window_activated, 'activated') window.on_trait_change(self._on_window_opening, 'opening') window.on_trait_change(self._on_window_opened, 'opened') window.on_trait_change(self._on_window_closing, 'closing') window.on_trait_change(self._on_window_closed, 'closed') # Event notification. self.window_created = TaskWindowEvent(window=window) if layout: # Create and add tasks. for task_id in layout.get_tasks(): task = self.create_task(task_id) if task: window.add_task(task) else: logger.error('Missing factory for task with ID %r', task_id) # Apply a suitable layout. if restore: layout = self._restore_layout_from_state(layout) window.set_window_layout(layout) return window def exit(self, force=False): """ Exits the application, closing all open task windows. Each window is sent a veto-able closing event. If any window vetoes the close request, no window will be closed. Otherwise, all windows will be closed and the GUI event loop will terminate. This method is not called when the user clicks the close button on a window or otherwise closes a window through his or her window manager. It is only called via the File->Exit menu item. It can also, of course, be called programatically. Parameters: ----------- force : bool, optional (default False) If set, windows will receive no closing events and will be destroyed unconditionally. This can be useful for reliably tearing down regression tests, but should be used with caution. Returns: -------- A boolean indicating whether the application exited. """ self._explicit_exit = True try: if not force: for window in reversed(self.windows): window.closing = event = Vetoable() if event.veto: return False self._prepare_exit() for window in reversed(self.windows): window.destroy() window.closed = True finally: self._explicit_exit = False return True ########################################################################### # Protected interface. ########################################################################### def _create_windows(self): """ Called at startup to create TaskWindows from the default or saved application layout. """ # Build a list of TaskWindowLayouts. self._load_state() if self.always_use_default_layout or \ not self._state.previous_window_layouts: window_layouts = self.default_layout else: # Choose the stored TaskWindowLayouts, but only if all the task IDs # are still valid. window_layouts = self._state.previous_window_layouts for layout in window_layouts: for task_id in layout.get_tasks(): if not self._get_task_factory(task_id): logger.warning('Saved application layout references ' 'non-existent task %r. Falling back to ' 'default application layout.' % task_id) window_layouts = self.default_layout break else: continue break # Create a TaskWindow for each TaskWindowLayout. for window_layout in window_layouts: window = self.create_window(window_layout, restore=self.always_use_default_layout) window.open() def _get_task_factory(self, id): """ Returns the TaskFactory with the specified ID, or None. """ for factory in self.task_factories: if factory.id == id: return factory return None def _prepare_exit(self): """ Called immediately before the extant windows are destroyed and the GUI event loop is terminated. """ self.application_exiting = self self._save_state() def _load_state(self): """ Loads saved application state, if possible. """ state = TasksApplicationState() filename = os.path.join(self.state_location, 'application_memento') if os.path.exists(filename): # Attempt to unpickle the saved application state. try: with open(filename, 'r') as f: restored_state = cPickle.load(f) if state.version == restored_state.version: state = restored_state else: logger.warn('Discarding outdated application layout') except: # If anything goes wrong, log the error and continue. logger.exception('Restoring application layout from %s', filename) self._state = state def _restore_layout_from_state(self, layout): """ Restores an equivalent layout from saved application state. """ # First, see if a window layout matches exactly. match = self._state.get_equivalent_window_layout(layout) if match: # The active task is not part of the equivalency relation, so we # ensure that it is correct. match.active_task = layout.get_active_task() layout = match # If that fails, at least try to restore the layout of individual tasks. else: layout = layout.clone_traits() for i, item in enumerate(layout.items): id = item if isinstance(item, basestring) else item.id match = self._state.get_task_layout(id) if match: layout.items[i] = match return layout def _save_state(self): """ Saves the application state. """ # Grab the current window layouts. window_layouts = [ w.get_window_layout() for w in self.windows ] self._state.previous_window_layouts = window_layouts # Attempt to pickle the application state. filename = os.path.join(self.state_location, 'application_memento') try: with open(filename, 'w') as f: cPickle.dump(self._state, f) except: # If anything goes wrong, log the error and continue. logger.exception('Saving application layout') #### Trait initializers ################################################### def _default_layout_default(self): window_layout = TaskWindowLayout() if self.task_factories: window_layout.items = [ self.task_factories[0].id ] return [ window_layout ] def _gui_default(self): return GUI(splash_screen=self.splash_screen) def _state_location_default(self): state_location = os.path.join(ETSConfig.application_home, 'tasks', ETSConfig.toolkit) if not os.path.exists(state_location): os.makedirs(state_location) logger.debug('Tasks state location is %s', state_location) return state_location #### Trait change handlers ################################################ def _on_window_activated(self, window, trait_name, event): self.active_window = window def _on_window_opening(self, window, trait_name, event): # Event notification. self.window_opening = window_event = VetoableTaskWindowEvent( window=window) if window_event.veto: event.veto = True def _on_window_opened(self, window, trait_name, event): self.windows.append(window) # Event notification. self.window_opened = TaskWindowEvent(window=window) def _on_window_closing(self, window, trait_name, event): # Event notification. self.window_closing = window_event = VetoableTaskWindowEvent( window=window) if window_event.veto: event.veto = True else: # Store the layout of the window. window_layout = window.get_window_layout() self._state.push_window_layout(window_layout) # If we're exiting implicitly and this is the last window, save # state, because we won't get another chance. if len(self.windows) == 1 and not self._explicit_exit: self._prepare_exit() def _on_window_closed(self, window, trait_name, event): self.windows.remove(window) # Event notification. self.window_closed = TaskWindowEvent(window=window) # Was this the last window? if len(self.windows) == 0: self.stop() class TasksApplicationState(HasStrictTraits): """ A class used internally by TasksApplication for saving and restoring application state. """ # TaskWindowLayouts for the windows extant at application exit. Only used if # 'always_use_default_layout' is disabled. previous_window_layouts = List(TaskWindowLayout) # A list of TaskWindowLayouts accumulated throughout the application's # lifecycle. window_layouts = List(TaskWindowLayout) # The "version" for the state data. This should be incremented whenever a # backwards incompatible change is made to this class or any of the layout # classes. This ensures that loading application state is always safe. version = Int(1) def get_equivalent_window_layout(self, window_layout): """ Gets an equivalent TaskWindowLayout, if there is one. """ for layout in self.window_layouts: if layout.is_equivalent_to(window_layout): return layout return None def get_task_layout(self, task_id): """ Gets a TaskLayout with the specified ID, there is one. """ for window_layout in self.window_layouts: for layout in window_layout.items: if layout.id == task_id: return layout return None def push_window_layout(self, window_layout): """ Merge a TaskWindowLayout into the accumulated list. """ self.window_layouts = [ layout for layout in self.window_layouts if not layout.is_equivalent_to(window_layout) ] self.window_layouts.insert(0, window_layout) envisage-4.4.0/envisage/ui/__init__.py0000644000175000017500000000000012252661404020345 0ustar davidcdavidc00000000000000envisage-4.4.0/envisage/ui/single_project/0000755000175000017500000000000012252662115021255 5ustar davidcdavidc00000000000000envisage-4.4.0/envisage/ui/single_project/api.py0000644000175000017500000000152212252661404022400 0ustar davidcdavidc00000000000000#----------------------------------------------------------------------------- # # Copyright (c) 2006 by Enthought, Inc. # All rights reserved. # # Author: Dave Peterson # #----------------------------------------------------------------------------- # IDs of services provided by this plugin from services import IPROJECT_MODEL, IPROJECT_UI # Commonly referred to classes within this plugin from factory_definition import FactoryDefinition from model_service import ModelService from project import Project from project_action import ProjectAction from project_factory import ProjectFactory from view.project_view import ProjectView # FIXME: Add back this import when it actually works :) #from editor.project_editor import ProjectEditor #### EOF ##################################################################### envisage-4.4.0/envisage/ui/single_project/project_action_set.py0000644000175000017500000000665012252661404025514 0ustar davidcdavidc00000000000000""" Single project action set. """ # Enthought library imports. from envisage.ui.action.api import Action, Group, Menu, ToolBar from envisage.ui.workbench.api import WorkbenchActionSet # This module's package. PKG = '.'.join(__name__.split('.')[:-1]) class ProjectActionSet(WorkbenchActionSet): """ Action set of a default Project. """ # The action set's globally unique identifier. id = 'envisage.ui.single_project.action_set' # List of menus we provide. menus = [ Menu( id='ProjectMenu', name='&Project', path='MenuBar/File', group='ProjectGroup', ), ] # List of groups we provide. groups = [ Group( id='OpenGroup', path='MenuBar/File/ProjectMenu' ), Group( id='SaveGroup', path='MenuBar/File/ProjectMenu' ), Group( id='CloseGroup', path='MenuBar/File/ProjectMenu' ), Group( id='ProjectGroup', path='MenuBar/File', before='ExitGroup' ), ] # List of toolbars we provide. tool_bars = [ ToolBar( name='Project', groups=['PerspectiveGroup', 'ProjectGroup'] ), ] # List of actions we provide. actions = [ # File menu actions. Action( class_name=PKG + '.action.api:NewProjectAction', group='OpenGroup', path='MenuBar/File/ProjectMenu', ), Action( class_name=PKG + '.action.api:OpenProjectAction', group='OpenGroup', path='MenuBar/File/ProjectMenu', ), Action( class_name=PKG + '.action.api:SaveProjectAction', group='SaveGroup', path='MenuBar/File/ProjectMenu', ), Action( class_name=PKG + '.action.api:SaveAsProjectAction', group='SaveGroup', path='MenuBar/File/ProjectMenu', ), Action( class_name=PKG + '.action.api:CloseProjectAction', group='CloseGroup', path='MenuBar/File/ProjectMenu', ), # Toolbar actions. Action( class_name=PKG + '.action.api:SwitchToAction', group='PerspectiveGroup', path='ToolBar/Project', ), Action( class_name=PKG + '.action.api:NewProjectAction', group='ProjectGroup', path='ToolBar/Project', ), Action( class_name=PKG + '.action.api:OpenProjectAction', group='ProjectGroup', path='ToolBar/Project', ), Action( class_name=PKG + '.action.api:SaveProjectAction', group='ProjectGroup', path='ToolBar/Project', ), Action( class_name=PKG + '.action.api:SaveAsProjectAction', group='ProjectGroup', path='ToolBar/Project', ), Action( class_name=PKG + '.action.api:CloseProjectAction', group='ProjectGroup', path='ToolBar/Project', ), ] #### 'WorkbenchActionSet' interface ####################################### # The Ids of the perspectives that the action set is enabled in. enabled_for_perspectives = ['Project'] # The Ids of the perspectives that the action set is visible in. visible_for_perspectives = ['Project'] envisage-4.4.0/envisage/ui/single_project/model_service.py0000644000175000017500000001035312252661404024451 0ustar davidcdavidc00000000000000#----------------------------------------------------------------------------- # # Copyright (c) 2005-2007 by Enthought, Inc. # All rights reserved. # #----------------------------------------------------------------------------- """ The Envisage service providing the model state for the single project plugin. """ # Standard imports import logging import os import shutil # Enthought library imports from envisage.api import IApplication from apptools.preferences.api import IPreferences from traits.api import Any, HasTraits, Instance, List # Setup a logger for this module. logger = logging.getLogger(__name__) class ModelService(HasTraits): """ The Envisage service providing the model state for the single project plugin. """ ########################################################################## # Attributes (Traits) ########################################################################## ### public 'ModelService' interface ###################################### # The Envisage application that this service is part of. application = Instance(IApplication) # The factory to use for creating new projects factory = Instance('envisage.ui.single_project.project_factory.' 'ProjectFactory') # The preferences to be exposed through this service. preferences = Instance(IPreferences) # The currently open project project = Instance('envisage.ui.single_project.project.Project') # The current selection within the current project. selection = List(Any) ########################################################################## # 'object' interface. ########################################################################## ### operator methods ##################################################### def __init__(self, application, factory, **traits): """ Constructor. We require a reference to an Envisage application and a project factory to create an instance. """ super(ModelService, self).__init__(application = application, factory = factory, **traits) return ########################################################################## # 'ModelService' interface. ########################################################################## ### public interface ##################################################### def are_projects_files(self): """ Returns True if project instances are saved as files and False if they are saved as directories. """ return self.factory.PROJECT_CLASS.PROJECTS_ARE_FILES def clean_location(self, location): """ Ensures that there are no existing files or directories at the specified location by removing them. Exceptions are raised if there are any errors cleaning out existing files or directories. """ logger.debug('Trying to clean location [%s]', location) if os.path.isfile(location): os.path.remove(location) else: shutil.rmtree(location) return def get_default_path(self): """ Return the default location for projects. """ return self.factory.PROJECT_CLASS.get_default_path(self.application) ### trait handlers ####################################################### def _project_changed(self, old, new): """ Called whenever the current project is changed. We hook this to make sure the new project knows it's current and the old project knows it's not. """ logger.debug('Detected project change from [%s] to [%s] in ' 'ModelService [%s]', old, new, self) if old is not None: old.stop() self.selection = [] if new is not None: new.start() return def _selection_changed(self, old, new): """ Called whenever the selection within the project is changed. Implemented simply to log the change. """ logger.debug('ModelService [%s] selection changed from [%s] to [%s] ', self, old, new) return ### EOF ###################################################################### envisage-4.4.0/envisage/ui/single_project/project.py0000644000175000017500000005616512252661404023312 0ustar davidcdavidc00000000000000#----------------------------------------------------------------------------- # # Copyright (c) 2005-2007 by Enthought, Inc. # All rights reserved. # # Author: Dave Peterson # #----------------------------------------------------------------------------- """ A base class for projects that can be displayed by the single_project plugin. """ # Standard library imports. import logging import os import sys # Enthought library imports from traits.etsconfig.api import ETSConfig import apptools.sweet_pickle from apptools.io.api import File from traits.api import Any, Bool, Dict, Directory, HasTraits, \ Instance, Property, Str from traitsui.api import Group, View from traits.util.clean_strings import clean_filename # Local imports. #from envisage.ui.single_project.editor.project_editor import \ # ProjectEditor from envisage.api import Application # Setup a logger for this module. logger = logging.getLogger(__name__) class Project(HasTraits): """ A base class for projects that can be displayed by the single_project plugin. """ ########################################################################## # CLASS Attributes ########################################################################## #### public 'Project' class interface #################################### # Indicates whether instances of this project class are stored as files or # directories. The rest of the single_project plugin will follow this # setting when using this project class. # # This is meant to be a constant for the lifetime of this class! PROJECTS_ARE_FILES = True # Current envisage application. application = Instance(Application, transient=True) #### protected 'Project' class interface ################################# # Format used to create a unique name from a location and a counter. _unique_name_format = '%s_%s' ########################################################################## # Attributes ########################################################################## #### public 'Project' interface ########################################## # True if this project contains un-saved state (has been modified.) dirty = Bool(False, transient=True) # True if we allow save requests on the project. is_save_allowed = Bool(True, transient=True) # True if we allow save_as requests on the project. is_save_as_allowed = Bool(True, transient=True) # The location of this project on the filesystem. The default value # depends on the runtime environment so we use a traits default method to # set it. location = Directory # The name of this project. This is calculated from the project's current # location or, if there is no location, from a default value. See the # property's getter method. name = Property(Str) # The UI view to use when creating a new project traits_view = View( Group('location'), title = 'New Project', id = 'envisage.single_project.project.Project', buttons = [ 'OK', 'Cancel' ], width = 0.33, # Ensure closing via the dialog close button is the same # as clicking cancel. close_result = False, # Ensure the user can resize the dialog. resizable = True, ) #### protected 'Project' interface ####################################### # A list of editors currently open to visualize our resources # FIXME: Re-add the ProjectEditor's(if we need them) once they are fixed. #_editors = Dict(Any, ProjectEditor, transient=True) # The cache of this project's name. We can't just initialize it to a # default value since the location may be cleared at any time. _name = Str(transient=True) ########################################################################## # 'Object' interface. ########################################################################## #### operator methods #################################################### def __getstate__(self): """ Get the state of this object for pickling. Extended to limit which attributes get pickled and also to add version numbers to our pickle. It is STRONGLY recommended that derived classes not leverage the pickling mechanism if they wish to store additional data in OTHER files as part of their project. Instead, they should override the *_save* method. Extending this method in derived classes IS appropriate if you want to store additional data in the SAME pickle file as the project. """ # Determine the starting point for our state. state = super(Project, self).__getstate__().copy() # Remove any transient traits. for trait_name in self.trait_names(transient=True): state.pop(trait_name, None) # Add in our current version number. Note use a different attribute # name from any base or derived class so that our numbers don't # override theirs. state['_project_version_major'] = 1 state['_project_version_minor'] = 0 return state def __setstate__(self, state): """ Restore the state of this object during unpickling. Extended to upgrade pickles to the current Project version. It is STRONGLY recommended that derived classes not leverage the unpickling mechanism if they wish to load data from additional pickle files. Instead, they should override the *_load* method. Extending this method in derived classes IS appropriate if you want to load additional data from the SAME pickle file as the project. """ # Get the version info out of the state dictionary. major = state.pop('_project_version_major', 0) minor = state.pop('_project_version_minor', 0) # Upgrade to version 1. if major < 1: # Remove any old attributes. # - name is now a calculated property instead of stored value. for key in ['name']: state.pop(key, None) # Restore our state. return super(Project, self).__setstate__(state) def __str__(self): """ Return the unofficial string representation of this object. """ result ='%s(name=%s)' % (super(Project, self).__str__(), self.name) return result ########################################################################## # 'Project' interface. ########################################################################## #### public interface #################################################### def get_default_project_location(self, application): """ Return the default location for a new project. """ path = self.get_default_path(application) name = clean_filename(self.get_default_name()) location = os.path.join(path, name) location = self._make_location_unique(location) return location def get_default_path(cls, application): """ Return the default path to the parent directory for a new project. """ # When unpickling, we don't have a reference to the current application, so we # fallback on application_home. if application is None: return ETSConfig.application_home app_preferences = application.preferences path_id = 'envisage.ui.' \ 'single_project.preferred_path' path = app_preferences.get(path_id) # If the 'preferred_path' variable isn't set in the user's preferences, # then we set to the the application home by default. if len(path) == 0: app_home = ETSConfig.application_home app_preferences.set(path_id, app_home) return app_home return path get_default_path = classmethod(get_default_path) def get_default_name(self): """ Return the default name for a new project. """ return 'New Project' def get_pickle_filename(cls, location): """ Generate the project's pickle filename given a source location. By default, the filename IS the specified location or, when saving projects as directories, a file called 'project' within a directory that is the specified location, Derived classes may wish to use the location as the basis for identifying the real pickle file. """ if cls.PROJECTS_ARE_FILES: result = location else: result = os.path.join(location, 'project') return result get_pickle_filename = classmethod(get_pickle_filename) def get_pickle_package(cls): """ Returns the pickle package to use for pickling and unpickling projects. Implementors can override this to customize the way in which projects are pickled and unpickled. This implementation returns the apptools.sweet_pickle package which supports versioning and refactoring of classes. """ return apptools.sweet_pickle get_pickle_package = classmethod(get_pickle_package) def load(cls, location, application): """ Load a project from a specified location. The loaded project's location is always set to the location the project was actually loaded from. Additionally, the dirty flag is cleared on the loaded project. An exception will be raised to indicate a failure. """ # Load the project in a manner that derived classes can modify. project = cls._load(location) # Ensure the project's location reflects where the project was loaded # from and that the dirty flag is not set. project.location = location project.dirty = False # Set the project's 'application' to the running application passed in. project.application = application return project load = classmethod(load) def register_editor(self, resource, editor, remove=False): """ Inform us that an editor has been opened for what is believed to be a resource in this project. Note that if this project can be represented as a hierarchy, the resource may not be a top level object in that hierarchy! """ # Warn if the resource is not part of this project if not self._contains_resource(resource): logger.warn('This Project [%s] does not contain resource [%s]' % \ (self, resource)) # Add or remove from our set of editors as requested if not remove: self._editors[resource] = editor else: del self._editors[resource] def save(self, location=None, overwrite=False): """ Save this project. The project is saved to its current location, identified by the value of the *location* trait, unless a new location is explicitly provided. The specification of a new location is used to do a 'save as' operation. If a new location is provided, and a file or directory already exists at that location, then an *AssertionError* exception is raised unless the overwrite flag is True. This ensures that users won't accidentally overwrite existing data. This method requires the overwrite flag because prompting the user to confirm the overwrite requires GUI interaction and thus should not be done at the model level. Note that, because we can rely on the location of a loaded project always being set to the location it was loaded from, there is no reason to try to force the *location* trait within a saved project to the location we are trying to save to. Instead, we update the value in the in-memory version of the project only if the save completed successfully. The dirty flag is always cleared upon a succesfull save of the project. An exception will be raised to indicate a failure. """ # Ensure saving (or save as) is allowed at this time. if location is None or location == self.location: if not self.is_save_allowed: raise AssertionError('Saving is currently not allowed.') elif location != self.location: if not self.is_save_as_allowed: raise AssertionError('Save as is currently not allowed.') # Use the internally-specified location unless a new location was # explicitly provided. The new location can not contain any starting # or trailing whitespace and it cannot overwrite an existing file or # directory unless that was explicitly allowed. loc = self.location if location is not None: location = location.strip() if len(location) > 0 and location != self.location: # Ensure we never overwrite existing files / directories just # because someone specified a new location. (Confirmation or # correction of overwriting requires prompting of the user and # is thus not part of the project model.) if os.path.exists(location) and overwrite is False: raise AssertionError('Can not overwrite existing ' + \ 'location [%s]' % location) # The requested location is valid so let's use it. loc = location # Ensure all necessary directories exist. If we're saving a file, then # these are the path upto the file name. If we're saving to a directory # then the path is the complete location. if self.PROJECTS_ARE_FILES: path, filename = os.path.split(loc) else: path = loc if len(path) > 0: f = File(path) if f.is_file: f.delete() if not f.exists: f.create_folders() # Save this project in a manner that derived classes can modify. self._save(loc) # If the save succeeds (no exceptions were raised), then update the # location of the project and clear the dirty flag. self.location = loc self.dirty = False return def start(self): """ Notify this project that it is now the 'current' project. This call should only be made by the project plugin framework! This call *could* happen multiple times to a project, but only if interwoven with paired calls to the 'stop' method. Derived classes should override this, and chain the base implementation, if they need to do anything when a project becomes current. """ logger.debug('Project [%s] started', self) # Ensure we start with an empty set of editors self._editors = {} def stop(self): """ Called only by the project plugin framework to notify this project it is no longer the current project. This call *could* happen multiple times to a project, but only if interwoven with paired calls to the 'start' method. Derived classes should override this, and chain the base implementation, if they need to do anything when a project stops being current. """ # Close all of the editors displaying our resources self._close_all_editors() logger.debug('Project [%s] stopped', self) #### protected interface ################################################# def _close_all_editors(self): """ Called to close all editors associated with this project. """ # NOTE: The close() method on the editor will call back to remove # itself from our set of registered editors. (This assumes the # editor is derived from ProjectEditor.) for editor in self._editors.values(): logger.debug('Project requesting close of ProjectEditor [%s]', editor) editor.close() def _close_resource_editors(self, resource): """ Close any editors associated with the specified resource(s). The passed value may be a single resource or a list of resources. The resources should be parts of this project but no error is generated if they are not, nor if they are not currently associated with an editor. """ # Ensure we're dealing with a list of resources. if not isinstance(resource, list): resource = [resource] # Close any editors associated with the resources for r in resource: editor = self._editors.get(r, None) if editor is not None: logger.debug('Requesting close of ProjectEditor [%s] from ' + \ 'Project [%s]', editor, self) editor.close() return def _contains_resource(self, resource): """ Called to determine if this project contains the specified resource. Note that if this project can be represented as a hierarchy, the resource may not be a top level object in that hierarchy! Derived classes must implement this! """ return False def _get_name(self): """ Returns the current name for this project. The name is always the last part of the path that is the project's location. If we have no location, then a default name is returned. """ # Prefer to use the cached version of the name if self._name is not None and len(self._name) > 0: result = self._name # Use (and cache) a name from our current location else: # Strip any trailing path separator off the current location so # that we use the last directory name if our location is a # directory location = self.location.rstrip(os.path.sep) # The project name is then the basename of the location self._name = os.path.basename(location) result = self._name return result def _load(cls, location): """ Load a project from the specified location. This method exists purely to allow derived classes to have an easy way to override or extend the loading of a project. The default behavior is to load the project from a file using the unpickle mechanism. The caller is notified of loading errors by raised exceptions. """ # Allow derived classes to determine the actual pickle file given the # requested source location. filename = cls.get_pickle_filename(location) logger.debug('Loading Project of class [%s] from [%s]', cls, filename) # Try to unpickle the project while making sure to close any file we # opened. fh = None try: fh = file(filename, 'rb') pickle_package = cls.get_pickle_package() project = pickle_package.load(fh) # Allow derived classes to customize behavior after unpickling # is complete. project._load_hook(location) logger.debug('Loaded Project [%s] from location [%s]', project, filename) # Ensure any opened file is closed finally: if fh: try: fh.close() except: logger.exception('Unable to close project file [%s]', filename) return project _load = classmethod(_load) def _load_hook(self, location): """ Finish loading of a project. This method exists purely to allow derived classes to customize the steps that finish the loading of a project. Note that the project's internal location value does not yet reflect the location the project was loaded from, nor do we guarantee the dirty flag isn't set. (Doing the right thing to both of these is done by the framework after this method!) """ pass def _location_default(self): """ Generates the default value for our location trait. """ return self.get_default_project_location(self.application) def _make_location_unique(cls, location): """ Return a location, based off the specified location, that does not already exist on the filesystem. """ result = location counter = 1 while os.path.exists(result): result = cls._unique_name_format % (location, counter) counter+=1 return result _make_location_unique = classmethod(_make_location_unique) def _save(self, location): """ Save this project to the specified location. This method exists purely to allow derived classes to have an easy way to override or extend the saving of a project. The default behavior is to save this project to a file using the pickle mechanism. The caller is notified of saving errors by raised exceptions. """ # Allow derived classes to determine the actual pickle file from the # specified location. filename = self.get_pickle_filename(location) logger.debug('Saving Project [%s] to [%s]', self, filename) # Pickle the object to a file while making sure to close any file we # open. Note that we can't just log or ignore errors here as the # caller needs to know whether we succeeded or not, and could possibly # handle the exception if they knew what it was. fh = None try: # Allow derived classes to customize behavior before pickling is # applied. self._save_hook(location) fh = file(filename, 'wb') pickle_package = self.get_pickle_package() pickle_package.dump(self, fh, 1) logger.debug('Saved Project [%s] to [%s]', self, filename) finally: try: if fh is not None: fh.close() except: logger.exception('Unable to close project pickle file [%s]', filename) return def _save_hook(self, location): """ Start saving a project. This method exists purely to allow derived classes to customize the steps that initiate the saving of a project. Note that the project's internal location value does not reflect the target location for the save. """ pass #### trait handlers ###################################################### def _location_changed(self, old, new): """ Called whenever the project's location changes. """ logger.debug('Location changed from [%s] to [%s] for Project [%s]', old, new, self) # Invalidate any cached project name old_name = self._name self._name = '' self.trait_property_changed('name', old_name, self.name) # Indicate this project is now dirty self.dirty = True #### EOF ##################################################################### envisage-4.4.0/envisage/ui/single_project/project_factory.py0000644000175000017500000000504712252661404025032 0ustar davidcdavidc00000000000000#----------------------------------------------------------------------------- # # Copyright (c) 2005-2007 by Enthought, Inc. # All rights reserved. # # Author: Dave Peterson # #----------------------------------------------------------------------------- """ A base class for project factories. """ # Standard library imports import logging # Enthought library imports from envisage.api import IApplication from traits.api import HasTraits, Instance # Local imports. from project import Project # Setup a logger for this module. logger = logging.getLogger(__name__) class ProjectFactory(HasTraits): """ A base class for project factories. """ ########################################################################## # Attributes ########################################################################## #### public 'ProjectFactory' interface ################################### # The class of the project created by this factory. # # This is provided so that the single_project services can call class # methods. # # This value is meant to be constant for the lifetime of this class! PROJECT_CLASS = Project # Current envisage application. application = Instance(IApplication) ########################################################################## # 'ProjectFactory' interface. ########################################################################## #### public method ####################################################### def create(self): """ Create a new project from scratch. This must return an instance of a Project or 'None'. A return value of 'None' indicates that no project could be created. The plugin will display the default traits view to the user so that they can configure this new project. """ return self.PROJECT_CLASS(application=self.application) def open(self, location): """ Open a project from the specified location. This must return an instance of a Project or 'None'. A return value of 'None' indicates that no project could be opened from the specified location. """ try: project = self.PROJECT_CLASS.load(location, self.application) except: logger.exception('Unable to load Project from location %s', location) project = None return project #### EOF ##################################################################### envisage-4.4.0/envisage/ui/single_project/ui_service_factory.py0000644000175000017500000000200312252661404025506 0ustar davidcdavidc00000000000000#----------------------------------------------------------------------------- # # Copyright (c) 2006-2007 by Enthought, Inc. # All rights reserved. # #----------------------------------------------------------------------------- """ The default UI service factory. """ # Enthought library imports. from traits.api import HasTraits, Int, Str # Local imports. from ui_service import UiService class UIServiceFactory(HasTraits): """ The default UI service factory. """ # The name of the class that implements the factory. class_name = Str # The priority of this factory priority = Int ########################################################################### # 'UIServiceFactory' interface. ########################################################################### def create_ui_service(self, *args, **kw): """ Create the UI service. """ return UiService(*args, **kw) #### EOF ###################################################################### envisage-4.4.0/envisage/ui/single_project/editor/0000755000175000017500000000000012252662115022543 5ustar davidcdavidc00000000000000envisage-4.4.0/envisage/ui/single_project/editor/project_editor.py0000644000175000017500000000546212252661404026140 0ustar davidcdavidc00000000000000#----------------------------------------------------------------------------- # # Copyright (c) 2005, 2006 by Enthought, Inc. # All rights reserved. # #----------------------------------------------------------------------------- """ A base class for editors that can be tracked by single project plugin projects. """ # Standard library imports. import logging # Enthought library imports from envisage.workbench import DecoratedEditor from traits.api import Instance # Application specific imports. from envisage.single_project.services import IPROJECT_MODEL # Setup a logger for this module. logger=logging.getLogger(__name__) class ProjectEditor(DecoratedEditor): """ A base class for editors that can be tracked by single project plugin projects. """ ######################################################################### # Attributes ######################################################################### ### public 'ProjectEditor' interface #################################### # The project containing the resource we're editing project = Instance('envisage.single_project.project.Project') ######################################################################### # `object` interface ######################################################################### #### operator methods ################################################### def __init__(self, **traits): """ Constructor. Extended to associate ourself with the current project. """ super(ProjectEditor, self).__init__(**traits) # Make sure the current project knows this editor is associated with # it's resources model_service = self.window.application.get_service(IPROJECT_MODEL) self.project = model_service.project self.project.register_editor(self.resource, self) return ######################################################################### # 'Editor' interface. ######################################################################### ### public 'Editor' interface ########################################### def destroy_control(self): """ Destroys the toolkit-specific control that represents the editor. Extended to ensure that the current project stops associating us with its resources. """ # Only do something if the editor is still open if self.control: logger.debug('Destroying control in ProjectEditor [%s]', self) # Unregister from the associated project immediately. self.project.register_editor(self.resource, self, remove=True) super(ProjectEditor, self).destroy_control() return #### EOF #################################################################### envisage-4.4.0/envisage/ui/single_project/editor/__init__.py0000644000175000017500000000000012252661404024642 0ustar davidcdavidc00000000000000envisage-4.4.0/envisage/ui/single_project/default_path_preference_page.py0000644000175000017500000000266112252661404027466 0ustar davidcdavidc00000000000000#----------------------------------------------------------------------------- # # Copyright (c) 2006 by Enthought, Inc. # All rights reserved. # #----------------------------------------------------------------------------- """ Preference page for default path for a project """ # Enthought library imports from apptools.preferences.ui.api import PreferencesPage from traits.api import Directory, Str from traitsui.api import View, Item # Global assignment of ID ID = 'envisage.ui.single_project' #------------------------------------------------------------------------------- # DefaultPathPreferencePage Class #------------------------------------------------------------------------------- class DefaultPathPreferencePage(PreferencesPage): """ Preference page for default path for a plugin. """ # The page name (this is what is shown in the preferences dialog. name = 'Single Project' # The path to the preferences node that contains the preferences. preferences_path = 'envisage.ui.single_project' #### Preferences ########################################################## # Choose the unit system that needs to be used for the project preferred_path = Directory('') # Set the traits view traits_view = View(Item('preferred_path', style='custom', tooltip='Path that will be used for storing projects')) ### EOF ------------------------------------------------------------------------ envisage-4.4.0/envisage/ui/single_project/project_plugin.py0000644000175000017500000003504012252661404024655 0ustar davidcdavidc00000000000000""" The Envisage single project plugin. """ # Standard library imports import logging # Enthought library imports. from envisage.api import ExtensionPoint, Plugin, ServiceOffer from envisage.ui.single_project.api import FactoryDefinition from pyface.action.api import MenuManager from pyface.workbench.api import Perspective from traits.api import Callable, List # Local imports. from model_service import ModelService from project_action_set import ProjectActionSet from services import IPROJECT_MODEL, IPROJECT_UI from ui_service_factory import UIServiceFactory # This module's package. PKG = '.'.join(__name__.split('.')[:-1]) # Setup a logger for this module. logger = logging.getLogger(__name__) ############################################################################### # `ProjectPerspective` class. ############################################################################### class ProjectPerspective(Perspective): """ A default perspective for the single_project plugin. """ # The perspective's name. name = 'Project' # Should this perspective be enabled or not? enabled = True # Should the editor area be shown in this perspective? show_editor_area = True # The contents of the perspective. # TODO: Setup the PerspectiveItems based on the areas in our perspective. #contents = [] ############################################################################## # 'ProjectPlugin' class. ############################################################################## 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 ### EOF ###################################################################### envisage-4.4.0/envisage/ui/single_project/factory_definition.py0000644000175000017500000000073412252661404025512 0ustar davidcdavidc00000000000000#Enthought imports from traits.api import HasTraits, Int, Str class FactoryDefinition(HasTraits): """ 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. """ # The name of the class that implements the factory. class_name = Str # The priority of this factory priority = Int envisage-4.4.0/envisage/ui/single_project/preferences.ini0000644000175000017500000000007312252661404024257 0ustar davidcdavidc00000000000000[enthought.envisage.ui.single_project] preferred_path = ''envisage-4.4.0/envisage/ui/single_project/__init__.py0000644000175000017500000000000012252661404023354 0ustar davidcdavidc00000000000000envisage-4.4.0/envisage/ui/single_project/view/0000755000175000017500000000000012252662115022227 5ustar davidcdavidc00000000000000envisage-4.4.0/envisage/ui/single_project/view/project_view.py0000644000175000017500000002666012252661404025313 0ustar davidcdavidc00000000000000#----------------------------------------------------------------------------- # # Copyright (c) 2005, 2006 by Enthought, Inc. # All rights reserved. # #----------------------------------------------------------------------------- """ The single project plugin's project view """ # Standard library imports. import logging from string import rfind # Enthought library imports from apptools.naming.api import Binding from traits.api import adapts, Any, HasTraits, Instance, Str from traitsui.api import Item, Group, TreeEditor, ITreeNode, \ ITreeNodeAdapter, View # Application specific imports. from envisage.api import IApplication from envisage.ui.single_project.project import Project from envisage.ui.single_project.services import IPROJECT_MODEL, \ IPROJECT_UI # Setup a logger for this module. logger=logging.getLogger(__name__) # Dummy EmptyProject class for when the ProjectView doesn't have a reference # to a Project. class EmptyProject(Project): pass class EmptyProjectAdapter(ITreeNodeAdapter): """ Adapter for our EmptyProject. """ adapts(EmptyProject, ITreeNode) #-- ITreeNodeAdapter Method Overrides -------------------------------------- def get_label(self): """ Gets the label to display for a specified object. """ return 'No project loaded.' class ProjectAdapter(ITreeNodeAdapter): """ Base ProjectAdapter for the root of the tree. """ adapts(Project, ITreeNode) #-- ITreeNodeAdapter Method Overrides -------------------------------------- def allows_children(self): """ Returns whether this object can have children. """ return False def has_children(self): """ Returns whether the object has children. """ return False def get_children(self): """ Gets the object's children. """ return [] def get_label(self): """ Gets the label to display for a specified object. """ return self.adaptee.name def get_tooltip(self): """ Gets the tooltip to display for a specified object. """ return "Project" def get_icon(self, is_expanded): """ Returns the icon for a specified object. """ return '' def can_auto_close(self): """ Returns whether the object's children should be automatically closed. """ return True class ProjectView(HasTraits): """ The single project plugin's project view """ ########################################################################## # Traits ########################################################################## #### public 'ProjectView' interface ###################################### # The Envisage application that this service is part of. application = Instance(IApplication) # The suffix currently applied to our name name_suffix = Str('') # The suffix applied to titles when the current project is dirty. title_suffix = Str('*') # Root node for the project. root = Instance(Project) # The traits view to display: view = View( Item('root', editor = TreeEditor(editable=False, auto_open=1), show_label = False, ), resizable = True ) ########################################################################## # 'View' interface. ########################################################################## def __init__(self, **traits): super(ProjectView, self).__init__(**traits) # Make sure our view stays in sync with the current project model_service = self._get_model_service() model_service.on_trait_change(self._on_project_changed, 'project') model_service.on_trait_change(self._on_project_selection_changed, 'selection') # Make sure our control is initialized to the current project. self._switch_projects(EmptyProject(), model_service.project) ########################################################################## # 'ProjectView' interface. ########################################################################## #### protected 'ProjectView' interface ################################# def _are_list_contents_different(self, list1, list2): """ Convenience method to determine if two lists contain different items. Returns True if the lists are different and False otherwise. """ set1 = set(list1) set2 = set(list2) return set1 != set2 def _clear_state(self): """ Clears out all indications of any project state from this view. """ #self.name_suffix = '' # Set the root to an EmptyProject. self.root = EmptyProject() return def _get_model_service(self): """ Return a reference to the single project plugin's model service. """ return self.application.get_service(IPROJECT_MODEL) def _get_ui_service(self): """ Return a reference to the single project plugin's UI service. """ return self.window.application.get_service(IPROJECT_UI) def _switch_projects(self, old, new): """ Switches this view to the specified new project from the old project. Either value may be None. """ # Remove listeners on the old project, if any. if old is not None and not isinstance(old, EmptyProject): self._update_project_listeners(old, remove=True) # Update our state according to what the new project is, making sure # to add listeners to any new project. if new is not None: logger.debug("Changing ProjectView to Project [%s]", new.name) self._sync_state_to_project(new) self._update_project_listeners(new) else: logger.debug("Changing ProjectView to no project") self._clear_state() return def _sync_state_to_project(self, project): """ Sync the state of this view to the specified project's state. """ logger.debug('Syncing ProjectView [%s] to project state [%s]', self, project) # Update our Project reference. self.root = project # Update our name suffix based on the dirty state of the project. #self.name_suffix = (project.dirty and self.title_suffix) or '' return def _update_project_listeners(self, project, remove=False): """ Update listeners on the specified project instance. If remove is False then listeners are added, else listeners are removed. """ project.on_trait_change(self._on_project_name_changed, 'name', remove) project.on_trait_change(self._on_project_dirty_changed, 'dirty', remove) return #### trait handlers ###################################################### def _name_suffix_changed(self, old, new): """ Handle changes to our name suffix. """ logger.debug('Detected change in name suffix to [%s] within ' + \ 'ProjectView [%s]', new, self) # Update our own name by removing the old suffix, if any, and adding # on the new suffix, if any. name = self.name if old is not None and len(old) > 0: index = rfind(name, " " + old) if index > -1: name = name[0:index] if new is not None and len(new) > 0: name = name + ' ' + new self.name = name # Update the containing window's suffix as well self.window.title_suffix = new return def _on_control_right_clicked (self, event): """ Handle events when the tree control itself is right-clicked. """ logger.debug('ProjectView control right-clicked') self._get_ui_service().display_default_context_menu(self.control, event) return def _on_key_pressed_changed(self, event): """ Handle key presses while our control has focus. """ logger.debug("ProjectView key pressed [%s]", event) # If the delete key was pressed, then delete the current selected # object. if event.key_code == 127: self._get_ui_service().delete_selection() return def _on_node_activated_changed(self, node): """ Handle nodes being activated (i.e. double-clicked.) """ logger.debug("ProjectView node activated [%s]", node) return def _on_project_changed(self, obj, trait_name, old, new): """ Handle when the current project changes. """ logger.debug('\n\n ***************** \n\n') logger.debug('Detected project changed from [%s] to [%s] in ' 'ProjectView [%s]', old, new, self) self._switch_projects(old, new) return def _on_project_dirty_changed(self, obj, trait_name, old, new): """ Handle the open project's dirty flag changing. """ logger.debug('Detected change in project dirty to [%s] within ' + \ 'ProjectView [%s]', new, self) suffix = (new and self.title_suffix) or '' #self.name_suffix = suffix return def _on_project_name_changed(self, obj, trait_name, old, new): """ Handle the open project's name changing. """ self._project_control.root.name = new return def _on_project_selection_changed(self, obj, trait_name, old, new): """ Handle the current project's selection changing. """ logger.debug('Detected project selection changed from [%s] ' + \ 'to [%s] within ProjectView [%s]', old, new, self) # Ensure that the Tree control's selection matches the selection in # the project. control_selection = self._project_control.selection if self._are_list_contents_different(new, control_selection): logger.debug(' Updating selection on tree control for ' + \ 'ProjectView [%s]', self) self._project_control.set_selection(new) # Ensure that this view's selection contains whatever was selected # within the project. if self._are_list_contents_different(new, self.selection): logger.debug(' Updating selection on ProjectView [%s]', self) self.selection = new return def _on_selection_changed(self, obj, trait_name, old, new): """ Handle selection changes in our tree control. """ logger.debug('Detected tree control selection change from [%s] ' + \ 'to [%s] within ProjectView [%s]', old, new, self) # Ensure that the project model service's record of selection contains # the same elements as the tree control. model_service = self._get_model_service() if self._are_list_contents_different(new, model_service.selection): logger.debug(' Updating selection on project model service') model_service.selection = new # Note that we don't have to update this view's selection record as # we do that through our listener on the project selection. return def _on_closing_changed(self, old, new): """ Handle when this view closes. """ logger.debug("ProjectView [%s] closing!", self) return #### EOF ##################################################################### envisage-4.4.0/envisage/ui/single_project/view/__init__.py0000644000175000017500000000000012252661404024326 0ustar davidcdavidc00000000000000envisage-4.4.0/envisage/ui/single_project/project_action.py0000644000175000017500000001650512252661404024641 0ustar davidcdavidc00000000000000#----------------------------------------------------------------------------- # # Copyright (c) 2005-2007 by Enthought, Inc. # All rights reserved. # # Author: Dave Peterson # #----------------------------------------------------------------------------- """ A base class for actions that determine their enabled status based on the state of the current project. """ # Standard library imports import logging import inspect # Enthought library imports from envisage.api import Application from envisage.ui.workbench.api import WorkbenchWindow from pyface.action.api import Action from traits.api import Instance, Str # Local imports. from envisage.ui.single_project.services import IPROJECT_MODEL # Setup a logger for this module. logger = logging.getLogger(__name__) class ProjectAction(Action): """ A base class for actions that determine their enabled status based on the state of the current project. This class is architected such that implementors can override the 'refresh' method without losing the default logic that determines the enabled state as a function of the current project. """ #### 'ProjectAction' interface #################################### # The application that the action is part of. This is a convenience # property and is equivalent to 'self.window.application'. application = Instance(Application) # The project model service we refresh our state against. model_service = Instance(IPROJECT_MODEL) # The universal object locator (UOL). This string is used to locate an # instance to invoke a method on. # # UOLs can currently have one of the following forms: # # * ``'service://a_service_identifier'`` # * ``'name://a/path/through/the/naming/service'`` # * ``'file://the/pathname/of/a/file/containing/a/UOL'`` # * ``'http://a/URL/pointing/to/a/text/document/containing/a/UOL'`` uol = Str # The name of the method to invoke on the object. method_name = Str ########################################################################## # 'object' interface ########################################################################## def __init__(self, *args, **kws): """ Constructor. Extended to setup a listener on the project model service so that we can refresh whenever the project changes. """ # Retrieve the project model service and register ourself to listen # for project state changes. This is done first to avoid errors # during any refresh calls triggered by further initialization. # FIXME: I don't think my implementation of the ProjectAction class is correct # because I can't see to get a reference to the current application. Because of # this, I'm not able to setup the listeners yet but this needs to be done eventually! """ if 'model_service' in kws: self.model_service = kws['model_service'] del kws['model_service'] else: self.model_service = self.window.application.get_service(IPROJECT_MODEL) self._update_model_service_listeners(remove=False) """ super(ProjectAction, self).__init__(*args, **kws) return ########################################################################## # 'Action' interface ########################################################################## #### public interface #################################################### def destroy(self): """ Destroy this action. Overridden here to remove our project model service listeners. """ self._update_model_service_listeners(remove=True) return ########################################################################## # 'ProjectAction' interface ########################################################################## #### public interface #################################################### def refresh(self): """ Refresh the enabled state of this action. This default implementation enables the action only when there is a current project. """ self.enabled = self._refresh_project_exists() return def perform(self, event): """ Performs the action. This implementation simply performs the action specified by **uol** and **method_name**. Override this method to add additional work to the performance of this action. """ self._perform_uol_action(event) return ########################################################################### # Private interface. ########################################################################### def _perform_uol_action(self, event): """ Called to perform the configured UOL action. """ # Find the object. object = event.window.application.get_service(self.uol) if object is not None: method = getattr(object, self.method_name) # If the only argument is 'self' then don't pass the event. This # makes it easier to hook up actions that invoke NON-UI methods. # # fixme: Should we check the argument spec of the method more # closely and only pass the event iff there is exactly one argument # called 'event'? args, varargs, varkw, dflts = inspect.getargspec(method) if len(args) == 1: method() else: method(event) else: logger.error("Cannot resolve UOL: %s" % self.uol) return #### protected interface ################################################# def _refresh_project_exists(self): """ Return the refresh state according to whether the model service has a current project. Returns True if this action should be enabled. False otherwise. """ enabled = False if self.model_service is not None \ and self.model_service.project is not None: enabled = True return enabled def _update_model_service_listeners(self, remove=False): """ Update our listeners on the project model service. These are done as individual listener methods so that derived classes can change the behavior when a single event occurs. """ logger.debug( (remove and 'Removing ' or 'Adding ') + \ 'listeners on project model service for ProjectAction [%s]', self) self.model_service.on_trait_change(self._on_project_changed, 'project', remove=remove) self.model_service.on_trait_change(self._on_project_selection_changed, 'selection', remove=remove) return #### trait handlers ###################################################### def _on_project_changed(self, obj, trait_name, old, new): """ Handle changes to the value of the current project. """ self.refresh() return def _on_project_selection_changed(self, obj, trait_name, old, new): """ Handle changes to the selection value within the current project. """ self.refresh() return #### EOF ##################################################################### envisage-4.4.0/envisage/ui/single_project/services.py0000644000175000017500000000103512252661404023451 0ustar davidcdavidc00000000000000#----------------------------------------------------------------------------- # # Copyright (c) 2006 by Enthought, Inc. # All rights reserved. # # Author: Dave Peterson # #----------------------------------------------------------------------------- # IDs of services provided by this plugin IPROJECT_MODEL = 'envisage.ui.single_project.model_service.ModelService' IPROJECT_UI = 'envisage.ui.single_project.ui_service.UiService' #### EOF ##################################################################### envisage-4.4.0/envisage/ui/single_project/project_runnable.py0000644000175000017500000000664112252661404025172 0ustar davidcdavidc00000000000000#----------------------------------------------------------------------------- # # Copyright (c) 2006-2007 by Enthought, Inc. # All rights reserved. # # Author: Dave Peterson # #----------------------------------------------------------------------------- """ A runnable that restores the last opened project. """ # Standard library imports import logging # Enthought library imports. from envisage import Runnable from envisage.workbench.services import IWORKBENCH from pyface.api import information # Application imports from services import IPROJECT_MODEL, IPROJECT_UI # Setup a logger for this module. logger = logging.getLogger(__name__) class ProjectRunnable(Runnable): """ A runnable that restores the last opened project. """ ########################################################################## # 'Runnable' interface. ########################################################################## #### public interface #################################################### def run(self, application): """ Run this runnable. Overridden here to: (a) ensure the UI service monitors for the closing of the application, and (b) restore the last opened project. """ # Ensure our UI service is listening for the application to close. # FIXME: This ugly hack (doing this here) is necessary only because # this plugin contributes to the workbench plugin and that means the # workbench insists on starting us first which means our UI service # can't directly reference the workbench service until after # everything has been started. ui_service = application.get_service(IPROJECT_UI) ui_service.listen_for_application_exit() # Load the project we were using when we last shutdown. model_service = application.get_service(IPROJECT_MODEL) location = model_service.preferences.get('project location', default=None) if location and len(location) > 0: logger.info("Opening last project from location [%s]", location) try: project = model_service.factory.open(location) except: logger.exception('Error during opening of last project.') project = None if project is not None: model_service.project = project else: information(self._get_parent_window(application), 'Unable to open last project from location:\t\n' '\t%s\n' % (location) + '\n\n' 'The project may no longer exist.', 'Can Not Open Last Project', ) else: logger.info('No previous project to open') return #### protected interface ################################################ def _get_parent_window(self, application): """ Find and return a reference to the application window. If one can not be found, then 'None' is returned. """ window = None try: workbench = application.get_service(IWORKBENCH) window = workbench.active_window.control except: logger.warn('Unable to retrieve application window reference.') return window #### EOF ###################################################################### envisage-4.4.0/envisage/ui/single_project/ui_service.py0000644000175000017500000007033612252661404023775 0ustar davidcdavidc00000000000000#----------------------------------------------------------------------------- # # Copyright (c) 2005-2007 by Enthought, Inc. # All rights reserved. # #----------------------------------------------------------------------------- """ A service to enable UI interactions with the single project plugin. """ # Standard library imports. import logging import os import shutil # Enthought library imports from apptools.preferences.api import bind_preference from apptools.io.api import File from apptools.naming.api import Context from pyface.api import CANCEL, confirm, ConfirmationDialog, \ DirectoryDialog, error, FileDialog, information, NO, OK, YES from pyface.action.api import MenuManager from pyface.timer.api import do_later, Timer from traits.api import Any, Event, HasTraits, Instance, Int # Local imports. from model_service import ModelService # Setup a logger for this module. logger = logging.getLogger(__name__) class UiService(HasTraits): """ A service to enable UI interactions with the single project plugin. """ ########################################################################## # Attributes ########################################################################## #### public 'UiService' interface ######################################## # The manager of the default context menu default_context_menu_manager = Instance(MenuManager) # A reference to our plugin's model service. model_service = Instance(ModelService) # The project control (in our case a tree). This is created by the # project view. Provided here so that sub-classes may access it. project_control = Any # Fired when a new project has been created. The value should be the # project instance that was created. project_created = Event # A timer to implement automatic project saving. timer = Instance(Timer) # The interval (minutes)at which automatic saving should occur. autosave_interval = Int(5) ########################################################################## # 'object' interface. ########################################################################## #### operator methods #################################################### def __init__(self, model_service, menu_manager, **traits): """ Constructor. Extended to require a reference to the plugin's model service to create an instance. """ super(UiService, self).__init__( model_service = model_service, default_context_menu_manager = menu_manager, **traits ) try: # Bind the autosave interval to the value specified in the # single project preferences p = self.model_service.preferences bind_preference(self, 'autosave_interval', 5, p) except: logger.exception('Failed to bind autosave_interval in [%s] to ' 'preferences.' % self) return ########################################################################## # 'UiService' interface. ########################################################################## #### public interface #################################################### def close(self, event): """ Close the current project. """ # Ensure any current project is ready for this change. if self.is_current_project_saved(event.window.control): # If we have a current project, close it. current = self.model_service.project if current is not None: logger.debug("Closing Project [%s]", current.name) self.model_service.project = None return def create(self, event): """ Create a new project. """ # Ensure any current project is ready for this change. if self.is_current_project_saved(event.window.control): # Use the registered factory to create a new project project = self.model_service.factory.create() if project is not None: # Allow the user to customize the new project dialog = project.edit_traits( parent = event.window.control, # FIXME: Due to a bug in traits, using a wizard dialog # causes all of the Instance traits on the object being # edited to be replaced with new instances without any # listeners on those traits being called. Since we can't # guarantee that our project's don't have Instance traits, # we can't use the wizard dialog type. #kind = 'wizard' kind = 'livemodal' ) # If the user closed the dialog with an ok, make it the # current project. if dialog.result: logger.debug("Created Project [%s]", project.name) self.model_service.project = project self.project_created = project return def display_default_context_menu(self, parent, event): """ Display the default context menu for the plugin's ui. This is the context menu used when neither a project nor the project's contents are right-clicked. """ # Determine the current workbench window. This should be safe since # we're only building a context menu when the user clicked on a # control that is contained in a window. workbench = self.model_service.application.get_service('envisage.ui.workbench.workbench.Workbench') window = workbench.active_window # Build our menu from envisage.workbench.action.action_controller import \ ActionController menu = self.default_context_menu_manager.create_menu(parent, controller = ActionController(window=window)) # Popup the menu (if an action is selected it will be performed # before before 'PopupMenu' returns). if menu.GetMenuItemCount() > 0: menu.show(event.x, event.y) return def delete_selection(self): """ Delete the current selection within the current project. """ # Only do something if we have a current project and a non-empty # selection current = self.model_service.project selection = self.model_service.selection[:] if current is not None and len(selection) > 0: logger.debug('Deleting selection from Project [%s]', current) # Determine the context for the current project. Raise an error # if we can't treat it as a context as then we don't know how # to delete anything. context = self._get_context_for_object(current) if context is None: raise Exception('Could not treat Project ' + \ '[%s] as a context' % current) # Filter out any objects in the selection that can NOT be deleted. deletables = [] for item in selection: rt = self._get_resource_type_for_object(item.obj) nt = rt.node_type if nt.can_delete(item): deletables.append(item) else: logger.debug('Node type reports selection item [%s] is ' 'not deletable.', nt) if deletables != []: # Confirm the delete operation with the user names = '\n\t'.join([b.name for b in deletables]) message = ('You are about to delete the following selected ' 'items:\n\t%s\n\n' 'Are you sure?') % names title = 'Delete Selected Items?' action = confirm(None, message, title) if action == YES: # Unbind all the deletable nodes if len(deletables) > 0: self._unbind_nodes(context, deletables) return def is_current_project_saved(self, parent_window): """ Give the user the option to save any modifications to the current project prior to closing it. If the user wanted to cancel the closing of the current project, this method returns False. Otherwise, it returns True. """ # The default is the user okay'd the closing of the project result = True # If the current project is dirty, handle that now by challenging the # user for how they want to handle them. current = self.model_service.project if not(self._get_project_state(current)): dialog = ConfirmationDialog( parent = parent_window, cancel = True, title = 'Unsaved Changes', message = 'Do you want to save the changes to project "%s"?' \ % (current.name), ) action = dialog.open() if action == CANCEL: result = False elif action == YES: result = self._save(current, parent_window) elif action == NO: # Delete the autosaved file as the user does not wish to # retain the unsaved changes. self._clean_autosave_location(current.location.strip()) return result def listen_for_application_exit(self): """ Ensure that we get notified of any attempts to, and thus have a chance to veto, the closing of the application. FIXME: Normally this should be called during startup of this plugin, however, Envisage won't let us find the workbench service then because we've made a contribution to its extension points and it insists on starting us first. """ workbench = self.model_service.application.get_service('envisage.ui.workbench.workbench.Workbench') workbench.on_trait_change(self._workbench_exiting, 'exiting') return def open(self, event): """ Open a project. """ # Ensure any current project is ready for this change. if self.is_current_project_saved(event.window.control): # Query the user for the location of the project to be opened. path = self._show_open_dialog(event.window.control) if path is not None: logger.debug("Opening project from location [%s]", path) project = self.model_service.factory.open(path) if project is not None: logger.debug("Opened Project [%s]", project.name) self.model_service.project = project else: msg = 'Unable to open %s as a project.' % path error(event.window.control, msg, title='Project Open Error') return def save(self, event): """ Save a project. """ current = self.model_service.project if current is not None: self._save(current, event.window.control) return def save_as(self, event): """ Save the current project to a different location. """ current = self.model_service.project if current is not None: self._save(current, event.window.control, prompt_for_location=True) return #### protected interface ################################################# def _auto_save(self, project): """ Called periodically by the timer's Notify function to automatically save the current project. The auto-saved project has the extension '.autosave'. """ # Save the project only if it has been modified. if project.dirty and project.is_save_as_allowed: location = project.location.strip() if not(location is None or len(location) < 1): autosave_loc = self._get_autosave_location(location) try: # We do not want the project's location and name to be # updated. project.save(autosave_loc, overwrite=True, autosave=True) msg = '[%s] auto-saved to [%s]' % (project, autosave_loc) logger.debug(msg) except: logger.exception('Error auto-saving project [%s]'% project) else: logger.exception('Error auto-saving project [%s] in ' 'location %s' % (project, location)) return def _clean_autosave_location(self, location): """ Removes any existing autosaved files or directories for the project at the specified location. """ autosave_loc = self._get_autosave_location(location) if os.path.exists(autosave_loc): self.model_service.clean_location(autosave_loc) return def _get_autosave_location(self, location): """ Returns the path for auto-saving the project in location. """ return os.path.join(os.path.dirname(location), os.path.basename(location) + '.autosave') def _get_context_for_object(self, obj): """ Return the context for the specified object. """ if isinstance(obj, Context): context = obj else: context = None resource_type = self._get_resource_type_for_object(obj) if resource_type is not None: factory = resource_type.context_adapter_factory if factory is not None: # FIXME: We probably should use a real environment and # context (parent context?) context = factory.adapt(obj, Context, {}, None) return context def _get_resource_type_for_object(self, obj): """ Return the resource type for the specified object. If no type could be found, returns None. """ resource_manager = self.model_service.resource_manager return resource_manager.get_type_of(obj) def _get_project_state(self, project): """ Returns True if the project is clean: i.e., the dirty flag is False and all autosaved versions have been deleted from the filesystem. """ result = True if project is not None: autosave_loc = self._get_autosave_location( project.location.strip()) if project.dirty or os.path.exists(autosave_loc): result = False return result def _get_user_location(self, project, parent_window): """ Prompt the user for a new location for the specified project. Returns the chosen location or, if the user cancelled, an empty string. """ # The dialog to use depends on whether we're prompting for a file or # a directory. if self.model_service.are_projects_files(): dialog = FileDialog(parent = parent_window, title = 'Save Project As', default_path = project.location, action = 'save as', ) title_type = 'File' else: dialog = DirectoryDialog(parent = parent_window, message = 'Choose a Directory for the Project', default_path = project.location, action = 'open' ) title_type = 'Directory' # Prompt the user for a new location and then validate we're not # overwriting something without getting confirmation from the user. result = "" while(dialog.open() == OK): location = dialog.path.strip() # If the chosen location doesn't exist yet, we're set. if not os.path.exists(location): logger.debug('Location [%s] does not exist yet.', location) result = location break # Otherwise, confirm with the user that they want to overwrite the # existing files or directories. If they don't want to, then loop # back and prompt them for a new location. else: logger.debug('Location [%s] exists. Prompting for overwrite ' 'permission.', location) message = 'Overwrite %s?' % location title = 'Project %s Exists' % title_type action = confirm(parent_window, message, title) if action == YES: # Only use the location if we successfully remove the # existing files or directories at that location. try: self.model_service.clean_location(location) result = location break # Otherwise, display the remove error to the user and give # them another chance to pick another location except Exception, e: msg = str(e) title = 'Unable To Overwrite %s' % location information(parent_window, msg, title) logger.debug('Returning user location [%s]', result) return result def _restore_from_autosave(self, project, autosave_loc): """ Restores the project from the version saved in autosave_loc. """ workbench = self.model_service.application.get_service( 'envisage.ui.workbench.workbench.Workbench') window = workbench.active_window app_name = workbench.branding.application_name message = ('The app quit unexpectedly when [%s] was being modified.\n' 'An autosaved version of this project exists.\n' 'Do you want to restore the project from the ' 'autosaved version ?' % project.name) title = '%s-%s' % (app_name, project.name) action = confirm(window.control, message, title, cancel=True, default=YES) if action == YES: try: saved_project = self.model_service.factory.open(autosave_loc) if saved_project is not None: # Copy over the autosaved version to the current project's # location, switch the model service's project, and delete # the autosaved version. loc = project.location.strip() saved_project.save(loc, overwrite=True) self.model_service.clean_location(autosave_loc) self.model_service.project = saved_project else: logger.debug('No usable project found in [%s].' % autosave_loc) except: logger.exception( 'Unable to restore project from [%s]' % autosave_loc) self._start_timer(self.model_service.project) return def _save(self, project, parent_window, prompt_for_location=False): """ Save the specified project. If *prompt_for_location* is True, or the project has no known location, then the user is prompted to provide a location to save to. Returns True if the project was saved successfully, False if not. """ location = project.location.strip() # If the project's existing location is valid, check if there are any # autosaved versions. autosave_loc = '' if location is not None and os.path.exists(location): autosave_loc = self._get_autosave_location(location) # Ask the user to provide a location if we were told to do so or # if the project has no existing location. if prompt_for_location or location is None or len(location) < 1: location = self._get_user_location(project, parent_window) # Rename any existing autosaved versions to the new project # location. if location is not None and len(location) > 0: self._clean_autosave_location(location) new_autosave_loc = self._get_autosave_location(location) if os.path.exists(autosave_loc): shutil.move(autosave_loc, new_autosave_loc) # If we have a location to save to, try saving the project. if location is not None and len(location) > 0: try: project.save(location) saved = True msg = '"%s" saved to %s' % (project.name, project.location) information(parent_window, msg, 'Project Saved') logger.debug(msg) except Exception, e: saved = False logger.exception('Error saving project [%s]', project) error(parent_window, str(e), title='Save Error') else: saved = False # If the save operation was successful, delete any autosaved files that # exist. if saved: self._clean_autosave_location(location) return saved def _show_open_dialog(self, parent): """ Show the dialog to open a project. """ # Determine the starting point for browsing. It is likely most # projects will be stored in the default path used when creating new # projects. default_path = self.model_service.get_default_path() project_class = self.model_service.factory.PROJECT_CLASS if self.model_service.are_projects_files(): dialog = FileDialog(parent=parent, default_directory=default_path, title='Open Project') if dialog.open() == OK: path = dialog.path else: path = None else: dialog = DirectoryDialog(parent=parent, default_path=default_path, message='Open Project') if dialog.open() == OK: path = project_class.get_pickle_filename(dialog.path) if File(path).exists: path = dialog.path else: error(parent, 'Directory does not contain a recognized ' 'project') path = None else: path = None return path def _start_timer(self, project): """ Resets the timer to work on auto-saving the current project. """ if self.timer is None: if self.autosave_interval > 0: # Timer needs the interval in millisecs self.timer = Timer(self.autosave_interval*60000, self._auto_save, project) return def _unbind_nodes(self, context, nodes): """ Unbinds all of the specified nodes that can be found within this context or any of its sub-contexts. This uses a breadth first algorithm on the assumption that the user will have likely selected peer nodes within a sub-context that isn't the deepest context. """ logger.debug('Unbinding nodes [%s] from context [%s] within ' 'UiService [%s]', nodes, context, self) # Iterate through all of the selected nodes looking for ones who's # name is within our context. context_names = context.list_names() for node in nodes[:]: if node.name in context_names: # Ensure we've found a matching node by matching the objects # as well. binding = context.lookup_binding(node.name) if id(node.obj) == id(binding.obj): # Remove the node from the context -AND- from the list of # nodes that are still being searched for. context.unbind(node.name) nodes.remove(node) # Stop if we've unbound the last node if len(nodes) < 1: break # If we haven't unbound the last node, then search any sub-contexts # for more nodes to unbind. else: # Build a list of all current sub-contexts of this context. subs = [] for name in context.list_names(): if context.is_context(name): obj = context.lookup_binding(name).obj sub_context = self._get_context_for_object(obj) if sub_context is not None: subs.append(sub_context) # Iterate through each sub-context, stopping as soon as possible # if we've run out of nodes. for sub in subs: self._unbind_nodes(sub, nodes) if len(nodes) < 1: break def _workbench_exiting(self, event): """ Handle the workbench polling to see if it can exit and shutdown the application. """ logger.debug('Detected workbench closing event in [%s]', self) # Determine if the current project is dirty, or if an autosaved file # exists for this project (i.e., the project has changes which were # captured in the autosave operation but were not saved explicitly by # the user). If so, let the user # decide whether to veto the closing event, save the project, or # ignore the dirty state. current = self.model_service.project if not(self._get_project_state(current)): # Find the active workbench window to be our dialog parent and # the application name to use in our dialog title. workbench = self.model_service.application.get_service('envisage.ui.workbench.workbench.Workbench') window = workbench.active_window app_name = workbench.branding.application_name # Show a confirmation dialog to the user. message = 'Do you want to save changes before exiting?' title = '%s - %s' % (current.name, app_name) action = confirm(window.control, message, title, cancel=True, default=YES) if action == YES: # If the save is successful, the autosaved file is deleted. if not self._save(current, window.control): event.veto = True elif action == NO: # Delete the autosaved file as the user does not wish to # retain the unsaved changes. self._clean_autosave_location(current.location.strip()) elif action == CANCEL: event.veto = True #### Trait change handlers ############################################### def _autosave_interval_changed(self, old, new): """ Restarts the timer when the autosave interval changes. """ self.timer = None if new > 0 and self.model_service.project is not None: self._start_timer(self.model_service.project) return def _project_changed_for_model_service(self, object, name, old, new): """ Detects if an autosaved version exists for the project, and displays a dialog to confirm restoring the project from the autosaved version. """ if old is not None: self.timer = None if new is not None: # Check if an autosaved version exists and if so, display a dialog # asking if the user wishes to restore the project from the # autosaved version. # Note: An autosaved version should exist only if the app crashed # unexpectedly. Regular exiting of the workbench should cause the # autosaved version to be deleted. autosave_loc = self._get_autosave_location(new.location.strip()) if (os.path.exists(autosave_loc)): # Issue a do_later command here so as to allow time for the # project view to be updated first to reflect the current # project's state. do_later(self._restore_from_autosave, new, autosave_loc) else: self._start_timer(new) return #### EOF ##################################################################### envisage-4.4.0/envisage/ui/single_project/action/0000755000175000017500000000000012252662115022532 5ustar davidcdavidc00000000000000envisage-4.4.0/envisage/ui/single_project/action/save_project_action.py0000644000175000017500000000634412252661404027134 0ustar davidcdavidc00000000000000""" An action that saves the current project. """ # Enthought library imports. from envisage.ui.single_project.project_action import ProjectAction from pyface.api import ImageResource ############################################################################## # class 'SaveProjectAction' ############################################################################## class SaveProjectAction(ProjectAction): """ An action that saves the current project. """ # The universal object locator (UOL). uol = 'envisage.ui.single_project.ui_service.UiService' # The name of the method to invoke on the object. method_name = 'save' # A longer description of the action. description = 'Save the current project' # The action's image (displayed on tool bar tools etc). image = ImageResource('save_project') # The action's name (displayed on menus/tool bar tools etc). name = 'Save' # A short description of the action used for tooltip text etc. tooltip = 'Save this project' #### public interface #################################################### def refresh(self): """ Refresh the enabled state of this action. This implementation enables the action when there is a current project which is marked as saveable. """ self.enabled = self._refresh_project_exists() and \ self._refresh_is_save_allowed() return #### trait handlers ###################################################### def _on_project_changed(self, obj, trait_name, old, new): """ Handle changes to the value of the current project. Extended to ensure that we listen for changes to the saveable flag on the current project. """ if old is not None: self._update_project_listeners(old, remove=True) if new is not None: self._update_project_listeners(new, remove=False) super(SaveAction, self)._on_project_changed(obj, trait_name, old, new) ########################################################################## # 'SaveAction' interface ########################################################################## #### protected interface ################################################# def _refresh_is_save_allowed(self): """ Return the refresh state according to whether the current project is marked as saveable. Returns True if the action should be enabled and False otherwise. """ return self.model_service.project.is_save_allowed def _update_project_listeners(self, project, remove): """ Update listeners on the specified project. """ logger.debug( (remove and 'Removing ' or 'Adding ') + \ 'listeners on project [%s] for SaveAction [%s]', project, self) project.on_trait_change(self._on_is_save_allowed, 'is_save_allowed', remove=remove) return #### trait handlers ###################################################### def _on_is_save_allowed(self, obj, trait_name, old, new): """ Handle changes to the value of the current project's is_save_allowed. """ self.refresh() return envisage-4.4.0/envisage/ui/single_project/action/switch_to_action.py0000644000175000017500000000151712252661404026450 0ustar davidcdavidc00000000000000""" An action that switches the project perspective. """ # Enthought library imports. from envisage.ui.single_project.project_action import ProjectAction from pyface.api import ImageResource class SwitchToAction(ProjectAction): """ An action that switches the project perspective. """ # A longer description of the action. description = 'View the current project in the Project perspective' # The action's image (displayed on tool bar tools etc). image = ImageResource('switch_project') # The action's name (displayed on menus/tool bar tools etc). name = 'Switch To Project' # A short description of the action used for tooltip text etc. tooltip = 'Go to the Project perspective' def perform(self, event): """ Perform the action. """ self.window.application.about() return envisage-4.4.0/envisage/ui/single_project/action/api.py0000644000175000017500000000045312252661404023657 0ustar davidcdavidc00000000000000from new_project_action import NewProjectAction from open_project_action import OpenProjectAction from save_project_action import SaveProjectAction from save_as_project_action import SaveAsProjectAction from close_project_action import CloseProjectAction from switch_to_action import SwitchToAction envisage-4.4.0/envisage/ui/single_project/action/images/0000755000175000017500000000000012252662115023777 5ustar davidcdavidc00000000000000envisage-4.4.0/envisage/ui/single_project/action/images/open_project.png0000644000175000017500000000115112252661404027172 0ustar davidcdavidc00000000000000‰PNG  IHDRóÿagAMA¯È7ŠétEXtSoftwareAdobe ImageReadyqÉe<ûIDATxÚbd```ê.øË€J»>h©Û@ü›  æÿ?ÞíÅÀ q ¼s1 31#º~€I0üþr ÃàÜŠµ@2x/!WØ€ÿÿ¾C¹ÿ@>Ó¿>†‚ ‚¯ q2N1HÓ€ðï÷; h(#+XÙ·[¯{ŒËË`W]¹ý»háú¯3jÀ¸‚¿?€5gÏe`bÀv“;‚û€Ô5€‚ðç\âÛ‡« p¹ôÚ/°wþÿyùÃ÷Wë8D¼ÍÑ Hüýæýù~ŸaÕŽï ©)É@C?‚$þÿÿ¡ÿýaøýû/Ûï ó—ìè –ÇõÄoÞ~†%H3Ã_° ¶ÿ…4ÃÏßÿ¸þ~…Ùþˆ<?ù°ãð†Ä7ˆ—þƒbä?Ü ö? µ`ùQ˜íÇø@ xõæ3î£? Þù V áüzáÔ+~þúlûa¯lH³ƒˆ¤!PíHÀýÿwÌ :¨í tÀ Ò @ ¤©ŒÛ« D ý@ªˆŸL  ÔcÄ &D@?Èy€ø(8^ €`™D³C Ã@¹öL3ãÿÿÿ(0ŒþëøIEND®B`‚envisage-4.4.0/envisage/ui/single_project/action/images/save_project.png0000644000175000017500000000121112252661404027164 0ustar davidcdavidc00000000000000‰PNG  IHDRóÿabKGDhv’|ay pHYs × ×B(›xtIME×  *;Õ‰=?IDAT8Ë¥“OH“aÇ?ï|k²a²%ýΉ #‚Ž™v¡Ñ¡„(v()Áè².£KT”Q±&±ƒZJ¢+:$«‘D ,H§æ¶w›·÷}÷<öšË }áÇsøñû<߇ïïQ[Ðï*²B^Ÿæb€*ú¿ô»¤™WTÐï’@3àT«ª¥|^0r+À'»ÇÏпœ©ÂL—ºR€b)(hÃd¥<ã^Ÿ¶x³ И‹ô‡C¬E½}ßãz¯ß¹Ùi9HÑýåÆ?ýþx1Ãíêé@Ͻ]%€‘@Ͼ³žRDJ¤:R@,t\xz‘õ-üœœV,@’Ò™FQleÃfi) b)7£¹(é|šöúC†Ø-ØZTtÀäõ y6•ëcëIEND®B`‚envisage-4.4.0/envisage/ui/single_project/action/images/new_project.png0000644000175000017500000000103412252661404027022 0ustar davidcdavidc00000000000000‰PNG  IHDRóÿabKGDhv’|ay pHYs × ×B(›xtIME×  :Xaå#©IDAT8Ë•“¿kAÇ?{9AÅ?«ìÒú lý‚ka#ˆÁB¶ZDÛ`¥k‘RBÀVp6‚­Eª˜”bq¹½œžfwÞ¼g1³Éqg¢~á10Ãû¼ï¼7“²H=3Êòªì Ì©,R“ïsQ©×€E cH¦ó»îÇç9ðÝÕm ·õ7g]•Q85…¤¦ÔU?BfÕÛÎòê*ðñÐ _{ln¬ó/*‹´ßB¢ƒ!›ë'TBbh H(ýüÿ÷j;Ä`À®€€B‰FpXüÿq R@ ú6äïßß( ÄéŸ]qDˆ qú?°Ìß Pï€Á€%K-`¼’°¹ H­ ¬@lÄÎ@,D¤þï@|ˆ÷,c€hv¨aÄPî΀bü 4r@€œô2s˜^IEND®B`‚envisage-4.4.0/envisage/ui/single_project/action/images/switch_project.png0000644000175000017500000000117612252661404027541 0ustar davidcdavidc00000000000000‰PNG  IHDRóÿabKGDhv’|ay pHYs × ×B(›xtIME×  //ô# IDAT8Ë“KhQ†¿{g¦IÓ¦dÚX"ÝÄ®ŠtS\W* "Ù ]¨à&ÝDtë.›l\»Q7ÖXAp'R‚øBAc'J“™É½]ÌDÓÆ`8\8‡ûÿòs ó9»Ãe²Nø( oþGùœ­ýV¥¯ò9['€`†e¢÷¾ à­½ë_¾QÒ‹ÿsf(¿Lµ!A+ÚN)„lUºœÉ:Ç×ný ÅÛQ>g—ºÐÁ* Å€ƒ”.e²ÎÑàUpo§tÐÚí¡•‹VmfÌ1?ÈùÚáÉÙ.å” ¿ÎÚo ü:Ú¯¡¼zÐë4I$ßSšÂó”Wï&BN¸¸F¡•üÀ‰j£•KlT’Ú9Å׈Ç|ñ)û€«¿ZqæámÆ’ÓÃD¢‚xB ¬6»'wQ™þÌÚÏià1›bT^´&n Ží?MK®ÐžÑÙâφ¾˜™¿›š3ëMzH~´ÞãU°"`Y`ZÃ)Aið}h!ÀÞ|ÎޔߣD“DR E!6*³±4®«ù½tˆ;_€NãƒR¿¹½>¾ÃÀó5ëKG¸uáåY`±û1 aÿÔµ·Ã {BP2Ž«Ë#€Dµ=í¹ò"¢/ÝO—™ÞÁªò\Û¨ÞIEND®B`‚envisage-4.4.0/envisage/ui/single_project/action/images/save_as_project.png0000644000175000017500000000076212252661404027661 0ustar davidcdavidc00000000000000‰PNG  IHDRóÿabKGDhv’|ay pHYs × ×B(›xtIME×  )4nsmIDAT8Ë“½JA…¿‰Ôn#ø6¦²Q±S±×GIae£e·O#b±–¢¾‚¤RQ°,E$Éúo’™k1ͯz`˜»ç›s÷î* …AB›rù8 Üè¨+ 1ÕûŽ…³Àö«PÍþ4€~»ê/­ßÙ£¿’¥¬yrU± R –Z\ôveorùx8þÔK˜Ï;÷wù¢0(6 >A…ÃýÝ7öR¶˜ËÇS KÔ_N}+ "D#¶ŽØˆ,{ Ž,6(óPÆíO(•j2gÆ V»³ä«Ÿ€aˆýÅ/±0.‰­ý¤À¿_:(¨¸æÙMAlH(Tظ˜hê½à¶Ì&+g«-c´º".o£ÕܪíÇI÷!IòJs—À€.§o»ÆOFQÀX=ç—Ë # #----------------------------------------------------------------------------- """ An action to close the current project. This is only enabled when there is a current project. """ # Enthought library imports from envisage.ui.single_project.project_action import ProjectAction from pyface.api import ImageResource ############################################################################## # class 'CloseProjectAction' ############################################################################## class CloseProjectAction(ProjectAction): """ An action to close the current project. This is only enabled when there is a current project. """ # The universal object locator (UOL). uol = 'envisage.ui.single_project.ui_service.UiService' # The name of the method to invoke on the object. method_name = 'close' # A longer description of the action. description = 'Close the current project' # The action's image (displayed on tool bar tools etc). image = ImageResource('close_project') # The action's name (displayed on menus/tool bar tools etc). name = 'Close' # A short description of the action used for tooltip text etc. tooltip = 'Close this project' #### EOF ##################################################################### envisage-4.4.0/envisage/ui/single_project/action/configure_action.py0000644000175000017500000000236412252661404026427 0ustar davidcdavidc00000000000000""" An action that configures an item in the project tree. """ # Enthought library imports. from envisage.ui.single_project.project_action import ProjectAction ############################################################################## # class 'ConfigureAction' ############################################################################## class ConfigureAction(ProjectAction): """ An action that configures an item in the project tree. 'Configures' in this sense means pop up a trait sheet! """ ########################################################################### # 'Action' interface. ########################################################################### def perform(self, event): """ Performs the action. """ # fixme: We would like to use "kind='modal'" here, but it doesn't # work! The bug we see is that when the dialog is complete the # viscosity model assigned to the trait is NOT the same as the # viscosity model in the material's '_dirty_objects' collection! ui = event.node.obj.edit_traits( parent=event.window.control, kind='livemodal' ) return #### EOF ###################################################################### envisage-4.4.0/envisage/ui/single_project/action/__init__.py0000644000175000017500000000000012252661404024631 0ustar davidcdavidc00000000000000envisage-4.4.0/envisage/ui/single_project/action/open_project_action.py0000644000175000017500000000175712252661404027142 0ustar davidcdavidc00000000000000""" An action that opens a project. """ # Enthought library imports. from envisage.ui.single_project.project_action import ProjectAction from pyface.api import ImageResource ############################################################################## # class 'OpenProjectAction' ############################################################################## class OpenProjectAction(ProjectAction): """ An action that opens a project. """ # The universal object locator (UOL). uol = 'envisage.ui.single_project.ui_service.UiService' # The name of the method to invoke on the object. method_name = 'open' # A longer description of the action. description = 'Open an existing project' # The action's image (displayed on tool bar tools etc). image = ImageResource('open_project') # The action's name (displayed on menus/tool bar tools etc). name = 'Open...' # A short description of the action used for tooltip text etc. tooltip = 'Open a project' envisage-4.4.0/envisage/ui/single_project/action/new_project_action.py0000644000175000017500000000176312252661404026767 0ustar davidcdavidc00000000000000""" An action that creates a new project. """ # Enthought library imports. from envisage.ui.single_project.project_action import ProjectAction from pyface.api import ImageResource ############################################################################## # class 'NewProjectAction' ############################################################################## class NewProjectAction(ProjectAction): """ An action that creates a new project. """ # The universal object locator (UOL). uol = 'envisage.ui.single_project.ui_service.UiService' # The name of the method to invoke on the object. method_name = 'create' # A longer description of the action. description = 'Create a project' # The action's image (displayed on tool bar tools etc). image = ImageResource('new_project') # The action's name (displayed on menus/tool bar tools etc). name = 'New...' # A short description of the action used for tooltip text etc. tooltip = 'Create a project' envisage-4.4.0/envisage/ui/single_project/action/rename_action.py0000644000175000017500000000436612252661404025721 0ustar davidcdavidc00000000000000""" An action that renames an item in the project tree. """ # Enthought library imports. from envisage.ui.single_project.project_action import ProjectAction class RenameAction(ProjectAction): """ Renames an item in the project tree. """ #### 'Action' interface ################################################### # The action's name (displayed on menus/tool bar tools etc). name = 'Rename' ########################################################################### # 'Action' interface. ########################################################################### def perform (self, event): """ Performs the action. """ event.widget.edit_label(event.node) return # fixme: This should be a selection listener action that probably is only # enabled if there is exactly ONE item in the selection and it is editable. ## ########################################################################### ## # 'Action' interface. ## ########################################################################### ## def refresh(self): ## """ Refresh the enabled/disabled state of the action etc. ## This is called whenever the workbench window that the action is in ## and/or the selection in that window have been changed. ## """ ## resource_manager = self.window.application.get_service( ## 'envisage.resource.IResourceManager' ## ) ## # fixme: It seems there is a glitch in the tree selection handling. ## # When the selection changes we get an empty selection first, then ## # the new selection. ## if len(self.window.selection) > 0: ## for node in self.window.selection: ## resource_type = resource_manager.get_type_of(node.obj) ## if resource_type is None or resource_type.node_type is None: ## self.enabled = False ## break ## if not resource_type.node_type.is_editable(node.obj): ## self.enabled = False ## break ## else: ## self.enabled = True ## return #### EOF ###################################################################### envisage-4.4.0/envisage/ui/single_project/action/save_as_project_action.py0000644000175000017500000000667412252661404027625 0ustar davidcdavidc00000000000000""" An action that saves the current project to a different location. """ # Enthought library imports. from envisage.ui.single_project.project_action import ProjectAction from pyface.api import ImageResource ############################################################################## # class 'SaveAsProjectAction' ############################################################################## class SaveAsProjectAction(ProjectAction): """ An action that saves the current project to a different location. """ # The universal object locator (UOL). uol = 'envisage.ui.single_project.ui_service.UiService' # The name of the method to invoke on the object. method_name = 'save_as' # A longer description of the action. description = 'Save the current project to a different location' # The action's image (displayed on tool bar tools etc). image = ImageResource('save_as_project') # The action's name (displayed on menus/tool bar tools etc). name = 'Save As...' # A short description of the action used for tooltip text etc. tooltip = 'Save this project to a different location' #### public interface #################################################### def refresh(self): """ Refresh the enabled state of this action. This implementation enables the action when there is a current project which is marked as being allowed to do a 'save as' operation. """ self.enabled = self._refresh_project_exists() and \ self._refresh_is_save_as_allowed() return #### trait handlers ###################################################### def _on_project_changed(self, obj, trait_name, old, new): """ Handle changes to the value of the current project. Extended to ensure that we listen for changes to the is_save_as_allowed flag on the current project. """ if old is not None: self._update_project_listeners(old, remove=True) if new is not None: self._update_project_listeners(new, remove=False) super(SaveAsAction, self)._on_project_changed(obj, trait_name, old, new) ########################################################################## # 'SaveAsAction' interface ########################################################################## #### protected interface ################################################# def _refresh_is_save_as_allowed(self): """ Return the refresh state according to whether the current project is marked as being capable of doing a 'save as'. Returns True if the action should be enabled and False otherwise. """ return self.model_service.project.is_save_as_allowed def _update_project_listeners(self, project, remove): """ Update listeners on the specified project. """ logger.debug( (remove and 'Removing ' or 'Adding ') + \ 'listeners on project [%s] for SaveAsAction [%s]', project, self) project.on_trait_change(self._on_is_save_as_allowed, 'is_save_as_allowed', remove=remove) return #### trait handlers ###################################################### def _on_is_save_as_allowed(self, obj, trait_name, old, new): """ Handle changes to the value of the current project's is_save_as_allowed. """ self.refresh() return envisage-4.4.0/envisage/ui/action/0000755000175000017500000000000012252662115017523 5ustar davidcdavidc00000000000000envisage-4.4.0/envisage/ui/action/i_action_manager_builder.py0000644000175000017500000000135512252661404025066 0ustar davidcdavidc00000000000000""" The interface for action manager builders. """ # Enthought library imports. from traits.api import Interface, List # Local imports. from action_set import ActionSet class IActionManagerBuilder(Interface): """ The interface for action manager builders. An action manager builder populates action managers (i.e. menus, menu bars and tool bars) from the menus, groups and actions defined in its action sets. """ # The action sets used by the builder. action_sets = List(ActionSet) def initialize_action_manager(self, action_manager, root): """ Initialize an action manager from the builder's action sets. """ #### EOF ###################################################################### envisage-4.4.0/envisage/ui/action/i_action_set.py0000644000175000017500000000416212252661404022540 0ustar davidcdavidc00000000000000""" The action set interface. """ # Enthought library imports. from traits.api import Dict, Interface, List, Str # Local imports. from action import Action from group import Group from menu import Menu from tool_bar import ToolBar class IActionSet(Interface): """ The action set interface. An action set is a collection of menus, groups, and actions. """ # The action set's globally unique identifier. id = Str # The action set's name. # # fixme: This is not currently used, but in future it will be the name that # is shown to the user when they are customizing perspectives by adding or # removing action sets etc. name = Str # The actions in this set. actions = List(Action) # The groups in this set. groups = List(Group) # The menus in this set. menus = List(Menu) # The tool bars in this set. tool_bars = List(ToolBar) # A mapping from human-readable names to globally unique IDs. # # This mapping is used when interpreting the first item in a location path # (i.e., the **path** trait of a **Location** instance). # # When the path is intepreted, the first component (i.e., the first item # before any '/') is checked to see if it is in the mapping, and if so it # is replaced with the value in the map. # # This technique allows paths to start with human readable names, as # opposed to IDs (which are required in order to manage the namespace of # all action sets). # # For example, in the Envisage workbench, the menu bar ID is: # # ``'envisage.workbench.menubar'`` # # Without aliases, you must specify a location like this: # # ``Location(path='envisage.workbench.menubar/ASubMenu/AGroup')`` # # This is a bit long-winded! Instead, you can define an alias: # # ``aliases = { 'MenuBar' : 'envisage.workbench.menubar' }`` # # In that case, you can specify a location like this: # # ``Location(path='MenuBar/ASubMenu/AGroup')`` # aliases = Dict(Str, Str) #### EOF ###################################################################### envisage-4.4.0/envisage/ui/action/location.py0000644000175000017500000000246012252661404021707 0ustar davidcdavidc00000000000000""" The location of a group, menu, or action, within an action hierarchy. """ # Enthought library imports. from traits.api import HasTraits, Str class Location(HasTraits): """ The location of a group, menu, or action, within an action hierarchy. """ # A forward-slash-separated path through the action hierarchy to the menu # to add the action, group or menu to. # # Examples # -------- # # * To add an item to the menu bar: ``path = "MenuBar"`` # # * To add an item to the tool bar: ``path = "ToolBar"`` # # * To add an item to a sub-menu: ``path = "MenuBar/File/New"`` # path = Str #### Placement of the action within the menu specified by the path ######## # The ID of the group to add the action or menu to (you can't have nested # groups). group = Str # The item appears after the item with this ID. # # - for groups, this is the ID of another group. # - for menus and actions, this is the ID of another menu or action. after = Str # The action appears before the item with this ID. # # - for groups, this is the ID of another group. # - for menus and actions, this is the ID of another menu or action. before = Str #### EOF ###################################################################### envisage-4.4.0/envisage/ui/action/api.py0000644000175000017500000000046012252661404020646 0ustar davidcdavidc00000000000000from i_action_set import IActionSet from i_action_manager_builder import IActionManagerBuilder from abstract_action_manager_builder import AbstractActionManagerBuilder from action import Action from action_set import ActionSet from group import Group from menu import Menu from tool_bar import ToolBar envisage-4.4.0/envisage/ui/action/action.py0000644000175000017500000000163512252661404021357 0ustar davidcdavidc00000000000000""" The *definition* of an action in a tool bar or menu. """ # Enthought library imports. from traits.api import Str # Local imports. from location import Location class Action(Location): """ The *definition* of an action in a tool bar or menu. """ #### Action implementation ################################################ # The action's name (appears on menus and toolbars etc). name = Str # The name of the class that implements the action. class_name = Str ########################################################################### # 'object' interface ########################################################################### def __str__(self): """ Return the 'informal' string representation of the object. """ return 'Action(%s)' % self.name __repr__ = __str__ #### EOF ###################################################################### envisage-4.4.0/envisage/ui/action/abstract_action_manager_builder.py0000644000175000017500000003535012252661404026443 0ustar davidcdavidc00000000000000""" Builds menus, menu bars and tool bars from action sets. """ # Enthought library imports. from pyface.action.api import ActionManager, MenuManager from traits.api import HasTraits, Instance, List, implements # Local imports. from action_set import ActionSet from action_set_manager import ActionSetManager from group import Group from i_action_manager_builder import IActionManagerBuilder class AbstractActionManagerBuilder(HasTraits): """ Builds menus, menu bars and tool bars from action sets. This class *must* be subclassed, and the following methods implemented:: _create_action _create_group _create_menu_manager """ implements(IActionManagerBuilder) #### 'IActionManagerBuilder' interface #################################### # The action sets used by the builder. action_sets = List(ActionSet) #### Private interface #################################################### _action_set_manager = Instance(ActionSetManager, ()) ########################################################################### # 'IActionManagerBuilder' interface. ########################################################################### def create_menu_bar_manager(self, root): """ Create a menu bar manager from the builder's action sets. """ menu_bar_manager = self._create_menu_bar_manager() self.initialize_action_manager(menu_bar_manager, root) return menu_bar_manager # fixme: V3 refactor loooong (and confusing) method! def create_tool_bar_managers(self, root): """ Creates all tool bar managers from the builder's action sets. """ ######################################## # New style (i.e multi) tool bars. ######################################## tool_bar_managers = [] for tool_bar in self._action_set_manager.get_tool_bars(root): # Get all of the groups for the tool bar. groups = [] for group in self._action_set_manager.get_groups(root): if group.path.startswith('%s/%s' % (root, tool_bar.name)): group.path = '/'.join(action.path.split('/')[1:]) groups.append(group) # Get all of the actions for the tool bar. actions = [] for action in self._action_set_manager.get_actions(root): if action.path.startswith('%s/%s' % (root, tool_bar.name)): action.path = '/'.join(action.path.split('/')[1:]) actions.append(action) # We don't add the tool bar if it is empty! if len(groups) + len(actions) > 0: tool_bar_manager = self._create_tool_bar_manager(tool_bar) # Add all groups and menus. self._add_groups_and_menus(tool_bar_manager, groups) # Add all of the actions ot the menu manager. self._add_actions(tool_bar_manager, actions) # Include the tool bar! tool_bar_managers.append(tool_bar_manager) ###################################################################### # Scoop up old groups and actions for the old style (single) tool bar. ###################################################################### # Get all of the groups for the tool bar. groups = [] for group in self._action_set_manager.get_groups(root): if group.path == root: groups.append(group) # Get all of the actions for the tool bar. actions = [] for action in self._action_set_manager.get_actions(root): if action.path == root: actions.append(action) # We don't add the tool bar if it is empty! if len(groups) + len(actions) > 0: from tool_bar import ToolBar tool_bar_manager = self._create_tool_bar_manager( ToolBar(name='Tool Bar', path=root, _action_set_=None) ) # Add all groups and menus. self._add_groups_and_menus(tool_bar_manager, groups) # Add all of the actions ot the menu manager. self._add_actions(tool_bar_manager, actions) # Include the tool bar! tool_bar_managers.insert(0, tool_bar_manager) return tool_bar_managers def initialize_action_manager(self, action_manager, root): """ Initialize an action manager from the builder's action sets. """ # Get all of the groups and menus for the specified root (for toolbars # there will **only** be groups). groups_and_menus = self._action_set_manager.get_groups(root) groups_and_menus.extend(self._action_set_manager.get_menus(root)) # Add all groups and menus. self._add_groups_and_menus(action_manager, groups_and_menus) # Get all actions for the specified root. actions = self._action_set_manager.get_actions(root) # Add all of the actions ot the menu manager. self._add_actions(action_manager, actions) return ########################################################################### # Protected 'AbstractActionManagerBuilder' interface. ########################################################################### def _create_action(self, action_definition): """ Creates an action implementation from a definition. """ raise NotImplementedError def _create_group(self, group_definition): """ Creates a group implementation from a definition. """ raise NotImplementedError def _create_menu_manager(self, menu_manager_definition): """ Creates a menu manager implementation from a definition. """ raise NotImplementedError def _create_menu_bar_manager(self): """ Creates a menu bar manager implementation. """ raise NotImplementedError def _create_tool_bar_manager(self, tool_bar_definition): """ Creates a tool bar manager implementation from a definition. """ raise NotImplementedError ########################################################################### # Private interface. ########################################################################### #### Trait change handler ################################################# def _action_sets_changed(self, old, new): """ Static trait change handler. """ self._action_set_manager.action_sets = new return #### Methods ############################################################## def _add_actions(self, action_manager, actions): """ Add the specified actions to an action manager. """ while len(actions) > 0: start = len(actions) for action in actions[:]: # Resolve the action's path to find the action manager that it # should be added to. # # If any of the menus in path are missing then this creates # them automatically (think 'mkdirs'!). target = self._make_submenus(action_manager, action.path) # Attempt to place the action. # # If the action needs to be placed 'before' or 'after' some # other action, but the other action has not yet been added # then we will try again later! if self._add_action(target, action): actions.remove(action) end = len(actions) # If we didn't succeed in placing *any* actions then we must have a # problem! if start == end: raise ValueError('Could not place %s' % actions) return def _add_action(self, action_manager, action): """ Add an action to an action manager. Return True if the action was added successfully. Return False if the action needs to be placed 'before' or 'after' some other action, but the other action has not yet been added. """ group = self._find_group(action_manager, action.group) if group is None: msg = 'No such group (%s) for %s' % (action.group, action) raise ValueError(msg) if len(action.before) > 0: item = group.find(action.before) if item is None: return False index = group.items.index(item) elif len(action.after) > 0: item = group.find(action.after) if item is None: return False index = group.items.index(item) + 1 else: index = len(group.items) group.insert(index, self._create_action(action)) return True def _add_groups_and_menus(self, action_manager, groups_and_menus): """ Add the specified groups and menus to an action manager. """ # The reason we put the groups and menus together is that as we iterate # over the list trying to add them, we might need to add a group before # we can add a menu and we might need to add a menu before we can add a # group! Hence, we take multiple passes over the list and we only barf # if, in any single iteration, we cannot add anything. while len(groups_and_menus) > 0: start = len(groups_and_menus) for item in groups_and_menus[:]: # Resolve the path to find the menu manager that we are about # to add the sub-menu or group to. target = self._find_action_manager(action_manager, item.path) if target is not None: # Attempt to place a group. if isinstance(item, Group): if self._add_group(target, item): groups_and_menus.remove(item) # Attempt to place a menu. elif self._add_menu(target, item): groups_and_menus.remove(item) end = len(groups_and_menus) # If we didn't succeed in adding *any* menus or groups then we # must have a problem! if start == end: raise ValueError('Could not place %s' % groups_and_menus) return def _add_group(self, action_manager, group): """ Add a group to an action manager. Return True if the group was added successfully. Return False if the group needs to be placed 'before' or 'after' some other group, but the other group has not yet been added. """ # Does the group already exist in the menu? If not then add it, # otherwise do nothing. if action_manager.find_group(group.id) is None: if len(group.before) > 0: item = action_manager.find_group(group.before) if item is None: return False index = action_manager.groups.index(item) elif len(group.after) > 0: item = action_manager.find_group(group.after) if item is None: return False index = action_manager.groups.index(item) + 1 else: # If the menu manger has an 'additions' group then make sure # that it is always the last one! In Pyface, the 'additions' # groups is created by default, so unless someone has # explicitly removed it, it *will* be there! additions = action_manager.find_group('additions') if additions is not None: index = action_manager.groups.index(additions) else: index = len(action_manager.groups) action_manager.insert(index, self._create_group(group)) return True def _add_menu(self, menu_manager, menu): """ Add a menu manager to a errr, menu manager. Return True if the menu was added successfully. Return False if the menu needs to be placed 'before' or 'after' some other item, but the other item has not yet been added. """ group = self._find_group(menu_manager, menu.group) if group is None: return False if len(menu.before) > 0: item = group.find(menu.before) if item is None: return False index = group.items.index(item) elif len(menu.after) > 0: item = group.find(menu.after) if item is None: return False index = group.items.index(item) + 1 else: index = len(group.items) # If the menu does *not* already exist in the group then add it. menu_item = group.find(menu.id) if menu_item is None: group.insert(index, self._create_menu_manager(menu)) # Otherwise, add all of the new menu's groups to the existing one. else: for group in menu.groups: self._add_group(menu_item, group) return True def _find_group(self, action_manager, id): """ Find the group with the specified ID. """ if len(id) > 0: group = action_manager.find_group(id) else: group = action_manager.find_group('additions') return group def _find_action_manager(self, action_manager, path): """ Return the action manager at the specified path. Returns None if the action manager cannot be found. """ components = path.split('/') if len(components) == 1: action_manager = action_manager else: action_manager = action_manager.find_item('/'.join(components[1:])) return action_manager def _make_submenus(self, menu_manager, path): """ Retutn the menu manager identified by the path. Make any intermediate menu-managers that are missing. """ components = path.split('/') # We skip the first component, because if the path is of length 1, then # the target menu manager is the menu manager passed in. for component in components[1:]: item = menu_manager.find_item(component) # If the menu manager does *not* contain an item with this ID then # create a sub-menu automatically. if item is None: item = MenuManager(id=component, name=component) menu_manager.append(item) # If the menu manager *does* already contain an item with this ID # then make sure it is a menu and not an action! elif not isinstance(item, ActionManager): msg = '%s is not a menu in path %s' % (item, path) raise ValueError(msg) menu_manager = item return menu_manager #### EOF ###################################################################### envisage-4.4.0/envisage/ui/action/menu.py0000644000175000017500000000445712252661404021053 0ustar davidcdavidc00000000000000""" The *definition* of a menu in a menu bar or menu. """ # Enthought library imports. from traits.api import Instance, List, Str # Local imports. from group import Group from location import Location class CGroup(Instance): """ A trait type for a 'Group' or anything that can be cast to a 'Group'. Currently, the only cast allowed is from string -> Group using the string as the group's ID. """ ########################################################################### # 'object' interface. ########################################################################### def __init__(self, **kw): """ Constructor. """ super(CGroup, self).__init__(klass=Group, **kw) return ########################################################################### # 'TraitType' interface. ########################################################################### def validate(self, object, name, value): """ Validate a value. """ if isinstance(value, basestring): value = Group(id=value) return super(CGroup, self).validate(object, name, value) class Menu(Location): """ The *definition* of a menu in a menu bar or menu. """ # The menu's unique identifier (unique within the group that the menu is to # be added to). id = Str # The menu name (appears on the menu bar or menu). name = Str # The groups in the menu. groups = List(CGroup) # The optional name of a class that implements the menu. The class must # support the **pyface.action.MenuManager** protocol. class_name = Str ########################################################################### # 'object' interface ########################################################################### def __str__(self): """ Return the 'informal' string representation of the object. """ return 'Menu(%s)' % self.name __repr__ = __str__ ########################################################################### # 'Menu' interface ########################################################################### def _id_default(self): """ Trait initializer. """ return self.name.strip('&') #### EOF ###################################################################### envisage-4.4.0/envisage/ui/action/action_set.py0000644000175000017500000000623412252661404022232 0ustar davidcdavidc00000000000000""" An action set is a collection of menus, groups, and actions. """ # Standard library imports. import logging # Enthought library imports. from traits.api import Bool, Dict, HasTraits, List, Str, implements from traits.util.camel_case import camel_case_to_words # Local imports. from action import Action from group import Group from i_action_set import IActionSet from menu import Menu from tool_bar import ToolBar # Logging. logger = logging.getLogger(__name__) class ActionSet(HasTraits): """ An action set is a collection of menus, groups, and actions. """ implements(IActionSet) # The action set's globally unique identifier. id = Str # The action set's name. # # fixme: This is not currently used, but in future it will be the name that # is shown to the user when they are customizing perspectives by adding or # removing action sets etc. name = Str # The actions in this set. actions = List(Action) # The groups in this set. groups = List(Group) # The menus in this set. menus = List(Menu) # The tool bars in this set. tool_bars = List(ToolBar) # Are the actions and menus in this set enabled (if they are disabled they # will be greyed out). Tool bars are generally not greyed out themselves, # but the actions within them are. enabled = Bool(True) # Are the actions, menus and tool bars in this set visible? visible = Bool(True) # A mapping from human-readable names to globally unique IDs. # # This mapping is used when interpreting the first item in a location path # (i.e., the **path** trait of a **Location** instance). # # When the path is intepreted, the first component (i.e., the first item # before any '/') is checked to see if it is in the mapping, and if so it # is replaced with the value in the map. # # This technique allows paths to start with human readable names, as # opposed to IDs (which are required in order to manage the namespace of # all action sets). # # For example, in the Envisage workbench, the menu bar ID is: # # ``'envisage.workbench.menubar'`` # # Without aliases, you must specify a location like this: # # ``Location(path='envisage.workbench.menubar/ASubMenu/AGroup')`` # # This is a bit long-winded! Instead, you can define an alias: # # ``aliases = { 'MenuBar' : 'envisage.workbench.menubar' }`` # # In that case, you can specify a location like this: # # ``Location(path='MenuBar/ASubMenu/AGroup')`` # aliases = Dict(Str, Str) #### Trait initializers ################################################### def _id_default(self): """ Trait initializer. """ id = '%s.%s' % (type(self).__module__, type(self).__name__) logger.warn('action set %s has no Id - using <%s>' % (self, id)) return id def _name_default(self): """ Trait initializer. """ name = camel_case_to_words(type(self).__name__) logger.warn('action set %s has no name - using <%s>' % (self, name)) return name #### EOF ###################################################################### envisage-4.4.0/envisage/ui/action/__init__.py0000644000175000017500000000000012252661404021622 0ustar davidcdavidc00000000000000envisage-4.4.0/envisage/ui/action/action_set_manager.py0000644000175000017500000000661512252661404023727 0ustar davidcdavidc00000000000000""" Manages a collection of action sets. """ # Enthought library imports. from traits.api import HasTraits, List # Local imports. from action_set import ActionSet class ActionSetManager(HasTraits): """ Manages a collection of action sets. """ #### 'ActionSetManager' interface ######################################### # The action sets that this manager manages. action_sets = List(ActionSet) ########################################################################### # 'ActionSetManager' interface. ########################################################################### def get_actions(self, root): """ Return all action definitions for a root. """ return self._get_items(self.action_sets, 'actions', root) def get_groups(self, root): """ Return all group definitions for a root. """ return self._get_items(self.action_sets, 'groups', root) def get_menus(self, root): """ Return all menu definitions for a root. """ return self._get_items(self.action_sets, 'menus', root) def get_tool_bars(self, root): """ Return all tool bar definitions for a root. """ return self._get_items(self.action_sets, 'tool_bars', root) ########################################################################### # 'Private' interface. ########################################################################### def _get_items(self, action_sets, attribute_name, root): """ Return all actions, groups or menus for a particular root. e.g. To get all of the groups:: self._get_items(action_sets, 'groups', root) """ items = [] for action_set in action_sets: for item in getattr(action_set, attribute_name): if self._get_root(item.path, action_set.aliases) == root: items.append(item) # fixme: Hacky, but the model needs to maintain the # action set that contributed the item. item._action_set_ = action_set # fixme: Even hackier if this is a menu then we need to # tag the action set onto all of the groups. if attribute_name in ['menus', 'toolbars']: for group in item.groups: group._action_set_ = action_set return items def _get_root(self, path, aliases): """ Return the effective root for a path. If the first component of the path matches an alias, then we return the value of the alias. e.g. If the aliases are:: {'MenuBar' : 'envisage.ui.workbench.menubar'} and the path is:: 'MenuBar/File/New' Then the effective root is:: 'envisage.ui.workbench.menubar' If the first component of the path does *not* match an alias, then it is returned as is. e.g. If the aliases are:: {'ToolBar' : 'envisage.ui.workbench.toolbar'} and the path is:: 'MenuBar/File/New' Then the effective root is:: 'MenuBar' """ components = path.split('/') if components[0] in aliases: root = aliases[components[0]] else: root = components[0] return root #### EOF ###################################################################### envisage-4.4.0/envisage/ui/action/tool_bar.py0000644000175000017500000000521012252661404021674 0ustar davidcdavidc00000000000000""" The *definition* of a tool bar. """ # Enthought library imports. from traits.api import Instance, List, Str # Local imports. from group import Group from location import Location # fixme: Remove duplication (in menu.py too!) class CGroup(Instance): """ A trait type for a 'Group' or anything that can be cast to a 'Group'. Currently, the only cast allowed is from string -> Group using the string as the group's ID. """ ########################################################################### # 'object' interface. ########################################################################### def __init__(self, **kw): """ Constructor. """ super(CGroup, self).__init__(klass=Group, **kw) return ########################################################################### # 'TraitType' interface. ########################################################################### def validate(self, object, name, value): """ Validate a value. """ if isinstance(value, basestring): value = Group(id=value) return super(CGroup, self).validate(object, name, value) class ToolBar(Location): """ The *definition* of a menu in a menu bar or menu. """ # The tool bars's unique identifier (unique within the multi-toolbar # that the tool bar is to be added to). id = Str # The tool bar name (appears when the tool bar is 'undocked'). name = Str # The groups in the tool bar. groups = List(CGroup) # The optional name of a class that implements the tool bar. The class must # support the **pyface.action.ToolBarManager** protocol. class_name = Str ########################################################################### # 'object' interface ########################################################################### def __str__(self): """ Return the 'informal' string representation of the object. """ return 'ToolBar(%s)' % self.name __repr__ = __str__ ########################################################################### # 'Location' interface ########################################################################### def _path_default(self): """ Trait initializer. """ return 'ToolBar' ########################################################################### # 'ToolBar' interface ########################################################################### def _id_default(self): """ Trait initializer. """ return self.name #### EOF ###################################################################### envisage-4.4.0/envisage/ui/action/group.py0000644000175000017500000000202612252661404021231 0ustar davidcdavidc00000000000000""" The *definition* of a group in a tool bar or menu. """ # Enthought library imports. from traits.api import Bool, Str # Local imports. from location import Location class Group(Location): """ The *definition* of a group in a tool bar or menu. """ # The group's unique identifier (unique within the tool bar, menu bar or # menu that the group is to be added to). id = Str # Does this group require a separator? separator = Bool(True) # The optional name of a class that implements the group. The class must # support the **pyface.action.Group** protocol. class_name = Str ########################################################################### # 'object' interface ########################################################################### def __str__(self): """ Return the 'informal' string representation of the object. """ return 'Group(%s)' % self.id __repr__ = __str__ #### EOF ###################################################################### envisage-4.4.0/envisage/package_plugin_manager.py0000644000175000017500000001145712252661404022656 0ustar davidcdavidc00000000000000""" A plugin manager that finds plugins in packages on the 'plugin_path'. """ import logging, sys from apptools.io import File from traits.api import Directory, List, on_trait_change from plugin_manager import PluginManager logger = logging.getLogger(__name__) class PackagePluginManager(PluginManager): """ A plugin manager that finds plugins in packages on the 'plugin_path'. All items in 'plugin_path' are directory names and they are all added to 'sys.path' (if not already present). Each directory is then searched for plugins as follows:- a) If the package contains a 'plugins.py' module, then we import it and look for a callable 'get_plugins' that takes no arguments and returns a list of plugins (i.e. instances that implement 'IPlugin'!). b) If the package contains any modules named in the form 'xxx_plugin.py' then the module is imported and if it contains a callable 'XXXPlugin' it is called with no arguments and it must return a single plugin. """ # Plugin manifest. PLUGIN_MANIFEST = 'plugins.py' #### 'PackagePluginManager' protocol ####################################### # A list of directories that will be searched to find plugins. plugin_path = List(Directory) @on_trait_change('plugin_path[]') def _plugin_path_changed(self, obj, trait_name, removed, added): self._update_sys_dot_path(removed, added) self.reset_traits(['_plugins']) #### Protected 'PluginManager' protocol ################################### def __plugins_default(self): """ Trait initializer. """ plugins = [ plugin for plugin in self._harvest_plugins_in_packages() if self._include_plugin(plugin.id) ] logger.debug('package plugin manager found plugins <%s>', plugins) return plugins #### Private protocol ##################################################### def _get_plugins_module(self, package_name): """ Import 'plugins.py' from the package with the given name. If the package does not exist, or does not contain 'plugins.py' then return None. """ try: module = __import__(package_name + '.plugins', fromlist=['plugins']) except ImportError: module = None return module # smell: Looooong and ugly! def _harvest_plugins_in_package(self, package_name, package_dirname): """ Harvest plugins found in the given package. """ # If the package contains a 'plugins.py' module, then we import it and # look for a callable 'get_plugins' that takes no arguments and returns # a list of plugins (i.e. instances that implement 'IPlugin'!). plugins_module = self._get_plugins_module(package_name) if plugins_module is not None: factory = getattr(plugins_module, 'get_plugins', None) if factory is not None: plugins = factory() # Otherwise, look for any modules in the form 'xxx_plugin.py' and # see if they contain a callable in the form 'XXXPlugin' and if they # do, call it with no arguments to get a plugin! else: plugins = [] logger.debug('Looking for plugins in %s' % package_dirname) for child in File(package_dirname).children or []: if child.ext == '.py' and child.name.endswith('_plugin'): module = __import__( package_name + '.' + child.name, fromlist=[child.name] ) atoms = child.name.split('_') capitalized = [atom.capitalize() for atom in atoms] factory_name = ''.join(capitalized) factory = getattr(module, factory_name, None) if factory is not None: plugins.append(factory()) return plugins def _harvest_plugins_in_packages(self): """ Harvest plugins found in packages on the plugin path. """ plugins = [] for dirname in self.plugin_path: for child in File(dirname).children or []: if child.is_package: plugins.extend( self._harvest_plugins_in_package( child.name, child.path ) ) return plugins def _update_sys_dot_path(self, removed, added): """ Add/remove the given entries from sys.path. """ for dirname in removed: if dirname in sys.path: sys.path.remove(dirname) for dirname in added: if dirname not in sys.path: sys.path.append(dirname) #### EOF ###################################################################### envisage-4.4.0/envisage/i_extension_point_user.py0000644000175000017500000000104512252661404022776 0ustar davidcdavidc00000000000000""" The interface for objects using the 'ExtensionPoint' trait type. """ # Enthought library imports. from traits.api import Instance, Interface # Local imports. from i_extension_registry import IExtensionRegistry class IExtensionPointUser(Interface): """ The interface for objects using the 'ExtensionPoint' trait type. """ # The extension registry that the object's extension points are stored in. extension_registry = Instance(IExtensionRegistry) #### EOF ###################################################################### envisage-4.4.0/envisage/developer/0000755000175000017500000000000012252662115017616 5ustar davidcdavidc00000000000000envisage-4.4.0/envisage/developer/code_browser/0000755000175000017500000000000012252662115022273 5ustar davidcdavidc00000000000000envisage-4.4.0/envisage/developer/code_browser/package.py0000644000175000017500000000366412252661404024251 0ustar davidcdavidc00000000000000""" Classes used to represent packages. """ # Standard library imports. from os.path import join # Enthought library imports. from apptools.io.api import File from traits.api import Instance, List, Str # Local imports. from namespace import Namespace class Package(Namespace): """ A package. """ #### 'Package' interface ################################################## # The namespace that the package is defined in. # # fixme: This is always None for packages so why should we have this trait! # If I remember correctly it is something to do with trait identification! namespace = Instance(Namespace) # The package contents (modules and sub-packages). contents = List # The absolute filename of the package. filename = Str # The package name. name = Str # The package's parent package (None if this is a top level package). parent = Instance('Package') ########################################################################### # 'Package' interface. ########################################################################### def create_sub_package(self, name): """ Creates a sub-package with the specified name. """ sub_package = File(join(self.filename, name)) sub_package.create_package() return def delete_sub_package(self, name): """ Deletes the sub-package with the specified name. """ sub_package = File(join(self.filename, name)) sub_package.delete() return ########################################################################### # Private interface ########################################################################### def _name_changed(self, name): """ Called when the package name has been changed. """ ##print '**** Package name changed', self.name return #### EOF ###################################################################### envisage-4.4.0/envisage/developer/code_browser/code_browser.py0000644000175000017500000001552712252661404025334 0ustar davidcdavidc00000000000000""" A simple traits-aware code browser. """ # Standard library imports. import cPickle, imp, logging, os, stat, warnings # Enthought library imports. from apptools.io.api import File from traits.api import Any, Bool, Event, HasTraits, Str # Local imports. from module import ModuleFactory from package import Package # Logging. logger = logging.getLogger(__name__) # Filter future warnings for maxint, since it causes warnings when compiling # anything that has RGBA colors defined as hex values. warnings.filterwarnings( "ignore", r'hex/oct constants > sys\.maxint .*', FutureWarning ) class CodeBrowser(HasTraits): """ A simple, traits-aware code browser. """ #### 'ClassBrowser' interface ############################################# # The filename of the (optional, persistent) code 'database'. filename = Str #### Events #### # Fired when a module is about to be parsed. parsing_module = Event # Fired when a module has been parsed. parsed_module = Event #### Private interface #################################################### # The code 'database' that contains every module that has been parsed. _database = Any # Has the code database been changed (i.e., do we need to save it)? _database_changed = Bool(False) ########################################################################### # 'CodeBrowser' interface. ########################################################################### def load(self): """ Load the code browser 'database'. """ # If a persisted code database exists then load it... if os.path.isfile(self.filename): logger.debug('loading code database...') f = file(self.filename, 'rb') self._database = cPickle.load(f) f.close() logger.debug('code database loaded.') # ... otherwise we have a nice, fresh and very empty database. else: self._database = {} return def save(self): """ Save the code browser 'database' to disk. """ if self._database_changed: logger.debug('saving code database...') f = file(self.filename, 'wb') cPickle.dump(self._database, f, 1) f.close() self._database_changed = False logger.debug('code database saved.') else: logger.debug('code database unchanged - nothing saved.') return def read_package(self, package_name): """ Parse every module in the specified package. """ filename = self.find_module(package_name) if filename is None: raise ValueError("no such package '%s'" % package_name) package = Package(filename=filename, name=package_name) self.read_directory(filename, package) return package def read_directory(self, filename, package=None): """ Parse every module in the specified directory. """ directory = File(filename) if not directory.is_folder: raise ValueError("%s is NOT a directory." % filename) if package is not None: contents = package.contents else: contents = [] for child in directory.children: # If the child is a Python file then parse it. if child.ext == '.py': contents.append(self.read_file(child.path, package)) # If the child is a sub-package then recurse! elif child.is_package: if package is not None: sub_package_name = '%s.%s' % (package.name, child.name) sub_package = Package( filename = child.path, name = sub_package_name, parent = package ) else: sub_package = Package(filename=child.path, name=child.name) self.read_directory(child.path, sub_package) contents.append(sub_package) return contents def read_file(self, filename, namespace=None): """ Parse a file. """ # Only parse the file if we haven't parsed it before or it has been # modified since we last parsed it! module, mod_time = self._database.get(filename, (None, None)) if module is None or mod_time != os.stat(filename)[stat.ST_MTIME]: # Event notification. self.parsing_module = filename logger.debug('parsing module %s' % filename) module_factory = ModuleFactory() try: module = module_factory.from_file(filename, namespace) # Event notification. self.parsed_module = module logger.debug('parsed module %s' % filename) # Add the parsed module to the database. self._database[filename] = ( module, os.stat(filename)[stat.ST_MTIME] ) self._database_changed = True except 'ddd': logger.debug('error parsing module %s' % filename) return module ## def read_module(self, module_name): ## """ Parses a module. """ ## filename = self.find_module(module_name) ## if filename is not None: ## module = self.read_file(filename) ## else: ## module = None ## return module def find_module(self, module_name, path=None): """ Return the filename for the specified module. """ components = module_name.split('.') try: # Look up the first component of the module name (of course it # could be the *only* component). f, filename, description = imp.find_module(components[0], path) # If the module is in a package then go down each level in the # package hierarchy in turn. if len(components) > 0: for component in components[1:]: f, filename, description = imp.find_module( component, [filename] ) except ImportError: filename = None return filename ########################################################################### # Private interface. ########################################################################### #### Trait initializers ################################################### def __database_default(self): """ Trait initializer. """ return {} #### Trait change handlers ################################################ def _filename_changed(self): """ Called when the filename of the code database is changed. """ # Load the contents of the database. self.load() return #### EOF ###################################################################### envisage-4.4.0/envisage/developer/code_browser/api.py0000644000175000017500000000025312252661404023416 0ustar davidcdavidc00000000000000from assign import Assign from code_browser import CodeBrowser from function import Function from klass import Klass from module import Module from package import Package envisage-4.4.0/envisage/developer/code_browser/enclbr.py0000644000175000017500000001031512252661404024112 0ustar davidcdavidc00000000000000""" The Enthought class browser module. """ # Standard library imports. import cPickle, imp, logging, os, stat, warnings # Enthought library imports. from apptools.io.api import File # Local imports. from module import ModuleFactory from package import Package # Logging. logger = logging.getLogger(__name__) # Filter future warnings for maxint, since it causes warnings when compiling # anything that has RGBA colors defined as hex values. warnings.filterwarnings( "ignore", r'hex/oct constants > sys\.maxint .*', FutureWarning ) # A cache that contains every module that has been parsed. MODULES = {} # Has the cache been modified in this process? MODULES_CHANGED = False def save_cache(filename): """ Saves the module cache to disk. """ global MODULES global MODULES_CHANGED if MODULES_CHANGED: logger.debug('saving cache...') f = file(filename, 'wb') cPickle.dump(MODULES, f, 1) f.close() logger.debug('cache saved') return def load_cache(filename): """ Loads the module cache from disk. """ global MODULES if os.path.isfile(filename): logger.debug('loading cache...') f = file(filename, 'rb') MODULES = cPickle.load(f) f.close() logger.debug('cache loaded...') else: MODULES = {} return def read_package(package_name): """ Parse every module in the specified package. """ filename = find_module(package_name) if filename is None: raise ValueError("no such package '%s'" % package_name) package = Package(filename=filename, name=package_name) read_directory(filename, package) return package def read_directory(filename, package=None): """ Parse every module in the specified directory. """ directory = File(filename) if not directory.is_folder: raise ValueError("%s is NOT a directory." % filename) if package is not None: contents = package.contents else: contents = [] for child in directory.children: if child.ext == '.py': contents.append(read_file(child.path, package)) elif child.is_package: if package is not None: sub_package_name = '%s.%s' % (package.name, child.name) sub_package = Package( filename=child.path, name=sub_package_name, parent=package ) else: sub_package = Package(filename=child.path, name=child.name) read_directory(child.path, sub_package) contents.append(sub_package) return contents def read_file(filename, namespace=None): """ Parses a file. """ global MODULES global MODULES_CHANGED module, mod_time = MODULES.get(filename, (None, None)) if module is None or mod_time != os.stat(filename)[stat.ST_MTIME]: logger.debug('parsing module %s' % filename) module_factory = ModuleFactory() try: module = module_factory.from_file(filename, namespace) # Add the parsed module to the cache. MODULES[filename] = (module, os.stat(filename)[stat.ST_MTIME]) MODULES_CHANGED = True except: logger.exception('error parsing file %s' % filename) return module def read_module(module_name): """ Parses a module. """ filename = find_module(module_name) if filename is not None: module = read_file(filename) else: module = None return module def find_module(module_name, path=None): """ Returns the filename for the specified module. """ components = module_name.split('.') try: # Look up the first component of the module name (of course it could be # the *only* component). f, filename, description = imp.find_module(components[0], path) # If the module is in a package then go down each level in the package # hierarchy in turn. if len(components) > 0: for component in components[1:]: f,filename,description = imp.find_module(component, [filename]) except ImportError: filename = None return filename #### EOF ###################################################################### envisage-4.4.0/envisage/developer/code_browser/namespace.py0000644000175000017500000001103012252661404024574 0ustar davidcdavidc00000000000000""" A Python namespace. """ # Standard library imports. import os, sys # Enthought library imports. from traits.api import Dict, HasTraits class Namespace(HasTraits): """ A Python namespace. """ #### 'Namespace' interface ################################################ # All of the names defined within the module. locals = Dict # All of the names imported into the module. imports = Dict # fixme: This is a dict from name -> bool indicating whether a name in # locals is a trait or not. Should locals be a dict containing a tuple? _is_trait = Dict ########################################################################### # 'Namespace' interface. ########################################################################### def is_trait(self, name): """ Attempt to resolve a name to see if it is a trait. """ # fixme: We might want to be a bit cleverer than this ;^) return True # Try the namespace's locals first. if name in self.locals: # These are the definitive traits markers! if name in ['TraitFactory', 'Trait', 'Instance']: is_trait = True # The name won't be in the '_is_trait' dictionary if it was not # defined by an assignment i.e., it is a class or function! elif not self._is_trait.get(name, False): is_trait = False else: next = self.locals[name] if len(next.source) > 0: is_trait = self.is_trait(next.source) else: is_trait = False # Is the name imported? elif self.is_imported(name): module_name = self.get_next_module_name(name) module = self.get_next_module(module_name) if module is not None: components = name.split('.') is_trait = module.is_trait(components[-1]) else: is_trait = False # If we have a parent namespace then try that. elif self.namespace is not None: is_trait = self.namespace.is_trait(name) # Otherwise give up! else: is_trait = False return is_trait def is_imported(self, name): """ Returns TRUE if a name is imported. """ components = name.split('.') return components[0] in self.imports def get_next_module_name(self, symbol): """ Returns the name of the module that a symbol was imported from. """ components = symbol.split('.') if len(components) == 1: module_name = self.imports[symbol] else: path = [] # The first component MUST have been imported. # # An empty string means 'import' as opposed to 'from' used. value = self.imports[components[0]] if len(value) > 0: path.append(value) module_name = '.'.join(path) return module_name path.append(components[0]) for component in components[1:-1]: path.append(component) module_name = '.'.join(path) return module_name def get_next_module(self, module_name): """ Returns a parsed module with the specified name. Returns None if the module cannot be found or is 'ignored'. We ignore all modules in the Python core, extension modules etc. """ # fixme: Circular imports! from enclbr import find_module, read_file # Try to find the module that the symbol came from. dirname = os.path.dirname(self.filename) filename = find_module(module_name, [dirname] + sys.path) if filename is not None: # If the filename refers to a directory then it must be a # package. if os.path.isdir(filename): filename = os.path.join(filename, '__init__.py') # fixme: We should probably put in some more formal filter # mechanism here! # # Ignore extension modules. if filename.endswith('.pyd') or filename.endswith('.so'): module = None # Ignore standard modules. elif filename.startswith(sys.prefix): module = None # Parse it! else: module = read_file(filename) else: module = None return module #### EOF ###################################################################### envisage-4.4.0/envisage/developer/code_browser/assign.py0000644000175000017500000001026412252661404024134 0ustar davidcdavidc00000000000000""" Classes used to represent assignment statements. """ # Standard library imports. import compiler from compiler.visitor import ASTVisitor # Enthought library imports. from traits.api import Any, Bool, HasTraits, Instance, Int, List, Str class Assign(HasTraits): """ An assignment statement. """ #### 'Assign' interface ################################################### # The namespace that the assignment statement is in. namespace = Instance( 'envisage.developer.code_browser.namespace.Namespace' ) # The line number within the module at which the assignment statement # appears. lineno = Int # The names being assigned to (in Python there can be more than one). targets = List(Str) # The expression being assigned to the targets (an AST node). expr = Any # We only care about assignments to trait types, not literals or # expressions etc. source = Str # Is this a trait assignment? is_trait = Bool(False) ########################################################################### # 'object' interface. ########################################################################### def __str__(self): """ Returns an informal string representation of the object. """ return 'Assign %s at %d' % (', '.join(self.targets), self.lineno) class AssignFactory(HasTraits): """ A factory for assignment statements. """ ########################################################################### # 'AssignFactory' interface. ########################################################################### def from_ast(self, namespace, node): """ Creates an assignment statement from an AST node. """ # Create a new assignment statement. assign = Assign( namespace = namespace, lineno = node.lineno, expr = node.expr ) # Walk the AST picking out the things we care about! compiler.walk(node, AssignVisitor(assign)) return assign class AssignVisitor(ASTVisitor): """ An AST visitor for assigment statements. """ ########################################################################### # 'object' interface. ########################################################################### def __init__(self, assign): """ Creates a new visitor. """ self.assign = assign return ########################################################################### # 'ASTVisitor' interface. ########################################################################### def visitAssName(self, node): """ Visits an assignment node. """ self.assign.targets.append(node.name) return def visitCallFunc(self, node): """ Visits a function call node. """ function_name = self._get_name(node.node) self.assign.source = function_name return def visitName(self, node): """ Visits a name node. """ self.assign.source = node.name return def visitGetattr(self, node): """ Visits a getattr node. """ self.assign.source = self._get_name(node) return ########################################################################### # Private interface. ########################################################################### def _get_name(self, node): """ Returns the (possibly dotted) name from a node. """ # fixme: Work out when node can be none here!!!!! I think it is safe # to ignore it in terms of working out what is a trait, but it would # be nice to know what is going on ;^) if node is not None: if isinstance(node, basestring): name = node elif not hasattr(node, 'getType'): name = '' elif node.getType() == compiler.ast.Name: name = node.name else: names = [self._get_name(child) for child in node.getChildren()] name = '.'.join(names) else: name = '' return name #### EOF ###################################################################### envisage-4.4.0/envisage/developer/code_browser/module.py0000644000175000017500000001517512252661404024143 0ustar davidcdavidc00000000000000""" Classes used to represent modules. """ # Standard library imports. import compiler, os from compiler.visitor import ASTVisitor # Enthought library imports. from apptools.io.api import File from traits.api import Any, Dict, HasTraits, Instance, Str # Local imports. from assign import Assign, AssignFactory from function import Function, FunctionFactory from klass import Klass, KlassFactory from namespace import Namespace class Module(Namespace): """ A module. """ #### 'Module' interface ################################################### # The namespace that the module is defined in. If the module is in a # package then this will be the appropriate 'Package' instance, otherwise # it will be None. namespace = Instance(Namespace) # The absolute filename of the module. filename = Str # The name of the module (this is currently the fully qualified name i.e., # it includes the names of the packages that the module is contained in). name = Str # The module's doc-string (None if there is no doc string, a string if # there is). doc = Any # The module-level attributes. attributes = Dict # The classes defined in the module. klasses = Dict # The module-level functions. functions = Dict ########################################################################### # 'object' interface. ########################################################################### def __str__(self): """ Returns an informal string representation of the object. """ return 'Module %s' % self.filename def __getstate__(self): """ Returns the state of the object for pickling. """ state = {} state['namespace'] = self.namespace state['filename'] = self.filename state['name'] = self.name state['doc'] = self.doc state['attributes'] = self.attributes state['klasses'] = self.klasses state['functions'] = self.functions return state def __setstate__(self, state): """ Sets the state object duting unpickling. """ self.namespace = state['namespace'] self.filename = state['filename'] self.name = state['name'] self.doc = state['doc'] self.attributes = state['attributes'] self.klasses = state['klasses'] self.functions = state['functions'] return class ModuleFactory(HasTraits): """ A factory for modules. """ ########################################################################### # 'ModuleFactory' interface. ########################################################################### def from_file(self, filename, namespace=None): """ Creates a module by parsing a file. """ # Parse the file. node = compiler.parseFile(filename) # Create a new module. module = Module( namespace = namespace, filename = filename, name = self._get_module_name(filename), doc = node.doc, ) # Walk the AST picking out the things we care about! compiler.walk(node, ModuleVisitor(module)) return module ########################################################################### # Private interface. ########################################################################### def _get_module_name(self, filename): """ Get the fully qualified module name for a filename. e.g. if the filename is:- '/enthought/envisage/core/core_plugin_definition.py' we would return:- 'envisage.core.core_plugin_definition' """ # Get the name of the module minus the '.py' module, ext = os.path.splitext(os.path.basename(filename)) # Start with the actual module name. module_path = [module] # If the directory is a Python package then add it to the module path. parent = File(os.path.dirname(filename)) while parent.is_package: module_path.insert(0, parent.name) parent = parent.parent return '.'.join(module_path) class ModuleVisitor(ASTVisitor): """ An AST visitor for top-level modules. """ ########################################################################### # 'object' interface. ########################################################################### def __init__(self, module): """ Creates a new visitor. """ self.module = module # Factories used to create klasses, functions and assignments from # AST nodes. self._klass_factory = KlassFactory() self._function_factory = FunctionFactory() self._assign_factory = AssignFactory() return ########################################################################### # 'ASTVisitor' interface. ########################################################################### def visitClass(self, node): """ Visits a class node. """ klass = self._klass_factory.from_ast(self.module, node) self.module.locals[node.name] = klass self.module.klasses[node.name] = klass return def visitFunction(self, node): """ Visits a function node. """ function = self._function_factory.from_ast(self.module, node) self.module.locals[node.name] = function self.module.functions[node.name] = function return def visitAssign(self, node): """ Visits an assignment node. """ assign = self._assign_factory.from_ast(self.module, node) # Does the assigment look like it *might* be a trait? (i.e., it is NOT # an expression or a constant etc.). if len(assign.source) > 0: assign.is_trait = self.module.is_trait(assign.source) else: assign.is_trait = False for target in assign.targets: self.module.locals[target] = assign self.module.attributes[target] = assign self.module._is_trait[target] = assign.is_trait return def visitFrom(self, node): """ Visits a from node. """ for name, what in node.names: self.module.imports[name] = node.modname return def visitImport(self, node): """ Visits an import node. """ for name, what in node.names: # fixme: We currently use the fact that we add an empty string to # the imports dictionary to tell the difference later on between # 'import' and 'from import'. self.module.imports[name] = '' return #### EOF ###################################################################### envisage-4.4.0/envisage/developer/code_browser/klass.py0000644000175000017500000001272412252661404023770 0ustar davidcdavidc00000000000000""" Classes used to represent classes. """ # Standard library imports. import compiler from compiler.visitor import ASTVisitor # Enthought library imports. from traits.api import Any, Dict, HasTraits, Instance, Int, List from traits.api import Property, Str # Local imports. from assign import AssignFactory from function import FunctionFactory from namespace import Namespace class Klass(Namespace): """ A class. """ #### 'Klass' interface #################################################### # The namespace that the class is defined in (this is *usually* a module, # but of course classes can be defined anywhere in Python). namespace = Instance(Namespace) # The line number in the module at which the class appears. lineno = Int # The name of the class. name = Str # The class' doc-string (None if there is no doc string, a string if there # is). doc = Any # The class' base classes. bases = List(Str) # The class' attributes. attributes = Dict # The class' traits. traits = Dict # The class' methods. methods = Dict ########################################################################### # 'object' interface. ########################################################################### def __str__(self): """ Returns an informal string representation of the object. """ return 'Klass %s at %d' % (self.name, self.lineno) def __getstate__(self): """ Returns the state of the object for pickling. """ state = {} state['namespace'] = self.namespace state['lineno'] = self.lineno state['name'] = self.name state['doc'] = self.doc state['bases'] = self.bases state['attributes'] = self.attributes state['traits'] = self.traits state['methods'] = self.methods return state def __setstate__(self, state): """ Sets the state object duting unpickling. """ self.namespace = state['namespace'] self.lineno = state['lineno'] self.name = state['name'] self.doc = state['doc'] self.bases = state['bases'] self.attributes = state['attributes'] self.traits = state['traits'] self.methods = state['methods'] return class KlassFactory(HasTraits): """ A factory for classes. """ ########################################################################### # 'KlassFactory' interface. ########################################################################### def from_ast(self, namespace, node): """ Creates a class from an AST node. """ # Create a new class. klass = Klass( namespace = namespace, lineno = node.lineno, name = node.name, doc = node.doc, bases = [self._get_name(base) for base in node.bases] ) # Walk the AST picking out the things we care about! compiler.walk(node, KlassVisitor(klass)) return klass ########################################################################### # Private interface. ########################################################################### # fixme: This same method is used in 'AssignVisitor'. def _get_name(self, node): """ Returns the (possibly dotted) name from a node. """ if isinstance(node, basestring): name = node elif hasattr(node, 'getType') and node.getType() == compiler.ast.Name: name = node.name else: names = [self._get_name(child) for child in node.getChildren()] name = '.'.join(names) return name class KlassVisitor(ASTVisitor): """ An AST visitor for classes. """ ########################################################################### # 'object' interface. ########################################################################### def __init__(self, klass): """ Creates a new visitor. """ self.klass = klass # Factories used to create klasses, functions and assignments from # AST nodes. self._function_factory = FunctionFactory() self._assign_factory = AssignFactory() return ########################################################################### # 'ASTVisitor' interface. ########################################################################### def visitFunction(self, node): """ Visits a function node. """ function = self._function_factory.from_ast(self.klass, node) self.klass.locals[node.name] = function self.klass.methods[node.name] = function return def visitAssign(self, node): """ Visits an assignment node. """ assign = self._assign_factory.from_ast(self.klass, node) # Does the assigment look like it *might* be a trait? (i.e., it is NOT # an expression or a constant etc.). if len(assign.source) > 0: assign.is_trait = self.klass.is_trait(assign.source) else: assign.is_trait = False for target in assign.targets: self.klass.locals[target] = assign self.klass._is_trait[target] = assign.is_trait if assign.is_trait: self.klass.traits[target] = assign else: self.klass.attributes[target] = assign return #### EOF ###################################################################### envisage-4.4.0/envisage/developer/code_browser/__init__.py0000644000175000017500000000000012252661404024372 0ustar davidcdavidc00000000000000envisage-4.4.0/envisage/developer/code_browser/function.py0000644000175000017500000000323212252661404024472 0ustar davidcdavidc00000000000000""" Classes used to represent functions and methods. """ # Enthought library imports. from traits.api import Any, HasTraits, Instance, Int, Str # Local imports. from namespace import Namespace class Function(Namespace): """ A function. """ #### 'Function' interface ################################################# # The namespace that the function is defined in. namespace = Instance(Namespace) # The line number in the module at which the function appears. lineno = Int # The name of the function. name = Str # The function's doc-string (None if there is no doc string, a string if # there is). doc = Any ########################################################################### # 'object' interface. ########################################################################### def __str__(self): """ Returns an informal string representation of the object. """ return 'Function %s at %d' % (self.name, self.lineno) class FunctionFactory(HasTraits): """ A factory for classes. """ ########################################################################### # 'FunctionFactory' interface. ########################################################################### def from_ast(self, namespace, node): """ Creates a class from an AST node. """ # Create a new function. function = Function( namespace = namespace, lineno = node.lineno, name = node.name, doc = node.doc ) return function #### EOF ###################################################################### envisage-4.4.0/envisage/developer/code_browser/example.py0000644000175000017500000000167612252661404024312 0ustar davidcdavidc00000000000000""" An example of using the Enthought class browser (enclbr) module. """ # Standard library imports. import sys, time # Enthought library imports. from envisage.developer.code_browser.api import CodeBrowser def main(argv): """ Do it! """ if len(argv) != 2: print 'Usage: python example.py module_name' print print 'e.g., python example.py traits' else: # Create a code browser. code_browser = CodeBrowser(filename='data.pickle') # Parse the specified package. start = time.time() contents = code_browser.read_package(sys.argv[1]) stop = time.time() print 'Time taken to parse', sys.argv[1], 'was', stop - start, 'secs' # Save the cache. code_browser.save('data.pickle') return # Entry point for testing. if __name__ == '__main__': main(sys.argv) #### EOF ###################################################################### envisage-4.4.0/envisage/developer/ui/0000755000175000017500000000000012252662115020233 5ustar davidcdavidc00000000000000envisage-4.4.0/envisage/developer/ui/api.py0000644000175000017500000000005612252661404021357 0ustar davidcdavidc00000000000000from view.plugin_browser import browse_plugin envisage-4.4.0/envisage/developer/ui/perspective/0000755000175000017500000000000012252662115022564 5ustar davidcdavidc00000000000000envisage-4.4.0/envisage/developer/ui/perspective/api.py0000644000175000017500000000006712252661404023712 0ustar davidcdavidc00000000000000from developer_perspective import DeveloperPerspective envisage-4.4.0/envisage/developer/ui/perspective/developer_perspective.py0000644000175000017500000000273412252661404027542 0ustar davidcdavidc00000000000000""" The Developer perspective. """ # Enthought library imports. from pyface.workbench.api import Perspective, PerspectiveItem class DeveloperPerspective(Perspective): """ The Developer perspective. This perspective is intented to contain views and editors useful for inspecting and debugging a running Envisage application. """ # The root of all view Ids in this package. ROOT = 'envisage.developer.ui.view' # View Ids. APPLICATION_BROWSER_VIEW = ROOT + '.application_browser_view' EXTENSION_REGISTRY_BROWSER_VIEW = ROOT + '.extension_registry_browser_view' SERVICE_REGISTRY_BROWSER_VIEW = ROOT + '.service_registry_browser_view' # The perspective's name. name = 'Developer' # Should the editor area be shown in this perspective? show_editor_area = True # The contents of the perspective. contents = [ PerspectiveItem( id = APPLICATION_BROWSER_VIEW, position = 'left' ), PerspectiveItem( id = EXTENSION_REGISTRY_BROWSER_VIEW, position = 'bottom', relative_to = APPLICATION_BROWSER_VIEW ), PerspectiveItem( id = 'Python', position = 'bottom', ), PerspectiveItem( id = SERVICE_REGISTRY_BROWSER_VIEW, position = 'right', ), ] #### EOF ###################################################################### envisage-4.4.0/envisage/developer/ui/perspective/__init__.py0000644000175000017500000000000012252661404024663 0ustar davidcdavidc00000000000000envisage-4.4.0/envisage/developer/ui/__init__.py0000644000175000017500000000000012252661404022332 0ustar davidcdavidc00000000000000envisage-4.4.0/envisage/developer/ui/view/0000755000175000017500000000000012252662115021205 5ustar davidcdavidc00000000000000envisage-4.4.0/envisage/developer/ui/view/service_registry_browser_view.py0000644000175000017500000000400512252661404027743 0ustar davidcdavidc00000000000000""" A view that allows a developer to browse the service registry. """ # Enthought library imports. from pyface.workbench.api import TraitsUIView from traits.api import Instance # The code browser protocol. CODE_BROWSER = 'envisage.developer.code_browser.api.CodeBrowser' class ServiceRegistryBrowserView(TraitsUIView): """ A view that allows a developer to browse the service registry. """ #### 'IWorkbenchPart' interface ########################################### # The part's globally unique identifier. id = 'envisage.developer.ui.view.service_registry_browser_view' # The part's name (displayed to the user). name = 'Services' #### 'IView' interface #################################################### # The category that the view belongs to (this can used to group views when # they are displayed to the user). category = 'Developer' #### 'ExtensionRegistryBrowserView' interface ############################# # The code browser used to parse Python code. code_browser = Instance(CODE_BROWSER) ########################################################################### # 'TraitsUIView' interface. ########################################################################### def _obj_default(self): """ Trait initializer. """ # Local imports. from service_registry_browser import ServiceRegistryBrowser service_registry_browser = ServiceRegistryBrowser( application = self.window.application, code_browser = self.code_browser ) return service_registry_browser ########################################################################### # 'ServiceRegistryBrowserView' interface. ########################################################################### def _code_browser_default(self): """ Trait initializer. """ return self.window.application.get_service(CODE_BROWSER) #### EOF ###################################################################### envisage-4.4.0/envisage/developer/ui/view/extension_registry_browser_tree.py0000644000175000017500000000744212252661404030314 0ustar davidcdavidc00000000000000""" The nodes used in the extension registry browser tree. """ # Enthought library imports. from envisage.api import IExtensionPoint, IExtensionRegistry from traits.api import Undefined from traitsui.api import TreeNode # fixme: non-api imports. from traitsui.value_tree import SingleValueTreeNodeObject from traitsui.value_tree import value_tree_nodes class IExtensionRegistryTreeNode(TreeNode): """ A tree node for an extension registry. """ ########################################################################### # 'TreeNode' interface. ########################################################################### def allows_children(self, obj): """ Return True if this object allows children. """ return True def get_children(self, obj): """ Get the object's children. """ extension_points = obj.get_extension_points() # Tag the extension registry onto the extension points. for extension_point in extension_points: extension_point.__extension_registry__ = obj return extension_points def is_node_for(self, obj): """ Return whether this is the node that handles a specified object. """ return IExtensionRegistry(obj, Undefined) is obj class IExtensionPointTreeNode(TreeNode): """ A tree node for an extension point. """ ########################################################################### # 'TreeNode' interface. ########################################################################### def allows_children(self, obj): """ Return True if this object allows children. """ return True def get_children(self, obj): """ Get the object's children. """ # fixme: This could be uglier, but I can't work out how ;^) index = 0 children = [] for extension in obj.__extension_registry__.get_extensions(obj.id): parent = SingleValueTreeNodeObject(value=obj, _index=index) children.append(parent.node_for('', extension)) index += 1 return children def get_label(self, obj): """ Get the object's label. """ return obj.id def is_node_for(self, obj): """ Return whether this is the node that handles a specified object. """ return IExtensionPoint(obj, Undefined) is obj # We override the following methods because 'ExtensionPoint' instances # are trait *types* and hence do not actually have traits themselves (i.e. # they do not inherit from 'HasTraits'). The default implementations of # these methods in 'TreeNode' attempt to call 'on_trait_change' to hook # up the listenrs, but obviously, if they don't have traits they don't have # 'on_trait_change' either ;^) # # fixme: If we make this node readonly will these go away?!? def when_label_changed(self, obj, callback, remove): """ Set up or remove listeners for label changes. """ return def when_children_replaced(self, obj, callback, remove): """ Set up or remove listeners for children being replaced. """ return def when_children_changed(self, obj, callback, remove): """ Set up or remove listenrs for children being changed. """ return extension_registry_browser_tree_nodes = [ IExtensionRegistryTreeNode( auto_open = True, label = '=Extension Points', rename = False, copy = False, delete = False, insert = False, menu = None, ), IExtensionPointTreeNode( rename = False, copy = False, delete = False, insert = False, menu = None, ), ] + value_tree_nodes #### EOF ###################################################################### envisage-4.4.0/envisage/developer/ui/view/api.py0000644000175000017500000000031212252661404022324 0ustar davidcdavidc00000000000000from application_browser_view import ApplicationBrowserView from extension_registry_browser_view import ExtensionRegistryBrowserView from service_registry_browser_view import ServiceRegistryBrowserView envisage-4.4.0/envisage/developer/ui/view/plugin_browser.py0000644000175000017500000001576012252661404024631 0ustar davidcdavidc00000000000000""" A UI for browsing a plugin. """ # Enthought library imports. from envisage.api import ExtensionPoint, IPlugin from traits.api import Delegate, HasTraits, Instance, List, Property from traits.api import Code, Str from traitsui.api import Item, TableEditor, View, VGroup from traitsui.table_column import ObjectColumn # fixme: non-api! class ExtensionPointModel(Hastraits): """ A model for browsing an extension point. """ # The plugin that offered the extension point. plugin = Instance(IPlugin) # The extension point. extension_point = Instance(IExtensionPoint) #### 'ExtensionPointModel' interface ###################################### class ExtensionModel(Hastraits): """ A model for browsing a contribution to an extension point. """ # The plugin that made the contribution. plugin = Instance(IPlugin) #### 'ContributionModel' interface ######################################## # The Id of the extension point that the contribution is for. extension_point_id = Str # The contributions to the extension point. contributions = List ########################################################################### # 'ApplicationModel' interface. ########################################################################### #### Trait initializers ################################################### def _contributions_default(self): """ Trait initializer. """ return plugin.application.get_extensions(self.extension_point_id) class PluginModel(HasTraits): """ A model for browsing a plugin. """ # The plugin that we are a model for. plugin = Instance(IPlugin) #### 'PluginModel' interface ############################################## # Models for each of the plugin's extension points. extension_point_models = List(ExtensionPointModel) # Models for each of the plugin's contributions to extension points. extension_models = List(ExtensionModel) ########################################################################### # 'ApplicationModel' interface. ########################################################################### #### Trait initializers ################################################### def _extension_models_default(self): """ Trait initializer. """ extension_models = [ ExtensionModel( plugin = plugin, extension_point_id = extension_point.id ) for extension_point in plugin.get_extension_points() ] return extension_models def _extension_point_models_default(self): """ Trait initializer. """ extension_point_models = [ ExtensionPointModel( plugin = plugin, extension_point = extension_point ) for extension_point in plugin.get_extension_points() ] return extension_point_models class ApplicationModel(HasTraits): """ A model for browsing an application. """ # The application that we are a model for. application = Instance(IApplication) #### 'ApplicationModel' interface ######################################### # Models for each of the application's plugins. plugin_models = List(PluginModel) ########################################################################### # 'ApplicationModel' interface. ########################################################################### #### Trait initializers ################################################### def _plugin_models_default(self): """ Trait initializer. """ return [PluginModel(plugin=plugin) for plugin in self.application] extension_point_table_editor = TableEditor( columns = [ ObjectColumn(name='id'), #ObjectColumn(name='desc') ], # selected = 'extension_point_selected', editable = True, edit_view = View(Item(name='desc', show_label=False), style='custom') ) plugin_browser_view = View( VGroup( Item(name='id'), Item(name='name'), label='General' ), VGroup( Item( name = 'extension_points', editor = extension_point_table_editor, show_label = False ), label='Extension Points', ), width = .8, height = .6 ) class ExtensionPointBrowser(HasTraits): """ The model used to view extension points. This browser is required because 'ExatenionPoint' instances are trait *types* and therefore do not have traits themselves and so this class is really just a 'traitified' wrapper. """ #### 'ExtensionPointBrowser' interface #################################### # The extension point that we are browsing. extension_point = Instance(ExtensionPoint) # The extension point's globally unique Id. id = Str # The extension point's description. desc = Code#Str ########################################################################### # 'ExtensionPoint' browser interface. ########################################################################### def _id_default(self): """ Trait initializer. """ return self.extension_point.id def _desc_default(self): """ Trait initializer. """ desc = self.extension_point.desc.strip() lines = [line.replace(' ', '', 2) for line in desc.splitlines()] return '\n'.join(lines) # Convenience trait to delegate an attribute to a plugin. DelegatedToPlugin = Delegate('plugin', modify=True) class PluginBrowser(HasTraits): """ A plugin browser. Actually, this class exists just because to use a trait editor we have to have a trait to edit! """ #### 'IPlugin' interface ################################################## # The plugins Id. id = DelegatedToPlugin # The plugin name. name = DelegatedToPlugin #### 'PluginBrowser' interface ############################################ # The extension points offered by the plugin. extension_points = Property(List) # The plugin that we are browsing. plugin = Instance(IPlugin) # The default traits UI view. traits_view = plugin_browser_view ########################################################################### # 'PluginBrowser' interface. ########################################################################### def _get_extension_points(self): """ Property getter. """ extension_points = [ ExtensionPointBrowser(extension_point=extension_point) for extension_point in self.plugin.extension_points ] return extension_points def browse_plugin(plugin): """ Browse a plugin. """ import inspect if inspect.isclass(plugin): plugin = plugin() plugin_browser = PluginBrowser(plugin=plugin) plugin_browser.configure_traits(view=plugin_browser_view) return #### EOF ###################################################################### envisage-4.4.0/envisage/developer/ui/view/service_registry_browser_tree.py0000644000175000017500000000730412252661404027735 0ustar davidcdavidc00000000000000""" The nodes used in the service registry browser tree. """ # Enthought library imports. from envisage.api import IExtensionPoint, IServiceRegistry from traits.api import Any, Dict, HasTraits, Instance, Int, List from traits.api import Property, Str, Undefined from traitsui.api import TreeNode # fixme: non-api imports. from traitsui.value_tree import SingleValueTreeNodeObject from traitsui.value_tree import value_tree_nodes class ServiceModel(HasTraits): """ A model of a registered service. """ # The service Id. id = Int # The service object. obj = Any # The service properties. properties = Dict class ProtocolModel(HasTraits): """ A model of all of the services of a single protocol. """ # The protocol (i.e. type) name. name = Str # All of the services offered for the protocol. services = List(ServiceModel) class ServiceRegistryModel(HasTraits): """ A model of a service registry. This model groups services by protocol. """ # The service registry that we are a model of. service_registry = Instance(IServiceRegistry) # The protocols contained in the registry. protocols = Property(List(ProtocolModel)) ########################################################################### # 'ServiceRegistryModel' interface. ########################################################################### def _get_protocols(self): """ Trait property getter. """ # fixme: Reaching into service registry. Not only is it ugly, but it # only works for the default implementation. Need to make this kind # of information available via the public API. all_services = self.service_registry._services.items() protocols = {} for service_id, (protocol_name, obj, properties) in all_services: protocol = protocols.get(protocol_name) if protocol is None: protocol = ProtocolModel(name=protocol_name) protocols[protocol_name] = protocol service_model = ServiceModel( id = service_id, obj = obj, properties = properties ) protocol.services.append(service_model) return protocols.values() class ProtocolModelTreeNode(TreeNode): """ A tree node for a protocol model. """ ########################################################################### # 'TreeNode' interface. ########################################################################### def allows_children(self, obj): """ Return True if this object allows children. """ return True def get_children(self, obj): """ Get the object's children. """ svtno = SingleValueTreeNodeObject() nodes = [] for service in obj.services: node = svtno.node_for(repr(service.obj), service.obj) node._protocol_ = obj.name node._service_id_ = service.id nodes.append(node) return nodes service_registry_browser_tree_nodes = [ TreeNode( node_for = [ServiceRegistryModel], auto_open = True, label = '=Services', children = 'protocols', rename = False, copy = False, delete = False, insert = False, menu = None, ), ProtocolModelTreeNode( node_for = [ProtocolModel], auto_open = True, label = 'name', rename = False, copy = False, delete = False, insert = False, menu = None, ), ] + value_tree_nodes #### EOF ###################################################################### envisage-4.4.0/envisage/developer/ui/view/extension_registry_browser_view.py0000644000175000017500000000404112252661404030317 0ustar davidcdavidc00000000000000""" A view that allows a developer to browse the extension registry. """ # Enthought library imports. from pyface.workbench.api import TraitsUIView from traits.api import Instance # The code browser protocol. CODE_BROWSER = 'envisage.developer.code_browser.api.CodeBrowser' class ExtensionRegistryBrowserView(TraitsUIView): """ A view that allows a developer to browse the extension registry. """ #### 'IWorkbenchPart' interface ########################################### # The part's globally unique identifier. id = 'envisage.developer.ui.view.extension_registry_browser_view' # The part's name (displayed to the user). name = 'Extension Points' #### 'IView' interface #################################################### # The category that the view belongs to (this can used to group views when # they are displayed to the user). category = 'Developer' #### 'ExtensionRegistryBrowserView' interface ############################# # The code browser used to parse Python code. code_browser = Instance(CODE_BROWSER) ########################################################################### # 'TraitsUIView' interface. ########################################################################### def _obj_default(self): """ Trait initializer. """ # Local imports. from extension_registry_browser import ExtensionRegistryBrowser extension_registry_browser = ExtensionRegistryBrowser( application = self.window.application, code_browser = self.code_browser ) return extension_registry_browser ########################################################################### # 'ExtensionRegistryBrowserView' interface. ########################################################################### def _code_browser_default(self): """ Trait initializer. """ return self.window.application.get_service(CODE_BROWSER) #### EOF ###################################################################### envisage-4.4.0/envisage/developer/ui/view/application_browser_tree.py0000644000175000017500000001216712252661404026653 0ustar davidcdavidc00000000000000""" The tree editor used in the application browser. """ # Enthought library imports. from envisage.api import IApplication, IExtensionPoint, IPlugin from traits.api import Any, HasTraits, List, Str, Undefined from traitsui.api import TreeEditor, TreeNode # fixme: non-api imports. from traitsui.value_tree import value_tree_nodes class Container(HasTraits): """ A container. """ # The object that contains the container ;^) parent = Any # The contents of the container. contents = List # The label. label = Str class IApplicationTreeNode(TreeNode): """ A tree node for an Envisage application. """ ########################################################################### # 'TreeNode' interface. ########################################################################### def allows_children(self, obj): """ Return True if this object allows children. """ return True def get_children(self, obj): """ Get the object's children. """ return [plugin for plugin in obj] def is_node_for(self, obj): """ Return whether this is the node that handles a specified object. """ return IApplication(obj, Undefined) is obj class IPluginTreeNode(TreeNode): """ A tree node for a Envisage plugins. """ ########################################################################### # 'TreeNode' interface. ########################################################################### def allows_children(self, obj): """ Return True if this object allows children. """ return False#True def get_children(self, obj): """ Get the object's children. """ extension_points = Container( label = 'Extension Points', parent = obj, contents = obj.get_extension_points() ) extensions = Container( label = 'Extensions', parent = obj, contents = self._get_extensions(obj) ) return [extension_points, extensions] def is_node_for(self, obj): """ Return whether this is the node that handles a specified object. """ return IPlugin(obj, Undefined) is obj def _get_extensions(self, plugin): from traitsui.value_tree import ListNode, StringNode class MyListNode(ListNode): label = Str def format_value(self, value): return self.label extensions = [] for trait_name, trait in plugin.traits().items(): if trait.extension_point is not None: node = MyListNode(label=trait.extension_point, value=plugin.get_extensions(trait.extension_point)) extensions.append(node) return extensions class IExtensionPointTreeNode(TreeNode): """ A tree node for an extension point. """ ########################################################################### # 'TreeNode' interface. ########################################################################### def allows_children(self, obj): """ Return True if this object allows children. """ return False def get_children(self, obj): """ Get the object's children. """ return [] def get_label(self, obj): """ Get the object's label. """ return obj.id def when_label_changed(self, obj, callback, remove): return def is_node_for(self, obj): """ Return whether this is the node that handles a specified object. """ return IExtensionPoint(obj, Undefined) is obj class ContainerTreeNode(TreeNode): """ A tree node for a Envisage plugins. """ ########################################################################### # 'TreeNode' interface. ########################################################################### def allows_children(self, obj): """ Return True if this object allows children. """ return True def get_children(self, obj): """ Get the object's children. """ return obj.contents def is_node_for(self, obj): """ Return whether this is the node that handles a specified object. """ return isinstance(obj, Container) application_browser_tree_nodes = [ IApplicationTreeNode( auto_open = True, label = 'id', rename = False, copy = False, delete = False, insert = False, menu = None, ), IExtensionPointTreeNode( rename = False, copy = False, delete = False, insert = False, menu = None, ), IPluginTreeNode( label = 'name', rename = False, copy = False, delete = False, insert = False, menu = None, ), ContainerTreeNode( label = 'label', rename = False, copy = False, delete = False, insert = False, menu = None, ), ] + value_tree_nodes #### EOF ###################################################################### envisage-4.4.0/envisage/developer/ui/view/application_browser.py0000644000175000017500000001007712252661404025632 0ustar davidcdavidc00000000000000""" A view showing a summary of the running application. """ # Standard library imports. import inspect # Enthought library imports. from envisage.api import IApplication, IPlugin from envisage.developer.code_browser.api import CodeBrowser from apptools.io.api import File from traits.api import Any, HasTraits, Instance from traitsui.api import Item, TreeEditor, View # fixme: non-api import. from envisage.plugins.text_editor.editor.text_editor import TextEditor # Local imports. from application_browser_tree import application_browser_tree_nodes application_browser_view = View( Item( name = 'application', show_label = False, editor = TreeEditor( nodes = application_browser_tree_nodes, editable = False, orientation = 'vertical', hide_root = True, show_icons = True, selected = 'selection', on_dclick = 'object.dclick' ) ), resizable = True, style = 'custom', title = 'Application', width = .1, height = .1 ) class ApplicationBrowser(HasTraits): """ An application browser. Actually, this class exists just because to use a trait editor we have to have a trait to edit! """ # The application that we are browsing. application = Instance(IApplication) # The code browser that we use to parse plugin source code. code_browser = Instance(CodeBrowser) # The workbench service. workbench = Instance('envisage.ui.workbench.api.Workbench') # The object that is currently selected in the tree. selection = Any # The default traits UI view. traits_view = application_browser_view ########################################################################### # 'ApplicationBrowser' interface. ########################################################################### #### Trait initializers ################################################### def _code_browser_default(self): """ Trait initializer. """ return self.application.get_service(CodeBrowser) def _workbench_default(self): """ Trait initializer. """ from envisage.ui.workbench.api import Workbench return self.application.get_service(Workbench) #### Trait change handlers ################################################ def _selection_changed(self, trait_name, old, new): """ Static trait change handler. """ #print 'Selection changed', trait_name, old, new return #### Methods ############################################################## def dclick(self, obj): """ Called when an object in the tree is double-clicked. """ if IPlugin(obj, None) is not None: # Parse the plugin source code. module = self._parse_plugin(obj) # Get the plugin klass. klass = self._get_plugin_klass(module, obj) # Edit the plugin. editor = self.workbench.edit( self._get_file_object(obj), kind=TextEditor ) # Move to the class definition. editor.select_line(klass.lineno) return ########################################################################### # Private interface. ########################################################################### def _get_file_object(self, obj): """ Return a 'File' object for an object's source file. """ return File(path=inspect.getsourcefile(type(obj))) def _get_plugin_klass(self, module, plugin): """ Get the klass that defines the plugin. """ for name, klass in module.klasses.items(): if name == type(plugin).__name__: break else: klass = None return klass def _parse_plugin(self, plugin): """ Parse the plugin source code. """ filename = self._get_file_object(plugin).path return self.code_browser.read_file(filename) #### EOF ###################################################################### envisage-4.4.0/envisage/developer/ui/view/application_browser_view.py0000644000175000017500000000374612252661404026671 0ustar davidcdavidc00000000000000""" A view that allows a developer to browse the current application. """ # Enthought library imports. from pyface.workbench.api import TraitsUIView from traits.api import Instance # The code browser protocol. CODE_BROWSER = 'envisage.developer.code_browser.api.CodeBrowser' class ApplicationBrowserView(TraitsUIView): """ A view that allows a developer to browse the current application. """ #### 'IWorkbenchPart' interface ########################################### # The part's globally unique identifier. id = 'envisage.developer.ui.view.application_browser_view' # The part's name (displayed to the user). name = 'Plugins' #### 'IView' interface #################################################### # The category that the view belongs to (this can used to group views when # they are displayed to the user). category = 'Developer' #### 'ApplicationBrowserView' interface ################################### # The code browser used to parse Python code. code_browser = Instance(CODE_BROWSER) ########################################################################### # 'TraitsUIView' interface. ########################################################################### def _obj_default(self): """ Trait initializer. """ # Local imports. from application_browser import ApplicationBrowser application_browser = ApplicationBrowser( application = self.window.application, code_browser = self.code_browser ) return application_browser ########################################################################### # 'ApplicationBrowserView' interface. ########################################################################### def _code_browser_default(self): """ Trait initializer. """ return self.window.application.get_service(CODE_BROWSER) #### EOF ###################################################################### envisage-4.4.0/envisage/developer/ui/view/__init__.py0000644000175000017500000000000012252661404023304 0ustar davidcdavidc00000000000000envisage-4.4.0/envisage/developer/ui/view/extension_registry_browser.py0000644000175000017500000001671512252661404027300 0ustar davidcdavidc00000000000000""" A view showing a summary of the running application. """ # Standard library imports. import inspect # Enthought library imports. from envisage.api import IApplication, IExtensionPoint from envisage.api import IExtensionRegistry from envisage.developer.code_browser.api import CodeBrowser from apptools.io.api import File from traits.api import HasTraits, Instance from traitsui.api import Item, TreeEditor, View # fixme: non-api import. from envisage.plugins.text_editor.editor.text_editor import TextEditor # Local imports. from extension_registry_browser_tree import \ extension_registry_browser_tree_nodes extension_registry_browser_view = View( Item( name = 'extension_registry', show_label = False, editor = TreeEditor( nodes = extension_registry_browser_tree_nodes, editable = False, orientation = 'vertical', hide_root = True, show_icons = True, on_dclick = 'object.dclick' ) ), resizable = True, style = 'custom', title = 'Extension Registry', width = .1, height = .1 ) class ExtensionRegistryBrowser(HasTraits): """ An extension registry browser. Actually, this class exists just because to use a trait editor we have to have a trait to edit! """ #### 'ExtensionRegistryBrowser' interface ################################# # The application that whose extension registry we are browsing. application = Instance(IApplication) # The code browser that we use to parse plugin source code. code_browser = Instance(CodeBrowser) # The extension registry that we are browsing. extension_registry = Instance(IExtensionRegistry) # The workbench service. workbench = Instance('envisage.ui.workbench.api.Workbench') # The default traits UI view. traits_view = extension_registry_browser_view ########################################################################### # 'ExtensionRegistryBrowser' interface. ########################################################################### #### Trait initializers ################################################### def _extension_registry_default(self): """ Trait initializer. """ return self.application def _workbench_default(self): """ Trait initializer. """ from envisage.ui.workbench.api import Workbench return self.application.get_service(Workbench) #### Methods ############################################################## def dclick(self, obj): """ Called when an object in the tree is double-clicked. """ # Double-click on an extension point. if IExtensionPoint(obj, None) is not None: self.dclick_extension_point(obj) # Double-click on an extension. elif IExtensionPoint(obj.parent.value, None) is not None: self.dclick_extension(obj) return def dclick_extension_point(self, obj): """ Called when an extension point is double-clicked. """ # Find the plugin that offered the extension point. plugin = self._get_plugin(obj) # Parse the plugin source code. module = self._parse_plugin(plugin) # Get the plugin klass. klass = self._get_plugin_klass(module, plugin) # Edit the plugin. editor = self.workbench.edit( self._get_file_object(plugin), kind=TextEditor ) # Was the extension point offered declaratively via a trait? trait_name = self._get_extension_point_trait(plugin, obj.id) if trait_name is not None: attribute = klass.attributes.get(trait_name) lineno = attribute.lineno else: lineno = klass.lineno editor.select_line(lineno) return def dclick_extension(self, obj): """ Called when an extension is double-clicked. """ extension_point = obj.parent.value index = obj.parent._index extension_registry = self.extension_registry extensions = extension_registry.extension_registry._extensions total = 0 provider_index = 0 for l in extensions[extension_point.id]: total = total + len(l) if index < total: break provider_index += 1 plugin = extension_registry.extension_registry._providers[provider_index] # Parse the plugin source code. module = self._parse_plugin(plugin) # Get the plugin klass. klass = self._get_plugin_klass(module, plugin) # Edit the plugin. editor = self.workbench.edit( self._get_file_object(plugin), kind=TextEditor ) # Was the extension offered declaratively? trait_name = self._get_extension_trait(plugin, extension_point.id) if trait_name is not None: # Does the trait have a default initializer? initializer = klass.methods.get('_%s_default' % trait_name) if initializer is not None: lineno = initializer.lineno else: attribute = klass.attributes.get(trait_name) lineno = attribute.lineno else: lineno = klass.lineno editor.select_line(lineno) return ########################################################################### # Private interface. ########################################################################### def _get_extension_trait(self, plugin, id): """ Return the extension trait with the specifed Id. Return None if the extension point was not declared via a trait. """ extension_traits = plugin.traits(contributes_to=id) if len(extension_traits) > 0: # There is *at most* one extension point trait per extension point. trait_name = extension_traits.keys()[0] else: trait_name = None return trait_name def _get_extension_point_trait(self, plugin, id): """ Return the extension point trait with the specifed Id. Return None if the extension point was not declared via a trait. """ extension_point_traits = plugin.traits(__extension_point__=True) for trait_name, trait in extension_point_traits.items(): if trait.trait_type.id == id: break else: trait_name = None return trait_name def _get_plugin(self, extension_point): """ Return the plugin that offered an extension point. """ for plugin in self.application: if extension_point in plugin.get_extension_points(): break else: plugin = None return plugin def _get_plugin_klass(self, module, plugin): """ Get the klass that defines the plugin. """ for name, klass in module.klasses.items(): if name == type(plugin).__name__: break else: klass = None return klass def _get_file_object(self, obj): """ Return a 'File' object for the object's source file. """ return File(path=inspect.getsourcefile(type(obj))) def _parse_plugin(self, plugin): """ Parse the plugin source code. """ filename = self._get_file_object(plugin).path return self.code_browser.read_file(filename) #### EOF ###################################################################### envisage-4.4.0/envisage/developer/ui/view/service_registry_browser.py0000644000175000017500000001522412252661404026716 0ustar davidcdavidc00000000000000""" A view showing a summary of the running application. """ # Standard library imports. import inspect # Enthought library imports. from envisage.api import IApplication, IExtensionPoint from envisage.api import IServiceRegistry from envisage.developer.code_browser.api import CodeBrowser from apptools.io.api import File from traits.api import Any, HasTraits, Instance from traitsui.api import Item, TreeEditor, View # fixme: non-api import. from envisage.plugins.text_editor.editor.text_editor import TextEditor # Local imports. from service_registry_browser_tree import \ service_registry_browser_tree_nodes service_registry_browser_view = View( Item( name = 'service_registry_model', show_label = False, editor = TreeEditor( nodes = service_registry_browser_tree_nodes, editable = False, orientation = 'vertical', hide_root = True, show_icons = True, on_dclick = 'object.dclick' ) ), resizable = True, style = 'custom', title = 'Service Registry', width = .1, height = .1 ) class ServiceRegistryBrowser(HasTraits): """ An extension registry browser. Actually, this class exists just because to use a trait editor we have to have a trait to edit! """ #### 'ServiceRegistryBrowser' interface ################################# # The application that whose extension registry we are browsing. application = Instance(IApplication) # The code browser that we use to parse plugin source code. code_browser = Instance(CodeBrowser) # The extension registry that we are browsing. service_registry = Instance(IServiceRegistry) # The extension registry that we are browsing. service_registry_model = Any#Instance(IServiceRegistry) # The workbench service. workbench = Instance('envisage.ui.workbench.api.Workbench') # The default traits UI view. traits_view = service_registry_browser_view ########################################################################### # 'ServiceRegistryBrowser' interface. ########################################################################### #### Trait initializers ################################################### def _service_registry_default(self): """ Trait initializer. """ return self.application.service_registry def _service_registry_model_default(self): """ Trait initializer. """ from service_registry_browser_tree import ServiceRegistryModel return ServiceRegistryModel(service_registry=self.service_registry) def _workbench_default(self): """ Trait initializer. """ workbench = self.application.get_service( 'envisage.ui.workbench.workbench.Workbench' ) return workbench #### Methods ############################################################## def dclick(self, obj): """ Called when an object in the tree is double-clicked. """ if hasattr(obj, '_service_id_'): protocol = obj._protocol_ id = obj._service_id_ service = obj.value for plugin in self.application: if id in plugin._service_ids: self.dclick_service(plugin, protocol, service) break else: self.workbench.information( 'Service not created by a plugin (%s)' % repr(service) ) return def dclick_service(self, plugin, protocol, obj): """ Called when an extension is double-clicked. """ # Parse the plugin source code. module = self._parse_plugin(plugin) # Get the plugin klass. klass = self._get_plugin_klass(module, plugin) # Edit the plugin. editor = self.workbench.edit( self._get_file_object(plugin), kind=TextEditor ) # Was the service offered declaratively? trait_name = self._get_service_trait(plugin, protocol, obj) if trait_name is not None: # Does the trait have a default initializer? initializer = klass.methods.get('_%s_default' % trait_name) if initializer is not None: lineno = initializer.lineno else: attribute = klass.attributes.get(trait_name) lineno = attribute.lineno else: lineno = klass.lineno editor.select_line(lineno) return ########################################################################### # Private interface. ########################################################################### def _get_service_trait(self, plugin, protocol, obj): """ Return the servicetrait with the specifed Id. Return None if the service was not declared via a trait. """ service_traits = plugin.traits(service=True) protocol = self.application.import_symbol(protocol) for trait_name, trait in service_traits.items(): if protocol == self._get_service_protocol(trait): break else: trait_name = None return trait_name def _get_service_protocol(self, trait): """ Determine the protocol to register a service trait with. """ # If a specific protocol was specified then use it. if trait.service_protocol is not None: protocol = trait.service_protocol # Otherwise, use the type of the objects that can be assigned to the # trait. # # fixme: This works for 'Instance' traits, but what about 'AdaptsTo' # and 'AdaptedTo' traits? else: # Note that in traits the protocol can be an actual class or # interfacem or the *name* of a class or interface. This allows # us to lazy load them! protocol = trait.trait_type.klass return protocol def _get_plugin_klass(self, module, plugin): """ Get the klass that defines the plugin. """ for name, klass in module.klasses.items(): if name == type(plugin).__name__: break else: klass = None return klass def _get_file_object(self, obj): """ Return a 'File' object for the object's source file. """ return File(path=inspect.getsourcefile(type(obj))) def _parse_plugin(self, plugin): """ Parse the plugin source code. """ filename = self._get_file_object(plugin).path return self.code_browser.read_file(filename) #### EOF ###################################################################### envisage-4.4.0/envisage/developer/ui/developer_ui_plugin.py0000644000175000017500000000341512252661404024650 0ustar davidcdavidc00000000000000""" The developer UI plugin. """ # Enthought library imports. from envisage.api import Plugin from traits.api import List class DeveloperUIPlugin(Plugin): """ The developer UI plugin. This plugin contains the UI part of the tools that (hopefully) help developers to inspect and debug a running Envisage workbench application. """ # The plugin Id. ID = 'envisage.developer.ui' # Extension points Ids. PERSPECTIVES = 'envisage.ui.workbench.perspectives' VIEWS = 'envisage.ui.workbench.views' #### 'IPlugin' interface ################################################## # The plugin's unique identifier. id = ID # The plugin's name (suitable for displaying to the user). name = 'Developer UI' #### 'DeveloperUIPlugin' interface ######################################## #### Extension points offered by this plugin ############################## # None. #### Contributions to extension points made by this plugin ################ perspectives = List(contributes_to=PERSPECTIVES) def _perspectives_default(self): """ Trait initializer. """ from envisage.developer.ui.perspective.api import ( DeveloperPerspective ) return [DeveloperPerspective] views = List(contributes_to=VIEWS) def _views_default(self): """ Trait initializer. """ from view.api import ( ApplicationBrowserView, ExtensionRegistryBrowserView, ServiceRegistryBrowserView ) views = [ ApplicationBrowserView, ExtensionRegistryBrowserView, ServiceRegistryBrowserView ] return views #### EOF ###################################################################### envisage-4.4.0/envisage/developer/developer_plugin.py0000644000175000017500000000273012252661404023535 0ustar davidcdavidc00000000000000""" The developer plugin. """ # Enthought library imports. from envisage.api import Plugin, ServiceOffer from traits.api import List # The package that this module is in. PKG = '.'.join(__name__.split('.')[:-1]) class DeveloperPlugin(Plugin): """ The developer plugin. This plugin contains the non-GUI part of the tools that (hopefully) help developers to inspect and debug a running Envisage workbench application. """ # The Ids of the extension points that this plugin contributes to. SERVICE_OFFERS = 'envisage.service_offers' #### 'IPlugin' interface ################################################## # The plugin's globally unique identifier. id = 'envisage.developer' # The plugin's name (suitable for displaying to the user). name = 'Developer' #### Extension points offered by this plugin ############################## # None. #### Contributions to extension points made by this plugin ################ service_offers = List(contributes_to=SERVICE_OFFERS) def _service_offers_default(self): """ Trait initializer. """ code_browser_class = PKG + '.code_browser.api.CodeBrowser' code_browser_service_offer = ServiceOffer( protocol = code_browser_class, factory = code_browser_class, scope = 'application' ) return [code_browser_service_offer] #### EOF ###################################################################### envisage-4.4.0/envisage/developer/charm/0000755000175000017500000000000012252662115020710 5ustar davidcdavidc00000000000000envisage-4.4.0/envisage/developer/charm/charm.py0000644000175000017500000000620012252661404022352 0ustar davidcdavidc00000000000000""" Charm - the model for a simple Python IDE. """ # Enthought library imports. from envisage.developer.api import CodeBrowser, Module from traits.api import Event, HasTraits, Instance, Str class Charm(HasTraits): """ Charm - the model for a simple Python IDE. """ #### 'Charm' interface #################################################### # The code browser. code_browser = Instance(CodeBrowser) # The filename of the code database. filename = Str('code_database.pickle') # The current module. module = Instance(Module) #### Events #### # Fired when a module is about to be parsed. parsing_module = Event # Fired when a module has been parsed. parsed_module = Event ########################################################################### # 'Charm' interface. ########################################################################### #### Trait initializers ################################################### def _code_browser_default(self): """ Trait initializer. """ code_browser = CodeBrowser(filename=self.filename) self._setup_code_browser(code_browser) return code_browser #### Methods ############################################################## def load(self): """ Loads the code database. """ self.code_browser.load() return def save(self): """ Saves the code database. """ self.code_browser.save() return ########################################################################### # Private interface. ########################################################################### def _setup_code_browser(self, code_browser): """ Adds trait change listeners to a code browser. """ code_browser.on_trait_change(self._on_parsing_module, 'parsing_module') code_browser.on_trait_change(self._on_parsed_module, 'parsed_module') return def _tear_down_code_browser(self, code_browser): """ Removes trait change listeners from a code browser. """ code_browser.on_trait_change( self._on_parsing_module, 'parsing_module', remove=True ) code_browser.on_trait_change( self._on_parsed_module, 'parsed_module', remove=True ) return #### Trait change handlers ################################################ def _code_browser_changed(self, old, new): """ Static trait change handler. """ if old is not None: self._tear_down_code_browser(old) if new is not None: self._setup_code_browser(new) return def _filename_changed(self, old, new): """ Static trait change handler. """ self.code_browser.filename = new return def _on_parsing_module(self, event): """ Dysnamic trait change handler. """ self.parsing_module = event return def _on_parsed_module(self, event): """ Dysnamic trait change handler. """ self.parsed_module = event return #### EOF ###################################################################### envisage-4.4.0/envisage/developer/charm/api.py0000644000175000017500000000003012252661404022024 0ustar davidcdavidc00000000000000from charm import Charm envisage-4.4.0/envisage/developer/charm/run.py0000644000175000017500000000010312252661404022060 0ustar davidcdavidc00000000000000import wx; app = wx.PySimpleApp() from ui.main import main; main() envisage-4.4.0/envisage/developer/charm/__init__.py0000644000175000017500000000000012252661404023007 0ustar davidcdavidc00000000000000envisage-4.4.0/envisage/developer/__init__.py0000644000175000017500000000000012252661404021715 0ustar davidcdavidc00000000000000envisage-4.4.0/envisage/service_offer.py0000644000175000017500000000216612252661404021031 0ustar davidcdavidc00000000000000""" An offer to provide a service. """ # Enthought library imports. from traits.api import Callable, Dict, Either, HasTraits, Str, Type class ServiceOffer(HasTraits): """ An offer to provide a service. """ #### 'ServiceOffer' interface ############################################# # The protocol that the service provides. # # This can be an actual class or interface, or a string that can be used to # import a class or interface. # # e.g. 'foo.bar.baz.Baz' is turned into 'from foo.bar.baz import Baz' protocol = Either(Str, Type) # A callable (or a string that can be used to import a callable) that is # the factory that creates the actual service object. # # e.g:: # # callable(**properties) -> Any # # e.g. 'foo.bar.baz.Baz' is turned into 'from foo.bar.baz import Baz' factory = Either(Str, Callable) # An optional set of properties to associate with the service offer. # # This dictionary is passed as keyword arguments to the factory. properties = Dict #### EOF ###################################################################### envisage-4.4.0/envisage/plugin_activator.py0000644000175000017500000000247012252661404021560 0ustar davidcdavidc00000000000000""" The default plugin activator. """ # Enthought library imports. from traits.api import HasTraits, implements # Local imports. from i_plugin_activator import IPluginActivator class PluginActivator(HasTraits): """ The default plugin activator. """ implements(IPluginActivator) ########################################################################### # 'IPluginActivator' interface. ########################################################################### def start_plugin(self, plugin): """ Start the specified plugin. """ # Connect all of the plugin's extension point traits so that the plugin # will be notified if and when contributions are added or removed. plugin.connect_extension_point_traits() # Register all services. plugin.register_services() # Plugin specific start. plugin.start() return def stop_plugin(self, plugin): """ Stop the specified plugin. """ # Plugin specific stop. plugin.stop() # Unregister all service. plugin.unregister_services() # Disconnect all of the plugin's extension point traits. plugin.disconnect_extension_point_traits() return #### EOF ###################################################################### envisage-4.4.0/envisage/i_plugin.py0000644000175000017500000000301712252661404020012 0ustar davidcdavidc00000000000000""" The plugin interface. """ # Enthought library imports. from traits.api import Instance, Interface, Str # Local imports. from i_plugin_activator import IPluginActivator class IPlugin(Interface): """ The plugin interface. """ # The activator used to start and stop the plugin. activator = Instance(IPluginActivator) # The application that the plugin is part of. application = Instance('envisage.api.IApplication') # The name of a directory (created for you) that the plugin can read and # write to at will. home = Str # The plugin's unique identifier. # # Where 'unique' technically means 'unique within the plugin manager', but # since the chances are that you will want to include plugins from external # sources, this really means 'globally unique'! Using the Python package # path might be useful here. e.g. 'envisage'. id = Str # The plugin's name (suitable for displaying to the user). name = Str def start(self): """ Start the plugin. This method is called by the framework when the application is starting up. If you want to start a plugin manually use:: application.start_plugin(plugin) """ def stop(self): """ Stop the plugin. This method is called by the framework when the application is stopping. If you want to stop a plugin manually use:: application.stop_plugin(plugin) """ #### EOF ###################################################################### envisage-4.4.0/envisage/plugin_event.py0000644000175000017500000000050412252661404020701 0ustar davidcdavidc00000000000000""" A plugin event. """ # Enthought library imports. from traits.api import Instance, Vetoable class PluginEvent(Vetoable): """ A plugin event. """ # The plugin that the event is for. plugin = Instance('envisage.api.IPlugin') #### EOF ###################################################################### envisage-4.4.0/envisage/i_service_registry.py0000644000175000017500000001042712252661404022107 0ustar davidcdavidc00000000000000""" The service registry interface. """ # Enthought library imports. from traits.api import Event, Interface class IServiceRegistry(Interface): """ The service registry interface. The service registry provides a 'Yellow Pages' style mechanism, in that services are published and looked up by protocol (meaning, *interface*, *type*, or *class* (for old-style classes!). It is called a 'Yellow Pages' mechanism because it is just like looking up a telephone number in the 'Yellow Pages' phone book. You use the 'Yellow Pages' instead of the 'White Pages' when you don't know the *name* of the person you want to call but you do know what *kind* of service you require. For example, if you have a leaking pipe, you know you need a plumber, so you pick up your 'Yellow Pages', go to the 'Plumbers' section and choose one that seems to fit the bill based on price, location, certification, etc. The service registry does exactly the same thing as the 'Yellow Pages', only with objects, and it even allows you to publish your own entries for free (unlike the *real* one)! """ # An event that is fired when a service is registered. registered = Event # An event that is fired when a service is unregistered. unregistered = Event def get_service(self, protocol, query='', minimize='', maximize=''): """ Return at most one service that matches the specified query. The protocol can be an actual class or interface, or the *name* of a class or interface in the form '.'. Return None if no such service is found. If no query is specified then a service that provides the specified protocol is returned (if one exists). NOTE: If more than one service exists that match the criteria then Don't try to guess *which* one it will return - it is random! """ def get_service_from_id(self, service_id): """ Return the service with the specified id. If no such service exists a 'ValueError' exception is raised. """ def get_services(self, protocol, query='', minimize='', maximize=''): """ Return all services that match the specified query. The protocol can be an actual class or interface, or the *name* of a class or interface in the form '.'. If no services match the query, then an empty list is returned. If no query is specified then all services that provide the specified protocol are returned (if any exist). """ def get_service_properties(self, service_id): """ Return the dictionary of properties associated with a service. If no such service exists a 'ValueError' exception is raised. The properties returned are 'live' i.e. changing them immediately changes the service registration. """ def register_service(self, protocol, obj, properties=None): """ Register a service. The protocol can be an actual class or interface, or the *name* of a class or interface in the form:: 'foo.bar.baz' Which is turned into the equivalent of an import statement that looks like:: from foo.bar import baz Return a service Id that can be used to unregister the service and to get/set any service properties. If 'obj' does not implement the specified protocol then it is treated as a 'service factory' that will be called the first time a service of the appropriate type is requested. A 'service factory' is simply a callable that takes the properties specified here as keyword arguments and returns an object. For *really* lazy loading, the factory can also be specified as a string which is used to import the callable. """ def set_service_properties(self, service_id, properties): """ Set the dictionary of properties associated with a service. If no such service exists a 'ValueError' exception is raised. """ def unregister_service(self, service_id): """ Unregister a service. If no such service exists a 'ValueError' exception is raised. """ #### EOF ###################################################################### envisage-4.4.0/envisage/i_extension_registry.py0000644000175000017500000000474412252661404022470 0ustar davidcdavidc00000000000000""" The interface for extension registries. """ # Enthought library imports. from traits.api import Interface class IExtensionRegistry(Interface): """ The interface for extension registries. """ def add_extension_point_listener(self, listener, extension_point_id=None): """ Add a listener for extensions being added or removed. A listener is any Python callable with the following signature:: def listener(extension_registry, extension_point_changed_event): ... If an extension point is specified then the listener will only be called when extensions are added to or removed from that extension point (the extension point may or may not have been added to the registry at the time of this call). If *no* extension point is specified then the listener will be called when extensions are added to or removed from *any* extension point. When extensions are added or removed all specific listeners are called first (in arbitrary order), followed by all non-specific listeners (again, in arbitrary order). """ def add_extension_point(self, extension_point): """ Add an extension point. If an extension point already exists with this Id then it is simply replaced. """ def get_extensions(self, extension_point_id): """ Return the extensions contributed to an extension point. Return an empty list if the extension point does not exist. """ def get_extension_point(self, extension_point_id): """ Return the extension point with the specified Id. Return None if no such extension point exists. """ def get_extension_points(self): """ Return all extension points that have been added to the registry. """ def remove_extension_point_listener(self,listener,extension_point_id=None): """ Remove a listener for extensions being added or removed. Raise a 'ValueError' if the listener does not exist. """ def remove_extension_point(self, extension_point_id): """ Remove an extension point. Raise an 'UnknownExtensionPoint' exception if no extension point exists with the specified Id. """ def set_extensions(self, extension_point_id, extensions): """ Set the extensions contributed to an extension point. """ #### EOF ###################################################################### envisage-4.4.0/envisage/extension_point_changed_event.py0000644000175000017500000000126312252661404024304 0ustar davidcdavidc00000000000000""" An event fired when an extension point's extensions have changed. """ # Enthought library imports. from traits.api import TraitListEvent class ExtensionPointChangedEvent(TraitListEvent): """ An event fired when an extension point's extensions have changed. """ def __init__ (self, extension_point_id=None, **kw): """ Constructor. """ # The base class has the 'index', 'removed' and 'added' attributes. super(ExtensionPointChangedEvent, self).__init__(**kw) # We add the extension point Id. self.extension_point_id = extension_point_id return #### EOF ###################################################################### envisage-4.4.0/envisage/plugin_manager.py0000644000175000017500000001574512252661404021207 0ustar davidcdavidc00000000000000""" A simple plugin manager implementation. """ from fnmatch import fnmatch import logging from traits.api import Event, HasTraits, Instance, List, Str, implements from i_application import IApplication from i_plugin import IPlugin from i_plugin_manager import IPluginManager from plugin_event import PluginEvent logger = logging.getLogger(__name__) class PluginManager(HasTraits): """ A simple plugin manager implementation. This implementation manages an explicit collection of plugin instances, e.g:: plugin_manager = PlugunManager( plugins = [ MyPlugin(), YourPlugin() ] ) Plugins can be added and removed after construction time via the methods 'add_plugin' and 'remove_plugin'. """ implements(IPluginManager) #### 'IPluginManager' protocol ############################################# # Fired when a plugin has been added to the manager. plugin_added = Event(PluginEvent) # Fired when a plugin has been removed from the manager. plugin_removed = Event(PluginEvent) #### 'PluginManager' protocol ############################################## # The application that the plugin manager is part of. application = Instance(IApplication) def _application_changed(self, trait_name, old, new): """ Static trait change handler. """ self._update_plugin_application([], self._plugins) return # An optional list of the Ids of the plugins that are to be excluded by # the manager. # # Each item in the list is actually an 'fnmatch' expression. exclude = List(Str) # An optional list of the Ids of the plugins that are to be included by # the manager (i.e. *only* plugins with Ids in this list will be added to # the manager). # # Each item in the list is actually an 'fnmatch' expression. include = List(Str) #### 'object' protocol ##################################################### def __init__(self, plugins=None, **traits): """ Constructor. We allow the caller to specify an initial list of plugins, but the list itself is not part of the public API. To add and remove plugins after construction, use the 'add_plugin' and 'remove_plugin' methods respectively. The manager is also iterable, so to iterate over the plugins use 'for plugin in plugin_manager'. """ super(PluginManager, self).__init__(**traits) if plugins is not None: self._plugins = plugins return def __iter__(self): """ Return an iterator over the manager's plugins. """ plugins = [ plugin for plugin in self._plugins if self._include_plugin(plugin.id) ] return iter(plugins) #### 'IPluginManager' protocol ############################################# def add_plugin(self, plugin): """ Add a plugin to the manager. """ self._plugins.append(plugin) self.plugin_added = PluginEvent(plugin=plugin) return def get_plugin(self, plugin_id): """ Return the plugin with the specified Id. """ for plugin in self._plugins: if plugin_id == plugin.id: if not self._include_plugin(plugin.id): plugin = None break else: plugin = None return plugin def remove_plugin(self, plugin): """ Remove a plugin from the manager. """ self._plugins.remove(plugin) self.plugin_removed = PluginEvent(plugin=plugin) return def start(self): """ Start the plugin manager. """ map(lambda plugin: self.start_plugin(plugin), self._plugins) return def start_plugin(self, plugin=None, plugin_id=None): """ Start the specified plugin. """ plugin = plugin or self.get_plugin(plugin_id) if plugin is not None: logger.debug('plugin %s starting', plugin.id) plugin.activator.start_plugin(plugin) logger.debug('plugin %s started', plugin.id) else: raise SystemError('no such plugin %s' % plugin_id) return def stop(self): """ Stop the plugin manager. """ # We stop the plugins in the reverse order that they were started. stop_order = self._plugins[:] stop_order.reverse() map(lambda plugin: self.stop_plugin(plugin), stop_order) return def stop_plugin(self, plugin=None, plugin_id=None): """ Stop the specified plugin. """ plugin = plugin or self.get_plugin(plugin_id) if plugin is not None: logger.debug('plugin %s stopping', plugin.id) plugin.activator.stop_plugin(plugin) logger.debug('plugin %s stopped', plugin.id) else: raise SystemError('no such plugin %s' % plugin_id) return #### Protected 'PluginManager' ############################################# # The plugins that the manager manages! _plugins = List(IPlugin) def __plugins_changed(self, trait_name, old, new): """ Static trait change handler. """ self._update_plugin_application(old, new) return def __plugins_items_changed(self, trait_name, old, new): """ Static trait change handler. """ self._update_plugin_application(new.removed, new.added) return def _include_plugin(self, plugin_id): """ Return True if the plugin should be included. This is just shorthand for:- if self._is_included(plugin_id) and not self._is_excluded(plugin_id): ... """ return self._is_included(plugin_id) and not self._is_excluded(plugin_id) #### Private protocol ###################################################### def _is_excluded(self, plugin_id): """ Return True if the plugin Id is excluded. If no 'exclude' patterns are specified then this method returns False for all plugin Ids. """ if len(self.exclude) == 0: return False for pattern in self.exclude: if fnmatch(plugin_id, pattern): return True return False def _is_included(self, plugin_id): """ Return True if the plugin Id is included. If no 'include' patterns are specified then this method returns True for all plugin Ids. """ if len(self.include) == 0: return True for pattern in self.include: if fnmatch(plugin_id, pattern): return True return False def _update_plugin_application(self, removed, added): """ Update the 'application' trait of plugins added/removed. """ for plugin in removed: plugin.application = None for plugin in added: plugin.application = self.application return #### EOF ###################################################################### envisage-4.4.0/envisage/composite_plugin_manager.py0000644000175000017500000001203712252661404023260 0ustar davidcdavidc00000000000000""" A plugin manager composed of other plugin managers! """ # Standard library imports. import logging # Enthought library imports. from traits.api import Event, HasTraits, Instance, List, implements from traits.api import on_trait_change # Local imports. from i_application import IApplication from i_plugin import IPlugin from i_plugin_manager import IPluginManager from plugin_event import PluginEvent from plugin_manager import PluginManager # Logging. logger = logging.getLogger(__name__) class CompositePluginManager(HasTraits): """ A plugin manager composed of other plugin managers! e.g:: plugin_manager = CompositePluginManager( plugin_mangers = [ EggBasketPluginManager(...), PackagePluginManager(...), ] ) """ implements(IPluginManager) #### 'IPluginManager' protocol ############################################# #### Events #### # Fired when a plugin has been added to the manager. plugin_added = Event(PluginEvent) # Fired when a plugin has been removed from the manager. plugin_removed = Event(PluginEvent) #### 'CompositePluginManager' protocol ##################################### # The application that the plugin manager is part of. application = Instance(IApplication) def _application_changed(self, trait_name, old, new): for plugin_manager in self.plugin_managers: plugin_manager.application = new return # The plugin managers that make up this plugin manager! # # This is currently a list of 'PluginManager's as opposed to, the more # preferable 'IPluginManager' because the interface doesn't currently # have an 'application' trait. Should we move 'application' up to # 'IPluginManager'? plugin_managers = List(PluginManager) @on_trait_change('plugin_managers[]') def _update_application(self, obj, trait_named, removed, added): for plugin_manager in removed: plugin_manager.application = self.application for plugin_manager in added: plugin_manager.application = self.application @on_trait_change('plugin_managers:plugin_added') def _plugin_added(self, obj, trait_name, old, new): self.plugin_added = new @on_trait_change('plugin_managers:plugin_removed') def _plugin_removed(self, obj, trait_name, old, new): self.plugin_removed = new #### Private protocol ###################################################### # The plugins that the manager manages! _plugins = List(IPlugin) def __plugins_default(self): plugins = [] for plugin_manager in self.plugin_managers: for plugin in plugin_manager: plugins.append(plugin) return plugins #### 'object' protocol #################################################### def __iter__(self): """ Return an iterator over the manager's plugins. """ plugins = [] for plugin_manager in self.plugin_managers: for plugin in plugin_manager: plugins.append(plugin) return iter(plugins) #### 'IPluginManager' protocol ############################################# def add_plugin(self, plugin): """ Add a plugin to the manager. """ raise NotImplementedError def get_plugin(self, plugin_id): """ Return the plugin with the specified Id. """ for plugin in self: if plugin_id == plugin.id: break else: plugin = None return plugin def remove_plugin(self, plugin): """ Remove a plugin from the manager. """ raise NotImplementedError def start(self): """ Start the plugin manager. """ map(lambda plugin: self.start_plugin(plugin), self) return def start_plugin(self, plugin=None, plugin_id=None): """ Start the specified plugin. """ plugin = plugin or self.get_plugin(plugin_id) if plugin is not None: logger.debug('plugin %s starting', plugin.id) plugin.activator.start_plugin(plugin) logger.debug('plugin %s started', plugin.id) else: raise SystemError('no such plugin %s' % plugin_id) return def stop(self): """ Stop the plugin manager. """ # We stop the plugins in the reverse order that they were started. stop_order = list(iter(self)) stop_order.reverse() map(lambda plugin: self.stop_plugin(plugin), stop_order) return def stop_plugin(self, plugin=None, plugin_id=None): """ Stop the specified plugin. """ plugin = plugin or self.get_plugin(plugin_id) if plugin is not None: logger.debug('plugin %s stopping', plugin.id) plugin.activator.stop_plugin(plugin) logger.debug('plugin %s stopped', plugin.id) else: raise SystemError('no such plugin %s' % plugin_id) return #### EOF ###################################################################### envisage-4.4.0/envisage/__init__.py0000644000175000017500000000021512252661404017740 0ustar davidcdavidc00000000000000# Copyright (c) 2007-2013 by Enthought, Inc. # All rights reserved. __version__ = '4.4.0' __requires__ = [ 'apptools', 'traits', ] envisage-4.4.0/envisage/service_registry.py0000644000175000017500000002112012252661404021567 0ustar davidcdavidc00000000000000""" The service registry. """ # Standard library imports. import logging # Enthought library imports. from traits.api import Dict, Event, HasTraits, Int, Undefined, implements, \ Interface # Local imports. from i_service_registry import IServiceRegistry from import_manager import ImportManager # Logging. logger = logging.getLogger(__name__) class NoSuchServiceError(Exception): """ Raised when a required service is not found. """ class ServiceRegistry(HasTraits): """ The service registry. """ implements(IServiceRegistry) #### IServiceRegistry interface ########################################## # An event that is fired when a service is registered. registered = Event # An event that is fired when a service is unregistered. unregistered = Event #### Private interface ################################################### # The services in the registry. # # { service_id : (protocol_name, obj, properties) } # # where: # # 'protocol_name' is the (possible dotted) name of the interface, type or # class that the object is registered against. # # 'obj' is the object that is registered (any old, Python object!). # # 'properties' is the arbitrary dictionary of properties that were # registered with the object. _services = Dict # The next service Id (service Ids are never persisted between process # invocations so this is simply an ever increasing integer!). _service_id = Int ########################################################################### # 'IServiceRegistry' interface. ########################################################################### def get_required_service(self, protocol, query='', minimize='',maximize=''): """ Return the service that matches the specified query. Raise a 'NoSuchServiceError' exception if no such service exists. """ service = self.get_service(protocol, query, minimize, maximize) if service is None: raise NoSuchServiceError(protocol) return service def get_service(self, protocol, query='', minimize='', maximize=''): """ Return at most one service that matches the specified query. """ services = self.get_services(protocol, query, minimize, maximize) if len(services) > 0: service = services[0] else: service = None return service def get_service_from_id(self, service_id): """ Return the service with the specified id. """ try: protocol, obj, properties = self._services[service_id] except KeyError: raise ValueError('no service with id <%d>' % service_id) return obj def get_services(self, protocol, query='', minimize='', maximize=''): """ Return all services that match the specified query. """ services = [] for service_id, (name, obj, properties) in self._services.items(): if self._get_protocol_name(protocol) == name: # If the protocol is a string then we need to import it! if isinstance(protocol, basestring): actual_protocol = ImportManager().import_symbol(protocol) # Otherwise, it is an actual protocol, so just use it! else: actual_protocol = protocol # If the registered service is actually a factory then use it # to create the actual object. obj = self._resolve_factory( actual_protocol, name, obj, properties, service_id ) # If a query was specified then only add the service if it # matches it! if len(query) == 0 or self._eval_query(obj, properties, query): services.append(obj) # Are we minimizing or maximising anything? If so then sort the list # of services by the specified attribute/property. if minimize != '': services.sort(None, lambda x: getattr(x, minimize)) elif maximize != '': services.sort(None, lambda x: getattr(x, maximize), reverse=True) return services def get_service_properties(self, service_id): """ Return the dictionary of properties associated with a service. """ try: protocol, obj, properties = self._services[service_id] properties = properties.copy() except KeyError: raise ValueError('no service with id <%d>' % service_id) return properties def register_service(self, protocol, obj, properties=None): """ Register a service. """ protocol_name = self._get_protocol_name(protocol) # Make sure each service gets its own properties dictionary. if properties is None: properties = {} service_id = self._next_service_id() self._services[service_id] = (protocol_name, obj, properties) self.registered = service_id logger.debug('service <%d> registered %s', service_id, protocol_name) return service_id def set_service_properties(self, service_id, properties): """ Set the dictionary of properties associated with a service. """ try: protocol, obj, old_properties = self._services[service_id] self._services[service_id] = protocol, obj, properties.copy() except KeyError: raise ValueError('no service with id <%d>' % service_id) return def unregister_service(self, service_id): """ Unregister a service. """ try: protocol, obj, properties = self._services.pop(service_id) self.unregistered = service_id logger.debug('service <%d> unregistered', service_id) except KeyError: raise ValueError('no service with id <%d>' % service_id) return ########################################################################### # Private interface. ########################################################################### def _create_namespace(self, service, properties): """ Create a namespace in which to evaluate a query. """ namespace = {} namespace.update(service.__dict__) namespace.update(properties) return namespace def _eval_query(self, service, properties, query): """ Evaluate a query over a single service. Return True if the service matches the query, otherwise return False. """ namespace = self._create_namespace(service, properties) try: result = eval(query, namespace) except: result = False return result def _get_protocol_name(self, protocol_or_name): """ Returns the full class name for a protocol. """ if isinstance(protocol_or_name, basestring): name = protocol_or_name else: name = '%s.%s' % ( protocol_or_name.__module__, protocol_or_name.__name__ ) return name def _is_service_factory(self, protocol, obj): """ Is the object a factory for services supporting the protocol? """ # fixme: Should we have a formal notion of service factory with an # appropriate API, or is this good enough? An API might have lifecycle # methods to both create and destroy the service?!? return not isinstance(obj, protocol) def _next_service_id(self): """ Returns the next service ID. """ self._service_id += 1 return self._service_id def _resolve_factory(self, protocol, name, obj, properties, service_id): """ If 'obj' is a factory then use it to create the actual service. """ # Is the registered service actually a service *factory*? if self._is_service_factory(protocol, obj): # A service factory is any callable that takes two arguments, the # first is the protocol, the second is the (possibly empty) # dictionary of properties that were registered with the service. # # If the factory is specified as a symbol path then import it. if isinstance(obj, basestring): obj = ImportManager().import_symbol(obj) obj = obj(**properties) # The resulting service object replaces the factory in the cache # (i.e. the factory will not get called again unless it is # unregistered first). self._services[service_id] = (name, obj, properties) return obj #### EOF ###################################################################### envisage-4.4.0/envisage/resource/0000755000175000017500000000000012252662115017460 5ustar davidcdavidc00000000000000envisage-4.4.0/envisage/resource/no_such_resource_error.py0000644000175000017500000000061612252661404024613 0ustar davidcdavidc00000000000000""" The exception raised when trying to open a non-existent resource. """ class NoSuchResourceError(Exception): """ The exception raised when trying to open a non-existent resource. """ def __init__(self, message=''): """ Constructor. """ Exception.__init__(self, message) return #### EOF ###################################################################### envisage-4.4.0/envisage/resource/api.py0000644000175000017500000000056512252661404020611 0ustar davidcdavidc00000000000000from i_resource_protocol import IResourceProtocol from i_resource_manager import IResourceManager from file_resource_protocol import FileResourceProtocol from http_resource_protocol import HTTPResourceProtocol from no_such_resource_error import NoSuchResourceError from package_resource_protocol import PackageResourceProtocol from resource_manager import ResourceManager envisage-4.4.0/envisage/resource/i_resource_protocol.py0000644000175000017500000000110312252661404024105 0ustar davidcdavidc00000000000000""" The interface for protocols that handle resource URLs. """ # Enthought library imports. from traits.api import Interface class IResourceProtocol(Interface): """ The interface for protocols that handle resource URLs. """ def file(self, address): """ Return a readable file-like object for the specified address. Raise a 'NoSuchResourceError' if the resource does not exist. e.g.:: protocol.file('acme.ui.workbench/preferences.ini') """ #### EOF ###################################################################### envisage-4.4.0/envisage/resource/file_resource_protocol.py0000644000175000017500000000224712252661404024606 0ustar davidcdavidc00000000000000""" A resource protocol for a local file system. """ # Standard library imports. import errno # Enthought library imports. from traits.api import HasTraits, implements # Local imports. from i_resource_protocol import IResourceProtocol from no_such_resource_error import NoSuchResourceError class FileResourceProtocol(HasTraits): """ A resource protocol for a local file system. """ implements(IResourceProtocol) ########################################################################### # 'IResourceProtocol' interface. ########################################################################### def file(self, address): """ Return a readable file-like object for the specified address. """ # Opened in binary mode to be consistent with package resources. This # means, for example, that line-endings will not be converted. try: f = file(address, 'rb') except IOError, e: if e.errno == errno.ENOENT: raise NoSuchResourceError(address) else: raise return f #### EOF ###################################################################### envisage-4.4.0/envisage/resource/__init__.py0000644000175000017500000000000012252661404021557 0ustar davidcdavidc00000000000000envisage-4.4.0/envisage/resource/i_resource_manager.py0000644000175000017500000000133312252661404023663 0ustar davidcdavidc00000000000000""" The resource manager interface. """ # Enthought library imports. from traits.api import Instance, Interface # Local imports. from i_resource_protocol import IResourceProtocol class IResourceManager(Interface): """ The resource manager interface. """ # The protocols used by the manager to resolve resource URLs. resource_protocols = Instance(IResourceProtocol) def file(self, url): """ Return a readable file-like object for the specified url. Raise a 'NoSuchResourceError' if the resource does not exist. e.g.:: manager.file('pkgfile://acme.ui.workbench/preferences.ini') """ #### EOF ###################################################################### envisage-4.4.0/envisage/resource/package_resource_protocol.py0000644000175000017500000000301312252661404025252 0ustar davidcdavidc00000000000000""" A resource protocol for package resources. """ # Standard library imports. import errno, pkg_resources # Enthought library imports. from traits.api import HasTraits, implements # Local imports. from i_resource_protocol import IResourceProtocol from no_such_resource_error import NoSuchResourceError class PackageResourceProtocol(HasTraits): """ A resource protocol for package resources. This protocol uses 'pkg_resources' to find and access resources. An address for this protocol is a string in the form:: 'package/resource' e.g:: 'acme.ui.workbench/preferences.ini' """ implements(IResourceProtocol) ########################################################################### # 'IResourceProtocol' interface. ########################################################################### def file(self, address): """ Return a readable file-like object for the specified address. """ first_forward_slash = address.index('/') package = address[:first_forward_slash] resource_name = address[first_forward_slash+1:] try: f = pkg_resources.resource_stream(package, resource_name) except IOError, e: if e.errno == errno.ENOENT: raise NoSuchResourceError(address) else: raise except ImportError: raise NoSuchResourceError(address) return f #### EOF ###################################################################### envisage-4.4.0/envisage/resource/http_resource_protocol.py0000644000175000017500000000204412252661404024641 0ustar davidcdavidc00000000000000""" A resource protocol for HTTP documents. """ # Enthought library imports. from traits.api import HasTraits, implements # Local imports. from i_resource_protocol import IResourceProtocol from no_such_resource_error import NoSuchResourceError class HTTPResourceProtocol(HasTraits): """ A resource protocol for HTTP documents. """ implements(IResourceProtocol) ########################################################################### # 'IResourceProtocol' interface. ########################################################################### def file(self, address): """ Return a readable file-like object for the specified address. """ # Do the import here 'cos I'm not sure how much this will actually # be used. import urllib2 try: f = urllib2.urlopen('http://' + address) except urllib2.HTTPError: raise NoSuchResourceError('http:://' + address) return f #### EOF ###################################################################### envisage-4.4.0/envisage/resource/tests/0000755000175000017500000000000012252662115020622 5ustar davidcdavidc00000000000000envisage-4.4.0/envisage/resource/tests/resource_manager_test_case.py0000644000175000017500000001071512252661404026553 0ustar davidcdavidc00000000000000""" Tests for the resource manager. """ # Standard library imports. import unittest import urllib2 import StringIO # Major package imports. from pkg_resources import resource_filename # Enthought library imports. from envisage.resource.api import ResourceManager from envisage.resource.api import NoSuchResourceError from traits.api import HasTraits, Int, Str # This module's package. PKG = 'envisage.resource.tests' # mimics urllib2.urlopen for some tests. # In setUp it replaces urllib2.urlopen for some tests, # and in tearDown, the regular urlopen is put back into place. def stubout_urlopen(url): if 'bogus' in url: raise urllib2.HTTPError(url, '404', 'No such resource', '', None) elif 'localhost' in url: return StringIO.StringIO('This is a test file.\n') else: raise ValueError('Unexpected URL %r in stubout_urlopen' % url) class ResourceManagerTestCase(unittest.TestCase): """ Tests for the resource manager. """ ########################################################################### # 'TestCase' interface. ########################################################################### def setUp(self): """ Prepares the test fixture before each test method is called. """ self.stored_urlopen = urllib2.urlopen urllib2.urlopen = stubout_urlopen return def tearDown(self): """ Called immediately after each test method has been called. """ urllib2.urlopen = self.stored_urlopen return ########################################################################### # Tests. ########################################################################### def test_file_resource(self): """ file resource """ rm = ResourceManager() # Get the filename of the 'api.py' file. filename = resource_filename('envisage.resource', 'api.py') # Open a file resource. f = rm.file('file://' + filename) self.assertNotEqual(f, None) contents = f.read() f.close() # Open the api file via the file system. g = file(filename, 'rb') self.assertEqual(g.read(), contents) g.close() return def test_no_such_file_resource(self): """ no such file resource """ rm = ResourceManager() # Open a file resource. self.failUnlessRaises( NoSuchResourceError, rm.file, 'file://../bogus.py' ) return def test_package_resource(self): """ package resource """ rm = ResourceManager() # Open a package resource. f = rm.file('pkgfile://envisage.resource/api.py') self.assertNotEqual(f, None) contents = f.read() f.close() # Get the filename of the 'api.py' file. filename = resource_filename('envisage.resource', 'api.py') # Open the api file via the file system. g = file(filename, 'rb') self.assertEqual(g.read(), contents) g.close() return def test_no_such_package_resource(self): """ no such package resource """ rm = ResourceManager() # Open a package resource. self.failUnlessRaises( NoSuchResourceError, rm.file, 'pkgfile://envisage.resource/bogus.py' ) self.failUnlessRaises( NoSuchResourceError, rm.file, 'pkgfile://completely.bogus/bogus.py' ) return def test_http_resource(self): """ http resource """ # Open an HTTP document resource. rm = ResourceManager() f = rm.file('http://localhost:1234/file.dat') self.assertNotEqual(f, None) contents = f.read() f.close() self.assertEquals(contents, 'This is a test file.\n') return def test_no_such_http_resource(self): """ no such http resource """ # Open an HTTP document resource. rm = ResourceManager() self.failUnlessRaises( NoSuchResourceError, rm.file, 'http://localhost:1234/bogus.dat' ) return def test_unknown_protocol(self): """ unknown protocol """ # Open an HTTP document resource. rm = ResourceManager() self.failUnlessRaises(ValueError, rm.file, 'bogus://foo/bar/baz') return # Entry point for stand-alone testing. if __name__ == '__main__': unittest.main() #### EOF ###################################################################### envisage-4.4.0/envisage/resource/tests/__init__.py0000644000175000017500000000000012252661404022721 0ustar davidcdavidc00000000000000envisage-4.4.0/envisage/resource/resource_manager.py0000644000175000017500000000366412252661404023364 0ustar davidcdavidc00000000000000""" The default resource manager. """ # Enthought library imports. from traits.api import Dict, HasTraits, Str, implements # Local imports. from i_resource_manager import IResourceManager from i_resource_protocol import IResourceProtocol class ResourceManager(HasTraits): """ The default resource manager. """ implements(IResourceManager) #### 'IResourceManager' interface ######################################### # The protocols used by the manager to resolve resource URLs. resource_protocols = Dict(Str, IResourceProtocol) ########################################################################### # 'IResourceManager' interface. ########################################################################### #### Trait initializers ################################################### def _resource_protocols_default(self): """ Trait initializer. """ # We do the import(s) here in case somebody wants a resource manager # that doesn't use the default protocol(s). from file_resource_protocol import FileResourceProtocol from http_resource_protocol import HTTPResourceProtocol from package_resource_protocol import PackageResourceProtocol resource_protocols = { 'file' : FileResourceProtocol(), 'http' : HTTPResourceProtocol(), 'pkgfile' : PackageResourceProtocol() } return resource_protocols #### Methods ############################################################## def file(self, url): """ Return a readable file-like object for the specified url. """ protocol_name, address = url.split('://') protocol = self.resource_protocols.get(protocol_name) if protocol is None: raise ValueError('unknown protocol in URL %s' % url) return protocol.file(address) #### EOF ###################################################################### envisage-4.4.0/envisage/category.py0000644000175000017500000000107212252661404020020 0ustar davidcdavidc00000000000000""" A definition of a category to be added to a class. """ # Enthought library imports. from traits.api import HasTraits, Str class Category(HasTraits): """ A definition of a category to be added to a class. """ #### 'Category' interface ################################################# # The name of the category class (the class that you want to add). class_name = Str # The name of the class that you want to add the category to. target_class_name = Str #### EOF ###################################################################### envisage-4.4.0/envisage/i_extension_point.py0000644000175000017500000000210712252661404021740 0ustar davidcdavidc00000000000000""" The interface for extension points. """ # Enthought library imports. from traits.api import Instance, Interface, Str, TraitType class IExtensionPoint(Interface): """ The interface for extension points. """ # A description of what the extension point is and does! (it is called # the slightly dubious, 'desc', instead of 'description', or, to be more # 'Pythonic', maybe 'doc' to match the 'desc' metadata used in traits). desc = Str # The extension point's unique identifier. # # Where 'unique' technically means 'unique within the extension registry', # but since the chances are that you will want to include extension points # from external sources, this really means 'globally unique'! Using the # Python package path might be useful here ;^) # # e.g. 'envisage.ui.workbench.views' id = Str # A trait type that describes what can be contributed to the extension # point. # # e.g. List(Str) trait_type = Instance(TraitType) #### EOF ###################################################################### envisage-4.4.0/envisage/service.py0000644000175000017500000000502612252661404017646 0ustar davidcdavidc00000000000000""" A trait type used to access services. """ # Standard library imports. import logging # Enthought library imports. from traits.api import TraitType # Logging. logger = logging.getLogger(__name__) class Service(TraitType): """ A trait type used to access services. Note that this is a trait *type* and hence does *NOT* have traits itself (i.e. it does *not* inherit from 'HasTraits'). """ ########################################################################### # 'object' interface. ########################################################################### def __init__( self, protocol=None, query='', minimize='', maximize='', **metadata ): """ Constructor. """ super(Service, self).__init__(**metadata) # The protocol that the service must provide. self._protocol = protocol # The optional query. self._query = query # The optional name of the trait/property to minimize. self._minimize = minimize # The optional name of the trait/property to maximize. self._maximize = maximize return ########################################################################### # 'TraitType' interface. ########################################################################### def get(self, obj, trait_name): """ Trait type getter. """ service_registry = self._get_service_registry(obj) obj = service_registry.get_service( self._protocol, self._query, self._minimize, self._maximize ) return obj def set(self, obj, name, value): """ Trait type setter. """ raise SystemError('Service traits cannot be set') ########################################################################### # Private interface. ########################################################################### def _get_service_registry(self, obj): """ Return the service registry in effect for an object. """ service_registry = getattr(obj, 'service_registry', None) if service_registry is None: raise ValueError( 'The "Service" trait type can only be used within objects ' \ 'that have a reference to a service registry via their ' \ '"service_registry" trait. ' \ 'Object <%s> Service protocol <%s>' % (obj, self._protocol) ) return service_registry #### EOF ###################################################################### envisage-4.4.0/envisage/twisted_application.py0000644000175000017500000000243312252661404022253 0ustar davidcdavidc00000000000000""" A non-GUI application with a twisted reactor event loop. Nothing is imported from twisted until the application is started so this module can safely live in the Envisage core without twisted being installed. """ # Standard library imports. import logging # Enthought library imports. from envisage.api import Application # Logging. logger = logging.getLogger(__name__) class TwistedApplication(Application): """ A non-GUI application with a twisted reactor event loop. """ def start(self): """ Start the application. """ started = super(TwistedApplication, self).start() # Don't start the event loop if the start was vetoed. if started: from twisted.internet import reactor logger.debug('---------- reactor starting ----------') reactor.run() return started def stop(self): """ Stop the application. """ stopped = super(TwistedApplication, self).stop() # Don't stop the event loop if the stop was vetoed. if stopped: from twisted.internet import reactor logger.debug('---------- reactor stopping ----------') reactor.stop() return stopped #### EOF ###################################################################### envisage-4.4.0/envisage/class_load_hook.py0000644000175000017500000000602512252661404021332 0ustar davidcdavidc00000000000000""" A hook to allow code be executed when a class is loaded. """ # Standard library imports. import sys # Enthought library imports. from traits.api import Callable, HasTraits, MetaHasTraits, Str class ClassLoadHook(HasTraits): """ A hook to allow code to be executed when a class is loaded. If the class is *already* loaded when the 'connect' method is called then the code is executed immediately. """ #### 'ClassLoadHook' interface ############################################ # The name of the class. When this class is loaded the 'on_class_loaded' # method is called. class_name = Str # A callable that will be executed when the class is loaded. The callable # must take a single argument which will be the loaded class. # # This is used in the default implementation of 'on_class_loaded'. If you # override that, then you don't have to set to this trait. on_load = Callable ########################################################################### # 'ClassLoadHook' interface. ########################################################################### def connect(self): """ Connect the load hook to listen for the class being loaded. """ MetaHasTraits.add_listener(self.on_class_loaded, self.class_name) # If the class has already been loaded then run the code now! cls = self._get_class(self.class_name) if cls is not None: self.on_class_loaded(cls) return def disconnect(self): """ Disconnect the load hook. """ MetaHasTraits.remove_listener(self.on_class_loaded, self.class_name) return def on_class_loaded(self, cls): """ This method is called when the class is loaded. If 'self.on_load' is not None, it calls 'self.on_load(cls)'. """ if self.on_load is not None: self.on_load(cls) return ########################################################################### # Private interface. ########################################################################### def _get_class(self, class_path): """ Returns the class defined by *class_path*. Returns **None** if the class has not yet been loaded. """ # Only check if the class name has at least a partial hierarchy. # # fixme: Comment should say why! if '.' in class_path: components = class_path.split('.') module_name = '.'.join(components[:-1]) class_name = components[-1] # The class is loaded if its module has been imported and the class # is defined in the module dictionary. module = sys.modules.get(module_name, None) if module is not None and hasattr(module, class_name): klass = getattr(module, class_name) else: klass = None else: klass = None return klass #### EOF ###################################################################### envisage-4.4.0/envisage/extension_provider.py0000644000175000017500000000312012252661404022125 0ustar davidcdavidc00000000000000""" The default base class for extension providers. """ # Enthought library imports. from traits.api import Event, HasTraits, implements # Local imports. from extension_point_changed_event import ExtensionPointChangedEvent from i_extension_provider import IExtensionProvider class ExtensionProvider(HasTraits): """ The default base class for extension providers. """ implements(IExtensionProvider) #### 'IExtensionProvider' interface ####################################### # The event fired when one of the provider's extension points has been # changed (where 'changed' means that the provider has added or removed # contributions to or from an extension point). extension_point_changed = Event(ExtensionPointChangedEvent) def get_extension_points(self): """ Return the extension points offered by the provider. """ return [] def get_extensions(self, extension_point_id): """ Return the provider's extensions to an extension point. """ return [] ##### Protected 'ExtensionProvider' interface ############################# def _fire_extension_point_changed( self, extension_point_id, added, removed, index ): """ Fire an extension point changed event. """ self.extension_point_changed = ExtensionPointChangedEvent( extension_point_id = extension_point_id, added = added, removed = removed, index = index ) return #### EOF ###################################################################### envisage-4.4.0/envisage/tests/0000755000175000017500000000000012252662115016773 5ustar davidcdavidc00000000000000envisage-4.4.0/envisage/tests/mutable_extension_registry.py0000644000175000017500000000410712252661404025024 0ustar davidcdavidc00000000000000""" A mutable, manually populated extension registry used for testing. """ # Enthought library imports. from envisage.api import ExtensionRegistry, UnknownExtension class MutableExtensionRegistry(ExtensionRegistry): """ A mutable, manually populated extension registry used for testing. """ ########################################################################### # 'MutableExtensionRegistry' interface. ########################################################################### def add_extension(self, extension_point_id, extension): """ Contribute an extension to an extension point. """ self.add_extensions(extension_point_id, [extension]) return def add_extensions(self, extension_point_id, extensions): """ Contribute a list of extensions to an extension point. """ self._check_extension_point(extension_point_id) old = self._get_extensions(extension_point_id) index = len(old) old.extend(extensions) # Let any listeners know that the extensions have been added. refs = self._get_listener_refs(extension_point_id) self._call_listeners(refs, extension_point_id, extensions, [], index) return def remove_extension(self, extension_point_id, extension): """ Remove a contribution from an extension point. """ self.remove_extensions(extension_point_id, [extension]) return def remove_extensions(self, extension_point_id, extensions): """ Remove a list of contributions from an extension point. """ for extension in extensions: try: self._get_extensions(extension_point_id).remove(extension) except ValueError: raise UnknownExtension(extension_point_id, extension) # Let any listeners know that the extensions have been removed. refs = self._get_listener_refs(extension_point_id) self._call_listeners(refs, extension_point_id, [], extensions, None) return #### EOF ###################################################################### envisage-4.4.0/envisage/tests/plugin_manager_test_case.py0000644000175000017500000002076412252661404024400 0ustar davidcdavidc00000000000000""" Tests for the plugin manager. """ # Enthought library imports. from envisage.api import Plugin, PluginManager from traits.api import Bool from traits.testing.unittest_tools import unittest class SimplePlugin(Plugin): """ A simple plugin. """ #### 'SimplePlugin' interface ############################################# started = Bool(False) stopped = Bool(False) ########################################################################### # 'IPlugin' interface. ########################################################################### def start(self): """ Start the plugin. """ self.started = True self.stopped = False return def stop(self): """ Stop the plugin. """ self.started = False self.stopped = True return class BadPlugin(Plugin): """ A plugin that just causes trouble ;^). """ ########################################################################### # 'IPlugin' interface. ########################################################################### def start(self): """ Start the plugin. """ raise 1/0 def stop(self): """ Stop the plugin. """ raise 1/0 class PluginManagerTestCase(unittest.TestCase): """ Tests for the plugin manager. """ ########################################################################### # 'TestCase' interface. ########################################################################### def setUp(self): """ Prepares the test fixture before each test method is called. """ return def tearDown(self): """ Called immediately after each test method has been called. """ return ########################################################################### # Tests. ########################################################################### def test_get_plugin(self): """ get plugin """ simple_plugin = SimplePlugin() plugin_manager = PluginManager(plugins=[simple_plugin]) # Get the plugin. plugin = plugin_manager.get_plugin(simple_plugin.id) self.assertEqual(plugin, simple_plugin) # Try to get a non-existent plugin. self.assertEqual(None, plugin_manager.get_plugin('bogus')) return def test_iteration_over_plugins(self): """ iteration over plugins """ simple_plugin = SimplePlugin() bad_plugin = BadPlugin() plugin_manager = PluginManager(plugins=[simple_plugin, bad_plugin]) # Iterate over the plugin manager's plugins. plugins = [] for plugin in plugin_manager: plugins.append(plugin) self.assertEqual([simple_plugin, bad_plugin], plugins) return def test_start_and_stop(self): """ start and stop """ simple_plugin = SimplePlugin() plugin_manager = PluginManager(plugins=[simple_plugin]) # Start the plugin manager. This starts all of the plugin manager's # plugins. plugin_manager.start() # Make sure the plugin was started. self.assertEqual(True, simple_plugin.started) # Stop the plugin manager. This stops all of the plugin manager's # plugins. plugin_manager.stop() # Make sure the plugin was stopped. self.assertEqual(True, simple_plugin.stopped) return def test_start_and_stop_errors(self): """ start and stop errors """ simple_plugin = SimplePlugin() bad_plugin = BadPlugin() plugin_manager = PluginManager(plugins=[simple_plugin, bad_plugin]) # Start the plugin manager. This starts all of the plugin manager's # plugins. self.failUnlessRaises(ZeroDivisionError, plugin_manager.start) # Stop the plugin manager. This stops all of the plugin manager's # plugins. self.failUnlessRaises(ZeroDivisionError, plugin_manager.stop) # Try to start a non-existent plugin. self.failUnlessRaises( SystemError, plugin_manager.start_plugin, plugin_id='bogus' ) # Try to stop a non-existent plugin. self.failUnlessRaises( SystemError, plugin_manager.stop_plugin, plugin_id='bogus' ) return def test_only_include_plugins_whose_ids_are_in_the_include_list(self): # Note that the items in the list use the 'fnmatch' syntax for matching # plugins Ids. include = ['foo', 'bar'] plugin_manager = PluginManager( include = include, plugins = [ SimplePlugin(id='foo'), SimplePlugin(id='bar'), SimplePlugin(id='baz') ] ) # The Ids of the plugins that we expect the plugin manager to find. expected = ['foo', 'bar'] # Make sure the plugin manager found only the required plugins and that # it starts and stops them correctly.. self._test_start_and_stop(plugin_manager, expected) return def test_only_include_plugins_matching_a_wildcard_in_the_include_list(self): # Note that the items in the list use the 'fnmatch' syntax for matching # plugins Ids. include = ['b*'] plugin_manager = PluginManager( include = include, plugins = [ SimplePlugin(id='foo'), SimplePlugin(id='bar'), SimplePlugin(id='baz') ] ) # The Ids of the plugins that we expect the plugin manager to find. expected = ['bar', 'baz'] # Make sure the plugin manager found only the required plugins and that # it starts and stops them correctly.. self._test_start_and_stop(plugin_manager, expected) return def test_ignore_plugins_whose_ids_are_in_the_exclude_list(self): # Note that the items in the list use the 'fnmatch' syntax for matching # plugins Ids. exclude = ['foo', 'baz'] plugin_manager = PluginManager( exclude = exclude, plugins = [ SimplePlugin(id='foo'), SimplePlugin(id='bar'), SimplePlugin(id='baz') ] ) # The Ids of the plugins that we expect the plugin manager to find. expected = ['bar'] # Make sure the plugin manager found only the required plugins and that # it starts and stops them correctly.. self._test_start_and_stop(plugin_manager, expected) return def test_ignore_plugins_matching_a_wildcard_in_the_exclude_list(self): # Note that the items in the list use the 'fnmatch' syntax for matching # plugins Ids. exclude = ['b*'] plugin_manager = PluginManager( exclude = exclude, plugins = [ SimplePlugin(id='foo'), SimplePlugin(id='bar'), SimplePlugin(id='baz') ] ) # The Ids of the plugins that we expect the plugin manager to find. expected = ['foo'] # Make sure the plugin manager found only the required plugins and that # it starts and stops them correctly.. self._test_start_and_stop(plugin_manager, expected) return #### Private protocol ##################################################### def _test_start_and_stop(self, plugin_manager, expected): """ Make sure the plugin manager starts and stops the expected plugins. """ # Make sure the plugin manager found only the required plugins. self.assertEqual(expected, [plugin.id for plugin in plugin_manager]) # Start the plugin manager. This starts all of the plugin manager's # plugins. plugin_manager.start() # Make sure all of the the plugins were started. for id in expected: plugin = plugin_manager.get_plugin(id) self.assertNotEqual(None, plugin) self.assertEqual(True, plugin.started) # Stop the plugin manager. This stops all of the plugin manager's # plugins. plugin_manager.stop() # Make sure all of the the plugins were stopped. for id in expected: plugin = plugin_manager.get_plugin(id) self.assertNotEqual(None, plugin) self.assertEqual(True, plugin.stopped) return # Entry point for stand-alone testing. if __name__ == '__main__': unittest.main() #### EOF ###################################################################### envisage-4.4.0/envisage/tests/extension_registry_test_case.py0000644000175000017500000001271712252661404025353 0ustar davidcdavidc00000000000000""" Tests for the base extension registry. """ # Enthought library imports. from envisage.api import Application, ExtensionPoint from envisage.api import ExtensionRegistry, UnknownExtensionPoint from traits.api import List from traits.testing.unittest_tools import unittest class ExtensionRegistryTestCase(unittest.TestCase): """ Tests for the base extension registry. """ ########################################################################### # 'TestCase' interface. ########################################################################### def setUp(self): """ Prepares the test fixture before each test method is called. """ # We do all of the testing via the application to make sure it offers # the same interface! self.registry = Application(extension_registry=ExtensionRegistry()) return def tearDown(self): """ Called immediately after each test method has been called. """ return ########################################################################### # Tests. ########################################################################### def test_empty_registry(self): """ empty registry """ registry = self.registry # Make sure there are no extensions. extensions = registry.get_extensions('my.ep') self.assertEqual(0, len(extensions)) # Make sure there are no extension points. extension_points = registry.get_extension_points() self.assertEqual(0, len(extension_points)) return def test_add_extension_point(self): """ add extension point """ registry = self.registry # Add an extension *point*. registry.add_extension_point(self._create_extension_point('my.ep')) # Make sure there's NO extensions. extensions = registry.get_extensions('my.ep') self.assertEqual(0, len(extensions)) # Make sure there's one and only one extension point. extension_points = registry.get_extension_points() self.assertEqual(1, len(extension_points)) self.assertEqual('my.ep', extension_points[0].id) return def test_get_extension_point(self): """ get extension point """ registry = self.registry # Add an extension *point*. registry.add_extension_point(self._create_extension_point('my.ep')) # Make sure we can get it. extension_point = registry.get_extension_point('my.ep') self.assertNotEqual(None, extension_point) self.assertEqual('my.ep', extension_point.id) return def test_remove_empty_extension_point(self): """ remove empty_extension point """ registry = self.registry # Add an extension point... registry.add_extension_point(self._create_extension_point('my.ep')) # ...and remove it! registry.remove_extension_point('my.ep') # Make sure there are no extension points. extension_points = registry.get_extension_points() self.assertEqual(0, len(extension_points)) return def test_remove_non_empty_extension_point(self): """ remove non-empty extension point """ registry = self.registry # Add an extension point... registry.add_extension_point(self._create_extension_point('my.ep')) # ... with some extensions... registry.set_extensions('my.ep', [42]) # ...and remove it! registry.remove_extension_point('my.ep') # Make sure there are no extension points. extension_points = registry.get_extension_points() self.assertEqual(0, len(extension_points)) # And that the extensions are gone too. self.assertEqual([], registry.get_extensions('my.ep')) return def test_remove_non_existent_extension_point(self): """ remove non existent extension point """ registry = self.registry self.failUnlessRaises( UnknownExtensionPoint, registry.remove_extension_point, 'my.ep' ) return def test_remove_non_existent_listener(self): """ remove non existent listener """ registry = self.registry def listener(registry, extension_point, added, removed, index): """ Called when an extension point has changed. """ self.listener_called = (registry, extension_point, added, removed) return self.failUnlessRaises( ValueError, registry.remove_extension_point_listener, listener ) return def test_set_extensions(self): """ set extensions """ registry = self.registry # Add an extension *point*. registry.add_extension_point(self._create_extension_point('my.ep')) # Set some extensions. registry.set_extensions('my.ep', [1, 2, 3]) # Make sure we can get them. self.assertEqual([1, 2, 3], registry.get_extensions('my.ep')) return ########################################################################### # Private interface. ########################################################################### def _create_extension_point(self, id, trait_type=List, desc=''): """ Create an extension point. """ return ExtensionPoint(id=id, trait_type=trait_type, desc=desc) # Entry point for stand-alone testing. if __name__ == '__main__': unittest.main() #### EOF ###################################################################### envisage-4.4.0/envisage/tests/i_foo.py0000644000175000017500000000033712252661404020443 0ustar davidcdavidc00000000000000""" A test class used to test adapters. """ # Enthought library imports. from traits.api import Interface class IFoo(Interface): pass #### EOF ###################################################################### envisage-4.4.0/envisage/tests/safeweakref_test_case.py0000644000175000017500000001011112252661404023654 0ustar davidcdavidc00000000000000""" Tests for safe weakrefs. """ # Standard library imports. import weakref # Enthought library imports. from envisage.safeweakref import ref from traits.api import HasTraits from traits.testing.unittest_tools import unittest class SafeWeakrefTestCase(unittest.TestCase): """ Tests for safe weakrefs. """ ########################################################################### # 'TestCase' interface. ########################################################################### def setUp(self): """ Prepares the test fixture before each test method is called. """ return def tearDown(self): """ Called immediately after each test method has been called. """ return ########################################################################### # Tests. ########################################################################### def test_can_create_weakref_to_bound_method(self): class Foo(HasTraits): def method(self): self.method_called = True f = Foo() # Get a weak reference to a bound method. r = ref(f.method) self.assertNotEqual(None, r()) # Make sure we can call it. r()() self.assert_(f.method_called) # Delete the object to delete the method! del f # The reference should now return None. self.assertEqual(None, r()) return def test_two_weakrefs_to_bound_method_are_identical(self): class Foo(HasTraits): def method(self): pass f = Foo() self.assert_(ref(f.method) is ref(f.method)) return def test_internal_cache_is_weak_too(self): # smell: Fragile test because we are reaching into the internals of the # object under test. # # I can't see a (clean!) way around this without adding something to # the public API that would only exist for testing, but in terms of # 'bang for the buck' I think this is good enough despite the # fragility. cache = ref._cache class Foo(HasTraits): def method(self): pass f = Foo() # Get the length of the cache before we do anything. len_cache = len(cache) # Create a weak reference to the bound method and make sure that # exactly one item has been added to the cache. r = ref(f.method) self.assertEqual(len_cache + 1, len(cache)) # Delete the instance! del f # Our `ref` should now reference nothing... self.assertEqual(None, r()) # ... and the cache should be back to its original size! self.assertEqual(len_cache, len(cache)) return def test_two_weakrefs_to_bound_method_are_equal(self): class Foo(HasTraits): def method(self): pass f = Foo() # Make sure that two references to the same method compare as equal. r1 = ref(f.method) r2 = ref(f.method) self.assertEqual(r1, r2) # Make sure that a reference compares as unequal to non-references! self.assert_(not r1 == 99) return def test_two_weakrefs_to_bound_method_hash_equally(self): class Foo(HasTraits): def method(self): pass f = Foo() # Make sure we can hash the references. r1 = ref(f.method) r2 = ref(f.method) self.assertEqual(hash(r1), hash(r2)) # Make sure we can hash non-bound methods. r1 = ref(Foo) r2 = ref(Foo) self.assertEqual(hash(r1), hash(r2)) return def test_get_builtin_weakref_for_non_bound_method(self): class Foo(HasTraits): pass f = Foo() # Get a weak reference to something that is not a bound method. r = ref(f) self.assertEqual(weakref.ref, type(r)) return # Entry point for stand-alone testing. if __name__ == '__main__': unittest.main() #### EOF ###################################################################### envisage-4.4.0/envisage/tests/composite_plugin_manager_test_case.py0000644000175000017500000001401112252661404026446 0ustar davidcdavidc00000000000000""" Tests for the composite plugin manager. """ from envisage.application import Application from envisage.composite_plugin_manager import CompositePluginManager from envisage.plugin_manager import PluginManager from envisage.plugin import Plugin from traits.api import Bool from traits.testing.unittest_tools import unittest class SimplePlugin(Plugin): """ A simple plugin. """ #### 'SimplePlugin' protocol ############################################### started = Bool(False) stopped = Bool(False) #### 'IPlugin' protocol ################################################### def start(self): """ Start the plugin. """ self.started = True self.stopped = False return def stop(self): """ Stop the plugin. """ self.started = False self.stopped = True return class CompositePluginManagerTestCase(unittest.TestCase): """ Tests for the composite plugin manager. """ #### 'unittest.TestCase' protocol ######################################### def setUp(self): """ Prepares the test fixture before each test method is called. """ return def tearDown(self): """ Called immediately after each test method has been called. """ return #### Tests ################################################################ def test_find_no_plugins_if_there_are_no_plugin_managers(self): plugin_manager = CompositePluginManager() ids = [plugin.id for plugin in plugin_manager] self.assertEqual(0, len(ids)) return def test_find_no_plugins_if_there_are_no_plugins_in_plugin_managers(self): plugin_manager = CompositePluginManager( plugin_managers=[PluginManager(), PluginManager()] ) ids = [plugin.id for plugin in plugin_manager] self.assertEqual(0, len(ids)) return def test_find_plugins_in_a_single_plugin_manager(self): plugin_manager = CompositePluginManager( plugin_managers=[ PluginManager( plugins=[SimplePlugin(id='red'), SimplePlugin(id='yellow')] ) ] ) ids = [plugin.id for plugin in plugin_manager] self.assertEqual(2, len(ids)) self.assertIn('red', ids) self.assertIn('yellow', ids) self._test_start_and_stop(plugin_manager, ['red', 'yellow']) return def test_find_plugins_in_a_multiple_plugin_managers(self): plugin_manager = CompositePluginManager( plugin_managers=[ PluginManager( plugins=[SimplePlugin(id='red'), SimplePlugin(id='yellow')] ), PluginManager( plugins=[SimplePlugin(id='green')] ) ] ) ids = [plugin.id for plugin in plugin_manager] self.assertEqual(3, len(ids)) self.assertIn('red', ids) self.assertIn('yellow', ids) self.assertIn('green', ids) self._test_start_and_stop(plugin_manager, ['red', 'yellow', 'green']) return def test_application_gets_propogated_to_plugin_managers(self): application = Application() composite_plugin_manager = CompositePluginManager( application = application, plugin_managers = [PluginManager(), PluginManager()] ) for plugin_manager in composite_plugin_manager.plugin_managers: self.assertEqual(application, plugin_manager.application) return def test_propogate_plugin_added_or_remove_events_from_plugin_managers(self): a = PluginManager() b = PluginManager() composite_plugin_manager = CompositePluginManager( plugin_managers = [a, b] ) composite_plugin_manager._plugins def added(obj, trait_name, old, new): added.count += 1 added.count = 0 composite_plugin_manager.on_trait_change(added, 'plugin_added') def removed(obj, trait_name, old, new): removed.count += 1 removed.count = 0 composite_plugin_manager.on_trait_change(removed, 'plugin_removed') a.add_plugin(Plugin(id='foo')) self.assertEqual(1, self._plugin_count(composite_plugin_manager)) a.remove_plugin(a.get_plugin('foo')) self.assertEqual(0, self._plugin_count(composite_plugin_manager)) return #### Private protocol ##################################################### def _plugin_count(self, plugin_manager): """ Return how many plugins the plugin manager contains. """ count = 0 for plugin in plugin_manager: count += 1 return count def _test_start_and_stop(self, plugin_manager, expected): """ Make sure the plugin manager starts and stops the expected plugins. """ # Make sure the plugin manager found only the required plugins. self.assertEqual(expected, [plugin.id for plugin in plugin_manager]) # Start the plugin manager. This starts all of the plugin manager's # plugins. plugin_manager.start() # Make sure all of the the plugins were started. for id in expected: plugin = plugin_manager.get_plugin(id) self.assertNotEqual(None, plugin) self.assertEqual(True, plugin.started) # Stop the plugin manager. This stops all of the plugin manager's # plugins. plugin_manager.stop() # Make sure all of the the plugins were stopped. for id in expected: plugin = plugin_manager.get_plugin(id) self.assertNotEqual(None, plugin) self.assertEqual(True, plugin.stopped) return # Entry point for stand-alone testing. if __name__ == '__main__': unittest.main() #### EOF ###################################################################### envisage-4.4.0/envisage/tests/service_registry_test_case.py0000644000175000017500000004065612252661404025002 0ustar davidcdavidc00000000000000""" Tests for the service registry. """ # Standard library imports. import sys # Enthought library imports. from envisage.api import Application, ServiceRegistry, NoSuchServiceError from traits.api import HasTraits, Int, Interface, implements from traits.testing.unittest_tools import unittest # This module's package. PKG = 'envisage.tests' def service_factory(**properties): """ A factory for foos. """ return HasTraits(**properties) class ServiceRegistryTestCase(unittest.TestCase): """ Tests for the service registry. """ ########################################################################### # 'TestCase' interface. ########################################################################### def setUp(self): """ Prepares the test fixture before each test method is called. """ # We do all of the testing via the application to make sure it offers # the same interface! self.service_registry = Application(service_registry=ServiceRegistry()) # module 'foo' need to be cleared out when this test is run by nose, # because other tests also import foo. if PKG + '.foo' in sys.modules: del sys.modules[PKG + '.foo'] return def tearDown(self): """ Called immediately after each test method has been called. """ return ########################################################################### # Tests. ########################################################################### def test_should_get_required_service(self): class Foo(HasTraits): price = Int foo = Foo() # Register a service factory. self.service_registry.register_service(Foo, foo) service = self.service_registry.get_required_service(Foo) self.assertIs(foo, service) return def test_should_get_exception_if_required_service_is_missing(self): class IFoo(Interface): price = Int with self.assertRaises(NoSuchServiceError): self.service_registry.get_required_service(IFoo) return def test_imported_service_factory(self): """ imported service factory """ class IFoo(Interface): price = Int # Register a service factory. self.service_registry.register_service( HasTraits, PKG + '.service_registry_test_case.service_factory', {'price' : 100} ) # Create a query that matches the registered object. service = self.service_registry.get_service(HasTraits, 'price <= 100') self.assertNotEqual(None, service) self.assertEqual(HasTraits, type(service)) # This shows that the properties were passed in to the factory. self.assertEqual(100, service.price) # Make sure that the object created by the factory is cached (i.e. we # get the same object back from now on!). service2 = self.service_registry.get_service(HasTraits, 'price <= 100') self.assert_(service is service2) return def test_function_service_factory(self): """ function service factory """ class IFoo(Interface): price = Int class Foo(HasTraits): implements(IFoo) price = Int def foo_factory(**properties): """ A factory for foos. """ return Foo(**properties) # Register a service factory. self.service_registry.register_service( IFoo, foo_factory, {'price' : 100} ) # Create a query that matches the registered object. service = self.service_registry.get_service(IFoo, 'price <= 100') self.assertNotEqual(None, service) self.assertEqual(Foo, type(service)) # Make sure that the object created by the factory is cached (i.e. we # get the same object back from now on!). service2 = self.service_registry.get_service(IFoo, 'price <= 100') self.assert_(service is service2) return def test_lazy_function_service_factory(self): """ lazy function service factory """ # Register a service factory by name. def foo_factory(**properties): """ A factory for foos. """ from envisage.tests.foo import Foo foo_factory.foo = Foo() return foo_factory.foo i_foo = PKG + '.i_foo.IFoo' foo = PKG + '.foo' self.service_registry.register_service(i_foo, foo_factory) # Get rid of the 'foo' module (used in other tests). if foo in sys.modules: del sys.modules[foo] # Make sure that we haven't imported the 'foo' module. self.assert_(foo not in sys.modules) # Look up a non-existent service. services = self.service_registry.get_services('bogus.IBogus') # Make sure that we *still* haven't imported the 'foo' module. self.assert_(foo not in sys.modules) # Look it up again. services = self.service_registry.get_services(i_foo) self.assertEqual([foo_factory.foo], services) self.assert_(foo in sys.modules) # Clean up! del sys.modules[foo] return def test_lazy_bound_method_service_factory(self): """ lazy bound method service factory """ i_foo = PKG + '.i_foo.IFoo' foo = PKG + '.foo' class ServiceProvider(HasTraits): """ A class that provides a service. This is used to make sure a bound method can be used as a service factory. """ # Register a service factory by name. def foo_factory(self, **properties): """ A factory for foos. """ from envisage.tests.foo import Foo self.foo = Foo() return self.foo sp = ServiceProvider() self.service_registry.register_service(i_foo, sp.foo_factory) # Get rid of the 'foo' module (used in other tests). if foo in sys.modules: del sys.modules[foo] # Make sure that we haven't imported the 'foo' module. self.assert_(foo not in sys.modules) # Look up a non-existent service. services = self.service_registry.get_services('bogus.IBogus') # Make sure that we *still* haven't imported the 'foo' module. self.assert_(foo not in sys.modules) # Look up the service. services = self.service_registry.get_services(i_foo) self.assertEqual([sp.foo], services) self.assert_(foo in sys.modules) # Clean up! del sys.modules[foo] return def test_get_services(self): """ get services """ class IFoo(Interface): pass class Foo(HasTraits): implements(IFoo) # Register two services. foo = Foo() self.service_registry.register_service(IFoo, foo) foo = Foo() self.service_registry.register_service(IFoo, foo) # Look it up again. services = self.service_registry.get_services(IFoo) self.assertEqual(2, len(services)) class IBar(Interface): pass # Lookup a non-existent service. services = self.service_registry.get_services(IBar) self.assertEqual([], services) return def test_get_services_with_strings(self): """ get services with strings """ from envisage.tests.foo import Foo # Register a couple of services using a string protocol name. protocol_name = 'envisage.tests.foo.IFoo' self.service_registry.register_service(protocol_name, Foo()) self.service_registry.register_service(protocol_name, Foo()) # Look them up using the same string! services = self.service_registry.get_services(protocol_name) self.assertEqual(2, len(services)) return def test_get_services_with_query(self): """ get services with query """ class IFoo(Interface): price = Int class Foo(HasTraits): implements(IFoo) price = Int # Register two services. # # This one shows how the object's attributes are used when evaluating # a query. foo = Foo(price=100) self.service_registry.register_service(IFoo, foo) # This one shows how properties can be specified that *take precedence* # over the object's attributes when evaluating a query. goo = Foo(price=10) self.service_registry.register_service(IFoo, goo, {'price' : 200}) # Create a query that doesn't match any registered object. services = self.service_registry.get_services(IFoo, 'color == "red"') self.assertEqual([], services) # Create a query that matches one of the registered objects. services = self.service_registry.get_services(IFoo, 'price <= 100') self.assertEqual([foo], services) # Create a query that matches both registered objects. services = self.service_registry.get_services(IFoo, 'price >= 100') self.assert_(foo in services) self.assert_(goo in services) self.assertEqual(2, len(services)) class IBar(Interface): pass # Lookup a non-existent service. services = self.service_registry.get_services(IBar, 'price <= 100') self.assertEqual([], services) return def test_get_service(self): """ get service """ class IFoo(Interface): pass class Foo(HasTraits): implements(IFoo) # Register a couple of services. foo = Foo() self.service_registry.register_service(IFoo, foo) goo = Foo() self.service_registry.register_service(IFoo, goo) # Look up one of them! service = self.service_registry.get_service(IFoo) self.assert_(foo is service or goo is service) class IBar(Interface): pass # Lookup a non-existent service. service = self.service_registry.get_service(IBar) self.assertEqual(None, service) return def test_get_service_with_query(self): """ get service with query """ class IFoo(Interface): price = Int class Foo(HasTraits): implements(IFoo) price = Int # Register two services. # # This one shows how the object's attributes are used when evaluating # a query. foo = Foo(price=100) self.service_registry.register_service(IFoo, foo) # This one shows how properties can be specified that *take precedence* # over the object's attributes when evaluating a query. goo = Foo(price=10) self.service_registry.register_service(IFoo, goo, {'price' : 200}) # Create a query that doesn't match any registered object. service = self.service_registry.get_service(IFoo, 'price < 100') self.assertEqual(None, service) # Create a query that matches one of the registered objects. service = self.service_registry.get_service(IFoo, 'price <= 100') self.assertEqual(foo, service) # Create a query that matches both registered objects. service = self.service_registry.get_service(IFoo, 'price >= 100') self.assert_(foo is service or goo is service) class IBar(Interface): pass # Lookup a non-existent service. service = self.service_registry.get_service(IBar, 'price <= 100') self.assertEqual(None, service) return def test_get_and_set_service_properties(self): """ get and set service properties """ class IFoo(Interface): price = Int class Foo(HasTraits): implements(IFoo) price = Int # Register two services. # # This one has no properties. foo = Foo(price=100) foo_id = self.service_registry.register_service(IFoo, foo) # This one has properties. goo = Foo(price=10) goo_id = self.service_registry.register_service( IFoo, goo, {'price' : 200} ) # Get the properties. foo_properties = self.service_registry.get_service_properties(foo_id) self.assertEqual({}, foo_properties) goo_properties = self.service_registry.get_service_properties(goo_id) self.assertEqual(200, goo_properties['price']) # Update the properties. foo_properties['price'] = 300 goo_properties['price'] = 500 # Set the properties. self.service_registry.set_service_properties(foo_id, foo_properties) self.service_registry.set_service_properties(goo_id, goo_properties) # Get the properties again. foo_properties = self.service_registry.get_service_properties(foo_id) self.assertEqual(300, foo_properties['price']) goo_properties = self.service_registry.get_service_properties(goo_id) self.assertEqual(500, goo_properties['price']) # Try to get the properties of a non-existent service. self.failUnlessRaises( ValueError, self.service_registry.get_service_properties, -1 ) # Try to set the properties of a non-existent service. self.failUnlessRaises( ValueError, self.service_registry.set_service_properties, -1, {} ) return def test_unregister_service(self): """ unregister service """ class IFoo(Interface): price = Int class Foo(HasTraits): implements(IFoo) price = Int # Register two services. # # This one shows how the object's attributes are used when evaluating # a query. foo = Foo(price=100) foo_id = self.service_registry.register_service(IFoo, foo) # This one shows how properties can be specified that *take precedence* # over the object's attributes when evaluating a query. goo = Foo(price=10) goo_id = self.service_registry.register_service( IFoo, goo, {'price' : 200} ) # Create a query that doesn't match any registered object. service = self.service_registry.get_service(IFoo, 'price < 100') self.assertEqual(None, service) # Create a query that matches one of the registered objects. service = self.service_registry.get_service(IFoo, 'price <= 100') self.assertEqual(foo, service) # Create a query that matches both registered objects. service = self.service_registry.get_service(IFoo, 'price >= 100') self.assert_(foo is service or goo is service) #### Now do some unregistering! #### # Unregister 'foo'. self.service_registry.unregister_service(foo_id) # This query should no longer match any of the registered objects. service = self.service_registry.get_service(IFoo, 'price <= 100') self.assertEqual(None, service) # Unregister 'goo'. self.service_registry.unregister_service(goo_id) # This query should no longer match any of the registered objects. service = self.service_registry.get_service(IFoo, 'price >= 100') self.assertEqual(None, service) # Try to unregister a non-existent service. self.failUnlessRaises( ValueError, self.service_registry.unregister_service, -1 ) return def test_minimize_and_maximize(self): """ minimize and maximize """ class IFoo(Interface): price = Int class Foo(HasTraits): implements(IFoo) price = Int # Register some objects with various prices. x = Foo(price=10) y = Foo(price=5) z = Foo(price=100) for foo in [x, y, z]: self.service_registry.register_service(IFoo, foo) # Find the service with the lowest price. service = self.service_registry.get_service(IFoo, minimize='price') self.assertNotEqual(None, service) self.assertEqual(Foo, type(service)) self.assertEqual(y, service) # Find the service with the highest price. service = self.service_registry.get_service(IFoo, maximize='price') self.assertNotEqual(None, service) self.assertEqual(Foo, type(service)) self.assertEqual(z, service) return # Entry point for stand-alone testing. if __name__ == '__main__': unittest.main() #### EOF ###################################################################### envisage-4.4.0/envisage/tests/event_tracker.py0000644000175000017500000000476512252661404022215 0ustar davidcdavidc00000000000000""" Used to track events in tests. """ # Enthought library imports. from traits.api import HasTraits, List, Str, Tuple class EventTracker(HasTraits): """ Used to track traits events. """ # The traits events that have fired. # # This is a list of tuples in the form:- # # (obj, trait_name, old, new) events = List(Tuple) # The names of the traits events that have fired. # # This is useful if you just care about the order of the events, not the # contents. event_names = List(Str) # The trait event subscriptions used by the tracker. # # This is a list of tuples in the form:- # # (obj, trait_name) # # Where 'obj' is the object to listen to, and 'trait_name' is the name of # the trait to listen to, or None to listen for all trait events. subscriptions = List(Tuple) ########################################################################### # Private interface. ########################################################################### #### Trait change handlers ################################################ def _subscriptions_changed(self, old, new): """ Static trait change handler. """ map(self._remove_subscription, old) map(self._add_subscription, new) return def _subscriptions_items_changed(self, event): """ Static trait change handler. """ map(self._remove_subscription, event.removed) map(self._add_subscription, event.added) return def _listener(self, obj, trait_name, old, new): """ Dynamic trait change listener. """ self.events.append((obj, trait_name, old, new)) self.event_names.append(trait_name) return #### Methods ############################################################## def _add_subscription(self, subscription): """ Add a subscription. """ obj, trait_name = subscription if trait_name is not None: obj.on_trait_change(self._listener, trait_name) else: obj.on_trait_change(self._listener) return def _remove_subscription(self, subscription): """ Remove a subscription. """ obj, trait_name = subscription if trait_name is not None: obj.on_trait_change(self._listener, trait_name, remove=True) else: obj.on_trait_change(self._listener, remove=True) return #### EOF ###################################################################### envisage-4.4.0/envisage/tests/foo.py0000644000175000017500000000045512252661404020134 0ustar davidcdavidc00000000000000""" A test class used in the service registry tests! """ # Enthought library imports. from traits.api import HasTraits, implements # Local imports. from i_foo import IFoo class Foo(HasTraits): implements(IFoo) #### EOF ###################################################################### envisage-4.4.0/envisage/tests/service_test_case.py0000644000175000017500000000460012252661404023037 0ustar davidcdavidc00000000000000""" Tests for the 'Service' trait type. """ # Enthought library imports. from envisage.api import Application, Plugin, Service from traits.api import HasTraits, Instance from traits.testing.unittest_tools import unittest class TestApplication(Application): """ The type of application used in the tests. """ id = 'test' class ServiceTestCase(unittest.TestCase): """ Tests for the 'Service' trait type. """ ########################################################################### # 'TestCase' interface. ########################################################################### def setUp(self): """ Prepares the test fixture before each test method is called. """ return def tearDown(self): """ Called immediately after each test method has been called. """ return ########################################################################### # Tests. ########################################################################### def test_service_trait_type(self): """ service trait type""" class Foo(HasTraits): pass class PluginA(Plugin): id = 'A' foo = Instance(Foo, (), service=True) class PluginB(Plugin): id = 'B' foo = Service(Foo) a = PluginA() b = PluginB() application = TestApplication(plugins=[a, b]) application.start() # Make sure the services were registered. self.assertEqual(a.foo, b.foo) # Stop the application. application.stop() # Make sure the service was unregistered. self.assertEqual(None, b.foo) # You can't set service traits! self.failUnlessRaises(SystemError, setattr, b, 'foo', 'bogus') return def test_service_trait_type_with_no_service_registry(self): """ service trait type with no service registry """ class Foo(HasTraits): pass class Bar(HasTraits): foo = Service(Foo) # We should get an exception because the object does not have an # 'service_registry' trait. b = Bar() self.failUnlessRaises(ValueError, getattr, b, 'foo') return # Entry point for stand-alone testing. if __name__ == '__main__': unittest.main() #### EOF ###################################################################### envisage-4.4.0/envisage/tests/egg_plugin_manager_test_case.py0000644000175000017500000001430112252661404025210 0ustar davidcdavidc00000000000000""" Tests for the Egg plugin manager. """ # Enthought library imports. from envisage.api import EggPluginManager # Local imports. from egg_based_test_case import EggBasedTestCase from traits.testing.unittest_tools import unittest class EggPluginManagerTestCase(EggBasedTestCase): """ Tests for the Egg plugin manager. """ ########################################################################### # Tests. ########################################################################### # fixme: Depending how many eggs are on sys.path, this test may take too # long to be part of the TDD cycle. def test_no_include_or_exclude(self): """ no include or exclude """ # Add all of the eggs in the egg basket. self._add_eggs_on_path([self.egg_dir]) # Make sure that the plugin manager only includes those plugins. plugin_manager = EggPluginManager() # We don't know how many plugins we will actually get - it depends on # what eggs are on sys.path! What we *do* know however is the the 3 # 'acme' test eggs should be in there! ids = [plugin.id for plugin in plugin_manager] self.assert_('acme.foo' in ids) self.assert_('acme.bar' in ids) self.assert_('acme.baz' in ids) return def test_include_specific(self): """ include specific """ # Add all of the eggs in the egg basket. self._add_eggs_on_path([self.egg_dir]) # The Ids of the plugins that we expect the plugin manager to find. expected = ['acme.foo', 'acme.bar'] # We explicitly limit the plugins to be just the 'acme' test plugins # because otherwise the egg plugin manager will pick up *every* plugin # in *every* egg on sys.path! include = ['acme\.foo', 'acme\.bar'] # Make sure that the plugin manager only includes those plugins. plugin_manager = EggPluginManager(include=include) # Make sure the plugin manager found only the required plugins and that # it starts and stops them correctly.. self._test_start_and_stop(plugin_manager, expected) return def test_include_multiple(self): """ include multiple """ # Add all of the eggs in the egg basket. self._add_eggs_on_path([self.egg_dir]) # The Ids of the plugins that we expect the plugin manager to find. expected = ['acme.foo', 'acme.bar', 'acme.baz'] # We explicitly limit the plugins to be just the 'acme' test plugins # because otherwise the egg plugin manager will pick up *every* plugin # in *every* egg on sys.path! include = ['acme.*'] # Make sure that the plugin manager only includes those plugins. plugin_manager = EggPluginManager(include=include) # Make sure the plugin manager found only the required plugins and that # it starts and stops them correctly.. self._test_start_and_stop(plugin_manager, expected) return def test_exclude_specific(self): """ exclude specific """ # Add all of the eggs in the egg basket. self._add_eggs_on_path([self.egg_dir]) # The Ids of the plugins that we expect the plugin manager to find. expected = ['acme.bar'] # We explicitly limit the plugins to be just the 'acme' test plugins # because otherwise the egg plugin manager will pick up *every* plugin # in *every* egg on sys.path! include = ['acme.*'] # Now exclude all but 'acme.bar'... exclude = ['acme\.foo', 'acme\.baz'] # Make sure that the plugin manager excludes the specified plugins. plugin_manager = EggPluginManager(include=include, exclude=exclude) # Make sure the plugin manager found only the required plugins and that # it starts and stops them correctly.. self._test_start_and_stop(plugin_manager, expected) return def test_exclude_multiple(self): """ exclude multiple """ # Add all of the eggs in the egg basket. self._add_eggs_on_path([self.egg_dir]) # The Ids of the plugins that we expect the plugin manager to find. expected = ['acme.foo'] # We explicitly limit the plugins to be just the 'acme' test plugins # because otherwise the egg plugin manager will pick up *every* plugin # in *every* egg on sys.path! include = ['acme.*'] # Now exclude every plugin that starts with 'acme.b'. exclude = ['acme\.b.*'] # Make sure that the plugin manager excludes the specified plugins. plugin_manager = EggPluginManager(include=include, exclude=exclude) # Make sure the plugin manager found only the required plugins and that # it starts and stops them correctly.. self._test_start_and_stop(plugin_manager, expected) return ########################################################################### # Private interface. ########################################################################### def _test_start_and_stop(self, plugin_manager, expected): """ Make sure the plugin manager starts and stops the expected plugins. """ # Make sure the plugin manager found only the required plugins. self.assertEqual(expected, [plugin.id for plugin in plugin_manager]) # Start the plugin manager. This starts all of the plugin manager's # plugins. plugin_manager.start() # Make sure all of the the plugins were started. for id in expected: plugin = plugin_manager.get_plugin(id) self.assertNotEqual(None, plugin) self.assertEqual(True, plugin.started) # Stop the plugin manager. This stops all of the plugin manager's # plugins. plugin_manager.stop() # Make sure all of the the plugins were stopped. for id in expected: plugin = plugin_manager.get_plugin(id) self.assertNotEqual(None, plugin) self.assertEqual(True, plugin.stopped) return # Entry point for stand-alone testing. if __name__ == '__main__': unittest.main() #### EOF ###################################################################### envisage-4.4.0/envisage/tests/extension_point_binding_test_case.py0000644000175000017500000001714512252661404026326 0ustar davidcdavidc00000000000000""" Tests for extension point bindings. """ # Enthought library imports. from envisage.api import ExtensionPoint from envisage.api import bind_extension_point from traits.api import HasTraits, List from traits.testing.unittest_tools import unittest # Local imports. # # We do these as absolute imports to allow nose to run from a different # working directory. from envisage.tests.mutable_extension_registry import ( MutableExtensionRegistry ) def listener(obj, trait_name, old, new): """ A useful trait change handler for testing! """ listener.obj = obj listener.trait_name = trait_name listener.old = old listener.new = new return class ExtensionPointBindingTestCase(unittest.TestCase): """ Tests for extension point binding. """ ########################################################################### # 'TestCase' interface. ########################################################################### def setUp(self): """ Prepares the test fixture before each test method is called. """ self.extension_registry = MutableExtensionRegistry() # Use the extension registry for all extension points and bindings. ExtensionPoint.extension_registry = self.extension_registry return def tearDown(self): """ Called immediately after each test method has been called. """ return ########################################################################### # Tests. ########################################################################### def test_untyped_extension_point(self): """ untyped extension point """ registry = self.extension_registry # Add an extension point. registry.add_extension_point(self._create_extension_point('my.ep')) # Add an extension. registry.add_extension('my.ep', 42) # Declare a class that consumes the extension. class Foo(HasTraits): x = List f = Foo() f.on_trait_change(listener) # Make some bindings. bind_extension_point(f, 'x', 'my.ep') # Make sure that the object was initialized properly. self.assertEqual(1, len(f.x)) self.assertEqual(42, f.x[0]) # Add another extension. registry.add_extension('my.ep', 'a string') # Make sure that the object picked up the new extension... self.assertEqual(2, len(f.x)) self.assert_(42 in f.x) self.assert_('a string' in f.x) # ... and that the correct trait change event was fired. self.assertEqual(f, listener.obj) self.assertEqual('x_items', listener.trait_name) self.assertEqual(1, len(listener.new.added)) self.assert_('a string' in listener.new.added) return def test_set_extensions_via_trait(self): """ set extensions via trait """ registry = self.extension_registry # Add an extension point. registry.add_extension_point(self._create_extension_point('my.ep')) # Add an extension. registry.add_extension('my.ep', 42) # Declare a class that consumes the extension. class Foo(HasTraits): x = List f = Foo() f.on_trait_change(listener) # Make some bindings. bind_extension_point(f, 'x', 'my.ep') # Make sure that the object was initialized properly. self.assertEqual(1, len(f.x)) self.assertEqual(42, f.x[0]) # Set the extensions. f.x = ['a string'] # Make sure that the object picked up the new extension... self.assertEqual(1, len(f.x)) self.assert_('a string' in f.x) self.assertEqual(1, len(registry.get_extensions('my.ep'))) self.assert_('a string' in registry.get_extensions('my.ep')) # ... and that the correct trait change event was fired. self.assertEqual(f, listener.obj) self.assertEqual('x', listener.trait_name) self.assertEqual(1, len(listener.new)) self.assert_('a string' in listener.new) return def test_set_extensions_via_registry(self): """ set extensions via registry """ registry = self.extension_registry # Add an extension point. registry.add_extension_point(self._create_extension_point('my.ep')) # Add an extension. registry.add_extension('my.ep', 42) # Declare a class that consumes the extension. class Foo(HasTraits): x = List f = Foo() f.on_trait_change(listener) # Make some bindings. bind_extension_point(f, 'x', 'my.ep') # Make sure that the object was initialized properly. self.assertEqual(1, len(f.x)) self.assertEqual(42, f.x[0]) # Set the extensions. registry.set_extensions('my.ep', ['a string']) # Make sure that the object picked up the new extension... self.assertEqual(1, len(f.x)) self.assert_('a string' in f.x) # ... and that the correct trait change event was fired. self.assertEqual(f, listener.obj) self.assertEqual('x', listener.trait_name) self.assertEqual(1, len(listener.new)) self.assert_('a string' in listener.new) return def test_explicit_extension_registry(self): """ explicit extension registry """ registry = self.extension_registry # Add an extension point. registry.add_extension_point(self._create_extension_point('my.ep')) # Add an extension. registry.add_extension('my.ep', 42) # Declare a class that consumes the extension. class Foo(HasTraits): x = List f = Foo() f.on_trait_change(listener) # Create an empty extension registry use that in the binding. extension_registry = MutableExtensionRegistry() # Make some bindings. bind_extension_point(f, 'x', 'my.ep', extension_registry) # Make sure that we pick up the empty extension registry and not the # default one. self.assertEqual(0, len(f.x)) return def test_should_be_able_to_bind_multiple_traits_on_a_single_object(self): registry = self.extension_registry # Add 2 extension points. registry.add_extension_point(self._create_extension_point('my.ep')) registry.add_extension_point(self._create_extension_point('another.ep')) # Declare a class that consumes both of the extension points. class Foo(HasTraits): x = List y = List f = Foo() # Bind two different traits on the object to the extension points. bind_extension_point(f, 'x', 'my.ep', registry) bind_extension_point(f, 'y', 'another.ep', registry) self.assertEqual(0, len(f.x)) self.assertEqual(0, len(f.y)) # Add some contributions to the extension points. registry.add_extension('my.ep', 42) registry.add_extensions('another.ep', [98, 99, 100]) # Make sure both traits were bound correctly. self.assertEqual(1, len(f.x)) self.assertEqual(3, len(f.y)) return ########################################################################### # Private interface. ########################################################################### def _create_extension_point(self, id, trait_type=List, desc=''): """ Create an extension point. """ return ExtensionPoint(id=id, trait_type=trait_type, desc=desc) # Entry point for stand-alone testing. if __name__ == '__main__': unittest.main() #### EOF ###################################################################### envisage-4.4.0/envisage/tests/core_plugin_test_case.py0000644000175000017500000002613612252661404023715 0ustar davidcdavidc00000000000000""" Tests for the core plugin. """ # Major package imports. from pkg_resources import resource_filename # Enthought library imports. from envisage.api import Application, Category, ClassLoadHook, Plugin from envisage.api import ServiceOffer from traits.api import HasTraits, Int, Interface, List from traits.testing.unittest_tools import unittest # This module's package. PKG = 'envisage.tests' class TestApplication(Application): """ The type of application used in the tests. """ id = 'core.plugin.test' class CorePluginTestCase(unittest.TestCase): """ Tests for the core plugin. """ ########################################################################### # 'TestCase' interface. ########################################################################### def setUp(self): """ Prepares the test fixture before each test method is called. """ return def tearDown(self): """ Called immediately after each test method has been called. """ return ########################################################################### # Tests. ########################################################################### def test_service_offers(self): """ service offers """ from envisage.core_plugin import CorePlugin class IMyService(Interface): pass class PluginA(Plugin): id = 'A' service_offers = List( contributes_to='envisage.service_offers' ) def _service_offers_default(self): """ Trait initializer. """ service_offers = [ ServiceOffer( protocol=IMyService, factory=self._my_service_factory ) ] return service_offers def _my_service_factory(self, **properties): """ Service factory. """ return 42 core = CorePlugin() a = PluginA() application = TestApplication(plugins=[core, a]) application.start() # Lookup the service. self.assertEqual(42, application.get_service(IMyService)) # Stop the core plugin. application.stop_plugin(core) # Make sure th service has gone. self.assertEqual(None, application.get_service(IMyService)) return def test_dynamically_added_service_offer(self): """ dynamically added service offer """ from envisage.core_plugin import CorePlugin class IMyService(Interface): pass class PluginA(Plugin): id = 'A' service_offers = List( contributes_to='envisage.service_offers' ) def _service_offers_default(self): """ Trait initializer. """ service_offers = [ ServiceOffer( protocol=IMyService, factory=self._my_service_factory ) ] return service_offers def _my_service_factory(self, **properties): """ Service factory. """ return 42 core = CorePlugin() a = PluginA() # Start off with just the core plugin. application = TestApplication(plugins=[core]) application.start() # Make sure the service does not exist! service = application.get_service(IMyService) self.assertIsNone(service) # Make sure the service offer exists... extensions = application.get_extensions('envisage.service_offers') self.assertEqual(0, len(extensions)) # Now add a plugin that contains a service offer. application.add_plugin(a) # Make sure the service offer exists... extensions = application.get_extensions('envisage.service_offers') self.assertEqual(1, len(extensions)) # ... and that the core plugin responded to the new service offer and # published it in the service registry. service = application.get_service(IMyService) self.assertEqual(42, service) return def test_categories(self): """ categories """ from envisage.core_plugin import CorePlugin class PluginA(Plugin): id = 'A' categories = List(contributes_to='envisage.categories') def _categories_default(self): """ Trait initializer. """ bar_category = Category( class_name = PKG + '.bar_category.BarCategory', target_class_name = CorePluginTestCase.__module__ + '.Bar' ) return [bar_category] core = CorePlugin() a = PluginA() application = TestApplication(plugins=[core, a]) application.start() # Create the target class. class Bar(HasTraits): x = Int # Make sure the category was imported and added. # # fixme: The following assertion was commented out. Please don't do # that! If a test fails we need to work out why - otherwise you have # just completely removed the benefits of having tests in the first # place! This test works for me on Python 2.4! self.assert_('y' in Bar.class_traits()) return def test_dynamically_added_category(self): """ dynamically added category """ from envisage.core_plugin import CorePlugin class PluginA(Plugin): id = 'A' categories = List(contributes_to='envisage.categories') def _categories_default(self): """ Trait initializer. """ bar_category = Category( class_name = PKG + '.bar_category.BarCategory', target_class_name = CorePluginTestCase.__module__ + '.Bar' ) return [bar_category] core = CorePlugin() a = PluginA() # Start with just the core plugin. application = TestApplication(plugins=[core]) application.start() # Now add a plugin that contains a category. application.add_plugin(a) # Create the target class. class Bar(HasTraits): x = Int # Make sure the category was imported and added. self.assert_('y' in Bar.class_traits()) return def test_class_load_hooks(self): """ class load hooks """ from envisage.core_plugin import CorePlugin def on_class_loaded(cls): """ Called when a class has been loaded. """ on_class_loaded.cls = cls return class PluginA(Plugin): id = 'A' class_load_hooks = List( [ ClassLoadHook( class_name = CorePluginTestCase.__module__ + '.Baz', on_load = on_class_loaded, ) ], contributes_to='envisage.class_load_hooks' ) core = CorePlugin() a = PluginA() application = TestApplication(plugins=[core, a]) application.start() # Make sure we ignore a class that we are not interested in! class Bif(HasTraits): pass # Make sure the class load hook was *ignored*. self.assert_(not hasattr(on_class_loaded, 'cls')) # Create the target class. class Baz(HasTraits): pass # Make sure the class load hook was called. # # fixme: The following assertion was commented out. Please don't do # that! If a test fails we need to work out why - otherwise you have # just completely removed the benefits of having tests in the first # place! This test works for me on Python 2.4! self.assertEqual(Baz, on_class_loaded.cls) return def test_dynamically_added_class_load_hooks(self): """ dynamically class load hooks """ from envisage.core_plugin import CorePlugin def on_class_loaded(cls): """ Called when a class has been loaded. """ on_class_loaded.cls = cls return class PluginA(Plugin): id = 'A' class_load_hooks = List( [ ClassLoadHook( class_name = CorePluginTestCase.__module__ + '.Baz', on_load = on_class_loaded, ) ], contributes_to='envisage.class_load_hooks' ) core = CorePlugin() a = PluginA() # Start with just the core plugin. application = TestApplication(plugins=[core]) application.start() # Now add a plugin that contains a class load hook. application.add_plugin(a) # Make sure we ignore a class that we are not interested in! class Bif(HasTraits): pass # Make sure the class load hook was *ignored*. self.assert_(not hasattr(on_class_loaded, 'cls')) # Create the target class. class Baz(HasTraits): pass # Make sure the class load hook was called. self.assertEqual(Baz, on_class_loaded.cls) return def test_preferences(self): """ preferences """ # The core plugin is the plugin that offers the preferences extension # point. from envisage.core_plugin import CorePlugin class PluginA(Plugin): id = 'A' preferences = List(contributes_to='envisage.preferences') def _preferences_default(self): """ Trait initializer. """ return ['file://' + resource_filename(PKG, 'preferences.ini')] core = CorePlugin() a = PluginA() application = TestApplication(plugins=[core, a]) application.run() # Make sure we can get one of the preferences. self.assertEqual('42', application.preferences.get('enthought.test.x')) return def test_dynamically_added_preferences(self): """ dynamically added preferences """ # The core plugin is the plugin that offers the preferences extension # point. from envisage.core_plugin import CorePlugin class PluginA(Plugin): id = 'A' preferences = List(contributes_to='envisage.preferences') def _preferences_default(self): """ Trait initializer. """ return ['file://' + resource_filename(PKG, 'preferences.ini')] core = CorePlugin() a = PluginA() # Start with just the core plugin. application = TestApplication(plugins=[core]) application.start() # Now add a plugin that contains a preference. application.add_plugin(a) # Make sure we can get one of the preferences. self.assertEqual('42', application.preferences.get('enthought.test.x')) return # Entry point for stand-alone testing. if __name__ == '__main__': unittest.main() #### EOF ###################################################################### envisage-4.4.0/envisage/tests/import_manager_test_case.py0000644000175000017500000000363312252661404024410 0ustar davidcdavidc00000000000000""" Tests for the import manager. """ # Enthought library imports. from envisage.api import Application, ImportManager from traits.testing.unittest_tools import unittest class ImportManagerTestCase(unittest.TestCase): """ Tests for the import manager. """ ########################################################################### # 'TestCase' interface. ########################################################################### def setUp(self): """ Prepares the test fixture before each test method is called. """ # We do all of the testing via the application to make sure it offers # the same interface! self.import_manager = Application(import_manager=ImportManager()) return def tearDown(self): """ Called immediately after each test method has been called. """ return ########################################################################### # Tests. ########################################################################### def test_import_dotted_symbol(self): """ import dotted symbol """ import tarfile symbol = self.import_manager.import_symbol('tarfile.TarFile') self.assertEqual(symbol, tarfile.TarFile) return def test_import_nested_symbol(self): """ import nested symbol """ import tarfile symbol = self.import_manager.import_symbol('tarfile:TarFile.open') self.assertEqual(symbol, tarfile.TarFile.open) return def test_import_dotted_module(self): """ import dotted module """ symbol = self.import_manager.import_symbol( 'envisage.api:ImportManager' ) self.assertEqual(symbol, ImportManager) return # Entry point for stand-alone testing. if __name__ == '__main__': unittest.main() #### EOF ###################################################################### envisage-4.4.0/envisage/tests/preferences.ini0000644000175000017500000000003012252661404021766 0ustar davidcdavidc00000000000000[enthought.test] x = 42 envisage-4.4.0/envisage/tests/application_test_case.py0000644000175000017500000003351112252661404023705 0ustar davidcdavidc00000000000000""" Tests for applications and plugins. """ # Standard library imports. import os, shutil, unittest # Enthought library imports. from traits.etsconfig.api import ETSConfig from envisage.api import Application, ExtensionPoint from envisage.api import Plugin, PluginManager from traits.api import Bool, Int, List # Local imports. # # We do these as absolute imports to allow nose to run from a different # working directory. from envisage.tests.event_tracker import EventTracker def listener(obj, trait_name, old, new): """ A useful trait change handler for testing! """ listener.obj = obj listener.trait_name = trait_name listener.old = old listener.new = new return def vetoer(event): """ A function that will veto an event. """ event.veto = True return class TestApplication(Application): """ The type of application used in the tests. """ id = 'test' class SimplePlugin(Plugin): """ A simple plugin. """ #### 'SimplePlugin' interface ############################################# started = Bool(False) stopped = Bool(False) ########################################################################### # 'IPlugin' interface. ########################################################################### def start(self): """ Start the plugin. """ self.started = True self.stopped = False return def stop(self): """ Stop the plugin. """ self.started = False self.stopped = True return class BadPlugin(Plugin): """ A plugin that just causes trouble ;^). """ ########################################################################### # 'IPlugin' interface. ########################################################################### def start(self): """ Start the plugin. """ raise 1/0 def stop(self): """ Stop the plugin. """ raise 1/0 class PluginA(Plugin): """ A plugin that offers an extension point. """ id = 'A' x = ExtensionPoint(List, id='a.x') class PluginB(Plugin): """ A plugin that contributes to an extension point. """ id = 'B' x = List(Int, [1, 2, 3], contributes_to='a.x') class PluginC(Plugin): """ Another plugin that contributes to an extension point! """ id = 'C' x = List(Int, [98, 99, 100], contributes_to='a.x') class ApplicationTestCase(unittest.TestCase): """ Tests for applications and plugins. """ ########################################################################### # 'TestCase' interface. ########################################################################### def setUp(self): """ Prepares the test fixture before each test method is called. """ # Make sure that the listener contents get cleand up before each test. listener.obj = None listener.trait_name = None listener.old = None listener.new = None return def tearDown(self): """ Called immediately after each test method has been called. """ return ########################################################################### # Tests. ########################################################################### def test_home(self): """ home """ application = TestApplication() # Make sure we get the right default value. self.assertEqual(ETSConfig.application_home, application.home) # Delete the directory. shutil.rmtree(application.home) # Create a new application. application = TestApplication() # Make sure the directory got created. self.assert_(os.path.exists(application.home)) # Delete the directory. shutil.rmtree(application.home) return def test_no_plugins(self): """ no plugins """ application = TestApplication() tracker = EventTracker( subscriptions = [ (application, 'starting'), (application, 'started'), (application, 'stopping'), (application, 'stopped') ] ) # Start the application. started = application.start() self.assertEqual(True, started) self.assertEqual(['starting', 'started'], tracker.event_names) # Stop the application. stopped = application.stop() self.assertEqual(True, stopped) self.assertEqual( ['starting', 'started', 'stopping', 'stopped'], tracker.event_names ) return def test_veto_starting(self): """ veto starting """ application = TestApplication() # This listener will veto the 'starting' event. application.on_trait_change(vetoer, 'starting') tracker = EventTracker( subscriptions = [ (application, 'starting'), (application, 'started'), (application, 'stopping'), (application, 'stopped') ] ) # Start the application. started = application.start() self.assertEqual(False, started) self.assert_('started' not in tracker.event_names) return def test_veto_stopping(self): """ veto stopping """ application = TestApplication() # This listener will veto the 'stopping' event. application.on_trait_change(vetoer, 'stopping') tracker = EventTracker( subscriptions = [ (application, 'starting'), (application, 'started'), (application, 'stopping'), (application, 'stopped') ] ) # Start the application. started = application.start() self.assertEqual(['starting', 'started'], tracker.event_names) self.assertEqual(True, started) # Stop the application. stopped = application.stop() self.assertEqual(False, stopped) self.assert_('stopped' not in tracker.event_names) return def test_start_and_stop_errors(self): """ start and stop errors """ simple_plugin = SimplePlugin() bad_plugin = BadPlugin() application = TestApplication(plugins=[simple_plugin, bad_plugin]) # Try to start the application - the bad plugin should barf. self.failUnlessRaises(ZeroDivisionError, application.start) # Try to stop the application - the bad plugin should barf. self.failUnlessRaises(ZeroDivisionError, application.stop) # Try to start a non-existent plugin. self.failUnlessRaises( SystemError, application.start_plugin, plugin_id='bogus' ) # Try to stop a non-existent plugin. self.failUnlessRaises( SystemError, application.stop_plugin, plugin_id='bogus' ) return def test_extension_point(self): """ extension point """ a = PluginA() b = PluginB() c = PluginC() application = TestApplication(plugins=[a, b, c]) application.start() # Make sure we can get the contributions via the application. extensions = application.get_extensions('a.x') self.assertEqual(6, len(extensions)) self.assertEqual([1, 2, 3, 98, 99, 100], extensions) # Make sure we can get the contributions via the plugin. extensions = a.x self.assertEqual(6, len(extensions)) self.assertEqual([1, 2, 3, 98, 99, 100], extensions) return def test_add_extension_point_listener(self): """ add extension point listener """ a = PluginA() b = PluginB() c = PluginC() # Start off with just two of the plugins. application = TestApplication(plugins=[a, b]) application.start() def listener(extension_registry, event): """ An extension point listener. """ listener.extension_point_id = event.extension_point_id listener.added = event.added listener.removed = event.removed return # Make sure we can get the contributions via the application. extensions = application.get_extensions('a.x') self.assertEqual(3, len(extensions)) self.assertEqual([1, 2, 3], extensions) # Add the listener. application.add_extension_point_listener(listener, 'a.x') # Now add the other plugin. application.add_plugin(c) # Make sure the listener was called. self.assertEqual('a.x', listener.extension_point_id) self.assertEqual([], listener.removed) self.assertEqual([98, 99, 100], listener.added) return def test_remove_extension_point_listener(self): """ remove extension point listener """ a = PluginA() b = PluginB() c = PluginC() # Start off with just one of the plugins. application = TestApplication(plugins=[a]) application.start() def listener(extension_registry, event): """ An extension point listener. """ listener.extension_point_id = event.extension_point_id listener.added = event.added listener.removed = event.removed return # Make sure we can get the contributions via the application. extensions = application.get_extensions('a.x') self.assertEqual(0, len(extensions)) # Add the listener. application.add_extension_point_listener(listener, 'a.x') # Now add one of the other plugins. application.add_plugin(b) # Make sure the listener was called. self.assertEqual('a.x', listener.extension_point_id) self.assertEqual([], listener.removed) self.assertEqual([1, 2, 3], listener.added) # Now remove the listener. listener.extension_point_id = None application.remove_extension_point_listener(listener, 'a.x') # Now add the final plugin. application.add_plugin(c) # Make sure the listener was *not* called. self.assertEqual(None, listener.extension_point_id) return def test_add_plugin(self): """ add plugin """ a = PluginA() b = PluginB() c = PluginC() # Start off with just two of the plugins. application = TestApplication(plugins=[a, b]) application.start() # Make sure we can get the contributions via the application. extensions = application.get_extensions('a.x') self.assertEqual(3, len(extensions)) self.assertEqual([1, 2, 3], extensions) # Make sure we can get the contributions via the plugin. extensions = a.x self.assertEqual(3, len(extensions)) self.assertEqual([1, 2, 3], extensions) # Now add the other plugin. application.add_plugin(c) # Make sure we can get the contributions via the application. extensions = application.get_extensions('a.x') self.assertEqual(6, len(extensions)) self.assertEqual([1, 2, 3, 98, 99, 100], extensions) # Make sure we can get the contributions via the plugin. extensions = a.x self.assertEqual(6, len(extensions)) self.assertEqual([1, 2, 3, 98, 99, 100], extensions) return def test_get_plugin(self): """ get plugin """ a = PluginA() b = PluginB() c = PluginC() # Start off with just two of the plugins. application = TestApplication(plugins=[a, b, c]) application.start() # Make sure we can get the plugins. self.assertEqual(a, application.get_plugin('A')) self.assertEqual(b, application.get_plugin('B')) self.assertEqual(c, application.get_plugin('C')) # Make sure we can't get one that isn't there ;^) self.assertEqual(None, application.get_plugin('BOGUS')) return def test_remove_plugin(self): """ remove plugin """ a = PluginA() b = PluginB() c = PluginC() application = TestApplication(plugins=[a, b, c]) application.start() # Make sure we can get the contributions via the application. extensions = application.get_extensions('a.x') self.assertEqual(6, len(extensions)) self.assertEqual([1, 2, 3, 98, 99, 100], extensions) # Make sure we can get the contributions via the plugin. extensions = a.x self.assertEqual(6, len(extensions)) self.assertEqual([1, 2, 3, 98, 99, 100], extensions) # Now remove one plugin. application.remove_plugin(b) # Make sure we can get the contributions via the application. extensions = application.get_extensions('a.x') self.assertEqual(3, len(extensions)) self.assertEqual([98, 99, 100], extensions) # Make sure we can get the contributions via the plugin. extensions = a.x self.assertEqual(3, len(extensions)) self.assertEqual([98, 99, 100], extensions) return def test_set_plugin_manager_at_contruction_time(self): """ set plugin manager at construction time""" a = PluginA() b = PluginB() c = PluginC() # Start off with just two of the plugins. application = TestApplication( plugin_manager=PluginManager(plugins=[a, b, c]) ) application.start() # Make sure we can get the plugins. self.assertEqual(a, application.get_plugin('A')) self.assertEqual(b, application.get_plugin('B')) self.assertEqual(c, application.get_plugin('C')) # Make sure we can't get one that isn't there ;^) self.assertEqual(None, application.get_plugin('BOGUS')) return # Entry point for stand-alone testing. if __name__ == '__main__': unittest.main() #### EOF ###################################################################### envisage-4.4.0/envisage/tests/__init__.py0000644000175000017500000000000012252661404021072 0ustar davidcdavidc00000000000000envisage-4.4.0/envisage/tests/extension_point_changed_test_case.py0000644000175000017500000002761712252661404026312 0ustar davidcdavidc00000000000000""" Tests for the events fired when extension points are changed. """ # Enthought library imports. from traits.testing.unittest_tools import unittest # Local imports. # # We do these as absolute imports to allow nose to run from a different # working directory. from envisage.tests.application_test_case import ( PluginA, PluginB, PluginC, TestApplication, listener ) class ExtensionPointChangedTestCase(unittest.TestCase): """ Tests for the events fired when extension points are changed. """ ########################################################################### # 'TestCase' interface. ########################################################################### def setUp(self): """ Prepares the test fixture before each test method is called. """ # Make sure that the listener contents get cleand up before each test. listener.obj = None listener.trait_name = None listener.old = None listener.new = None return def tearDown(self): """ Called immediately after each test method has been called. """ return ########################################################################### # Tests. ########################################################################### def test_set_extension_point(self): """ set extension point """ a = PluginA() application = TestApplication(plugins=[a]) application.start() # Try to set the extension point. self.failUnlessRaises(SystemError, setattr, a, 'x', [1, 2, 3]) return def test_append(self): """ append """ a = PluginA(); a.on_trait_change(listener, 'x_items') b = PluginB() c = PluginC() application = TestApplication(plugins=[a, b, c]) application.start() # fixme: If the extension point has not been accessed then the # provider extension registry can't work out what has changed, so it # won't fire a changed event. self.assertEqual([1, 2, 3, 98, 99, 100], a.x) # Append a contribution. b.x.append(4) # Make sure we pick up the new contribution via the application. extensions = application.get_extensions('a.x') extensions.sort() self.assertEqual(7, len(extensions)) self.assertEqual([1, 2, 3, 4, 98, 99, 100], extensions) # Make sure we pick up the new contribution via the plugin. extensions = a.x[:] extensions.sort() self.assertEqual(7, len(extensions)) self.assertEqual([1, 2, 3, 4, 98, 99, 100], extensions) # Make sure we got a trait event telling us that the contributions # to the extension point have been changed. self.assertEqual(a, listener.obj) self.assertEqual('x_items', listener.trait_name) self.assertEqual([4], listener.new.added) self.assertEqual([], listener.new.removed) self.assertEqual(3, listener.new.index) return def test_remove(self): """ remove """ a = PluginA(); a.on_trait_change(listener, 'x_items') b = PluginB() c = PluginC() application = TestApplication(plugins=[a, b, c]) application.start() # fixme: If the extension point has not been accessed then the # provider extension registry can't work out what has changed, so it # won't fire a changed event. self.assertEqual([1, 2, 3, 98, 99, 100], a.x) # Remove a contribution. b.x.remove(3) # Make sure we pick up the correct contributions via the application. extensions = application.get_extensions('a.x') extensions.sort() self.assertEqual(5, len(extensions)) self.assertEqual([1, 2, 98, 99, 100], extensions) # Make sure we pick up the correct contributions via the plugin. extensions = a.x[:] extensions.sort() self.assertEqual(5, len(extensions)) self.assertEqual([1, 2, 98, 99, 100], extensions) # Make sure we got a trait event telling us that the contributions # to the extension point have been changed. self.assertEqual(a, listener.obj) self.assertEqual('x_items', listener.trait_name) self.assertEqual([], listener.new.added) self.assertEqual([3], listener.new.removed) self.assertEqual(2, listener.new.index) return def test_assign_empty_list(self): """ assign empty list """ a = PluginA(); a.on_trait_change(listener, 'x_items') b = PluginB() c = PluginC() application = TestApplication(plugins=[a, b, c]) application.start() # fixme: If the extension point has not been accessed then the # provider extension registry can't work out what has changed, so it # won't fire a changed event. self.assertEqual([1, 2, 3, 98, 99, 100], a.x) # Assign an empty list to one of the plugin's contributions. b.x = [] # Make sure we pick up the correct contribution via the application. extensions = application.get_extensions('a.x') extensions.sort() self.assertEqual(3, len(extensions)) self.assertEqual([98, 99, 100], extensions) # Make sure we pick up the correct contribution via the plugin. extensions = a.x[:] extensions.sort() self.assertEqual(3, len(extensions)) self.assertEqual([98, 99, 100], extensions) # Make sure we got a trait event telling us that the contributions # to the extension point have been changed. self.assertEqual(a, listener.obj) self.assertEqual('x_items', listener.trait_name) self.assertEqual([], listener.new.added) self.assertEqual([1, 2, 3], listener.new.removed) self.assertEqual(0, listener.new.index.start) self.assertEqual(3, listener.new.index.stop) return def test_assign_empty_list_no_event(self): """ assign empty list no event """ a = PluginA(); a.on_trait_change(listener, 'x_items') b = PluginB() c = PluginC() application = TestApplication(plugins=[a, b, c]) application.start() # Assign an empty list to one of the plugin's contributions. b.x = [] # Make sure we pick up the correct contribution via the application. extensions = application.get_extensions('a.x') extensions.sort() self.assertEqual(3, len(extensions)) self.assertEqual([98, 99, 100], extensions) # Make sure we pick up the correct contribution via the plugin. extensions = a.x[:] extensions.sort() self.assertEqual(3, len(extensions)) self.assertEqual([98, 99, 100], extensions) # We shouldn't get a trait event here because we haven't accessed the # extension point yet! self.assertEqual(None, listener.obj) return def test_assign_non_empty_list(self): """ assign non-empty list """ a = PluginA(); a.on_trait_change(listener, 'x_items') b = PluginB() c = PluginC() application = TestApplication(plugins=[a, b, c]) application.start() # fixme: If the extension point has not been accessed then the # provider extension registry can't work out what has changed, so it # won't fire a changed event. self.assertEqual([1, 2, 3, 98, 99, 100], a.x) # Assign a non-empty list to one of the plugin's contributions. b.x = [2, 4, 6, 8] # Make sure we pick up the new contribution via the application. extensions = application.get_extensions('a.x') extensions.sort() self.assertEqual(7, len(extensions)) self.assertEqual([2, 4, 6, 8, 98, 99, 100], extensions) # Make sure we pick up the new contribution via the plugin. extensions = a.x[:] extensions.sort() self.assertEqual(7, len(extensions)) self.assertEqual([2, 4, 6, 8, 98, 99, 100], extensions) # Make sure we got a trait event telling us that the contributions # to the extension point have been changed. self.assertEqual(a, listener.obj) self.assertEqual('x_items', listener.trait_name) self.assertEqual([2, 4, 6, 8], listener.new.added) self.assertEqual([1, 2, 3], listener.new.removed) self.assertEqual(0, listener.new.index.start) self.assertEqual(4, listener.new.index.stop) return def test_add_plugin(self): """ add plugin """ a = PluginA(); a.on_trait_change(listener, 'x_items') b = PluginB() c = PluginC() # Start off with just two of the plugins. application = TestApplication(plugins=[a, b]) application.start() # Make sure we can get the contributions via the application. extensions = application.get_extensions('a.x') extensions.sort() self.assertEqual(3, len(extensions)) self.assertEqual([1, 2, 3], extensions) # Make sure we can get the contributions via the plugin. extensions = a.x[:] extensions.sort() self.assertEqual(3, len(extensions)) self.assertEqual([1, 2, 3], extensions) # Now add the other plugin. application.add_plugin(c) # Make sure we can get the contributions via the application. extensions = application.get_extensions('a.x') extensions.sort() self.assertEqual(6, len(extensions)) self.assertEqual([1, 2, 3, 98, 99, 100], extensions) # Make sure we can get the contributions via the plugin. extensions = a.x[:] extensions.sort() self.assertEqual(6, len(extensions)) self.assertEqual([1, 2, 3, 98, 99, 100], extensions) # Make sure we got a trait event telling us that the contributions # to the extension point have been changed. self.assertEqual(a, listener.obj) self.assertEqual('x_items', listener.trait_name) self.assertEqual([98, 99, 100], listener.new.added) self.assertEqual([], listener.new.removed) self.assertEqual(3, listener.new.index) return def test_remove_plugin(self): """ remove plugin """ a = PluginA(); a.on_trait_change(listener, 'x_items') b = PluginB() c = PluginC() application = TestApplication(plugins=[a, b, c]) application.start() # Make sure we can get the contributions via the application. extensions = application.get_extensions('a.x') extensions.sort() self.assertEqual(6, len(extensions)) self.assertEqual([1, 2, 3, 98, 99, 100], extensions) # Make sure we can get the contributions via the plugin. extensions = a.x[:] extensions.sort() self.assertEqual(6, len(extensions)) self.assertEqual([1, 2, 3, 98, 99, 100], extensions) # Now remove one plugin. application.remove_plugin(b) # Make sure we can get the contributions via the application. extensions = application.get_extensions('a.x') extensions.sort() self.assertEqual(3, len(extensions)) self.assertEqual([98, 99, 100], extensions) # Make sure we can get the contributions via the plugin. extensions = a.x[:] extensions.sort() self.assertEqual(3, len(extensions)) self.assertEqual([98, 99, 100], extensions) # Make sure we got a trait event telling us that the contributions # to the extension point have been changed. self.assertEqual(a, listener.obj) self.assertEqual('x_items', listener.trait_name) self.assertEqual([], listener.new.added) self.assertEqual([1, 2, 3], listener.new.removed) self.assertEqual(0, listener.new.index) return # Entry point for stand-alone testing. if __name__ == '__main__': unittest.main() #### EOF ###################################################################### envisage-4.4.0/envisage/tests/class_load_hook_test_case.py0000644000175000017500000000655312252661404024534 0ustar davidcdavidc00000000000000""" Tests for class load hooks. """ from envisage.api import ClassLoadHook from traits.api import HasTraits from traits.testing.unittest_tools import unittest # This module's package. PKG = 'envisage.tests' class ClassLoadHookTestCase(unittest.TestCase): """ Tests for class load hooks. """ ########################################################################### # 'TestCase' interface. ########################################################################### def setUp(self): """ Prepares the test fixture before each test method is called. """ return def tearDown(self): """ Called immediately after each test method has been called. """ return ########################################################################### # Tests. ########################################################################### def test_connect(self): """ connect """ def on_class_loaded(cls): """ Called when a class is loaded. """ on_class_loaded.cls = cls return # To register with 'MetaHasTraits' we use 'module_name.class_name'. hook = ClassLoadHook( class_name = ClassLoadHookTestCase.__module__ + '.Foo', on_load = on_class_loaded ) hook.connect() class Foo(HasTraits): pass self.assertEqual(Foo, on_class_loaded.cls) return def test_class_already_loaded(self): """ class already loaded """ def on_class_loaded(cls): """ Called when a class is loaded. """ on_class_loaded.cls = cls return # To register with 'MetaHasTraits' we use 'module_name.class_name'. hook = ClassLoadHook( class_name = self._get_full_class_name(ClassLoadHookTestCase), on_load = on_class_loaded ) hook.connect() # Make sure the 'on_load' got called immediately because the class is # already loaded. self.assertEqual(ClassLoadHookTestCase, on_class_loaded.cls) return def test_disconnect(self): """ disconnect """ def on_class_loaded(cls): """ Called when a class is loaded. """ on_class_loaded.cls = cls return # To register with 'MetaHasTraits' we use 'module_name.class_name'. hook = ClassLoadHook( class_name = ClassLoadHookTestCase.__module__ + '.Foo', on_load = on_class_loaded ) hook.connect() class Foo(HasTraits): pass self.assertEqual(Foo, on_class_loaded.cls) # 'Reset' the listener, on_class_loaded.cls = None # Now disconnect. hook.disconnect() class Foo(HasTraits): pass self.assertEqual(None, on_class_loaded.cls) return ########################################################################### # Private interface. ########################################################################### def _get_full_class_name(self, cls): """ Return the full (possibly) dotted name of a class. """ return cls.__module__ + '.' + cls.__name__ # Entry point for stand-alone testing. if __name__ == '__main__': unittest.main() #### EOF ###################################################################### envisage-4.4.0/envisage/tests/egg_basket_plugin_manager_test_case.py0000644000175000017500000001330112252661404026540 0ustar davidcdavidc00000000000000""" Tests for the 'Egg Basket' plugin manager. """ from os.path import dirname, join from envisage.egg_basket_plugin_manager import EggBasketPluginManager from traits.testing.unittest_tools import unittest class EggBasketPluginManagerTestCase(unittest.TestCase): """ Tests for the 'Egg Basket' plugin manager. """ #### 'unittest.TestCase' protocol ######################################### def setUp(self): """ Prepares the test fixture before each test method is called. """ # The location of the 'eggs' test data directory. self.eggs_dir = join(dirname(__file__), 'eggs') return def tearDown(self): """ Called immediately after each test method has been called. """ return #### Tests ################################################################ def test_find_plugins_in_eggs_on_the_plugin_path(self): plugin_manager = EggBasketPluginManager( plugin_path=[self.eggs_dir] ) ids = [plugin.id for plugin in plugin_manager] self.assertEqual(len(ids), 3) self.assertIn('acme.foo', ids) self.assertIn('acme.bar', ids) self.assertIn('acme.baz', ids) return def test_only_find_plugins_whose_ids_are_in_the_include_list(self): # Note that the items in the list use the 'fnmatch' syntax for matching # plugins Ids. include = ['acme.foo', 'acme.bar'] plugin_manager = EggBasketPluginManager( plugin_path = [self.eggs_dir], include = include ) # The Ids of the plugins that we expect the plugin manager to find. expected = ['acme.foo', 'acme.bar'] # Make sure the plugin manager found only the required plugins and that # it starts and stops them correctly.. self._test_start_and_stop(plugin_manager, expected) return def test_only_find_plugins_matching_a_wildcard_in_the_include_list(self): # Note that the items in the list use the 'fnmatch' syntax for matching # plugins Ids. include = ['acme.b*'] plugin_manager = EggBasketPluginManager( plugin_path = [self.eggs_dir], include = include ) # The Ids of the plugins that we expect the plugin manager to find. expected = ['acme.bar', 'acme.baz'] # Make sure the plugin manager found only the required plugins and that # it starts and stops them correctly.. self._test_start_and_stop(plugin_manager, expected) return def test_ignore_plugins_whose_ids_are_in_the_exclude_list(self): # Note that the items in the list use the 'fnmatch' syntax for matching # plugins Ids. exclude = ['acme.foo', 'acme.baz'] plugin_manager = EggBasketPluginManager( plugin_path = [self.eggs_dir], exclude = exclude ) # The Ids of the plugins that we expect the plugin manager to find. expected = ['acme.bar'] # Make sure the plugin manager found only the required plugins and that # it starts and stops them correctly.. self._test_start_and_stop(plugin_manager, expected) return def test_ignore_plugins_matching_a_wildcard_in_the_exclude_list(self): # Note that the items in the list use the 'fnmatch' syntax for matching # plugins Ids. exclude = ['acme.b*'] plugin_manager = EggBasketPluginManager( plugin_path = [self.eggs_dir], exclude = exclude ) # The Ids of the plugins that we expect the plugin manager to find. expected = ['acme.foo'] # Make sure the plugin manager found only the required plugins and that # it starts and stops them correctly.. self._test_start_and_stop(plugin_manager, expected) return def test_reflect_changes_to_the_plugin_path(self): plugin_manager = EggBasketPluginManager() ids = [plugin.id for plugin in plugin_manager] self.assertEqual(len(ids), 0) plugin_manager.plugin_path.append(self.eggs_dir) ids = [plugin.id for plugin in plugin_manager] self.assertEqual(len(ids), 3) self.assertIn('acme.foo', ids) self.assertIn('acme.bar', ids) self.assertIn('acme.baz', ids) del plugin_manager.plugin_path[0] ids = [plugin.id for plugin in plugin_manager] self.assertEqual(len(ids), 0) return #### Private protocol ##################################################### def _test_start_and_stop(self, plugin_manager, expected): """ Make sure the plugin manager starts and stops the expected plugins. """ # Make sure the plugin manager found only the required plugins. self.assertEqual(expected, [plugin.id for plugin in plugin_manager]) # Start the plugin manager. This starts all of the plugin manager's # plugins. plugin_manager.start() # Make sure all of the the plugins were started. for id in expected: plugin = plugin_manager.get_plugin(id) self.assertNotEqual(None, plugin) self.assertEqual(True, plugin.started) # Stop the plugin manager. This stops all of the plugin manager's # plugins. plugin_manager.stop() # Make sure all of the the plugins were stopped. for id in expected: plugin = plugin_manager.get_plugin(id) self.assertNotEqual(None, plugin) self.assertEqual(True, plugin.stopped) return # Entry point for stand-alone testing. if __name__ == '__main__': unittest.main() #### EOF ###################################################################### envisage-4.4.0/envisage/tests/package_plugin_manager_test_case.py0000644000175000017500000001324712252661404026051 0ustar davidcdavidc00000000000000""" Tests for the 'Package' plugin manager. """ from os.path import dirname, join from envisage.package_plugin_manager import PackagePluginManager from traits.testing.unittest_tools import unittest class PackagePluginManagerTestCase(unittest.TestCase): """ Tests for the 'Package' plugin manager. """ #### 'unittest.TestCase' protocol ######################################### def setUp(self): """ Prepares the test fixture before each test method is called. """ # The location of the 'plugins' test data directory. self.plugins_dir = join(dirname(__file__), 'plugins') return def tearDown(self): """ Called immediately after each test method has been called. """ return #### Tests ################################################################ def test_find_plugins_in_packages_on_the_plugin_path(self): plugin_manager = PackagePluginManager(plugin_path=[self.plugins_dir]) ids = [plugin.id for plugin in plugin_manager] self.assertEqual(len(ids), 3) self.assertIn('banana', ids) self.assertIn('orange', ids) self.assertIn('pear', ids) return def test_only_find_plugins_whose_ids_are_in_the_include_list(self): # Note that the items in the list use the 'fnmatch' syntax for matching # plugins Ids. include = ['orange', 'pear'] plugin_manager = PackagePluginManager( plugin_path = [self.plugins_dir], include = include ) # The Ids of the plugins that we expect the plugin manager to find. expected = ['orange', 'pear'] # Make sure the plugin manager found only the required plugins and that # it starts and stops them correctly.. self._test_start_and_stop(plugin_manager, expected) return def test_only_find_plugins_matching_a_wildcard_in_the_include_list(self): # Note that the items in the list use the 'fnmatch' syntax for matching # plugins Ids. include = ['*r*'] plugin_manager = PackagePluginManager( plugin_path = [self.plugins_dir], include = include ) # The Ids of the plugins that we expect the plugin manager to find. expected = ['orange', 'pear'] # Make sure the plugin manager found only the required plugins and that # it starts and stops them correctly.. self._test_start_and_stop(plugin_manager, expected) return def test_ignore_plugins_whose_ids_are_in_the_exclude_list(self): # Note that the items in the list use the 'fnmatch' syntax for matching # plugins Ids. exclude = ['orange', 'pear'] plugin_manager = PackagePluginManager( plugin_path = [self.plugins_dir], exclude = exclude ) # The Ids of the plugins that we expect the plugin manager to find. expected = ['banana'] # Make sure the plugin manager found only the required plugins and that # it starts and stops them correctly.. self._test_start_and_stop(plugin_manager, expected) return def test_ignore_plugins_matching_a_wildcard_in_the_exclude_list(self): # Note that the items in the list use the 'fnmatch' syntax for matching # plugins Ids. exclude = ['*r*'] plugin_manager = PackagePluginManager( plugin_path = [self.plugins_dir], exclude = exclude ) # The Ids of the plugins that we expect the plugin manager to find. expected = ['banana'] # Make sure the plugin manager found only the required plugins and that # it starts and stops them correctly.. self._test_start_and_stop(plugin_manager, expected) return def test_reflect_changes_to_the_plugin_path(self): plugin_manager = PackagePluginManager() ids = [plugin.id for plugin in plugin_manager] self.assertEqual(len(ids), 0) plugin_manager.plugin_path.append(self.plugins_dir) ids = [plugin.id for plugin in plugin_manager] self.assertEqual(len(ids), 3) self.assertIn('banana', ids) self.assertIn('orange', ids) self.assertIn('pear', ids) del plugin_manager.plugin_path[0] ids = [plugin.id for plugin in plugin_manager] self.assertEqual(len(ids), 0) return #### Private protocol ##################################################### def _test_start_and_stop(self, plugin_manager, expected): """ Make sure the plugin manager starts and stops the expected plugins. """ # Make sure the plugin manager found only the required plugins. self.assertItemsEqual( expected, [plugin.id for plugin in plugin_manager] ) # Start the plugin manager. This starts all of the plugin manager's # plugins. plugin_manager.start() # Make sure all of the the plugins were started. for id in expected: plugin = plugin_manager.get_plugin(id) self.assertNotEqual(None, plugin) self.assertEqual(True, plugin.started) # Stop the plugin manager. This stops all of the plugin manager's # plugins. plugin_manager.stop() # Make sure all of the the plugins were stopped. for id in expected: plugin = plugin_manager.get_plugin(id) self.assertNotEqual(None, plugin) self.assertEqual(True, plugin.stopped) return # Entry point for stand-alone testing. if __name__ == '__main__': unittest.main() #### EOF ###################################################################### envisage-4.4.0/envisage/tests/egg_based_test_case.py0000644000175000017500000000460612252661404023305 0ustar davidcdavidc00000000000000""" Base class for Egg-based test cases. """ import pkg_resources from os.path import dirname, join from traits.testing.unittest_tools import unittest class EggBasedTestCase(unittest.TestCase): """ Base class for Egg-based test cases. """ ########################################################################### # 'TestCase' interface. ########################################################################### def setUp(self): """ Prepares the test fixture before each test method is called. """ # The location of the 'eggs' directory. self.egg_dir = join(dirname(__file__), 'eggs') return def tearDown(self): """ Called immediately after each test method has been called. """ return ########################################################################### # Private interface. ########################################################################### def _add_egg(self, filename, working_set=None): """ Create and add a distribution from the specified '.egg'. """ if working_set is None: working_set = pkg_resources.working_set # The eggs must be in our egg directory! filename = join(dirname(__file__), 'eggs', filename) # Create a distribution for the egg. distributions = pkg_resources.find_distributions(filename) # Add the distributions to the working set (this makes any Python # modules in the eggs available for importing). map(working_set.add, distributions) return def _add_eggs_on_path(self, path, working_set=None): """ Add all eggs found on the path to a working set. """ if working_set is None: working_set = pkg_resources.working_set environment = pkg_resources.Environment(path) # 'find_plugins' identifies those distributions that *could* be added # to the working set without version conflicts or missing requirements. distributions, errors = working_set.find_plugins(environment) if len(errors) > 0: raise SystemError('Cannot find eggs %s' % errors) # Add the distributions to the working set (this makes any Python # modules in the eggs available for importing). map(working_set.add, distributions) return #### EOF ###################################################################### envisage-4.4.0/envisage/tests/plugin_test_case.py0000644000175000017500000003100612252661404022675 0ustar davidcdavidc00000000000000""" Tests for plugins. """ # Standard library imports. from os.path import exists, join # Enthought library imports. from envisage.api import Application, ExtensionPoint from envisage.api import IPluginActivator, Plugin, contributes_to from traits.api import HasTraits, Instance, Int, Interface, List from traits.api import implements from traits.testing.unittest_tools import unittest def listener(obj, trait_name, old, new): """ A useful trait change handler for testing! """ listener.obj = obj listener.trait_name = trait_name listener.old = old listener.new = new return class TestApplication(Application): """ The type of application used in the tests. """ id = 'test' class PluginTestCase(unittest.TestCase): """ Tests for plugins. """ ########################################################################### # 'TestCase' interface. ########################################################################### def setUp(self): """ Prepares the test fixture before each test method is called. """ return def tearDown(self): """ Called immediately after each test method has been called. """ return ########################################################################### # Tests. ########################################################################### def test_id_policy(self): """ id policy """ # If no Id is specified then use 'module_name.class_name'. p = Plugin() self.assertEqual('envisage.plugin.Plugin', p.id) # If an Id is specified make sure we use it! p = Plugin(id='wilma') self.assertEqual('wilma', p.id) # Make sure setting the name doesn't interfere with the Id. p = Plugin(name='fred', id='wilma') self.assertEqual('wilma', p.id) self.assertEqual('fred', p.name) return def test_name_policy(self): """ name policy """ # If name is specified then use the plugin's class name. p = Plugin() self.assertEqual('Plugin', p.name) # If a name is specified make sure we use it! p = Plugin(name='wilma') self.assertEqual('wilma', p.name) # Try a camel case plugin class. class ThisIsMyPlugin(Plugin): pass p = ThisIsMyPlugin() self.assertEqual('This Is My Plugin', p.name) return def test_plugin_activator(self): """ plugin activator. """ class NullPluginActivator(HasTraits): """ A plugin activator that does nothing! """ implements(IPluginActivator) def start_plugin(self, plugin): """ Start a plugin. """ self.started = plugin return def stop_plugin(self, plugin): """ Stop a plugin. """ self.stopped = plugin return class PluginA(Plugin): id = 'A' class PluginB(Plugin): id = 'B' plugin_activator = NullPluginActivator() a = PluginA(activator=plugin_activator) b = PluginB() application = TestApplication(plugins=[a, b]) application.start() # Make sure A's plugin activator was called. self.assertEqual(a, plugin_activator.started) # Stop the application. application.stop() # Make sure A's plugin activator was called. self.assertEqual(a, plugin_activator.stopped) return def test_service(self): """ service """ class Foo(HasTraits): pass class Bar(HasTraits): pass class Baz(HasTraits): pass class PluginA(Plugin): id = 'A' foo = Instance(Foo, (), service=True) bar = Instance(Bar, (), service=True) baz = Instance(Baz, (), service=True) a = PluginA() application = TestApplication(plugins=[a]) application.start() # Make sure the services were registered. self.assertNotEqual(None, application.get_service(Foo)) self.assertEqual(a.foo, application.get_service(Foo)) self.assertNotEqual(None, application.get_service(Bar)) self.assertEqual(a.bar, application.get_service(Bar)) self.assertNotEqual(None, application.get_service(Baz)) self.assertEqual(a.baz, application.get_service(Baz)) application.stop() # Make sure the service was unregistered. self.assertEqual(None, application.get_service(Foo)) self.assertEqual(None, application.get_service(Bar)) self.assertEqual(None, application.get_service(Baz)) return def test_service_protocol(self): """ service protocol """ class IFoo(Interface): pass class IBar(Interface): pass class Foo(HasTraits): implements(IFoo, IBar) class PluginA(Plugin): id = 'A' foo = Instance(Foo, (), service=True, service_protocol=IBar) a = PluginA() application = TestApplication(plugins=[a]) application.start() # Make sure the service was registered with the 'IBar' protocol. self.assertNotEqual(None, application.get_service(IBar)) self.assertEqual(a.foo, application.get_service(IBar)) application.stop() # Make sure the service was unregistered. self.assertEqual(None, application.get_service(IBar)) return def test_multiple_trait_contributions(self): """ multiple trait contributions """ class PluginA(Plugin): id = 'A' x = ExtensionPoint(List, id='x') class PluginB(Plugin): id = 'B' x = List([1, 2, 3], contributes_to='x') y = List([4, 5, 6], contributes_to='x') a = PluginA() b = PluginB() application = TestApplication(plugins=[a, b]) # We should get an error because the plugin has multiple traits # contributing to the same extension point. self.failUnlessRaises(ValueError, application.get_extensions, 'x') return def test_exception_in_trait_contribution(self): """ exception in trait contribution """ class PluginA(Plugin): id = 'A' x = ExtensionPoint(List, id='x') class PluginB(Plugin): id = 'B' x = List(contributes_to='x') def _x_default(self): """ Trait initializer. """ raise 1/0 a = PluginA() b = PluginB() application = TestApplication(plugins=[a, b]) # We should get an when we try to get the contributions to the # extension point. self.failUnlessRaises( ZeroDivisionError, application.get_extensions, 'x' ) return def test_contributes_to(self): """ contributes to """ class PluginA(Plugin): id = 'A' x = ExtensionPoint(List, id='x') class PluginB(Plugin): id = 'B' x = List([1, 2, 3], contributes_to='x') a = PluginA() b = PluginB() application = TestApplication(plugins=[a, b]) # We should get an error because the plugin has multiple traits # contributing to the same extension point. self.assertEqual([1, 2, 3], application.get_extensions('x')) return def test_contributes_to_decorator(self): """ contributes to decorator """ class PluginA(Plugin): id = 'A' x = ExtensionPoint(List, id='x') class PluginB(Plugin): id = 'B' @contributes_to('x') def _x_contributions(self): return [1, 2, 3] a = PluginA() b = PluginB() application = TestApplication(plugins=[a, b]) self.assertEqual([1, 2, 3], application.get_extensions('x')) return def test_contributes_to_decorator_ignored_if_trait_present(self): """ contributes to decorator ignored if trait present """ class PluginA(Plugin): id = 'A' x = ExtensionPoint(List, id='x') class PluginB(Plugin): id = 'B' x = List([1, 2, 3], contributes_to='x') @contributes_to('x') def _x_contributions(self): return [4, 5, 6] a = PluginA() b = PluginB() application = TestApplication(plugins=[a, b]) self.assertEqual([1, 2, 3], application.get_extensions('x')) return def test_add_plugins_to_empty_application(self): """ add plugins to empty application """ 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 return class PluginB(Plugin): id = 'B' x = List(Int, [1, 2, 3], contributes_to='x') class PluginC(Plugin): id = 'C' x = List(Int, [4, 5, 6], contributes_to='x') a = PluginA() b = PluginB() c = PluginC() # Create an empty application. application = TestApplication() application.start() # Add the plugin that offers the extension point. application.add_plugin(a) ####################################################################### # fixme: Currently, we connect up extension point traits when the # plugin is started. Is this right? Should we start plugins by default # when we add them (and maybe have the ability to add a plugin without # starting it?). # # I think we should start the plugin, otherwise you have the wierdness # that the extension contributed by the plugin are available after # the call to 'add_plugin', but the plugin isn't started?!? ####################################################################### application.start_plugin(a) ####################################################################### # fixme: Currently, we only fire changed events if an extension point # has already been accessed! Is this right? ####################################################################### self.assertEqual([], a.x) # Add a plugin that contributes to the extension point. application.add_plugin(b) # Make sure that we pick up B's extensions and that the appropriate # trait event was fired. self.assertEqual([1, 2, 3], a.x) self.assertEqual([1, 2, 3], a.added) # Add another plugin that contributes to the extension point. application.add_plugin(c) self.assertEqual([1, 2, 3, 4, 5, 6], a.x) self.assertEqual([4, 5, 6], a.added) # Remove the first contributing plugin. application.remove_plugin(b) self.assertEqual([4, 5, 6], a.x) self.assertEqual([1, 2, 3], a.removed) # Remove the second contributing plugin. application.remove_plugin(c) self.assertEqual([], a.x) self.assertEqual([4, 5, 6], a.removed) return def test_home(self): """ home """ class PluginA(Plugin): id = 'A' class PluginB(Plugin): id = 'B' a = PluginA() b = PluginB() application = TestApplication(plugins=[a, b]) # Make sure that each plugin gets its own directory. self.assertEqual(join(application.home, 'plugins', a.id), a.home) self.assertEqual(join(application.home, 'plugins', b.id), b.home) # Make sure that the directories got created. self.assert_(exists(a.home)) self.assert_(exists(b.home)) # Create a new application with plugins with the same Id to make sure # that it all works when the directories already exist. a = PluginA() b = PluginB() application = TestApplication(plugins=[a, b]) # Make sure that each plugin gets its own directory. self.assertEqual(join(application.home, 'plugins', a.id), a.home) self.assertEqual(join(application.home, 'plugins', b.id), b.home) # Make sure the directories got created. self.assert_(exists(a.home)) self.assert_(exists(b.home)) return # Entry point for stand-alone testing. if __name__ == '__main__': unittest.main() #### EOF ###################################################################### envisage-4.4.0/envisage/tests/provider_extension_registry_test_case.py0000644000175000017500000004512612252661404027265 0ustar davidcdavidc00000000000000""" Tests for the provider extension registry. """ # Standard imports import unittest # Enthought library imports. from envisage.api import ExtensionPoint, ExtensionProvider from envisage.api import ProviderExtensionRegistry from traits.api import Int, List # Local imports. from extension_registry_test_case import ExtensionRegistryTestCase class ProviderExtensionRegistryTestCase(ExtensionRegistryTestCase): """ Tests for the provider extension registry. """ ########################################################################### # 'TestCase' interface. ########################################################################### def setUp(self): """ Prepares the test fixture before each test method is called. """ self.registry = ProviderExtensionRegistry() return def tearDown(self): """ Called immediately after each test method has been called. """ return ########################################################################### # Tests. ########################################################################### def test_providers(self): """ providers """ registry = self.registry # Some providers. class ProviderA(ExtensionProvider): """ An extension provider. """ def get_extension_points(self): """ Return the extension points offered by the provider. """ return [ExtensionPoint(List, 'x')] def get_extensions(self, extension_point): """ Return the provider's contributions to an extension point. """ if extension_point == 'x': extensions = [42, 43] else: extensions = [] return extensions class ProviderB(ExtensionProvider): """ An extension provider. """ def get_extensions(self, extension_point): """ Return the provider's contributions to an extension point. """ if extension_point == 'x': extensions = [44, 45, 46] else: extensions = [] return extensions class ProviderC(ExtensionProvider): """ An empty provider! """ # Add the providers to the registry. registry.add_provider(ProviderA()) registry.add_provider(ProviderB()) registry.add_provider(ProviderC()) # The provider's extensions should now be in the registry. extensions = registry.get_extensions('x') self.assertEqual(5, len(extensions)) self.assertEqual(range(42, 47), extensions) # Make sure there's one and only one extension point. extension_points = registry.get_extension_points() self.assertEqual(1, len(extension_points)) self.assertEqual('x', extension_points[0].id) return def test_provider_extensions_changed(self): """ provider extensions changed """ registry = self.registry # Some providers. class ProviderA(ExtensionProvider): """ An extension provider. """ x = List(Int) def get_extension_points(self): """ Return the extension points offered by the provider. """ return [ExtensionPoint(List, 'my.ep')] def get_extensions(self, extension_point_id): """ Return the provider's contributions to an extension point. """ if extension_point_id == 'my.ep': return self.x else: extensions = [] return extensions def _x_changed(self, old, new): """ Static trait change handler. """ self._fire_extension_point_changed( 'my.ep', new, old, slice(0, len(old)) ) return def _x_items_changed(self, event): """ Static trait change handler. """ self._fire_extension_point_changed( 'my.ep', event.added, event.removed, event.index ) return class ProviderB(ExtensionProvider): """ An extension provider. """ x = List(Int) def get_extensions(self, extension_point_id): """ Return the provider's contributions to an extension point. """ if extension_point_id == 'my.ep': return self.x else: extensions = [] return extensions def _x_changed(self, old, new): """ Static trait change handler. """ self._fire_extension_point_changed( 'my.ep', new, old, slice(0, len(old)) ) return def _x_items_changed(self, event): """ Static trait change handler. """ self._fire_extension_point_changed( 'my.ep', event.added, event.removed, event.index ) return # Add the providers to the registry. a = ProviderA(x=[42]) b = ProviderB(x=[99, 100]) registry.add_provider(a) registry.add_provider(b) # The provider's extensions should now be in the registry. extensions = registry.get_extensions('my.ep') self.assertEqual(3, len(extensions)) self.assertEqual([42, 99, 100], extensions) # Add an extension listener to the registry. def listener(registry, event): """ A useful trait change handler for testing! """ listener.registry = registry listener.extension_point = event.extension_point_id listener.added = event.added listener.removed = event.removed listener.index = event.index return registry.add_extension_point_listener(listener, 'my.ep') # Add a new extension via the provider. a.x.append(43) # Make sure the listener got called. self.assertEqual('my.ep', listener.extension_point) self.assertEqual([43], listener.added) self.assertEqual([], listener.removed) self.assertEqual(1, listener.index) # Now we should get the new extension. extensions = registry.get_extensions('my.ep') self.assertEqual(4, len(extensions)) self.assertEqual([42, 43, 99, 100], extensions) # Insert a new extension via the other provider. b.x.insert(0, 98) # Make sure the listener got called. self.assertEqual('my.ep', listener.extension_point) self.assertEqual([98], listener.added) self.assertEqual([], listener.removed) self.assertEqual(2, listener.index) # Now we should get the new extension. extensions = registry.get_extensions('my.ep') self.assertEqual(5, len(extensions)) self.assertEqual([42, 43, 98, 99, 100], extensions) # Completely change a provider's extensions. b.x = [1, 2] # Make sure the listener got called. self.assertEqual('my.ep', listener.extension_point) self.assertEqual([1, 2], listener.added) self.assertEqual([98, 99, 100], listener.removed) self.assertEqual(2, listener.index.start) self.assertEqual(5, listener.index.stop) # Now we should get the new extension. extensions = registry.get_extensions('my.ep') self.assertEqual(4, len(extensions)) self.assertEqual([42, 43, 1, 2], extensions) return def test_add_provider(self): """ add provider """ registry = self.registry # A provider. class ProviderA(ExtensionProvider): """ An extension provider. """ def get_extension_points(self): """ Return the extension points offered by the provider. """ return [ExtensionPoint(List, 'x')] def get_extensions(self, extension_point): """ Return the provider's contributions to an extension point. """ if extension_point == 'x': return [42] else: extensions = [] return extensions def _x_items_changed(self, event): """ Static trait change handler. """ self._fire_extension_point_changed( 'x', event.added, event.removed, event.index ) return # Add the provider to the registry. registry.add_provider(ProviderA()) # The provider's extensions should now be in the registry. extensions = registry.get_extensions('x') self.assertEqual(1, len(extensions)) self.assert_(42 in extensions) # Add an extension listener to the registry. def listener(registry, event): """ A useful trait change handler for testing! """ listener.registry = registry listener.extension_point = event.extension_point_id listener.added = event.added listener.removed = event.removed listener.index = event.index return registry.add_extension_point_listener(listener, 'x') # Add a new provider. class ProviderB(ExtensionProvider): """ An extension provider. """ def get_extensions(self, extension_point): """ Return the provider's contributions to an extension point. """ if extension_point == 'x': extensions = [43, 44] else: extensions = [] return extensions registry.add_provider(ProviderB()) # Make sure the listener got called. self.assertEqual('x', listener.extension_point) self.assertEqual([43, 44], listener.added) self.assertEqual([], listener.removed) # Now we should get the new extensions. extensions = registry.get_extensions('x') self.assertEqual(3, len(extensions)) self.assert_(42 in extensions) self.assert_(43 in extensions) self.assert_(44 in extensions) return def test_get_providers(self): """ get providers """ registry = self.registry # Some providers. class ProviderA(ExtensionProvider): """ An extension provider. """ class ProviderB(ExtensionProvider): """ An extension provider. """ a = ProviderA() b = ProviderB() # Add the provider to the registry. registry.add_provider(a) registry.add_provider(b) # Make sure we can get them. self.assertEqual([a, b], registry.get_providers()) return def test_remove_provider(self): """ remove provider """ registry = self.registry # Some providers. class ProviderA(ExtensionProvider): """ An extension provider. """ def get_extension_points(self): """ Return the extension points offered by the provider. """ return [ExtensionPoint(List, 'x'), ExtensionPoint(List, 'y')] def get_extensions(self, extension_point): """ Return the provider's contributions to an extension point. """ if extension_point == 'x': return [42] else: extensions = [] return extensions def _x_items_changed(self, event): """ Static trait change handler. """ self._fire_extension_point_changed( 'x', event.added, event.removed, event.index ) return class ProviderB(ExtensionProvider): """ An extension provider. """ def get_extensions(self, extension_point): """ Return the provider's contributions to an extension point. """ if extension_point == 'x': extensions = [43, 44] else: extensions = [] return extensions # Add the providers to the registry. a = ProviderA() b = ProviderB() registry.add_provider(a) registry.add_provider(b) # The provider's extensions should now be in the registry. extensions = registry.get_extensions('x') self.assertEqual(3, len(extensions)) self.assert_(42 in extensions) self.assert_(43 in extensions) self.assert_(44 in extensions) # Add an extension listener to the registry. def listener(registry, event): """ A useful trait change handler for testing! """ listener.registry = registry listener.extension_point = event.extension_point_id listener.added = event.added listener.removed = event.removed return registry.add_extension_point_listener(listener, 'x') # Remove one of the providers. registry.remove_provider(b) # Make sure the listener got called. self.assertEqual('x', listener.extension_point) self.assertEqual([], listener.added) self.assertEqual([43, 44], listener.removed) # Make sure we don't get the removed extensions. extensions = registry.get_extensions('x') self.assertEqual(1, len(extensions)) self.assert_(42 in extensions) # Now remove the provider that declared the extension point. registry.remove_provider(a) # Make sure the extension point is gone. self.assertEqual(None, registry.get_extension_point('x')) # Make sure we don't get the removed extensions. extensions = registry.get_extensions('x') self.assertEqual(0, len(extensions)) # Make sure the listener got called. self.assertEqual('x', listener.extension_point) self.assertEqual([], listener.added) self.assertEqual([42], listener.removed) return def test_remove_provider_with_no_contributions(self): """ remove provider with no contributions """ registry = self.registry # Some providers. class ProviderA(ExtensionProvider): """ An extension provider. """ def get_extension_points(self): """ Return the extension points offered by the provider. """ return [ExtensionPoint(List, 'x'), ExtensionPoint(List, 'y')] def get_extensions(self, extension_point): """ Return the provider's contributions to an extension point. """ return [] # Add the provider to the registry. a = ProviderA() registry.add_provider(a) # The provider's extensions should now be in the registry. extensions = registry.get_extensions('x') self.assertEqual(0, len(extensions)) # Add an extension listener to the registry. def listener(registry, event): """ A useful trait change handler for testing! """ listener.registry = registry listener.extension_point = event.extension_point_id listener.added = event.added listener.removed = event.removed return registry.add_extension_point_listener(listener, 'x') # Remove the provider that declared the extension point. registry.remove_provider(a) # Make sure the extension point is gone. self.assertEqual(None, registry.get_extension_point('x')) # Make sure we don't get the removed extensions. extensions = registry.get_extensions('x') self.assertEqual(0, len(extensions)) # Make sure the listener did not get called (since the provider did # not make any contributions anyway!). self.assertEqual(None, getattr(listener, 'registry', None)) return def test_remove_non_existent_provider(self): """ remove provider """ registry = self.registry # Some providers. class ProviderA(ExtensionProvider): """ An extension provider. """ pass a = ProviderA() # Remove one of the providers. self.failUnlessRaises(ValueError, registry.remove_provider, a) return # Overriden to test differing behavior between the provider registry and # the base class. def test_set_extensions(self): """ set extensions """ registry = self.registry # Add an extension *point*. registry.add_extension_point(self._create_extension_point('my.ep')) # Set some extensions. self.failUnlessRaises( SystemError, registry.set_extensions, 'my.ep', [1, 2, 3] ) return def test_remove_non_empty_extension_point(self): """ remove non-empty extension point """ registry = self.registry # Some providers. class ProviderA(ExtensionProvider): """ An extension provider. """ def get_extension_points(self): """ Return the extension points offered by the provider. """ return [ExtensionPoint(List, 'x')] def get_extensions(self, extension_point): """ Return the provider's contributions to an extension point. """ if extension_point == 'x': extensions = [42, 43] else: extensions = [] return extensions # Add the provider to the registry. registry.add_provider(ProviderA()) # The provider's extensions should now be in the registry. extensions = registry.get_extensions('x') self.assertEqual(2, len(extensions)) self.assertEqual(range(42, 44), extensions) # Make sure there's one and only one extension point. extension_points = registry.get_extension_points() self.assertEqual(1, len(extension_points)) self.assertEqual('x', extension_points[0].id) # Remove the extension point. registry.remove_extension_point('x') # Make sure there are no extension points. extension_points = registry.get_extension_points() self.assertEqual(0, len(extension_points)) # And that the extensions are gone too. self.assertEqual([], registry.get_extensions('x')) return # Entry point for stand-alone testing. if __name__ == '__main__': unittest.main() #### EOF ###################################################################### envisage-4.4.0/envisage/tests/extension_point_test_case.py0000644000175000017500000001745712252661404024642 0ustar davidcdavidc00000000000000""" Tests for extension points. """ # Enthought library imports. from envisage.api import Application, ExtensionPoint from envisage.api import ExtensionRegistry from traits.api import HasTraits, Int, List, TraitError from traits.testing.unittest_tools import unittest class TestBase(HasTraits): """ Base class for all test classes that use the 'ExtensionPoint' type. """ extension_registry = None class ExtensionPointTestCase(unittest.TestCase): """ Tests for extension points. """ ########################################################################### # 'TestCase' interface. ########################################################################### def setUp(self): """ Prepares the test fixture before each test method is called. """ # We do all of the testing via the application to make sure it offers # the same interface! self.registry = Application(extension_registry=ExtensionRegistry()) # Set the extension registry used by the test classes. TestBase.extension_registry = self.registry return def tearDown(self): """ Called immediately after each test method has been called. """ return ########################################################################### # Tests. ########################################################################### 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. self.failUnlessRaises(TypeError, ExtensionPoint, Int, 'my.ep') return def test_no_reference_to_extension_registry(self): """ no reference to extension registry """ registry = self.registry # Add an extension point. registry.add_extension_point(self._create_extension_point('my.ep')) # Set the extensions. registry.set_extensions('my.ep', 'xxx') # Declare a class that consumes the extension. class Foo(HasTraits): x = ExtensionPoint(List(Int), id='my.ep') # We should get an exception because the object does not have an # 'extension_registry' trait. f = Foo() self.failUnlessRaises(ValueError, getattr, f, 'x') return 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 def test_untyped_extension_point(self): """ untyped extension point """ registry = self.registry # Add an extension point. registry.add_extension_point(self._create_extension_point('my.ep')) # Set the extensions. registry.set_extensions('my.ep', [42, 'a string', True]) # Declare a class that consumes the extension. class Foo(TestBase): x = ExtensionPoint(id='my.ep') # Make sure that instances of the class pick up the extensions. f = Foo() self.assertEqual(3, len(f.x)) self.assertEqual([42, 'a string', True], f.x) g = Foo() self.assertEqual(3, len(g.x)) self.assertEqual([42, 'a string', True], g.x) return def test_typed_extension_point(self): """ typed extension point """ registry = self.registry # Add an extension point. registry.add_extension_point(self._create_extension_point('my.ep')) # Set the extensions. registry.set_extensions('my.ep', [42, 43, 44]) # Declare a class that consumes the extension. class Foo(TestBase): x = ExtensionPoint(List(Int), id='my.ep') # Make sure that instances of the class pick up the extensions. f = Foo() self.assertEqual(3, len(f.x)) self.assertEqual([42, 43, 44], f.x) g = Foo() self.assertEqual(3, len(g.x)) self.assertEqual([42, 43, 44], g.x) return def test_invalid_extension_point(self): """ invalid extension point """ registry = self.registry # Add an extension point. registry.add_extension_point(self._create_extension_point('my.ep')) # Set the extensions. registry.set_extensions('my.ep', 'xxx') # Declare a class that consumes the extension. class Foo(TestBase): x = ExtensionPoint(List(Int), id='my.ep') # Make sure we get a trait error because the type of the extension # doesn't match that of the extension point. f = Foo() self.failUnlessRaises(TraitError, getattr, f, 'x') return def test_extension_point_with_no_id(self): """ extension point with no Id """ def factory(): class Foo(TestBase): x = ExtensionPoint(List(Int)) self.failUnlessRaises(ValueError, factory) return def test_set_untyped_extension_point(self): """ set untyped extension point """ 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') # Make sure that when we set the trait the extension registry gets # updated. f = Foo() f.x = [42] self.assertEqual([42], registry.get_extensions('my.ep')) return def test_set_typed_extension_point(self): """ set typed extension point """ 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(List(Int), id='my.ep') # Make sure that when we set the trait the extension registry gets # updated. f = Foo() f.x = [42] self.assertEqual([42], registry.get_extensions('my.ep')) return ########################################################################### # Private interface. ########################################################################### def _create_extension_point(self, id, trait_type=List, desc=''): """ Create an extension point. """ return ExtensionPoint(id=id, trait_type=trait_type, desc=desc) # Entry point for stand-alone testing. if __name__ == '__main__': unittest.main() #### EOF ###################################################################### envisage-4.4.0/envisage/tests/bar_category.py0000644000175000017500000000036012252661404022005 0ustar davidcdavidc00000000000000""" A test class used to test categories. """ # Enthought library imports. from traits.api import HasTraits, Int class BarCategory(HasTraits): y = Int #### EOF ###################################################################### envisage-4.4.0/envisage/tests/slice_test_case.py0000644000175000017500000000757512252661404022514 0ustar davidcdavidc00000000000000""" Tests to help find out how trait list events work. These tests exist because when we are using the 'ExtensionPoint' trait type we try to mimic trait list events when extensions are added or removed. """ # Enthought library imports. from traits.api import HasTraits, List from traits.testing.unittest_tools import unittest # The starting list for all tests. TEST_LIST = [7, 9, 2, 3, 4, 1, 6, 5, 8, 0] def listener(obj, trait_name, old, event): """ Recreate a list operation from a trait list event. """ clone = TEST_LIST[:] # If nothing was added then this is a 'del' or 'remove' operation. if len(event.added) == 0: if isinstance(event.index, slice): del clone[event.index] else: del clone[event.index : event.index + len(event.removed)] # If nothing was removed then it is an 'append', 'insert' or 'extend' # operation. elif len(event.removed) == 0: if isinstance(event.index, slice): clone[event.index] = event.added[0] else: clone.insert(event.index, event.added[0]) # Otherwise, it is an assigment ('sort' and 'reverse' fall into this # category). else: if isinstance(event.index, slice): clone[event.index] = event.added[0] else: clone[event.index : event.index + len(event.added)] = event.added listener.clone = clone return class SliceTestCase(unittest.TestCase): """ Tests to help find out how trait list events work. """ ########################################################################### # 'TestCase' interface. ########################################################################### def setUp(self): """ Prepares the test fixture before each test method is called. """ class Foo(HasTraits): l = List self.f = Foo(l=TEST_LIST) self.f.on_trait_change(listener, 'l_items') return def tearDown(self): """ Called immediately after each test method has been called. """ # Make sure we successfully recreated the operation. self.assertEqual(self.f.l, listener.clone) return ########################################################################### # Tests. ########################################################################### def test_append(self): """ append """ self.f.l.append(99) return def test_insert(self): """ insert """ self.f.l.insert(3, 99) return def test_extend(self): """ extend """ self.f.l.append([99, 100]) return def test_remove(self): """ remove """ self.f.l.remove(5) return def test_reverse(self): """ reverse """ self.f.l.reverse() return def test_sort(self): """ sort """ self.f.l.sort() return def test_pop(self): """ remove """ self.f.l.pop() return def test_del_all(self): """ del all """ del self.f.l[:] return def test_assign_item(self): """ assign item """ self.f.l[3] = 99 return def test_del_item(self): """ del item """ del self.f.l[3] return def test_assign_slice(self): """ assign slice """ self.f.l[2:4] = [88, 99] return def test_del_slice(self): """ del slice """ del self.f.l[2:5] return def test_assign_extended_slice(self): """ assign extended slice """ self.f.l[2:6:2] = [88, 99] return def test_del_extended_slice(self): """ del extended slice """ del self.f.l[2:6:2] return # Entry point for stand-alone testing. if __name__ == '__main__': unittest.main() #### EOF ###################################################################### envisage-4.4.0/envisage/i_plugin_manager.py0000644000175000017500000000367112252661404021512 0ustar davidcdavidc00000000000000""" The plugin manager interface. """ # Enthought library imports. from traits.api import Event, Interface # Local imports. from plugin_event import PluginEvent class IPluginManager(Interface): """ The plugin manager interface. """ #### Events #### # Fired when a plugin has been added to the manager. plugin_added = Event(PluginEvent) # Fired when a plugin has been removed from the manager. plugin_removed = Event(PluginEvent) def __iter__(self): """ Return an iterator over the manager's plugins. """ def add_plugin(self, plugin): """ Add a plugin to the manager. """ def get_plugin(self, plugin_id): """ Return the plugin with the specified Id. Return None if no such plugin exists. """ def remove_plugin(self, plugin): """ Remove a plugin from the manager. """ def start(self): """ Start the plugin manager. This starts all of the manager's plugins. """ def start_plugin(self, plugin=None, plugin_id=None): """ Start the specified plugin. If a plugin is specified then start it. If no plugin is specified then the Id is used to look up the plugin and then start it. If no such plugin exists then a 'SystemError' exception is raised. """ def stop(self): """ Stop the plugin manager. This stop's all of the plugin manager's plugins (in the reverse order that they were started). """ def stop_plugin(self, plugin=None, plugin_id=None): """ Stop the specified plugin. If a plugin is specified then stop it (the Id is ignored). If no plugin is specified then the Id is used to look up the plugin and then stop it. If no such plugin exists then a 'SystemError' exception is raised. """ #### EOF ###################################################################### envisage-4.4.0/envisage/application.py0000644000175000017500000004061212252661404020511 0ustar davidcdavidc00000000000000""" An extensible, pluggable, application. """ # Standard library imports. import logging, os # Enthought library imports. from traits.etsconfig.api import ETSConfig from apptools.preferences.api import IPreferences, ScopedPreferences from apptools.preferences.api import set_default_preferences from traits.api import Delegate, Event, HasTraits, Instance, Str from traits.api import VetoableEvent, implements # Local imports. from i_application import IApplication from i_extension_registry import IExtensionRegistry from i_import_manager import IImportManager from i_plugin_manager import IPluginManager from i_service_registry import IServiceRegistry from application_event import ApplicationEvent from import_manager import ImportManager # Logging. logger = logging.getLogger(__name__) class Application(HasTraits): """ An extensible, pluggable, application. This class handles the common case for non-GUI applications, and it is intended to be subclassed to change start/stop behaviour etc. """ implements(IApplication) #### 'IApplication' interface ############################################# # The application's globally unique identifier. id = Str # The name of a directory (created for you) to which the application can # read and write non-user accessible data, i.e. configuration information, # preferences, etc. home = Str # The name of a directory (created for you upon access) to which the # application can read and write user-accessible data, e.g. projects created # by the user. user_data = Str # The root preferences node. preferences = Instance(IPreferences) #### Events #### # Fired when the application is starting. starting = VetoableEvent(ApplicationEvent) # Fired when all plugins have been started. started = Event(ApplicationEvent) # Fired when the application is stopping. stopping = VetoableEvent(ApplicationEvent) # Fired when all plugins have been stopped. stopped = Event(ApplicationEvent) #### 'IPluginManager' interface ########################################### #### Events #### # Fired when a plugin has been added. plugin_added = Delegate('plugin_manager', modify=True) # Fired when a plugin has been removed. plugin_removed = Delegate('plugin_manager', modify=True) #### 'Application' interface ############################################## # These traits allow application developers to build completely different # styles of extensible application. It allows Envisage to be used as a # framework for frameworks ;^) # # The extension registry. extension_registry = Instance(IExtensionRegistry) # The plugin manager (starts and stops plugins etc). plugin_manager = Instance(IPluginManager) # The service registry. service_registry = Instance(IServiceRegistry) #### Private interface #################################################### # The import manager. _import_manager = Instance(IImportManager, factory=ImportManager) ########################################################################### # 'object' interface. ########################################################################### def __init__(self, plugins=None, **traits): """ Constructor. We allow the caller to specify an initial list of plugins, but the list itself is not part of the public API. To add and remove plugins after after construction, use the 'add_plugin' and 'remove_plugin' methods respectively. The application is also iterable, so to iterate over the plugins use 'for plugin in application: ...'. """ super(Application, self).__init__(**traits) # fixme: We have to initialize the application home here (i.e. we can't # wait until the 'home' trait is accessed) because the scoped # preferences uses 'ETSConfig.application' home as the name of the # default preferences file. self._initialize_application_home() # Set the default preferences node used by the preferences package. # This allows 'PreferencesHelper' and 'PreferenceBinding' instances to # be used as more convenient ways to access preferences. # # fixme: This is another sneaky global! set_default_preferences(self.preferences) # We allow the caller to specify an initial list of plugins, but the # list itself is not part of the public API. To add and remove plugins # after construction, use the 'add_plugin' and 'remove_plugin' methods # respectively. The application is also iterable, so to iterate over # the plugins use 'for plugin in application: ...'. if plugins is not None: map(self.add_plugin, plugins) return ########################################################################### # 'IApplication' interface. ########################################################################### #### Trait initializers ################################################### def _home_default(self): """ Trait initializer. """ return ETSConfig.application_home def _user_data_default(self): """ Trait initializer. """ user_data = os.path.join( ETSConfig.user_data, self.id ) # Make sure it exists! if not os.path.exists(user_data): os.makedirs(user_data) return user_data def _preferences_default(self): """ Trait initializer. """ return ScopedPreferences() #### Methods ############################################################## def run(self): """ Run the application. """ if self.start(): self.stop() return ########################################################################### # 'IExtensionRegistry' interface. ########################################################################### def add_extension_point_listener(self, listener, extension_point_id=None): """ Add a listener for extensions being added/removed. """ self.extension_registry.add_extension_point_listener( listener, extension_point_id ) return def add_extension_point(self, extension_point): """ Add an extension point. """ self.extension_registry.add_extension_point(extension_point) return def get_extensions(self, extension_point_id): """ Return a list containing all contributions to an extension point. """ return self.extension_registry.get_extensions(extension_point_id) def get_extension_point(self, extension_point_id): """ Return the extension point with the specified Id. """ return self.extension_registry.get_extension_point(extension_point_id) def get_extension_points(self): """ Return all extension points that have been added to the registry. """ return self.extension_registry.get_extension_points() def remove_extension_point_listener(self,listener,extension_point_id=None): """ Remove a listener for extensions being added/removed. """ self.extension_registry.remove_extension_point_listener( listener, extension_point_id ) return def remove_extension_point(self, extension_point_id): """ Remove an extension point. """ self.extension_registry.remove_extension_point(extension_point_id) return def set_extensions(self, extension_point_id, extensions): """ Set the extensions contributed to an extension point. """ self.extension_registry.set_extensions(extension_point_id, extensions) return ########################################################################### # 'IImportManager' interface. ########################################################################### def import_symbol(self, symbol_path): """ Import the symbol defined by the specified symbol path. """ return self._import_manager.import_symbol(symbol_path) ########################################################################### # 'IPluginManager' interface. ########################################################################### def __iter__(self): """ Return an iterator over the manager's plugins. """ return iter(self.plugin_manager) def add_plugin(self, plugin): """ Add a plugin to the manager. """ self.plugin_manager.add_plugin(plugin) return def get_plugin(self, plugin_id): """ Return the plugin with the specified Id. """ return self.plugin_manager.get_plugin(plugin_id) def remove_plugin(self, plugin): """ Remove a plugin from the manager. """ self.plugin_manager.remove_plugin(plugin) return def start(self): """ Start the plugin manager. Returns True unless the start was vetoed. """ # fixme: This method is notionally on the 'IPluginManager' interface # but that interface knows nothing about the vetoable events etc and # hence doesn't have a return value. logger.debug('---------- application starting ----------') # Lifecycle event. self.starting = event = self._create_application_event() if not event.veto: # Start the plugin manager (this starts all of the manager's # plugins). self.plugin_manager.start() # Lifecycle event. self.started = self._create_application_event() logger.debug('---------- application started ----------') else: logger.debug('---------- application start vetoed ----------') return not event.veto def start_plugin(self, plugin=None, plugin_id=None): """ Start the specified plugin. """ return self.plugin_manager.start_plugin(plugin, plugin_id) def stop(self): """ Stop the plugin manager. Returns True unless the stop was vetoed. """ # fixme: This method is notionally on the 'IPluginManager' interface # but that interface knows nothing about the vetoable events etc and # hence doesn't have a return value. logger.debug('---------- application stopping ----------') # Lifecycle event. self.stopping = event = self._create_application_event() if not event.veto: # Stop the plugin manager (this stops all of the manager's # plugins). self.plugin_manager.stop() # Save all preferences. self.preferences.save() # Lifecycle event. self.stopped = self._create_application_event() logger.debug('---------- application stopped ----------') else: logger.debug('---------- application stop vetoed ----------') return not event.veto def stop_plugin(self, plugin=None, plugin_id=None): """ Stop the specified plugin. """ return self.plugin_manager.stop_plugin(plugin, plugin_id) ########################################################################### # 'IServiceRegistry' interface. ########################################################################### def get_required_service(self, protocol, query='', minimize='',maximize=''): """ Return the service that matches the specified query. Raise a 'NoSuchServiceError' exception if no such service exists. """ service = self.service_registry.get_required_service( protocol, query, minimize, maximize ) return service 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_from_id(self, service_id): """ Return the service with the specified id. """ return self.service_registry.get_service_from_id(service_id) 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 ########################################################################### # 'Application' interface. ########################################################################### #### Trait initializers ################################################### def _extension_registry_default(self): """ Trait initializer. """ # Do the import here to emphasize the fact that this is just the # default implementation and that the application developer is free # to override it! from plugin_extension_registry import PluginExtensionRegistry return PluginExtensionRegistry(plugin_manager=self) def _plugin_manager_default(self): """ Trait initializer. """ # Do the import here to emphasize the fact that this is just the # default implementation and that the application developer is free # to override it! from plugin_manager import PluginManager return PluginManager(application=self) def _service_registry_default(self): """ Trait initializer. """ # Do the import here to emphasize the fact that this is just the # default implementation and that the application developer is free # to override it! from service_registry import ServiceRegistry return ServiceRegistry() ########################################################################### # Private interface. ########################################################################### #### Trait change handlers ################################################ # fixme: We have this to make it easier to assign a new plugin manager # at construction time due to the fact that the plugin manager needs a # reference to the application and vice-versa, e.g. we can do # # application = Application(plugin_manager=EggPluginManager()) # # If we didn't have this then we would have to do this:- # # application = Application() # application.plugin_manager = EggPluginManager(application=application) # # Of course, it would be better if the plugin manager didn't require a # reference to the application at all (it currently uses it to set the # 'application' trait of plugin instances - but that is only done for the # same reason as this (i.e. it is nice to be able to pass plugins into the # application constructor). def _plugin_manager_changed(self, trait_name, old, new): """ Static trait change handler. """ if old is not None: old.application = None if new is not None: new.application = self return #### Methods ############################################################## def _create_application_event(self): """ Create an application event. """ return ApplicationEvent(application=self) def _initialize_application_home(self): """ Initialize the application home directory. """ ETSConfig.application_home = os.path.join( ETSConfig.application_data, self.id ) # Make sure it exists! if not os.path.exists(ETSConfig.application_home): os.makedirs(ETSConfig.application_home) return #### EOF ###################################################################### envisage-4.4.0/envisage/plugin_extension_registry.py0000644000175000017500000000405612252661404023532 0ustar davidcdavidc00000000000000""" An extension registry that uses plugins as extension providers. """ # Enthought library imports. from traits.api import Instance, on_trait_change # Local imports. from i_plugin_manager import IPluginManager from provider_extension_registry import ProviderExtensionRegistry class PluginExtensionRegistry(ProviderExtensionRegistry): """ An extension registry that uses plugins as extension providers. The application's plugins are used as the registries providers so adding or removing a plugin affects the extension points and extensions etc. """ #### 'PluginExtensionRegistry' interface ################################## # The plugin manager that has the plugins we are after! plugin_manager = Instance(IPluginManager) ########################################################################### # 'PluginExtensionRegistry' interface. ########################################################################### #### Trait change handlers ################################################ def _plugin_manager_changed(self, trait_name, old, new): """ Static trait change handler. """ # In practise I can't see why you would ever want (or need) to change # the registry's plugin manager on the fly, but hey... Hence, 'old' # will probably always be 'None'! if old is not None: for plugin in old: self.remove_provider(plugin) if new is not None: for plugin in new: self.add_provider(plugin) return @on_trait_change('plugin_manager:plugin_added') def _on_plugin_added(self, obj, trait_name, old, event): """ Dynamic trait change handler. """ self.add_provider(event.plugin) return @on_trait_change('plugin_manager:plugin_removed') def _on_plugin_removed(self, obj, trait_name, old, event): """ Dynamic trait change handler. """ self.remove_provider(event.plugin) return #### EOF ###################################################################### envisage-4.4.0/envisage/plugin.py0000644000175000017500000003421112252661404017502 0ustar davidcdavidc00000000000000""" The default implementation of the 'IPlugin' interface. """ # Standard library imports. import inspect, logging, os from os.path import exists, join # Enthought library imports. from traits.api import Instance, List, Property, Str, implements from traits.util.camel_case import camel_case_to_words # Local imports. from extension_point import ExtensionPoint from extension_provider import ExtensionProvider from i_application import IApplication from i_extension_point_user import IExtensionPointUser from i_extension_registry import IExtensionRegistry from i_plugin import IPlugin from i_plugin_activator import IPluginActivator from i_service_registry import IServiceRegistry from i_service_user import IServiceUser from plugin_activator import PluginActivator # Logging. logger = logging.getLogger(__name__) class Plugin(ExtensionProvider): """ The default implementation of the 'IPlugin' interface. This class is intended to be subclassed for each plugin that you create. """ implements(IPlugin, IExtensionPointUser, IServiceUser) #### 'IPlugin' interface ################################################## # The activator used to start and stop the plugin. # # By default the *same* activator instance is used for *all* plugins of # this type. activator = Instance(IPluginActivator, PluginActivator()) # The application that the plugin is part of. application = Instance(IApplication) # The name of a directory (created for you) that the plugin can read and # write to at will. home = Str # The plugin's unique identifier. # # If no identifier is specified then the module and class name of the # plugin are used to create an Id with the form 'module_name.class_name'. id = Str # The plugin's name (suitable for displaying to the user). # # If no name is specified then the plugin's class name is used with an # attempt made to turn camel-case class names into words separated by # spaces (e.g. if the class name is 'MyPlugin' then the name would be # 'My Plugin'). Of course, if you really care about the actual name, then # just set it! name = Str #### 'IExtensionPointUser' interface ###################################### # The extension registry that the object's extension points are stored in. extension_registry = Property(Instance(IExtensionRegistry)) #### 'IServiceUser' interface ############################################# # The service registry that the object's services are stored in. service_registry = Property(Instance(IServiceRegistry)) #### Private interface #################################################### # 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 ########################################################################### # 'IServiceUser' interface. ########################################################################### def _get_service_registry(self): """ Trait property getter. """ return self.application ########################################################################### # 'IExtensionProvider' interface. ########################################################################### def get_extension_points(self): """ Return the extension points offered by the provider. """ extension_points = [ trait.trait_type for trait in self.traits(__extension_point__=True).values() ] return extension_points def get_extensions(self, extension_point_id): """ Return the provider's extensions to an extension point. """ # Each class can have at most *one* trait that contributes to a # particular extension point. # # fixme: We make this restriction in case that in future we can wire up # the list traits directly. If we don't end up doing that then it is # fine to allow mutiple traits! trait_names = self.trait_names(contributes_to=extension_point_id) # FIXME: This is a temporary fix, which was necessary due to the # namespace refactor, but should be removed at some point. if len(trait_names) == 0: old_id = 'enthought.' + extension_point_id trait_names = self.trait_names(contributes_to=old_id) # if trait_names: # print 'deprecated:', old_id if len(trait_names) == 0: # If there is no contributing trait then look for any decorated # methods. extensions = self._harvest_methods(extension_point_id) # FIXME: This is a temporary fix, which was necessary due to the # namespace refactor, but should be removed at some point. if not extensions: old_id = 'enthought.' + extension_point_id extensions = self._harvest_methods(old_id) # if extensions: # print 'deprecated:', old_id elif len(trait_names) == 1: extensions = self._get_extensions_from_trait(trait_names[0]) else: raise self._create_multiple_traits_exception(extension_point_id) return extensions ########################################################################### # 'IPlugin' interface. ########################################################################### #### Trait initializers ################################################### def _home_default(self): """ Trait initializer. """ # Each plugin gets a sub-directory of a 'plugins' directory in # 'application.home'. # # i.e. .../my.application.id/plugins/ plugins_dir = join(self.application.home, 'plugins') if not exists(plugins_dir): os.mkdir(plugins_dir) # Now create the 'home' directory of this plugin. home_dir = join(plugins_dir, self.id) if not exists(home_dir): os.mkdir(home_dir) return home_dir def _id_default(self): """ Trait initializer. """ id = '%s.%s' % (type(self).__module__, type(self).__name__) logger.warn('plugin %s has no Id - using <%s>' % (self, id)) return id def _name_default(self): """ Trait initializer. """ name = camel_case_to_words(type(self).__name__) logger.warn('plugin %s has no name - using <%s>' % (self, name)) return name #### Methods ############################################################## def start(self): """ Start the plugin. This method will *always* be empty so that you never have to call 'super(xxx, self).start()' if you provide an implementation in a derived class. The framework does what it needs to do when it starts a plugin by means of the plugin's activator. """ pass def stop(self): """ Stop the plugin. This method will *always* be empty so that you never have to call 'super(xxx, self).stop()' if you provide an implementation in a derived class. The framework does what it needs to do when it stops a plugin by means of the plugin's activator. """ pass ########################################################################### # 'Plugin' interface. ########################################################################### def connect_extension_point_traits(self): """ Connect all of the plugin's extension points. This means that the plugin will be notified if and when contributions are add or removed. """ ExtensionPoint.connect_extension_point_traits(self) return def disconnect_extension_point_traits(self): """ Disconnect all of the plugin's extension points.""" ExtensionPoint.disconnect_extension_point_traits(self) return def register_services(self): """ Register the services offered by the plugin. """ for trait_name, trait in self.traits(service=True).items(): logger.warn( 'DEPRECATED: Do not use the "service=True" metadata anymore. ' 'Services should now be offered using the service ' 'offer extension point (envisage.service_offers) ' 'from the core plugin. ' 'Plugin %s trait <%s>' % (self, trait_name) ) # Register a service factory for the trait. service_id = self._register_service_factory(trait_name, trait) # We save the service Id so that so that we can unregister the # service when the plugin is stopped. self._service_ids.append(service_id) return def unregister_services(self): """ Unregister any service offered by the plugin. """ # Unregister the services in the reverse order that we registered # them. service_ids = self._service_ids[:] service_ids.reverse() for service_id in service_ids: self.application.unregister_service(service_id) # Just in case the plugin is started again! self._service_ids = [] return ########################################################################### # Private interface. ########################################################################### #### Trait change handlers ################################################ def _anytrait_changed(self, trait_name, old, new): """ Static trait change handler. """ # Ignore the '_items' part of the trait name (if it is there!), and get # the actual trait. base_trait_name = trait_name.split('_items')[0] trait = self.trait(base_trait_name) # If the trait is one that contributes to an extension point then fire # an appropriate 'extension point changed' event. if trait.contributes_to is not None: if trait_name.endswith('_items'): added = new.added removed = new.removed index = new.index else: added = new removed = old index = slice(0, max(len(old), len(new))) # Let the extension registry know about the change. self._fire_extension_point_changed( trait.contributes_to, added, removed, index ) return #### Methods ############################################################## def _create_multiple_traits_exception(self, extension_point_id): """ Create the exception raised when multiple traits are found. """ exception = ValueError( 'multiple traits for extension point <%s> in plugin <%s>' % ( extension_point_id, self.id ) ) return exception def _get_extensions_from_trait(self, trait_name): """ Return the extensions contributed via the specified trait. """ try: extensions = getattr(self, trait_name) except: logger.exception( 'getting extensions from %s, trait <%s>' % (self, trait_name) ) raise return extensions def _get_service_protocol(self, trait): """ Determine the protocol to register a service trait with. """ # If a specific protocol was specified then use it. if trait.service_protocol is not None: protocol = trait.service_protocol # Otherwise, use the type of the objects that can be assigned to the # trait. # # fixme: This works for 'Instance' traits, but what about 'AdaptsTo' # and 'AdaptedTo' traits? else: # Note that in traits the protocol can be an actual class or # interfacem or the *name* of a class or interface. This allows # us to lazy load them! protocol = trait.trait_type.klass return protocol def _harvest_methods(self, extension_point_id): """ Harvest all method-based contributions. """ extensions = [] for name, value in inspect.getmembers(self): if self._is_extension_method(value, extension_point_id): result = getattr(self, name)() if not isinstance(result, list): result = [result] extensions.extend(result) return extensions def _is_extension_method(self, value, extension_point_id): """ Return True if the value is an extension method. i.e. If the method is one that makes a contribution to the extension point. Currently there is exactly one way to make a method make a contribution, and that is to mark it using the 'contributes_to' decorator, e.g:: @contributes_to('acme.motd.messages') def get_messages(self): ... messages = [...] ... return messages """ is_extension_method = inspect.ismethod(value) \ and extension_point_id == getattr(value,'__extension_point__',None) return is_extension_method def _register_service_factory(self, trait_name, trait): """ Register a service factory for the specified trait. """ # Determine the protocol that the service should be registered with. protocol = self._get_service_protocol(trait) # Register a factory for the service so that it will be lazily loaded # the first time somebody asks for a service with the same protocol # (this could obviously be a lambda function, but I thought it best to # be more explicit 8^). def factory(**properties): """ A service factory. """ return getattr(self, trait_name) return self.application.register_service(protocol, factory) #### EOF ###################################################################### envisage-4.4.0/envisage/extension_point.py0000644000175000017500000002211612252661404021432 0ustar davidcdavidc00000000000000""" A trait type used to declare and access extension points. """ # Standard library imports. import inspect, weakref # Enthought library imports. from traits.api import List, TraitType, Undefined, implements # Local imports. from i_extension_point import IExtensionPoint def contributes_to(id): """ A factory for extension point decorators! As an alternative to making contributions via traits, you can use this decorator to mark any method on a 'Plugin' as contributing to an extension point (note this is *only* used on 'Plugin' instances!). e.g. Using a trait you might have something like:: class MyPlugin(Plugin): messages = List(contributes_to='acme.messages') def _messages_default(self): return ['Hello', 'Hola'] whereas, using the decorator, it would be:: class MyPlugin(Plugin): @contributes_to('acme.messages') def _get_messages(self): return ['Hello', 'Hola'] There is not much in it really, but the decorator version looks a little less like 'magic' since it doesn't require the developer to know about Traits default initializers. However, if you know that you will want to dynamically change your contributions then use the trait version because all you have to do is change the value of the trait and the framework will react accordingly. """ def decorator(fn): """ A decorator for marking methods as extension contributors. """ fn.__extension_point__ = id return fn return decorator # Exception message template. INVALID_TRAIT_TYPE = 'extension points must be "List"s e.g. List, List(Int)' \ ' but a value of %s was specified.' class ExtensionPoint(TraitType): """ A trait type used to declare and access extension points. Note that this is a trait *type* and hence does *NOT* have traits itself (i.e. it does *not* inherit from 'HasTraits'). """ # Even though trait types do not themselves have traits, we can still # declare that we implement an interface (since it is just PyProtocols # underneath the covers). implements(IExtensionPoint) ########################################################################### # 'ExtensionPoint' *CLASS* interface. ########################################################################### @staticmethod def bind(obj, trait_name, extension_point_id): """ Create a binding to an extension point. """ from extension_point_binding import bind_extension_point return bind_extension_point(obj, trait_name, extension_point_id) @staticmethod def connect_extension_point_traits(obj): """ Connect all of the 'ExtensionPoint' traits on an object. """ for trait_name, trait in obj.traits(__extension_point__=True).items(): trait.trait_type.connect(obj, trait_name) return @staticmethod def disconnect_extension_point_traits(obj): """ Disconnect all of the 'ExtensionPoint' traits on an object. """ for trait_name, trait in obj.traits(__extension_point__=True).items(): trait.trait_type.disconnect(obj, trait_name) return ########################################################################### # 'object' interface. ########################################################################### def __init__(self, trait_type=List, id=None, **metadata): """ Constructor. """ # We add '__extension_point__' to the metadata to make the extension # point traits easier to find with the 'traits' and 'trait_names' # methods on 'HasTraits'. metadata['__extension_point__'] = True super(ExtensionPoint, self).__init__(**metadata) # The trait type that describes the extension point. # # If we are handed a trait type *class* e.g. List, instead of a trait # type *instance* e.g. List() or List(Int) etc, then we instantiate it. if inspect.isclass(trait_type): trait_type = trait_type() # Currently, we only support list extension points (we may in the # future want to allow other collections e.g. dictionaries etc). if not isinstance(trait_type, List): raise TypeError(INVALID_TRAIT_TYPE % trait_type) self.trait_type = trait_type # The Id of the extension point. if id is None: raise ValueError('an extension point must have an Id') self.id = id # A dictionary that is used solely to keep a reference to all extension # point listeners alive until their associated objects are garbage # collected. # # Dict(weakref.ref(Any), Dict(Str, Callable)) self._obj_to_listeners_map = weakref.WeakKeyDictionary() return ########################################################################### # 'TraitType' interface. ########################################################################### def get(self, obj, trait_name): """ Trait type getter. """ extension_registry = self._get_extension_registry(obj) # Get the extensions to this extension point. extensions = extension_registry.get_extensions(self.id) # Make sure the contributions are of the appropriate type. return self.trait_type.validate(obj, trait_name, extensions) def set(self, obj, name, value): """ Trait type setter. """ extension_registry = self._get_extension_registry(obj) # Note that some extension registry implementations may not support the # setting of extension points (the default, plugin extension registry # for exxample ;^). extension_registry.set_extensions(self.id, value) return ########################################################################### # 'ExtensionPoint' interface. ########################################################################### def connect(self, obj, trait_name): """ Connect the extension point to a trait on an object. This allows the object to react when contributions are added or removed from the extension point. fixme: It would be nice to be able to make the connection automatically but we would need a slight tweak to traits to allow the trait type to be notified when a new instance that uses the trait type is created. """ def listener(extension_registry, event): """ Listener called when an extension point is changed. """ # If an index was specified then we fire an '_items' changed event. if event.index is not None: name = trait_name + '_items' old = Undefined new = event # Otherwise, we fire a normal trait changed event. else: name = trait_name old = event.removed new = event.added obj.trait_property_changed(name, old, new) return extension_registry = self._get_extension_registry(obj) # Add the listener to the extension registry. extension_registry.add_extension_point_listener(listener, self.id) # Save a reference to the listener so that it does not get garbage # collected until its associated object does. listeners = self._obj_to_listeners_map.setdefault(obj, {}) listeners[trait_name] = listener return def disconnect(self, obj, trait_name): """ Disconnect the extension point from a trait on an object. """ extension_registry = self._get_extension_registry(obj) listener = self._obj_to_listeners_map[obj].get(trait_name) if listener is not None: # Remove the listener from the extension registry. extension_registry.remove_extension_point_listener( listener, self.id ) # Clean up. del self._obj_to_listeners_map[obj][trait_name] return ########################################################################### # Private interface. ########################################################################### def _get_extension_registry(self, obj): """ Return the extension registry in effect for an object. """ extension_registry = getattr(obj, 'extension_registry', None) if extension_registry is None: # If the debug package is present then log the call stack to help # the developer see where the problem occurred. try: from etsdevtools.debug.api import log_called_from log_called_from(10) except: pass raise ValueError( 'The "ExtensionPoint" trait type can only be used in ' \ 'objects that have a reference to an extension registry ' \ 'via their "extension_registry" trait. ' \ 'Extension point Id <%s>' % self.id ) return extension_registry #### EOF ###################################################################### envisage-4.4.0/envisage/extension_point_binding.py0000644000175000017500000001461312252661404023127 0ustar davidcdavidc00000000000000""" A binding between a trait on an object and an extension point. """ # Standard library imports. import weakref # Enthought library imports. from traits.api import Any, HasTraits, Instance, Str, Undefined # Local imports. from i_extension_registry import IExtensionRegistry class ExtensionPointBinding(HasTraits): """ A binding between a trait on an object and an extension point. """ #### 'ExtensionPointBinding' *CLASS* interface ############################ # We keep a reference to each binding alive until its associated object # is garbage collected. _bindings = weakref.WeakKeyDictionary() #### 'ExtensionPointBinding' interface #################################### # The object that we are binding the extension point to. obj = Any # The Id of the extension point. extension_point_id = Str # The extension registry used by the binding. If this trait is not set then # the class-scope extension registry set on the 'ExtensionPoint' class is # used (and if that is not set then the binding won't work ;^) extension_registry = Instance(IExtensionRegistry) # The name of the trait that we are binding the extension point to. trait_name = Str #### Private interface #################################################### # A flag that prevents us from setting a trait twice. _event_handled = False ########################################################################### # 'object' interface. ########################################################################### def __init__(self, **traits): """ Constructor. """ super(ExtensionPointBinding, self).__init__(**traits) # Initialize the object's trait from the extension point. self._set_trait(notify=False) # Wire-up the trait change and extension point handlers. self._initialize() # We keep a reference to each binding alive until its associated # object is garbage collected. bindings = ExtensionPointBinding._bindings.setdefault(self.obj, []) bindings.append(self) return ########################################################################### # 'ExtensionPointBinding' interface. ########################################################################### #### Trait initializers ################################################### def _extension_registry_default(self): """ Trait initializer. """ # fixme: Sneaky global!!!!! from extension_point import ExtensionPoint return ExtensionPoint.extension_registry ########################################################################### # Private interface. ########################################################################### #### Trait change handlers ################################################ def _on_trait_changed(self, obj, trait_name, old, new): """ Dynamic trait change handler. """ if not self._event_handled: self._set_extensions(new) return def _on_trait_items_changed(self, obj, trait_name, old, event): """ Dynamic trait change handler. """ if not self._event_handled: self._set_extensions(getattr(obj, self.trait_name)) return #### Other observer pattern listeners ##################################### def _extension_point_listener(self, extension_registry, event): """ Listener called when an extension point is changed. """ self._event_handled = True if event.index is not None: self._update_trait(event) else: self._set_trait(notify=True) self._event_handled = False return #### Methods ############################################################## def _initialize(self): """ Wire-up trait change handlers etc. """ # Listen for the object's trait being changed. self.obj.on_trait_change( self._on_trait_changed, self.trait_name ) self.obj.on_trait_change( self._on_trait_items_changed, self.trait_name + '_items' ) # Listen for the extension point being changed. self.extension_registry.add_extension_point_listener( self._extension_point_listener, self.extension_point_id ) return def _set_trait(self, notify): """ Set the object's trait to the value of the extension point. """ value = self.extension_registry.get_extensions(self.extension_point_id) traits = {self.trait_name : value} self.obj.set(trait_change_notify=notify, **traits) return def _update_trait(self, event): """ Update the object's trait to the value of the extension point. """ self._set_trait(notify=False) self.obj.trait_property_changed( self.trait_name + '_items', Undefined, event ) return def _set_extensions(self, extensions): """ Set the extensions to an extension point. """ self.extension_registry.set_extensions( self.extension_point_id, extensions ) return # Factory function for creating bindings. def bind_extension_point( obj, trait_name, extension_point_id, extension_registry=None ): """ Create a binding to an extension point. """ # 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 'ExtensionPointBinding' # instance is created. # # This is because we only want to set the 'extension_registry' 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 appropriate registry. # Also, if we try to set the 'extension_registry' 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, 'extension_point_id' : extension_point_id } if extension_registry is not None: traits['extension_registry'] = extension_registry return ExtensionPointBinding(**traits) #### EOF ###################################################################### envisage-4.4.0/envisage/i_import_manager.py0000644000175000017500000000244212252661404021521 0ustar davidcdavidc00000000000000""" The interface for import managers. """ # Enthought library imports. from traits.api import Interface class IImportManager(Interface): """ The interface for import managers. """ def import_symbol(self, symbol_path): """ Import the symbol defined by the specified symbol path. 'symbol_path' is a string containing the path to a symbol through the Python package namespace. It can be in one of two forms: 1) 'foo.bar.baz' Which is turned into the equivalent of an import statement that looks like:: from foo.bar import baz With the value of 'baz' being returned. 2) 'foo.bar:baz' (i.e. a ':' separating the module from the symbol) Which is turned into the equivalent of:: from foo import bar eval('baz', bar.__dict__) With the result of the 'eval' being returned. The second form is recommended as it allows for nested symbols to be retreived, e.g. the symbol path 'foo.bar:baz.bling' becomes:: from foo import bar eval('baz.bling', bar.__dict__) The first form is retained for backwards compatability. """ #### EOF ###################################################################### envisage-4.4.0/envisage/extension_registry.py0000644000175000017500000001342512252661404022154 0ustar davidcdavidc00000000000000""" A base class for extension registry implementation. """ # Standard library imports. import logging # Enthought library imports. from traits.api import Dict, HasTraits, implements # Local imports. from extension_point_changed_event import ExtensionPointChangedEvent from i_extension_registry import IExtensionRegistry import safeweakref from unknown_extension_point import UnknownExtensionPoint # Logging. logger = logging.getLogger(__name__) class ExtensionRegistry(HasTraits): """ A base class for extension registry implementation. """ implements(IExtensionRegistry) ########################################################################### # Protected 'ExtensionRegistry' interface. ########################################################################### # A dictionary of extensions, keyed by extension point. _extensions = Dict # The extension points that have been added *explicitly*. _extension_points = Dict # Extension listeners. # # These are called when extensions are added to or removed from an # extension point. # # e.g. Dict(extension_point, [weakref.ref(callable)]) # # A listener is any Python callable with the following signature:- # # def listener(extension_registry, extension_point_changed_event): # ... _listeners = Dict ########################################################################### # 'IExtensionRegistry' interface. ########################################################################### def add_extension_point_listener(self, listener, extension_point_id=None): """ Add a listener for extensions being added or removed. """ listeners = self._listeners.setdefault(extension_point_id, []) listeners.append(safeweakref.ref(listener)) return def add_extension_point(self, extension_point): """ Add an extension point. """ self._extension_points[extension_point.id] = extension_point logger.debug('extension point <%s> added', extension_point.id) return def get_extensions(self, extension_point_id): """ Return the extensions contributed to an extension point. """ return self._get_extensions(extension_point_id)[:] def get_extension_point(self, extension_point_id): """ Return the extension point with the specified Id. """ return self._extension_points.get(extension_point_id) def get_extension_points(self): """ Return all extension points. """ return self._extension_points.values() def remove_extension_point_listener(self,listener,extension_point_id=None): """ Remove a listener for extensions being added or removed. """ listeners = self._listeners.setdefault(extension_point_id, []) listeners.remove(safeweakref.ref(listener)) return def remove_extension_point(self, extension_point_id): """ Remove an extension point. """ self._check_extension_point(extension_point_id) # Remove the extension point. del self._extension_points[extension_point_id] # Remove any extensions to the extension point. if extension_point_id in self._extensions: old = self._extensions[extension_point_id] del self._extensions[extension_point_id] else: old = [] refs = self._get_listener_refs(extension_point_id) self._call_listeners(refs, extension_point_id, [], old, 0) logger.debug('extension point <%s> removed', extension_point_id) return def set_extensions(self, extension_point_id, extensions): """ Set the extensions contributed to an extension point. """ self._check_extension_point(extension_point_id) old = self._get_extensions(extension_point_id) self._extensions[extension_point_id] = extensions refs = self._get_listener_refs(extension_point_id) self._call_listeners(refs, extension_point_id, extensions, old, None) return ########################################################################### # Protected 'ExtensionRegistry' interface. ########################################################################### def _call_listeners(self, refs, extension_point_id, added, removed, index): """ Call listeners that are listening to an extension point. """ event = ExtensionPointChangedEvent( extension_point_id = extension_point_id, added = added, removed = removed, index = index ) for ref in refs: listener = ref() if listener is not None: listener(self, event) return def _check_extension_point(self, extension_point_id): """ Check to see if the extension point exists. Raise an 'UnknownExtensionPoint' if it does not. """ if not extension_point_id in self._extension_points: raise UnknownExtensionPoint(extension_point_id) return def _get_extensions(self, extension_point_id): """ Return the extensions for the given extension point. """ return self._extensions.setdefault(extension_point_id, []) def _get_listener_refs(self, extension_point_id): """ Get weak references to all listeners to an extension point. Returns a list containing the weak references to those listeners that are listening to this extension point specifically first, followed by those that are listening to any extension point. """ refs = [] refs.extend(self._listeners.get(extension_point_id, [])) refs.extend(self._listeners.get(None, [])) return refs #### EOF ###################################################################### envisage-4.4.0/envisage/application_event.py0000644000175000017500000000054412252661404021712 0ustar davidcdavidc00000000000000""" An application event. """ # Enthought library imports. from traits.api import Instance, Vetoable class ApplicationEvent(Vetoable): """ An application event. """ # The application that the event is for. application = Instance('envisage.api.IApplication') #### EOF ###################################################################### envisage-4.4.0/envisage/i_service_user.py0000644000175000017500000000077612252661404021223 0ustar davidcdavidc00000000000000""" The interface for objects using the 'Service' trait type. """ # Enthought library imports. from traits.api import Instance, Interface # Local imports. from i_service_registry import IServiceRegistry class IServiceUser(Interface): """ The interface for objects using the 'Service' trait type. """ # The service registry that the object's services are stored in. service_registry = Instance(IServiceRegistry) #### EOF ###################################################################### envisage-4.4.0/envisage.egg-info/0000755000175000017500000000000012252662115017323 5ustar davidcdavidc00000000000000envisage-4.4.0/envisage.egg-info/not-zip-safe0000644000175000017500000000000112252662111021545 0ustar davidcdavidc00000000000000 envisage-4.4.0/envisage.egg-info/top_level.txt0000644000175000017500000000001112252662115022045 0ustar davidcdavidc00000000000000envisage envisage-4.4.0/envisage.egg-info/requires.txt0000644000175000017500000000001712252662115021721 0ustar davidcdavidc00000000000000apptools traitsenvisage-4.4.0/envisage.egg-info/entry_points.txt0000644000175000017500000000013412252662115022617 0ustar davidcdavidc00000000000000 [envisage.plugins] envisage.core = envisage.core_plugin:CorePlugin envisage-4.4.0/envisage.egg-info/PKG-INFO0000644000175000017500000000523712252662115020427 0ustar davidcdavidc00000000000000Metadata-Version: 1.1 Name: envisage Version: 4.4.0 Summary: extensible application framework Home-page: http://code.enthought.com/projects/envisage/ Author: ETS Developers Author-email: enthought-dev@enthought.com License: BSD Download-URL: http://www.enthought.com/repo/ets/envisage-4.4.0.tar.gz Description: ========================================== envisage: extensible application framework ========================================== .. image:: https://travis-ci.org/enthought/envisage.png :alt: Build Status :target: https://travis-ci.org/enthought/envisage http://github.enthought.com/envisage Envisage is a Python-based framework for building extensible applications, that is, applications whose functionality can be extended by adding "plug-ins". Envisage provides a standard mechanism for features to be added to an application, whether by the original developer or by someone else. In fact, when you build an application using Envisage, the entire application consists primarily of plug-ins. In this respect, it is similar to the Eclipse and Netbeans frameworks for Java applications. Each plug-in is able to: - Advertise where and how it can be extended (its "extension points"). - Contribute extensions to the extension points offered by other plug-ins. - Create and share the objects that perform the real work of the application ("services"). The Envisage project provides the basic machinery of the Envisage framework. This project contains no plug-ins. You are free to use: - plug-ins from the EnvisagePlugins project - plug-ins from other ETS projects that expose their functionality as plug-ins - plug-ins that you create yourself Prerequisites ------------- * `traits `_ Platform: Windows Platform: Linux Platform: Mac OS-X Platform: Unix Platform: Solaris Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: Intended Audience :: Science/Research Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: MacOS Classifier: Operating System :: Microsoft :: Windows Classifier: Operating System :: OS Independent Classifier: Operating System :: POSIX Classifier: Operating System :: Unix Classifier: Programming Language :: Python Classifier: Topic :: Scientific/Engineering Classifier: Topic :: Software Development Classifier: Topic :: Software Development :: Libraries envisage-4.4.0/envisage.egg-info/SOURCES.txt0000644000175000017500000003051712252662115021215 0ustar davidcdavidc00000000000000README.rst setup.py envisage/__init__.py envisage/api.py envisage/application.py envisage/application_event.py envisage/category.py envisage/class_load_hook.py envisage/composite_plugin_manager.py envisage/core_plugin.py envisage/egg_basket_plugin_manager.py envisage/egg_plugin_manager.py envisage/egg_utils.py envisage/extension_point.py envisage/extension_point_binding.py envisage/extension_point_changed_event.py envisage/extension_provider.py envisage/extension_registry.py envisage/i_application.py envisage/i_extension_point.py envisage/i_extension_point_user.py envisage/i_extension_provider.py envisage/i_extension_registry.py envisage/i_import_manager.py envisage/i_plugin.py envisage/i_plugin_activator.py envisage/i_plugin_manager.py envisage/i_provider_extension_registry.py envisage/i_service_registry.py envisage/i_service_user.py envisage/import_manager.py envisage/package_plugin_manager.py envisage/plugin.py envisage/plugin_activator.py envisage/plugin_event.py envisage/plugin_extension_registry.py envisage/plugin_manager.py envisage/provider_extension_registry.py envisage/safeweakref.py envisage/service.py envisage/service_offer.py envisage/service_registry.py envisage/twisted_application.py envisage/unknown_extension.py envisage/unknown_extension_point.py envisage.egg-info/PKG-INFO envisage.egg-info/SOURCES.txt envisage.egg-info/dependency_links.txt envisage.egg-info/entry_points.txt envisage.egg-info/not-zip-safe envisage.egg-info/requires.txt envisage.egg-info/top_level.txt envisage/developer/__init__.py envisage/developer/developer_plugin.py envisage/developer/charm/__init__.py envisage/developer/charm/api.py envisage/developer/charm/charm.py envisage/developer/charm/run.py envisage/developer/code_browser/__init__.py envisage/developer/code_browser/api.py envisage/developer/code_browser/assign.py envisage/developer/code_browser/code_browser.py envisage/developer/code_browser/enclbr.py envisage/developer/code_browser/example.py envisage/developer/code_browser/function.py envisage/developer/code_browser/klass.py envisage/developer/code_browser/module.py envisage/developer/code_browser/namespace.py envisage/developer/code_browser/package.py envisage/developer/ui/__init__.py envisage/developer/ui/api.py envisage/developer/ui/developer_ui_plugin.py envisage/developer/ui/perspective/__init__.py envisage/developer/ui/perspective/api.py envisage/developer/ui/perspective/developer_perspective.py envisage/developer/ui/view/__init__.py envisage/developer/ui/view/api.py envisage/developer/ui/view/application_browser.py envisage/developer/ui/view/application_browser_tree.py envisage/developer/ui/view/application_browser_view.py envisage/developer/ui/view/extension_registry_browser.py envisage/developer/ui/view/extension_registry_browser_tree.py envisage/developer/ui/view/extension_registry_browser_view.py envisage/developer/ui/view/plugin_browser.py envisage/developer/ui/view/service_registry_browser.py envisage/developer/ui/view/service_registry_browser_tree.py envisage/developer/ui/view/service_registry_browser_view.py envisage/plugins/__init__.py envisage/plugins/debug/__init__.py envisage/plugins/debug/fbi_plugin.py envisage/plugins/debug/fbi_plugin_definition.py envisage/plugins/event_manager/__init__.py envisage/plugins/event_manager/plugin.py envisage/plugins/ipython_shell/__init__.py envisage/plugins/ipython_shell/api.py envisage/plugins/ipython_shell/i_namespace_view.py envisage/plugins/ipython_shell/ipython_shell_plugin.py envisage/plugins/ipython_shell/actions/__init__.py envisage/plugins/ipython_shell/actions/ipython_shell_actions.py envisage/plugins/ipython_shell/view/__init__.py envisage/plugins/ipython_shell/view/api.py envisage/plugins/ipython_shell/view/ipython_shell_view.py envisage/plugins/ipython_shell/view/namespace_view.py envisage/plugins/python_shell/__init__.py envisage/plugins/python_shell/api.py envisage/plugins/python_shell/i_python_shell.py envisage/plugins/python_shell/python_shell_plugin.py envisage/plugins/python_shell/view/__init__.py envisage/plugins/python_shell/view/api.py envisage/plugins/python_shell/view/namespace_view.py envisage/plugins/python_shell/view/python_shell_view.py envisage/plugins/refresh_code/__init__.py envisage/plugins/refresh_code/actions.py envisage/plugins/refresh_code/refresh_code_action_set.py envisage/plugins/refresh_code/refresh_code_plugin.py envisage/plugins/refresh_code/refresh_code_plugin_definition.py envisage/plugins/remote_editor/__init__.py envisage/plugins/remote_editor/actions.py envisage/plugins/remote_editor/api.py envisage/plugins/remote_editor/enshell_client.py envisage/plugins/remote_editor/envisage_remote_editor.py envisage/plugins/remote_editor/i_remote_editor.py envisage/plugins/remote_editor/i_remote_shell.py envisage/plugins/remote_editor/preferences.ini envisage/plugins/remote_editor/remote_editor_controller.py envisage/plugins/remote_editor/remote_editor_plugin.py envisage/plugins/remote_editor/remote_shell_controller.py envisage/plugins/remote_editor/communication/__init__.py envisage/plugins/remote_editor/communication/client.py envisage/plugins/remote_editor/communication/server.py envisage/plugins/remote_editor/communication/util.py envisage/plugins/remote_editor/communication/tests/__init__.py envisage/plugins/remote_editor/communication/tests/test_communication.py envisage/plugins/remote_editor/editor_plugins/__init__.py envisage/plugins/remote_editor/editor_plugins/editor_plugin.py envisage/plugins/remote_editor/editor_plugins/editra/__init__.py envisage/plugins/remote_editor/editor_plugins/editra/editra_plugin.py envisage/plugins/remote_editor/editor_plugins/editra/start_editra.py envisage/plugins/remote_editor/editor_plugins/editra/images/image_LICENSE.txt envisage/plugins/remote_editor/editor_plugins/editra/images/python_run_16x16.png envisage/plugins/remote_editor/editor_plugins/editra/images/python_run_24x24.png envisage/plugins/remote_editor/editor_plugins/editra/images/python_run_32x32.png envisage/plugins/remote_editor/editor_plugins/editra/images/python_run_48x48.png envisage/plugins/remote_editor/editor_plugins/editra/images/python_runsel_16x16.png envisage/plugins/remote_editor/editor_plugins/editra/images/python_runsel_24x24.png envisage/plugins/remote_editor/editor_plugins/editra/images/python_runsel_32x32.png envisage/plugins/remote_editor/editor_plugins/editra/images/python_runsel_48x48.png envisage/plugins/tasks/__init__.py envisage/plugins/tasks/python_shell_plugin.py envisage/plugins/text_editor/__init__.py envisage/plugins/text_editor/actions.py envisage/plugins/text_editor/api.py envisage/plugins/text_editor/text_editor_action_set.py envisage/plugins/text_editor/text_editor_plugin.py envisage/plugins/text_editor/editor/__init__.py envisage/plugins/text_editor/editor/text_editor.py envisage/plugins/text_editor/editor/text_editor_handler.py envisage/plugins/update_checker/__init__.py envisage/plugins/update_checker/tools.py envisage/plugins/update_checker/update_checker_dialog.py envisage/plugins/update_checker/update_checker_plugin.py envisage/plugins/update_checker/update_info.py envisage/plugins/update_checker/update_info_view.py envisage/resource/__init__.py envisage/resource/api.py envisage/resource/file_resource_protocol.py envisage/resource/http_resource_protocol.py envisage/resource/i_resource_manager.py envisage/resource/i_resource_protocol.py envisage/resource/no_such_resource_error.py envisage/resource/package_resource_protocol.py envisage/resource/resource_manager.py envisage/resource/tests/__init__.py envisage/resource/tests/resource_manager_test_case.py envisage/tests/__init__.py envisage/tests/application_test_case.py envisage/tests/bar_category.py envisage/tests/class_load_hook_test_case.py envisage/tests/composite_plugin_manager_test_case.py envisage/tests/core_plugin_test_case.py envisage/tests/egg_based_test_case.py envisage/tests/egg_basket_plugin_manager_test_case.py envisage/tests/egg_plugin_manager_test_case.py envisage/tests/event_tracker.py envisage/tests/extension_point_binding_test_case.py envisage/tests/extension_point_changed_test_case.py envisage/tests/extension_point_test_case.py envisage/tests/extension_registry_test_case.py envisage/tests/foo.py envisage/tests/i_foo.py envisage/tests/import_manager_test_case.py envisage/tests/mutable_extension_registry.py envisage/tests/package_plugin_manager_test_case.py envisage/tests/plugin_manager_test_case.py envisage/tests/plugin_test_case.py envisage/tests/preferences.ini envisage/tests/provider_extension_registry_test_case.py envisage/tests/safeweakref_test_case.py envisage/tests/service_registry_test_case.py envisage/tests/service_test_case.py envisage/tests/slice_test_case.py envisage/ui/__init__.py envisage/ui/action/__init__.py envisage/ui/action/abstract_action_manager_builder.py envisage/ui/action/action.py envisage/ui/action/action_set.py envisage/ui/action/action_set_manager.py envisage/ui/action/api.py envisage/ui/action/group.py envisage/ui/action/i_action_manager_builder.py envisage/ui/action/i_action_set.py envisage/ui/action/location.py envisage/ui/action/menu.py envisage/ui/action/tool_bar.py envisage/ui/single_project/__init__.py envisage/ui/single_project/api.py envisage/ui/single_project/default_path_preference_page.py envisage/ui/single_project/factory_definition.py envisage/ui/single_project/model_service.py envisage/ui/single_project/preferences.ini envisage/ui/single_project/project.py envisage/ui/single_project/project_action.py envisage/ui/single_project/project_action_set.py envisage/ui/single_project/project_factory.py envisage/ui/single_project/project_plugin.py envisage/ui/single_project/project_runnable.py envisage/ui/single_project/services.py envisage/ui/single_project/ui_service.py envisage/ui/single_project/ui_service_factory.py envisage/ui/single_project/action/__init__.py envisage/ui/single_project/action/api.py envisage/ui/single_project/action/close_project_action.py envisage/ui/single_project/action/configure_action.py envisage/ui/single_project/action/new_project_action.py envisage/ui/single_project/action/open_project_action.py envisage/ui/single_project/action/rename_action.py envisage/ui/single_project/action/save_as_project_action.py envisage/ui/single_project/action/save_project_action.py envisage/ui/single_project/action/switch_to_action.py envisage/ui/single_project/action/images/close_project.png envisage/ui/single_project/action/images/new_project.png envisage/ui/single_project/action/images/open_project.png envisage/ui/single_project/action/images/save_as_project.png envisage/ui/single_project/action/images/save_project.png envisage/ui/single_project/action/images/switch_project.png envisage/ui/single_project/editor/__init__.py envisage/ui/single_project/editor/project_editor.py envisage/ui/single_project/view/__init__.py envisage/ui/single_project/view/project_view.py envisage/ui/tasks/__init__.py envisage/ui/tasks/api.py envisage/ui/tasks/preferences_category.py envisage/ui/tasks/preferences_dialog.py envisage/ui/tasks/preferences_pane.py envisage/ui/tasks/task_extension.py envisage/ui/tasks/task_factory.py envisage/ui/tasks/task_window.py envisage/ui/tasks/task_window_event.py envisage/ui/tasks/tasks_application.py envisage/ui/tasks/tasks_plugin.py envisage/ui/tasks/action/__init__.py envisage/ui/tasks/action/api.py envisage/ui/tasks/action/exit_action.py envisage/ui/tasks/action/preferences_action.py envisage/ui/tasks/action/task_window_launch_group.py envisage/ui/tasks/action/task_window_toggle_group.py envisage/ui/workbench/__init__.py envisage/ui/workbench/api.py envisage/ui/workbench/default_action_set.py envisage/ui/workbench/preferences.ini envisage/ui/workbench/workbench.py envisage/ui/workbench/workbench_action_manager_builder.py envisage/ui/workbench/workbench_action_set.py envisage/ui/workbench/workbench_application.py envisage/ui/workbench/workbench_editor_manager.py envisage/ui/workbench/workbench_plugin.py envisage/ui/workbench/workbench_preferences.py envisage/ui/workbench/workbench_preferences_page.py envisage/ui/workbench/workbench_window.py envisage/ui/workbench/action/__init__.py envisage/ui/workbench/action/about_action.py envisage/ui/workbench/action/api.py envisage/ui/workbench/action/edit_preferences_action.py envisage/ui/workbench/action/exit_action.py envisage/ui/workbench/action/images/exit.png envisage/ui/workbench/action/images/image_LICENSE.txt envisage/ui/workbench/action/images/preferences.png envisage/ui/workbench/images/about.png envisage/ui/workbench/images/application.ico envisage/ui/workbench/images/image_LICENSE.txtenvisage-4.4.0/envisage.egg-info/dependency_links.txt0000644000175000017500000000000112252662115023371 0ustar davidcdavidc00000000000000 envisage-4.4.0/PKG-INFO0000644000175000017500000000523712252662115015134 0ustar davidcdavidc00000000000000Metadata-Version: 1.1 Name: envisage Version: 4.4.0 Summary: extensible application framework Home-page: http://code.enthought.com/projects/envisage/ Author: ETS Developers Author-email: enthought-dev@enthought.com License: BSD Download-URL: http://www.enthought.com/repo/ets/envisage-4.4.0.tar.gz Description: ========================================== envisage: extensible application framework ========================================== .. image:: https://travis-ci.org/enthought/envisage.png :alt: Build Status :target: https://travis-ci.org/enthought/envisage http://github.enthought.com/envisage Envisage is a Python-based framework for building extensible applications, that is, applications whose functionality can be extended by adding "plug-ins". Envisage provides a standard mechanism for features to be added to an application, whether by the original developer or by someone else. In fact, when you build an application using Envisage, the entire application consists primarily of plug-ins. In this respect, it is similar to the Eclipse and Netbeans frameworks for Java applications. Each plug-in is able to: - Advertise where and how it can be extended (its "extension points"). - Contribute extensions to the extension points offered by other plug-ins. - Create and share the objects that perform the real work of the application ("services"). The Envisage project provides the basic machinery of the Envisage framework. This project contains no plug-ins. You are free to use: - plug-ins from the EnvisagePlugins project - plug-ins from other ETS projects that expose their functionality as plug-ins - plug-ins that you create yourself Prerequisites ------------- * `traits `_ Platform: Windows Platform: Linux Platform: Mac OS-X Platform: Unix Platform: Solaris Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: Intended Audience :: Science/Research Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: MacOS Classifier: Operating System :: Microsoft :: Windows Classifier: Operating System :: OS Independent Classifier: Operating System :: POSIX Classifier: Operating System :: Unix Classifier: Programming Language :: Python Classifier: Topic :: Scientific/Engineering Classifier: Topic :: Software Development Classifier: Topic :: Software Development :: Libraries envisage-4.4.0/setup.py0000644000175000017500000000330712252661404015545 0ustar davidcdavidc00000000000000# Copyright (c) 2008-2013 by Enthought, Inc. # All rights reserved. from os.path import join from setuptools import setup, find_packages info = {} execfile(join('envisage', '__init__.py'), info) setup( name = 'envisage', version = info['__version__'], author = "Martin Chilvers, et. al.", author_email = "info@enthought.com", maintainer = 'ETS Developers', maintainer_email = 'enthought-dev@enthought.com', url = "http://code.enthought.com/projects/envisage/", download_url = ('http://www.enthought.com/repo/ets/envisage-%s.tar.gz' % info['__version__']), classifiers = [c.strip() for c in """\ Development Status :: 5 - Production/Stable Intended Audience :: Developers Intended Audience :: Science/Research License :: OSI Approved :: BSD License Operating System :: MacOS Operating System :: Microsoft :: Windows Operating System :: OS Independent Operating System :: POSIX Operating System :: Unix Programming Language :: Python Topic :: Scientific/Engineering Topic :: Software Development Topic :: Software Development :: Libraries """.splitlines() if len(c.strip()) > 0], description = 'extensible application framework', long_description = open('README.rst').read(), entry_points = """ [envisage.plugins] envisage.core = envisage.core_plugin:CorePlugin """, ext_modules = [], install_requires = info['__requires__'], license = "BSD", packages = find_packages(), package_data = {'': ['images/*', '*.ini',]}, platforms = ["Windows", "Linux", "Mac OS-X", "Unix", "Solaris"], zip_safe = False, ) envisage-4.4.0/README.rst0000644000175000017500000000263312252661404015523 0ustar davidcdavidc00000000000000========================================== envisage: extensible application framework ========================================== .. image:: https://travis-ci.org/enthought/envisage.png :alt: Build Status :target: https://travis-ci.org/enthought/envisage http://github.enthought.com/envisage Envisage is a Python-based framework for building extensible applications, that is, applications whose functionality can be extended by adding "plug-ins". Envisage provides a standard mechanism for features to be added to an application, whether by the original developer or by someone else. In fact, when you build an application using Envisage, the entire application consists primarily of plug-ins. In this respect, it is similar to the Eclipse and Netbeans frameworks for Java applications. Each plug-in is able to: - Advertise where and how it can be extended (its "extension points"). - Contribute extensions to the extension points offered by other plug-ins. - Create and share the objects that perform the real work of the application ("services"). The Envisage project provides the basic machinery of the Envisage framework. This project contains no plug-ins. You are free to use: - plug-ins from the EnvisagePlugins project - plug-ins from other ETS projects that expose their functionality as plug-ins - plug-ins that you create yourself Prerequisites ------------- * `traits `_