pax_global_header00006660000000000000000000000064140626542100014512gustar00rootroot0000000000000052 comment=92ae3280d782b34ad96ba16ef1089281d5687223 envisage-6.0.1/000077500000000000000000000000001406265421000133175ustar00rootroot00000000000000envisage-6.0.1/.coveragerc000066400000000000000000000006721406265421000154450ustar00rootroot00000000000000[run] branch = True source = envisage omit = */tests/* [report] # Regexes for lines to exclude from consideration exclude_lines = # Have to re-enable the standard pragma pragma: no cover # Don't complain about missing debug-only code: def __repr__ # Don't complain if tests don't hit defensive assertion code: raise AssertionError raise NotImplementedError if __name__ == .__main__.: ignore_errors = True envisage-6.0.1/.gitattributes000066400000000000000000000001421406265421000162070ustar00rootroot00000000000000# For source archives, store the commit hash for HEAD in the setup.py file /setup.py export-subst envisage-6.0.1/.github/000077500000000000000000000000001406265421000146575ustar00rootroot00000000000000envisage-6.0.1/.github/ISSUE_TEMPLATE/000077500000000000000000000000001406265421000170425ustar00rootroot00000000000000envisage-6.0.1/.github/ISSUE_TEMPLATE/release_template.md000066400000000000000000000072001406265421000226760ustar00rootroot00000000000000--- name: Release Checklist about: Use this template for upcoming releases. title: "[Release Number] Release Checklist" --- Target date: Release Candidate by YYYY-MM-DD Release by YYYY-MM-DD Release candidate ------------------- - [ ] Verify that all relevant PRs have been merged to master. - [ ] Create a PR against master to bump version number, merge that PR - [ ] From the commit just before bumping the version, create a new branch `maint/` - [ ] Update changelog and open PR targeting a new `maint/` branch - [ ] Update `ci-src-requirements.txt` if needed - [ ] Check MANIFEST and requirements are still up to date. - [ ] Update version in setup.py for the prerelease, open 2 PRs against `maint/` - [ ] Create a new branch from `maint/`: `git checkout maint/`; `git pull`; `git checkout -b call-it-anything-you-like` - [ ] Set `PRERELEASE` to "rc1" and `IS_RELEASED` to true, commit, e.g. `git commit -m "Set IS_RELEASED to true for prerelease rc1"` - [ ] Open and merge a PR against `maint/` - [ ] Create a new branch from `maint/`, Flip `IS_RELEASED` back to false, commit. - [ ] Open and merge another PR against `maint/` - [ ] Tag (annotated!) the release candidate on the commit where IS_RELEASED is set to true, e.g. `git tag -a -m "Release candidate rc1" rc1 ` - [ ] Push the tag to GitHub - [ ] Upload to PyPI - `git checkout `, `git clean -ffxd`, `python setup.py sdist`, `twine check dist/<...>.tar.gz`, `twine upload dist/<...>.tar.gz` - [ ] Announcement for the release candidate Release blockers ---------------- - [ ] (add blocking issues/PRs here) Pre-release --- - [ ] Backport PRs that have been merged to master to the maintenance branch. Use the "need backport ..." tag if there is one (but don't rely 100% on it) - [ ] Verify that no other open issue needs to be addressed before the release. - [ ] Test against other ETS packages and other ETS-using projects - [ ] Check MANIFEST, requirements, changelog are still up to date. - [ ] Test building the documentation Release ------- - [ ] Create branch `release/` from `maint/` branch. - [ ] Set release to ``, and set `IS_RELEASED` is true; commit - [ ] Install from source distribution and run tests again - [ ] Open a PR against `maint/` with this being the last commit so that CI is built on the release commit - [ ] Once CI is done building merge PR - [ ] Bump the micro version number i.e. `` and set `IS_RELEASED` to false; commit. - [ ] Open and then merge a separate PR against `maint/` - [ ] From the commit at which `IS_RELEASED` is true and version is ``, tag (annotated!) `git tag -a -m "Release " ` - [ ] Push the tag `git push origin ` - [ ] Make PR targeting `gh-pages` branch: Generate documentation and copy the content to the branch. Verify that the resulting index.html looks good. - [ ] Upload to PyPI - [ ] Test the PyPI package Post-release ------------- - [ ] Package update for `enthought/free` repository (for EDM) - [ ] Backport release note and change log to master, and possibly `maint/` branch. - [ ] Update GitHub Release pages https://github.com/enthought/envisage/releases - [ ] Announcement (e.g. Google Group) envisage-6.0.1/.gitignore000066400000000000000000000003471406265421000153130ustar00rootroot00000000000000# file types to ignore *.pyc *.pyd *.so *~ .DS_Store # ignore the build directories *.egg-info/ build/ dist/ /docs/build/ /docs/source/api/*.rst !/docs/source/api/envisage.api.rst # Auto-generated by setup.py envisage/version.py envisage-6.0.1/.travis.yml000066400000000000000000000031301406265421000154250ustar00rootroot00000000000000language: generic dist: xenial services: - xvfb addons: apt: packages: - libxkbcommon-x11-0 - libxcb-icccm4 - libxcb-image0 - libxcb-keysyms1 - libxcb-randr0 - libxcb-render-util0 - libxcb-xinerama0 # Wx dependencies - libsdl2-2.0-0 env: global: - INSTALL_EDM_VERSION="3.0.1" PYTHONUNBUFFERED="1" QT_DEBUG_PLUGINS="1" matrix: include: - env: RUNTIME=3.6 TOOLKITS="null pyqt5 pyside2 wx" cache: directories: - "~/.cache" before_install: - mkdir -p "${HOME}/.cache/download" - if [[ ${TRAVIS_OS_NAME} == 'linux' ]]; then ./install-edm-linux.sh; export PATH="${HOME}/edm/bin:${PATH}"; fi - if [[ ${TRAVIS_OS_NAME} == 'osx' ]]; then ./install-edm-osx.sh; export PATH="${PATH}:/usr/local/bin"; fi - edm install -y wheel click coverage install: - for toolkit in ${TOOLKITS}; do if [[ ${TRAVIS_EVENT_TYPE} == "cron" ]] ; then edm run -- python etstool.py install --runtime=${RUNTIME} --toolkit=${toolkit} --source || exit; else edm run -- python etstool.py install --runtime=${RUNTIME} --toolkit=${toolkit} || exit; fi; done script: - edm run -- python etstool.py flake8 --runtime=${RUNTIME} --toolkit=null - for toolkit in ${TOOLKITS}; do edm run -- python etstool.py test --runtime=${RUNTIME} --toolkit=${toolkit} || exit; done notifications: slack: secure: XOXbiCa2wjxwYpRPPfib863y1jE2PiDBtsCZQUDPh3IrJhHAAGEPgcICbMYPkgKz3+6rqVvfwwj1Xictw4wI8dtvcqROz5iZek/14V3tDhPAp0iHBEvOibSFZP7VK6tZgVp7uTSc1p4bJd2GEX0yKMyaOdXCYQf42y6YYV97xNA= on_success: change on_failure: always envisage-6.0.1/CHANGES.rst000066400000000000000000000325631406265421000151320ustar00rootroot00000000000000==================== Envisage CHANGELOG ==================== Version 6.0.1 ============= Released: 2021-06-18 This bugfix release fixes the issue where Extension Point resolution was happening too eagerly, which caused issues during application startup time in certain cases. We recommend all users of Envisage to upgrade to this bugfix version. Fixes ----- - Revert PR #354, which caused the issue #417. (#422) Tests ----- - Ensure that the testsuite passes with minimal dependencies. (#423) - Add a regression test for issue #417. (#421) Version 6.0.0 ============= Released: 2021-05-14 This major release focuses on speeding up Envisage applications. We achieved this speedup by removing unused functionality in the package. Specifically, we removed the ``@contributes_to`` decorator and the code needed to handle methods decorated with the above decorator. Additionally, with this release, parts of envisage start using the new traits observation framework instead of the old traits ``on_trait_change``. So, Envisage now depends on Traits version >= 6.2. Features -------- - Support ``observe(name:items)`` for Extension Points. (#354) Changes ------- - Replace ``Either`` trait type with ``Union``. (#405) - Rewrite ``*_changed`` static trait handlers to use ``observe``. (#401) - Replace ``depends_on`` in ``Property`` traits with ``observe``. (#400) - Change default pickle protocol to be compatible with Python >= 3.4. (#390) Removals -------- - Remove ``contributes_to`` decorator and supporting code. (#402) - Remove unnecessary return statements throughout the codebase. (#393) Build ----- - Ensure that the cron job installs all necessary dependencies. (#383) Version 5.0.0 ============= Released: 2020-12-03 This is a major release mainly relating to code modernization. In this release, support for Python versions <3.6 have been dropped. The class_load_hooks and single_project modules have been removed. Additionally, there were various fixes to bugs, examples, tests, and documentation. Demo examples are also distributed as package data such that they are visible via the "etsdemo" GUI application (to be installed separately). Features -------- - Re-export CorePlugin in envisage.api (#332) - Create and fill plugin subpackage api modules (#323) - Add relevant classes to envisage.ui.tasks.api (#322) Fixes ----- - Fix index slice in ExtensionPointChangedEvent when plugin changes (#357) - Fix ValueError from unregistering services when application stops (#345) - Fix the MOTD example (#319) - Fix the Hello_World example (#318) - Fix the attractors tasks application example (#317) - Make TasksApplication.gui expect an IGUI interface, not a GUI instance (#301) Documentation ------------- - Contribute examples to etsdemo (#380) - Refactor documentation links to source on GitHub (#379) - Make example run from any directory (#377) - Setup intersphinx in docs (#343) - Add documentation for envisage APIs (#340) - Use jinja templates for API documentation (#339) - Improve API docs : document traits (#334) - Rebuild documentation, mostly to fix search functionality (#290) Deprecations ------------ - Deprecate safeweakref and replace its uses (#275) Removals -------- - Drop support for Python 3 versions older than Python 3.6. (#341) - Remove single_project (#331) - Remove class_load_hooks and ClassLoadHook (#321) Tests ----- - Add tests for ExtensionRegistry getters (#349) - Add tests to demonstrate behaviour when mutating extension point directly (#346) - Use mixin instead of having ProviderExtensionRegistryTestCase inherit from ExtensionRegistryTestCase (#335) - Switch on default warning flag for CI test command (#326) - Add test eggs for Python 3.9 and remove eggs for Python 2.7 (#289) Build ----- - Turn off macOS builds on Travis CI (#375) - Fix CI cron job setup to install apptools (#348) - Update setup.py to allow prerelease version (#344) - Add wx as being supported in etstool, add it back to CI, and test against wxPython v4.x (#336) - Update EDM version to 3.0.1 in Travis CI and Appveyor. (#297) - Stop reporting code coverage in CI (#288) - Fix CI setup on Linux, Windows (#287) - Remove support for PySide and PyQt4 from CI (#285) - Add Slack notification for Travis CI runs (#283) - Add flake8 check to etstool and CI (#268) Version 4.9.2 ============= Released: 2020-02-17 This is a bugfix release that fixes tests that assumed the existence of categories machinery (which is removed in Traits 6.0.0). Fixes ----- - Conditionally skip tests that fail against Traits 6.0.0 due to the removal of Categories. (#263) Version 4.9.1 ============= Released: 2020-02-13 This is a bugfix release aimed at ensuring compatibility with the upcoming Traits 6.0.0 release. Fixes ----- - Fix tests that fail against Traits 6.0.0 due to the removal of double nesting in list events. (#255) - Replace a comment mention of ``AdaptedTo`` with ``Supports``. (#253) - Remove dependence on ``clean_filename`` from Traits. (#252) - Replace a use of the deprecated ``DictStrAny`` trait with ``Dict(Str, Any)``. (#250) Version 4.9.0 ============= Released: 2019-11-19 This is a minor feature release with a small handful of fixes, and a single new feature to make the ``IPythonKernelPlugin`` easier to use for applications. Features -------- - Add an option to allow the ``InternalIPKernel`` to initialise its kernel at kernel creation time. At some point in the future, this will become the default behaviour. (#227) Fixes ----- - Replace a use of the deprecated ``adapts`` function with ``register_factory``. (#234) - In the ``IPKernelApp``, correctly restore the original state of ``IPython.utils.io.std*`` streams even if those streams didn't exist originally. (#232) - Remove duplicate copyright header from autogenerated version file. (#220) Tests ----- - Remove a ``print`` call from a unit test. (#240) - Add unit tests for the ``envisage.ui.single_project`` adapters. (#235) - Add unit tests to check that ``InternalIPKernel`` doesn't affect ``sys.path``. (#233) - Fix the test suite not to write to the user's ``~/.ipython`` directory. (#231) - Fix the test suite not to write to the user's ``~/.enthought`` directory. (#230) - Remove an unused import and a useless ``tearDown`` method in the ``IPythonKernel`` tests. (#223) - Fix ``DeprecationWarning``s from uses of long-deprecated ``TestCase`` methods. (#222) - Add test eggs for Python 3.8. (#214) Build ----- - Rename changelog extension from ``.txt`` to ``.rst``. (#238) - Update EDM version used in Travis CI and Appveyor. (#236) - Add ``mock`` to test dependencies on Python 2. (#229) - Fix status badges in ``README``. (#216) Version 4.8.0 ============= Released: 2019-09-13 The main focus of this feature release is the ``IPythonKernelPlugin``, which has been updated to work with the latest IPython-related packages from PyPI, and is now much more careful about releasing resources allocated. Also in this release, a number of outdated, incomplete or otherwise nonfunctional pieces of code were removed. Features -------- - Improved ``repr`` for ``ExtensionPoint`` objects. (#142) Changes ------- - Drop support for Python versions older than 2.7 and Python 3 versions older than Python 3.5. (#139) - The ``IPythonKernelPlugin`` now releases all allocated resources (threads, file descriptors, etc.) and undoes global state changes at plugin ``stop`` time. (#188) - Suppress the Ctrl-C message printed by the IPython kernel at start time. (#182) - Add license headers to all files, and make license header statements consistent. (#192) Fixes ----- - Use a fixed pickle protocol when saving task layout state, to avoid cross-Python-version difficulties. (#179) - Fix deprecation warnings from use of ``Logger.warn``. (#178) - Fix some Python 3 syntax errors in example scripts. (#171) Removals -------- - Remove the unsupported and incomplete ``UpdateCheckerPlugin``. (#199) - Remove the ``plugin.debug`` empty submodule. (#195) - Remove the old ``IPythonShell`` plugin, which was based on pre-IPython 1.0. (#173) - Remove the non-functional ``RefreshCodePlugin``. (#202) - Remove ``project_runnable``, which was never functional. (#169) - Remove outdated debugging fallback from the ``ExtensionPoint`` source. (#167) - Remove ``FBIPlugin``. (#166) - Remove the ``remote_editor`` plugins. (#137) Documentation ------------- - Add docstrings for tasks plugin extension points. (#181) - Fix incorrect documentation for ``always_use_default_layout``. (#177) - Spell "Pyface" correctly. (#176) - NumPyDoc style fixes. (#168) - Add API documentation, with corresponding build infrastructure. (#165) - Fix invalid syntax in Tetris example. (#158) - Use the Enthought Sphinx Theme for documentation. (#157) Tests ----- - Remove dependency on the ``nose`` package, and rename test modules. All tests can now be discovered and run using ``unittest``. (#200, #194) Build ----- - Revise version-handling mechanisms and other minor details in ``setup.py`` script. (#197, #190) - Remove unused and outdated ``tox.ini`` file. (#201) - Update ``etstool.py`` to work with a non-EDM bootstrap environment on Windows. (#203) - Test against other ETS packages from source, using Travis CI cron jobs. (#162) - Fix deprecated pieces in Travis CI configuration. (#160, #159) - Update EDM version used, and clean up and simplify Travis CI and Appveyor configurations. (#152) - Usability improvements to ``etstool.py``. (#145, #148) Version 4.7.2 ============= Released: 03 May 2019 Fixes ----- * Fix some broken imports and name errors in the ``envisage.developer`` package. (#130) * Add missing test data to support running tests on Python 3.7. (#136) * Fix reversed interpretation of the ``TasksApplication.always_use_default_layout`` when creating task windows. (#144) * In the ``InternalIPKernel`` plugin, restore original standard streams (``stdout``, ``stdin``, ``stderr``) at plugin stop time. (#146) * In the ``InternalIPKernel`` plugin, fix ``ResourceWarnings`` from unclosed pipes attached to qt consoles. (#147) Version 4.7.1 ============= Released : 31 January 2019 Changes ------- * Replace use of deprecated ``HasTraits.set`` method (#118) Fixes ----- * Fix IPython GUI kernel issue when used with ipykernel 4.7.0 (#123) * Fix infinite recursion issue when harvesting extension methods (#121) Version 4.7.0 ============= Changes ------- * Update CI setup and include ``ipykernel`` in devenv (#105, #111, #114) * Use ``--gui`` rather than ``--matplotlib`` when starting IPython kernel (#101) * Downgrade level of a logging message (#95) Fixes ----- * Fix old-style relative import (#109) * Fix attractors example (#103) * Stop the IOPubThread as part of IPython kernel shutdown (#100) * Fix Sphinx conf to be able to build docs again (#91) * Fix deprecated IPython import (#92) * Fix task layout serialization under Python 3 (#90) Version 4.6.0 ============= This is an incremental release, mainly consisting of bug fixes. The most significant change is the support for IPython >= 4 in the IPython plugin. Thanks to @corranwebster, @dpinte, @itziakos, @jonathanrocher, @kamalx, @rahulporuri, @robmcmullen, @sjagoe Enhancements ------------ * IPython kernel plugin now supports IPython >= 4 (#82) * Remove usage of deprecated IPython QtConsole API (#80) * Defer selection of toolkit and avoid creating GUI applications as side-effects as much as possible (#77, #76) Fixes ----- * Fixes for tests under Python 3.5 (#86) * Work around for issue with Traits in Python 3 (#78) * Replace uses of ‘file’ and ‘execfile’ (#75) * Fix MOTD_Using_Eggs example (#66) * Fix broken and outdated links in documentation (#72) * Fix link to docs from README (#70) * Fix degenerate case where window is created with no layout (#44) Version 4.5.1 ============= Enhancements ------------ * Add tox for testing package install (#67) Fixes ----- * Include missing test files in the package data (#67) * Include missing test cases for Python 3.4 (#67) Version 4.5.0 ============= New features ------------ * IPythonKernelPlugin for Tasks: run an IPython kernel within the envisage app and expose it as a service (#54). * Envisage now supports Python 3.4 (#61). Enhancements ------------ * Allow loading plugins from an egg basket even when some eggs are invalid (#40, #46). * Add a simple ``GUIApplication`` to bootstrap basic plugin-driven applications (#34). * Split the IPython kernel and IPython menu action into two separate plugins for flexibility (#57). Fixes ----- * Use new Traits interfaces and adaptation implementation (#37). * Envisage now configures the logger with a ``NullHandler`` to avoid spurios unconfigured logger warnings (#45). * Envisage no longer swallows exceptions in plugin startup (#50). * Various fixes to continuous integration configuration (#47, #60). Version 4.4.0 ============= The major component of this feature is to work with the new ``traits.adaptation`` mechanism in place of the deprecated ``traits.protocols``, maintaining compatibility with ``traits`` version 4.4.0. This release also adds a new method to retrieve a service that is required by the application and provides documentation and test updates. New features ------------ * Added a simple GUIApplication class (673c8f6) * Added a method to get a required service (94dfdea) Enhancements ------------ * Updated to use the new traits.adaptation functionality (34fa5e6) Fixes ----- * Updated links to point to github instead of svn codebase (87cdb87) * Fixed test cases and added to Travis-CI (6c11d9f) envisage-6.0.1/LICENSE.txt000066400000000000000000000031201406265421000151360ustar00rootroot00000000000000This software is OSI Certified Open Source Software. OSI Certified is a certification mark of the Open Source Initiative. Copyright (c) 2006, Enthought, Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Enthought, Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. envisage-6.0.1/MANIFEST.in000066400000000000000000000004201406265421000150510ustar00rootroot00000000000000include CHANGES.rst include LICENSE.txt include MANIFEST.in include README.rst include image_LICENSE.txt include image_LICENSE_CP.txt recursive-include envisage/tests/plugins *.py recursive-include envisage/tests/eggs *.egg recursive-include envisage/tests/bad_eggs *.egg envisage-6.0.1/README.rst000066400000000000000000000045541406265421000150160ustar00rootroot00000000000000========================================== envisage: extensible application framework ========================================== .. image:: https://travis-ci.org/enthought/envisage.svg?branch=master :alt: Build Status :target: https://travis-ci.org/enthought/envisage .. image:: https://codecov.io/github/enthought/envisage/coverage.svg?branch=master :alt: Code Coverage :target: https://codecov.io/github/enthought/envisage?branch=master Documentation: https://docs.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 or its own. - 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. You are free to use: - the envisage ``CorePlugin`` available through the `envisage.api `__ module - plug-ins from the envisage `plugins `__ module - plug-ins from other ETS projects that expose their functionality as plug-ins - plug-ins that you create yourself Prerequisites ------------- The supported versions of Python are Python >= 3.6. Envisage requires: * `apptools `_ * `traits `_ Envisage has the following optional dependencies: * `Ipykernel `_ * `Tornado `_ * `Pyface `_ * `TraitsUI `_ To build the full documentation one needs: * `Sphinx `_ version 2.1 or later. * The `Enthought Sphinx Theme `_. envisage-6.0.1/appveyor-clean-cache.txt000066400000000000000000000001031406265421000200400ustar00rootroot00000000000000# Increment the number below to trigger an Appveyor cache clean. 1 envisage-6.0.1/appveyor-run.cmd000066400000000000000000000007631406265421000164610ustar00rootroot00000000000000:: Execute etstool operation for every tookit in the argument list :: Options :: %1 -- operation :: %2 -- runtime :: %3... -- tookits :: :: SETLOCAL EnableDelayedExpansion SET counter=0 FOR %%x IN (%*) DO ( SET /A counter=!counter! + 1 IF !counter! EQU 1 SET operation=%%x IF !counter! EQU 2 SET runtime=%%x IF !counter! GTR 2 CALL edm run -- python etstool.py !operation! --runtime=!runtime! --toolkit=%%x || GOTO error ) GOTO end :error: ENDLOCAL EXIT /b %ERRORLEVEL% :end: ENDLOCAL ECHO.Done envisage-6.0.1/appveyor.yml000066400000000000000000000012401406265421000157040ustar00rootroot00000000000000build: false image: Visual Studio 2019 environment: global: PYTHONUNBUFFERED: "1" INSTALL_EDM_VERSION: "3.0.1" matrix: - RUNTIME: '3.6' TOOLKITS: "null pyqt5 pyside2 wx" cache: - C:\Users\appveyor\.cache -> appveyor-clean-cache.txt - C:\Users\appveyor\AppData\Local\pip\Cache -> appveyor-clean-cache.txt init: - ps: $Env:path = "C:/Enthought/edm;" + $Env:path - ps: md C:/Users/appveyor/.cache -Force install: - install-edm-windows.cmd - edm install -y wheel click coverage - appveyor-run.cmd install %runtime% %toolkits% test_script: - appveyor-run.cmd flake8 %runtime% %toolkits% - appveyor-run.cmd test %runtime% %toolkits% envisage-6.0.1/ci-src-requirements.txt000066400000000000000000000000001406265421000177470ustar00rootroot00000000000000envisage-6.0.1/docs/000077500000000000000000000000001406265421000142475ustar00rootroot00000000000000envisage-6.0.1/docs/Makefile000066400000000000000000000060761406265421000157200ustar00rootroot00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source .PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " changes to make an overview of all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: -rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/envisage.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/envisage.qhc" latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \ "run these through (pdf)latex." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." envisage-6.0.1/docs/source/000077500000000000000000000000001406265421000155475ustar00rootroot00000000000000envisage-6.0.1/docs/source/api.rst000066400000000000000000000002311406265421000170460ustar00rootroot00000000000000API documentation ================= This section contains auto-generated API documentation for Envisage. .. toctree:: :maxdepth: 3 api/envisage envisage-6.0.1/docs/source/api/000077500000000000000000000000001406265421000163205ustar00rootroot00000000000000envisage-6.0.1/docs/source/api/envisage.api.rst000066400000000000000000000007461406265421000214320ustar00rootroot00000000000000.. (C) Copyright 2018-2021 Enthought, Inc., Austin, TX All rights reserved. This software is provided without warranty under the terms of the BSD license included in LICENSE.txt and may be redistributed only under the conditions described in the aforementioned license. The license is also available online at http://www.enthought.com/licenses/BSD.txt Thanks for using Enthought open source! envisage.api module =================== .. automodule:: envisage.api envisage-6.0.1/docs/source/api/templates/000077500000000000000000000000001406265421000203165ustar00rootroot00000000000000envisage-6.0.1/docs/source/api/templates/modules.rst_t000066400000000000000000000012341406265421000230430ustar00rootroot00000000000000.. (C) Copyright 2007-2021 Enthought, Inc., Austin, TX All rights reserved. This software is provided without warranty under the terms of the BSD license included in LICENSE.txt and may be redistributed only under the conditions described in the aforementioned license. The license is also available online at http://www.enthought.com/licenses/BSD.txt Thanks for using Enthought open source! {% macro automodule(modname, options) -%} .. automodule:: {{ modname }} {%- for option in options %} :{{ option }}: {%- endfor %} {%- endmacro -%} {{ [basename, "module"] | join(' ') | e | heading }} {{ automodule(qualname, automodule_options) }} envisage-6.0.1/docs/source/api/templates/package.rst_t000066400000000000000000000016231406265421000227700ustar00rootroot00000000000000.. (C) Copyright 2007-2021 Enthought, Inc., Austin, TX All rights reserved. This software is provided without warranty under the terms of the BSD license included in LICENSE.txt and may be redistributed only under the conditions described in the aforementioned license. The license is also available online at http://www.enthought.com/licenses/BSD.txt Thanks for using Enthought open source! {% macro automodule(modname, options) -%} .. automodule:: {{ modname }} {%- for option in options %} :{{ option }}: {%- endfor %} {%- endmacro -%} {%- macro toctree(docnames) -%} .. toctree:: {% for docname in docnames %} {{ docname }} {%- endfor %} {%- endmacro -%} {{ [pkgname, "package"] | join(" ") | e | heading }} {{ automodule(pkgname, automodule_options) }} {%- if submodules %} {{ toctree(submodules) }} {% endif %} {%- if subpackages %} {{ toctree(subpackages) }} {% endif %} envisage-6.0.1/docs/source/conf.py000066400000000000000000000174261406265421000170600ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! # This file is execfile()d with the current directory set to its containing # dir. # # The contents of this file are pickled, so don't put values in the namespace # that aren't pickleable (module imports are okay, they're removed # automatically). # # All configuration values have a default value; values that are commented out # serve to show the default value. import envisage import enthought_sphinx_theme # If your extensions are in another directory, add it here. If the directory # is relative to the documentation root, use os.path.abspath to make it # absolute, like shown here. # sys.path.append(os.path.abspath('some/directory')) # General configuration # --------------------- # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = [ "sphinx.ext.autodoc", "sphinx.ext.extlinks", "sphinx.ext.githubpages", "sphinx.ext.intersphinx", "sphinx.ext.napoleon", "sphinx.ext.viewcode", "traits.util.trait_documenter", ] # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] # The suffix of source filenames. source_suffix = ".rst" # The master toctree document. master_doc = "index" # General substitutions. project = "envisage" copyright = "2008-2021, Enthought" # The default replacements for |version| and |release|, also used in various # other places throughout the built documents. version = release = envisage.__version__ # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: # today = '' # Else, today_fmt is used as the format for a strftime call. today_fmt = "%B %d, %Y" # List of documents that shouldn't be included in the build. # unused_docs = [] # List of directories, relative to source directories, that shouldn't be # searched for source files. # exclude_dirs = [] # The reST default role (used for this markup: `text`) to use for all # documents. # default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. # add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). # add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. # show_authors = False # Substitutions reusable for all files. rst_epilog = """ .. # substitutions for API objects .. |Application| replace:: :class:`~envisage.application.Application` .. |IApplication| replace:: :class:`~envisage.i_application.IApplication` .. |ExtensionPoint| replace:: :class:`~envisage.extension_point.ExtensionPoint` .. |Plugin| replace:: :class:`~envisage.plugin.Plugin` .. |IPlugin| replace:: :class:`~envisage.i_plugin.IPlugin` .. |envisage.api| replace:: :mod:`envisage.api` .. |envisage.ui.workbench| replace:: :mod:`envisage.ui.workbench.api` .. # substitutions for the Hello World example .. |Hello World| replace:: :github-demo:`Hello World ` .. # substitutions for MOTD examples .. |acme.motd| replace:: :github-demo:`acme.motd ` .. |acme.motd.software_quotes| replace:: :github-demo:`acme.motd.software_quotes ` .. |MOTD| replace:: :github-demo:`MOTD ` .. |IMOTD| replace:: :github-demo:`IMOTD ` .. |MOTDPlugin| replace:: :github-demo:`MOTDPlugin ` .. |MOTD run| replace:: :github-demo:`run.py ` .. |IMessage| replace:: :github-demo:`IMessage ` .. |Message| replace:: :github-demo:`Message ` .. |messages.py| replace:: :github-demo:`message.py ` .. |Message of the Day| replace:: :github-demo:`Message of the Day ` """ # noqa: E501 # Options for HTML output # ----------------------- # Use the Enthought Sphinx Theme (see # https://github.com/enthought/enthought-sphinx-theme) html_theme_path = [enthought_sphinx_theme.theme_path] html_theme = "enthought" # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". html_title = "Envisage Documentation" # A shorter title for the navigation bar. Default is the same as html_title. # html_short_title = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = [] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. html_last_updated_fmt = "%b %d, %Y" # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. html_use_smartypants = True # Custom sidebar templates, maps document names to template names. # html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. # html_additional_pages = {} # If false, no module index is generated. html_use_modindex = False # If false, no index is generated. # html_use_index = True # If true, the index is split into individual pages for each letter. # html_split_index = False # If true, the reST sources are included in the HTML build as _sources/. # html_copy_source = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. # html_use_opensearch = '' # If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). # html_file_suffix = '' # Output file base name for HTML help builder. htmlhelp_basename = "Envisagedoc" # Options for LaTeX output # ------------------------ # The paper size ('letter' or 'a4'). # latex_paper_size = 'letter' # The font size ('10pt', '11pt' or '12pt'). # latex_font_size = '10pt' # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, document class # [howto/manual]). latex_documents = [ ( "index", "Envisage.tex", "Envisage Documentation", "Enthought, Inc.", "manual", ), ] # The name of an image file (relative to this directory) to place at the top of # the title page. # latex_logo = "enthought_logo.jpg" latex_logo = "e-logo-rev.png" # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. # latex_use_parts = False # Additional stuff for the LaTeX preamble. # latex_preamble = '' # Documents to append as an appendix to all manuals. # latex_appendices = [] # If false, no module index is generated. # latex_use_modindex = True # -- Options for intersphinx extension --------------------------------------- intersphinx_mapping = { "pyface": ("https://docs.enthought.com/pyface", None), "python": ("https://docs.python.org/3", None), "traits": ("https://docs.enthought.com/traits", None), "traitsui": ("https://docs.enthought.com/traitsui", None), } # -- Options for extlinks extension ------------------------------------------- extlinks = { 'github-demo': ( 'https://github.com/enthought/envisage/tree/master/envisage/examples/demo/%s', # noqa: E501 '') } envisage-6.0.1/docs/source/e-logo-rev.png000066400000000000000000000075111406265421000202350ustar00rootroot00000000000000PNG  IHDRoi*sRGB pHYs  tIMELIDATx]ktT>z\$m ȥ!TD jV|_CWph PT\"B$$6aIf9qfB%= By}; vo`!4ChVv[12 !HGiyg):QVd8SmsQAj2~~AH3qU:?!4[a6SRu ǎ7H'1"_Qq!JbBwx$I-S^QO>AO~( HAPU)=Ojjm+ v$~ئ"33zviJn[*.\v(/E1U`Ycֿ&y3g>=x$;GS@]d1YÓo"۾X6n8o2 ,c_܊U?y" "cdL5HfFj~}Q]H錩/Oxcq'~lӕ_ ţeW\| &cLMhdȶ9-՗ $ Θڳ9i˗>xa6>#E _h2$}앿"a\l߰0/"ޑҦ.*:UQyٕ~`:oYfxu? b)<̜>җ'rYgԾ6ngeSMkm>uv" Snhj ̌ry_ݚLM01@$(]vƏ{_{#&>4l|c.8~rK05bjԈm;14*:Ο3yK|ީT\> 8nd٤B]j맻]8#&[5TEUlu#u\/kk^6t=Zo`Ӌ-,R'*EP1#EQ DfsnlOYYYҨ!${G2yZ~\pN|olӋnϯBu-\$5˘TYgNR^\8gF{@|4Ņ0ov2֊^:j)D"zM En1]WfN@wǛ뿨k B|c!>8T'JԉaZxubOW~;c%dLynظedNSt~WX\f-pO',9UI21`xĥd  ,{ER"Z G 4PLq@$#15! G}\.-2kEfV=G15Q&ph!9Ce Cvj(# 5#GX:InHJZmڞU__(h݆' H7cHκ})"Db-&`i\eU?*YJ05 D S[GabDěrqEʪ9կm"4LwtGTدr{OPۿhj?:}"i b:/7yA@eK#$t13mj51K &^w !%PSSSֆlr{s^#w4DmQI S#3a@57Q; S#:į v4yR+A&P0j/))-&Z4S.[Z2d^!j8J01-j(T!05Q)"jԌ+@vpd"'4LuyC͉cv,@A1i_qLq|s4bvGz!U !KIQD1E3[1vI $00h6FL̙dnu˞?SScw\LGaʃcf-N]y/4u: c c PM18_h>4~h޽f l%&N^>?2=iC)9v!˜j>hN'N~(aİ}Wx+' u0?1sL _/>_nH ! x9zq@bzlLؘO_6Ac6~t=F&מc2\汋rh3.婓Jx`x^_>_mqKkj+-++Y.zw3TU+qܹ~M\_:pBI" D5 JcTubd!P%+~fz*EP]6R2;/uz] g,'Nd=C^n188D,dZ}W/)~ǎ/z~*0P]g*ݐ[{s]b76 $?`[퍘JTDDKŽ t "((}qqwZΦO11fZ XSXk71E~;{GbN#"k" r@4˗mrN"srLڀ?Vh?݁nw'?0l۶`bF4]2UU ;llgL bkx'ۄ&%QU#c*B{awE|DǶBhZ-f/wIENDB`envisage-6.0.1/docs/source/envisage_core_documentation/000077500000000000000000000000001406265421000233115ustar00rootroot00000000000000envisage-6.0.1/docs/source/envisage_core_documentation/core.rst000066400000000000000000000007541406265421000250010ustar00rootroot00000000000000Core ==== The goal of the *Core* project is to provide a set of general-purpose tools for building extensible, pluggable applications. Hopefully, these tools are useful whether you are building an airline reservation system, a web server or a game of tetris! There are just 3 simple concepts at the heart of every Envisage application: 1) `Extension Points`_ 2) Services_ 3) Plugins_ .. _`Extension Points`: extension_points.html .. _`Plugins`: plugins.html .. _`Services`: services.html envisage-6.0.1/docs/source/envisage_core_documentation/extension_points.rst000066400000000000000000000211061406265421000274530ustar00rootroot00000000000000Extension Points ================ Whether or not we as software developers like to admit it, most (if not all) of the applications we write need to change over time. We fix bugs, and we add, modify, and remove features. In other words, we spend most of our time either fixing or extending applications. Sometimes we extend our applications by changing the actual code, sometimes we have other ad hoc extension mechanisms in place -- a text file here, a directory of scripts there. As applications grow, they often end up with numerous places where they can be extended, but with a different extension mechanism at each one. This makes it hard for developers who want to extend the application to know a) *where* they can add extensions, and b) *how* to add them. Envisage attempts to address this problem by admitting up front that applications need to be extensible, and by providing a standard way for developers to advertise the places where extension can occur (known as *extension points*), and for other developers to contribute *extensions* to them. In Envisage, extension points and the extensions contributed to them are stored in the *extension registry*. To see how extension points actually work, let's take a look at the |Message of the Day| example included in the Envisage distribution. This example shows how to build a very simple application that prints a (hopefully witty, educational, or inspiring) "Message of the Day" chosen at random from a list of contributed messages. Declaring an Extension Point ---------------------------- Plugins declare their extension points in one of two ways: 1) Declaratively - using the |ExtensionPoint| trait type 2) Programmatically - by overriding the 'get_extension_points' method. In the MOTD example, the |acme.motd| plugin needs to advertise an extension point that allows other plugins to contribute new messages. Using the |ExtensionPoint| trait type, the plugin would look like this:: class MOTDPlugin(Plugin): """ The MOTD Plugin. """" ... # The messages extension point. messages = ExtensionPoint( List(IMessage), id='acme.motd.messages', desc = """ This extension point allows you to contribute messages to the 'Message Of The Day'. """ ) ... Overriding the 'get_extension_points' method might look somthing like:: class MOTDPlugin(Plugin): """ The MOTD Plugin. """" ... def get_extension_points(self): """ Return the plugin's extension points. """ messages = ExtensionPoint( List(IMessage), id='acme.motd.messages', desc = """ This extension point allows you to contribute messages to the 'Message Of The Day'. """ ) return [messages] ... Either way, this tells us three things about the extension point: 1) That the extension point is called "acme.motd.messages" 2) That every item in a list of contributions to the extension point must implement the |IMessage| interface. 3) That the extension point allows you to contribute messages! Making contributions to an Extension Point ------------------------------------------ The |Message of the Day| example has a second plugin, |acme.motd.software_quotes| that contributes some pithy quotes about software development to the application. First of all, we have to create the messages that we want to add. Remember that when the |acme.motd| plugin advertised the extension point, it told us that every contribution had to implement the |IMessage| interface. Happily, there is a class that does just that already defined for us (|Message|) and so we create a simple module (|messages.py|) and add our |Message| instances to it:: messages = [ ... Message( author = "Martin Fowler", text = "Any fool can write code that a computer can understand. Good" " programmers write code that humans can understand." ) Message( author = "Chet Hendrickson", text = "The rule is, 'Do the simplest thing that could possibly" " work', not the most stupid." ) ... ] Now we create a plugin for the |acme.motd.software_quotes| package and tell Envisage about the messages that we have just created. Again there are are two ways that a plugin can do this: 1) Declaratively - using the 'contributes_to' trait metadata 2) Programmatically - by overriding the 'get_extensions' method. The declarative version looks like this:: class SoftwareQuotesPlugin(Plugin): """ The software quotes plugin. """ ... # The 'contributes_to' trait metadata tells Envisage the ID of the # extension point that this trait contributes to. messages = List(contributes_to='acme.motd.messages') def _messages_default(self): """ Trait initializer. """ # It is good practise to only import your extensions when they # are actually required. from messages import messages return messages ... The messages are contributed simply by creating a list trait and setting its "contributes_to" metadata to the ID of the extension point that we want to contribute to. All we have to do then is to intialize the trait with our messages and "Job done"! Note that if a plugin changes a list of contributions then the extension registry will be updated automatically, and anybody that is consuming the extensions will be notified accordingly. The programmatic version looks like this:: class SoftwareQuotesPlugin(Plugin): """ The software quotes plugin. """ ... def get_extensions(self, extension_point_id): """ Get the plugin's contributions to an extension point. """ if extension_point_id == 'acme.motd.messages': from messages import messages extensions = messages else: extensions = [] return extensions ... The difference between this and the declarative version is that the application is not automatically notified if the plugin wants to change its contributions to an extension point. To do this manually fire an 'extension_point_changed' event. Retrieving the contributions to an Extension Point -------------------------------------------------- OK, here's where we are so far: One plugin (|acme.motd|) has advertised the fact that it has an extension point called "acme.motd.messages", and that the contributions to the extension point must implement the |IMessage| interface. Another plugin (|acme.motd.software_quotes|) has kindly offered to contribute some messages about software development. Now we need to know how to retrieve the contributed messages at runtime. In the MOTD example, the messages are retrieved by the |acme.motd| plugin:: class MOTDPlugin(Plugin): """ The MOTD Plugin. """" ... # The messages extension point. messages = ExtensionPoint( List(IMessage), id='acme.motd.messages', desc = """ This extension point allows you to contribute messages to the 'Message Of The Day'. """ ) ... def _motd_default(self): """ Trait initializer. """ # Only do imports when you need to! from motd import MOTD return MOTD(messages=self.messages) ... As you can see, all we have to do is to access the **messages** extension point trait when we create our instance of the |MOTD| class. This example demonstrates a common pattern in Envisage application development, in that contributions to extension points are most often used by plugin implementations to create and initialize services (in this case, an instance of the |MOTD| class). The extension registry can also be accessed through the following method on the |IApplication| interface:: def get_extensions(self, extension_point): """ Return a list containing all contributions to an extension point. Return an empty list if the extension point does not exist. """ For example, to get the messages contributed to the "acme.motd.messages" extension point you would use:: messages = application.get_extensions('acme.motd.messages') Note however, that using the |ExtensionPoint| trait type, adds the ability to validate the contributions -- in this case, to make sure that they are all objects that implement (or can be adapted to) the |IMessage| interface. It also automatically connects the trait so that the plugin will receive trait change events if extensions are added/removed to/from the extension point at runtime. .. _`Python Eggs`: http://peak.telecommunity.com/DevCenter/PythonEggs envisage-6.0.1/docs/source/envisage_core_documentation/front.rst000066400000000000000000000033171406265421000251770ustar00rootroot00000000000000==================== Front Matter ==================== :Authors: Martin Chilvers :Version: Document Version 1 :Copyright: 2008 Martin Chilvers. All Rights Reserved. Redistribution and use of this document in source and derived forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source or derived format (for example, Portable Document Format or Hypertext Markup Language) must retain the above copyright notice, this list of conditions and the following disclaimer. * Neither the name of Enthought, Inc., nor the names of contributors may be used to endorse or promote products derived from this document without specific prior written permission. THIS DOCUMENT IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS DOCUMENT, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. All trademarks and registered trademarks are the property of their respective owners. | Enthought, Inc. | 515 Congress Avenue | Suite 2100 | Austin TX 78701 | 1.512.536.1057 (voice) | 1.512.536.1059 (fax) | http://www.enthought.com | info@enthought.com envisage-6.0.1/docs/source/envisage_core_documentation/glossary.rst000066400000000000000000000020771406265421000257140ustar00rootroot00000000000000Glossary ======== Application The thing that plugins plugin to! Extension Point A well defined place where new functionality and/or data can be contributed to an application. Extension (aka Contribution) An *actual* piece of functionality or data that is contributed (this is why extensions are often known as contributions). Think of 'extension points' as the 'where' and 'extensions' as the 'what'. Extension Registry The place where (by default) all of the extension points and their associated extensions are stored. Service In Envisage, a service is *any* object that a developer thinks is sufficiently useful to want to share it. Service Registry The 'yellow pages' style mechanism that is used to publish and look up services. Plugin The mechanism for delivering new functionality to an application. A plugin can do 3 simple things: 1) offer extension points 2) make contributions to extension points (including its own) 3) create and publish services Plugin Manager Finds, starts, and manages the plugins that make up an application. envisage-6.0.1/docs/source/envisage_core_documentation/gui_application.rst000066400000000000000000000033261406265421000272160ustar00rootroot00000000000000GUIApplication ============== The :py:class:`GUIApplication` subclass of the standard Envisage :py:class:`Application` class uses Pyface's cross-platform GUI tools to ensure that an appropriate back-end application object is instantiated before plugins run their :py:meth:`start` methods, and then ensures that the GUI mainloop is started after the :py:meth:`start` methods have successfully completed. This allows developers to have a single place to start the application, but have plugins to listen for the :py:attr:`application_initialized` event to create UI components. In particular, Traits UIs can be created in an event listener method by calling :py:meth:`edit_traits`, something like this:: class MyViewPlugin(Plugin): model = Instance ui = Instance('traitsui.ui.UI') @on_trait_change('application:application_initialized') def create_ui(self): # we need to keep a reference to the ui object self.ui = self.model.edit_traits() This is intended to be a cheap and simple way to take an existing application and start "lifting" it to Envisage, without having to use a more heavyweight UI framework such as Tasks immediately. Developers can work on adding extensibility to the application's model without having to at the same time add extensibility to their UI. The :py:class:`GUIApplication` exposes the following public API: .. py:attribute:: gui The Pyface :py:class:`GUI` instance the :py:class:`GUIApplication` creates. .. py:attribute:: splash_screen An optional Pyface :py:class:`ISplashScreen` that gets shown while the plugins are started. .. py:attribute:: application_initialized An event which is fired after the UI mainloop has started. envisage-6.0.1/docs/source/envisage_core_documentation/howto_create_a_plugin.rst000066400000000000000000000253711406265421000304140ustar00rootroot00000000000000How To Create a Plugin for an Envisage Application ================================================== This document describes the process of creating an Envisage plugin that works with a Workbench-based application. There are several questions to consider when designing a plugin: * What does it do? * What functionality does it offer to other plugins? * What does it add to what other plugins already do? * What does it need from other plugins? What Does Your Plugin Do? ------------------------- "What your plugin does" refers to the basic functionality that your plugin adds to the application. Very often, you want to take some pre-existing chunk of functionality (module, library, etc.) and make it available within the Envisage application. Assuming the library has a well-defined API, you do not need to alter it in any way. You only create the plugin code to wrap it for Envisage. Sometimes, however, you are designing a new chunk of functionality just for the Envisage application. In this case, step back and think about how you would design the core functionality of the plugin, independent of Envisage. It is important to keep this separate from the plugin machinery that make that functionality work within Envisage. In either case, we will refer to the code that does this core functionality as "the library". Example Library ~~~~~~~~~~~~~~~ Suppose you have a library that implements a game of Tetris. You want to add this game to an Envisage application that lets users pick a game to play from a catalog of available games. You have a Tetris class that looks something like this:: class Tetris(HasTraits): # Basic colors background_color = Color foreground_color = Color # Shapes to use in the game shapes = List(IShape) ... In the following sections, we'll look at ways that this library can be integrated into the application by a plugin. What Does Your Plugin Offer to Other Plugins? --------------------------------------------- There are two ways that a plugin can provide functionality to other plugins. * **Offering services:** Your plugin may provide an API, the core functionality of your library, as a service. Yours might be the only provider of that type of service in an application, or it might be one of many. * **Defining extension points:** Your plugin may offer one or more extension points, which means that your plugin will do something specific with contributions to that extension point. Contributions to an extension point must be instances of a type or interface that you specify. An extension point is a trait attribute whose type is ExtensionPoint, which consists of a List of some other type, plus an ID. In other words, an extension point is a list, used by your plugin, that is populated by plugins. Examples of Offering Functionality to Other Plugins ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Suppose that the games application defines an "IGame" interface that it uses to control games, with methods for starting, stopping, displaying scores, and so on. In this case, you offer the Tetris library as a service that implements the "IGame" interface used by the application. (You might need to create an adapter for your Tetris class to the IGame interface, but we'll ignore that.) You can manually register a service, but a simple way to do it is to contribute to the 'envisage.services_offers' extension point of the Core plugin. This ensures that the plugin is registered, and is created when it is needed. You also want to allow users to contribute their own Tetris shapes, so you define an extension point for shapes. We'll leave aside the question of how users actually define their shapes. The point is that the catalog of shapes is extensible. (You would probably also contribute some basic shapes from your plugin, so that users don't *need* to contribute any.) What Does Your Plugin Add to Other Plugins? ------------------------------------------- Other plugins may provide extension points that are useful to your plugin. If so, you can contribute to those extension points. Essentially, your plugin passes one or more objects to the plugin whose extension point you are contributing to, and that plugin "does the right thing" with those items. Examples of Contributing to Extension Points ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Continuing the example of the Tetris game, the application might keep a list of available games, and present the list for the user to select from. Thus, it might have a 'games' extension point, to which you can contribute an object containing information about your Tetris game, such as name, description, icon, and entry point. More concretely, you want to specify default colors for the foreground and background colors of the game, but allow users to change them and save their changes across sessions. For specifying default preference values and saving changed values, you can contribute to the 'envisage.preferences' extension point offered by the Envisage core plugin; for a UI to change preferences, you can contribute to the 'envisage.ui.workbench.preferences_pages' extension point offered by the Workbench plugin. (A game application probably wouldn't use the Workbench plugin, but we'll assume it does to avoid using a fictional plugin.) A contribution to the preferences extension point must be a URL of a preferences file (readable by ConfigObj). A plugin typically has only one preferences file, even if it has many categories of preferences. A contribution to the preference_pages extension point must be a callable that returns an object that implements the `apptools.preferences.ui.api.IPreferencesPage` interface. Such an object typically has a Traits UI view that can be used in the Preferences dialog box to set the values of the preference attributes. A plugin may have multiple preferences pages, depending on how it groups the items to be configured. There are two strategies for defining a callable that returns an object: * Subclass from a class that implements the interface, in this case `apptools.preferences.ui.api.PreferencesPage`. * Define a factory function that returns an appropriate object. This strategy is not needed for preferences pages, but can be helpful when the object being returned contains a reference to a service. In either case, the contribution is a trait attribute on the plugin object. What Does Your Plugin Need from Other Plugins? ---------------------------------------------- Your plugin may need to use the API of some other plugin. In Envisage, you use the other plugin's API via a service, rather than directly. This allows for the plugin offering the service to be replaced with another one, transparently to your plugin. There may be multiple plugins offering a particular type of service, but a client plugin uses only one instance of a service at any given time. Example of Using a Service ~~~~~~~~~~~~~~~~~~~~~~~~~~ The service you use may be your own. For the Tetris game, the object that you contribute to the application's 'games' extension point needs to be able to start the game. However, to reduce memory overhead, you don't want the Tetris library to be imported until the user actually chooses to play Tetris. Using the service offered by the Tetris plugin is a way to accomplish that. Complete Example ---------------- The complete plugin for the Tetris game might look like this:: class TetrisPlugin(Plugin): """ Plugin to make the Tetris library available in Envisage. """ ##### IPlugin Interface ################################################ ### Extension points offered by the plugin # Shapes to be used in the game shape = ExtensionPoint(List(IShape), id='acme.tetris.shapes') ### Contributions to extension points my_shapes = List(contributes_to='acme.tetris.shapes') def _my_shapes_default(self): """ Trait initializer for 'my_shapes' contribution to this plugin's own 'shapes' extension point. """ return [ Shape1(), Shape2(), Shape3() ] games = List(contributes_to='acme.game_player.game_infos') def _games_default(self): """ Trait initializer for 'games' contribution to the application plugin's 'games' extension point. """ return [ GameInfo(name='Tetris', icon='tetris.png', description='Classic shape-fitting puzzle game', entry_point=self._start_game) ] preferences = List(contributes_to='envisage.preferences') def _preferences_default(self): """ Trait initializer for 'preferences' contribution. """ return ['pkgfile://acme.tetris.plugin/preferences.ini'] preferences_pages = List(contributes_to= 'envisage.ui.workbench.preferences_pages') def _preferences_pages_default(self): """ Trait initializer for 'preferences_pages' contribution. """ from acme.tetris.plugin.preferences_pages import \ TetrisPreferencesPages return [ TetrisPreferencesPages ] services_offers = List(contributes_to='envisages.service_offers') def _service_offers_default(self): """ Trait initializer for 'service_offers' contribution. """ return [ ServiceOffer(protocol=IGame, factory=self._create_tetris_service, properties={'name':'tetris'}) ] #### Private interface ################################################# def _create_tetris_service(self, **properties): """ Factory method for the Tetris service. """ tetris = Tetris() # This creates the non-Envisage library object. # Hook up the extension point contributions to the library object trait. bind_extension_point(tetris, 'shapes', 'acme.tetris.shapes') # Hook up the preferences to the library object traits. bind_preference(tetris, 'background_color', 'acme.tetris.background_color') bind_preference(tetris, 'foreground_color', 'acme.tetris.foreground_color') return tetris def _start_game(self): """ Starts a Tetris game. """ game = self.application.get_service(IGame, "name == 'tetris'") game.start() envisage-6.0.1/docs/source/envisage_core_documentation/images/000077500000000000000000000000001406265421000245565ustar00rootroot00000000000000envisage-6.0.1/docs/source/envisage_core_documentation/images/application.png000066400000000000000000001000741406265421000275710ustar00rootroot00000000000000PNG  IHDRcsRGBgAMA a cHRMz&u0`:pQ< pHYsttfxIDATx^ݽu3hȀ)q= Ό GGPn ʜ!޽wo6EUUgﵚ?@@@T@@@|+j6&      @iZiw|@@@U5{@@(ZyČ@@@{T-  GwO@@'@պ   @yZyČ@@@{T-}?~{c+.cj?B z;AEMMd @vT-;RD  )Ƈ,z:ڟ~mW{zTLZ!@Zց矟t5U wCC٪cTi$U[; 򺖿 J=.;n?lQ:": NTm'8!@>k(?h5T"[ Tmˍr, -`'0]"U+o? PV03O_H>Amzx*sgL|nat')!b xH2=l}6Gwi/仳mK_ FvGN%bWBi$g8&BUKJ埧Ʉc O|4)l}RͶg?<4g/$"Qx'=vMN PɶG9?'w/iNi,?[ůٍ[I _NzN4o{kmܑHSUm^=sgOs?/O7Yor P ]*!0c GR1jS0ZmE|fN'}^oi7Fy|-Ë8%Mh-ޝ9-]I1M@  @fc%>ViC&GЙO*\Umwq힜Ɗ 5ękU؉8%If>{ro>AR擒|eYs@ Po "@&k_{Vr>-[vwMci`iSe*QnbGzQʶ y'pF3O}O/vVvjvS=JdRHtDut- eS)̙PUz5cj6Yr<=e;ncn\v{Ь?|ܺYUo}=U\] +f .2"\`\əP^j#WB^>IdߧdK0/ #!D+e%kG -" P0] J% ĴrU{Lޤha-i6# Wj[izk~X4ԎS6ڤo=JMwaJiSJ{`- @Zͤ@`4P?'9ʀڗ<~qGpiӪkкa3jGז9 S3D;NIR!-$wܣ u'=`@Ti 0O 13s'gBjO?4m1Aoo __wc'9O`?ʃt8pOqJ}q&p=׻ 8\O.ƪ  1ͷt:!}60w?~ =4'W{r_I7y1()i%ʩ?8%Q/LccދǏԭtnC[e 9|'1n"9tuRa}k4UkZu|VNݾt㧟?5Q׼d~_M+$O*= =-ӕɅ2rdV]j]vF#2IW˙Pҳa-GjW/{y@JA^l鑒Hݗy|? kmvAզw{tD̏0;9_Y4KEP"x֛cb;N)h EE7. 5Dd4޵U@T`  @}rnGCiw8 @( @֞b8\A#@SV~"-qJʌ PV 1@3@V;k) 3$ @iZiw|@@@T&@@@#@Պ%&  P5{@@(U+   @@@@8T[bB   U@@@Pn !  T@@@#@n7@@@@T-` ^_E@@ 5 i~HU"$$  U pߩZv&! TD8UR'  @=Z5j}d  T-_4@`j  @EZ3hrE1T@@PkAUN ".  p/ S3"k" D8 5@" CXCȨv=.  p/ S3"k" D8 )\@UQ;TUd  T-_4@`j  @EZ3hR$  @=Z5j}d  T-_4@`j  @EZ3hrE1T@@PkAUN ".  p/ [3^B  Z_~5@@N"@ըZ/FN ".  pFը3^Z  TQ5  @qUjŽ,|\@@6P5Fը  jTls @@8BQ5F@@(UjTO_  FըUC@@8TQ^m|d  GP5Fը  jT}q.  @UjT @@P5FՊ{Y9U  !@ըUj  GQ5Vȧ/E@hUjT!  PFըZq/6> @@#UjT @@P5FՊ{YŹ  mjTQ5@@#@ըU+e@V nYϩc8!UjT @ %0۷ߥcCl"pUjT7m# P)EYjXLCTQ5 Eԩtw8\FըZoNF@ #$m0q .@vTQ5 a"$2Uj 4p6쫣jTS  p)$]zLjTP  pwO8Ϻ8ҶԵ:FըuoNuEE@ /RiOx5–ֻ>TQ5 (ZS^ۧKQ5vS! P UjJQP#jT]]Ujj>SBMxV_HըR*jPv^se@ P5vsDFըZժ@V P5.m]T.*EB&CJ | pUjGs P5FN'@C#@`OIsҟMWv0 UUZ~o/|D_~}?o~T5Rk™QQ5Dͫ'xHNT툿_(e7=<+,q𲝐\U˵\gUjIV>Y2E{01j1 CPm FzWQ/tx߿gcʔq U2 m!gDk=Fo%DQˎP5jw= kTmSDD^H'"iØ9;7TcEzQjTmEnߏjXUKíxijW5f?y!\@QNJLJUB̷=4U!bbj2C P*٪rM`cjT-ZfEF:G@Z4mձ[i9{aۇQ5jvUϲHϒB il1euT!@ըȔ]s#̹NL_Gըy˕U;Aզ)f_aT!@4Jju?'@ըZ6Ua֞~$6ڧSe lVjsc?1!G&Vgk[jEWcTTB{~/)wo^U` P3TM(ڧ4v Frsam\_ʞ~ȯmmR^~z{qm`BOY~ [{5vljT-@ "Oҩ8|o^{=7VT@1nqiF+KպV#L8ܯoFx}Aʠf,!Лi,g]c.'0$Э zWnUxډR Uja-5YKTh3.Z,֨U;Ujv7Ou5 !IA"й*jㅏ۟s-%|K0FU^K9U+!ؙjyf+m~lFޛ{iۯP5FN' 7=- IUjTtTMEvCNըp,qifP'@ը[2[ójTN5C- !Fռ"5Z/%@ը颒Xzj0&wA<5~=56gkJj kmMc: 3FըZjs9=y[KT  @.TQ+Ku#Пyz`kj:A(TQ+i tiom;!.CZP5Fը; jǨ&5mxEP5FvJcOѳ Fըz% J TKEQ5FL{OSX[3}xZu@Fըz%X 0"@|c-EѡQ@ UjT!O{4Y6OK̽$jT[/'uBQޕQH@ UjT!O4mӉ񴔬1 &TQ|kKjTmAx]x@ UjT!Uj)oc5cW Oy,tUjTmC^kT Ӟ=MѰfeP7) PFըچd׶AQv7ik<-}K k-d'@ըUKJӳ\:Ti 6z+_9[[@TQ5@F.x.DHZʽ,eAXTQ4=jP5v>IKU{A#uDjTQ5P5v[Foo#$@ըUKJ_.UjQ[la#il36w#BQ5FH"@ըZcлP|gim?:\شZ6|eP5VȮMz]tN%@պS0O O %=\Q2y'neοY5ilMhj!-djKjT-rP"/T-;Uk#fQgX{Z3:sZ3|NS5q86ck~K74w+DD4v3.T- 懤j P5v|Bu46 7O@U-TbjTGz45/"@b!dUU+*T:6j>9@/ݡ)"T-_4@`jb.*oaF՚%@Պ&@'Z3hMTYSyU4+S2c !v>hUjY6O P5ήY Uj4bxjΠ2H O[lA]V xZUkZ"@BEdvRh-ﵵH[_֯T:oT\Z3hRdzkkB<ګy͸\ MZh fShp5.w6T]@   ckY0 i$T$ЎAT-BF|PUkbQ]dޑY9P5Ն PLoH`l3YR~C`MTּ{z H8 n U bn$jT-jzj12j\f T?E #/ Så@ Pylx[ZZ3hR$ @FժHMT-BF|P ѬB nT-_4@`;L8Ujy-U p 1}:Sf2+@bTo9!0IXCȨ]GDH'йyL*D<T-_4@`s^qe@==wUJ8 5@"l!ЭQJznUQ;O{ˊ@ thk.T@:/ {I DjvjΠ2-m?Q @Q5kLc !v>@`~l{,N"@BEdvo?'E'Љyl~'[`uZ3hR$ ^O.7PkA Ѭ8U; "/ S5@m[䑍\#@Ad t {~ @EI[iumBUQ;[QWb@4fkO'.@T-_4@`n%d @3fFpT-4@@@ [TstCFXCȨ=ICHPyO̸\ Pyqĕ@> TjkܮV]#  ckSc3g@<}peKXCȨ)op5@*l@uZh -d @EJ~ЈbZETjΠ2  '(MH PkAUժU&TDa#imSE`U  ة8 p%(a#iWec!p*  ckXIw$AahuGmfZ5C#P=r/}wY p[x΂өZh .k/~Y2z=e}=s,g=XT-4@@uWl={bwɒ:D]I\N9ܙ!is烪li<^lZ+E @l݅-K  j1;U>L\h@luQkAĠప AazO<-1-tĨZh .]P KIO @ rV꺨Z3hըu;l2Vu!p=x>)aDc !v>WB1Ԙyl6/6+#zceT-_4@`Ļ ԛy!P |}uޔZ3h( h Ht69y +#6Iu:c !v> b4jc[,y 4*e5yGWj!2Vl2#K>R>TZ>a@ a8 m!XO䃭U=HSJM" JV>7Uc !v>p/F8d \ζ|߾:Icac;v/ emDzW|֊z.fG>]صX]zқRg]ȶ,ؚdѳiyM1i;sI}Nb!dd.Ca}w}}K@au*NJrڜ鳥j!2(Y>R9a`H vb IK  c{oɈWaoG~ ^ZI[,}v)0H Msc !v>dsQ kowO+G҆3Q^{ w@ARKW{u(] 5B qMr  c{wAl+B&kkRICak{:^-m7uC=`^y"26/팯J<6A)$f %\8Z tUVjJ%9t'c/Eb!dbcsP{/1& 6c'W U  ؅pc E]P㫶yH"'<㽷9Wp@T-4@@h % -jYaC}l_7G?d%J>泃\6m[}l}< g.>ߥ=ǣe Wj!2UB1U;mQT8FW(8e>E}jΠ2H P}o:4&6RөZ5>[*6 X:UK-GvjЗT`l,t/ SbÐPzdOF&~/|BΥjΠ2 ӠjTmz5Q`?XCȨ곥HV7I}BiM`*΢j!2;U"$2dk˒%r}Z3hRdd$2EǞֺ5Q`k9XCȨVKT22ɎU)Ɗ֩Rm/DFu[vi kƅ'[J~ҎjΠ2J GFBն:GA6nk#U+:XCȨꃥ׭FնZu6}T-_4@`jq%&%^ n.**\XZFAd t  )joJjMkښHңbuGRkA}^]x2IҠjE֦ě;TdTm?yukLkjTĨabcU p 15@6@=[#d^Q5H Cb!dRAʴ_ P5jS6`.$jL ;, nU.HBW[9YV=(!U먰&tyse@`7ø#U+!ݻÙ@ (UjA߰jD3D@!@ըZbVN3@@|TKR" jT?g Z1U+'   P>bTDŽoiJyOJWԸǟJ"TcE@@'PݥHC]RqZhVUϮ@(@6(ew+Pr  (Lnms*r~|ypXt\]A$U+_~4C@@lMKUبUj  JnNxkTa5@ɓ# -hT6l|-Ľ{!sj8:lX|\jVqVtDUu~Wm }~I^\@h@U6+UmUmŲa)j[+Ti-   SzԲ~W}l^:I^\@hU{DնJPB  gj3p=T5 +vv8s}@@%]cB>VwrM{נj-NkA@&кM~~֞fXU;;>  FU־3Ꮴx8գqo?yH{Ti-  UGg''m*ZWcTp  @KUaamhk7%{6gO`k!6]:@8@Ӫve&iÂثS:$͓85U\QÙ# -(F\\\]s?}]:@8U{['GQ:oFّ@@jU'IQ\Q5  P<jO>\=R5V|`:@@PO=ddknkTQ5@@ PB'_Q5V|`jc0K@@jM4nRGFը  @U ,9@Uj9SЊZWb@@U j@@P5֟3u[-@8HQ qoXv0Z9@Uj9SЊZWb@@U j@@P5֟3u[-@8HQ qoXv0Z9@Uj9SЊZWb@@U j@@P5֟3u[-@8HQ qoXv0Z9@Uj9SЊZWb@@U j@@P5֟3u[-@8HQ qoXv0Z9@Uj9SЊZWb@@U j@@P5֟3u[-@8HQ qoXv0Z9@Uj9SЊZWb@@U j@@P5֟3u[-@8HQ qoXv0Z9@Uj9SЊZWb@@U j@@P5֟3u[-@8HQ qoXv0Z9@Uj9SЊZWb@@U j@@P5֟3u[-@8HQ qoXv0Z9@ޟ)ݖ&ISb" 踠/J37g@Vf tE] _RTg  @Z-|ISz# ]j@"+b@@*%@"!pnVU4T6  , Sb" j2^i2m@@+T-vhU" j΢2>U7Z9  `  #k  Pi cw;ZWEY,  P/, Sz# ]j2]EaE@& PHkۡUՄB@@*P@gUL@@؂H 5@" @EZC*@@*P@gUL@@؂H k(   T-vhU5Q@@ T-Y4@jU&D@j2H   5j8vC @@ T-Y4@jU&D@j2B3  UZUMC@U t Z$@@Z-hR$  @ Z5Ъj>B@U t Z$@@Z-hrЌ  @"!pnVU@@ @Ed|VE`2I@@`  #k  Pi cw;@@ @Ed|VE`2I@@`  #k/4# PHkۡUD=@@*P@gUL@@؂H 5@" @]: Un@@*"pK_=` P" PgPUr  p'@b!dUU@@*"@BEdvVQ`2U@@  ck  Pc !v>@@*"@BEdvVQ`2U@@  ck/4# PkAU;@@PUL@@8 5@" CXCȨ)@@PUL@@8 )   T-BF|PU5@@"T-_4@`j&SE@jΠ2H   j12j烪h @@"T-_4@`j&SE@jΠ2zB3  UQ;TUMC@U  ةZET@@Z3hR$  @=Z5j>B@U  ةZET@@Z3hrЌ  @b!dUU@@*"@BEdvVQ`2U@@  ck  Pc !v>@@*"@BEdvVQ`2U@@  ck/4# PkAU;@@PUL@@8 5@" CXCȨ)@@PUL@@8 )   T-BF|PU5@@"T-_4@`j&SE@jΠ2H   j12j烪h @@"T-_4@`j&SE@jΠ2zB3  UQ;TUMC@U  ةZET@@Z3h~   PAU6%;@ }/ :4w?$['gDə@; CRTMl%@դ @ T- H FT-*E3. @¬!jUUi@&YDP(s@I:@ZTf\@0 76>mMjE@ U {WUm_>nB T-*E3. @բEdyʱ#qA/?@ShU,"QZ0h rcӨZ U UJьP0kq&jUck[9>d cd"@բEdyyᦜoijk98m}ݫ2U1T-*E3. @Adt bF4,U,"QZ5D >OL R}\gu&@P(s@29 9V+} UXsGdA6.lf~GBcnpAW|p&j 2 z% $ C$y\: :6yݿN J8V*gj&p,.֨Uۛ_~U~]o(Yl-}8vqNp鯰sbotU8N/3}k.德wLx8[{.X['ԳxNܳO w`=F"!pn.M!v^73̫ v1ڟ>=i9Mvf$='.٭vËW0쁱hg-AÑwky9)v2 ~_nz~ :gxw|HTzFb3b~ܽ81wl3, CR;iQ9+B v۱p$O=#5m5BR5r&[i9+E'$YWk Ϲ[' S\1,M>ƻ{ 91|A4Uۙ8堸M7B+{v _8|)Eby|ɻ[#i<+*v2:U;8=SjTmc^T-&b5yD Y,ЉȔ,{ڹna؝f O>Wa32OENB SjT- ?8$U;p%XMO[,a5P32%v?6ȃΰ#_'pWqpGWR۱o[=pi=Mt?9T-- ϙwJ×Lo!Sst}kh2,U33΢j7-A>s\U&e8 ;,3dY kYfSڡZ-|IZƷ̅_\`)Y!qYaGz0lFM=U+㔌Uj !Z.h2s=y<uX  Q;Vq;Fsj KQ[r[heT-˛%[L#@)99wM>r݅6֔Ԏ4@&j^1D6E8!$3(4I P{vFKw3-ne|&snctUI`.jMn gfιp7CVڭg>pYU j>$U _x>1s)g[Q?=;L#v¥%;j},&6]͖?t9 )֤ H-LU4@T->U&5 ,XKAʲ5[l@`-NxW7$U eTM%HHA 5VBjtH !T=Sqቩի%Ub5;wrJ~d$7FM \'ö́*Kj lAd$DDʋ'k{D=j}Y 4ŹDv$3G=]rj{һ=* [AJYB2?cnWZ`Ъj=3+mԱi~U˓͟%s=:l!v 'ӏ/zxwFa- {?ǹB2WW:~d;9Qk-؂Z$tUc}zpv\5IiA<ʯ*lce~d5uK< i/ +4˿Te>yZ^rpvM T-0uhU;v0KӀomk 9bWOq H閼2LU3ZGImAVX[HS}6a6񓈗èZ#?o1ǟPj3t 86P5nv6ܣooU=k`{iW?b$JNL U̿[ IW=Qj{ k4@؂H c #B>)v4U?.bQ>>l.yy>\'SЩ.g8yӚKՒ'ڝֱ/n~?~8FW yx%v kWO'm>)+xÆ?>9[̟Z o` '?:UmdF+w`N\&y#f{-ƎAS8 @{dW1x*ͨڿ~ד*TmΔ7(n)*sP CRT-%\8@F;2Հj7|B[Y)zCx1Y8h=z.m'^O˪=I~,lkocgǻ9N9=O2fNYl-"ohóCV|bkφQ/26TWr%vO 2.mJ?b o?6Iզ颌5ߒPajaU4@/c1(dB _sUW~rJqhAQ$sU߼|!}{ /G>qb%{cAwM>Oo򱐏~_'':꺪M}+svAWf=k!{1ݡְQ3SQqa?1xIQ OV ޤ؅Yf*Es< ?EQ^;XX+aωw6OSY(]m_lm̊bM?drbiSqUfkXyqiYTAdt y NSnS )6 Z뿎=#ѯnNr 6I}z_5U%H~ʳCni&(E9U+9ϝ{23U c2NPG]pAK澫6<Ñۉgx$6"0GTtR P(s@Hl^Sȯ_1T{m:xG,h%lwcEg ?R.s;2<}g1[yVM*j䗻Dh!O$Uo~|ۤ/<~\}sU=An&aչ=jHݏW+\j 2 cZGTm6(rV3 M}4`П9SK bWUՆ?~{g O5.ڣQ9.lT[{~:DIj?bY qO׫^anUoJ7~{fmurjT-y\ULU x\+m2mg FyfˏyzG_zKI]aR^_Uۈ}8k 7hW׭KөZyC'K.{bSU)&o魩"{VϫжO 2͓Y\ǺoO>ͽTX>;?VoP(s@LwO'ytT0eb>RwKsX9L/X->qד_ܵ0ɥOr:9(MAN$3_]߾_fEՖ#aP/|z{VՆf8m*\|jeg?0IėcTU-@}%xrϠjO?r09q ϯ7?5U:t#tPR:ya:  àk }_ a$_hA/ןN-T՞glɄ_ax_K[HAj?vSOja;.>VKE=ƶ~',0Mf1oaYTFyL?>ϗA`6k;vSrja5p޶{!P?pAEY]CP\) 0 -vbVZh O w&VQ~˵GJl-=13,gB%}\~$öJs2æ^j 2 ڱ2H '0S]h,)s_Ҙ'@:-8A⸷sʸy|ۮxzk~[KrEa }U {WU-<7 M29;x^"[UՙjIV8eWʐ|﷫ =e. S̑X|fTBU3TZEYE`Slf(F毛ޝKl&Oj 2 -Yjԏ(j𩚤J] N+Qܻ!Zj[S4#TM8V؂Z$tUI* @.NR Tl%@BT"@&UTlAU-OӷtGP53DakT-0xhU^iE p6'@T-O&FGwUt%jUO J#@,*%@cE ! PJ37(Uk ۳U2 ZI"@iZi#@Z0h rcSi) Zٞ% @Z5D j'yFD PJ33"NU64@ET@zTl@RT-@4@AHU64@YC=vFD P=K@J P(s@՞2?\OU64@0 76>]J#@,*%@¬!jUU+-4(U43mhU2 ZI"@iZٞ% @Z0h R$ U43mhU {WUO'@,*%@բEdyV~Rh PVig   àkTZh> p=@g  P)f Q>> from acme.motd.api import Message, MOTD >>> motd = MOTD(messages=[Message(author='Anon', text='Hello World!')]) >>> message = motd.motd() >>> print '"%s" - %s' % (message.text, message.author) "Hello World!" - Anon >>> Well, we had to get "Hello World" in there somewhere! The important point here is that this code is written without any knowledge of Envisage or extensibility whatsoever. It is just a small, reusable piece of code (albeit a very simple one). Not So Plain Ol' MOTD --------------------- Now lets look at the steps that we have to go through to use this code and turn it into an extensible, pluggable Envisage application. Create the main Application class ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ First of all, we need to create an object that represents the application itself. In Envisage, this can be any object that implements the |IApplication| interface, but is usually either an instance of the default |Application| class, or one derived from it. In the |MOTD| example, we create the class in the |MOTD run| module as follows:: application = Application( id = 'acme.motd', plugins = [CorePlugin(), MOTDPlugin(), SoftwareQuotesPlugin()] ) application.run() In this case, we use the simplest way to tell Envisage which plugins make up the application by passing them in explicitly. Envisage applications allow you to completely configure how plugins are located by setting the plugin manager. Envisage ships with two plugin managers by default, one that simply takes a list of plugins as in the above example, and one that finds all plugins offered via the 'envisage.plugins' entry point in Python Eggs. The 'acme.motd' plugin ~~~~~~~~~~~~~~~~~~~~~~ As shown above, the corresponding plugin implementation is in the |MOTDPlugin| class:: class MOTDPlugin(Plugin): """ The 'Message of the Day' plugin. This plugin simply prints the 'Message of the Day' to stdout. """ # The IDs of the extension points that this plugin offers. MESSAGES = 'acme.motd.messages' # The IDs of the extension points that this plugin contributes to. SERVICE_OFFERS = 'envisage.service_offers' #### 'IPlugin' interface ################################################## # The plugin's unique identifier. id = 'acme.motd' # The plugin's name (suitable for displaying to the user). name = 'MOTD' #### Extension points offered by this plugin ############################## # The messages extension point. # # Notice that we use the string name of the 'IMessage' interface rather # than actually importing it. This makes sure that the import only happens # when somebody actually gets the contributions to the extension point. messages = ExtensionPoint( List(Instance('acme.motd.api.IMessage')), id=MESSAGES, desc=""" This extension point allows you to contribute messages to the 'Message Of The Day'. """ ) #### Contributions to extension points made by this plugin ################ service_offers = List(contributes_to=SERVICE_OFFERS) def _service_offers_default(self): """ Trait initializer. """ # Register the protocol as a string containing the actual module path # (do not use a module path that goes via an 'api.py' file as this does # not match what Python thinks the module is!). This allows the service # to be looked up by passing either the exact same string, or the # actual protocol object itself. motd_service_offer = ServiceOffer( protocol = 'acme.motd.i_motd.IMOTD', factory = self._create_motd_service ) return [motd_service_offer] ########################################################################### # Private interface. ########################################################################### def _create_motd_service(self): """ Factory method for the 'MOTD' service. """ # Only do imports when you need to! This makes sure that the import # only happens when somebody needs an 'IMOTD' service. from motd import MOTD return MOTD(messages=self.messages) # This plugin does all of its work in this method which gets called when # the application has started all of its plugins. @on_trait_change('application:started') def _print_motd(self): """ Print the 'Message of the Day' to stdout! """ # Note that we always offer the service via its name, but look it up # via the actual protocol. from acme.motd.api import IMOTD # Lookup the MOTD service. motd = self.application.get_service(IMOTD) # Get the message of the day... message = motd.motd() # ... and print it. print '\n"%s"\n\n- %s' % (message.text, message.author) return Although it is obviously a bit of overkill, the example shows how we would take a |MOTD| object and register it a service for other parts of the application to use. Sadly, in this example, there are no other parts of the application, so we just lookup and use the service ourselves! The 'acme.motd.software_quotes' plugin ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ First of all, we have to create the messages that we want to add. Remember that when the |acme.motd| plugin advertised the extension point, it told us that every contribution had to implement the |IMessage| interface. Happily, there is a class that does just that already defined for us (|Message|) and so we create a simple module ('messages.py'_) and add our |Message| instances to it:: messages = [ ... Message( author = "Martin Fowler", text = "Any fool can write code that a computer can understand. Good" " programmers write code that humans can understand." ) Message( author = "Chet Hendrickson", text = "The rule is, 'Do the simplest thing that could possibly" " work', not the most stupid." ) ... ] Now we create a plugin for the |acme.motd.software_quotes| package and tell Envisage about the messages that we have just created:: class SoftwareQuotesPlugin(Plugin): """ The 'Software Quotes' plugin. """ #### 'IPlugin' interface ################################################## # The plugin's unique identifier. id = 'acme.motd.software_quotes' # The plugin's name (suitable for displaying to the user). name = 'Software Quotes' #### Contributions to extension points made by this plugin ################ # Messages for the 'Message Of The Day'. messages = List(contributes_to='acme.motd.messages') ########################################################################### # 'SoftwareQuotesPlugin' interface. ########################################################################### def _messages_default(self): """ Trait initializer. """ # Only do imports when you need to! from messages import messages return messages envisage-6.0.1/docs/source/envisage_core_documentation/message_of_the_day_(using_eggs).rst000066400000000000000000000304621406265421000322100ustar00rootroot00000000000000The "Message of the Day" (MOTD) Example ======================================= Only marginally more complicated than the traditional "Hello World" example, this is a simple application that prints a witty, educational, or inspiring "Message of the Day". Obviously, if we actually had to write this application, we might not choose to use a framework like Envisage (it is one of the rare applications that is so simple that why would you bother), but it does serve to illustrate all of the fundamental aspects of building an Envisage application. All of the code for this example can be found in the |Message of the Day| directory of the Envisage distribution. This directory contains two subdirectories: dist This directory contains the actual runnable application as it *might* actually be distributed/deployed (of course, the *actual* deployment is up to you). To run the application:: cd dist python run.py (or equivalent, depending on your operating system and shell) src This directory contains the source code for the eggs that make up the application. This is there to allow easy access to the example code, but would obviously not normally need to be deployed. Before we dive right in to building our extensible application, let's take a small, but important, step back. Envisage is designed to be an integration platform -- a place where you can bring existing code and with a (hopefully) minimal amount of effort, make it work with the rest of the application. Because of this, we will start the MOTD example by designing the application without any knowledge of Envisage whatsoever. This is obviously a good idea in general, as it allows our code to be reused outside of Envisage applications. Plain Ol' MOTD -------------- So lets take a look at our non-Envisage aware MOTD application, the code for which is in the |acme.motd| package. A good place to start as a developer *using* any package in Envisage (and, for that matter, the entire Enthought tool-suite) is to look at any interfaces and classes exposed via its 'api.py' module. In this case, there are 2 interfaces 1) |IMOTD| The interface for simple "Message of the Day" functionality. 2) |IMessage| The interface supported by each message returned by the motd() method on the |IMOTD| interface. We also (*not* coincidentally!) have 2 corresponding implementation classes: 1) |MOTD| 2) |Message| As you can see, the |MOTD| class simply contains a list of messages and when its motd() method is called, it returns a random choice from that list. An example of using our |MOTD| class at the Python prompt might be:: >>> from acme.motd.api import Message, MOTD >>> motd = MOTD(messages=[Message(author='Anon', text='Hello World!')]) >>> message = motd.motd() >>> print '"%s" - %s' % (message.text, message.author) "Hello World!" - Anon >>> Well, we had to get "Hello World" in there somewhere! The important point here is that this code is written without any knowledge of Envisage or extensibility whatsoever. It is just a small, reusable piece of code (albeit a very simple one). Not So Plain Ol' MOTD --------------------- Now lets look at the steps that we have to go through to use this code and turn it into an extensible, pluggable Envisage application. Create the main Application class ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ First of all, we need to create an object that represents the application itself. In Envisage, this can be any object that implements the |IApplication| interface, but is usually either an instance of the default |Application| class, or one derived from it. In the |MOTD| example, we create the class in the |MOTD run| module as follows:: def run(): """ The function that starts your application. """ # Create and run the application. from envisage.api import Application return Application(id='acme.motd').run() Note that the |MOTD run| file also contains some boilerplate code to add the application's `Python Eggs`_ to the ``sys.path``, but this is not specific to Envisage - that code would be required for any egg-based application. Create the 'acme.motd' plugin ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ This is the plugin that will deliver the "Message of the Day" functionality into the application. It will do this by declaring an extension point to allow other plugins to contribute messages, and by using contributions to create an instance of the |MOTD| class and to publish it as a service. By default, Envisage finds plugins via Python eggs, so all we have to do is to declare the existence of our plugin using the "envisage.plugins" entry point in our 'setup.py' module:: setup( ... entry_points = """ ... [envisage.plugins] acme.motd = acme.motd.motd_plugin:MOTDPlugin ... """ ) The left-hand-side of the 'acme.motd = acme.motd.motd_plugin:MOTDPlugin' line *must* be the same as the 'id' trait as specified in the 'MOTDPlugin' class - in this case 'acme.motd'. While this smacks of duplication, it allows plugin managers such as the 'EggPluginManager' to filter unwanted plugins by id without the need to import and instantiate them. Notice that we don't import the plugin from an 'api.py' module. This is to delay importing implementation code until it is actually needed. As showm above, the corresponding plugin implementation is in the |MOTDPlugin| class:: class MOTDPlugin(Plugin): """ The 'Message of the Day' plugin. This plugin simply prints the 'Message of the Day' to stdout. """ # The IDs of the extension points that this plugin offers. MESSAGES = 'acme.motd.messages' # The IDs of the extension points that this plugin contributes to. SERVICE_OFFERS = 'envisage.service_offers' #### 'IPlugin' interface ################################################## # The plugin's unique identifier. id = 'acme.motd' # The plugin's name (suitable for displaying to the user). name = 'MOTD' #### Extension points offered by this plugin ############################## # The messages extension point. # # Notice that we use the string name of the 'IMessage' interface rather # than actually importing it. This makes sure that the import only happens # when somebody actually gets the contributions to the extension point. messages = ExtensionPoint( List(Instance('acme.motd.api.IMessage')), id=MESSAGES, desc=""" This extension point allows you to contribute messages to the 'Message Of The Day'. """ ) #### Contributions to extension points made by this plugin ################ service_offers = List(contributes_to=SERVICE_OFFERS) def _service_offers_default(self): """ Trait initializer. """ # Register the protocol as a string containing the actual module path # (do not use a module path that goes via an 'api.py' file as this does # not match what Python thinks the module is!). This allows the service # to be looked up by passing either the exact same string, or the # actual protocol object itself. motd_service_offer = ServiceOffer( protocol = 'acme.motd.i_motd.IMOTD', factory = self._create_motd_service ) return [motd_service_offer] ########################################################################### # Private interface. ########################################################################### def _create_motd_service(self): """ Factory method for the 'MOTD' service. """ # Only do imports when you need to! This makes sure that the import # only happens when somebody needs an 'IMOTD' service. from motd import MOTD return MOTD(messages=self.messages) # This plugin does all of its work in this method which gets called when # the application has started all of its plugins. @on_trait_change('application:started') def _print_motd(self): """ Print the 'Message of the Day' to stdout! """ # Note that we always offer the service via its name, but look it up # via the actual protocol. from acme.motd.api import IMOTD # Lookup the MOTD service. motd = self.application.get_service(IMOTD) # Get the message of the day... message = motd.motd() # ... and print it. print '\n"%s"\n\n- %s' % (message.text, message.author) return Although it is obviously a bit of overkill, the example shows how we would take a |MOTD| object and register it a service for other parts of the application to use. Sadly, in this example, there are no other parts of the application, so we just lookup and use the service ourselves! Build the 'acme.motd' egg ~~~~~~~~~~~~~~~~~~~~~~~~~ To deploy the plugin into an application, we have to build it as an egg (this is only because we are using eggs as our deployment mechanism, if you do not want to use eggs then obviously you don't have to do any of this!):: cd .../examples/MOTD/src/acme.motd python setup.py bdist_egg --dist-dir ../../dist/eggs If we run the application now , we will be told to work hard and be good to our Mothers. Good advice indeed, but what it really shows is that we haven't yet contributed any messages to the application. Lets do this next. Create the 'acme.motd.software_quotes' plugin ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ First of all, we have to create the messages that we want to add. Remember that when the |acme.motd| plugin advertised the extension point, it told us that every contribution had to implement the |IMessage| interface. Happily, there is a class that does just that already defined for us (|Message|) and so we create a simple module ('messages.py'_) and add our |Message| instances to it:: messages = [ ... Message( author = "Martin Fowler", text = "Any fool can write code that a computer can understand. Good" " programmers write code that humans can understand." ) Message( author = "Chet Hendrickson", text = "The rule is, 'Do the simplest thing that could possibly" " work', not the most stupid." ) ... ] Now we create a plugin for the |acme.motd.software_quotes| package and tell Envisage about the messages that we have just created:: class SoftwareQuotesPlugin(Plugin): """ The 'Software Quotes' plugin. """ #### 'IPlugin' interface ################################################## # The plugin's unique identifier. id = 'acme.motd.software_quotes' # The plugin's name (suitable for displaying to the user). name = 'Software Quotes' #### Extension point contributions ######################################## # Messages for the 'Message Of The Day'. messages = List(contributes_to='acme.motd.messages') ########################################################################### # 'SoftwareQuotesPlugin' interface. ########################################################################### def _messages_default(self): """ Trait initializer. """ # Only do imports when you need to! from messages import messages return messages And finally we go to the 'setup.py' file for the |acme.motd.software_quotes| egg and tell Envisage about the plugin:: setup( entry_points = """ [envisage.plugins] acme.motd.software_quotes = acme.motd.software_quotes.software_quotes_plugin:SoftwareQuotesPlugin ... """ ) Build the 'acme.motd.software_quotes' egg ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ To deploy the plugin into an application, we have to build it as an egg:: cd .../examples/MOTD/src/acme.motd.software_quotes python setup.py bdist_egg --dist-dir ../../dist/eggs If we run the application now , we will (if all is well!) get a random, pithy quote about software development! To add more messages to the application in future, all we have to do is to create other plugins similar to the 'acme.motd.software_quotes' egg and drop them into the '.../examples/MOTD/dist/eggs' directory. We have successfully built our first extensible, pluggable application! .. _`Python Eggs`: http://peak.telecommunity.com/DevCenter/PythonEggs envisage-6.0.1/docs/source/envisage_core_documentation/plugins.rst000066400000000000000000000061531406265421000255310ustar00rootroot00000000000000Plugins ======= *Plugins* are used to "deliver" `Extension Points`_, contributions to extension points, and Services_ into an Envisage application. In fact, plugins can be thought of as simply being "delivery trucks" -- they rarely (if ever) do any *real* work themselves. In other words, plugins can do 3 things: 1) Declare extension points 2) Make contributions to extension points (including its own) 3) Create and publish services Plugins are located and managed by the *plugin manager*, and, whilst Envisage is designed such that you can write your own plugin manager, by default, it uses an implementation based on `Python Eggs`_. Creating a Plugin ----------------- All plugins must implement the |IPlugin| interface (an easy way to achieve this is to subclass the base |Plugin| class), and should provide the following "housekeeping" information: - a globally unique identifier (e.g., "acme.motd") This obviously allows Envisage to identify a particular plugin in a sea of other plugins. Technically, this identifier only need be unique within your application, but since the intent is for applications to be able to include plugins from (potentially) all over the world, using the common reverse domain name notation is probably a good idea! - a name This name should be suitable for display to the user. - a doc string! A nice description of the intent and features of your plugin. Here is a snippet from the |acme.motd| plugin that is part of the |Message of the Day| example:: class MOTDPlugin(Plugin): """ The 'Message of the Day' plugin. When this plugin is started it prints the 'Message of the Day' to stdout. """ #### 'IPlugin' interface ############################################## # The plugin's unique identifier. id = 'acme.motd' # The plugin's name (suitable for displaying to the user). name = 'MOTD' Plugin Lifecycle ---------------- Plugins have a lifecycle in the context of an application. There are currently two key lifecycle methods on the |IPlugin| interface:: 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) """ When Envisage starts up it calls the start() method of every plugin in the the same order that its iterator returns them in. This depends on the plugin manager being used, but by default this will be either a) the order of the list of plugins that was passed into the application or b) the order of the plugins based on egg dependencies. When the application stops, it calls the stop() method of each plugin in the reverse order that they were started in. .. _`Extension Points`: extension_points.html .. _`Python Eggs`: http://peak.telecommunity.com/DevCenter/PythonEggs .. _Services: services.html envisage-6.0.1/docs/source/envisage_core_documentation/preferences.rst000066400000000000000000000027741406265421000263560ustar00rootroot00000000000000 Preferences with Envisage ========================= Envisage presents preferences with two different extension points: * PREFERENCES = 'envisage.preferences' * PREFERENCES_PAGES = 'envisage.ui.workbench.preferences_pages' The first one is only model-related and is for programmatic access to preferences, whereas the second one is for displaying UIs to the user in the workbench plugin. Preferences ------------ The contribution point is simply a list of URLs to the preference file, e.g.:: preferences_pages = List( ['pkgfile://acme.acmelab/preferences.ini'], contributes_to=PREFERENCES_PAGES) where acme.acmelab is the python-module-like path to the package in which the default preferences.ini is stored. A plugin usually needs only one preferences file, regardless of how many preference pages or settings it has. Preferences pages ------------------ The preference pages are a Traits UI view to wrap the preferences and allow the user to modify them. A preference page is defined as in the preference_manager example in the AppTools examples. It can than be contributed to the workbench, as in:: preferences_pages = List(contributes_to=PREFERENCES_PAGES) def _preferences_pages_default(self): """ Trait initializer. """ from acme.preference_pages \ import ACMEPreferencePages return [ACMEPreferencePages, ] A plugin needs to contribute a preferences pages class for each category of preferences it contributes. envisage-6.0.1/docs/source/envisage_core_documentation/services.rst000066400000000000000000000257041406265421000256760ustar00rootroot00000000000000Services ======== Services are the real "guts" of an Envisage application -- they are the objects that do the actual work! To get the job done, services obviously need to interact, and so Envisage provides a way for them to find each other. This is where the *service registry* comes in. Don't be fazed by the term *service*. In Envisage it just means any objects in your application that you want to share between plugins. Services can be any Python object and do not have to inherit from any Envisage class or even implement any particular interface! Service Registry ================ The service registry provides a "Yellow Pages" style mechanism, in that services are published and looked up by *protocol* meaning *interface*, or *type*. 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)! In an Envisage application, the service registry is accessed through the following methods on the |IApplication| interface:: def get_service(self, protocol, query='', minimimize='', maximize=''): """ Return at most one service that matches the specified query. """ def get_service_properties(self, service_id): """ Return the dictionary of properties associated with a service. """ def get_services(self, protocol, query='', minimimize='', maximize=''): """ Return all services that match the specified query. """ def register_service(self, protocol, obj, properties=None): """ Register a service. Returns a service ID that can be used to retrieve any service properties, and to unregister the service. """ def unregister_service(self, service_id): """ Unregister a service. """ The easiest way to explain the workings of these methods is to take a look at some examples, and so to continue our plumber theme, let's assume we have the following interface and implementation:: class IPlumber(Interface): """ What plumbers do! """ # The plumber's name. name = Str # The plumber's location. location = Str # The price per hour (in say, Estonian Krooni ;^) price = Int def fix_leaking_pipe(self, pipe): """ Fix a leaking pipe! """ @provides(IPlumber) class Plumber(HasTraits): """ An actual plumber implementation! """ # The plumber's name. name = Str # The plumber's location. location = Str # The price per hour (in say, Estonian Krooni ;^) price = Int def fix_leaking_pipe(self, pipe): """ Fix a leaking pipe! """ ... code that actually fixes it! ... Registering a service --------------------- To register a service, create an object and call the register_service() method, passing it the protocol (interface or type) to publish the object under (for "protocol" think "Yellow Pages" section), and the object to publish. Note that the object to publish does *not* have to inherit from any particular base class or implement any special interface -- any arbitrary Python object will do:: fred = Plumber(name='fred', location='BH1', price=90) fred_id = application.register_service(IPlumber, fred) Note that each registered service gets assigned an ID that is unique within the current process. This can be used later to access its properties, or to unregister it, etc. You can also associate an arbitrary dictionary of properties with an object when you register it. These properties, along with the actual attributes of the service itself, can be used later to lookup the service using the query mechanism as shown in Section 4. :: wilma = Plumber(name='wilma', location='BH6') wilma_id = application.register_service(IPlumber, wilma, {'price' : 125}) Note that the symbol name of the protocol can be specified instead of the actual type or class. Say, for example, that the *IPlumber* interface can be imported via 'from acme.plumber.api import IPlumber', then the registration can be written as:: wilma_id = application.register_service('acme.plumber.api.IPlumber', wilma, {'price' : 125}) This comes in handy when using service factories (see later) to make sure that implementation classes are imported only when necessary. Looking up a service -------------------- Looking up a service is just as easy -- call get_service() specifiying the protocol of the service required:: plumber = application.get_service(IPlumber) Assuming that we have registered both *fred* and *wilma* as in Section 1, then there is no way of knowing which of those objects would be returned. The choice of the object returned does *not* necessarily reflect the order in which they were added, so don't depend on it. Note that the symbol name of the protocol can be specified instead of the actual type or class. Say, for example, that the *IPlumber* interface can be imported via 'from acme.plumber.api import IPlumber', then the service lookup can be written as:: plumber = application.get_service('acme.plumber.api.IPlumber') This comes in handy when using service factories (see later) to make sure that implementation classes are imported only when necessary. Looking up a list of services ----------------------------- You can also look up *all* services of a particular protocol:: plumbers = application.get_services(IPlumber) Assuming the registrations in Section 1, this returns a list containing both *fred* and *wilma*, again in arbitrary order. Using queries ------------- The get_service() and get_services() methods both take optional arguments that allow more control over the selection of an appropriate service. The first of these is the *query* argument, which is a string containing an arbitrary Python expression that is evaluated for each service, with the service only being returned if the expression evaluates to True. The namespace that the expression is evaluated in is created by first adding each of the service's attributes, followed by any additional properties that were specified when the service was registered (i.e., properties take precedence over attributes). Once again, assuming that we have registered *fred* and *wilma* as in Section 1, let's look at how to use the query mechanism to be more selective about the plumber(s) we look up. Find all plumbers whose price is less than 100 Krooni/Hour:: plumbers = application.get_services(IPlumber, "price < 100") This query would return a list containing one plumber, *fred*. Find plumbers named *fred*:: plumbers = application.get_services(IPlumber, "name == 'fred'") This query, again (and unsurprisingly), would return a list containing just *fred*. Queries can be used with the singular form of the get_service() method too, in which case only one of the services that matches the query is returned:: plumber = application.get_service(IPlumber, "price < 200") This query would return *either* *fred* or *wilma*. Using *minimize* and *maximize* ------------------------------- The *minimize* and *maximize* (optional) arguments to the get_service() and get_services() methods allow the services returned to be sorted by an attribute or property in either ascending or descending order respectively. To find the cheapest plumber:: cheapest = application.get_service(IPlumber, minimize='price') Or, if you believe that you get what you pay for, the most expensive:: most_expensive = application.get_service(IPlumber, maximize='price') The *minimize* and *maximize* arguments can also be used in conjunction with a query. For example to find the cheapest plumber in my area:: cheap_and_local = application.get_service(IPlumber, "location='BH6'", minimize='price') This query would definitely give the job to *wilma*! Unregistering a service ----------------------- When you register a service, Envisage returns a value that uniquely identifies the service within the current process (i.e., it is not suitable for persisting to use next time the application is run). To unregister a service, call the unregister_service() method, passing in the appropriate identifier:: fred = Plumber(name='fred', location='BH1', price=90) fred_id = application.register_service(IPlumber, fred) ... application.unregister_service(fred_id) Getting any additional service properties ----------------------------------------- If you associate an arbitrary dictionary of properties with an object when you register it, you can retrieve those properties by calling the get_service_properties() method with the appropriate service identifier:: wilma = Plumber(name='wilma', location='BH6') wilma_id = application.register_service(IPlumber, wilma, {'price':125}) ... properties = application.get_service_properties(wilma_id) This call would return a dictionary containing the following:: {'price' : 125} To set the properties for a service that has already been registered, use:: wilma = Plumber(name='wilma', location='BH6') wilma_id = application.register_service(IPlumber, wilma, {'price':125}) ... application.set_service_properties(wilma_id, {'price' : 150}) Note however, that in practise, it is more common to use the actual attributes of a service object for the purposes of querying, but this is useful if you want additional properties that aren't part of the object's type. Service Factories ----------------- Last, but not least, we will look at an important feature of the service registry, namely, service factories. Service factories allow a Python callable to be registered in place of an actual service object. The callable is invoked the first time anybody asks for a service with the same type that the factory was registered against, and the object returned by the callable replaces the factory in the registry (so that the next time it is asked for it is simply returned as normal). To register a service factory, just register any callable that takes two arguments. The first is the protocol (type) of the service being requested, and the second is the (possibly empty) dictionary of properties that were registered along with the factory, e.g.:: def wilma_factory(protocol, properties): """ A service factory that creates wilma the plumber! """ return Plumber(name='wilma', location='BH6') To register the factory, we just use 'application.register_service' as usual:: wilma_id = application.register_service(IPlumber, wilma_factory, {'price':125}) Now, the first time somebody tries to get any 'IPlumber' service, the factory is called and the returned plumber object replaces the factory in the registry. envisage-6.0.1/docs/source/envisage_core_documentation/workbench.rst000066400000000000000000000040561406265421000260320ustar00rootroot00000000000000Workbench ========= The workbench plugin, found in the :mod:`envisage.ui.workbench.api` package, provides a style of user interface that is often (but not exclusively) found in integrated development environments (IDEs). Note that this does not mean that all of your user interfaces must fit this pattern -- just that if they do, then we have done a lot of the work for you. Workbench user interfaces are based on 3 simple concepts: 1) Views Views are primarily used to present information to the user to help them perform their current task. In an IDE application, views might be: - file tree - class outlines 2) Editors Editors allow the user to manipulate data and objects to perform their current task. Editors are really the focus of users attention with the views used to provide supporting information. Editors are grouped together geographically in what is known as the *editor area*. In an IDE application, editors would contain the source code that a developer is currently working on. 3) Perspectives A perspective is a particular grouping of views (usually around the editor area) that correspond to a user task. For example, in an IDE, I might have the following perspectives: - Coding perspective This is the perspective that the user (in this case a developer) would be in when they are actually writing the code. It might contain views that show the files in the current project, the outline of the current class etc, and the editors would contain the source code that they are actually working on. - Debugging perspective In this perspective, the user would still see the source code in the editors, but the views might show things like breakpoints, variable watches, stack information etc. In keeping with the Envisage philosophy of making code as reuseable as possible, the workbench plugin is just a thin layer over the Pyface Workbench widget to allow views, editors and perspectives to be contributed via plugins. .. toctree:: :maxdepth: 2 preferences.rst envisage-6.0.1/docs/source/index.rst000066400000000000000000000003561406265421000174140ustar00rootroot00000000000000Envisage Documentation ====================== .. toctree:: :maxdepth: 2 :glob: envisage_core_documentation/index tasks_user_manual/index api Indices and tables ================== * :ref:`genindex` * :ref:`search` envisage-6.0.1/docs/source/tasks_user_manual/000077500000000000000000000000001406265421000212675ustar00rootroot00000000000000envisage-6.0.1/docs/source/tasks_user_manual/extensibility.rst000066400000000000000000000424441406265421000247250ustar00rootroot00000000000000.. _extensibility: =============== Extensibility =============== .. index:: Envisage The foregoing sections have described those elements of the Tasks framework that belong to the Pyface project; as such, our imports have been from the ``pyface.tasks`` package. We now discuss how Tasks can be used in conjunction with Envisage to build extensible applications. Accordingly, our imports in this section will be from the ``envisage.ui.tasks`` package. As remarked in the :ref:`introduction`, some familiarity with the Envisage plugin framework is assumed. For more information about Envisage, the reader is referred to the :doc:`../envisage_core_documentation/index`. .. _tasks-plugin: The Tasks Plugin ================ In creating an extensible Tasks application, we imagine two primary extensibility use cases: 1. **Contributing to an existing task:** In this case, the user wishes to add some combination of dock panes, menu items, and tool bar buttons to an existing task. In other words, the user would like to extend a task's functionality without changing its fundamental purpose. 2. **Contributing a new task:** Alternatively, the user may find that the none of the existing tasks are suitable for the functonality that he or she wishes to implement. Thus the user decides to contribute an entirely new task. The Tasks plugin has an extension point corresponding to each of these two use cases. These extensions points are: .. index:: TaskFactory 1. ``envisage.ui.tasks.tasks``: A list of ``TaskFactory`` instances. ``TaskFactory`` is a lightweight class for associating a task factory with a name and an ID. We shall see an example of its use in the following subsection. .. index:: TaskExtension 2. ``envisage.ui.tasks.task_extensions``: A list of ``TaskExtension`` instances. A ``TaskExtension`` is a bundle of menu bar, tool bar, and dock pane additions to an existing task. This class is discussed in detail in the subsection on :ref:`extending-a-task`. The Tasks plugin also provides two extensions points that permit the creation of extensible preferences dialogs. We defer discussion of this functionality to the subsection on :ref:`creating-a-preferences-dialog`. .. _tasks-applications: .. index:: application, TasksApplication Creating a Tasks Application ============================ Let us imagine that we are building a (slightly whimsical) application for visualizing `strange attractors `_, in two and three dimensions [1]_. We take for granted the existence of tasks for performing each of these two kinds of visualization. Like any Envisage application, our application will contain a ``Plugin`` instance to expose functionality to other plugins. In this case, we will contribute our two tasks to the Tasks plugin. Because the Tasks plugin is responsible for creating tasks and the windows that contain them, it expects to receive *factories* for creating ``Task`` instances rather than the instances themselves. The ``TaskFactory`` class fulfills this role. With this in mind, we can define a ``Plugin`` for our application:: class AttractorsPlugin(Plugin): #### 'IPlugin' interface ############################################## # The plugin's unique identifier. id = 'example.attractors' # The plugin's name (suitable for displaying to the user). name = 'Attractors' #### Contributions to extension points made by this plugin ############ tasks = List(contributes_to='envisage.ui.tasks.tasks') def _tasks_default(self): return [ TaskFactory(id = 'example.attractors.task_2d', name = '2D Visualization', factory = Visualize2dTask), TaskFactory(id = 'example.attractors.task_3d', name = '3D Visualization', factory = Visualize3dTask) ] .. index:: application; layout, TaskWindowLayout Having contributed tasks to the Tasks plugin, we must now specify how the tasks shall be added to windows to constitute our application. We call this specification the *application-level layout* to distinguish it from the lower-level layout attached to a task. Concretely, an application-level layout consists of a set of ``TaskWindowLayout`` objects, each of which indicates which tasks are attached to the window, which task is active in the window, and, optionally, the size and position of the window. The default application-level layout is defined inside our application class, which must inherit ``TasksApplication``:: class AttractorsApplication(TasksApplication): #### 'IApplication' interface ######################################### # The application's globally unique identifier. id = 'example.attractors' # The application's user-visible name. name = 'Attractors' #### 'TasksApplication' interface ##################################### # The default application-level layout for the application. default_layout = [ TaskWindowLayout('example.attractors.task_2d', 'example.attractors.task_3d', size=(800, 600)) ] Observe that each of the IDs specified in the layout must correspond to the ID of a ``TaskFactory`` that has been contributed to the Tasks plugin. Also note that the ``TaskWindowLayout`` class has an ``active_task`` attribute; by omitting it, we indicate that the first task in the task list is to be active by default. .. index:: application; state restoration By default, the Tasks framework will restore application-level layout when the application is restarted. That is, the set of windows and tasks attached to those windows that is extant when the application exits will be restored when the application is started again. If, however, the ``always_use_default_layout`` attribute of the application is set, the default application layout will be applied when the application is restarted. Tasks will still attempt to restore as much user interface state as possible, including window positions and task layouts. This setting is particularly useful for multi-window applications. Apart from this functionality, the Tasks plugin provides no additional *default* behavior for managing tasks and their windows, permitting users to switch tasks within a window, etc. This is to be expected, as these behaviors are fundamentally application-specific. That said, we shall see in :ref:`global-task-extensions` that the Tasks plugins provides a few built-in extensions for implementing common behaviors. .. _creating-a-preferences-dialog: Creating a Preferences Dialog ============================= .. index:: preferences There are three extensions points associated with preferences. One of these extension points is built into the Envisage core plugin, while the other two belong to the Tasks plugin. Let us survey each of them in turn. 1. ``envisage.preferences``: A list of locators for default preferences files (INI files). This extension point is at the model level in the preferences system. .. index:: preferences; category 2. ``envisage.ui.tasks.preferences_categories``: A list of ``PreferencesCategory`` instances. Preference categories have name and ID attributes. To each category with a given name corresponds a tab with that name in the preferences dialog, unless there is only a single category, in which the case the tab bar will not be shown. .. index:: preferences; pane 3. ``envisage.ui.tasks.preferences_panes``: A list of ``PreferencesPane`` instances. A preferences pane defines a set of user interface elements for changing application preferences via a model object called a ``PreferencesHelper``. A preferences pane has a name and an ID, as well as a ``category`` attribute for specifying the ID of the category to which it belongs. Preferences panes are stacked vertically among the other panes in their category. By default, the category of a pane is "General". As a convenience, if a category with the specified ID does not exist, it will be created automatically. Note that both preference panes and categories have ``before`` and ``after`` attributes for specifying their order, if this is necessary. See the next subsection for more information about this idiom. We shall now expand the example from the previous subsection by adding a preferences dialog for changing the default task and the application-level state restoration behavior. By doing so, we shall see concretely how to use the preferences system in Tasks, as well as reinforce our knowledge about application-level layout. We begin by defining "preferences.ini", our default preferences file:: [example.attractors] default_task = example.attractors.task_2d always_use_default_layout = False and contributing it to the Envisage core plugin:: class AttractorsPlugin(Plugin): [ ... ] preferences = List(contributes_to='envisage.preferences') def _preferences_default(self): return [ 'pkgfile://example.attractors/preferences.ini' ] This construction assumes that the attractors example is in Python's path (in the ``example.attractors`` package). Alternatively, we could have used the "file://" prefix in conjunction with an absolute path on the local filesystem. We can now define two classes: a preferences helper and preferences pane. The preferences helper is a model-level class that makes accessing the keys in the preferences file convenient and type safe. The preferences pane, introduced above, exposes a Traits UI view for this helper object:: from envisage.ui.tasks.api import PreferencesPane, TaskFactory from apptools.preferences.api import PreferencesHelper class AttractorsPreferences(PreferencesHelper): #### 'PreferencesHelper' interface #################################### # The path to the preference node that contains the preferences. # Notice that this corresponds to the section header in our preferences # file above. preferences_path = 'example.attractors' #### Preferences ###################################################### default_task = Str always_use_default_layout = Bool class AttractorsPreferencesPane(PreferencesPane): #### 'PreferencesPane' interface ###################################### # The factory to use for creating the preferences model object. model_factory = AttractorsPreferences #### 'AttractorsPreferencesPane' interface ############################ task_map = Dict(Str, Str) # Notice that the default context for trait names is that of the model # object, and that we must prefix names for this object with 'handler.'. view = View(Group(Item('always_use_default_layout'), Item('default_task', editor = EnumEditor(name='handler.task_map'), enabled_when = 'always_use_default_layout'), label='Application startup'), resizable=True) def _task_map_default(self): return dict((factory.id, factory.name) for factory in self.dialog.application.task_factories) Finally, we modify our application to make use of this new functionality:: class AttractorsApplication(TasksApplication): [ ... ] #### 'TasksApplication' interface ##################################### default_layout = List(TaskWindowLayout) always_use_default_layout = Property(Bool) #### 'AttractorsApplication' interface ################################ preferences_helper = Instance(AttractorsPreferences) def _default_layout_default(self): active_task = self.preferences_helper.default_task tasks = [ factory.id for factory in self.task_factories ] return [ TaskWindowLayout(*tasks, active_task = active_task, size = (800, 600)) ] def _get_always_use_default_layout(self): return self.preferences_helper.always_use_default_layout def _preferences_helper_default(self): return AttractorsPreferences(preferences = self.preferences) and contribute the preferences pane to the Tasks plugin:: class AttractorsPlugin(Plugin): [ ... ] preferences_panes = List( contributes_to='envisage.ui.tasks.preferences_panes') def _preferences_panes_default(self): return [ AttractorsPreferencesPane ] .. _extending-a-task: Extending an Existing Task ========================== Contributions are made to an existing task via the ``TaskExtension`` class, which was briefly introduced above. ``TaskExtension`` is a simple class with three attributes: 1. ``task_id``: The ID of the task to extend. 2. ``actions``: A list of ``SchemaAddition`` objects. 3. ``dock_pane_factories``: A list of callables for creating dock panes. .. index:: SchemaAddition The second attributes requires further discussion. In the previous section, we remarked that a task's menu and tool bars are defined using schemas; the ``SchemaAddition`` class provides a mechanism for inserting new items into these schemas. .. index:: path A schema implicitly defines a *path* for each of its elements. For example, in the schema:: SMenuBar(SMenu(SGroup([ ... ], id = 'SaveGroup'), [ ... ], id = 'File', name = '&File), SMenu([ ... ], id = 'Edit', name = '&Edit')) the edit menu has the path "MenuBar/Edit". Likewise, the save group in the file menu has the path "MenuBar/File/SaveGroup". We might define an addition for this menu as follows:: SchemaAddition(factory = MyContributedGroup, path = 'MenuBar/File') where ``factory`` is a callable that produces either a schema or an object from the Pyface action API [2]_. A schema addition that produces a schema can in turn be extended by another schema addition. If it produces a Pyface object, it cannot be further extended. In this case we have opted for latter, using a custom subclass of ``Group``. .. index:: before, after The group created from the schema addition above would be inserted at the bottom of the file menu. The ``SchemaAddition`` class provides two further attributes for specifying with greater precision the location of the insertion: ``before`` and ``after``. Setting one of these attributes to the ID of a schema with the same path ensures that the insertion will be made before or after, respectively, that schema. For example, in the expanded addition:: SchemaAddition(factory = MyContributedGroup, before = 'SaveGroup', path = 'MenuBar/File') the created group would be inserted before the save group. If both ``before`` and ``after`` are set, Tasks will attempt to honor both of them [3]_. In the event that Tasks cannot, the menu order is undefined (although the insertions are guaranteed to made) and an error is logged. .. _global-task-extensions: Global Task Extensions ======================= .. index:: TaskExtension; global When creating an application with several tasks it is frequently the case that certain menu bar or tool bar actions should be present in all tasks. Such actions might include an "Exit" item in the "File" menu or an "About" item in the "Help" menu. One can, of course, include these items in the schemas of each task; indeed, if the actions require task-specific behavior, this is the only reasonable approach to take. But for actions that are truly global in nature Tasks provides an alternative that may be more convenient. To create a ``TaskExtension`` that applies to all tasks, simply omit the ``task_id`` attribute. Tasks itself contributes a global task extension with the following menu items: - A group of actions in the menu with ID "View" for toggling the visibility of dock panes (see ``pyface.tasks.action.api.DockPaneToggleGroup``) - A "Preferences" action in the menu with ID "Edit", if the application has any preferences panes - An "Exit" action in the menu with ID "File" The user is free to supplement these items by contributing additional global task extensions. For example, to provide a simple mechanism for changing tasks, one might add include the built-in task switching group in the "View" menu, either at the toplevel or as a sub-menu (see ``pyface.tasks.action.api.TaskToggleGroup``). For switching between windows, Tasks includes the ``TaskWindowToggleGroup``. This class, as well as several other menu-related conveniences, can be found in ``envisage.ui.tasks.action.api``. .. rubric:: Footnotes .. [1] In this section, we shall be referencing--often with considerable simplification--the Attractors example code in the EnvisagePlugins package, available :github-demo:`online ` and in the ETS distribution. .. [2] Although they are expanded into Pyface action items, schemas belong to a distinct API. It is beyond the scope of this document to describe the Pyface action API. For more complete documentation, the reader is referred to the `Pyface documentation `_. .. [3] Tasks differs from the Workbench in this regard. envisage-6.0.1/docs/source/tasks_user_manual/front.rst000066400000000000000000000033151406265421000231530ustar00rootroot00000000000000==================== Front Matter ==================== :Authors: Evan Patterson :Version: Document Version 1 :Copyright: 2011 Enthought, Inc. All Rights Reserved. Redistribution and use of this document in source and derived forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source or derived format (for example, Portable Document Format or Hypertext Markup Language) must retain the above copyright notice, this list of conditions and the following disclaimer. * Neither the name of Enthought, Inc., nor the names of contributors may be used to endorse or promote products derived from this document without specific prior written permission. THIS DOCUMENT IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS DOCUMENT, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. All trademarks and registered trademarks are the property of their respective owners. | Enthought, Inc. | 515 Congress Avenue | Suite 2100 | Austin TX 78701 | 1.512.536.1057 (voice) | 1.512.536.1059 (fax) | http://www.enthought.com | info@enthought.com envisage-6.0.1/docs/source/tasks_user_manual/index.rst000066400000000000000000000002231406265421000231250ustar00rootroot00000000000000Tasks User Manual ================= .. toctree:: :maxdepth: 3 front.rst intro.rst layout.rst menus.rst extensibility.rst envisage-6.0.1/docs/source/tasks_user_manual/intro.rst000066400000000000000000000102361406265421000231560ustar00rootroot00000000000000.. _introduction: ============== Introduction ============== Tasks is an extensible, toolkit-independent framework for building scriptable, task-oriented user interfaces. This document describes its concepts and design principles, surveys the classes in its API, and provides a brief tutorial illustrating its use. We assume that the reader has a basic familiarity with the concepts of Traits and Traits UI. These packages are well documented in their respective user manuals. In the :ref:`extensibility` section of this document, some additional knowledge of the Envisage plugin framework is assumed. For more detailed information concerning the Tasks API, the reader is referred to the Tasks API Reference. What is a Task? --------------- .. index:: task, pane .. _what-is-a-task: For the purposes of this document, a *task* is a collection of user interface elements, called *panes*, which are present in a single window, unified by a specific purpose, and possessed of a certain structure. In addition, a task may provide menus, toolbars, and configuration options appropriate for these elements. .. index:: central pane At the heart of every task is its *central pane*, which exposes its core functionality. The central pane is always visible and occupies the center of the window. For example, in an IDE there will be at least one task concerned with writing code. A code editing widget would constitute the central pane for this task. If the IDE also provided a GUI construction task (i.e., a WYSIWYG user interface builder ala Glade or Qt Designer), the central pane would consist of the "canvas" upon which the user arranges UI elements. .. index:: dock pane In addition to the central pane, a task may include any number of subsidiary panes. These panes are arranged around the central pane in various *dock areas*, for which reason they are called *dock panes*. Dock panes provide functionality that is relevant but unessential to the task at hand. For example, in the code editing task described above, the list of dock panes might include a file browser, a context-sensitive documentation window, a compilation log, and a debugger. In general, dock panes can be moved from one dock area to another, can be made visible or hidden, and can be detached from the main window. Historical Background --------------------- .. index:: background, Workbench .. _historical-background: Many of the ideas behind the Tasks plugin originate in another Envisage plugin, called the Workbench (which, in turn, took considerable inspiration from Eclipse). While the Workbench is useful for creating IDE-style applications---it was designed for this purpose---there is a large class of applications for which it is too inflexible. Significant issues include: - **A lack of distinction between the semantics of UI elements and the layout of those elements.** A perspective (the Workbench analogue of a task) has very little responsibility besides saving the state the user interface. Views (the analogues of dock panes) are added directly to the Workbench, which means that they cannot be restricted to certain perspectives, nor can a perspective exercise meaningful control over the layout of its views. Furthermore, since there is no structure imposed on views by the active perspective, there is no mechanism for coupling UI components, if this becomes necessary. Finally, since the application menus are not connected to perspectives, it is very difficult to maintain multiple sets of menus over the application lifecycle. - **A non-customizable central pane.** The notion of editors is inextricably connected to the Workbench; the central pane must be a notebook-style collection of editors. The above are design considerations that could not be remedied without a vast degree of backwards-incompatible change. Less systemic deficiencies of the Workbench include: - **A lack of robust support for multi-window applications.** The Workbench does not correctly persist layout state for multiple windows. - **An inflexible API for exposing user-configurable preferences.** The preferences dialog does not permit fine-grained layout of UI elements. Tasks has been designed specifically to address these issues. envisage-6.0.1/docs/source/tasks_user_manual/layout.rst000066400000000000000000000225661406265421000233510ustar00rootroot00000000000000.. _layout: ======================= Window Layout ======================= First and foremost, the Tasks plugin is designed to facilitate the layout of modern, customizable user interfaces. Accordingly, we shall begin by learning how to use Tasks to perform basic window layout. .. index:: task; lifecycle Lifecycle of a Task ------------------- .. index:: model-view-controller pattern, MVC The Tasks plugin adheres to the model-view-controller (MVC) design pattern [1]_, for which the ``Task`` class functions as the controller and the ``TaskWindow`` class as the view, with the model being application-specific [2]_. Insofar as a ``Task`` is a controller, it does not itself instantiate or contain any GUI controls, although it does provide factories and a default layout for the controls that it manages. Controls are instantiated when a ``Task`` is assigned to a ``TaskWindow``. Specifically, the following actions are taken at this time: 1. The task's central pane is constructed and attached to the window 2. All of the task's dock panes are constructed, even if they are not by default visible 3. The visible dock panes are laid out around the central pane according to the task's default layout or persisted UI state information While the ``Task`` remains connected to the ``TaskWindow``, neither its central pane nor its dock panes will be destroyed. If the user "closes" a dock pane, the pane will be merely hidden, and can be re-activated via the application's View menu. Only when the ``Task`` itself is closed---that is, when it is removed from the ``TaskWindow``---will its panes be destroyed. This marks the end of the ``Task``'s lifecycle. If the task is to be re-used, a new instance should be constructed. .. index:: task; defining Defining a Task --------------- Minimally, a task is defined by subclassing ``Task`` and providing a central pane. For example, we define a task for editing Python scripts:: from pyface.tasks.api import Task class ExampleTask(Task): id = 'example.example_task' name = 'Python Script Editor' def create_central_pane(self): return PythonEditorPane() .. index:: ID, name The ``id`` attribute is a unique internal identifier for the task, whereas ``name`` is a user-visible descriptor for the task. We shall see that this is a common pattern in the Tasks framework. .. index:: central pane; defining All tasks must implement ``create_central_pane()`` and return a TaskPane instance. We might define the ``PythonEditorPane`` pane above as follows, making use of the ``PythonEditor`` available in Pyface:: from pyface.api import PythonEditor from pyface.tasks.api import TaskPane from traits.api import Instance class PythonEditorPane(TaskPane): id = 'example.python_editor_pane' name = 'Python Editor' editor = Instance(PythonEditor) def create(self, parent): self.editor = PythonEditor(parent) self.control = self.editor.control def destroy(self): self.editor.destroy() self.control = self.editor = None Besides providing an ID and a name for the pane, we implement the two basic methods of ``TaskPane``, namely ``create(parent)`` and ``destroy()``. The former constructs a toolkit-specific control for the pane and assigns its to the pane's ``control`` attribute. The latter performs the inverse operation, destroying the control and clearing ``control`` attribute. These methods give one full control over how a pane is constructed, but as we shall see below there are other, more convenient methods for defining a pane. .. index:: dock pane; defining Defining a Dock Pane -------------------- Now we imagine that we are building a very primitive Python IDE and that we would like to add a dock pane for browsing the local filesystem. We could create a ``DockPane`` subclass similarly to the ``TaskPane`` above, implementing the ``create_contents(parent)`` method of ``DockPane`` to provide the toolkit-specific control for the file browser. But if we are familiar with Traits UI we see that it would be more convenient to use the Traits UI ``FileEditor`` for this purpose. The Tasks framework provides the ``TraitsDockPane`` class to facilitate this. We define the pane as follows:: from pyface.tasks.api import TraitsDockPane from traits.api import Event, File, List, Str from traitsui.api import View, Item, FileEditor class FileBrowserPane(TraitsDockPane): #### TaskPane interface ############################################### id = 'example.file_browser_pane' name = 'File Browser' #### FileBrowserPane interface ######################################## # Fired when a file is double-clicked. activated = Event # The list of wildcard filters for filenames. filters = List(Str) # The currently selected file. selected_file = File # The view used to construct the dock pane's widget. view = View(Item('selected_file', editor=FileEditor(dclick_name='activated', filter_name='filters'), style='custom', show_label=False), resizable=True) When a control is needed for the pane, it will be constructed using the standard Traits UI mechanisms. There exist additional options, not described here, for specifying a model object, which is often important when building a complex application. There is also a ``TraitsTaskPane`` class that provides similar functionality for defining Traits-based central panes. As always, the reader is referred to the Tasks API documentation for more information. Now let us amend the example task defined above with a ``create_dock_panes()`` method. This method returns the list of dock pane instances associated with the task. We also define a method on our task for opening a file in the editor, which we connect to the dock pane's ``activated`` event:: class ExampleTask(Task): [ ... ] def create_dock_panes(self): """ Create the file browser and connect to its double click event. """ browser = PythonScriptBrowserPane() handler = lambda: self.open_file(browser.selected_file) browser.on_trait_change(handler, 'activated') return [ browser ] def open_file(self, filename): """ Open the file with the specified path in the central pane. """ self.window.central_pane.editor.path = filename .. index:: task; layout, PaneItem, TaskLayout, Tabbed, Splitter Providing a Default Layout -------------------------- Although dock panes are designed to be moved around and otherwise manipulated by the user, we often have a particular default layout in mind when designing an application. The Tasks framework provides the ``TaskLayout`` class to make the specification of this layout possible. Usually, we are only concerned with four attributes of this class, namely ``left``, ``right``, ``bottom``, and ``top``. Each of these attributes may be assigned a layout item, which is either a ``PaneItem``, for specifying a particular dock pane; a ``Tabbed`` item, containing other ``PaneItem`` instances; or a ``Splitter``, containing arbitrary subitems. A few examples should suffice to make this clear. To stack the dock pane with ID 'dock_pane_1' on top of that with ID 'dock_pane_2', with both to the left of the central pane, one specifies:: left = Splitter(PaneItem('dock_pane_1'), PaneItem('dock_pane_2'), orientation='vertical') .. index:: HSplitter, VSplitter We could also have used ``VSplitter``, which is a convenient abbreviation for a splitter with vertical orientation. Similarly, ``HSplitter`` is an abbrevation for a splitter with horizontal orientation. To put these dock panes in tab group below the central pane, we might write:: bottom_panes = Tabbed(PaneItem('dock_pane_1', height=400), PaneItem('dock_pane_2')) Observe that we have explicitly provided a height for the first dock pane. Provided that the dock pane's underlying control does not have a conflicting minimum or maximum size constraint, Tasks guarantees that it will honor this height exactly. Of course, if ``width`` or ``height`` are not provided, Tasks will use the dock pane's toolkit-specific size hint to determine its size. Now we will provide our example task with a simple layout using the ``default_layout`` attribute of ``Task``:: class ExampleTask(Task): [ ... ] default_layout = TaskLayout( left=PaneItem('example.python_script_browser_pane')) Note that dock panes that do not appear in the layout will not be visible by default. A task without a default layout is equivalent to a task with an empty layout; in both cases, only the central pane will be visible by default. Finally, note that the layout behavior is undefined if a dock pane appears multiple times in a layout. .. rubric:: Footnotes .. [1] For more information about the MVC pattern as used in ETS, the reader is referred to the `Introduction `_ of the Traits UI User Guide. .. [2] Throughout this document, "``Task``" will refer to the class of that name in the Tasks API, while "task" will be reserved for the general UI concept, and similarly for other terms. envisage-6.0.1/docs/source/tasks_user_manual/menus.rst000066400000000000000000000113601406265421000231510ustar00rootroot00000000000000.. _menus: ==================== Menus and Tool Bars ==================== In addition to specifying a layout of panes, a task can include a menu bar, a status bar, and any number of tool bars. In this section, we shall see how the Tasks framework supports these important user interface elements. It is important to recall that a task is a kind of *blueprint* for a user interface. As such, a task can provide blueprints, or *schemas* as we shall henceforth call them, for menus and tool bars, but does not itself contain menu or tool bar controls. This distinction, though perhaps not very useful in the present section, will take on considerable importance in the subsequent section on :ref:`extensibility`. .. index:: menu bar Defining a Menu Bar ------------------- .. index:: Action Resuming our example of the script editing task from the previous section, we shall define some menu items for opening and saving files. As in Traits UI and Pyface, individual menu items are instances of the ``Action`` class. We might define the 'Open' action as follows:: from pyface.action.api import Action class OpenAction(Action): name = 'Open' accelerator = 'Ctrl+O' def perform(self, event): event.task.open() Of course, we must also implement the ``open()`` method on our task:: from pyface.api import FileDialog, OK class ExampleTask(Task): [ ... ] def open(self): """ Shows a dialog to open a file. """ dialog = FileDialog(parent=self.window.control, wildcard='*.py') if dialog.open() == OK: # Recall that 'open_file' was defined in the previous section. self.open_file(dialog.path) .. index:: MenuBarSchema, MenuSchema, GroupSchema, SMenuBar, SMenu, SGroup Now let us suppose that we have similarly implemented a ``SaveAction`` and a ``save()`` method on our task. We would like to add these actions to a menu bar. Tasks provides several ``Schema`` classes for this purpose: :``MenuBarSchema``: The root of a menu hierarchy. Contains some number of menu schemas. :``MenuSchema``: A menu or sub-menu in the menu hierarchy. :``GroupSchema``: A group of menu items that are logically related and that may or may not require separators from other groups. For convenience, these classes also have the abbreviated names ``SMenuBar``, ``SMenu``, and ``SGroup``, respectively. We can implement the menu bar for our task using these abbreviations:: from pyface.tasks.action.api import SMenu, SMenuBar class ExampleTask(Task): [ ... ] menu_bar = SMenuBar(SMenu(OpenAction(), SaveAction(), id='File', name='&File')) .. index:: TaskAction The pattern of having an ``Action`` instance call back to its task is so common that there is a predefined class for this purpose. We can use this ``TaskAction`` class to implement the above menu more simply, without having to define separately the ``OpenAction`` and ``SaveAction`` classes:: from pyface.tasks.action.api import SMenu, SMenuBar, TaskAction class ExampleTask(Task): [ ... ] menu_bar = SMenuBar(SMenu(TaskAction(name='Open...', method='open', accelerator='Ctrl+O'), TaskAction(name='Save', method='save', accelerator='Ctrl+S'), id='File', name='&File')) .. index:: tool bar Defining a Tool Bar ------------------- .. index:: ToolBarSchema, SToolBar Like a menu bar, a tool bar uses the ``Action`` class to represent individual items. A tool bar, however, is defined with a different set of schemas: :``ToolBarSchema``: The root of a tool bar hierarchy. Contains some number of group schemas and actions. :``GroupSchema``: A group of tool bar buttons that are logically related and that may or may not require separators from other groups. As above, these classes are often abbreviated as ``SToolBar`` and ``SGroup``, respectively. Let us now add a tool bar with buttons for opening and saving files to our script editing task:: from pyface.api import ImageResource from pyface.tasks.action.api import SToolBar, TaskAction class ExampleTask(Task): [ ... ] tool_bars = [ SToolBar(TaskAction(method='open', tooltip='Open a file', image=ImageResource('document_open')), TaskAction(method='save', tooltip='Save the current file', image=ImageResource('document_save'))) ] envisage-6.0.1/envisage/000077500000000000000000000000001406265421000151205ustar00rootroot00000000000000envisage-6.0.1/envisage/__init__.py000066400000000000000000000014271406265421000172350ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! try: from envisage.version import version as __version__ except ImportError: # If we get here, we're using a source tree that hasn't been created via # the setup script. __version__ = "unknown" # Per logging best practices, add a NullHandler to the root 'envisage' # logger. import logging logging.getLogger(__name__).addHandler(logging.NullHandler()) del logging envisage-6.0.1/envisage/api.py000066400000000000000000000055111406265421000162450ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ Primary API for envisage Interfaces ---------- - :class:`~.IApplication` - :class:`~.IExtensionPoint` - :class:`~.IExtensionPointUser` - :class:`~.IExtensionProvider` - :class:`~.IExtensionRegistry` - :class:`~.IImportManager` - :class:`~.IPlugin` - :class:`~.IPluginActivator` - :class:`~.IPluginManager` - :class:`~.IServiceRegistry` Application, plugin and related classes ------------------------------------------ - :class:`~.Application` - :class:`~.CorePlugin` - :class:`~.EggPluginManager` - :class:`~.ExtensionPoint` - :class:`~.ExtensionPointBinding` - :func:`~.bind_extension_point` - :class:`~.ExtensionProvider` - :class:`~.ExtensionPointChangedEvent` - :class:`~.ImportManager` - :class:`~.Plugin` - :class:`~.PluginActivator` - :class:`~.PluginExtensionRegistry` - :class:`~.PluginManager` - :class:`~.ProviderExtensionRegistry` - :class:`~.Service` - :class:`~.ServiceOffer` - :class:`~.ServiceRegistry` Exceptions ---------- - :class:`~.NoSuchServiceError` - :class:`~.UnknownExtension` - :class:`~.UnknownExtensionPoint` """ 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 .core_plugin import CorePlugin from .egg_plugin_manager import EggPluginManager from .extension_registry import ExtensionRegistry from .extension_point import ExtensionPoint 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 .unknown_extension import UnknownExtension from .unknown_extension_point import UnknownExtensionPoint envisage-6.0.1/envisage/application.py000066400000000000000000000411351406265421000200010ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ An extensible, pluggable, application. """ # Standard library imports. import logging import os # Enthought library imports. from apptools.preferences.api import IPreferences, ScopedPreferences from apptools.preferences.api import set_default_preferences from traits.api import ( Delegate, Event, HasTraits, Instance, observe, provides, Str, VetoableEvent ) from traits.etsconfig.api import ETSConfig # 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__) @provides(IApplication) 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. """ #### '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().__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: for plugin in plugins: self.add_plugin(plugin) ########################################################################### # '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() ########################################################################### # '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 ) def add_extension_point(self, extension_point): """ Add an extension point. """ self.extension_registry.add_extension_point(extension_point) 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 ) def remove_extension_point(self, extension_point_id): """ Remove an extension point. """ self.extension_registry.remove_extension_point(extension_point_id) 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) ########################################################################### # '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) 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) 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) def unregister_service(self, service_id): """ Unregister a service. """ self.service_registry.unregister_service(service_id) ########################################################################### # '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). @observe("plugin_manager") def _update_application(self, event): """ Static trait change handler. """ old, new = event.old, event.new if old is not None: old.application = None if new is not None: new.application = self #### 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) envisage-6.0.1/envisage/application_event.py000066400000000000000000000012521406265421000211760ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ 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") envisage-6.0.1/envisage/composite_plugin_manager.py000066400000000000000000000125001406265421000225420ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ A plugin manager composed of other plugin managers! """ # Standard library imports. import logging # Enthought library imports. from traits.api import ( Event, HasTraits, Instance, List, observe, on_trait_change, provides, ) # 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__) @provides(IPluginManager) class CompositePluginManager(HasTraits): """ A plugin manager composed of other plugin managers! e.g:: plugin_manager = CompositePluginManager( plugin_mangers = [ EggBasketPluginManager(...), PackagePluginManager(...), ] ) """ #### '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) @observe("application") def _update_application(self, event): new = event.new for plugin_manager in self.plugin_managers: plugin_manager.application = new # 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_on_plugins(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. """ for plugin in self: self.start_plugin(plugin) 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) 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() for plugin in stop_order: self.stop_plugin(plugin) 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) envisage-6.0.1/envisage/core_plugin.py000066400000000000000000000142301406265421000200000ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ The Envisage core plugin. """ # Enthought library imports. from envisage.extension_point import ExtensionPoint from envisage.plugin import Plugin from envisage.service_offer import ServiceOffer from traits.api import List, 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 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 ID for preferences PREFERENCES = "envisage.preferences" #: Extension point ID for service offers 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 ############################## #: preferences ExtensionPoint 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 _update_preferences(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) #: service offers ExtensionPoint 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 _register_new_services(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. """ for service in event.added: self._register_service_offer(service) #### 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) # 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) ########################################################################### # Private interface. ########################################################################### 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() def _register_service_offers(self, service_offers): """ Register a list of service offers. """ return list(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 envisage-6.0.1/envisage/egg_basket_plugin_manager.py000066400000000000000000000134641406265421000226450ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ A plugin manager that finds plugins in eggs on the 'plugin_path'. """ import logging import sys import traceback import pkg_resources from traits.api import Callable, 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 #################################### # If a plugin cannot be loaded for any reason, this callable is called # with the following arguments: entry_point, exception. on_broken_plugin = Callable def _on_broken_plugin_default(self): def handle_broken_plugin(entry_point, exc): raise exc return handle_broken_plugin # If a distribution cannot be loaded for any reason # (mainly VersionConflict), this callable is called with the following # arguments: distribution, exception. on_broken_distribution = Callable # A list of directories that will be searched to find plugins. plugin_path = List(Directory) @on_trait_change("plugin_path[]") def _update_path_and_reset_plugins(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.warning( "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, self._handle_broken_distributions, ) # 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, self._handle_broken_distributions, ) plugins = [] for entry_point in self._get_plugin_entry_points(plugin_working_set): if self._include_plugin(entry_point.name): try: plugin = self._create_plugin_from_entry_point( entry_point, application ) plugins.append(plugin) except Exception as exc: exc_tb = traceback.format_exc() msg = "Error loading plugin: %s (from %s)\n%s" % ( entry_point.name, entry_point.dist.location, exc_tb, ) logger.error(msg) self.on_broken_plugin(entry_point, exc) return plugins def _handle_broken_distributions(self, errors): logger.error("Error loading distributions: %s", errors) if self.on_broken_distribution is None: raise SystemError("Cannot find eggs %s" % errors) else: for dist, exc in errors.items(): self.on_broken_distribution(dist, exc) 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) envisage-6.0.1/envisage/egg_plugin_manager.py000066400000000000000000000107031406265421000213050ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ A plugin manager that gets its plugins from Eggs. """ # Standard library imports. import logging import re # 3rd party imports. import pkg_resources # 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.warning( "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 envisage-6.0.1/envisage/egg_utils.py000066400000000000000000000066031406265421000174610ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ Utility functions for working with Python Eggs. """ # 3rd party imports. import pkg_resources # Enthought library imports. from traits.util.toposort import topological_sort def add_eggs_on_path(working_set, path, on_error=None): """ 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: if on_error: on_error(errors) else: 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). for distribution in distributions: working_set.add(distribution) 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: entry_map = distribution.get_entry_map(entry_point_name) entry_points.extend(list(entry_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 envisage-6.0.1/envisage/examples/000077500000000000000000000000001406265421000167365ustar00rootroot00000000000000envisage-6.0.1/envisage/examples/__init__.py000066400000000000000000000000001406265421000210350ustar00rootroot00000000000000envisage-6.0.1/envisage/examples/_demo.py000066400000000000000000000020101406265421000203640ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ Utilities for supporting Envisage's demo examples. """ import contextlib import os import sys @contextlib.contextmanager def demo_path(path): """ Context manager to temporarily insert the directory containing the demo script to sys.path such that demo examples can be run using local packages. This function should only be used by Envisage example files. Parameters ---------- path : Path-like Path to the demo script to be run. """ path = os.path.dirname(os.fspath(path)) try: sys.path.insert(0, path) yield finally: sys.path.remove(path) envisage-6.0.1/envisage/examples/_etsdemo_info.py000066400000000000000000000017461406265421000221320ustar00rootroot00000000000000# (C) Copyright 2005-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ This module provides functions to be advertised in the distribution entry points. """ import pkg_resources def info(request): """ Return a configuration for contributing examples to the Demo application. Parameters ---------- request : dict Information provided by the demo application. Currently this is a placeholder. Returns ------- response : dict """ return dict( version=1, name="Envisage Examples", root=( pkg_resources.resource_filename("envisage.examples", "demo") ), ) envisage-6.0.1/envisage/examples/demo/000077500000000000000000000000001406265421000176625ustar00rootroot00000000000000envisage-6.0.1/envisage/examples/demo/GUI_Application/000077500000000000000000000000001406265421000226315ustar00rootroot00000000000000envisage-6.0.1/envisage/examples/demo/GUI_Application/traitsui_gui_app.py000066400000000000000000000023101406265421000265470ustar00rootroot00000000000000""" A simple example of a GUIApplication which wraps a TraitsUI """ from traits.api import HasTraits, Str, Int, Enum, Instance, on_trait_change from envisage.api import Plugin from envisage.ui.gui_application import GUIApplication from traitsui.api import View, Item, OKCancelButtons class Person(HasTraits): """ A typical traits model object """ name = Str("John Doe") age = Int(21) gender = Enum("Male", "Female") view = View( Item("name"), Item("age"), Item("gender"), buttons=OKCancelButtons, ) class PersonViewPlugin(Plugin): """ The 'Person View' plugin. This plugin waits for the application to start, and then creates a traits UI. """ ui = Instance("traitsui.ui.UI") @on_trait_change("application:application_initialized") def on_application_start(self): """ Start the UI. """ person = Person() # keep a reference to the ui object to avoid garbage collection self.ui = person.edit_traits() # Application entry point. if __name__ == "__main__": # Create the application. application = GUIApplication( id="person_view", plugins=[PersonViewPlugin()] ) # Run it! application.run() envisage-6.0.1/envisage/examples/demo/Hello_World/000077500000000000000000000000001406265421000220745ustar00rootroot00000000000000envisage-6.0.1/envisage/examples/demo/Hello_World/hello_world.py000066400000000000000000000050471406265421000247660ustar00rootroot00000000000000""" The Envisage version of the old chestnut. """ # Enthought library imports. from envisage.api import Application, ExtensionPoint, Plugin from traits.api import List, Str class HelloWorld(Plugin): """ The 'Hello World' plugin. This plugin offers a single extension point 'greetings' which is a list of greetings, one of which is used to produce the ' World' message when the plugin is started. """ # This tells us that the plugin offers the 'greetings' extension point, # and that plugins that want to contribute to it must each provide a list # of strings (Str). greetings = ExtensionPoint( List(Str), id="greetings", desc='Greetings for "Hello World"' ) # Plugin's have two important lifecyle methods, 'start' and 'stop'. These # methods are called automatically by Envisage when the application is # started and stopped respectively. def start(self): """ Start the plugin. """ # Standard library imports. # # We put this import here just to emphasize that it is only used in # this specific plugin. import random print("{} World!".format(random.choice(self.greetings))) class Greetings(Plugin): """ A plugin that contributes to the 'greetings' extension point. """ # This tells us that the plugin contributes the value of this trait to the # 'greetings' extension point. greetings = List(["Hello", "G'day"], contributes_to="greetings") class MoreGreetings(Plugin): """ Another plugin that contributes to the 'greetings' extension point. """ # This tells us that the plugin contributes the value of this trait to the # 'greetings' extension point. greetings = List(contributes_to="greetings") # This shows how you can use a standard trait initializer to populate the # list dynamically. def _greetings_default(self): """ Trait initializer. """ extensions = [ "The %s application says %s" % (self.application.id, greeting) for greeting in ["Bonjour", "Hola"] ] return extensions # Application entry point. if __name__ == "__main__": # Create the application. # # An application is simply a collection of plugins. In this case we # specify the plugins explicitly, but the mechanism for finding plugins # is configurable by setting the application's 'plugin_manager' trait. application = Application( id="hello.world", plugins=[HelloWorld(), Greetings(), MoreGreetings()] ) # Run it! application.run() envisage-6.0.1/envisage/examples/demo/MOTD/000077500000000000000000000000001406265421000204255ustar00rootroot00000000000000envisage-6.0.1/envisage/examples/demo/MOTD/acme/000077500000000000000000000000001406265421000213325ustar00rootroot00000000000000envisage-6.0.1/envisage/examples/demo/MOTD/acme/__init__.py000066400000000000000000000000771406265421000234470ustar00rootroot00000000000000# Copyright (c) 2007 by Enthought, Inc. # All rights reserved. envisage-6.0.1/envisage/examples/demo/MOTD/acme/motd/000077500000000000000000000000001406265421000222755ustar00rootroot00000000000000envisage-6.0.1/envisage/examples/demo/MOTD/acme/motd/__init__.py000066400000000000000000000000771406265421000244120ustar00rootroot00000000000000# Copyright (c) 2007 by Enthought, Inc. # All rights reserved. envisage-6.0.1/envisage/examples/demo/MOTD/acme/motd/api.py000066400000000000000000000002231406265421000234150ustar00rootroot00000000000000from acme.motd.i_message import IMessage from acme.motd.i_motd import IMOTD from acme.motd.message import Message from acme.motd.motd import MOTD envisage-6.0.1/envisage/examples/demo/MOTD/acme/motd/i_message.py000066400000000000000000000004721406265421000246060ustar00rootroot00000000000000""" The interface for 'Message of the Day' messages. """ # Enthought library imports. from traits.api import Interface, Str class IMessage(Interface): """ The interface for 'Message of the Day' messages. """ # The author of the message. author = Str # The text of the message. text = Str envisage-6.0.1/envisage/examples/demo/MOTD/acme/motd/i_motd.py000066400000000000000000000005101406265421000241160ustar00rootroot00000000000000""" The 'Message of the Day' interface. """ # Enthought library imports. from traits.api import Interface class IMOTD(Interface): """ The 'Message of the Day' interface. """ def motd(self): """ Return the message of the day. Returns an object that implements the 'IMessage' interface. """ envisage-6.0.1/envisage/examples/demo/MOTD/acme/motd/message.py000066400000000000000000000006401406265421000242730ustar00rootroot00000000000000""" The default implementation of the 'IMessage' interface. """ # Enthought library imports. from traits.api import HasTraits, Str, provides # Local imports. from acme.motd.i_message import IMessage @provides(IMessage) class Message(HasTraits): """ The default implementation of the 'IMessage' interface. """ # The author of the message. author = Str # The text of the message. text = Str envisage-6.0.1/envisage/examples/demo/MOTD/acme/motd/motd.py000066400000000000000000000020461406265421000236140ustar00rootroot00000000000000""" The 'Message of the Day' implementation! """ # Standard library imports. from random import choice # Enthought library imports. from traits.api import HasTraits, List, provides # Local imports. from acme.motd.i_message import IMessage from acme.motd.i_motd import IMOTD from acme.motd.message import Message @provides(IMOTD) class MOTD(HasTraits): """ The 'Message of the Day' implementation! """ # The default message is used when there are no other messages! DEFAULT_MESSAGE = Message( author="Anon", text="Work hard and be good to your Mother" ) # The list of possible messages. messages = List(IMessage) ########################################################################### # 'IMOTD' interface. ########################################################################### def motd(self): """ Prints a random message. """ if len(self.messages) > 0: message = choice(self.messages) else: message = self.DEFAULT_MESSAGE return message envisage-6.0.1/envisage/examples/demo/MOTD/acme/motd/motd_plugin.py000066400000000000000000000067511406265421000252010ustar00rootroot00000000000000""" The 'Message of the Day' plugin """ # In the interest of lazy loading you should only import from the following # packages at the module level of a plugin:: # # - envisage # - traits # # Eveything else should be imported when it is actually required. # Enthought library imports. from envisage.api import ExtensionPoint, Plugin, ServiceOffer from traits.api import Instance, List, on_trait_change class MOTDPlugin(Plugin): """ The 'Message of the Day' plugin. This plugin simply prints the 'Message of the Day' to stdout. """ # The Ids of the extension points that this plugin offers. MESSAGES = "acme.motd.messages" # The Ids of the extension points that this plugin contributes to. SERVICE_OFFERS = "envisage.service_offers" #### 'IPlugin' interface ################################################## # The plugin's unique identifier. id = "acme.motd" # The plugin's name (suitable for displaying to the user). name = "MOTD" #### Extension points offered by this plugin ############################## # The messages extension point. # # Notice that we use the string name of the 'IMessage' interface rather # than actually importing it. This makes sure that the import only happens # when somebody actually gets the contributions to the extension point. messages = ExtensionPoint( List(Instance("acme.motd.api.IMessage")), id=MESSAGES, desc=""" This extension point allows you to contribute messages to the 'Message Of The Day'. """, ) #### Contributions to extension points made by this plugin ################ service_offers = List(contributes_to=SERVICE_OFFERS) def _service_offers_default(self): """ Trait initializer. """ # Register the protocol as a string containing the actual module path # and *not* a module path that goes via an 'api.py' file as this does # not match what Python thinks the module is! This allows the service # to be looked up by passing either the exact same string, or the # actual protocol object itself (the latter is the preferred way of # doing it!). motd_service_offer = ServiceOffer( protocol="acme.motd.i_motd.IMOTD", factory=self._create_motd_service, ) return [motd_service_offer] ########################################################################### # Private interface. ########################################################################### def _create_motd_service(self): """ Factory method for the 'MOTD' service. """ # Only do imports when you need to! This makes sure that the import # only happens when somebody needs an 'IMOTD' service. from acme.motd.motd import MOTD return MOTD(messages=self.messages) # This plugin does all of its work in this method which gets called when # the application has started all of its plugins. @on_trait_change("application:started") def _print_motd(self): """ Print the 'Message of the Day' to stdout! """ # Note that we always offer the service via its name, but look it up # via the actual protocol. from acme.motd.api import IMOTD # Lookup the MOTD service. motd = self.application.get_service(IMOTD) # Get the message of the day... message = motd.motd() # ... and print it. print('\n"%s"\n\n- %s' % (message.text, message.author)) envisage-6.0.1/envisage/examples/demo/MOTD/acme/motd/software_quotes/000077500000000000000000000000001406265421000255275ustar00rootroot00000000000000envisage-6.0.1/envisage/examples/demo/MOTD/acme/motd/software_quotes/__init__.py000066400000000000000000000000771406265421000276440ustar00rootroot00000000000000# Copyright (c) 2007 by Enthought, Inc. # All rights reserved. envisage-6.0.1/envisage/examples/demo/MOTD/acme/motd/software_quotes/messages.py000066400000000000000000000036711406265421000277170ustar00rootroot00000000000000""" Some messages! """ from acme.motd.api import Message messages = [ Message( author="Bertrand Meyer", text="You can have quality software, or you can have pointer" " arithmetic; but you cannot have both at the same time.", ), Message( author="Edsger W. Dijkstra", text="The effective programmer is keenly aware of the limited size" " of his own head.", ), Message( author="Richard Buckminster-Fuller", text="When I'm working on a problem, I never think about beauty. I" " think only how to solve the problem. But when I have finished, if" " the solution is not beautiful, I know it is wrong.", ), Message( author="Tom Gilb", text="If you don't know what you're doing, don't do it on a large" " scale.", ), Message( author="Arthur Norman", text="The best way to implement hard code is never to know you're" " implementing it.", ), Message( author="Albert Einstein", text="Any intelligent fool can make things bigger, more complex," " and more violent. It takes a touch of genius - and a lot of courage" " - to move in the opposite direction.", ), Message( author="Martin Fowler", text="Any fool can write code that a computer can understand. Good" " programmers write code that humans can understand.", ), Message( author="Chet Hendrickson", text="The rule is, 'Do the simplest thing that could possibly" " work', not the most stupid.", ), Message( author="Ron Jeffries", text="If you're working sixty hour weeks, you're too tired to be" " doing good software.", ), Message( author="Edward Tufte", text="Clutter and confusion are failures of design, not attributes" " of information. There's no such thing as information overload.", ), ] envisage-6.0.1/envisage/examples/demo/MOTD/acme/motd/software_quotes/software_quotes_plugin.py000066400000000000000000000016521406265421000327150ustar00rootroot00000000000000""" The 'Software Quotes' plugin """ # Enthought library imports. from envisage.api import Plugin from traits.api import List class SoftwareQuotesPlugin(Plugin): """ The 'Software Quotes' plugin. """ #### 'IPlugin' interface ################################################## # The plugin's unique identifier. id = "acme.motd.software_quotes" # The plugin's name (suitable for displaying to the user). name = "Software Quotes" #### Extension points offered by this plugin ############################## # None #### Contributions to extension points made by this plugin ################ # Messages for the 'Message Of The Day'. messages = List(contributes_to="acme.motd.messages") def _messages_default(self): """ Trait initializer. """ # Only do imports when you need to! from acme.motd.software_quotes.messages import messages return messages envisage-6.0.1/envisage/examples/demo/MOTD/run.py000066400000000000000000000016661406265421000216140ustar00rootroot00000000000000""" Run the MOTD example application. """ # Enthought library imports. from envisage.api import Application, CorePlugin def main(): """ Run the application. """ # Import here so that this script can be run from anywhere without # having to install the packages. from acme.motd.motd_plugin import MOTDPlugin from acme.motd.software_quotes.software_quotes_plugin import ( SoftwareQuotesPlugin, ) # Create an application containing the appropriate plugins. application = Application( id="acme.motd", plugins=[CorePlugin(), MOTDPlugin(), SoftwareQuotesPlugin()], ) # Run it! return application.run() if __name__ == "__main__": # This context manager is added so that one can run this example from any # directory without necessarily having installed the examples as packages. from envisage.examples._demo import demo_path with demo_path(__file__): main() envisage-6.0.1/envisage/examples/demo/plugins/000077500000000000000000000000001406265421000213435ustar00rootroot00000000000000envisage-6.0.1/envisage/examples/demo/plugins/tasks/000077500000000000000000000000001406265421000224705ustar00rootroot00000000000000envisage-6.0.1/envisage/examples/demo/plugins/tasks/attractors/000077500000000000000000000000001406265421000246565ustar00rootroot00000000000000envisage-6.0.1/envisage/examples/demo/plugins/tasks/attractors/__init__.py000066400000000000000000000000001406265421000267550ustar00rootroot00000000000000envisage-6.0.1/envisage/examples/demo/plugins/tasks/attractors/attractors_application.py000066400000000000000000000036461406265421000320120ustar00rootroot00000000000000# Enthought library imports. from envisage.ui.tasks.api import TasksApplication from pyface.tasks.api import TaskWindowLayout from traits.api import Bool, Instance, List, Property # Local imports. from attractors.attractors_preferences import ( AttractorsPreferences, AttractorsPreferencesPane, ) class AttractorsApplication(TasksApplication): """ The chaotic attractors Tasks application. """ #### 'IApplication' interface ############################################# # The application's globally unique identifier. id = "example.attractors" # The application's user-visible name. name = "Attractors" #### 'TasksApplication' interface ######################################### # The default window-level layout for the application. default_layout = List(TaskWindowLayout) # Whether to restore the previous application-level layout when the # applicaton is started. always_use_default_layout = Property(Bool) #### 'AttractorsApplication' interface #################################### preferences_helper = Instance(AttractorsPreferences) ########################################################################### # Private interface. ########################################################################### #### Trait initializers ################################################### def _default_layout_default(self): active_task = self.preferences_helper.default_task tasks = [factory.id for factory in self.task_factories] return [ TaskWindowLayout(*tasks, active_task=active_task, size=(800, 600)) ] def _preferences_helper_default(self): return AttractorsPreferences(preferences=self.preferences) #### Trait property getter/setters ######################################## def _get_always_use_default_layout(self): return self.preferences_helper.always_use_default_layout envisage-6.0.1/envisage/examples/demo/plugins/tasks/attractors/attractors_plugin.py000066400000000000000000000036251406265421000310020ustar00rootroot00000000000000# Standard library imports. import os.path # Enthought library imports. from envisage.api import Plugin from envisage.ui.tasks.api import TaskFactory from traits.api import List class AttractorsPlugin(Plugin): """ The chaotic attractors plugin. """ # Extension point IDs. PREFERENCES = "envisage.preferences" PREFERENCES_PANES = "envisage.ui.tasks.preferences_panes" TASKS = "envisage.ui.tasks.tasks" #### 'IPlugin' interface ################################################## # The plugin's unique identifier. id = "example.attractors" # The plugin's name (suitable for displaying to the user). name = "Attractors" #### Contributions to extension points made by this plugin ################ preferences = List(contributes_to=PREFERENCES) preferences_panes = List(contributes_to=PREFERENCES_PANES) tasks = List(contributes_to=TASKS) ########################################################################### # Protected interface. ########################################################################### def _preferences_default(self): filename = os.path.join(os.path.dirname(__file__), "preferences.ini") return ["file://" + filename] def _preferences_panes_default(self): from attractors.attractors_preferences import AttractorsPreferencesPane return [AttractorsPreferencesPane] def _tasks_default(self): from attractors.visualize_2d_task import Visualize2dTask from attractors.visualize_3d_task import Visualize3dTask return [ TaskFactory( id="example.attractors.task_2d", name="2D Visualization", factory=Visualize2dTask, ), TaskFactory( id="example.attractors.task_3d", name="3D Visualization", factory=Visualize3dTask, ), ] envisage-6.0.1/envisage/examples/demo/plugins/tasks/attractors/attractors_preferences.py000066400000000000000000000044001406265421000317750ustar00rootroot00000000000000# Enthought library imports. from envisage.ui.tasks.api import PreferencesPane, TaskFactory from apptools.preferences.api import PreferencesHelper from traits.api import Bool, Dict, Enum, List, Str from traitsui.api import EnumEditor, HGroup, VGroup, Item, Label, View class AttractorsPreferences(PreferencesHelper): """ The preferences helper for the Attractors application. """ #### 'PreferencesHelper' interface ######################################## # The path to the preference node that contains the preferences. preferences_path = "example.attractors" #### Preferences ########################################################## # The task to activate on app startup if not restoring an old layout. default_task = Str # Whether to always apply the default application-level layout. # See TasksApplication for more information. always_use_default_layout = Bool class AttractorsPreferencesPane(PreferencesPane): """ The preferences pane for the Attractors application. """ #### 'PreferencesPane' interface ########################################## # The factory to use for creating the preferences model object. model_factory = AttractorsPreferences #### 'AttractorsPreferencesPane' interface ################################ task_map = Dict(Str, Str) view = View( VGroup( HGroup( Item("always_use_default_layout"), Label("Always use the default active task on startup"), show_labels=False, ), HGroup( Label("Default active task:"), Item( "default_task", editor=EnumEditor(name="handler.task_map") ), enabled_when="always_use_default_layout", show_labels=False, ), label="Application startup", ), resizable=True, ) ########################################################################### # Private interface. ########################################################################### def _task_map_default(self): return dict( (factory.id, factory.name) for factory in self.dialog.application.task_factories ) envisage-6.0.1/envisage/examples/demo/plugins/tasks/attractors/help/000077500000000000000000000000001406265421000256065ustar00rootroot00000000000000envisage-6.0.1/envisage/examples/demo/plugins/tasks/attractors/help/henon.html000066400000000000000000000032601406265421000276040ustar00rootroot00000000000000

The Hénon map is a discrete-time dynamical system. It is one of the most studied examples of dynamical systems that exhibit chaotic behavior. The Hénon map takes a point (x_n, y_n) in the plane and maps it to a new point

The map depends on two parameters, a and b, which for the canonical Hénon map have values of a = 1.4 and b = 0.3. For the canonical values the Hénon map is chaotic. For other values of a and b the map may be chaotic, intermittent, or converge to a periodic orbit. An overview of the type of behavior of the map at different parameter values may be obtained from its orbit diagram.

The map was introduced by Michel Hénon as a simplified model of the Poincaré section of the Lorenz model. For the canonical map, an initial point of the plane will either approach a set of points known as the Hénon strange attractor, or diverge to infinity. The Hénon attractor is a fractal, smooth in one direction and a Cantor set in another. Numerical estimates yield a correlation dimension of 1.42 ± 0.02[1] and a Hausdorff dimension of 1.261 ± 0.003[2] for the attractor of the canonical map.

As a dynamical system, the canonical Hénon map is interesting because, unlike the logistic map, its orbits defy a simple description.

Text courtesy of Wikipedia under the CC Share-Alike License.

envisage-6.0.1/envisage/examples/demo/plugins/tasks/attractors/help/images/000077500000000000000000000000001406265421000270535ustar00rootroot00000000000000envisage-6.0.1/envisage/examples/demo/plugins/tasks/attractors/help/images/henon1.png000066400000000000000000000016541406265421000307570ustar00rootroot00000000000000PNG  IHDR2#'0PLTEtttbbb@@@000"""PPP 9V7IDATX ?hQǿӗ%iS! Eq8-B-Tp`X %uE(VX:8V vppppPݽwrKӭo }Z3m Ù!'8_jd.ܔkr0Lq}RP =A %'GLX,3mە\|DITmjY|圗jE YZB!2r8P"UvF'Jn/*R},bw@+a)B&U~Å>` z)f% ~WJ^y shܒ)9t!2/z tu%7 cs-ZRc-hQdؘwA!d=' lыBF>gddY~q w-I##4t0ֱThh"kU_t]Ə\kI-sl_m| %7֒jQxP)ι"+xs+T huywҎ*:˨Q[mq{˹q؟s4GIaZk:J20iش<}/ɇa \4[9$S 7pcpp wgqhq}Y<90IENDB`envisage-6.0.1/envisage/examples/demo/plugins/tasks/attractors/help/images/lorenz2.png000066400000000000000000000014371406265421000311610ustar00rootroot00000000000000PNG  IHDR)Ը0PLTE@@@PPPtttbbb"""̶ 000bIDATH VMhAd7M)Z.(-ɋ"$bEmA($ſPiCKoz19 CED('A} n &޷3o߼ AIRUڸL5 OMZ~JeqH9wM͢ 5=,fmPKr+Cwd5ۨZA#E-Ƴ8PZ)YrsjM 'ti j,f+|Ňk[@i?k|I!p%K~0TslZ?.BjTJ:ܸ~'"3HQӃFASm~'1g'>TH!G >qژaiϮ,,jT@0cv~֙C.hEF`%Q9@3d#>`qqu)Z_HvNxȞ;&݋k:8:xK_-oioX>Q3hvuz>`_:*{ .$ug"Kaw Tc|Q_wgE/]A_V \;Qr%HUWdNRPe %| =N/szt/mkF60AɑэmSwGIENDB`envisage-6.0.1/envisage/examples/demo/plugins/tasks/attractors/help/images/lorenz3.png000066400000000000000000000011731406265421000311570ustar00rootroot00000000000000PNG  IHDRt)=0PLTE@@@PPPtttbbb"""̶ 000LIDATH 1H#Ah1wC#UW{pW"jci,\ppJ;"ih#( 6hv¡Vfgј؝7ٙg3=խQGQ-j4.?P12ݓFV&c8GC |YIKXz*k5X)7-kAf7_yTXP<++E4 |QC iޠ :RKNǕd#ˏX/B^(TwZ p(b=K<+,EHbk\xilo8{fd^G@CKN+ȋ" 8PNut1h˵30D5'Bi O\B)xvMƮ h#v| 0 6Rϯ\;ƹ!?M_vf\1է\֢7Mך(hiG+ j{<ں1hYYrȑ{Nv.ԯ9$6s='.wpx=IENDB`envisage-6.0.1/envisage/examples/demo/plugins/tasks/attractors/help/images/rossler1.png000066400000000000000000000010311406265421000313260ustar00rootroot00000000000000PNG  IHDRm)0PLTE@@@PPPtttbbb"""̶ 000bIDATH ?H@ƿJ&ME.,88 8UK Xf-X h6Mnr2#/!w3lR(c T4N<><'Y6ku%Gh@s,% f %،UsQXةŎ^x W'snsqv Y-MFwV Zo_0ёI޽]ŖZiɱ1\$ '津'eoaΏd*^+o "b}`$~xIENDB`envisage-6.0.1/envisage/examples/demo/plugins/tasks/attractors/help/images/rossler2.png000066400000000000000000000010571406265421000313370ustar00rootroot00000000000000PNG  IHDRh)+6q[0PLTE@@@PPPtttbbb"""̶ 000bIDATH ?O@ƟZ A$8A'#&tLN$u61,?Rrk{y w/$hmU6m24҃ y#@Z}+;Z1uB$( EKGbzc|0k/8SHKo%(-|0CRkYz8*CZy<&FHCƅ}[q?t)&!;(+CߖudY~8MCbOț`~h+zgb?cG ii\ в& )D?zƥ*5ODq-/n\y'aB2^+S eRse^F^.Q~ʝ1uPh~IENDB`envisage-6.0.1/envisage/examples/demo/plugins/tasks/attractors/help/images/rossler3.png000066400000000000000000000014441406265421000313400ustar00rootroot00000000000000PNG  IHDR)Ú50PLTE@@@PPPtttbbb"""̶ 000LIDATH V=ha~&܏5J-$EN[F<&UbDpb""E): ]c\Ŀ޽w$'3D+L1:ldRJ&")eiԲm׻RbeZ jHe /Zx%(Hl ;M^+)h}i㢕'-l5|ǟ8W F>Em߇d1;6[)lEmy &t} %"ԺK]gƘ!1-#c>i ,s8Qb987sVU-rU"ny))O9q7FԥvK30qd*G̔( -yLTZ;? 18&5W=gY`

The Lorenz attractor itself, and the equations from which it is derived, were introduced by Edward Lorenz in 1963, who derived it from the simplified equations of convection rolls arising in the equations of the atmosphere.

In addition to its interest to the field of non-linear mathematics, the Lorenz model has important implications for climate and weather prediction. The model is an explicit statement that planetary and stellar atmospheres may exhibit a variety of quasi-periodic regimes that are, although fully deterministic, subject to abrupt and seemingly random change.

From a technical standpoint, the Lorenz oscillator is nonlinear, three-dimensional and deterministic. In 2001 it was proven by Warwick Tucker that for a certain set of parameters the system exhibits chaotic behavior and displays what is today called a strange attractor. The strange attractor in this case is a fractal of Hausdorff dimension between 2 and 3. Grassberger (1983) has estimated the Hausdorff dimension to be 2.06 ± 0.01 and the correlation dimension to be 2.05 ± 0.01.

The equations that govern the Lorenz oscillator are:

where σ is called the Prandtl number and ρ is called the Rayleigh number. All σ, ρ, β > 0, but usually σ = 10, β = 8/3 and ρ is varied. The system exhibits chaotic behavior for ρ = 28 but displays knotted periodic orbits for other values of ρ. For example, with ρ = 99.96 it becomes a T(3,2) torus knot.

When σ ≠ 0 and β (ρ-1) ≥ 0, the equations generate three critical points. The critical points at (0,0,0) correspond to no convection, and the critical points at (√(β(ρ-1)), √(β(ρ-1)), ρ-1) and (-√(β(ρ-1), -√(β(ρ-1)), ρ-1) correspond to steady convection. This pair is stable only if ρ= (σ(σ+β+3))/(σ-β-1), which can hold only for positive ρ if σ > β+1.

Text courtesy of Wikipedia under the CC Share-Alike License.

envisage-6.0.1/envisage/examples/demo/plugins/tasks/attractors/help/rossler.html000066400000000000000000000037621406265421000301750ustar00rootroot00000000000000

The Rössler attractor is the attractor for the Rössler system, a system of three non-linear ordinary differential equations. These differential equations define a continuous-time dynamical system that exhibits chaotic dynamics associated with the fractal properties of the attractor. Some properties of the Rössler system can be deduced via linear methods such as eigenvectors, but the main features of the system require non-linear methods such as Poincaré maps and bifurcation diagrams. The original Rössler paper says the Rössler attractor was intended to behave similarly to the Lorenz attractor, but also be easier to analyze qualitatively. An orbit within the attractor follows an outward spiral close to the x,y plane around an unstable fixed point. Once the graph spirals out enough, a second fixed point influences the graph, causing a rise and twist in the z-dimension. In the time domain, it becomes apparent that although each variable is oscillating within a fixed range of values, the oscillations are chaotic. This attractor has some similarities to the Lorenz attractor, but is simpler and has only one manifold. Otto Rössler designed the Rössler attractor in 1976, but the originally theoretical equations were later found to be useful in modeling equilibrium in chemical reactions. The defining equations are:

Rössler studied the chaotic attractor with a = 0.2, b = 0.2, and c = 5.7, though properties of a = 0.1, b = 0.1, and c = 14 have been more commonly used since.

Text courtesy of Wikipedia under the CC Share-Alike License.

envisage-6.0.1/envisage/examples/demo/plugins/tasks/attractors/model/000077500000000000000000000000001406265421000257565ustar00rootroot00000000000000envisage-6.0.1/envisage/examples/demo/plugins/tasks/attractors/model/__init__.py000066400000000000000000000000001406265421000300550ustar00rootroot00000000000000envisage-6.0.1/envisage/examples/demo/plugins/tasks/attractors/model/henon.py000066400000000000000000000035721406265421000274460ustar00rootroot00000000000000# System library imports. from scipy import array, zeros # Enthought library imports. from traits.api import ( Array, Float, HasTraits, Int, Property, Str, cached_property, provides, ) from traitsui.api import Item, View # Local imports. from attractors.model.i_plottable_2d import IPlottable2d @provides(IPlottable2d) class Henon(HasTraits): """ The model object for the Henon map. """ #### 'Henon' interface #################################################### # Equation parameters. a = Float(1.4, auto_set=False, enter_set=True) b = Float(0.3, auto_set=False, enter_set=True) # Iteration parameters. initial_point = Array(value=[0.1, 0.1]) steps = Int(10000) # Iteration results. points = Property(Array, observe="a, b, initial_point, steps") # Configuration view. view = View( Item("a"), Item("b"), Item("initial_point"), Item("steps"), resizable=True, ) #### 'IPlottable2D' interface ############################################# name = Str("Henon Map") plot_type = Str("scatter") x_data = Property(Array, observe="points") y_data = Property(Array, observe="points") x_label = Str("x") y_label = Str("y") ########################################################################### # Protected interface. ########################################################################### @cached_property def _get_points(self): point = self.initial_point points = zeros((self.steps, 2)) for i in range(self.steps): x, y = points[i] = point point = array([y + 1 - self.a * x ** 2, self.b * x]) return points @cached_property def _get_x_data(self): return self.points[:, 0] @cached_property def _get_y_data(self): return self.points[:, 1] envisage-6.0.1/envisage/examples/demo/plugins/tasks/attractors/model/i_model_2d.py000066400000000000000000000003101406265421000303170ustar00rootroot00000000000000# Enthought library imports. from traits.api import Array, Interface, Str class IModel2d(Interface): # The user-visible name of the model. name = Str x_data = Array y_data = Array envisage-6.0.1/envisage/examples/demo/plugins/tasks/attractors/model/i_model_3d.py000066400000000000000000000033331406265421000303300ustar00rootroot00000000000000# Enthought library imports. from traits.api import ( Array, DelegatesTo, HasTraits, Interface, Instance, Property, Str, Trait, cached_property, ) from traitsui.api import Group, Item, View class IModel3d(Interface): """ A model object that produces an array of 3D points. """ # The user-visible name of the model. name = Str # An n-by-3 array. points = Array class IModel3dIPlottable2dMixin(HasTraits): """ Mixin class to facilitate defining a IModel3d -> IPlottable2D adapter. """ #### 'Adapter' interface ################################################## adaptee = Instance(IModel3d) #### 'IPlottable2D' interface ############################################# name = DelegatesTo("adaptee") x_data = Property(Array, observe="adaptee.points, x_label") y_data = Property(Array, observe="adaptee.points, y_label") x_label = Trait("x", {"x": 0, "y": 1, "z": 2}) y_label = Trait("y", {"x": 0, "y": 1, "z": 2}) view = View( Group( Group( Item("adaptee", style="custom", show_label=False), label="Model", ), Group( Item("x_label", label="X axis"), Item("y_label", label="Y axis"), label="Plot", ), ) ) ########################################################################### # Protected interface. ########################################################################### @cached_property def _get_x_data(self): return self.adaptee.points[:, self.x_label_] @cached_property def _get_y_data(self): return self.adaptee.points[:, self.y_label_] envisage-6.0.1/envisage/examples/demo/plugins/tasks/attractors/model/i_plottable_2d.py000066400000000000000000000003571406265421000312200ustar00rootroot00000000000000# Enthought library imports. from traits.api import Enum, Str # Local imports. from attractors.model.i_model_2d import IModel2d class IPlottable2d(IModel2d): plot_type = Enum("line", "scatter") x_label = Str y_label = Str envisage-6.0.1/envisage/examples/demo/plugins/tasks/attractors/model/lorenz.py000066400000000000000000000051631406265421000276460ustar00rootroot00000000000000# System library imports. from scipy import arange, array from scipy.integrate import odeint # Enthought libary imports. from traits.api import ( Adapter, Array, Float, HasTraits, Instance, Property, Str, cached_property, provides, register_factory, ) from traitsui.api import View, Item # Local imports from attractors.model.i_model_3d import IModel3d, IModel3dIPlottable2dMixin from attractors.model.i_plottable_2d import IPlottable2d @provides(IModel3d) class Lorenz(HasTraits): """ The model object for the Lorenz attractor. """ #### 'IModel3d' interface ################################################# name = Str("Lorenz Attractor") points = Property( Array, observe="prandtl, rayleigh, beta, initial_point, times" ) #### 'Lorenz' interface ################################################### # Equation parameters. prandtl = Float(10.0, auto_set=False, enter_set=True) rayleigh = Float(28.0, auto_set=False, enter_set=True) beta = Float(8.0 / 3.0, auto_set=False, enter_set=True) # Integration parameters. initial_point = Array(value=[0.0, 1.0, 0.0]) time_start = Float(0.0) time_stop = Float(100.0) time_step = Float(0.01) times = Property(Array, observe="time_start, time_stop, time_step") # Configuration view. view = View( Item("prandtl"), Item("rayleigh"), Item("beta"), Item("initial_point"), Item("time_start"), Item("time_stop"), Item("time_step"), resizable=True, ) ########################################################################### # 'Lorenz' interface. ########################################################################### def compute_step(self, point, time): x, y, z = point return array( [ self.prandtl * (y - x), x * (self.rayleigh - z) - y, x * y - self.beta * z, ] ) ########################################################################### # Protected interface. ########################################################################### @cached_property def _get_points(self): return odeint(self.compute_step, self.initial_point, self.times) @cached_property def _get_times(self): return arange(self.time_start, self.time_stop, self.time_step) @provides(IPlottable2d) class LorenzIPlottable2dAdapter(Adapter, IModel3dIPlottable2dMixin): adaptee = Instance(Lorenz) plot_type = Str("line") register_factory(LorenzIPlottable2dAdapter, Lorenz, IPlottable2d) envisage-6.0.1/envisage/examples/demo/plugins/tasks/attractors/model/rossler.py000066400000000000000000000047071406265421000300310ustar00rootroot00000000000000# System library imports. from scipy import arange, array from scipy.integrate import odeint # Enthought libary imports. from traits.api import ( Adapter, Array, Float, HasTraits, Instance, Property, Str, cached_property, provides, register_factory, ) from traitsui.api import View, Item # Local imports from attractors.model.i_model_3d import IModel3d, IModel3dIPlottable2dMixin from attractors.model.i_plottable_2d import IPlottable2d @provides(IModel3d) class Rossler(HasTraits): """ The model object for the Rossler attractor. """ #### 'IModel3d' interface ################################################# name = Str("Rossler Attractor") points = Property(Array, observe="a, b, c, initial_point, times") #### 'Rossler' interface ################################################## # Equation parameters. a = Float(0.2, auto_set=False, enter_set=True) b = Float(0.2, auto_set=False, enter_set=True) c = Float(5.7, auto_set=False, enter_set=True) # Integration parameters. initial_point = Array(value=[0.0, 1.0, 0.0]) time_start = Float(0.0) time_stop = Float(100.0) time_step = Float(0.01) times = Property(Array, observe="time_start, time_stop, time_step") # Configuration view. view = View( Item("a"), Item("b"), Item("c"), Item("initial_point"), Item("time_start"), Item("time_stop"), Item("time_step"), resizable=True, ) ########################################################################### # 'Rossler' interface. ########################################################################### def compute_step(self, point, time): x, y, z = point return array([-y - z, x + self.a * y, self.b + z * (x - self.c)]) ########################################################################### # Protected interface. ########################################################################### @cached_property def _get_points(self): return odeint(self.compute_step, self.initial_point, self.times) @cached_property def _get_times(self): return arange(self.time_start, self.time_stop, self.time_step) @provides(IPlottable2d) class RosslerIPlottable2dAdapter(Adapter, IModel3dIPlottable2dMixin): adaptee = Instance(Rossler) plot_type = Str("line") register_factory(RosslerIPlottable2dAdapter, Rossler, IPlottable2d) envisage-6.0.1/envisage/examples/demo/plugins/tasks/attractors/model_config_pane.py000066400000000000000000000012361406265421000306620ustar00rootroot00000000000000# Enthought library imports. from pyface.tasks.api import TraitsDockPane from traits.api import HasTraits, Instance from traitsui.api import Item, View class ModelConfigPane(TraitsDockPane): """ A simple dock pane for editing an attractor model's configuration options. """ #### 'ITaskPane' interface ################################################ id = "example.attractors.model_config_pane" name = "Model Configuration" #### 'ModelConfigPane' interface ########################################## model = Instance(HasTraits) view = View( Item("pane.model", style="custom", show_label=False), resizable=True ) envisage-6.0.1/envisage/examples/demo/plugins/tasks/attractors/model_help_pane.py000066400000000000000000000034461406265421000303520ustar00rootroot00000000000000# Standard library imports. import codecs import os.path # Enthought library imports. from pyface.tasks.api import TraitsDockPane from traits.api import HasTraits, Instance, Property, Str, cached_property from traitsui.api import HTMLEditor, Item, View # Constants. HELP_PATH = os.path.join(os.path.dirname(__file__), "help") class ModelHelpPane(TraitsDockPane): """ A dock pane for viewing any help associated with a model. """ #### 'ITaskPane' interface ################################################ id = "example.attractors.model_help_pane" name = "Model Information" #### 'ModelConfigPane' interface ########################################## model = Instance(HasTraits) html = Property(Str, observe="model") view = View( Item( "pane.html", editor=HTMLEditor(base_url=HELP_PATH, open_externally=True), show_label=False, ), width=300, resizable=True, ) ########################################################################### # Protected interface. ########################################################################### @cached_property def _get_html(self): """ Fetch the help HTML for the current model. """ if self.model is None: return "No model selected." # Determine the name of the model. model = self.model while hasattr(model, "adaptee"): model = model.adaptee name = model.__class__.__name__.lower() # Load HTML file, if possible. path = os.path.join(HELP_PATH, name + ".html") if os.path.isfile(path): with codecs.open(path, "r", "utf-8") as f: return f.read() else: return "No information available for model." envisage-6.0.1/envisage/examples/demo/plugins/tasks/attractors/plot_2d_pane.py000066400000000000000000000061561406265421000276060ustar00rootroot00000000000000# Enthought library imports. from chaco.chaco_plot_editor import ChacoPlotItem from pyface.tasks.api import TraitsTaskPane from traits.api import ( Dict, Enum, Instance, List, Property, Str, on_trait_change, ) from traitsui.api import EnumEditor, HGroup, Item, Label, View # Local imports. from attractors.model.i_plottable_2d import IPlottable2d class Plot2dPane(TraitsTaskPane): #### 'ITaskPane' interface ################################################ id = "example.attractors.plot_2d_pane" name = "Plot 2D Pane" #### 'Plot2dPane' interface ############################################### active_model = Instance(IPlottable2d) models = List(IPlottable2d) plot_type = Property(Str, observe="active_model.plot_type") title = Property(Str, observe="active_model.name") x_data = Property(observe="active_model.x_data") y_data = Property(observe="active_model.y_data") x_label = Property(Str, observe="active_model.x_label") y_label = Property(Str, observe="active_model.y_label") view = View( HGroup( Label("Model: "), Item("active_model", editor=EnumEditor(name="_enum_map")), show_labels=False, ), ChacoPlotItem( "x_data", "y_data", show_label=False, resizable=True, orientation="h", marker="pixel", marker_size=1, type_trait="plot_type", title="", x_label_trait="x_label", y_label_trait="y_label", color="blue", bgcolor="white", border_visible=False, border_width=1, ), resizable=True, ) #### Private traits ####################################################### _enum_map = Dict(IPlottable2d, Str) ########################################################################### # Protected interface. ########################################################################### #### Trait property getters/setters ####################################### def _get_plot_type(self): return self.active_model.plot_type if self.active_model else "line" def _get_title(self): return self.active_model.name if self.active_model else "" def _get_x_data(self): return self.active_model.x_data if self.active_model else [] def _get_y_data(self): return self.active_model.y_data if self.active_model else [] def _get_x_label(self): return self.active_model.x_label if self.active_model else "" def _get_y_label(self): return self.active_model.y_label if self.active_model else "" #### Trait change handlers ################################################ @on_trait_change("models[]") def _update_models(self): # Make sure that the active model is valid with the new model list. if self.active_model not in self.models: self.active_model = self.models[0] if self.models else None # Refresh the EnumEditor map. self._enum_map = dict((model, model.name) for model in self.models) envisage-6.0.1/envisage/examples/demo/plugins/tasks/attractors/plot_3d_pane.py000066400000000000000000000042661406265421000276070ustar00rootroot00000000000000# Enthought library imports. from mayavi.tools.mlab_scene_model import MlabSceneModel from mayavi.core.ui.mayavi_scene import MayaviScene from pyface.tasks.api import TraitsTaskPane from traits.api import ( Dict, Enum, Instance, List, Property, Str, on_trait_change, ) from traitsui.api import EnumEditor, HGroup, Item, Label, View from tvtk.pyface.scene_editor import SceneEditor # Local imports. from attractors.model.i_model_3d import IModel3d class Plot3dPane(TraitsTaskPane): #### 'ITaskPane' interface ################################################ id = "example.attractors.plot_3d_pane" name = "Plot 3D Pane" #### 'Plot3dPane' interface ############################################### active_model = Instance(IModel3d) models = List(IModel3d) scene = Instance(MlabSceneModel, ()) view = View( HGroup( Label("Model: "), Item("active_model", editor=EnumEditor(name="_enum_map")), show_labels=False, ), Item( "scene", editor=SceneEditor(scene_class=MayaviScene), show_label=False, ), resizable=True, ) #### Private traits ####################################################### _enum_map = Dict(IModel3d, Str) ########################################################################### # Protected interface. ########################################################################### #### Trait change handlers ################################################ @on_trait_change("active_model.points") def _update_scene(self): self.scene.mlab.clf() if self.active_model: x, y, z = self.active_model.points.swapaxes(0, 1) self.scene.mlab.plot3d(x, y, z, line_width=1.0, tube_radius=None) @on_trait_change("models[]") def _update_models(self): # Make sure that the active model is valid with the new model list. if self.active_model not in self.models: self.active_model = self.models[0] if self.models else None # Refresh the EnumEditor map. self._enum_map = dict((model, model.name) for model in self.models) envisage-6.0.1/envisage/examples/demo/plugins/tasks/attractors/preferences.ini000066400000000000000000000001411406265421000276540ustar00rootroot00000000000000[example.attractors] default_task = example.attractors.task_3d always_use_default_layout = False envisage-6.0.1/envisage/examples/demo/plugins/tasks/attractors/visualize_2d_task.py000066400000000000000000000056231406265421000306600ustar00rootroot00000000000000# Enthought library imports. from pyface.tasks.action.api import SGroup, SMenu, SMenuBar, TaskToggleGroup from pyface.tasks.api import Task, TaskLayout, Tabbed, PaneItem from traits.api import Any, Instance, List, adapt # Local imports. from attractors.model.i_plottable_2d import IPlottable2d from attractors.model_config_pane import ModelConfigPane from attractors.model_help_pane import ModelHelpPane from attractors.plot_2d_pane import Plot2dPane class Visualize2dTask(Task): """ A task for visualizing attractors in 2D. """ #### 'Task' interface ##################################################### id = "example.attractors.task_2d" name = "2D Visualization" menu_bar = SMenuBar( SMenu(id="File", name="&File"), SMenu(id="Edit", name="&Edit"), SMenu(TaskToggleGroup(), id="View", name="&View"), ) #### 'Visualize2dTask' interface ########################################## # The attractor model that is currently active (visible in the center # pane). active_model = Any # The list of available attractor models. models = List(Instance(IPlottable2d)) ########################################################################### # 'Task' interface. ########################################################################### def create_central_pane(self): """ Create a plot pane with a list of models. Keep track of which model is active so that dock panes can introspect it. """ pane = Plot2dPane(models=self.models) self.active_model = pane.active_model pane.on_trait_change(self._update_active_model, "active_model") return pane def create_dock_panes(self): return [ ModelConfigPane(model=self.active_model), ModelHelpPane(model=self.active_model), ] ########################################################################### # Protected interface. ########################################################################### #### Trait initializers ################################################### def _default_layout_default(self): return TaskLayout( left=Tabbed( PaneItem("example.attractors.model_config_pane"), PaneItem("example.attractors.model_help_pane"), ) ) def _models_default(self): from attractors.model.henon import Henon from attractors.model.lorenz import Lorenz from attractors.model.rossler import Rossler models = [Henon(), Lorenz(), Rossler()] return [adapt(model, IPlottable2d) for model in models] #### Trait change handlers ################################################ def _update_active_model(self): self.active_model = self.window.central_pane.active_model for dock_pane in self.window.dock_panes: dock_pane.model = self.active_model envisage-6.0.1/envisage/examples/demo/plugins/tasks/attractors/visualize_3d_task.py000066400000000000000000000052641406265421000306620ustar00rootroot00000000000000# Enthought library imports. from pyface.tasks.action.api import SGroup, SMenu, SMenuBar, TaskToggleGroup from pyface.tasks.api import Task, TaskLayout, Tabbed, PaneItem from traits.api import Any, List # Local imports. from attractors.model_config_pane import ModelConfigPane from attractors.model_help_pane import ModelHelpPane from attractors.plot_3d_pane import Plot3dPane class Visualize3dTask(Task): """ A task for visualizing attractors in 3D. """ #### 'Task' interface ##################################################### id = "example.attractors.task_3d" name = "3D Visualization" menu_bar = SMenuBar( SMenu(id="File", name="&File"), SMenu(id="Edit", name="&Edit"), SMenu(TaskToggleGroup(), id="View", name="&View"), ) #### 'Visualize3dTask' interface ########################################## # The attractor model that is currently active (visible in the center # pane). active_model = Any # The list of available attractor models. models = List ########################################################################### # 'Task' interface. ########################################################################### def create_central_pane(self): """ Create a plot pane with a list of models. Keep track of which model is active so that dock panes can introspect it. """ pane = Plot3dPane(models=self.models) self.active_model = pane.active_model pane.on_trait_change(self._update_active_model, "active_model") return pane def create_dock_panes(self): return [ ModelConfigPane(model=self.active_model), ModelHelpPane(model=self.active_model), ] ########################################################################### # Protected interface. ########################################################################### #### Trait initializers ################################################### def _default_layout_default(self): return TaskLayout( left=Tabbed( PaneItem("example.attractors.model_config_pane"), PaneItem("example.attractors.model_help_pane"), ) ) def _models_default(self): from attractors.model.lorenz import Lorenz from attractors.model.rossler import Rossler return [Lorenz(), Rossler()] #### Trait change handlers ################################################ def _update_active_model(self): self.active_model = self.window.central_pane.active_model for dock_pane in self.window.dock_panes: dock_pane.model = self.active_model envisage-6.0.1/envisage/examples/demo/plugins/tasks/index.rst000066400000000000000000000003121406265421000243250ustar00rootroot00000000000000Welcome to the attractors example. To run the application:: python run_attractor.py Note that this example application depends on the following additional packages:: numpy, scipy, chaco, mayavi envisage-6.0.1/envisage/examples/demo/plugins/tasks/run_attractor.py000066400000000000000000000014771406265421000257420ustar00rootroot00000000000000# Plugin imports. from envisage.api import CorePlugin from envisage.ui.tasks.api import TasksPlugin def main(argv): """ Run the application. """ # Import here so that this script can be run from anywhere without # having to install the packages. from attractors.attractors_plugin import AttractorsPlugin from attractors.attractors_application import AttractorsApplication plugins = [CorePlugin(), TasksPlugin(), AttractorsPlugin()] app = AttractorsApplication(plugins=plugins) app.run() if __name__ == "__main__": import sys # This context manager is added so that one can run this example from any # directory without necessarily having installed the examples as packages. from envisage.examples._demo import demo_path with demo_path(__file__): main(sys.argv) envisage-6.0.1/envisage/examples/tests/000077500000000000000000000000001406265421000201005ustar00rootroot00000000000000envisage-6.0.1/envisage/examples/tests/__init__.py000066400000000000000000000000001406265421000221770ustar00rootroot00000000000000envisage-6.0.1/envisage/examples/tests/test__demo.py000066400000000000000000000014441406265421000225770ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! import os import sys import unittest from envisage.examples._demo import demo_path class TestDemoUtilities(unittest.TestCase): """ Test utility functions in the _demo module.""" def test_sys_path_inserted(self): path = os.path.join("dirname", "file.py") with demo_path(path): self.assertIn("dirname", sys.path) self.assertNotIn("dirname", sys.path) envisage-6.0.1/envisage/examples/tests/test_etsdemo_info.py000066400000000000000000000013141406265421000241630ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! import os import pkg_resources import unittest from envisage.examples._etsdemo_info import info class TestETSDemoInfo(unittest.TestCase): def test_info(self): # input to info is currently just a placeholder response = info({}) self.assertTrue(os.path.exists(response['root'])) envisage-6.0.1/envisage/extension_point.py000066400000000000000000000174041406265421000207250ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ A trait type used to declare and access extension points. """ # Standard library imports. import inspect import weakref # Enthought library imports. from traits.api import List, TraitType, Undefined, provides # Local imports. from .i_extension_point import IExtensionPoint # Exception message template. INVALID_TRAIT_TYPE = ( 'extension points must be "List"s e.g. List, List(Int)' " but a value of %s was specified." ) # Even though trait types do not themselves have traits, we can still # declare that we implement an interface. @provides(IExtensionPoint) 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'). """ ########################################################################### # '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) @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) ########################################################################### # '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().__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() def __repr__(self): """ String representation of an ExtensionPoint object """ return "ExtensionPoint(id={!r})".format(self.id) ########################################################################### # '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) ########################################################################### # '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) 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 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] ########################################################################### # 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: 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 envisage-6.0.1/envisage/extension_point_binding.py000066400000000000000000000150161406265421000224140ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ A binding between a trait on an object and 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().__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) ########################################################################### # '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) 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)) #### 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 #### 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 ) 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.trait_set(trait_change_notify=notify, **traits) 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 ) def _set_extensions(self, extensions): """ Set the extensions to an extension point. """ self.extension_registry.set_extensions( self.extension_point_id, extensions ) # 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) envisage-6.0.1/envisage/extension_point_changed_event.py000066400000000000000000000023701406265421000235730ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ 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().__init__(**kw) # We add the extension point Id. self.extension_point_id = extension_point_id def __repr__(self): return ("ExtensionPointChangedEvent(extension_point_id={!r}, " "index={!r}, removed={!r}, added={!r})").format( self.extension_point_id, self.index, self.removed, self.added) envisage-6.0.1/envisage/extension_provider.py000066400000000000000000000035271406265421000214270ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ The default base class for extension providers. """ # Enthought library imports. from traits.api import Event, HasTraits, provides # Local imports. from .extension_point_changed_event import ExtensionPointChangedEvent from .i_extension_provider import IExtensionProvider @provides(IExtensionProvider) class ExtensionProvider(HasTraits): """ The default base class for extension providers. """ #### '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, ) envisage-6.0.1/envisage/extension_registry.py000066400000000000000000000157071406265421000214500ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ A base class for extension registry implementation. """ # Standard library imports. import logging import types import weakref # Enthought library imports. from traits.api import Dict, HasTraits, provides # Local imports. from .extension_point_changed_event import ExtensionPointChangedEvent from .i_extension_registry import IExtensionRegistry from .unknown_extension_point import UnknownExtensionPoint # Logging. logger = logging.getLogger(__name__) def _saferef(listener): """ Weak reference for a (possibly bound method) listener. Returns a weakref.WeakMethod reference for bound methods, and a regular weakref.ref otherwise. This means that for example ``_saferef(myobj.mymethod)`` returns a reference whose lifetime is connected to the lifetime of the object ``myobj``, rather than the lifetime of the temporary method ``myobj.mymethod``. Parameters ---------- listener : callable Listener to return a weak reference for. This can be either a plain function, a bound method, or some other form of callable. Returns ------- weakref.ref A weak reference to the listener. This will be a ``weakref.WeakMethod`` object if the listener is an instance of ``types.MethodType``, and a plain ``weakref.ref`` otherwise. """ if isinstance(listener, types.MethodType): return weakref.WeakMethod(listener) else: return weakref.ref(listener) @provides(IExtensionRegistry) class ExtensionRegistry(HasTraits): """ A base class for extension registry implementation. """ ########################################################################### # 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(_saferef(listener)) 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) 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 list(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(_saferef(listener)) 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) 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) ########################################################################### # 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) def _check_extension_point(self, extension_point_id): """ Check to see if the extension point exists. Raise an 'UnknownExtensionPoint' if it does not. """ if extension_point_id not in self._extension_points: raise UnknownExtensionPoint(extension_point_id) 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 envisage-6.0.1/envisage/i_application.py000066400000000000000000000042551406265421000203130ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ 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() """ envisage-6.0.1/envisage/i_extension_point.py000066400000000000000000000026151406265421000212330ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ 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) envisage-6.0.1/envisage/i_extension_point_user.py000066400000000000000000000015541406265421000222720ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ 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) envisage-6.0.1/envisage/i_extension_provider.py000066400000000000000000000024651406265421000217370ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ 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. """ envisage-6.0.1/envisage/i_extension_registry.py000066400000000000000000000054721406265421000217560ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ 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. """ envisage-6.0.1/envisage/i_import_manager.py000066400000000000000000000031501406265421000210050ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ 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. """ envisage-6.0.1/envisage/i_plugin.py000066400000000000000000000035411406265421000173030ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ 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) """ envisage-6.0.1/envisage/i_plugin_activator.py000066400000000000000000000024271406265421000213610ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ 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. """ envisage-6.0.1/envisage/i_plugin_manager.py000066400000000000000000000044021406265421000207720ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ 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. """ envisage-6.0.1/envisage/i_provider_extension_registry.py000066400000000000000000000017471406265421000236710ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ 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. """ envisage-6.0.1/envisage/i_service_registry.py000066400000000000000000000111351406265421000213730ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ 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. """ envisage-6.0.1/envisage/i_service_user.py000066400000000000000000000015051406265421000205010ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ 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) envisage-6.0.1/envisage/ids.py000066400000000000000000000050611406265421000162530ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ This module redefines the Extension Point IDs and Service IDs defined on Plugins provided by Envisage. Note that this module does not contain IDs defined by all of the Plugins available in Envisage. The Plugins themselves remain the ground truth for the IDs. This module is simply a convenient location from which the user can import the IDs. """ #### Extension Points ######################################################### #: Extension Point to contribute preference files, defined on the #: ``CorePlugin``. PREFERENCES = 'envisage.preferences' #: Extension Point to contribute ``ServiceOffer`` s, defined on the #: ``CorePlugin``. SERVICE_OFFERS = 'envisage.service_offers' # NOTE : The other PythonShellPlugin defines extension points with the same ID. #: Extension Point to contribute name/value pairs that will be bound to the #: ``PythonShell``, defined on the ``PythonShellPlugin``. BINDINGS = 'envisage.plugins.python_shell.bindings' #: Extension Point to contribute commands that will be executed in the #: ``PythonShell``, defined on the ``PythonShellPlugin``. COMMANDS = 'envisage.plugins.python_shell.commands' #: Extension Point to contribute name/value pairs that will be bound to the #: ``IPythonShell``, defined on the ``IPythonKernelPlugin``. IPYTHON_NAMESPACE = 'ipython_plugin.namespace' #: Extension Point to contribute preferences categories, defined on the #: ``TasksPlugin``. PREFERENCES_CATEGORIES = 'envisage.ui.tasks.preferences_categories' #: Extension Point to contribute preference panes, defined on the #: ``TasksPlugin``. PREFERENCES_PANES = 'envisage.ui.tasks.preferences_panes' #: Extension Point to contribute task factories, defined on the #: ``TasksPlugin``. TASKS = 'envisage.ui.tasks.tasks' #: Extension Point to contribute task extensions, defined on the #: ``TasksPlugin``. TASK_EXTENSIONS = 'envisage.ui.tasks.task_extensions' #### Services ################################################################ #: Service to access the active ``InternalIPKernel`` instance in the #: application, defined on the ``IPythonKernelPlugin``. IPYTHON_KERNEL_PROTOCOL = 'envisage.plugins.ipython_kernel.internal_ipkernel.InternalIPKernel' # noqa: E501 envisage-6.0.1/envisage/import_manager.py000066400000000000000000000047171406265421000205070ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ The default import manager implementation. """ # Enthought library imports. from traits.api import HasTraits, provides # Local imports. from .i_import_manager import IImportManager @provides(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). """ ########################################################################### # '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 envisage-6.0.1/envisage/package_plugin_manager.py000066400000000000000000000121621406265421000221370ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ A plugin manager that finds plugins in packages on the 'plugin_path'. """ import logging import 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 _update_path_and_reset_plugins(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) envisage-6.0.1/envisage/plugin.py000066400000000000000000000317371406265421000170030ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ The default implementation of the 'IPlugin' interface. """ # Standard library imports. import logging import os from os.path import exists, join # Enthought library imports. from traits.api import Instance, List, Property, Str, provides 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__) @provides(IPlugin, IExtensionPointUser, IServiceUser) class Plugin(ExtensionProvider): """ The default implementation of the 'IPlugin' interface. This class is intended to be subclassed for each plugin that you create. """ #### '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) if len(trait_names) == 0: extensions = [] 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.warning( "plugin {} has no Id - using <{}>".format( object.__repr__(self), id) ) return id def _name_default(self): """ Trait initializer. """ name = camel_case_to_words(type(self).__name__) logger.warning( "plugin {} has no name - using <{}>".format( object.__repr__(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().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().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) def disconnect_extension_point_traits(self): """ Disconnect all of the plugin's extension points.""" ExtensionPoint.disconnect_extension_point_traits(self) def register_services(self): """ Register the services offered by the plugin. """ for trait_name, trait in self.traits(service=True).items(): logger.warning( '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) 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: try: self.application.get_service_from_id(service_id) except ValueError: # the service may have already been individually unregistered pass else: self.application.unregister_service(service_id) # Just in case the plugin is started again! self._service_ids = [] ########################################################################### # 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, len(old)) # Let the extension registry know about the change. self._fire_extension_point_changed( trait.contributes_to, added, removed, index ) #### 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 Exception: 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 'Supports' 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 _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) ########################################################################### # 'object' interface. ########################################################################### def __repr__(self): """ String representation of a Plugin object """ return "Plugin(id={!r}, name={!r})".format(self.id, self.name) envisage-6.0.1/envisage/plugin_activator.py000066400000000000000000000031271406265421000210470ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ The default plugin activator. """ # Enthought library imports. from traits.api import HasTraits, provides # Local imports. from .i_plugin_activator import IPluginActivator @provides(IPluginActivator) class PluginActivator(HasTraits): """ The default plugin activator. """ ########################################################################### # '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() 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() envisage-6.0.1/envisage/plugin_event.py000066400000000000000000000012121406265421000201650ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ A 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") envisage-6.0.1/envisage/plugin_extension_registry.py000066400000000000000000000046021406265421000230160ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ An extension registry that uses plugins as extension providers. """ # Enthought library imports. from traits.api import Instance, observe, 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 ################################################ @observe("plugin_manager") def _update_providers(self, event): """ Static trait change handler. """ old, new = event.old, event.new # 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) @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) @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) envisage-6.0.1/envisage/plugin_manager.py000066400000000000000000000163041406265421000204660ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ A simple plugin manager implementation. """ from fnmatch import fnmatch import logging from traits.api import Event, HasTraits, Instance, List, observe, provides, Str 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__) @provides(IPluginManager) class PluginManager(HasTraits): """ A simple plugin manager implementation. This implementation manages an explicit collection of plugin instances, e.g:: plugin_manager = PluginManager(plugins=[MyPlugin(), YourPlugin()]) Plugins can be added and removed after construction time via the methods 'add_plugin' and 'remove_plugin'. """ #### '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) @observe("application") def _set_new_application_on_all_plugins(self, event): """ Static trait change handler. """ self._update_application_on_plugins([], self._plugins) #: 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().__init__(**traits) if plugins is not None: self._plugins = plugins 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) 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) def start(self): """ Start the plugin manager. """ for plugin in self._plugins: self.start_plugin(plugin) 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) 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() for plugin in stop_order: self.stop_plugin(plugin) 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) #### Protected 'PluginManager' ############################################ # The plugins that the manager manages! _plugins = List(IPlugin) @observe("_plugins") def _update_application_on_all_plugins(self, event): """ Static trait change handler. """ old, new = event.old, event.new self._update_application_on_plugins(old, new) @observe("_plugins:items") def _update_application_on_changed_plugins(self, event): """ Static trait change handler. """ self._update_application_on_plugins(event.removed, event.added) 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_application_on_plugins(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 envisage-6.0.1/envisage/plugins/000077500000000000000000000000001406265421000166015ustar00rootroot00000000000000envisage-6.0.1/envisage/plugins/__init__.py000066400000000000000000000000001406265421000207000ustar00rootroot00000000000000envisage-6.0.1/envisage/plugins/event_manager/000077500000000000000000000000001406265421000214145ustar00rootroot00000000000000envisage-6.0.1/envisage/plugins/event_manager/__init__.py000066400000000000000000000000001406265421000235130ustar00rootroot00000000000000envisage-6.0.1/envisage/plugins/event_manager/api.py000066400000000000000000000010041406265421000225320ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ - :class:`~.EventManagerPlugin` """ from envisage.plugins.event_manager.plugin import EventManagerPlugin envisage-6.0.1/envisage/plugins/event_manager/plugin.py000066400000000000000000000025411406265421000232660ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ 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 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-6.0.1/envisage/plugins/ipython_kernel/000077500000000000000000000000001406265421000216335ustar00rootroot00000000000000envisage-6.0.1/envisage/plugins/ipython_kernel/__init__.py000066400000000000000000000000001406265421000237320ustar00rootroot00000000000000envisage-6.0.1/envisage/plugins/ipython_kernel/actions.py000066400000000000000000000015061406265421000236470ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! from pyface.tasks.action.api import TaskAction from traits.api import Instance from .internal_ipkernel import InternalIPKernel class StartQtConsoleAction(TaskAction): """ Open in a separate window a Qt console attached to a, existing kernel. """ id = "ipython_qtconsole" name = "IPython Console" kernel = Instance(InternalIPKernel) def perform(self, event=None): self.kernel.new_qt_console() envisage-6.0.1/envisage/plugins/ipython_kernel/api.py000066400000000000000000000016731406265421000227650ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ - :class:`~.StartQtConsoleAction` - :class:`~.InternalIPKernel` - :class:`~.IPythonKernelPlugin` - :attr:`~.IPYTHON_KERNEL_PROTOCOL` - :class:`~.IPythonKernelUIPlugin` """ from envisage.plugins.ipython_kernel.actions import StartQtConsoleAction from envisage.plugins.ipython_kernel.internal_ipkernel import InternalIPKernel from envisage.plugins.ipython_kernel.ipython_kernel_plugin import ( IPythonKernelPlugin, IPYTHON_KERNEL_PROTOCOL, ) from envisage.plugins.ipython_kernel.ipython_kernel_ui_plugin import ( IPythonKernelUIPlugin, ) envisage-6.0.1/envisage/plugins/ipython_kernel/heartbeat.py000066400000000000000000000040771406265421000241540ustar00rootroot00000000000000# (C) Copyright 2019-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ A simple ping-pong style heartbeat that runs in a thread. Modified from upstream to enable the heartbeat thread to be shut down cleanly. """ # We're currently using ipykernel v4.10.1. In that version, there's no way to # cleanly shut down the Heartbeat thread. However, ipykernel v5.x allows the # thread to be shut down by terminating the corresponding context. The relevant # code is here: # # https://github.com/ipython/ipykernel/blob/18f2ef77b6a72109a1e50d8229e7216f1cfc2e39/ipykernel/heartbeat.py#L103-L111 # # The key change is to explicitly catch the termination attempt and close # the socket. Without this, the Context.term call from the main thread # will hang: the open socket prevents termination. # # This version of Heartbeat subclasses the upstream version to introduce the # minimal changes necessary to make shutdown feasible with the v4.10.1 code. # # Once we're using ipykernel 5.x, this module can be removed and we can # revert to using the upstream Heartbeat class. import ipykernel.heartbeat import zmq class Heartbeat(ipykernel.heartbeat.Heartbeat): """ A simple ping-pong style heartbeat that runs in a thread. Modified from upstream to enable the thread to be shut down cleanly. """ def run(self): try: super().run() except zmq.ZMQError as e: # We expect to get here on normal context termination, but # not otherwise; re-raise if the error is not the expected one # (but close the socket anyway, to allow the context termination # to complete without blocking the main thread). self.socket.close() if e.errno != zmq.ETERM: raise envisage-6.0.1/envisage/plugins/ipython_kernel/internal_ipkernel.py000066400000000000000000000107241406265421000257160ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ This code has been inspired from the IPython repository https://github.com/ipython/ipython/blob/2.x/examples/Embedding/internal_ipkernel.py """ import atexit import warnings import ipykernel.connect from envisage.plugins.ipython_kernel.kernelapp import IPKernelApp from traits.api import Any, HasStrictTraits, Instance, List def _gui_kernel(gui_backend): """ Launch and return an IPython kernel GUI with support. Parameters ---------- gui_backend -- string or None The GUI mode used to initialize the GUI mode. For options, see the `ipython --gui` help pages. If None, the kernel is initialized without GUI support. """ kernel = IPKernelApp.instance() argv = ["python"] if gui_backend is not None: argv.append("--gui={}".format(gui_backend)) kernel.initialize(argv) return kernel class InternalIPKernel(HasStrictTraits): """ Represents an IPython kernel and the consoles attached to it. """ #: The IPython kernel. ipkernel = Instance(IPKernelApp) #: A list of connected Qt consoles. consoles = List() #: The kernel namespace. #: Use `Any` instead of `Dict` because this is an IPython dictionary #: object. namespace = Any() #: The values used to initialize the kernel namespace. #: This is a list of tuples (name, value). initial_namespace = List() def init_ipkernel(self, gui_backend=None): """ Initialize the IPython kernel. Parameters ---------- gui_backend -- string, optional The GUI mode used to initialize the GUI event loop integration. For options, see the `ipython --gui` help pages. If not given, no event loop integration is set up. .. note:: Use of this argument is deprecated! """ # For backwards compatibility, we allow a kernel to be initialized # twice, and we ignore the second initialization, but warn. if self.ipkernel is not None: warnings.warn( ( "The IPython kernel has already been initialized. A " "second call to init_ipkernel has no effect. In the " "future, a second initialization may become an error." ), DeprecationWarning, ) return if gui_backend is not None: warnings.warn( ( "The gui_backend argument is deprecated. " "Integration with a GUI event loop can be " "achieved by setting the 'eventloop' attribute on the " "kernel instance" ), DeprecationWarning, ) # Start IPython kernel with GUI event loop support self.ipkernel = _gui_kernel(gui_backend) # This application will also act on the shell user namespace self.namespace = self.ipkernel.shell.user_ns self.namespace.update(dict(self.initial_namespace)) def new_qt_console(self): """ Start a new qtconsole connected to our kernel. """ console = ipykernel.connect.connect_qtconsole( self.ipkernel.connection_file, argv=["--no-confirm-exit"], ) self.consoles.append(console) return console def cleanup_consoles(self): """ Kill all existing consoles. """ for c in self.consoles: c.kill() c.wait() c.stdout.close() c.stderr.close() self.consoles = [] def shutdown(self): """ Shut the kernel down. Existing IPython consoles are killed first. """ if self.ipkernel is not None: self.cleanup_consoles() self.ipkernel.close() # ipkernel.close is only registered for ipykernel 5.1.2 and later, # but unregistering something that wasn't registered is safe. atexit.unregister(self.ipkernel.close) self.ipkernel = None # Remove stored singleton to facilitate garbage collection. IPKernelApp.clear_instance() envisage-6.0.1/envisage/plugins/ipython_kernel/ipython_kernel_plugin.py000066400000000000000000000101511406265421000266130ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ An IPython kernel plugin. """ import logging import warnings # Enthought library imports. from envisage.api import ( bind_extension_point, ExtensionPoint, Plugin, ServiceOffer, ) from traits.api import Bool, Instance, List # Constants kept around for backwards compatibility. # These will be removed in a future release. # Extension point IDs. SERVICE_OFFERS = "envisage.service_offers" IPYTHON_NAMESPACE = "ipython_plugin.namespace" # Protocol for the contributed service offer. IPYTHON_KERNEL_PROTOCOL = ( "envisage.plugins.ipython_kernel.internal_ipkernel.InternalIPKernel") logger = logging.getLogger(__name__) class IPythonKernelPlugin(Plugin): """ An IPython kernel plugin. """ #: The plugin unique identifier. id = "envisage.plugins.ipython_kernel" #: The plugin name (suitable for displaying to the user). name = "IPython embedded kernel plugin" #: Extension point for objects contributed to the IPython kernel namespace. IPYTHON_NAMESPACE = 'ipython_plugin.namespace' #: Ipython kernel namespace ExtensionPoint kernel_namespace = ExtensionPoint( List, id=IPYTHON_NAMESPACE, desc=""" Variables to add to the IPython kernel namespace. This is a list of tuples (name, value). """, ) #: Service offers contributed by this plugin. service_offers = List(contributes_to=SERVICE_OFFERS) # Protocol for the contributed service offer. IPYTHON_KERNEL_PROTOCOL = 'envisage.plugins.ipython_kernel.internal_ipkernel.InternalIPKernel' # noqa: E501 #: Whether to initialize the kernel when the service is created. #: The default is ``False```, for backwards compatibility. It will change #: to ``True`` in a future version of Envisage. External users wanting #: to use the future behaviour now should pass ``init_ipkernel=True`` #: when creating the plugin. init_ipkernel = Bool(False) def stop(self): """ Stop the plugin. """ self._destroy_kernel() # Private traits and methods #: The InternalIPKernel instance provided by the service. _kernel = Instance(IPYTHON_KERNEL_PROTOCOL) def _create_kernel(self): from .internal_ipkernel import InternalIPKernel # This shouldn't happen with a normal lifecycle, but add a warning # just in case. if self._kernel is not None: warnings.warn( "A kernel already exists. " "No new kernel will be created.", RuntimeWarning, ) return logger.debug("Creating the embedded IPython kernel") kernel = self._kernel = InternalIPKernel() bind_extension_point( kernel, "initial_namespace", IPYTHON_NAMESPACE, self.application ) if self.init_ipkernel: kernel.init_ipkernel() else: warnings.warn( ( "In the future, the IPython kernel will be initialized " "automatically at creation time. To enable this " "future behaviour now, create the plugin using " "IPythonKernelPlugin(init_ipkernel=True)" ), DeprecationWarning, ) return kernel def _destroy_kernel(self): """ Destroy any existing kernel. """ if self._kernel is None: return logger.debug("Shutting down the embedded IPython kernel") self._kernel.shutdown() self._kernel = None def _service_offers_default(self): ipython_kernel_service_offer = ServiceOffer( protocol=IPYTHON_KERNEL_PROTOCOL, factory=self._create_kernel, ) return [ipython_kernel_service_offer] envisage-6.0.1/envisage/plugins/ipython_kernel/ipython_kernel_ui_plugin.py000066400000000000000000000037261406265421000273220ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ An IPython kernel plugin. """ # Enthought library imports. from envisage.plugins.ipython_kernel.ipython_kernel_plugin import ( IPYTHON_KERNEL_PROTOCOL, ) from envisage.ui.tasks.api import TaskExtension from envisage.api import Plugin from traits.api import List from pyface.tasks.action.api import SGroup, SchemaAddition TASK_EXTENSIONS = "envisage.ui.tasks.task_extensions" class IPythonKernelUIPlugin(Plugin): """ Contributes UI actions on top of the IPython Kernel Plugin. """ #### 'IPlugin' interface ################################################## #: The plugin unique identifier. id = "envisage.plugins.ipython_kernel_ui" #: The plugin name (suitable for displaying to the user). name = "IPython embedded kernel UI plugin" #### Contributions to extension points made by this plugin ################ contributed_task_extensions = List(contributes_to=TASK_EXTENSIONS) #### Trait initializers ################################################### def _contributed_task_extensions_default(self): from .actions import StartQtConsoleAction def menu_factory(): kernel = self.application.get_service(IPYTHON_KERNEL_PROTOCOL) return SGroup(StartQtConsoleAction(kernel=kernel), id="ipython") return [ TaskExtension( actions=[ SchemaAddition( path="MenuBar/View", factory=menu_factory, id="IPythonSchema", ), ] ) ] envisage-6.0.1/envisage/plugins/ipython_kernel/kernelapp.py000066400000000000000000000275431406265421000242010ustar00rootroot00000000000000# (C) Copyright 2019-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ This module contains an extended version of the upstream ipykernel IPKernelApp. The main reason for extending is to support clean shutdown. """ import atexit import logging import os import sys import ipykernel.ipkernel import ipykernel.kernelapp import ipykernel.zmqshell import IPython.utils.io import zmq from envisage.plugins.ipython_kernel.heartbeat import Heartbeat # Sentinel object used to represent a missing attribute. _MISSING = object() class IPKernelApp(ipykernel.kernelapp.IPKernelApp): """ Patched version of the IPKernelApp, mostly to support clean shutdown. """ # Methods overridden from the base class ################################## def init_heartbeat(self): """start the heart beating Overridden from the base class in order to swap in our own Heartbeat class in place of the official one. Our Heartbeat class is modified to allow the heartbeat thread to be shut down cleanly. This override can be removed once we're on ipkernel 5.x. """ # heartbeat doesn't share context, because it mustn't be blocked # by the GIL, which is accessed by libzmq when freeing zero-copy # messages hb_ctx = zmq.Context() self.heartbeat = Heartbeat( hb_ctx, (self.transport, self.ip, self.hb_port), ) self.hb_port = self.heartbeat.port self.log.debug("Heartbeat REP Channel on port: %i" % self.hb_port) self.heartbeat.start() def patch_io(self): """Patch important libraries that can't handle sys.stdout forwarding. Overridden from the base class to do nothing. The base class method monkeypatches faulthandler so that calling faulthandler.enable() while streams are redirected doesn't fail. This override bypasses that patching. Users of the Envisage plugin are advised to enable faulthandler (if desired) as part of application setup, before the plugin is started. Related: https://github.com/ipython/ipykernel/issues/91 """ pass def configure_tornado_logger(self): """ Configure tornado logging. Adds a NullHandler to the tornado root logger, if there are no handlers already present on that logger. If no tornado handler is present at the time the IO loop is started, tornado will call logging.basicConfig, which isn't what we want to happen. Overridden from the base class, which unconditionally adds a new StreamHandler every time. See also: - https://github.com/tornadoweb/tornado/blob/v6.0.3/tornado/ioloop.py#L427-L445 # noqa: E501 - https://github.com/tornadoweb/tornado/pull/741 """ logger = logging.getLogger("tornado") if not logger.handlers: logger.addHandler(logging.NullHandler()) def log_connection_info(self): """ Display connection info, and store ports. Overridden to not write information to __stdout__. We don't usually want this in applications that embed an IPython kernel (as opposed to the case where IPython effectively *is* the application). """ basename = os.path.basename(self.connection_file) if ( basename == self.connection_file or os.path.dirname(self.connection_file) == self.connection_dir ): # use shortname tail = basename else: tail = self.connection_file lines = [ "To connect another client to this kernel, use:", " --existing %s" % tail, ] # log connection info # info-level, so often not shown. # frontends should use the %connect_info magic # to see the connection info for line in lines: self.log.info(line) self.ports = dict( shell=self.shell_port, iopub=self.iopub_port, stdin=self.stdin_port, hb=self.hb_port, control=self.control_port, ) # Methods extending the base class methods ################################ def init_crash_handler(self): """ Set up a suitable exception hook. Extended to keep track of the original sys.excepthook value, so that it can be restored later. """ self._original_sys_excepthook = sys.excepthook super().init_crash_handler() def init_io(self): """ Redirect input streams and set a display hook. Extended to store the original sys attributes so that they can be restored later. """ if self.outstream_class: self._original_sys_stdout = sys.stdout self._original_sys_stderr = sys.stderr if self.displayhook_class: self._original_sys_displayhook = sys.displayhook super().init_io() def init_kernel(self): """ Create the kernel object itself. Extended to store the original values of IPython.utils.io.stdout and IPython.utils.io.stderr, so that they can be restored later. """ self._original_ipython_utils_io_stdout = getattr( IPython.utils.io, "stdout", _MISSING ) self._original_ipython_utils_io_stderr = getattr( IPython.utils.io, "stderr", _MISSING ) super().init_kernel() # New methods, mostly to control shutdown ################################# def close(self): """ Undo the effects of the initialize method: - free resources allocated during initialization - undo changes to global state """ # Note: the upstream ipykernel introduced its own close method in # v5.1.2, along with an atexit handler for that method. See # https://github.com/ipython/ipykernel/pull/412. For ipykernel versions # of 5.1.2 or later, this method overrides the base class version. self.close_shell() self.close_kernel() self.close_io() self.close_heartbeat() self.close_sockets() self.cleanup_connection_file() atexit.unregister(self.cleanup_connection_file) self.close_crash_handler() self.close_profile_dir() self.cleanup_singletons() def close_shell(self): """ Clean up resources allocated by the shell. """ shell = self.kernel.shell # Clean up script magics object, which is contained in a reference # cycle, and has a __del__ method, preventing its removal in Python 2. magics_manager = shell.magics_manager script_magics = magics_manager.registry["ScriptMagics"] script_magics.kill_bg_processes() atexit.unregister(script_magics.kill_bg_processes) script_magics.magics.clear() script_magics.shell = None script_magics.parent = None # The shell's cleanup method restores the sys.module changes. shell.cleanup() # The atexit_operations method ends the history manager session, # but doesn't stop the history manager's save_thread, so we need # to do that separately. shell.atexit_operations() atexit.unregister(shell.atexit_operations) shell.history_manager.save_thread.stop() atexit.unregister(shell.history_manager.save_thread.stop) # Rely on garbage collection to clean up the file connection. shell.history_manager.db.close() # Remove some references to avoid keeping objects alive unnecessarily. del shell.configurables[:] del shell.sys_excepthook del shell._orig_sys_module_state del shell._orig_sys_modules_main_mod def close_kernel(self): """ Undo setup from init_kernel. """ # Unhook listeners, and close kernel streams (which also closes # the corresponding zmq.Socket objects). kernel = self.kernel while kernel.shell_streams: stream = kernel.shell_streams.pop() stream.stop_on_recv() stream.close() # Remove selected references to allow effective garbage collection. kernel.stdin_socket = None # Undo changes to IPython.utils.io made at shell creation time. # The values written by the shell keep references that prevent # proper garbage collection from taking place. if self._original_ipython_utils_io_stderr is _MISSING: del IPython.utils.io.stderr else: IPython.utils.io.stderr = self._original_ipython_utils_io_stderr del self._original_ipython_utils_io_stderr if self._original_ipython_utils_io_stdout is _MISSING: del IPython.utils.io.stdout else: IPython.utils.io.stdout = self._original_ipython_utils_io_stdout del self._original_ipython_utils_io_stdout def close_io(self): """ Undo the effects of init_io. Restores sys module attributes altered by init_io. """ if self.displayhook_class: sys.displayhook = self._original_sys_displayhook del self._original_sys_displayhook if self.outstream_class: sys.stderr.close() sys.stderr = self._original_sys_stderr del self._original_sys_stderr sys.stdout.close() sys.stdout = self._original_sys_stdout del self._original_sys_stdout def close_iopub(self): """ Close iopub-related resources. """ iopub_socket = self.iopub_thread.socket # Remove the atexit handler that's registered. self.iopub_thread.stop() atexit.unregister(self.iopub_thread.stop) iopub_socket.close() def close_crash_handler(self): """ Undo the global state change from init_crash_handler. Restore the sys.excepthook attribute. """ sys.excepthook = self._original_sys_excepthook del self._original_sys_excepthook def close_heartbeat(self): """ Stop the heartbeat thread, by terminating the corresponding zmq.Context. """ # This should interrupt the zmq.device call. self.heartbeat.context.term() self.heartbeat.join() def close_sockets(self): """ Unbind, close and destroy sockets created by init_sockets. """ self.close_iopub() for channel in ("shell", "control", "stdin"): self.log.debug("Closing %s channel", channel) socket = getattr(self, channel + "_socket", None) if socket and not socket.closed: socket.close() # ipykernel 5.1.2 and later creates its own context. Earlier versions # use the shared zmq context. Ref: ipython/ipykernel#412. if hasattr(self, "context"): self.log.debug("Terminating zmq context") self.context.term() self.log.debug("Terminated zmq context") def close_profile_dir(self): """ Undo changes made in init_profile_dir. """ ipython_dir_entry = os.path.abspath(self.ipython_dir) if ipython_dir_entry in sys.path: sys.path.remove(ipython_dir_entry) def cleanup_singletons(self): """ Clear SingletonConfigurable instances. """ # These instances will otherwise hinder garbage collection, # and prevent a clean recreation of a new kernel app. ipykernel.zmqshell.ZMQInteractiveShell.clear_instance() ipykernel.ipkernel.IPythonKernel.clear_instance() envisage-6.0.1/envisage/plugins/ipython_kernel/tests/000077500000000000000000000000001406265421000227755ustar00rootroot00000000000000envisage-6.0.1/envisage/plugins/ipython_kernel/tests/__init__.py000066400000000000000000000000001406265421000250740ustar00rootroot00000000000000envisage-6.0.1/envisage/plugins/ipython_kernel/tests/test_internal_ipkernel.py000066400000000000000000000242351406265421000301210ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! import atexit import gc import os import shutil import sys import tempfile import threading import unittest from unittest import mock import warnings try: import ipykernel # noqa: F401 except ImportError: ipykernel_available = False else: ipykernel_available = True if ipykernel_available: import ipykernel.iostream import ipykernel.ipkernel import ipykernel.kernelapp import ipykernel.zmqshell import IPython.utils.io import tornado.ioloop import zmq from envisage.plugins.ipython_kernel.api import InternalIPKernel @unittest.skipUnless( ipykernel_available, "skipping tests that require the ipykernel package" ) class TestInternalIPKernel(unittest.TestCase): def setUp(self): # Make sure that IPython-related files are written to a temporary # directory instead of the home directory. tmpdir = tempfile.mkdtemp() self.addCleanup(shutil.rmtree, tmpdir) self._old_ipythondir = os.environ.get("IPYTHONDIR") os.environ["IPYTHONDIR"] = tmpdir def tearDown(self): # Restore previous state of the IPYTHONDIR environment variable. old_ipythondir = self._old_ipythondir if old_ipythondir is None: del os.environ["IPYTHONDIR"] else: os.environ["IPYTHONDIR"] = old_ipythondir def test_lifecycle(self): kernel = InternalIPKernel() self.assertIsNone(kernel.ipkernel) kernel.init_ipkernel(gui_backend=None) self.assertIsNotNone(kernel.ipkernel) self.assertIsInstance(kernel.ipkernel, ipykernel.kernelapp.IPKernelApp) kernel.new_qt_console() kernel.new_qt_console() self.assertEqual(len(kernel.consoles), 2) kernel.shutdown() self.assertIsNone(kernel.ipkernel) self.assertEqual(len(kernel.consoles), 0) def test_initial_namespace(self): kernel = InternalIPKernel(initial_namespace=[("x", 42.1)]) kernel.init_ipkernel(gui_backend=None) self.assertIn("x", kernel.namespace) self.assertEqual(kernel.namespace["x"], 42.1) kernel.shutdown() def test_shutdown_restores_output_streams(self): original_stdin = sys.stdin original_stdout = sys.stdout original_stderr = sys.stderr self.create_and_destroy_kernel() self.assertIs(sys.stdin, original_stdin) self.assertIs(sys.stdout, original_stdout) self.assertIs(sys.stderr, original_stderr) def test_shutdown_restores_sys_modules_main(self): original_sys_modules_main = sys.modules["__main__"] self.create_and_destroy_kernel() self.assertIs(sys.modules["__main__"], original_sys_modules_main) def test_shutdown_restores_displayhook_and_excepthook(self): original_displayhook = sys.displayhook original_excepthook = sys.excepthook self.create_and_destroy_kernel() self.assertIs(sys.displayhook, original_displayhook) self.assertIs(sys.excepthook, original_excepthook) def test_shutdown_restores_sys_path(self): original_sys_path = sys.path[:] self.create_and_destroy_kernel() self.assertEqual(sys.path, original_sys_path) def test_shutdown_closes_console_pipes(self): kernel = InternalIPKernel() kernel.init_ipkernel(gui_backend=None) console = kernel.new_qt_console() self.assertFalse(console.stdout.closed) self.assertFalse(console.stderr.closed) kernel.shutdown() self.assertTrue(console.stdout.closed) self.assertTrue(console.stderr.closed) def test_ipython_util_io_globals_restored(self): original_io_stdin = IPython.utils.io.stdin original_io_stdout = IPython.utils.io.stdout original_io_stderr = IPython.utils.io.stderr self.create_and_destroy_kernel() self.assertIs(IPython.utils.io.stdin, original_io_stdin) self.assertIs(IPython.utils.io.stdout, original_io_stdout) self.assertIs(IPython.utils.io.stderr, original_io_stderr) def test_ipython_util_io_globals_restored_if_they_dont_exist(self): # Regression test for enthought/envisage#218 original_io_stdin = IPython.utils.io.stdin original_io_stdout = IPython.utils.io.stdout original_io_stderr = IPython.utils.io.stderr del IPython.utils.io.stdin del IPython.utils.io.stdout del IPython.utils.io.stderr try: self.create_and_destroy_kernel() self.assertFalse(hasattr(IPython.utils.io, "stdin")) self.assertFalse(hasattr(IPython.utils.io, "stdout")) self.assertFalse(hasattr(IPython.utils.io, "stderr")) finally: IPython.utils.io.stdin = original_io_stdin IPython.utils.io.stdout = original_io_stdout IPython.utils.io.stderr = original_io_stderr def test_io_pub_thread_stopped(self): self.create_and_destroy_kernel() io_pub_threads = self.objects_of_type(ipykernel.iostream.IOPubThread) for thread in io_pub_threads: self.assertFalse(thread.thread.is_alive()) def test_no_threads_leaked(self): threads_before = threading.active_count() self.create_and_destroy_kernel() threads_after = threading.active_count() self.assertEqual(threads_before, threads_after) def test_no_new_atexit_handlers(self): # Caution: this is a rather fragile and indirect test. We want # to know that all cleanup has happened when shutting down the # kernel, with none of that cleanup deferred to atexit handlers. # # Since we have no direct way to get hold of the atexit handlers on # Python 3, we instead use the number of referents from the # atexit module as a proxy. # # If this test starts failing, try adding a warmup cycle. If the # first call to self.create_and_destroy_kernel adds new referents, # that's not a big deal. But if every call consistently adds new # referents, then there's something to be fixed. atexit_handlers_before = len(gc.get_referents(atexit)) self.create_and_destroy_kernel() atexit_handlers_after = len(gc.get_referents(atexit)) self.assertEqual(atexit_handlers_before, atexit_handlers_after) def test_zmq_sockets_closed(self): # Previously, tests were leaking file descriptors linked to # zmq.Socket objects. Check that all extant sockets are closed. self.create_and_destroy_kernel() sockets = self.objects_of_type(zmq.Socket) self.assertTrue(all(socket.closed for socket in sockets)) def test_ipykernel_live_objects(self): # Check that all IPython-related objects have been cleaned up # as expected. self.create_and_destroy_kernel() # Remove anything kept alive by cycles. (There are too many cycles # to break them individually.) gc.collect() shells = self.objects_of_type(ipykernel.zmqshell.ZMQInteractiveShell) self.assertEqual(shells, []) kernels = self.objects_of_type(ipykernel.ipkernel.IPythonKernel) self.assertEqual(kernels, []) kernel_apps = self.objects_of_type(ipykernel.kernelapp.IPKernelApp) self.assertEqual(kernel_apps, []) def test_initialize_twice(self): # Trying to re-initialize an already initialized IPKernelApp can # happen right now as a result of refactoring, but eventually # it should be an error. For now, it's a warning. kernel = InternalIPKernel() self.assertIsNone(kernel.ipkernel) kernel.init_ipkernel(gui_backend=None) try: self.assertIsNotNone(kernel.ipkernel) ipkernel = kernel.ipkernel with warnings.catch_warnings(record=True) as warn_msgs: warnings.simplefilter("always", category=DeprecationWarning) kernel.init_ipkernel(gui_backend=None) # Check that the existing kernel has not been replaced. self.assertIs(ipkernel, kernel.ipkernel) finally: kernel.shutdown() # Check that we got the expected warning message. self.assertEqual(len(warn_msgs), 1) message = str(warn_msgs[0].message) self.assertIn("already been initialized", message) def test_init_ipkernel_with_explicit_gui_backend(self): loop = tornado.ioloop.IOLoop.current() # Kernel initialization adds an "enter_eventloop" call to the # ioloop event loop queue. Mock to avoid modifying the actual event # loop. with mock.patch.object(loop, "add_callback") as mock_add_callback: with warnings.catch_warnings(record=True) as warn_msgs: warnings.simplefilter("always", category=DeprecationWarning) # Use of gui_backend is deprecated. kernel = InternalIPKernel() kernel.init_ipkernel(gui_backend="qt4") kernel.shutdown() mock_add_callback.reset_mock() # Check that we got the expected warning message. matching_messages = [ msg for msg in warn_msgs if "gui_backend argument is deprecated" in str(msg.message) ] self.assertEqual(len(matching_messages), 1) # Helper functions. def objects_of_type(self, type): """ Find and return a list of all currently tracked instances of the given type. """ return [obj for obj in gc.get_objects() if isinstance(obj, type)] def create_and_destroy_kernel(self): """ Set up a new kernel with two associated consoles, then shut everything down. """ kernel = InternalIPKernel() kernel.init_ipkernel(gui_backend=None) kernel.new_qt_console() kernel.new_qt_console() kernel.shutdown() envisage-6.0.1/envisage/plugins/ipython_kernel/tests/test_ipython_kernel_plugin.py000066400000000000000000000153001406265421000310150ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! import contextlib import os import shutil import tempfile import unittest from unittest import mock import warnings from traits.api import List from envisage.api import Application, CorePlugin, Plugin from envisage.tests.ets_config_patcher import ETSConfigPatcher # Skip these tests unless ipykernel is available. try: import ipykernel # noqa: F401 except ImportError: ipykernel_available = False else: ipykernel_available = True if ipykernel_available: from envisage.plugins.ipython_kernel.api import ( InternalIPKernel, IPythonKernelPlugin, IPYTHON_KERNEL_PROTOCOL, ) from envisage.plugins.ipython_kernel.ipython_kernel_plugin import ( IPYTHON_NAMESPACE, ) @unittest.skipUnless( ipykernel_available, "skipping tests that require the ipykernel package" ) class TestIPythonKernelPlugin(unittest.TestCase): def setUp(self): ets_config_patcher = ETSConfigPatcher() ets_config_patcher.start() self.addCleanup(ets_config_patcher.stop) # Make sure that IPython-related files are written to a temporary # directory instead of the home directory. tmpdir = tempfile.mkdtemp() self.addCleanup(shutil.rmtree, tmpdir) self._old_ipythondir = os.environ.get("IPYTHONDIR") os.environ["IPYTHONDIR"] = tmpdir def tearDown(self): # Restore previous state of the IPYTHONDIR environment variable. old_ipythondir = self._old_ipythondir if old_ipythondir is None: del os.environ["IPYTHONDIR"] else: os.environ["IPYTHONDIR"] = old_ipythondir def test_import_from_api(self): # Regression test for enthought/envisage#108 from envisage.plugins.ipython_kernel.api import IPYTHON_KERNEL_PROTOCOL self.assertIsInstance(IPYTHON_KERNEL_PROTOCOL, str) def test_kernel_service(self): # See that we can get the IPython kernel service when the plugin is # there. with self.running_app() as app: kernel = app.get_service(IPYTHON_KERNEL_PROTOCOL) self.assertIsInstance(kernel, InternalIPKernel) self.assertIsNotNone(kernel.ipkernel) # After application stop, the InternalIPKernel object should # also have been shut down. self.assertIsNone(kernel.ipkernel) def test_kernel_namespace_extension_point(self): class NamespacePlugin(Plugin): kernel_namespace = List(contributes_to=IPYTHON_NAMESPACE) def _kernel_namespace_default(self): return [("y", "hi")] plugins = [ IPythonKernelPlugin(init_ipkernel=True), NamespacePlugin(), ] with self.running_app(plugins=plugins) as app: kernel = app.get_service(IPYTHON_KERNEL_PROTOCOL) self.assertIn("y", kernel.namespace) self.assertEqual(kernel.namespace["y"], "hi") def test_get_service_twice(self): with self.running_app() as app: kernel1 = app.get_service(IPYTHON_KERNEL_PROTOCOL) kernel2 = app.get_service(IPYTHON_KERNEL_PROTOCOL) self.assertIs(kernel1, kernel2) def test_service_not_used(self): # If the service isn't used, no kernel should be created. from envisage.plugins.ipython_kernel import internal_ipkernel kernel_instances = [] class TrackingInternalIPKernel(internal_ipkernel.InternalIPKernel): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) kernel_instances.append(self) patcher = mock.patch.object( internal_ipkernel, "InternalIPKernel", TrackingInternalIPKernel, ) with patcher: kernel_plugin = IPythonKernelPlugin(init_ipkernel=True) with self.running_app(plugins=[kernel_plugin]): pass self.assertEqual(kernel_instances, []) def test_service_used(self): # This is a complement to the test_service_not_used test. It's mostly # here as a double check on the somewhat messy test machinery used in # test_service_not_used: if the assumptions (e.g., on the location that # InternalIPKernel is imported from) in test_service_not_used break, # then this test will likely break too. from envisage.plugins.ipython_kernel import internal_ipkernel kernel_instances = [] class TrackingInternalIPKernel(internal_ipkernel.InternalIPKernel): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) kernel_instances.append(self) patcher = mock.patch.object( internal_ipkernel, "InternalIPKernel", TrackingInternalIPKernel, ) with patcher: kernel_plugin = IPythonKernelPlugin(init_ipkernel=True) with self.running_app(plugins=[kernel_plugin]) as app: app.get_service(IPYTHON_KERNEL_PROTOCOL) self.assertEqual(len(kernel_instances), 1) def test_no_init(self): # Testing deprecated behaviour where the kernel is not initialized. plugins = [IPythonKernelPlugin()] with self.running_app(plugins=plugins) as app: with warnings.catch_warnings(record=True) as warn_msgs: warnings.simplefilter("always", category=DeprecationWarning) app.get_service(IPYTHON_KERNEL_PROTOCOL) matching_messages = [ msg for msg in warn_msgs if isinstance(msg.message, DeprecationWarning) if "kernel will be initialized" in str(msg.message) ] self.assertEqual(len(matching_messages), 1) @contextlib.contextmanager def running_app(self, plugins=None): """ Returns a context manager that provides a running application. Parameters ---------- plugins : list of Plugin, optional Plugins to use in the application, other than the CorePlugin (which is always included). If not given, an IPythonKernelPlugin is instantiated and used. """ if plugins is None: plugins = [IPythonKernelPlugin(init_ipkernel=True)] app = Application(plugins=[CorePlugin()] + plugins, id="test") app.start() try: yield app finally: app.stop() envisage-6.0.1/envisage/plugins/python_shell/000077500000000000000000000000001406265421000213115ustar00rootroot00000000000000envisage-6.0.1/envisage/plugins/python_shell/__init__.py000066400000000000000000000000001406265421000234100ustar00rootroot00000000000000envisage-6.0.1/envisage/plugins/python_shell/api.py000066400000000000000000000007001406265421000224310ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! from .i_python_shell import IPythonShell envisage-6.0.1/envisage/plugins/python_shell/i_python_shell.py000066400000000000000000000020521406265421000247020ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ A 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. """ envisage-6.0.1/envisage/plugins/python_shell/python_shell_plugin.py000066400000000000000000000051501406265421000257520ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ 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] envisage-6.0.1/envisage/plugins/python_shell/view/000077500000000000000000000000001406265421000222635ustar00rootroot00000000000000envisage-6.0.1/envisage/plugins/python_shell/view/__init__.py000066400000000000000000000000001406265421000243620ustar00rootroot00000000000000envisage-6.0.1/envisage/plugins/python_shell/view/api.py000066400000000000000000000007061406265421000234110ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! from .python_shell_view import PythonShellView envisage-6.0.1/envisage/plugins/python_shell/view/namespace_view.py000066400000000000000000000113761406265421000256330ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ A view containing the contents of a Python shell namespace. """ # 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": 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, observe="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-6.0.1/envisage/plugins/python_shell/view/python_shell_view.py000066400000000000000000000175351406265421000264120ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ A view containing an interactive Python shell. """ # Standard library imports. import logging import 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, Dict, Event, Instance, Property, provides, Str # 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, lines): for line in lines: self.write(line) def flush(self): pass def isatty(self): return 1 @provides(IPythonShell) class PythonShellView(View): """ A view containing an interactive Python shell. """ #### 'IView' interface #################################################### # The part's globally unique identifier. id = "envisage.plugins.python_shell_view" # The part's name (displayed to the user). name = "Python" # The default position of the view relative to the item specified in the # 'relative_to' trait. position = "bottom" #### 'PythonShellView' interface ########################################## # The interpreter's namespace. namespace = Property(Dict(Str, Any)) # 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().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 ########################################################################### # 'PythonShellView' interface. ########################################################################### #### Properties ########################################################### def _get_namespace(self): """ Property getter. """ return self.shell.interpreter().locals def _get_names(self): """ Property getter. """ return list(self.shell.interpreter().locals.keys()) #### Methods ############################################################## def bind(self, name, value): """ Binds a name to a value in the interpreter's namespace. """ self.shell.bind(name, value) 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 #### 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) 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) def _on_write_stdout(self, text): """ Dynamic trait change handler. """ self.shell.control.write(text) envisage-6.0.1/envisage/plugins/tasks/000077500000000000000000000000001406265421000177265ustar00rootroot00000000000000envisage-6.0.1/envisage/plugins/tasks/__init__.py000066400000000000000000000000001406265421000220250ustar00rootroot00000000000000envisage-6.0.1/envisage/plugins/tasks/python_shell_plugin.py000066400000000000000000000077601406265421000244000ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ 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, 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-6.0.1/envisage/plugins/text_editor/000077500000000000000000000000001406265421000211335ustar00rootroot00000000000000envisage-6.0.1/envisage/plugins/text_editor/__init__.py000066400000000000000000000000001406265421000232320ustar00rootroot00000000000000envisage-6.0.1/envisage/plugins/text_editor/actions.py000066400000000000000000000027611406265421000231530ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! import 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-6.0.1/envisage/plugins/text_editor/api.py000066400000000000000000000007721406265421000222640ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! from .text_editor_action_set import TextEditorActionSet from .editor.text_editor import TextEditor envisage-6.0.1/envisage/plugins/text_editor/editor/000077500000000000000000000000001406265421000224215ustar00rootroot00000000000000envisage-6.0.1/envisage/plugins/text_editor/editor/__init__.py000066400000000000000000000000001406265421000245200ustar00rootroot00000000000000envisage-6.0.1/envisage/plugins/text_editor/editor/text_editor.py000066400000000000000000000150561406265421000253340ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ A 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, observe 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 _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: with open(self.obj.path, "w", encoding="utf-8") as f: f.write(self.text) # We have just saved the file so we ain't dirty no more! self.dirty = False 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() ########################################################################### # '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( 'exec(open(r"%s").read())' % self.obj.path, hidden=False ) def select_line(self, lineno): """ Selects the specified line. """ self.ui.info.text.selected_line = lineno ########################################################################### # 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 ################################################ @observe("obj") def _handle_update_to_object(self, event): """ Static trait change handler. """ new = event.new # 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) with open(new.path, "r", encoding="utf-8") as f: self.text = f.read() @observe("text") def _update_dirty(self, event): """ Static trait change handler. """ if self.traits_inited(): self.dirty = True @observe("dirty") def _update_name(self, event): """ Static trait change handler. """ dirty = event.new if len(self.obj.path) > 0: if dirty: self.name = basename(self.obj.path) + "*" else: self.name = basename(self.obj.path) #### 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(next(_id_generator)) while self.window.get_editor_by_id(id) is not None: id = prefix + str(next(_id_generator)) return id envisage-6.0.1/envisage/plugins/text_editor/editor/text_editor_handler.py000066400000000000000000000022301406265421000270170ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ 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() def save(self, info): """ Save the text to disk. """ info.object.save() envisage-6.0.1/envisage/plugins/text_editor/text_editor_action_set.py000066400000000000000000000022231406265421000262460ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! from 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-6.0.1/envisage/plugins/text_editor/text_editor_plugin.py000066400000000000000000000022621406265421000254170ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ 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-6.0.1/envisage/provider_extension_registry.py000066400000000000000000000257401406265421000233600ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ An extension registry implementation with multiple providers. """ # Standard library imports. import logging # Enthought library imports. from traits.api import List, provides, 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__) @provides(IProviderExtensionRegistry) class ProviderExtensionRegistry(ExtensionRegistry): """ An extension registry implementation with multiple providers. """ #### 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) 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) ########################################################################### # 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 extension_point_id not in self._extension_points: logger.warning( "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 that that makes it any clearer ;^) all = [] for extensions_of_single_provider in extensions: all.extend(extensions_of_single_provider) 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 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] ########################################################################### # 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 extension_point_id not 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 ) #### 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 envisage-6.0.1/envisage/resource/000077500000000000000000000000001406265421000167475ustar00rootroot00000000000000envisage-6.0.1/envisage/resource/__init__.py000066400000000000000000000000001406265421000210460ustar00rootroot00000000000000envisage-6.0.1/envisage/resource/api.py000066400000000000000000000014231406265421000200720ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! from .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-6.0.1/envisage/resource/file_resource_protocol.py000066400000000000000000000027511406265421000240750ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ A resource protocol for a local file system. """ # Standard library imports. import errno # Enthought library imports. from traits.api import HasTraits, provides # Local imports. from .i_resource_protocol import IResourceProtocol from .no_such_resource_error import NoSuchResourceError @provides(IResourceProtocol) class FileResourceProtocol(HasTraits): """ A resource protocol for a local file system. """ ########################################################################### # '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 = open(address, "rb") except IOError as e: if e.errno == errno.ENOENT: raise NoSuchResourceError(address) else: raise return f envisage-6.0.1/envisage/resource/http_resource_protocol.py000066400000000000000000000026231406265421000241330ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ A resource protocol for HTTP documents. """ # Enthought library imports. from traits.api import HasTraits, provides # Local imports. from .i_resource_protocol import IResourceProtocol from .no_such_resource_error import NoSuchResourceError @provides(IResourceProtocol) class HTTPResourceProtocol(HasTraits): """ A resource protocol for HTTP documents. """ ########################################################################### # 'IResourceProtocol' interface. ########################################################################### def file(self, address): """ Return a readable file-like object for the specified address. """ # Do the imports here 'cos I'm not sure how much this will actually # be used. from urllib.error import HTTPError from urllib.request import urlopen try: f = urlopen("http://" + address) except HTTPError: raise NoSuchResourceError("http:://" + address) return f envisage-6.0.1/envisage/resource/i_resource_manager.py000066400000000000000000000020421406265421000231500ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ 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') """ envisage-6.0.1/envisage/resource/i_resource_protocol.py000066400000000000000000000016111406265421000234000ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ 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') """ envisage-6.0.1/envisage/resource/no_such_resource_error.py000066400000000000000000000013041406265421000240750ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ 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) envisage-6.0.1/envisage/resource/package_resource_protocol.py000066400000000000000000000035451406265421000245530ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ A resource protocol for package resources. """ # Standard library imports. import errno # 3rd party imports. import pkg_resources # Enthought library imports. from traits.api import HasTraits, provides # Local imports. from .i_resource_protocol import IResourceProtocol from .no_such_resource_error import NoSuchResourceError @provides(IResourceProtocol) 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' """ ########################################################################### # '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 as e: if e.errno == errno.ENOENT: raise NoSuchResourceError(address) else: raise except ImportError: raise NoSuchResourceError(address) return f envisage-6.0.1/envisage/resource/resource_manager.py000066400000000000000000000043571406265421000226530ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ The default resource manager. """ # Enthought library imports. from traits.api import Dict, HasTraits, Str, provides # Local imports. from .i_resource_manager import IResourceManager from .i_resource_protocol import IResourceProtocol @provides(IResourceManager) class ResourceManager(HasTraits): """ The default resource manager. """ #### '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) envisage-6.0.1/envisage/resource/tests/000077500000000000000000000000001406265421000201115ustar00rootroot00000000000000envisage-6.0.1/envisage/resource/tests/__init__.py000066400000000000000000000000001406265421000222100ustar00rootroot00000000000000envisage-6.0.1/envisage/resource/tests/test_resource_manager.py000066400000000000000000000110561406265421000250460ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ Tests for the resource manager. """ # Standard library imports. import unittest from urllib.error import HTTPError import urllib.request from io 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 # Module to patch urlopen in during testing. url_library = urllib.request # This module's package. PKG = "envisage.resource.tests" # mimics `urlopen` for some tests. # In setUp it replaces `urlopen` for some tests, # and in tearDown, the regular `urlopen` is put back into place. def stubout_urlopen(url): if "bogus" in url: raise HTTPError(url, "404", "No such resource", "", None) elif "localhost" in url: return 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 = url_library.urlopen url_library.urlopen = stubout_urlopen def tearDown(self): """ Called immediately after each test method has been called. """ url_library.urlopen = self.stored_urlopen ########################################################################### # 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. with open(filename, "rb") as g: self.assertEqual(g.read(), contents) def test_no_such_file_resource(self): """ no such file resource """ rm = ResourceManager() # Open a file resource. with self.assertRaises(NoSuchResourceError): rm.file("file://../bogus.py") 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. with open(filename, "rb") as g: self.assertEqual(g.read(), contents) def test_no_such_package_resource(self): """ no such package resource """ rm = ResourceManager() # Open a package resource. with self.assertRaises(NoSuchResourceError): rm.file("pkgfile://envisage.resource/bogus.py") with self.assertRaises(NoSuchResourceError): rm.file("pkgfile://completely.bogus/bogus.py") 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.assertEqual(contents, "This is a test file.\n") def test_no_such_http_resource(self): """ no such http resource """ # Open an HTTP document resource. rm = ResourceManager() with self.assertRaises(NoSuchResourceError): rm.file("http://localhost:1234/bogus.dat") def test_unknown_protocol(self): """ unknown protocol """ # Open an HTTP document resource. rm = ResourceManager() with self.assertRaises(ValueError): rm.file("bogus://foo/bar/baz") envisage-6.0.1/envisage/safeweakref.py000066400000000000000000000040741406265421000177620ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ 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 warnings import weakref class ref(object): """An implementation of weak references that works for bound methods and caches them. If ``object`` is a bound method, returns a ``weakref.WeakMethod`` for that method. This ensures that the method is kept alive for the lifetime of the object that it's bound to. For any other ``object``, a normal ``weakref.ref`` is returned. .. deprecated:: 5.0.0 """ _cache = weakref.WeakKeyDictionary() def __new__(cls, obj, callback=None): warnings.warn( message=( "safeweakref.ref is deprecated, and will be removed in a " "future version of Envisage" ), category=DeprecationWarning, stacklevel=2, ) if getattr(obj, "__self__", None) is not None: # Bound method # Caching func_cache = cls._cache.setdefault(obj.__self__, {}) self = func_cache.get(obj.__func__) if self is None: self = weakref.WeakMethod(obj, callback) func_cache[obj.__func__] = self return self else: return weakref.ref(obj, callback) envisage-6.0.1/envisage/service.py000066400000000000000000000057121406265421000171370ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ A 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().__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 def __repr__(self): """ String representation of a Service object """ return "Service(protocol={!r})".format(self._protocol) ########################################################################### # '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. \n' "Object %s\nService protocol %s" % (obj, self._protocol) ) return service_registry envisage-6.0.1/envisage/service_offer.py000066400000000000000000000027121406265421000203150ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ An offer to provide a service. """ # Enthought library imports. from traits.api import Callable, Dict, HasTraits, Str, Type, Union 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 = Union(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 = Union(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 envisage-6.0.1/envisage/service_registry.py000066400000000000000000000215471406265421000210730ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ The service registry. """ # Standard library imports. import logging # Enthought library imports. from traits.api import Dict, Event, HasTraits, Int, provides # 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. """ @provides(IServiceRegistry) class ServiceRegistry(HasTraits): """ The service registry. """ #### 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, str): 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(key=lambda x: getattr(x, minimize)) elif maximize != "": services.sort(key=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) 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) ########################################################################### # 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 Exception: 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, str): 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, str): 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 envisage-6.0.1/envisage/tests/000077500000000000000000000000001406265421000162625ustar00rootroot00000000000000envisage-6.0.1/envisage/tests/__init__.py000066400000000000000000000000001406265421000203610ustar00rootroot00000000000000envisage-6.0.1/envisage/tests/bad_eggs/000077500000000000000000000000001406265421000200155ustar00rootroot00000000000000envisage-6.0.1/envisage/tests/bad_eggs/acme.bad-0.1a1-py3.5.egg000066400000000000000000000070261406265421000235320ustar00rootroot00000000000000PKvLD>"wacme/__init__.pyA 0yŀpEYþb㠲1 7Ѩ /-&}׽a`y g |<K X~1HFuX)ĩ60z{pXPK%J6- (acme/__pycache__/__init__.cpython-35.pyc2$ 0TfF,FT&1!XoAvz|Qjq~iQrj&-܂[)9Eyɩ8 E@/̜ ]C=3 x3tT}y@ *oq槔ڱ r4PKvLDqqxacme/bad/__init__.pyA 0E9ŀ hEt]J=A1 X<y<_+Cy`xǺ] >:!.,?_| [NRm3Tڅ7.[<;3?PKvLD*acme/bad/bad_plugin.pyMj0:Š,ltB7jYRS+K6)4i}zJ)zYjB6ס7Pq}q393wrG=8.ZDPFD{5e¶)b&TO40C-"H:NcodMZMw)d5=pq7)+eSM܈*ؕGo=nZe r$-\%0D0 w?Ýr%PK%Jb>,acme/bad/__pycache__/__init__.cpython-35.pycMN 0|("tp/pQ 8 }z䙰{g樜QU!uȤ22CDuVL$}C2]8r}'x@,J΅/؎^) @H&AXTԹzyۘruC4/^79EPK%J2EGG-INFO/dependency_links.txtPK%JjF]4KEGG-INFO/entry_points.txtRԼT̼Xpbrn^Rb- EVN)`X5PK%JiEGG-INFO/namespace_packages.txtKLMJzI)\PK%J˒EGG-INFO/PKG-INFOE 0D| 6+ J5 E]_6Л<E] \0A C)^ʂi T |A]!K(Y Z_=r|P6 $u~BOl 8M*&)}m@+PK%J ` EGG-INFO/requires.txtKLMKPK%JΑKEGG-INFO/SOURCES.txtuA0EޅrƘѸT40bh\I3I+[(X8aWjA]y BH=gB&q&Xb.#Au!O0KoPK%J߳EGG-INFO/top_level.txtKLMPK%J2EGG-INFO/zip-safePKvLD>"wacme/__init__.pyPK%J6- (acme/__pycache__/__init__.cpython-35.pycPKvLDqqxacme/bad/__init__.pyPKvLD*Iacme/bad/bad_plugin.pyPK%Jb>,acme/bad/__pycache__/__init__.cpython-35.pycPK%JGL7.acme/bad/__pycache__/bad_plugin.cpython-35.pycPK%J2EGG-INFO/dependency_links.txtPK%JjF]4K(EGG-INFO/entry_points.txtPK%JiEGG-INFO/namespace_packages.txtPK%J˒EGG-INFO/PKG-INFOPK%J ` EGG-INFO/requires.txtPK%JΑKEGG-INFO/SOURCES.txtPK%J߳ EGG-INFO/top_level.txtPK%J2 EGG-INFO/zip-safePK envisage-6.0.1/envisage/tests/bad_eggs/acme.bad-0.1a1-py3.6.egg000066400000000000000000000070161406265421000235320ustar00rootroot00000000000000PKKL*EGG-INFO/PKG-INFOM0D~m`ەL4j5Z.ЄےR]#TA<1 ;s1 !oؿd9+G"}חfO(zhpF1>(w)Ǧ+~tNqjƟ!'vݐe L}Lb]LOfaA}|PKKLΑKEGG-INFO/SOURCES.txtuA0EޅrƘѸT40bh\I3I+[(X8aWjA]y BH=gB&q&Xb.#Au!O0KoPKKL2EGG-INFO/dependency_links.txtPKKLjF]4KEGG-INFO/entry_points.txtRԼT̼Xpbrn^Rb- EVN)`X5PKKLiEGG-INFO/namespace_packages.txtKLMJzI)\PKKL ` EGG-INFO/requires.txtKLMKPKKL߳EGG-INFO/top_level.txtKLMPKKL2EGG-INFO/zip-safePKeJL>"wacme/__init__.pyA 0yŀpEYþb㠲1 7Ѩ /-&}׽a`y g |<K X~1HFuX)ĩ60z{pXPKKL(acme/__pycache__/__init__.cpython-36.pyc3c% 0 Tf,TfFF`M[EEɩ~̷3s Jo &$%$&sW2MJ3sRR2Kr+t t+,LSsSe+aRjr"ȽPKeJLqqxacme/bad/__init__.pyA 0E9ŀ hEt]J=A1 X<y<_+Cy`xǺ] >:!.,?_| [NRm3Tڅ7.[<;3?PKeJL*acme/bad/bad_plugin.pyMj0:Š,ltB7jYRS+K6)4i}zJ)zYjB6ס7Pq}q393wrG=8.ZDPFD{5e¶)b&TO40C-"H:NcodMZMw)d5=pq7)+eSM܈*ؕGo=nZe r$-\%0D0 w?Ýr%PKKLD,acme/bad/__pycache__/__init__.cpython-36.pyc3c% 0 Tf,TfFF`M[EEɩ~̷3s Jo &$%$&sW2M̓J3sRR2Kr+t t+,LSsSSe+aRjr&PKKLp G.acme/bad/__pycache__/bad_plugin.cpython-36.pycR0qKC:\ TBHe3%R'=bg!&v&&PzH~g?<;:&?׀o8^.Q(L1q9W< U@▀&P%FۓxƧN2۬ui#޶(P?bU0m U#%A@s$jN Jm00]Nvʯ~"ÊoƗqu\#*ِ5Q<(҂V$|ؤVueQ]=}Ϯ/z|>ț)?t_~l–%@PI '(1 q%c/=, =C+E9V릯u^&xɺN5K,xrﮁ PIyȹRHv;I *&L?կIbSuRl݊t+P~PKKL*EGG-INFO/PKG-INFOPKKLΑKEGG-INFO/SOURCES.txtPKKL2EGG-INFO/dependency_links.txtPKKLjF]4KEGG-INFO/entry_points.txtPKKLi6EGG-INFO/namespace_packages.txtPKKL ` EGG-INFO/requires.txtPKKL߳EGG-INFO/top_level.txtPKKL2EGG-INFO/zip-safePKeJL>"w,acme/__init__.pyPKKL(acme/__pycache__/__init__.cpython-36.pycPKeJLqqxacme/bad/__init__.pyPKeJL*racme/bad/bad_plugin.pyPKKLD,acme/bad/__pycache__/__init__.cpython-36.pycPKKLp G.acme/bad/__pycache__/bad_plugin.cpython-36.pycPK envisage-6.0.1/envisage/tests/bad_eggs/acme.bad-0.1a1-py3.7.egg000066400000000000000000000070231406265421000235310ustar00rootroot00000000000000PKDKN˒EGG-INFO/PKG-INFOE 0D| 6+ J5 E]_6Л<E] \0A C)^ʂi T |A]!K(Y Z_=r|P6 $u~BOl 8M*&)}m@+PKDKNΑKEGG-INFO/SOURCES.txtuA0EޅrƘѸT40bh\I3I+[(X8aWjA]y BH=gB&q&Xb.#Au!O0KoPKDKN2EGG-INFO/dependency_links.txtPKDKNjF]4KEGG-INFO/entry_points.txtRԼT̼Xpbrn^Rb- EVN)`X5PKDKNiEGG-INFO/namespace_packages.txtKLMJzI)\PKDKN ` EGG-INFO/requires.txtKLMKPKDKN߳EGG-INFO/top_level.txtKLMPKDKN2EGG-INFO/zip-safePKz*M>"wacme/__init__.pyA 0yŀpEYþb㠲1 7Ѩ /-&}׽a`y g |<K X~1HFuX)ĩ60z{pXPKDKN\"(acme/__pycache__/__init__.cpython-37.pycsbݵӢ%3 f vb! ʐ̸1i!##C c0&-ނҢb?M[\E%SRsRsS Soqă9+X_&I9)I)%zz&ff&ɹ@2fTO)Ic`PKz*Mqqxacme/bad/__init__.pyA 0E9ŀ hEt]J=A1 X<y<_+Cy`xǺ] >:!.,?_| [NRm3Tڅ7.[<;3?PKz*M*acme/bad/bad_plugin.pyMj0:Š,ltB7jYRS+K6)4i}zJ)zYjB6ס7Pq}q393wrG=8.ZDPFD{5e¶)b&TO40C-"H:NcodMZMw)d5=pq7)+eSM܈*ؕGo=nZe r$-\%0D0 w?Ýr%PKDKNf,acme/bad/__pycache__/__init__.cpython-37.pycsbݵӢ3 f vb! ʐ̸1i!##C c0&-ނҢb?M[\E%SRsRsS Soqă9+X_I9)I)%zz&ff&ɹI)@2TO)IchPKDKN*EL.acme/bad/__pycache__/bad_plugin.cpython-37.pycRA0'qKˠa*b3bRFF#*RUHӕYXn)&8h`5sIvW`wJ޳=O|N{y$@4cQ˸-/W|n x2~IF8Z3TۓHu~#9">t:g' 50&28D  ı{MoaGel^e&^jhJ;旎sN?EaDXoFUTa]8Z#քKp / Ziҷ_Xe0^BRVqWyy}z$>^_جcj\WA⏜Ib1:ɋ;Ff 4b}ol؇4%Pj3TW*ˡN(i |%`=Gg{{wUXE(>炊@63 r`6WI&Ebݕ~euRce]ublijݎvK`PKDKN˒EGG-INFO/PKG-INFOPKDKNΑKEGG-INFO/SOURCES.txtPKDKN2EGG-INFO/dependency_links.txtPKDKNjF]4KEGG-INFO/entry_points.txtPKDKNi*EGG-INFO/namespace_packages.txtPKDKN ` uEGG-INFO/requires.txtPKDKN߳EGG-INFO/top_level.txtPKDKN2EGG-INFO/zip-safePKz*M>"w acme/__init__.pyPKDKN\"(acme/__pycache__/__init__.cpython-37.pycPKz*Mqqxacme/bad/__init__.pyPKz*M*jacme/bad/bad_plugin.pyPKDKNf,acme/bad/__pycache__/__init__.cpython-37.pycPKDKN*EL.acme/bad/__pycache__/bad_plugin.cpython-37.pycPK envisage-6.0.1/envisage/tests/bad_eggs/acme.bad-0.1a1-py3.8.egg000066400000000000000000000073771406265421000235460ustar00rootroot00000000000000PK*D0O˒EGG-INFO/PKG-INFOE 0D| 6+ J5 E]_6Л<E] \0A C)^ʂi T |A]!K(Y Z_=r|P6 $u~BOl 8M*&)}m@+PK*D0OΑKEGG-INFO/SOURCES.txtuA0EޅrƘѸT40bh\I3I+[(X8aWjA]y BH=gB&q&Xb.#Au!O0KoPK*D0O2EGG-INFO/dependency_links.txtPK*D0OjF]4KEGG-INFO/entry_points.txtRԼT̼Xpbrn^Rb- EVN)`X5PK*D0OiEGG-INFO/namespace_packages.txtKLMJzI)\PK*D0O ` EGG-INFO/requires.txtKLMKPK*D0O߳EGG-INFO/top_level.txtKLMPK*D0O2EGG-INFO/zip-safePK[A0O>"wacme/__init__.pyA 0yŀpEYþb㠲1 7Ѩ /-&}׽a`y g |<K X~1HFuX)ĩ60z{pXPK*D0OT;7(acme/__pycache__/__init__.cpython-38.pyc b:X F hXH2043.`LeZȐ̠t ;=(8(9OW||fnA~QI|-Ģ[`N|J"V`IRifN~RJfq^nbr~qnY~jz~brn>Lz8lrSJsR؀f|9PK[A0Oqqxacme/bad/__init__.pyA 0E9ŀ hEt]J=A1 X<y<_+Cy`xǺ] >:!.,?_| [NRm3Tڅ7.[<;3?PK[A0O Ywacme/bad/bad_plugin.pySM0W !TܲJ{ؖT  9pl㏖{&N)"/23~acrD%gc{ :G$J80^xd$z{ `|X>'*4LS |ج< FD U^);k&PѻDm=6hZn <:X(5Y|~<9-m}xSAl0PRHA`2X|KWl4eRN;KU?j1pV{ b\8C9StxB tif.X42Vi,&gzˀօLӂbE8ufVOui@V lW+ІLz8lrSJsR؀|9PK*D0O XYS.acme/bad/__pycache__/bad_plugin.cpython-38.pycώ0mIgy˪HRYBHV!mXB1%R'Bp}RxNxPv+.XIƙ7]Ʒ} Aw ҌIF1R/71O}&CpM 0ʓ 27G색GE^&':x<<.C$u In| 55;uga(& zÙ>J*Ҷ*WtW.fa Ea`Z,M~K,gupIYř^ttry~FC\lp֠RMG Ѐmیwsɋ[LfS;N\?n7)b:BΌRuW,uqOkP).` X- Qz({ B6AϹ" BI3OI&Eb$d::|]_"6˱w;^tOQ~PK*D0O˒EGG-INFO/PKG-INFOPK*D0OΑKEGG-INFO/SOURCES.txtPK*D0O2EGG-INFO/dependency_links.txtPK*D0OjF]4KEGG-INFO/entry_points.txtPK*D0Oi*EGG-INFO/namespace_packages.txtPK*D0O ` uEGG-INFO/requires.txtPK*D0O߳EGG-INFO/top_level.txtPK*D0O2EGG-INFO/zip-safePK[A0O>"w acme/__init__.pyPK*D0OT;7(acme/__pycache__/__init__.cpython-38.pycPK[A0Oqqxacme/bad/__init__.pyPK[A0O Ywkacme/bad/bad_plugin.pyPK*D0O[e,acme/bad/__pycache__/__init__.cpython-38.pycPK*D0O XYS.acme/bad/__pycache__/bad_plugin.cpython-38.pycPK envisage-6.0.1/envisage/tests/bad_eggs/acme.bad-0.1a1-py3.9.egg000066400000000000000000000073661406265421000235450ustar00rootroot00000000000000PK0KoPKZPX]^'Nacme/bad/bad_plugin.pySMk@WLmpN(r CoeKW~h%GNBKIv{v4j+[\_ϯ>ڄ7)nLa-­֐*<8$E6d`W-$ G` 'F'*Lˇ;Ѫ :6ja-h$Tx؀T>8' k |Z:40v :1[*FE^fQ92Qj֕7XNE!zev}`k2[tAr0ZAf1I_UН@Uut2qGkA5QxpV7wČ0(;4|$WVF`#.m%Vi>& x]#ӠC^0ڴ# J02#ϓ#lTD:$7S%]37)=M:oȓ7Z] +\*J*ۦǞt y x~ R$\!Ķ.KYt3l[~K_PK(L]3iT'iRB<O5EE wTkgyX4d)0υTE&SAT)q'7 PK<"gn#^jtN3{ZMZCpɂjI@Q-}BMr !l3x4+<.$T*L\ rTE]/#˱/w;^uȖNF PKZPX]^'NTacme/bad/bad_plugin.pyPK"wacme/__init__.pyA 0yŀpEYþb㠲1 7Ѩ /-&}׽a`y g |<K X~1HFuX)ĩ60z{pXPK%J4]\Q(acme/__pycache__/__init__.cpython-35.pycM1 @DF Q$ Olv?!Mn4^i[oaʁf,`^xß<瓳Y dpc@Pdp =45ӂ9)bZv.I\V\i o @m3Y6R\Ԧ_o(]]Lys(vpyXjٕt ?_PK\r@qqxacme/bar/__init__.pyA 0E9ŀ hEt]J=A1 X<y<_+Cy`xǺ] >:!.,?_| [NRm3Tڅ7.[<;3?PKWV@Aacme/bar/bar_plugin.pyRj0+ (HP-%UBئIދ;3;ZJ){vU(uMJ^p_ k\ Sz!VNe'dj^cTdʳ">4T^VԘDŊ Ad+%x&%Z{BpG1$koF5r'^T&l-KWU;G;|IKbg'v,ǵL: c0EぁlFr%PK%Ja,acme/bar/__pycache__/__init__.cpython-35.pycMN 0|(E(⢃_FA_HG6$tkKY 'Nq,ƫI{^0!-de*I]Ss!5LO RMIQ;PK%Jb}b.acme/bar/__pycache__/bar_plugin.cpython-35.pyc͊@o?1 #Up3d14 "2bV^eST6t2]ću+=2+ur;7pђH 4dPhnBiBeAf{+-6|Re ҁiO'_ }5jQ,'fa.1;l1&3;+@np9.y20;xH~#̦]tkOJBsa[DtÕ|@ dA4n &lz"%hmKoC|c}ϙ ͭ46Kۋܣ$z}yUUI>j%˹s4F!I-u@r6BL QB؎ ag;YV,T"BdAEѵÂ+uW{~vi넝PK%J2EGG-INFO/dependency_links.txtPK%J?]f4KEGG-INFO/entry_points.txtRԼT̼Xpbrn^Rb- EVNE`X5PK%J?EGG-INFO/namespace_packages.txtKLMJzIE\PK%J}h6EGG-INFO/PKG-INFOE 0D| 6+  &%okWg3g 15Wb5K(ٿŚBa>:\Ц>Dw~y@7h|8/ N΢OhԬd{6_mPK%J ` EGG-INFO/requires.txtKLMKPK%Jt-KEGG-INFO/SOURCES.txtu0 !c"F⹙PQV.OC{GY, X`йPjEFb[Y1!kX@Fb%lbz0f Yv%i>0qoPK%J߳EGG-INFO/top_level.txtKLMPK%J2EGG-INFO/zip-safePK\r@>"wacme/__init__.pyPK%J4]\Q(acme/__pycache__/__init__.cpython-35.pycPK\r@qqxacme/bar/__init__.pyPKWV@AGacme/bar/bar_plugin.pyPK%Ja,acme/bar/__pycache__/__init__.cpython-35.pycPK%Jb}b.acme/bar/__pycache__/bar_plugin.cpython-35.pycPK%J2EGG-INFO/dependency_links.txtPK%J?]f4KEGG-INFO/entry_points.txtPK%J?lEGG-INFO/namespace_packages.txtPK%J}h6EGG-INFO/PKG-INFOPK%J ` uEGG-INFO/requires.txtPK%Jt-KEGG-INFO/SOURCES.txtPK%J߳u EGG-INFO/top_level.txtPK%J2 EGG-INFO/zip-safePK envisage-6.0.1/envisage/tests/eggs/acme.bar-0.1a1-py3.6.egg000066400000000000000000000067511406265421000227470ustar00rootroot00000000000000PKL2>EGG-INFO/PKG-INFOM0D~m`ەL$j5Z/ЄےR]#!x`w2cC(_ؿd9F"w}ԗfO(zhpF1>(w)Ǧ+^: lu_ϐ;YnHC۪`&>o.&Uܦ~q0fx >L>PKLt-KEGG-INFO/SOURCES.txtu0 !c"F⹙PQV.OC{GY, X`йPjEFb[Y1!kX@Fb%lbz0f Yv%i>0qoPKL2EGG-INFO/dependency_links.txtPKL?]f4KEGG-INFO/entry_points.txtRԼT̼Xpbrn^Rb- EVNE`X5PKL?EGG-INFO/namespace_packages.txtKLMJzIE\PKL ` EGG-INFO/requires.txtKLMKPKL߳EGG-INFO/top_level.txtKLMPKL2EGG-INFO/zip-safePKeJL>"wacme/__init__.pyA 0yŀpEYþb㠲1 7Ѩ /-&}׽a`y g |<K X~1HFuX)ĩ60z{pXPKL(acme/__pycache__/__init__.cpython-36.pyc3c% 0 Tf,TfFF`M[EEɩ~̷3s Jo &$%$&sW2MJ3sRR2Kr+t t+,LSsSe+aRjr"ȽPKeJLqqxacme/bar/__init__.pyA 0E9ŀ hEt]J=A1 X<y<_+Cy`xǺ] >:!.,?_| [NRm3Tڅ7.[<;3?PKeJLAacme/bar/bar_plugin.pyRj0+ (HP-%UBئIދ;3;ZJ){vU(uMJ^p_ k\ Sz!VNe'dj^cTdʳ">4T^VԘDŊ Ad+%x&%Z{BpG1$koF5r'^T&l-KWU;G;|IKbg'v,ǵL: c0EぁlFr%PKLҏԵ,acme/bar/__pycache__/__init__.cpython-36.pyc3c% 0 Tf,TfFF`M[EEɩ~̷3s Jo &$%$&sW2M̓J3sRR2Kr+t t+,LSsSe+aRjr&PKL`?..acme/bar/__pycache__/bar_plugin.cpython-36.pycRM@"xpXM1fM1ٞ2c%ݬ'<MEyQ?B~z9G" P`-drpJ.9%ppp5u I4ԜFu^Սiϑ˪*C[@DRf@LrsK7X }GM؈4+uH4+ZG0BUQoGWmjڨETͧu~c,ZmwbyX7mRYܜ=;O&g7/&jr1؆c(Ԡ_^٤G=97L9"U}t=FƷ\aDyxgq Vi օRi1L|ʐc-&n} cLKAPr.'3ǸP&]$sc߉ Zm_pvRyt6kkyHC/PKL2>EGG-INFO/PKG-INFOPKLt-KEGG-INFO/SOURCES.txtPKL2EGG-INFO/dependency_links.txtPKL?]f4KEGG-INFO/entry_points.txtPKL?5EGG-INFO/namespace_packages.txtPKL ` EGG-INFO/requires.txtPKL߳EGG-INFO/top_level.txtPKL2EGG-INFO/zip-safePKeJL>"w+acme/__init__.pyPKL(acme/__pycache__/__init__.cpython-36.pycPKeJLqqxacme/bar/__init__.pyPKeJLAqacme/bar/bar_plugin.pyPKLҏԵ,acme/bar/__pycache__/__init__.cpython-36.pycPKL`?..acme/bar/__pycache__/bar_plugin.cpython-36.pycPK envisage-6.0.1/envisage/tests/eggs/acme.bar-0.1a1-py3.7.egg000066400000000000000000000067561406265421000227550ustar00rootroot00000000000000PKDKN}h6EGG-INFO/PKG-INFOE 0D| 6+  &%okWg3g 15Wb5K(ٿŚBa>:\Ц>Dw~y@7h|8/ N΢OhԬd{6_mPKDKNt-KEGG-INFO/SOURCES.txtu0 !c"F⹙PQV.OC{GY, X`йPjEFb[Y1!kX@Fb%lbz0f Yv%i>0qoPKDKN2EGG-INFO/dependency_links.txtPKDKN?]f4KEGG-INFO/entry_points.txtRԼT̼Xpbrn^Rb- EVNE`X5PKDKN?EGG-INFO/namespace_packages.txtKLMJzIE\PKDKN ` EGG-INFO/requires.txtKLMKPKDKN߳EGG-INFO/top_level.txtKLMPKDKN2EGG-INFO/zip-safePKz*M>"wacme/__init__.pyA 0yŀpEYþb㠲1 7Ѩ /-&}׽a`y g |<K X~1HFuX)ĩ60z{pXPKDKN\"(acme/__pycache__/__init__.cpython-37.pycsbݵӢ%3 f vb! ʐ̸1i!##C c0&-ނҢb?M[\E%SRsRsS Soqă9+X_&I9)I)%zz&ff&ɹ@2fTO)Ic`PKz*Mqqxacme/bar/__init__.pyA 0E9ŀ hEt]J=A1 X<y<_+Cy`xǺ] >:!.,?_| [NRm3Tڅ7.[<;3?PKz*MAacme/bar/bar_plugin.pyRj0+ (HP-%UBئIދ;3;ZJ){vU(uMJ^p_ k\ Sz!VNe'dj^cTdʳ">4T^VԘDŊ Ad+%x&%Z{BpG1$koF5r'^T&l-KWU;G;|IKbg'v,ǵL: c0EぁlFr%PKDKNxnp,acme/bar/__pycache__/__init__.cpython-37.pycsbݵӢ3 f vb! ʐ̸1i!##C c0&-ނҢb?M[\E%SRsRsS Soqă9+X_I9)I)%zz&ff&ɹIE@2TO)IchPKDKNN`3.acme/bar/__pycache__/bar_plugin.cpython-37.pycRMn@Ip)*;Vʋ:UB)"f5,F,ٱ;D*g3poYqVq(E0Ҽ~ _,% Ii$;Sp)yM٭#]IoN{NsVzߖ7F'el';Ƣ(̀JJVX >҃v[\lD:^$f^ʯ#~&ÊQoMbA-UΧ~m,(JMVwbyXg9M\$iYߜ]㳛Ku9rd]Еݪ>nGUǣ#`d: $[,eC{OJY͢a+Z%V*U-N~+ee`+W7Tw ]{2-,rsA'D7E9.RIM1I gGuF+om4pPKDKN}h6EGG-INFO/PKG-INFOPKDKNt-KEGG-INFO/SOURCES.txtPKDKN2EGG-INFO/dependency_links.txtPKDKN?]f4KEGG-INFO/entry_points.txtPKDKN?)EGG-INFO/namespace_packages.txtPKDKN ` tEGG-INFO/requires.txtPKDKN߳EGG-INFO/top_level.txtPKDKN2EGG-INFO/zip-safePKz*M>"wacme/__init__.pyPKDKN\"(acme/__pycache__/__init__.cpython-37.pycPKz*Mqqxacme/bar/__init__.pyPKz*MAiacme/bar/bar_plugin.pyPKDKNxnp,acme/bar/__pycache__/__init__.cpython-37.pycPKDKNN`3.acme/bar/__pycache__/bar_plugin.cpython-37.pycPK envisage-6.0.1/envisage/tests/eggs/acme.bar-0.1a1-py3.8.egg000066400000000000000000000073321406265421000227450ustar00rootroot00000000000000PKD0O}h6EGG-INFO/PKG-INFOE 0D| 6+  &%okWg3g 15Wb5K(ٿŚBa>:\Ц>Dw~y@7h|8/ N΢OhԬd{6_mPKD0Ot-KEGG-INFO/SOURCES.txtu0 !c"F⹙PQV.OC{GY, X`йPjEFb[Y1!kX@Fb%lbz0f Yv%i>0qoPKD0O2EGG-INFO/dependency_links.txtPKD0O?]f4KEGG-INFO/entry_points.txtRԼT̼Xpbrn^Rb- EVNE`X5PKD0O?EGG-INFO/namespace_packages.txtKLMJzIE\PKD0O ` EGG-INFO/requires.txtKLMKPKD0O߳EGG-INFO/top_level.txtKLMPKD0O2EGG-INFO/zip-safePK[A0O>"wacme/__init__.pyA 0yŀpEYþb㠲1 7Ѩ /-&}׽a`y g |<K X~1HFuX)ĩ60z{pXPKD0OT;7(acme/__pycache__/__init__.cpython-38.pyc b:X F hXH2043.`LeZȐ̠t ;=(8(9OW||fnA~QI|-Ģ[`N|J"V`IRifN~RJfq^nbr~qnY~jz~brn>Lz8lrSJsR؀f|9PK[A0Oqqxacme/bar/__init__.pyA 0E9ŀ hEt]J=A1 X<y<_+Cy`xǺ] >:!.,?_| [NRm3Tڅ7.[<;3?PK[A0OѮ cacme/bar/bar_plugin.pySM0WL{JHU[ {5:(c'lPwWj%{ϓ!cX{rrp3}x3:M t&p}zbJAУ;`Ő^zG:se" @: j!k<:_G ']ڥozŸ\ h Hm Tg:QO^-dF{+kkA`D1Edt ~R¤"5؇`oL3,pD!zw}`,j- ? ;(ܕ`Uܑ_, "{:N k\𬨝iAzCƭ )Z<8.R{ ɶz>ƷzRB\%K7Z 3fVtwaj*)J^5ȶd1%4Yw9;X yrqBRP`zQ`)ȑP۸br\}t`oV3,<\)/PKD0ON"s,acme/bar/__pycache__/__init__.cpython-38.pyc b:XiF hXH2043.`LeZȐ̠t ;=(8(9OW||fnA~QI|-Ģ[`N|J"V`ERifN~RJfq^nbr~qnY~jz~brn~Rb>ІLz8lrSJsR؀|9PKD0On/%+.acme/bar/__pycache__/bar_plugin.cpython-38.pycj0%Y4nJuZc0hvcB230eJ^W;rІLOG>\?^&~:C2Q[;SW['v"NR̖ts>weQd!#Mbx4v\۞ 3 SVB%fʌzEw&߉$P1#⫧UL֘g$ ɚ5~elQ5FtyM{5[ gVunNϢ y1|h љr (5n;5YG=zN4E x:1a+G{`.r24BʅʵMGʼef뮔Km;P$X}`Ʒ"?4wa'ք ]mg2Lϸ_Ju8S6*ߍ7wi PKD0O}h6EGG-INFO/PKG-INFOPKD0Ot-KEGG-INFO/SOURCES.txtPKD0O2EGG-INFO/dependency_links.txtPKD0O?]f4KEGG-INFO/entry_points.txtPKD0O?)EGG-INFO/namespace_packages.txtPKD0O ` tEGG-INFO/requires.txtPKD0O߳EGG-INFO/top_level.txtPKD0O2EGG-INFO/zip-safePK[A0O>"wacme/__init__.pyPKD0OT;7(acme/__pycache__/__init__.cpython-38.pycPK[A0Oqqxacme/bar/__init__.pyPK[A0OѮ cjacme/bar/bar_plugin.pyPKD0ON"s,acme/bar/__pycache__/__init__.cpython-38.pycPKD0On/%+.acme/bar/__pycache__/bar_plugin.cpython-38.pycPK envisage-6.0.1/envisage/tests/eggs/acme.bar-0.1a1-py3.9.egg000066400000000000000000000072661406265421000227540ustar00rootroot00000000000000PK:\Ц>Dw~y@7h|8/ N΢OhԬd{6_mPK0qoPK(L]3iT'iRB<O5EE wTkgyX4d)0υTE&SAT)q'7 PKvwuY1cMx4q%\۝3 SBLREw&?$P1#ML5g$f醵~mlQV FuyC =_e9M\o//+y5bdЙrn75YG=~rwN34e(d65a+' Q0Ce5Z!RZʶ'eQ*u(וw_J(S,X>0[VVN80B3k. O3zfg巬V *3kʚhś>-! PK"wacme/__init__.pyA 0yŀpEYþb㠲1 7Ѩ /-&}׽a`y g |<K X~1HFuX)ĩ60z{pXPKɔ%J4]\Q(acme/__pycache__/__init__.cpython-35.pycM1 @DF Q$ Olv?!Mn4^i[oaʁf,`^xß<瓳Y dpc@Pdp =45ӂ9)bZv.I\V\i o @m3Y6R\Ԧ_o(]]Lys(vpyXjٕt ?_PK\r@>"wacme/baz/__init__.pyA 0yŀpEYþb㠲1 7Ѩ /-&}׽a`y g |<K X~1HFuX)ĩ60z{pXPKWV@rhacme/baz/baz_plugin.pyRj0+ (HP-%EB%6M-^tݙRJ#b`UJ)!d{z>'''<'3^tȅo Wk%$Ylw-&2@ꀮ5fo5f"`IT$-W_rL=90efa8Z_9xσy!dpa@Agbp^.Uk$F+ "YCX M٤m^(6hM0Ys2.&e;]PKɔ%Jwb.acme/baz/__pycache__/baz_plugin.cpython-35.pyc͊@o?1 #Up3d14 "2bV^eSSeH:JZn|a|l] 7zdVTuw*9;:o~cx! *A р\BҀV LRN'_yPU~Sևaθ";P$bF#D',M0$a{.Em:'J-n &3+!3<4MUXv[0 YvCRZ P| /E޴QfUs}q4~6ᓧ\bBկP!Yīպ1g4GSSշLFxJYwT6ygG?Y봔e%6yMZvlErtj1vWɽ#L2-g;,1_?MQZn&Zm3,Rw[l2N؉PKɔ%J2EGG-INFO/dependency_links.txtPKɔ%J 4KEGG-INFO/entry_points.txtRԼT̼Xpbrn^Rb- EVNU`X5PKɔ%JEGG-INFO/namespace_packages.txtKLMJzIU\PKɔ%J,EGG-INFO/PKG-INFOE 0D| 6+ J5 E]_mMJ zX839a'$7 zx) Pq0o YBɚLU|l .hS;7eg~X kxbGkIm*Va4=/ZPKɔ%JMh: EGG-INFO/requires.txtKLMKJ,PKɔ%J0^[KEGG-INFO/SOURCES.txtu0 !c"F⹙PQ*.OC{(Fm,I~bйPjEF_b[!!4ĚK6a.ij<>gBRwt >:PKɔ%J߳EGG-INFO/top_level.txtKLMPKɔ%J2EGG-INFO/zip-safePK\r@>"wacme/__init__.pyPKɔ%J4]\Q(acme/__pycache__/__init__.cpython-35.pycPK\r@>"wacme/baz/__init__.pyPKWV@rhFacme/baz/baz_plugin.pyPKɔ%Jl2,acme/baz/__pycache__/__init__.cpython-35.pycPKɔ%Jwb.acme/baz/__pycache__/baz_plugin.cpython-35.pycPKɔ%J2EGG-INFO/dependency_links.txtPKɔ%J 4KEGG-INFO/entry_points.txtPKɔ%JlEGG-INFO/namespace_packages.txtPKɔ%J,EGG-INFO/PKG-INFOPKɔ%JMh: vEGG-INFO/requires.txtPKɔ%J0^[KEGG-INFO/SOURCES.txtPKɔ%J߳v EGG-INFO/top_level.txtPKɔ%J2 EGG-INFO/zip-safePK envisage-6.0.1/envisage/tests/eggs/acme.baz-0.1a1-py3.6.egg000066400000000000000000000067531406265421000227610ustar00rootroot00000000000000PK酪L6EGG-INFO/PKG-INFOM0D~m`ەL4j5Z/ЄےRvj3'3'⎡)ˌi T |›L搳r 0*~}~h󄢃'b[r{gX mKxbGkih]-6Ťnf/-PK酪L0^[KEGG-INFO/SOURCES.txtu0 !c"F⹙PQ*.OC{(Fm,I~bйPjEF_b[!!4ĚK6a.ij<>gBRwt >:PK酪L2EGG-INFO/dependency_links.txtPK酪L 4KEGG-INFO/entry_points.txtRԼT̼Xpbrn^Rb- EVNU`X5PK酪LEGG-INFO/namespace_packages.txtKLMJzIU\PK酪LMh: EGG-INFO/requires.txtKLMKJ,PK酪L߳EGG-INFO/top_level.txtKLMPK酪L2EGG-INFO/zip-safePKeJL>"wacme/__init__.pyA 0yŀpEYþb㠲1 7Ѩ /-&}׽a`y g |<K X~1HFuX)ĩ60z{pXPK酪L(acme/__pycache__/__init__.cpython-36.pyc3c% 0 Tf,TfFF`M[EEɩ~̷3s Jo &$%$&sW2MJ3sRR2Kr+t t+,LSsSe+aRjr"ȽPKeJL>"wacme/baz/__init__.pyA 0yŀpEYþb㠲1 7Ѩ /-&}׽a`y g |<K X~1HFuX)ĩ60z{pXPKeJLrhacme/baz/baz_plugin.pyRj0+ (HP-%EB%6M-^tݙRJ#b`UJ)!d{z>'''<'3^tȅo Wk%$Ylw-&2@ꀮ5fo5f"G" P`9-dr'w .9%ppp5u $\|eҞUYeՍhϑ˲C[@DRf@LrsK7X }GMhE:^&,+ZG0BUQë&1MؠV*y: U6u;1\n&K&.oΞӳS5jbCM0]jЯ>wnlRǣk ~_ENHY1[`뿑-{!`'|?QF)ytBuRh@MnXO$&RPHXƱ{wXxp7>?%APr.'3Ǹ˱^_guqReƾ4&ɚīmtז1;PK酪L6EGG-INFO/PKG-INFOPK酪L0^[KEGG-INFO/SOURCES.txtPK酪L2EGG-INFO/dependency_links.txtPK酪L 4KEGG-INFO/entry_points.txtPK酪L6EGG-INFO/namespace_packages.txtPK酪LMh: EGG-INFO/requires.txtPK酪L߳EGG-INFO/top_level.txtPK酪L2EGG-INFO/zip-safePKeJL>"w,acme/__init__.pyPK酪L(acme/__pycache__/__init__.cpython-36.pycPKeJL>"wacme/baz/__init__.pyPKeJLrhqacme/baz/baz_plugin.pyPK酪L ',acme/baz/__pycache__/__init__.cpython-36.pycPK酪L!..acme/baz/__pycache__/baz_plugin.cpython-36.pycPK envisage-6.0.1/envisage/tests/eggs/acme.baz-0.1a1-py3.7.egg000066400000000000000000000067571406265421000227660ustar00rootroot00000000000000PKDKN,EGG-INFO/PKG-INFOE 0D| 6+ J5 E]_mMJ zX839a'$7 zx) Pq0o YBɚLU|l .hS;7eg~X kxbGkIm*Va4=/ZPKDKN0^[KEGG-INFO/SOURCES.txtu0 !c"F⹙PQ*.OC{(Fm,I~bйPjEF_b[!!4ĚK6a.ij<>gBRwt >:PKDKN2EGG-INFO/dependency_links.txtPKDKN 4KEGG-INFO/entry_points.txtRԼT̼Xpbrn^Rb- EVNU`X5PKDKNEGG-INFO/namespace_packages.txtKLMJzIU\PKDKNMh: EGG-INFO/requires.txtKLMKJ,PKDKN߳EGG-INFO/top_level.txtKLMPKDKN2EGG-INFO/zip-safePKz*M>"wacme/__init__.pyA 0yŀpEYþb㠲1 7Ѩ /-&}׽a`y g |<K X~1HFuX)ĩ60z{pXPKDKN\"(acme/__pycache__/__init__.cpython-37.pycsbݵӢ%3 f vb! ʐ̸1i!##C c0&-ނҢb?M[\E%SRsRsS Soqă9+X_&I9)I)%zz&ff&ɹ@2fTO)Ic`PKz*M>"wacme/baz/__init__.pyA 0yŀpEYþb㠲1 7Ѩ /-&}׽a`y g |<K X~1HFuX)ĩ60z{pXPKz*Mrhacme/baz/baz_plugin.pyRj0+ (HP-%EB%6M-^tݙRJ#b`UJ)!d{z>'''<'3^tȅo Wk%$Ylw-&2@ꀮ5fo5f"҃v[\4"Nr-f^ʯ#~&ÊQ7:6uP#* yZ e&t;1<[n KH:㤨n.Σեj5n Z#by֪Q5Q7$'(-0qyu?}`P<V<~Awi9ёPKDKN,EGG-INFO/PKG-INFOPKDKN0^[KEGG-INFO/SOURCES.txtPKDKN2EGG-INFO/dependency_links.txtPKDKN 4KEGG-INFO/entry_points.txtPKDKN*EGG-INFO/namespace_packages.txtPKDKNMh: uEGG-INFO/requires.txtPKDKN߳EGG-INFO/top_level.txtPKDKN2EGG-INFO/zip-safePKz*M>"w acme/__init__.pyPKDKN\"(acme/__pycache__/__init__.cpython-37.pycPKz*M>"wacme/baz/__init__.pyPKz*Mrhiacme/baz/baz_plugin.pyPKDKNoM,acme/baz/__pycache__/__init__.cpython-37.pycPKDKNN 3.acme/baz/__pycache__/baz_plugin.cpython-37.pycPK envisage-6.0.1/envisage/tests/eggs/acme.baz-0.1a1-py3.8.egg000066400000000000000000000073331406265421000227560ustar00rootroot00000000000000PKD0O,EGG-INFO/PKG-INFOE 0D| 6+ J5 E]_mMJ zX839a'$7 zx) Pq0o YBɚLU|l .hS;7eg~X kxbGkIm*Va4=/ZPKD0O0^[KEGG-INFO/SOURCES.txtu0 !c"F⹙PQ*.OC{(Fm,I~bйPjEF_b[!!4ĚK6a.ij<>gBRwt >:PKD0O2EGG-INFO/dependency_links.txtPKD0O 4KEGG-INFO/entry_points.txtRԼT̼Xpbrn^Rb- EVNU`X5PKD0OEGG-INFO/namespace_packages.txtKLMJzIU\PKD0OMh: EGG-INFO/requires.txtKLMKJ,PKD0O߳EGG-INFO/top_level.txtKLMPKD0O2EGG-INFO/zip-safePK[A0O>"wacme/__init__.pyA 0yŀpEYþb㠲1 7Ѩ /-&}׽a`y g |<K X~1HFuX)ĩ60z{pXPKD0OT;7(acme/__pycache__/__init__.cpython-38.pyc b:X F hXH2043.`LeZȐ̠t ;=(8(9OW||fnA~QI|-Ģ[`N|J"V`IRifN~RJfq^nbr~qnY~jz~brn>Lz8lrSJsR؀f|9PK[A0O>"wacme/baz/__init__.pyA 0yŀpEYþb㠲1 7Ѩ /-&}׽a`y g |<K X~1HFuX)ĩ60z{pXPK[A0Oo₅ cacme/baz/baz_plugin.pySM0WL{JHU[ xVA]iWŗ$~3=?O0Zaa>l'X7&}A l~CW r+l҃7u8r@֙((aQ t]cL{;GXeitd9O$ KCcQSnU A9J*/m=Jnw'5.xV4 =!Vv|-T|΍QD])=d[=jhl)CZPڎҥyxX+SzM+0tUDrp%d[u%4Yw9;XytqLRP`zQ`)ȑP۸br\}t`}~l~kPKD0Ok?,acme/baz/__pycache__/__init__.cpython-38.pyc b:X F hXH2043.`LeZȐ̠t ;=(8(9OW||fnA~QI|-Ģ[`N|J"V`ERifN~RJfq^nbr~qnY~jz~brn~Rb>ІLz8lrSJsR؀|9PKD0Oys,+.acme/baz/__pycache__/baz_plugin.cpython-38.pycߊ@g&dMW/Y*l u]dHsH$5^/WʭW^y&nƁLN8/~~JlB~q]P`YĨy)yOح\{7f_Tpr7'A2ci|XˢȒC4ۡ(D hd= <^!x4"Nrf{(Ž3&)N$qoWu<$a0L׬u+P`,1Rkpݫ*`4ju}Qbos_PKD0O,EGG-INFO/PKG-INFOPKD0O0^[KEGG-INFO/SOURCES.txtPKD0O2EGG-INFO/dependency_links.txtPKD0O 4KEGG-INFO/entry_points.txtPKD0O*EGG-INFO/namespace_packages.txtPKD0OMh: uEGG-INFO/requires.txtPKD0O߳EGG-INFO/top_level.txtPKD0O2EGG-INFO/zip-safePK[A0O>"w acme/__init__.pyPKD0OT;7(acme/__pycache__/__init__.cpython-38.pycPK[A0O>"wacme/baz/__init__.pyPK[A0Oo₅ cjacme/baz/baz_plugin.pyPKD0Ok?,acme/baz/__pycache__/__init__.cpython-38.pycPKD0Oys,+.acme/baz/__pycache__/baz_plugin.cpython-38.pycPK envisage-6.0.1/envisage/tests/eggs/acme.baz-0.1a1-py3.9.egg000066400000000000000000000072731406265421000227620ustar00rootroot00000000000000PKgBRwt >:PK1M`SԴ/f%(vi"Yߓ(dM亖de=hw;Q| |1C`A%iبlv:^"g]{9i~`QH̾Cù%_b0di~y(Ѵҟjg} ͑ܣ:e^?Bj. XM^ԠC^0Z#No%fVGZQۤA5WUzH- dY?<ܴ!JLmjG'b ypF xśKeBU{q@]upM䟧OA;D'|]2qc7`ݫ[ dsOPK/17 19G<h*eTY\_\khgh[aaofX!/h^A-ҜT;9APK&G~@2Q[;S'W;'v!#~οZ5ga*ch}XȒc4ۣ'( hv;-<g{m[8*uP4Pt{oqOmRH 3(<oje<$Q86+`,5l0RbNp:`\%Eu{~y]No_]ɫX/cl;@u/1ZuWͭQCOf@=W[Xr`1 &e'l.@<7&\C=GM sGvX-K%ׄWd./cguE)Lכ$J>tS\(*:j?Zv%'PK\r@־øxacme/foo/__init__.pyA 0E9ŀ "ݸ. O1bL$ ޾g>WͭQC"0&`gi-8{ޅ'CAxw>lZGRmiؖ-@%_8Ff~Lae7p>FF6!URPF?@{XˋUQBMWYgO1?ObZh4~}}D3X9~?cUI3cm)QA|LpHhy/Jrލ8XίVYq9*GcBbG$ ~f2M0vN;чNR4BY]hth~ܕG`]Z:bG_PKϔ%J2EGG-INFO/dependency_links.txtPKϔ%J>4KEGG-INFO/entry_points.txtRԼT̼Xpbrn^Z~- EVn`X5PKϔ%J̯uEGG-INFO/namespace_packages.txtKLMJzi\PKϔ%J%EGG-INFO/PKG-INFOE 0D| 5' J5 E=/qْ"֞ o.1p4RRX 4ߕJDmȄ-h7厓8vy#Op72D[\1spYo*QR{1zȞPKϔ%Jw,EGG-INFO/SOURCES.txtu 0 K8<%Kœ!9|w3,V'lI~,`}j.9u>u(r@$s%C0]ZI cſHLv@ ~PKϔ%J߳EGG-INFO/top_level.txtKLMPKϔ%J2EGG-INFO/zip-safePK\r@־øxacme/__init__.pyPKϔ%J(acme/__pycache__/__init__.cpython-35.pycPK\r@־øxacme/foo/__init__.pyPKWV@QbIacme/foo/foo_plugin.pyPKϔ%J1X5,acme/foo/__pycache__/__init__.cpython-35.pycPKϔ%J[b.acme/foo/__pycache__/foo_plugin.cpython-35.pycPKϔ%J2EGG-INFO/dependency_links.txtPKϔ%J>4KEGG-INFO/entry_points.txtPKϔ%J̯unEGG-INFO/namespace_packages.txtPKϔ%J%EGG-INFO/PKG-INFOPKϔ%Jw,wEGG-INFO/SOURCES.txtPKϔ%J߳3 EGG-INFO/top_level.txtPKϔ%J2n EGG-INFO/zip-safePK  envisage-6.0.1/envisage/tests/eggs/acme.foo-0.1a1-py3.6.egg000066400000000000000000000065441406265421000227660ustar00rootroot00000000000000PKLKDEGG-INFO/PKG-INFOM0E~m`ەL4j5zRhtH) ^*nrɽLꉱu&, PWe:\ȇ=ˊjfH G#aʡnJb5҇3cg0ж,DKӷqHcY3*4PKLw,EGG-INFO/SOURCES.txtu 0 K8<%Kœ!9|w3,V'lI~,`}j.9u>u(r@$s%C0]ZI cſHLv@ ~PKL2EGG-INFO/dependency_links.txtPKL>4KEGG-INFO/entry_points.txtRԼT̼Xpbrn^Z~- EVn`X5PKL̯uEGG-INFO/namespace_packages.txtKLMJzi\PKL߳EGG-INFO/top_level.txtKLMPKL2EGG-INFO/zip-safePKeJL־øxacme/__init__.pyA 0E9ŀ "ݸ. O1bL$ ޾g>WͭQCWͭQCgu|21$"_ƿnBCw5۲9&5*=`k;*~7R@!h=hhx(ZdRLw;| /D`XUI|dHl2MXnSU\Z߉(*/`tyݤe67t|zr#X\rKﴟ\K4`]ôWc,b{=&3a}7b='(&V(JT;T4*ʊ~*f`=gsCإt>炊@n2M{&N*ac V!V\lb۶G4PKLKDEGG-INFO/PKG-INFOPKLw,EGG-INFO/SOURCES.txtPKL2EGG-INFO/dependency_links.txtPKL>4KEGG-INFO/entry_points.txtPKL̯u/EGG-INFO/namespace_packages.txtPKL߳zEGG-INFO/top_level.txtPKL2EGG-INFO/zip-safePKeJL־øxacme/__init__.pyPKL(acme/__pycache__/__init__.cpython-36.pycPKeJL־øxacme/foo/__init__.pyPKeJLQb.acme/foo/foo_plugin.pyPKLִ{,|acme/foo/__pycache__/__init__.cpython-36.pycPKLj..{acme/foo/__pycache__/foo_plugin.cpython-36.pycPK  envisage-6.0.1/envisage/tests/eggs/acme.foo-0.1a1-py3.7.egg000066400000000000000000000065501406265421000227640ustar00rootroot00000000000000PKDKN%EGG-INFO/PKG-INFOE 0D| 5' J5 E=/qْ"֞ o.1p4RRX 4ߕJDmȄ-h7厓8vy#Op72D[\1spYo*QR{1zȞPKDKNw,EGG-INFO/SOURCES.txtu 0 K8<%Kœ!9|w3,V'lI~,`}j.9u>u(r@$s%C0]ZI cſHLv@ ~PKDKN2EGG-INFO/dependency_links.txtPKDKN>4KEGG-INFO/entry_points.txtRԼT̼Xpbrn^Z~- EVn`X5PKDKN̯uEGG-INFO/namespace_packages.txtKLMJzi\PKDKN߳EGG-INFO/top_level.txtKLMPKDKN2EGG-INFO/zip-safePKz*M־øxacme/__init__.pyA 0E9ŀ "ݸ. O1bL$ ޾g>WͭQCWͭQCut<58"_FoLmw_Sdl+;(̀JʠFX  >҃v\E:6f^ʯ#~&ÊQGMjA Uͧa0U%uqXd_WMRoNNͳsu>b\ѕ۪>~G U/["&aS 2c6~ 2XyoTx ijJV#>ҢJ$v֝J஁ N\PMfq`ˑ^~tʭ&r4oO/6_mZGtPKDKN%EGG-INFO/PKG-INFOPKDKNw,EGG-INFO/SOURCES.txtPKDKN2zEGG-INFO/dependency_links.txtPKDKN>4KEGG-INFO/entry_points.txtPKDKN̯u#EGG-INFO/namespace_packages.txtPKDKN߳nEGG-INFO/top_level.txtPKDKN2EGG-INFO/zip-safePKz*M־øxacme/__init__.pyPKDKNiL(acme/__pycache__/__init__.cpython-37.pycPKz*M־øx|acme/foo/__init__.pyPKz*MQb&acme/foo/foo_plugin.pyPKDKN[͹,tacme/foo/__pycache__/__init__.cpython-37.pycPKDKN |3.wacme/foo/__pycache__/foo_plugin.cpython-37.pycPK  envisage-6.0.1/envisage/tests/eggs/acme.foo-0.1a1-py3.8.egg000066400000000000000000000071241406265421000227630ustar00rootroot00000000000000PKD0O%EGG-INFO/PKG-INFOE 0D| 5' J5 E=/qْ"֞ o.1p4RRX 4ߕJDmȄ-h7厓8vy#Op72D[\1spYo*QR{1zȞPKD0Ow,EGG-INFO/SOURCES.txtu 0 K8<%Kœ!9|w3,V'lI~,`}j.9u>u(r@$s%C0]ZI cſHLv@ ~PKD0O2EGG-INFO/dependency_links.txtPKD0O>4KEGG-INFO/entry_points.txtRԼT̼Xpbrn^Z~- EVn`X5PKD0O̯uEGG-INFO/namespace_packages.txtKLMJzi\PKD0O߳EGG-INFO/top_level.txtKLMPKD0O2EGG-INFO/zip-safePK[A0O־øxacme/__init__.pyA 0E9ŀ "ݸ. O1bL$ ޾g>WͭQCLz8lrSJsR؀f|9PK[A0O־øxacme/foo/__init__.pyA 0E9ŀ "ݸ. O1bL$ ޾g>WͭQCІLz8lrSJsR؀|9PKD0O~}+.acme/foo/__pycache__/foo_plugin.cpython-38.pycj0%Y4nJuZ(c 0hvcQ330eJ^W;rІLOG>\?^&{~:ErZQ[;SSW;'v$NxqXY6عx;Vyf[E"`1W.8w46< @ >h1┥HBEZSE7wdD`GNL֘g$ Ɋ5~e lR #U*^s/Y+Ȫ:*TW'gչ< l6ΆnUǣ[>c]> 3NEN( ]NFH9O %eӑаmݕf䛎/% Vv. PԚ7A˹LwkV%3%efS$ߎ׶۴wi PKD0O%EGG-INFO/PKG-INFOPKD0Ow,EGG-INFO/SOURCES.txtPKD0O2zEGG-INFO/dependency_links.txtPKD0O>4KEGG-INFO/entry_points.txtPKD0O̯u#EGG-INFO/namespace_packages.txtPKD0O߳nEGG-INFO/top_level.txtPKD0O2EGG-INFO/zip-safePK[A0O־øxacme/__init__.pyPKD0Ozb(acme/__pycache__/__init__.cpython-38.pycPK[A0O־øx}acme/foo/__init__.pyPK[A0O֔m c'acme/foo/foo_plugin.pyPKD0O`8,dacme/foo/__pycache__/__init__.cpython-38.pycPKD0O~}+.hacme/foo/__pycache__/foo_plugin.cpython-38.pycPK  envisage-6.0.1/envisage/tests/eggs/acme.foo-0.1a1-py3.9.egg000066400000000000000000000070601406265421000227630ustar00rootroot00000000000000PKu(r@$s%C0]ZI cſHLv@ ~PK4KEGG-INFO/entry_points.txtRԼT̼Xpbrn^Z~- EVn`X5PKu@eZx@6\PKwwv1" E<8\pimOOx@|` )+fodnq-MJIbFW/&5M`M8Mkm(*kTfM{x{ ]C^7qf==?ǧo.xu6Bgv-kn+k̳Vzll+fit$d65a+G{ Q0c{rJʶ'eaY:2-_J,c+}+O ]|5aorsA'Dי=33 {^sUnט4oߍ]ZC:PK4KEGG-INFO/entry_points.txtPK 0. This did not work on # Py3. Test changed to check the len(distributions) if len(distributions) == 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). for distribution in distributions: working_set.add(distribution) envisage-6.0.1/envisage/tests/test_egg_basket_plugin_manager.py000066400000000000000000000232771406265421000250510ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ Tests for the 'Egg Basket' plugin manager. """ import glob from os.path import basename, dirname, join import shutil import tempfile import sys import unittest import pkg_resources from envisage.egg_basket_plugin_manager import EggBasketPluginManager 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") self.bad_eggs_dir = join(dirname(__file__), "bad_eggs") def tearDown(self): """ Called immediately after each test method has been called. """ # Undo any side-effects: egg_basket_plugin_manager modifies sys.path. sys_path = [] for path in sys.path: if self.bad_eggs_dir not in path: sys_path.append(path) sys.path = sys_path # `envisage.egg_utils.get_entry_points_in_egg_order` modifies the # global working set. pkg_resources.working_set = pkg_resources.WorkingSet() #### 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) 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) 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) 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) 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) 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) def test_ignore_broken_plugins_raises_exceptions_by_default(self): plugin_manager = EggBasketPluginManager( plugin_path=[self.bad_eggs_dir, self.eggs_dir], ) with self.assertRaises(ImportError): list(plugin_manager) def test_ignore_broken_plugins_loads_good_plugins(self): data = {"count": 0} def on_broken_plugin(ep, exc): data["count"] += 1 data["entry_point"] = ep data["exc"] = exc plugin_manager = EggBasketPluginManager( plugin_path=[self.bad_eggs_dir, self.eggs_dir], on_broken_plugin=on_broken_plugin, ) 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) self.assertEqual(data["count"], 1) self.assertEqual(data["entry_point"].name, "acme.bad") exc = data["exc"] self.assertTrue(isinstance(exc, ImportError)) def test_ignore_broken_distributions_raises_exceptions_by_default(self): plugin_manager = EggBasketPluginManager( plugin_path=[ self.bad_eggs_dir, self._create_broken_distribution_eggdir("acme.foo*.egg"), ], ) with self.assertRaises(SystemError): iter(plugin_manager) def test_ignore_broken_distributions_loads_good_distributions(self): data = {"count": 0} def on_broken_distribution(dist, exc): data["count"] += 1 data["distribution"] = dist data["exc"] = exc plugin_manager = EggBasketPluginManager( plugin_path=[ self.eggs_dir, self._create_broken_distribution_eggdir("acme.foo*.egg"), ], on_broken_distribution=on_broken_distribution, ) 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) self.assertEqual(data["count"], 1) self.assertEqual(data["distribution"].project_name, "acme.foo") exc = data["exc"] self.assertTrue(isinstance(exc, pkg_resources.VersionConflict)) #### 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) def _create_broken_distribution_eggdir(self, egg_pat, replacement=None): """ Copy a good egg to a different version egg name in a new temp dir and return the new directory. Parameters ---------- egg_pat: a glob pattern for the egg in `self.egg_dir` eg 'foo.bar*.egg' replacement: a string replacement for the version part of egg name. If None, '1' is appended to the original version. Returns ------- The newly created dir where the new broken egg is copied. Adding this dir to plugin_path will cause VersionConflict on trying to load distributions. """ tmpdir = tempfile.mkdtemp() self.addCleanup(shutil.rmtree, tmpdir) # Copy the egg to the temp dir and rename it eggs = glob.glob(join(self.eggs_dir, egg_pat)) for egg in eggs: egg_name = basename(egg) split_name = egg_name.split("-") if replacement is None: split_name[1] += "1" else: split_name[1] = replacement new_name = "-".join(split_name) shutil.copy(egg, join(tmpdir, new_name)) return tmpdir envisage-6.0.1/envisage/tests/test_egg_plugin_manager.py000066400000000000000000000144441406265421000235140ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ Tests for the Egg plugin manager. """ # Enthought library imports. from envisage.api import EggPluginManager # Local imports. from .test_egg_based import EggBasedTestCase 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.assertTrue("acme.foo" in ids) self.assertTrue("acme.bar" in ids) self.assertTrue("acme.baz" in ids) 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 = [r"acme\.foo", r"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) 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) 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 = [r"acme\.foo", r"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) 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 = [r"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) ########################################################################### # 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) envisage-6.0.1/envisage/tests/test_extension_point.py000066400000000000000000000210231406265421000231160ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ Tests for extension points. """ # Standard library imports. import unittest # Enthought library imports. from envisage.api import Application, ExtensionPoint from envisage.api import ExtensionRegistry from traits.api import HasTraits, Int, List, TraitError 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. """ 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 def test_invalid_extension_point_type(self): """ invalid extension point type """ # Extension points currently have to be 'List's of something! The # default is a list of anything. with self.assertRaises(TypeError): ExtensionPoint(Int, "my.ep") 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() with self.assertRaises(ValueError): getattr(f, "x") def test_extension_point_changed(self): """ extension point changed """ registry = self.registry # Add an extension point. registry.add_extension_point(self._create_extension_point("my.ep")) # Declare a class that consumes the extension. class Foo(TestBase): x = ExtensionPoint(id="my.ep") def _x_changed(self): """ Static trait change handler. """ self.x_changed_called = True f = Foo() # Connect the extension points on the object so that it can listen # for changes. ExtensionPoint.connect_extension_point_traits(f) # Set the extensions. registry.set_extensions("my.ep", [42, "a string", True]) # Make sure that instances of the class pick up the extensions. self.assertEqual(3, len(f.x)) self.assertEqual([42, "a string", True], f.x) # Make sure the trait change handler was called. self.assertTrue(f.x_changed_called) # Reset the change handler flag. f.x_changed_called = False # Disconnect the extension points on the object. ExtensionPoint.disconnect_extension_point_traits(f) # Set the extensions. registry.set_extensions("my.ep", [98, 99, 100]) # Make sure the trait change handler was *not* called. self.assertEqual(False, f.x_changed_called) def test_mutate_extension_point_no_effect(self): """ Extension point is recomputed so mutation has no effect. """ 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", [1, 2, 3]) # Declare a class that consumes the extension. class Foo(TestBase): x = ExtensionPoint(List(Int), id="my.ep") # when f = Foo() f.x.append(42) # then # The registry is not changed, and the extension point is still the # same as before self.assertEqual(registry.get_extensions("my.ep"), [1, 2, 3]) self.assertEqual(f.x, [1, 2, 3]) 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) 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) 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() with self.assertRaises(TraitError): getattr(f, "x") def test_extension_point_with_no_id(self): """ extension point with no Id """ def factory(): class Foo(TestBase): x = ExtensionPoint(List(Int)) with self.assertRaises(ValueError): factory() 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")) 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")) def test_extension_point_str_representation(self): """ test the string representation of the extension point """ ep_repr = "ExtensionPoint(id={!r})" ep = self._create_extension_point("my.ep") self.assertEqual(ep_repr.format("my.ep"), str(ep)) self.assertEqual(ep_repr.format("my.ep"), repr(ep)) ########################################################################### # 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) envisage-6.0.1/envisage/tests/test_extension_point_binding.py000066400000000000000000000163101406265421000246130ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ Tests for extension point bindings. """ # Standard library imports. import unittest # Enthought library imports. from envisage.api import ExtensionPoint from envisage.api import bind_extension_point from traits.api import HasTraits, List # Local imports. 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 class ExtensionPointBindingTestCase(unittest.TestCase): """ Tests for extension point binding. """ 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 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.assertTrue(42 in f.x) self.assertTrue("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.assertTrue("a string" in listener.new.added) 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.assertTrue("a string" in f.x) self.assertEqual(1, len(registry.get_extensions("my.ep"))) self.assertTrue("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.assertTrue("a string" in listener.new) 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.assertTrue("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.assertTrue("a string" in listener.new) 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)) 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)) ########################################################################### # 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) envisage-6.0.1/envisage/tests/test_extension_point_changed.py000066400000000000000000000316571406265421000246050ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ Tests for the events fired when extension points are changed. """ # Standard library imports. import unittest # Local imports. from envisage.api import ExtensionPointChangedEvent from envisage.tests.test_application import ( PluginA, PluginB, PluginC, TestApplication, listener, ) class ExtensionPointChangedTestCase(unittest.TestCase): """ Tests for the events fired when extension points are changed. """ 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 def test_set_extension_point(self): """ set extension point """ a = PluginA() application = TestApplication(plugins=[a]) application.start() # Try to set the extension point. with self.assertRaises(SystemError): setattr(a, "x", [1, 2, 3]) def test_mutate_extension_point_no_events(self): """ Mutation will not emit change event for name_items """ a = PluginA() a.on_trait_change(listener, "x_items") b = PluginB() c = PluginC() application = TestApplication(plugins=[a, b, c]) application.start() # when a.x.append(42) # then self.assertIsNone(listener.obj) 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) 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) 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) 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) 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) # Keep the old values for later slicing check source_values = list(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) # The removed entry should match what the old values say self.assertEqual( listener.new.removed, source_values[listener.new.index] ) # If we use the index and apply the changes to the old list, we should # recover the new list source_values[listener.new.index] = listener.new.added self.assertEqual(source_values, application.get_extensions("a.x")) self.assertEqual(0, listener.new.index.start) self.assertEqual(3, listener.new.index.stop) 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) 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) def test_extension_point_change_event_str_representation(self): """ test string representation of the ExtensionPointChangedEvent class """ desired_repr = ("ExtensionPointChangedEvent(extension_point_id={}, " "index=0, removed=[], added=[])") ext_pt_changed_evt = ExtensionPointChangedEvent(extension_point_id=1) self.assertEqual(desired_repr.format(1), str(ext_pt_changed_evt)) self.assertEqual(desired_repr.format(1), repr(ext_pt_changed_evt)) envisage-6.0.1/envisage/tests/test_extension_registry.py000066400000000000000000000153421406265421000236440ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ Tests for the base extension registry. """ # Standard library imports. import contextlib import unittest # Enthought library imports. from envisage.api import Application, ExtensionPoint from envisage.api import ExtensionRegistry from traits.api import List from envisage.tests.test_extension_registry_mixin import ( ExtensionRegistryTestMixin ) class ExtensionRegistryTestCase(ExtensionRegistryTestMixin, unittest.TestCase): """ Tests for the base extension registry. """ 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()) 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")) 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")) def make_function_listener(events): """ Return a simple non-method extension point listener. The listener appends events to the ``events`` list. """ def listener(registry, event): events.append(event) return listener class ListensToExtensionPoint: """ Class with a method that can be used as an extension point listener. """ def __init__(self, events): self.events = events def listener(self, registry, event): self.events.append(event) class ExtensionPointListenerLifetimeTestCase(unittest.TestCase): def setUp(self): # We do all of the testing via the application to make sure it offers # the same interface! registry = Application(extension_registry=ExtensionRegistry()) extension_point = ExtensionPoint(id="my.ep", trait_type=List()) registry.add_extension_point(extension_point) self.registry = registry # A place to record events that listeners receive. self.events = [] def test_add_nonmethod_listener(self): listener = make_function_listener(self.events) self.registry.add_extension_point_listener(listener, "my.ep") with self.assertAppendsTo(self.events): self.registry.set_extensions("my.ep", [1, 2, 3]) def test_remove_nonmethod_listener(self): listener = make_function_listener(self.events) self.registry.add_extension_point_listener(listener, "my.ep") self.registry.remove_extension_point_listener(listener, "my.ep") with self.assertDoesNotModify(self.events): self.registry.set_extensions("my.ep", [4, 5, 6, 7]) def test_nonmethod_listener_lifetime(self): listener = make_function_listener(self.events) self.registry.add_extension_point_listener(listener, "my.ep") # The listener should not kept alive by the registry. del listener with self.assertDoesNotModify(self.events): self.registry.set_extensions("my.ep", [4, 5, 6, 7]) def test_add_method_listener(self): obj = ListensToExtensionPoint(self.events) self.registry.add_extension_point_listener(obj.listener, "my.ep") # At this point, the bound method 'obj.listener' no longer # exists; it's already been garbage collected. Nevertheless, the # listener should still fire. with self.assertAppendsTo(self.events): self.registry.set_extensions("my.ep", [1, 2, 3]) def test_remove_method_listener(self): obj = ListensToExtensionPoint(self.events) # The two occurences of `obj.listener` below refer to different # objects. Nevertheless, they _compare_ equal, so the removal # should still be effective. self.registry.add_extension_point_listener(obj.listener, "my.ep") self.registry.remove_extension_point_listener(obj.listener, "my.ep") with self.assertDoesNotModify(self.events): self.registry.set_extensions("my.ep", [1, 2, 3]) def test_method_listener_lifetime(self): obj = ListensToExtensionPoint(self.events) self.registry.add_extension_point_listener(obj.listener, "my.ep") # Removing the last reference to the object should deactivate # the listener. del obj with self.assertDoesNotModify(self.events): self.registry.set_extensions("my.ep", [1, 2, 3]) # Helper assertions ####################################################### @contextlib.contextmanager def assertAppendsTo(self, some_list): """ Assert that exactly one element is appended to a list. Return a context manager that checks that the code in the corresponding with block appends exactly one element to the given list. """ old_length = len(some_list) yield new_length = len(some_list) diff = new_length - old_length self.assertEqual( diff, 1, msg="Expected exactly one new element; got {}".format(diff), ) @contextlib.contextmanager def assertDoesNotModify(self, some_list): """ Assert that a list is unchanged. Return a context manager that checks that the code in the corresponding with block does not modify the length of the given list. """ old_length = len(some_list) yield new_length = len(some_list) diff = new_length - old_length self.assertEqual( diff, 0, msg="Expected no new elements; got {}".format(diff), ) envisage-6.0.1/envisage/tests/test_extension_registry_mixin.py000066400000000000000000000104161406265421000250450ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ Base set of tests for extension registry and its subclasses wrapped in a mixin class. """ # Enthought library imports. from envisage.api import ExtensionPoint from envisage.api import UnknownExtensionPoint from traits.api import List class ExtensionRegistryTestMixin: """ Base set of tests for extension registry and its subclasses. Test cases inherriting from this mixin should define a setUp method that defines self.registry as an instance of ExtensionPointRegistry. """ 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)) 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) 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) def test_get_extension_point_return_none_if_not_found(self): """ get extension point return None if id is not found. """ self.assertIsNone(self.registry.get_extension_point("i.do.not.exist")) def test_get_extensions_mutation_no_effect_if_undefined(self): """ test one cannot mutate the registry by mutating the list. """ # The extension point with id "my.ep" has not been defined extensions = self.registry.get_extensions("my.ep") # when extensions.append([[1, 2]]) # then # the registry is not affected. self.assertEqual(self.registry.get_extensions("my.ep"), []) 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)) def test_remove_non_existent_extension_point(self): """ remove non existent extension point """ registry = self.registry with self.assertRaises(UnknownExtensionPoint): registry.remove_extension_point("my.ep") 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) with self.assertRaises(ValueError): registry.remove_extension_point_listener(listener) def create_extension_point(self, id, trait_type=List, desc=""): """ Create an extension point. """ return ExtensionPoint(id=id, trait_type=trait_type, desc=desc) envisage-6.0.1/envisage/tests/test_ids.py000066400000000000000000000060001406265421000204460ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! import unittest import envisage.ids from envisage.api import CorePlugin from envisage.plugins.python_shell.python_shell_plugin import PythonShellPlugin from envisage.ui.tasks.api import TasksPlugin # Skip tests involving the IPython kernel unless ipykernel is available. try: from envisage.plugins.ipython_kernel.api import IPythonKernelPlugin except ImportError: IPythonKernelPlugin = None class TestIds(unittest.TestCase): def test_id_strings(self): extension_point_ids = [ # Extension point IDs "PREFERENCES", "SERVICE_OFFERS", "BINDINGS", "COMMANDS", "IPYTHON_NAMESPACE", "PREFERENCES_CATEGORIES", "PREFERENCES_PANES", "TASKS", "TASK_EXTENSIONS", # Service IDs "IPYTHON_KERNEL_PROTOCOL", ] for extension_point_id in extension_point_ids: id_value = getattr(envisage.ids, extension_point_id) self.assertIsInstance(id_value, str) def test_id_strings_against_plugin_constants(self): # Check extension point IDs against ground truth on plugins self.check_id_against_plugin("PREFERENCES", CorePlugin) self.check_id_against_plugin("SERVICE_OFFERS", CorePlugin) self.check_id_against_plugin("BINDINGS", PythonShellPlugin) self.check_id_against_plugin("COMMANDS", PythonShellPlugin) self.check_id_against_plugin("PREFERENCES_CATEGORIES", TasksPlugin) self.check_id_against_plugin("PREFERENCES_PANES", TasksPlugin) self.check_id_against_plugin("TASKS", TasksPlugin) self.check_id_against_plugin("TASK_EXTENSIONS", TasksPlugin) @unittest.skipIf( IPythonKernelPlugin is None, "skipping tests that require IPython packages", ) def test_id_strings_against_plugin_constants_ipykernel(self): # Check extension point IDs against ground truth on plugins self.check_id_against_plugin("IPYTHON_NAMESPACE", IPythonKernelPlugin) # Check service IDs against ground truth on plugins self.check_id_against_plugin( "IPYTHON_KERNEL_PROTOCOL", IPythonKernelPlugin) def check_id_against_plugin(self, id_string, plugin_klass): """ Check that the value of an id string matches that of a class variable. Parameters ---------- id_string : str Identifier for the id. plugin_klass : type The Plugin subclass to check. """ self.assertEqual( getattr(envisage.ids, id_string), getattr(plugin_klass, id_string) ) envisage-6.0.1/envisage/tests/test_import_manager.py000066400000000000000000000031431406265421000227000ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ Tests for the import manager. """ # Standard library imports. import unittest # Enthought library imports. from envisage.api import Application, ImportManager class ImportManagerTestCase(unittest.TestCase): """ Tests for the import manager. """ 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()) def test_import_dotted_symbol(self): """ import dotted symbol """ import tarfile symbol = self.import_manager.import_symbol("tarfile.TarFile") self.assertEqual(symbol, tarfile.TarFile) 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) def test_import_dotted_module(self): """ import dotted module """ symbol = self.import_manager.import_symbol( "envisage.api:ImportManager" ) self.assertEqual(symbol, ImportManager) envisage-6.0.1/envisage/tests/test_package_plugin_manager.py000066400000000000000000000126231406265421000243420ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ Tests for the 'Package' plugin manager. """ from os.path import dirname, join import unittest from envisage.package_plugin_manager import PackagePluginManager class PackagePluginManagerTestCase(unittest.TestCase): """ Tests for the 'Package' plugin manager. """ 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") 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) 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) 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) 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) 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) 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) #### 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( list(sorted(expected)), list(sorted(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) envisage-6.0.1/envisage/tests/test_plugin.py000066400000000000000000000270641406265421000212020ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ Tests for plugins. """ # Standard library imports. from os.path import exists, join import unittest # Enthought library imports. from envisage.api import Application, ExtensionPoint from envisage.api import IPluginActivator, Plugin from envisage.tests.ets_config_patcher import ETSConfigPatcher from traits.api import HasTraits, Instance, Int, Interface, List, provides 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 class TestApplication(Application): """ The type of application used in the tests. """ id = "test" class PluginTestCase(unittest.TestCase): """ Tests for plugins. """ def setUp(self): ets_config_patcher = ETSConfigPatcher() ets_config_patcher.start() self.addCleanup(ets_config_patcher.stop) 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) 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) def test_plugin_activator(self): """ plugin activator. """ @provides(IPluginActivator) class NullPluginActivator(HasTraits): """ A plugin activator that does nothing! """ def start_plugin(self, plugin): """ Start a plugin. """ self.started = plugin def stop_plugin(self, plugin): """ Stop a plugin. """ self.stopped = plugin 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) 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)) def test_service_protocol(self): """ service protocol """ class IFoo(Interface): pass class IBar(Interface): pass @provides(IFoo, IBar) class Foo(HasTraits): pass 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)) 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. with self.assertRaises(ValueError): application.get_extensions("x") 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. with self.assertRaises(ZeroDivisionError): application.get_extensions("x") 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")) 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 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) 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.assertTrue(exists(a.home)) self.assertTrue(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.assertTrue(exists(a.home)) self.assertTrue(exists(b.home)) def test_no_recursion(self): """ Regression test for #119. """ class PluginA(Plugin): id = "A" x = ExtensionPoint(List, id="bob") application = Application(plugins=[PluginA()]) application.get_extensions("bob") def test_plugin_str_representation(self): """ test the string representation of the plugin """ plugin_repr = "Plugin(id={!r}, name={!r})" plugin = Plugin(id="Fred", name="Wilma") self.assertEqual(plugin_repr.format("Fred", "Wilma"), str(plugin)) self.assertEqual(plugin_repr.format("Fred", "Wilma"), repr(plugin)) envisage-6.0.1/envisage/tests/test_plugin_manager.py000066400000000000000000000177471406265421000227030ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ Tests for the plugin manager. """ # Standard library imports. import unittest # Enthought library imports. from envisage.api import Plugin, PluginManager from traits.api import Bool 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 def stop(self): """ Stop the plugin. """ self.started = False self.stopped = True 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. """ 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")) 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) 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) 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. with self.assertRaises(ZeroDivisionError): plugin_manager.start() # Stop the plugin manager. This stops all of the plugin manager's # plugins. with self.assertRaises(ZeroDivisionError): plugin_manager.stop() # Try to start a non-existent plugin. with self.assertRaises(SystemError): plugin_manager.start_plugin(plugin_id="bogus") # Try to stop a non-existent plugin. with self.assertRaises(SystemError): plugin_manager.stop_plugin(plugin_id="bogus") 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) 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) 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) 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) #### 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) envisage-6.0.1/envisage/tests/test_provider_extension_registry.py000066400000000000000000000437451406265421000255660ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ Tests for the provider extension registry. """ # Standard library 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 envisage.tests.test_extension_registry_mixin import ( ExtensionRegistryTestMixin ) class ProviderExtensionRegistryTestCase( ExtensionRegistryTestMixin, unittest.TestCase): """ Tests for the provider extension registry. """ def setUp(self): """ Prepares the test fixture before each test method is called. """ self.registry = ProviderExtensionRegistry() 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(list(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) 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)) ) def _x_items_changed(self, event): """ Static trait change handler. """ self._fire_extension_point_changed( "my.ep", event.added, event.removed, event.index ) 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)) ) def _x_items_changed(self, event): """ Static trait change handler. """ self._fire_extension_point_changed( "my.ep", event.added, event.removed, event.index ) # 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 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) 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 ) # 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.assertTrue(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 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.assertTrue(42 in extensions) self.assertTrue(43 in extensions) self.assertTrue(44 in extensions) 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()) 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 ) 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.assertTrue(42 in extensions) self.assertTrue(43 in extensions) self.assertTrue(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 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.assertTrue(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) 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 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)) 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. with self.assertRaises(ValueError): registry.remove_provider(a) 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. with self.assertRaises(SystemError): registry.set_extensions("my.ep", [1, 2, 3]) 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(list(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")) envisage-6.0.1/envisage/tests/test_safeweakref.py000066400000000000000000000104731406265421000221630ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ Tests for safe weakrefs. """ # Standard library imports. import unittest import weakref # Enthought library imports. from envisage.safeweakref import ref from traits.api import HasTraits class SafeWeakrefTestCase(unittest.TestCase): """ Tests for safe weakrefs. """ 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. with self.assertWarns(DeprecationWarning): r = ref(f.method) self.assertNotEqual(None, r()) # Make sure we can call it. r()() self.assertTrue(f.method_called) # Delete the object to delete the method! del f # The reference should now return None. self.assertEqual(None, r()) def test_two_weakrefs_to_bound_method_are_identical(self): class Foo(HasTraits): def method(self): pass f = Foo() with self.assertWarns(DeprecationWarning): ref1 = ref(f.method) ref2 = ref(f.method) self.assertIs(ref1, ref2) 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. with self.assertWarns(DeprecationWarning): 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)) 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. with self.assertWarns(DeprecationWarning): r1 = ref(f.method) r2 = ref(f.method) self.assertEqual(r1, r2) # Make sure that a reference compares as unequal to non-references! self.assertTrue(not r1 == 99) 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. with self.assertWarns(DeprecationWarning): r1 = ref(f.method) r2 = ref(f.method) self.assertEqual(hash(r1), hash(r2)) # Make sure we can hash non-bound methods. with self.assertWarns(DeprecationWarning): r1 = ref(Foo) r2 = ref(Foo) self.assertEqual(hash(r1), hash(r2)) 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. with self.assertWarns(DeprecationWarning): r = ref(f) self.assertEqual(weakref.ref, type(r)) def test_deprecated(self): class Foo(HasTraits): def method(self): pass f = Foo() with self.assertWarns(DeprecationWarning): ref(f.method) with self.assertWarns(DeprecationWarning): ref(f) envisage-6.0.1/envisage/tests/test_service.py000066400000000000000000000046001406265421000213330ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ Tests for the 'Service' trait type. """ # Standard library imports. import unittest # Enthought library imports. from envisage.api import Application, Plugin, Service from traits.api import HasTraits, Instance class TestApplication(Application): """ The type of application used in the tests. """ id = "test" class ServiceTestCase(unittest.TestCase): """ Tests for the 'Service' trait type. """ 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! with self.assertRaises(SystemError): setattr(b, "foo", "bogus") 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() with self.assertRaises(ValueError): getattr(b, "foo") def test_service_str_representation(self): """ test the string representation of the service """ class Foo(HasTraits): pass service_repr = "Service(protocol={!r})" service = Service(Foo) self.assertEqual(service_repr.format(Foo), str(service)) self.assertEqual(service_repr.format(Foo), repr(service)) envisage-6.0.1/envisage/tests/test_service_registry.py000066400000000000000000000403731406265421000232720ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ Tests for the service registry. """ # Standard library imports. import sys import unittest # Enthought library imports. from envisage.api import Application, ServiceRegistry, NoSuchServiceError from traits.api import HasTraits, Int, Interface, provides # 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, # because other tests also import foo. if PKG + ".foo" in sys.modules: del sys.modules[PKG + ".foo"] ########################################################################### # 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) 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) 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 + ".test_service_registry.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.assertTrue(service is service2) def test_function_service_factory(self): """ function service factory """ class IFoo(Interface): price = Int @provides(IFoo) class Foo(HasTraits): 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.assertTrue(service is service2) 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.assertTrue(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.assertTrue(foo not in sys.modules) # Look it up again. services = self.service_registry.get_services(i_foo) self.assertEqual([foo_factory.foo], services) self.assertTrue(foo in sys.modules) # Clean up! del sys.modules[foo] 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.assertTrue(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.assertTrue(foo not in sys.modules) # Look up the service. services = self.service_registry.get_services(i_foo) self.assertEqual([sp.foo], services) self.assertTrue(foo in sys.modules) # Clean up! del sys.modules[foo] def test_get_services(self): """ get services """ class IFoo(Interface): pass @provides(IFoo) class Foo(HasTraits): pass # 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) 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)) def test_get_services_with_query(self): """ get services with query """ class IFoo(Interface): price = Int @provides(IFoo) class Foo(HasTraits): 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.assertTrue(foo in services) self.assertTrue(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) def test_get_service(self): """ get service """ class IFoo(Interface): pass @provides(IFoo) class Foo(HasTraits): pass # 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.assertTrue(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) def test_get_service_with_query(self): """ get service with query """ class IFoo(Interface): price = Int @provides(IFoo) class Foo(HasTraits): 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.assertTrue(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) def test_get_and_set_service_properties(self): """ get and set service properties """ class IFoo(Interface): price = Int @provides(IFoo) class Foo(HasTraits): 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. with self.assertRaises(ValueError): self.service_registry.get_service_properties(-1) # Try to set the properties of a non-existent service. with self.assertRaises(ValueError): self.service_registry.set_service_properties(-1, {}) def test_unregister_service(self): """ unregister service """ class IFoo(Interface): price = Int @provides(IFoo) class Foo(HasTraits): 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.assertTrue(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. with self.assertRaises(ValueError): self.service_registry.unregister_service(-1) def test_minimize_and_maximize(self): """ minimize and maximize """ class IFoo(Interface): price = Int @provides(IFoo) class Foo(HasTraits): 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) envisage-6.0.1/envisage/tests/test_slice.py000066400000000000000000000125731406265421000210020ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ 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. """ # Standard library imports. import unittest # Enthought library imports. from traits.api import HasTraits, List # 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[:] added = event.added # Backwards compatibility for Traits < 6.0, where event.added may # be a list containing a list containing the added elements. This # block can be removed once compatibility with Traits < 6.0 is no # longer needed. Ref: enthought/traits#300. if len(added) == 1 and isinstance(added[0], list): added = added[0] # If nothing was added then this is a 'del' or 'remove' operation. if len(added) == 0: if isinstance(event.index, slice): del clone[event.index] else: # workaroud for traits bug in Python 3 # https://github.com/enthought/traits/issues/334 index = event.index if event.index is not None else 0 del clone[index: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] = added else: clone[event.index:event.index] = added # Otherwise, it is an assigment ('sort' and 'reverse' fall into this # category). else: if isinstance(event.index, slice): clone[event.index] = added else: clone[event.index:event.index + len(added)] = added listener.clone = clone class SliceTestCase(unittest.TestCase): """ Tests to help find out how trait list events work. """ def setUp(self): """ Prepares the test fixture before each test method is called. """ class Foo(HasTraits): elts = List self.f = Foo(elts=TEST_LIST) self.f.on_trait_change(listener, "elts_items") def test_append(self): """ append """ self.f.elts.append(99) # Make sure we successfully recreated the operation. self.assertEqual(self.f.elts, listener.clone) def test_insert(self): """ insert """ self.f.elts.insert(3, 99) # Make sure we successfully recreated the operation. self.assertEqual(self.f.elts, listener.clone) def test_extend(self): """ extend """ self.f.elts.extend([99, 100]) # Make sure we successfully recreated the operation. self.assertEqual(self.f.elts, listener.clone) def test_remove(self): """ remove """ self.f.elts.remove(5) # Make sure we successfully recreated the operation. self.assertEqual(self.f.elts, listener.clone) def test_reverse(self): """ reverse """ self.f.elts.reverse() # Make sure we successfully recreated the operation. self.assertEqual(self.f.elts, listener.clone) def test_sort(self): """ sort """ self.f.elts.sort() # Make sure we successfully recreated the operation. self.assertEqual(self.f.elts, listener.clone) def test_pop(self): """ remove """ self.f.elts.pop() # Make sure we successfully recreated the operation. self.assertEqual(self.f.elts, listener.clone) def test_del_all(self): """ del all """ del self.f.elts[:] # Make sure we successfully recreated the operation. self.assertEqual(self.f.elts, listener.clone) def test_assign_item(self): """ assign item """ self.f.elts[3] = 99 # Make sure we successfully recreated the operation. self.assertEqual(self.f.elts, listener.clone) def test_del_item(self): """ del item """ del self.f.elts[3] # Make sure we successfully recreated the operation. self.assertEqual(self.f.elts, listener.clone) def test_assign_slice(self): """ assign slice """ self.f.elts[2:4] = [88, 99] # Make sure we successfully recreated the operation. self.assertEqual(self.f.elts, listener.clone) def test_del_slice(self): """ del slice """ del self.f.elts[2:5] # Make sure we successfully recreated the operation. self.assertEqual(self.f.elts, listener.clone) def test_assign_extended_slice(self): """ assign extended slice """ self.f.elts[2:6:2] = [88, 99] # Make sure we successfully recreated the operation. self.assertEqual(self.f.elts, listener.clone) def test_del_extended_slice(self): """ del extended slice """ del self.f.elts[2:6:2] # Make sure we successfully recreated the operation. self.assertEqual(self.f.elts, listener.clone) envisage-6.0.1/envisage/tests/test_version.py000066400000000000000000000035571406265421000213720ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ Tests for the envisage.__version__ attribute and the envisage.version module contents. """ import unittest import pkg_resources import envisage class TestVersion(unittest.TestCase): def test_dunder_version(self): self.assertIsInstance(envisage.__version__, str) # Round-trip through parse_version; this verifies not only # that the version is valid, but also that it's properly normalised # according to the PEP 440 rules. parsed_version = pkg_resources.parse_version(envisage.__version__) self.assertEqual(str(parsed_version), envisage.__version__) def test_version_version(self): # Importing inside the test to ensure that we get a test error # in the case where the version module does not exist. from envisage.version import version self.assertIsInstance(version, str) parsed_version = pkg_resources.parse_version(version) self.assertEqual(str(parsed_version), version) def test_version_git_revision(self): from envisage.version import git_revision self.assertIsInstance(git_revision, str) # Check the form of the revision. Could use a regex, but that seems # like overkill. self.assertEqual(len(git_revision), 40) self.assertLessEqual(set(git_revision), set("0123456789abcdef")) def test_versions_match(self): import envisage.version self.assertEqual(envisage.version.version, envisage.__version__) envisage-6.0.1/envisage/ui/000077500000000000000000000000001406265421000155355ustar00rootroot00000000000000envisage-6.0.1/envisage/ui/__init__.py000066400000000000000000000000001406265421000176340ustar00rootroot00000000000000envisage-6.0.1/envisage/ui/action/000077500000000000000000000000001406265421000170125ustar00rootroot00000000000000envisage-6.0.1/envisage/ui/action/__init__.py000066400000000000000000000000001406265421000211110ustar00rootroot00000000000000envisage-6.0.1/envisage/ui/action/abstract_action_manager_builder.py000066400000000000000000000360551406265421000257350ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ 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, observe, provides # Local imports. from .action_set import ActionSet from .action_set_manager import ActionSetManager from .group import Group from .i_action_manager_builder import IActionManagerBuilder @provides(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 """ #### '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(group.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) ########################################################################### # 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 ################################################# @observe("action_sets") def _update_action_sets_on_manager(self, event): """ Static trait change handler. """ new = event.new self._action_set_manager.action_sets = new #### 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) 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) 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 envisage-6.0.1/envisage/ui/action/action.py000066400000000000000000000023441406265421000206440ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ 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__ envisage-6.0.1/envisage/ui/action/action_set.py000066400000000000000000000067451406265421000215300ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ 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, provides from traits.util.camel_case import camel_case_to_words # Local imports. from .action import Action from .group import Group from .menu import Menu from .tool_bar import ToolBar from .i_action_set import IActionSet # Logging. logger = logging.getLogger(__name__) @provides(IActionSet) class ActionSet(HasTraits): """ 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) # 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.warning("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.warning("action set %s has no name - using <%s>" % (self, name)) return name envisage-6.0.1/envisage/ui/action/action_set_manager.py000066400000000000000000000073241406265421000232140ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ 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 envisage-6.0.1/envisage/ui/action/api.py000066400000000000000000000013161406265421000201360ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! from .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-6.0.1/envisage/ui/action/group.py000066400000000000000000000025351406265421000205250ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ 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__ envisage-6.0.1/envisage/ui/action/i_action_manager_builder.py000066400000000000000000000020641406265421000243530ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ 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. """ envisage-6.0.1/envisage/ui/action/i_action_set.py000066400000000000000000000046741406265421000220370ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ 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) envisage-6.0.1/envisage/ui/action/location.py000066400000000000000000000031661406265421000212020ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ 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 envisage-6.0.1/envisage/ui/action/menu.py000066400000000000000000000051071406265421000203330ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ 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().__init__(klass=Group, **kw) ########################################################################### # 'TraitType' interface. ########################################################################### def validate(self, object, name, value): """ Validate a value. """ if isinstance(value, str): value = Group(id=value) return super().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("&") envisage-6.0.1/envisage/ui/action/tests/000077500000000000000000000000001406265421000201545ustar00rootroot00000000000000envisage-6.0.1/envisage/ui/action/tests/__init__.py000066400000000000000000000000001406265421000222530ustar00rootroot00000000000000envisage-6.0.1/envisage/ui/action/tests/dummy_action_manager_builder.py000066400000000000000000000042401406265421000264160ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ A menu builder that doesn't build real actions! """ # Enthought library imports. from envisage.ui.action.api import AbstractActionManagerBuilder from pyface.action.api import Action, Group, MenuManager from pyface.action.api import MenuBarManager class DummyActionManagerBuilder(AbstractActionManagerBuilder): """ An action manager builder that doesn't build real actions! This makes it very easy to test! """ ########################################################################### # 'DummyActionManagerBuilder' interface. ########################################################################### def create_menu_bar_manager(self, root): """ Create a menu bar manager from the builder's action sets. """ menu_bar_manager = MenuBarManager(id="MenuBar") self.initialize_action_manager(menu_bar_manager, root) return menu_bar_manager ########################################################################### # Protected 'AbstractActionManagerBuilder' interface. ########################################################################### def _create_action(self, action_definition): """ Create an action implementation from a definition. """ return Action(name=action_definition.class_name) def _create_group(self, group_definition): """ Create a group implementation from a definition. """ return Group(id=group_definition.id) def _create_menu_manager(self, menu_definition): """ Create a menu manager implementation from a definition. """ menu_manager = MenuManager(id=menu_definition.id) for group_definition in menu_definition.groups: menu_manager.insert(-1, Group(id=group_definition.id)) return menu_manager envisage-6.0.1/envisage/ui/action/tests/test_action_manager_builder.py000066400000000000000000000551261406265421000262530ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ Tests for the action manager builder. """ # Standard library imports. import unittest # Enthought library imports. from envisage.ui.action.api import Action, ActionSet, Group, Menu # Local imports. from .dummy_action_manager_builder import DummyActionManagerBuilder class ActionManagerBuilderTestCase(unittest.TestCase): """ Tests for the action manager builder. """ def test_action_with_nonexistent_group(self): """ action with non-existent group """ action_sets = [ ActionSet( actions=[ Action( class_name="Exit", path="MenuBar/File", group="Bogus" ), ] ), ] # Create a builder containing the action set. builder = DummyActionManagerBuilder(action_sets=action_sets) # Create a menu bar manager for the 'MenuBar'. with self.assertRaises(ValueError): builder.create_menu_bar_manager("MenuBar") def test_action_with_nonexistent_sibling(self): """ action with non-existent sibling """ action_sets = [ ActionSet( actions=[ Action( class_name="Exit", path="MenuBar/File", before="NonExistentAction", ), ] ), ] # Create a builder containing the action set. builder = DummyActionManagerBuilder(action_sets=action_sets) # Create a menu bar manager for the 'MenuBar'. with self.assertRaises(ValueError): builder.create_menu_bar_manager("MenuBar") def test_group_with_nonexistent_sibling(self): """ group with non-existent sibling """ action_sets = [ ActionSet( groups=[ Group(id="FileMenuGroup", path="MenuBar", before="Bogus") ] ) ] # Create a builder containing the action set. builder = DummyActionManagerBuilder(action_sets=action_sets) # Create a menu bar manager for the 'MenuBar'. with self.assertRaises(ValueError): builder.create_menu_bar_manager("MenuBar") def test_menu_with_nonexistent_sibling(self): """ menu with non-existent sibling """ action_sets = [ ActionSet( menus=[Menu(name="&File", path="MenuBar", before="Bogus")] ) ] # Create a builder containing the action set. builder = DummyActionManagerBuilder(action_sets=action_sets) # Create a menu bar manager for the 'MenuBar'. with self.assertRaises(ValueError): builder.create_menu_bar_manager("MenuBar") def test_action_with_path_component_that_is_not_a_menu(self): """ action with path component that is not a menu """ action_sets = [ ActionSet( actions=[ Action(class_name="Exit", path="MenuBar/File"), Action(class_name="Broken", path="MenuBar/File/Exit"), ] ), ] # Create a builder containing the action set. builder = DummyActionManagerBuilder(action_sets=action_sets) # Create a menu bar manager for the 'MenuBar'. with self.assertRaises(ValueError): builder.create_menu_bar_manager("MenuBar") def test_single_top_level_menu_with_no_group(self): """ single top level menu with no group """ action_sets = [ActionSet(menus=[Menu(name="&File", path="MenuBar")])] # Create a builder containing the action set. builder = DummyActionManagerBuilder(action_sets=action_sets) # Create a menu bar manager for the 'MenuBar'. menu_bar_manager = builder.create_menu_bar_manager("MenuBar") # Make sure that the 'File' menu was added to the 'additions' group # of the menubar. self.assertEqual(1, len(menu_bar_manager.groups)) group = menu_bar_manager.find_group("additions") ids = [item.id for item in group.items] self.assertEqual(["File"], ids) def test_single_top_level_group(self): """ single top level group """ action_sets = [ ActionSet(groups=[Group(id="FileMenuGroup", path="MenuBar")]) ] # Create a builder containing the action set. builder = DummyActionManagerBuilder(action_sets=action_sets) # Create a menu bar manager for the 'MenuBar'. menu_bar_manager = builder.create_menu_bar_manager("MenuBar") # Make sure that the group was added before the 'additions' group. self.assertEqual(2, len(menu_bar_manager.groups)) ids = [group.id for group in menu_bar_manager.groups] self.assertEqual(["FileMenuGroup", "additions"], ids) def test_top_level_menus_with_no_groups(self): """ top level menus with_no groups """ action_sets = [ ActionSet( menus=[ Menu(name="&File", path="MenuBar"), Menu(name="&Edit", path="MenuBar"), Menu(name="&Tools", path="MenuBar"), Menu(name="&Help", path="MenuBar"), ], ) ] # Create a builder containing the action set. builder = DummyActionManagerBuilder(action_sets=action_sets) # Create a menu bar manager for the 'MenuBar'. menu_bar_manager = builder.create_menu_bar_manager("MenuBar") # Make sure that all of the menus were added the the 'additions' group # of the menubar (and in the right order!). self.assertEqual(1, len(menu_bar_manager.groups)) group = menu_bar_manager.find_group("additions") ids = [item.id for item in group.items] self.assertEqual(["File", "Edit", "Tools", "Help"], ids) def test_top_level_menus_no_groups_before_and_after(self): """ top level menus no groups, before and after """ action_sets = [ ActionSet( menus=[Menu(name="&Edit", path="MenuBar", after="File")], ), ActionSet(menus=[Menu(name="&File", path="MenuBar")]), ActionSet(menus=[Menu(name="&Help", path="MenuBar")]), ActionSet( menus=[Menu(name="&Tools", path="MenuBar", before="Help")], ), ] # Create a builder containing the action set. builder = DummyActionManagerBuilder(action_sets=action_sets) # Create a menu bar manager for the 'MenuBar'. menu_manager = builder.create_menu_bar_manager("MenuBar") # Make sure that all of the menus were added the the 'additions' group # of the menubar. self.assertEqual(1, len(menu_manager.groups)) additions = menu_manager.find_group("additions") ids = [item.id for item in additions.items] self.assertEqual(["File", "Edit", "Tools", "Help"], ids) def test_top_level_menu_non_existent_group(self): """ top level menu non-existent group """ action_sets = [ ActionSet( menus=[ Menu(name="&File", path="MenuBar", group="FileMenuGroup"), ], ) ] # Create a builder containing the action set. builder = DummyActionManagerBuilder(action_sets=action_sets) # Create a menu bar manager for the 'MenuBar'. with self.assertRaises(ValueError): builder.create_menu_bar_manager("MenuBar") def test_top_level_menu_group(self): """ top level menu group """ action_sets = [ ActionSet( groups=[Group(id="FileMenuGroup", path="MenuBar")], menus=[ Menu(name="&File", path="MenuBar", group="FileMenuGroup"), ], ) ] # Create a builder containing the action set. builder = DummyActionManagerBuilder(action_sets=action_sets) # Create a menu bar manager for the 'MenuBar'. menu_manager = builder.create_menu_bar_manager("MenuBar") # Make sure that the 'File' menu was added to the 'FileMenuGroup' # group of the menubar. self.assertEqual(2, len(menu_manager.groups)) ids = [group.id for group in menu_manager.groups] self.assertEqual(["FileMenuGroup", "additions"], ids) group = menu_manager.find_group("FileMenuGroup") self.assertEqual("File", group.items[0].id) def test_sub_menus_no_groups(self): """ sub-menus no groups """ # We split the contributions into different action sets just because # that is how it might end up in an actual application... not because # you *have* to split them up this way! action_sets = [ ActionSet( menus=[ Menu(name="&File", path="MenuBar"), Menu(name="&Edit", path="MenuBar"), Menu(name="&Tools", path="MenuBar"), Menu(name="&Help", path="MenuBar"), ], ), ActionSet(menus=[Menu(name="&New", path="MenuBar/File")]), ] # Create a builder containing the action set. builder = DummyActionManagerBuilder(action_sets=action_sets) # Create a menu bar manager for the 'MenuBar'. menu_manager = builder.create_menu_bar_manager("MenuBar") # Make sure the 'New' sub-menu got added to the 'additions' group # of the 'File' menu. menu = menu_manager.find_item("File") additions = menu.find_group("additions") self.assertEqual("New", additions.items[0].id) def test_actions_no_groups(self): """ actions no groups """ # We split the contributions into different action sets just because # that is how it might end up in an actual application... not because # you *have* to split them up this way! action_sets = [ ActionSet( menus=[ Menu(name="&File", path="MenuBar"), Menu(name="&Edit", path="MenuBar"), Menu(name="&Tools", path="MenuBar"), Menu(name="&Help", path="MenuBar"), ] ), ActionSet( actions=[ Action(class_name="Exit", path="MenuBar/File"), Action(class_name="About", path="MenuBar/Help"), ] ), ] # Create a builder containing the action set. builder = DummyActionManagerBuilder(action_sets=action_sets) # Create a menu bar manager for the 'MenuBar'. menu_manager = builder.create_menu_bar_manager("MenuBar") # Make sure the 'ExitAction' action got added to the 'additions' group # of the 'File' menu. menu = menu_manager.find_item("File") additions = menu.find_group("additions") self.assertEqual("Exit", additions.items[0].id) # Make sure the 'AboutAction' action got added to the 'additions' group # of the 'File' menu. menu = menu_manager.find_item("Help") additions = menu.find_group("additions") self.assertEqual("About", additions.items[0].id) def test_actions_make_submenus(self): """ actions make submenus """ action_sets = [ ActionSet( actions=[ Action(class_name="Folder", path="MenuBar/File/New"), Action(class_name="File", path="MenuBar/File/New"), ] ) ] # Create a builder containing the action set. builder = DummyActionManagerBuilder(action_sets=action_sets) # Create a menu bar manager for the 'MenuBar'. menu_manager = builder.create_menu_bar_manager("MenuBar") # Make sure the 'File' menu got added to the 'additions' group of the # menubar. self.assertEqual(1, len(menu_manager.groups)) additions = menu_manager.find_group("additions") self.assertEqual("File", additions.items[0].id) # Make sure the 'New' sub-menu got added to the 'additions' group # of the 'File' menu. menu = menu_manager.find_item("File") additions = menu.find_group("additions") self.assertEqual("New", additions.items[0].id) # Make sure the new 'Folder' and 'File' actions got added to the # 'additions' group of the 'New' menu. menu = menu_manager.find_item("File/New") additions = menu.find_group("additions") self.assertEqual("Folder", additions.items[0].id) self.assertEqual("File", additions.items[1].id) def test_actions_make_submenus_before_and_after(self): """ actions make submenus before and after """ action_sets = [ ActionSet( actions=[ Action( class_name="File", path="MenuBar/File/New", after="Folder", ), Action( class_name="Project", path="MenuBar/File/New", before="Folder", ), Action(class_name="Folder", path="MenuBar/File/New"), ] ) ] # Create a builder containing the action set. builder = DummyActionManagerBuilder(action_sets=action_sets) # Create a menu bar manager for the 'MenuBar'. menu_manager = builder.create_menu_bar_manager("MenuBar") # Make sure the 'File' menu got added to the 'additions' group of the # menubar. self.assertEqual(1, len(menu_manager.groups)) additions = menu_manager.find_group("additions") self.assertEqual("File", additions.items[0].id) # Make sure the 'New' sub-menu got added to the 'additions' group # of the 'File' menu. menu = menu_manager.find_item("File") additions = menu.find_group("additions") self.assertEqual("New", additions.items[0].id) # Make sure the new 'Folder' and 'File' actions got added to the # 'additions' group of the 'New' menu. menu = menu_manager.find_item("File/New") additions = menu.find_group("additions") ids = [item.id for item in additions.items] self.assertEqual(["Project", "Folder", "File"], ids) def test_explicit_groups(self): """ explicit groups """ action_sets = [ ActionSet( menus=[ Menu(name="&File", path="MenuBar"), Menu(name="&Edit", path="MenuBar"), Menu(name="&Tools", path="MenuBar"), Menu(name="&Help", path="MenuBar"), ], ), ActionSet( menus=[ Menu(name="&New", path="MenuBar/File", group="NewGroup"), ], ), ActionSet( actions=[ Action( class_name="Exit", path="MenuBar/File", group="ExitGroup", ), ] ), ActionSet( groups=[ Group(id="ExitGroup", path="MenuBar/File"), Group( id="SaveGroup", path="MenuBar/File", after="NewGroup" ), Group( id="NewGroup", path="MenuBar/File", before="ExitGroup" ), ] ), ] # Create a builder containing the action set. builder = DummyActionManagerBuilder(action_sets=action_sets) # Create a menu bar manager for the 'MenuBar'. menu_manager = builder.create_menu_bar_manager("MenuBar") # Make sure that all of the menus were added the the 'additions' group # of the menubar. self.assertEqual(1, len(menu_manager.groups)) additions = menu_manager.find_group("additions") ids = [item.id for item in additions.items] self.assertEqual(["File", "Edit", "Tools", "Help"], ids) # Make sure the 'File' menu has got 3 groups, 'NewGroup', 'ExitGroup' # and 'additions' (and in that order!). menu = menu_manager.find_item("File") self.assertEqual(4, len(menu.groups)) ids = [group.id for group in menu.groups] self.assertEqual( ["NewGroup", "SaveGroup", "ExitGroup", "additions"], ids ) # Make sure the 'New' sub-menu got added to the 'NewGroup' group # of the 'File' menu. menu = menu_manager.find_item("File") group = menu.find_group("NewGroup") self.assertEqual("New", group.items[0].id) # Make sure the 'Exit' action got added to the 'ExitGroup' group # of the 'File' menu. menu = menu_manager.find_item("File") group = menu.find_group("ExitGroup") self.assertEqual("Exit", group.items[0].id) def test_actions_and_menus_in_groups(self): """ actions and menus in groups """ action_sets = [ ActionSet( menus=[ Menu( name="&File", path="MenuBar", groups=["NewGroup", "ExitGroup"], ), Menu(name="&Edit", path="MenuBar"), Menu(name="&Tools", path="MenuBar"), Menu(name="&Help", path="MenuBar"), ], ), ActionSet( menus=[ Menu(name="&New", path="MenuBar/File", group="NewGroup"), ], ), ActionSet( actions=[ Action( class_name="Exit", path="MenuBar/File", group="ExitGroup", ), ] ), ] # Create a builder containing the action set. builder = DummyActionManagerBuilder(action_sets=action_sets) # Create a menu bar manager for the 'MenuBar'. menu_manager = builder.create_menu_bar_manager("MenuBar") # Make sure that all of the menus were added the the 'additions' group # of the menubar. self.assertEqual(1, len(menu_manager.groups)) additions = menu_manager.find_group("additions") ids = [item.id for item in additions.items] self.assertEqual(["File", "Edit", "Tools", "Help"], ids) # Make sure the 'File' menu has got 3 groups, 'NewGroup', 'ExitGroup' # and 'additions' (and in that order!). menu = menu_manager.find_item("File") self.assertEqual(3, len(menu.groups)) ids = [group.id for group in menu.groups] self.assertEqual(["NewGroup", "ExitGroup", "additions"], ids) # Make sure the 'New' sub-menu got added to the 'NewGroup' group # of the 'File' menu. menu = menu_manager.find_item("File") group = menu.find_group("NewGroup") self.assertEqual("New", group.items[0].id) # Make sure the 'Exit' action got added to the 'ExitGroup' group # of the 'File' menu. menu = menu_manager.find_item("File") group = menu.find_group("ExitGroup") self.assertEqual("Exit", group.items[0].id) def test_duplicate_menu(self): """ duplicate menu """ action_sets = [ ActionSet( menus=[ Menu( name="&File", path="MenuBar", groups=["NewGroup", "ExitGroup"], ), ], ), ActionSet( menus=[ Menu(name="&File", path="MenuBar", groups=["ExtraGroup"]), ], ), ] # Create a builder containing the action set. builder = DummyActionManagerBuilder(action_sets=action_sets) # Create a menu bar manager for the 'MenuBar'. menu_manager = builder.create_menu_bar_manager("MenuBar") # Make sure that all of the menus were added the the 'additions' group # of the menubar. self.assertEqual(1, len(menu_manager.groups)) # Make sure we only get *one* 'File' menu. additions = menu_manager.find_group("additions") ids = [item.id for item in additions.items] self.assertEqual(["File"], ids) # Make sure the 'File' menu has got 4 groups, 'NewGroup', 'ExitGroup', # 'ExtraGroup' and 'additions' (and in that order!). menu = menu_manager.find_item("File") self.assertEqual(4, len(menu.groups)) ids = [group.id for group in menu.groups] self.assertEqual( ["NewGroup", "ExitGroup", "ExtraGroup", "additions"], ids ) def test_duplicate_group(self): """ duplicate group """ action_sets = [ ActionSet( menus=[ Menu( name="&File", path="MenuBar", groups=["NewGroup", "ExitGroup"], ), ], ), ActionSet( menus=[ Menu(name="&File", path="MenuBar", groups=["NewGroup"]), ], ), ] # Create a builder containing the action set. builder = DummyActionManagerBuilder(action_sets=action_sets) # Create a menu bar manager for the 'MenuBar'. menu_manager = builder.create_menu_bar_manager("MenuBar") # Make sure that all of the menus were added the the 'additions' group # of the menubar. self.assertEqual(1, len(menu_manager.groups)) # Make sure we only get *one* 'File' menu. additions = menu_manager.find_group("additions") ids = [item.id for item in additions.items] self.assertEqual(["File"], ids) # Make sure the 'File' menu has got 3 groups, 'NewGroup', 'ExitGroup' # and 'additions' (and in that order!). menu = menu_manager.find_item("File") self.assertEqual(3, len(menu.groups)) ids = [group.id for group in menu.groups] self.assertEqual(["NewGroup", "ExitGroup", "additions"], ids) envisage-6.0.1/envisage/ui/action/tool_bar.py000066400000000000000000000056401406265421000211720ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ 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().__init__(klass=Group, **kw) ########################################################################### # 'TraitType' interface. ########################################################################### def validate(self, object, name, value): """ Validate a value. """ if isinstance(value, str): value = Group(id=value) return super().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 envisage-6.0.1/envisage/ui/gui_application.py000066400000000000000000000054151406265421000212630ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ Envisage GUI Application ------------------------ This class handles the life-cycle of a Pyface GUI. Plugins can display windows via mechinisms such as edit_traits(). This is intended to be a very simple shell for lifting an existing pure TraitsUI or Pyface (or even Qt) app into an Envisage app. More sophisticated applications should use Tasks. """ from traits.api import Event, Supports from envisage.api import Application class GUIApplication(Application): """ The entry point for an Envisage GUI application. This class handles the life-cycle of a Pyface GUI. Plugins can display windows via mechinisms such as edit_traits(). This is intended to be a very simple shell for lifting an existing pure TraitsUI or Pyface (or even Qt) app into an Envisage app. More sophisticated applications should use Tasks. """ #### 'GUIApplication' interface ######################################### #: The Pyface GUI for the application. gui = Supports("pyface.i_gui.IGUI") #: The splash screen for the application. By default, there is no splash #: screen. splash_screen = Supports("pyface.i_splash_screen.ISplashScreen") #### Application lifecycle events ######################################### #: Fired after the GUI event loop has been started. application_initialized = Event ########################################################################### # 'IApplication' interface. ########################################################################### def run(self): """ Run the application. Returns ------- bool 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: gui.set_trait_later(self, "application_initialized", self) # Start the GUI event loop. The application will block here. gui.start_event_loop() # clean up plugins once event loop stops self.stop() return started #### Trait initializers ################################################### def _gui_default(self): from pyface.api import GUI return GUI(splash_screen=self.splash_screen) envisage-6.0.1/envisage/ui/tasks/000077500000000000000000000000001406265421000166625ustar00rootroot00000000000000envisage-6.0.1/envisage/ui/tasks/__init__.py000066400000000000000000000000001406265421000207610ustar00rootroot00000000000000envisage-6.0.1/envisage/ui/tasks/action/000077500000000000000000000000001406265421000201375ustar00rootroot00000000000000envisage-6.0.1/envisage/ui/tasks/action/__init__.py000066400000000000000000000000001406265421000222360ustar00rootroot00000000000000envisage-6.0.1/envisage/ui/tasks/action/api.py000066400000000000000000000010641406265421000212630ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! from .task_window_launch_group import ( TaskWindowLaunchAction, TaskWindowLaunchGroup, ) from .task_window_toggle_group import TaskWindowToggleGroup envisage-6.0.1/envisage/ui/tasks/action/exit_action.py000066400000000000000000000022251406265421000230200ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! # Enthought library imports. 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-6.0.1/envisage/ui/tasks/action/preferences_action.py000066400000000000000000000037741406265421000243620ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! # 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().__init__(PreferencesAction(), **traits) envisage-6.0.1/envisage/ui/tasks/action/task_window_launch_group.py000066400000000000000000000052561406265421000256200ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! # 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, observe, 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 ################################################ @observe("task") def _update_name(self, event): """ Name the action (unless a name has already been assigned). """ task = event.new if task and not self.name: name = "" 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-6.0.1/envisage/ui/tasks/action/task_window_toggle_group.py000066400000000000000000000100501406265421000256130ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! # Enthought library imports. from pyface.action.api import Action, ActionItem, Group from traits.api import Any, Instance, List, Property, Str, on_trait_change class TaskWindowToggleAction(Action): """ An action for activating an application window. """ #### 'Action' interface ################################################### name = Property(Str, observe="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 "" @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().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-6.0.1/envisage/ui/tasks/api.py000066400000000000000000000023171406265421000200100ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ - :class:`~.PreferencesCategory` - :class:`~.PreferencesDialog` - :class:`~.PreferencesTab` - :class:`~.PreferencesPane` - :class:`~.TaskExtension` - :class:`~.TaskFactory` - :class:`~.TaskWindow` - :class:`~.TaskWindowEvent` - :class:`~.VetoableTaskWindowEvent` - :class:`~.TasksApplication` - :class:`~.TasksApplicationState` - :class:`~.TasksPlugin` """ from .preferences_category import PreferencesCategory from .preferences_dialog import PreferencesDialog, PreferencesTab from .preferences_pane import PreferencesPane from .task_extension import TaskExtension from .task_factory import TaskFactory from .task_window import TaskWindow from .task_window_event import TaskWindowEvent, VetoableTaskWindowEvent from .tasks_application import TasksApplication, TasksApplicationState from .tasks_plugin import TasksPlugin envisage-6.0.1/envisage/ui/tasks/preferences_category.py000066400000000000000000000022221406265421000234300ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! # Enthought library imports. from traits.api import HasTraits, Str 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 = Str # 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-6.0.1/envisage/ui/tasks/preferences_dialog.py000066400000000000000000000117671406265421000230700ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! # Enthought library imports. from traits.api import Bool, HasTraits, Instance, List, Str, 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 = Str 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().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-6.0.1/envisage/ui/tasks/preferences_pane.py000066400000000000000000000063071406265421000225460ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! # Enthought library imports. from apptools.preferences.api import PreferencesHelper from traits.api import Callable, 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 = list( 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().close(info, is_ok) envisage-6.0.1/envisage/ui/tasks/task_extension.py000066400000000000000000000020101406265421000222630ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! # 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-6.0.1/envisage/ui/tasks/task_factory.py000066400000000000000000000030201406265421000217200ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! # Enthought library imports. from traits.api import Callable, HasTraits, Str 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 = Str #: 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-6.0.1/envisage/ui/tasks/task_window.py000066400000000000000000000042321406265421000215660ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! # 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), observe="_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: form = "%s - %s" title = form % (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-6.0.1/envisage/ui/tasks/task_window_event.py000066400000000000000000000015011406265421000227630ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! # 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-6.0.1/envisage/ui/tasks/tasks_application.py000066400000000000000000000476051406265421000227600ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! # Standard library imports. import logging import pickle import os.path # Enthought library imports. from envisage.api import Application, ExtensionPoint from traits.api import ( Bool, Callable, Directory, Event, HasStrictTraits, Instance, Int, List, Str, Vetoable, ) from traits.etsconfig.api import ETSConfig # Logging. logger = logging.getLogger(__name__) #: Default filename for saving layout information DEFAULT_STATE_FILENAME = "application_memento" 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 ID for task factories TASK_FACTORIES = "envisage.ui.tasks.tasks" #: Extension point ID for task extensions TASK_EXTENSIONS = "envisage.ui.tasks.task_extensions" #: Pickle protocol to use for persisting layout information. layout_save_protocol = Int(4) #### 'TasksApplication' interface ######################################### #: The active task window (the last one to get focus). active_window = Instance("envisage.ui.tasks.task_window.TaskWindow") #: The Pyface GUI for the application. gui = Instance("pyface.i_gui.IGUI") #: Icon for the whole application. Will be used to override all taskWindows #: icons to have the same. icon = Instance("pyface.image_resource.ImageResource", allow_none=True) #: The name of the application (also used on window title bars). name = Str #: The splash screen for the application. By default, there is no splash #: screen. splash_screen = Instance("pyface.splash_screen.SplashScreen") #: The directory on the local file system used to persist window layout #: information. state_location = Directory #: The filename that the application uses to persist window layout #: information. state_filename = Str(DEFAULT_STATE_FILENAME) #: 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(Instance("envisage.ui.tasks.task_window.TaskWindow")) #: The factory for creating task windows. window_factory = Callable #### 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( Instance("pyface.tasks.task_window_layout.TaskWindowLayout") ) #: Whether to always apply the default *application level* layout when the #: application is started. Even if this is True, 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( Instance("envisage.ui.tasks.task_window_event.TaskWindowEvent") ) #: Fired when a task window is opening. window_opening = Event( Instance("envisage.ui.tasks.task_window_event.VetoableTaskWindowEvent") ) #: Fired when a task window has been opened. window_opened = Event( Instance("envisage.ui.tasks.task_window_event.TaskWindowEvent") ) #: Fired when a task window is closing. window_closing = Event( Instance("envisage.ui.tasks.task_window_event.VetoableTaskWindowEvent") ) #: Fired when a task window has been closed. window_closed = Event( Instance("envisage.ui.tasks.task_window_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 ------- bool 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 ------- pyface.tasks.task.Task 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 ------- envisage.ui.tasks.task_window.TaskWindow The new TaskWindow. """ from .task_window_event import TaskWindowEvent from pyface.tasks.task_window_layout import TaskWindowLayout 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) else: # Create an empty layout to set default size and position only layout = TaskWindowLayout() 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 ------- bool 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: if self.always_use_default_layout: window = self.create_window(window_layout, restore=False) else: window = self.create_window(window_layout, restore=True) 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, self.state_filename) if os.path.exists(filename): # Attempt to unpickle the saved application state. logger.debug("Loading application state from %s", filename) try: with open(filename, "rb") as f: restored_state = pickle.load(f) except Exception: # If anything goes wrong, log the error and continue. logger.exception("Error while restoring application state") else: if state.version == restored_state.version: state = restored_state logger.debug("Application state successfully restored") else: logger.warning( "Discarding outdated application state: " "expected version %s, got version %s", state.version, restored_state.version, ) else: logger.debug("No saved application state found at %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, str) 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, self.state_filename) logger.debug("Saving application state to %s", filename) try: with open(filename, "wb") as f: pickle.dump(self._state, f, protocol=self.layout_save_protocol) except Exception: # If anything goes wrong, log the error and continue. logger.exception("Error while saving application state") else: logger.debug("Application state successfully saved") #### Trait initializers ################################################### def _window_factory_default(self): from envisage.ui.tasks.task_window import TaskWindow return TaskWindow def _default_layout_default(self): from pyface.tasks.task_window_layout import TaskWindowLayout window_layout = TaskWindowLayout() if self.task_factories: window_layout.items = [self.task_factories[0].id] return [window_layout] def _gui_default(self): from pyface.gui import GUI 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): from .task_window_event import VetoableTaskWindowEvent # 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): from .task_window_event import TaskWindowEvent self.windows.append(window) # Event notification. self.window_opened = TaskWindowEvent(window=window) def _on_window_closing(self, window, trait_name, event): from .task_window_event import VetoableTaskWindowEvent # 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): from .task_window_event import TaskWindowEvent 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( Instance("pyface.tasks.task_window_layout.TaskWindowLayout") ) # A list of TaskWindowLayouts accumulated throughout the application's # lifecycle. window_layouts = List( Instance("pyface.tasks.task_window_layout.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-6.0.1/envisage/ui/tasks/tasks_plugin.py000066400000000000000000000136371406265421000217510ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! # Enthought library imports. from envisage.api import ExtensionPoint, Plugin, ServiceOffer from traits.api import Callable, Instance, List # Local imports. from .preferences_category import PreferencesCategory # 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 ############################## #: Contributed preference categories. Contributions to this extension #: point must have type ``PreferencesCategory``. Preference categories #: will be created automatically if necessary; this extension point is #: useful when ensuring that a category is inserted at a specific location. preferences_categories = ExtensionPoint( List(PreferencesCategory), id=PREFERENCES_CATEGORIES, desc=""" This extension point makes preference categories available to the application. Note that preference categories will be created automatically if necessary; this extension point is useful when one wants to ensure that a category is inserted at a specific location. """, ) #: Contributed preference pane factories. Each contribution to this #: extension point must be a callable with the signature #: ``callable(**traits) -> PreferencePane``. preferences_panes = ExtensionPoint( List(Callable), id=PREFERENCES_PANES, desc=""" A preferences pane appears in the preferences dialog to allow the user manipulate certain preference values. Each contribution to this extension point must be a factory that creates a preferences pane, where 'factory' means any callable with the following signature:: callable(**traits) -> PreferencesPane The easiest way to contribute such a factory is to create a class that derives from 'envisage.ui.tasks.api.PreferencesPane'. """, ) #: Contributed task factories. Contributions to this extension point #: must have type ``TaskFactory``. tasks = ExtensionPoint( List(Instance("envisage.ui.tasks.task_factory.TaskFactory")), id=TASKS, desc=""" This extension point makes tasks avaiable to the application. Each contribution to the extension point must be an instance of 'envisage.tasks.api.TaskFactory. """, ) #: Contributed task extensions. Contributions to this extension point #: must have type ``TaskExtension``. task_extensions = ExtensionPoint( List(Instance("envisage.ui.tasks.task_extension.TaskExtension")), id=TASK_EXTENSIONS, desc=""" This extension point permits the contribution of new actions and panes to existing tasks (without creating a new task). Each contribution to the extension point must be an instance of 'envisage.tasks.api.TaskExtension'. """, ) #### Contributions to extension points made by this plugin ################ my_service_offers = List(contributes_to=SERVICE_OFFERS) def _my_service_offers_default(self): preferences_dialog_service_offer = ServiceOffer( protocol="envisage.ui.tasks.preferences_dialog.PreferencesDialog", factory=self._create_preferences_dialog_service, ) return [preferences_dialog_service_offer] my_task_extensions = List(contributes_to=TASK_EXTENSIONS) def _my_task_extensions_default(self): from .action.exit_action import ExitAction from .action.preferences_action import PreferencesGroup from .task_extension import TaskExtension from pyface.tasks.action.api import DockPaneToggleGroup, SchemaAddition actions = [ SchemaAddition(id="Exit", factory=ExitAction, path="MenuBar/File"), SchemaAddition( id="Preferences", factory=PreferencesGroup, path="MenuBar/Edit" ), SchemaAddition( id="DockPaneToggleGroup", factory=DockPaneToggleGroup, path="MenuBar/View", ), ] return [TaskExtension(actions=actions)] ########################################################################### # Private interface. ########################################################################### def _create_preferences_dialog_service(self): """ Factory method for preferences dialog service. """ from .preferences_dialog import PreferencesDialog dialog = PreferencesDialog(application=self.application) dialog.trait_set( categories=self.preferences_categories, panes=[ factory(dialog=dialog) for factory in self.preferences_panes ], ) return dialog envisage-6.0.1/envisage/ui/tasks/tests/000077500000000000000000000000001406265421000200245ustar00rootroot00000000000000envisage-6.0.1/envisage/ui/tasks/tests/__init__.py000066400000000000000000000000001406265421000221230ustar00rootroot00000000000000envisage-6.0.1/envisage/ui/tasks/tests/data/000077500000000000000000000000001406265421000207355ustar00rootroot00000000000000envisage-6.0.1/envisage/ui/tasks/tests/data/application_memento_v2.pkl000066400000000000000000000010561406265421000261050ustar00rootroot00000000000000cenvisage.ui.tasks.tasks_application TasksApplicationState q)q}q(Xprevious_window_layoutsqctraits.trait_handlers TraitListObject q)qcpyface.tasks.task_window_layout TaskWindowLayout q)q}q(X active_taskq Xq Xitemsq h)q }q (Xnameqh X name_itemsqX items_itemsqubXpositionqJJqXsizeqMMqX size_stateqXnormalqX__traits_version__qX5.1.2quba}q(hhhXprevious_window_layouts_itemsqubXwindow_layoutsqh)q}q(hhhXwindow_layouts_itemsqubXversionqKhhub.envisage-6.0.1/envisage/ui/tasks/tests/data/application_memento_v3.pkl000066400000000000000000000010561406265421000261060ustar00rootroot00000000000000cenvisage.ui.tasks.tasks_application TasksApplicationState q)q}q(Xprevious_window_layoutsqctraits.trait_handlers TraitListObject q)qcpyface.tasks.task_window_layout TaskWindowLayout q)q}q(X active_taskq Xq Xitemsq h)q }q (Xnameqh X name_itemsqX items_itemsqubXpositionqJJqXsizeqMMqX size_stateqXnormalqX__traits_version__qX5.1.2quba}q(hhhXprevious_window_layouts_itemsqubXwindow_layoutsqh)q}q(hhhXwindow_layouts_itemsqubXversionqKhhub.envisage-6.0.1/envisage/ui/tasks/tests/test_tasks_application.py000066400000000000000000000076121406265421000251530ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! import os import pickle import shutil import tempfile import unittest import pkg_resources from envisage.ui.tasks.api import TasksApplication from envisage.ui.tasks.tasks_application import DEFAULT_STATE_FILENAME from pyface.i_gui import IGUI from traits.api import HasTraits, provides requires_gui = unittest.skipIf( os.environ.get("ETS_TOOLKIT", "none") in {"null", "none"}, "Test requires a non-null GUI backend", ) @provides(IGUI) class DummyGUI(HasTraits): pass @requires_gui class TestTasksApplication(unittest.TestCase): def setUp(self): self.tmpdir = tempfile.mkdtemp() self.addCleanup(shutil.rmtree, self.tmpdir) @unittest.skipUnless( 3 <= pickle.HIGHEST_PROTOCOL, "Test uses pickle protocol 3" ) def test_layout_save_with_protocol_3(self): # Test that the protocol can be overridden on a per-application basis. state_location = self.tmpdir # Create application, and set it up to exit as soon as it's launched. app = TasksApplication( state_location=state_location, layout_save_protocol=3, ) app.on_trait_change(app.exit, "application_initialized") memento_file = os.path.join(state_location, app.state_filename) self.assertFalse(os.path.exists(memento_file)) app.run() self.assertTrue(os.path.exists(memento_file)) # Check that the generated file uses protocol 3. with open(memento_file, "rb") as f: protocol_bytes = f.read(2) self.assertEqual(protocol_bytes, b"\x80\x03") def test_layout_load(self): # Check we can load a previously-created state. That previous state # has an main window size of (492, 743) (to allow us to check that # we're actually using the file). stored_state_location = pkg_resources.resource_filename( "envisage.ui.tasks.tests", "data" ) state_location = self.tmpdir shutil.copyfile( os.path.join(stored_state_location, "application_memento_v2.pkl"), os.path.join(state_location, DEFAULT_STATE_FILENAME), ) app = TasksApplication(state_location=state_location) app.on_trait_change(app.exit, "application_initialized") app.run() state = app._state self.assertEqual(state.previous_window_layouts[0].size, (492, 743)) @unittest.skipUnless( 3 <= pickle.HIGHEST_PROTOCOL, "Test uses pickle protocol 3" ) def test_layout_load_pickle_protocol_3(self): # Same as the above test, but using a state stored with pickle # protocol 3. stored_state_location = pkg_resources.resource_filename( "envisage.ui.tasks.tests", "data" ) state_location = self.tmpdir shutil.copyfile( os.path.join(stored_state_location, "application_memento_v3.pkl"), os.path.join(state_location, "fancy_state.pkl"), ) # Use a non-standard filename, to exercise that machinery. app = TasksApplication( state_location=state_location, state_filename="fancy_state.pkl", ) app.on_trait_change(app.exit, "application_initialized") app.run() state = app._state self.assertEqual(state.previous_window_layouts[0].size, (492, 743)) def test_gui_trait_expects_IGUI_interface(self): # Trivial test where we simply set the trait # and the test passes because no errors are raised. app = TasksApplication() app.gui = DummyGUI() envisage-6.0.1/envisage/ui/workbench/000077500000000000000000000000001406265421000175175ustar00rootroot00000000000000envisage-6.0.1/envisage/ui/workbench/__init__.py000066400000000000000000000000001406265421000216160ustar00rootroot00000000000000envisage-6.0.1/envisage/ui/workbench/action/000077500000000000000000000000001406265421000207745ustar00rootroot00000000000000envisage-6.0.1/envisage/ui/workbench/action/__init__.py000066400000000000000000000000001406265421000230730ustar00rootroot00000000000000envisage-6.0.1/envisage/ui/workbench/action/about_action.py000066400000000000000000000024221406265421000240150ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ 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() envisage-6.0.1/envisage/ui/workbench/action/api.py000066400000000000000000000010341406265421000221150ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! from .about_action import AboutAction from .edit_preferences_action import EditPreferencesAction from .exit_action import ExitAction envisage-6.0.1/envisage/ui/workbench/action/edit_preferences_action.py000066400000000000000000000034751406265421000262220ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ 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() envisage-6.0.1/envisage/ui/workbench/action/exit_action.py000066400000000000000000000025371406265421000236630ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ 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() envisage-6.0.1/envisage/ui/workbench/action/images/000077500000000000000000000000001406265421000222415ustar00rootroot00000000000000envisage-6.0.1/envisage/ui/workbench/action/images/exit.png000066400000000000000000000015541406265421000237250ustar00rootroot00000000000000PNG  IHDRa3IDAT8m_L[`RBKmˈs,721LpaJF4Abq^M6$ȦƘm:-0aȟB7HKmm)9NDxJkygy!ycfw*=k@zÝO_}gv~# 'o7ffy_c?إl+c-"7f.[?]_0G=3pB{ $B<^xIRs1,'N9e,'H M À@]Ef;EOkg_=<ټ̐zSs +%Ȥ!fU Zi1b*JKzS-"  b#Ɲ T._|^~ ri6ҜrlLv %2ʛA D= CdsTC*"5ɄbE hkFx)I9Pwo誂MQ m;@^/1sф6fn.l١AW(} ܭ54Qx%:53eADx_1*eI_1r9I!Cۥ璻NU4g5 ŪcӺ 柯cuTƠtHwWF-aC YN(q51n- 8>?D&_;fn;4`55fIf˯m=|`/7(9q*{.{p4ߍf3?Z`p]IENDB`envisage-6.0.1/envisage/ui/workbench/action/images/image_LICENSE.txt000066400000000000000000000006721406265421000252330ustar00rootroot00000000000000The 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-6.0.1/envisage/ui/workbench/action/images/preferences.png000066400000000000000000000015061406265421000252520ustar00rootroot00000000000000PNG  IHDRa IDAT8uSMle}M/^gel)RDOR/(M`HЖ8Pz(BJF*@8Eij5v&'GF{yf@D;FE- nb~~޿~Xt;N/!˲0=="#"@,,x_j'GF"BW^?5ð^=r/J}:595 I+vFNTUU*W*T3 2- Z^^ζ$Z:7Ǿ'ǁ (O(R4m`- VΙ3#Qw" ^N?666P7ûgϾ}sd*lmӃkT(黥%Ep xsU?P,Q:iUOԉ/\;Cö۷nc\^?hDi?ᢟ;NLطprDSFNz@T 8F ib1>Q|$<|D"qc^cM+|zʴ(_(/FJ=ޤͿOwe_6|pve0 K%(yBZSvݿgY$IAપ cvTU8>>dY,Nd>U(eIY? q!ݗ}sZ2S.{pmp'E"H$q\o;? /q IENDB`envisage-6.0.1/envisage/ui/workbench/api.py000066400000000000000000000012311406265421000206370ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ 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 envisage-6.0.1/envisage/ui/workbench/default_action_set.py000066400000000000000000000031131406265421000237230ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ 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", ), ] envisage-6.0.1/envisage/ui/workbench/images/000077500000000000000000000000001406265421000207645ustar00rootroot00000000000000envisage-6.0.1/envisage/ui/workbench/images/about.png000066400000000000000000000210661406265421000226110ustar00rootroot00000000000000PNG  IHDRP\m:sBIT|dtEXtSoftwarewww.inkscape.org< IDATxyxU'OIMtoi)dw6·~:#^|μ.̫36ਠ((PZR( M,mf_n.K4<虿f*++vܙUSS#p\dg2rYa^je'.coW c?򫟗: ΄ HUs'5AwrXR l:Z ǒqyvM[SǸaP1XԜ@HP?.ϯifˍcܰLu@}r:$VKtӉfffb;r9C&9d1_8A (M^줫jzbb355}|ZMS4CxL;ZP~3֏UrxY@5Qav<_kOZ,ɓJQOO 5srr7nh!̽{&^tI۶mx=V*,M6X>A EjkkO?l[ְ!aRVV&lllj4^1|߮l\SS*a}L&III+ ao>;~|8h4T*,RɪmٲeHNcĩT*r֮]\~}O{8N:t(`0P%$C,;v;YV3V+nᇔQ"D<[LP+mZҦ8;vH|@,;֭[*,, ^R̎oꫯ6Df GiiTs)] {ymhh544!Ν7v;zYAyy?H6lؠx?8*@ y䑶DǃpvޝfZ)Lf}:nٲe mp[|tGiiiJAǏ'I֮]a2xNj/ܩ) Q\\l۷ou:ŋ555BFH)L&ׯq8M---\EJ E,`|l޼yAyyxB5B233ͯJ@sM70RGGqسgOo&##QZZ޻wo2;hA *jjL.\\J8L %l/dNVTl6۳hѢpZdL&̋/FAf^6h#٘P.찻GWw]M8peJ%##Á(_wuu1rssݮ"ɬ׮];;;ֻ$pfj6MKKp|MѴFyVkT xsTyl5isd21B0 nO4PԐZ:g \.7p^ww7=ܹŘ\mjPs;zBכԀ}?輠؝c#l&lj v`Ͱ`&bh4NK V Hhth-&U(}|Ȏy!^; .,|nWVzh%o`Xn( #B 'u@64DHsF.|p5J!Avpmc>XMHj Ezq# IkfsdP89IoQ ȑ+ {ʼ8qc F0^ ZH"r/v/$E"шѮT˵׿ߨF(LP|<3FC V)1].״3ϧ|']]],3x^$xݒ2ތ:ۋOhtI=nɓ'^w 0d-Dp0PXXhH$v˗/s3D"q̙3bDIe_ hXܨ/ ZsD%%%ځѺ)))ȉ'M:@X r^vm 6СCVuTߊn:dSO&}k.[4BJAyy``DnGmۖe2٣tPٳ纺:]C)5557|3WRdʢVnV.))1Zt:?L8p8~iJcc# ##RRRb EĦD(S Ƴ1"I'[*u {aj>DT&z=/L߿T*;Z-].sL&۴iSH$dZj^./\ 8uT¥KV@6B2 bp8+%oߞ3gCAA:#6D^xyǎi׮]kjj36Jhnnj&/󆌀]kٜش}j p; V03GlrܲeKÇ~EBйyT:}N@.++`FlXTj+..mll455ʨU^9sL`%%F-4 ߼ys7|TQQ`0hUUU(_*]fz\$]]"d(SbpGhƌF@VBAohh`i4j%d2[AA599yX3&L&6lؠ^b8L&swyZ18<E~Xvڞ&VOOrt:= P͛77p8aC֭[׽xb=@RRRD"WmذAhnnfuwwӓ9996L7-\WRCqvM צX@)]:pע.6#:Pz]a#QÑD! =7|s6bBгhѢz>|%ԱP8SRR"|(YYYeǻ#Fr`"V4[5i[2gnxx((>]A{(pހ/g^j(-+feηS3s5S>PQ2Db_ V tq˵ r|_ `3O+Ϗ*"Q J$ $-Cs(Bz3_0%IqYib;.5:&j'_?p' ^' P,dj4_u*ϼ`K1"G0& 9 3$Y8 Ȏv+oȀ&O`Bǃ=*1Tᄏ+33sRbn"%t uzcV^0O z&HpƠ*/[ɾܘ">}:T*jvXiin |""Vtʨ?_"_7?÷.0Z(:AUC8 E4?EQB(:zd‘db߾})pbؑg^~z}6F" Βu3Qg*XS201$L&[nmiiaVVq`(rEQ"==ݖI94w@*d9[_ਟ>#)wy礇bD(Jjʽ 2i+EbL]6* v"AU".ꙗ`#T;'ⰋS/q.HxZMN@Lc2jdȷX@RE7N[&H eZGƀ=3bM3a7(:8/!*lN5\IE0n7$ ȺQm_FFgӯG?9ަ!~ O `~&3&>Ѥ̱+٧9EG>>ͷYɯ߫& 1ËU #^=+TqM;zU1b▌z5\iᨴn*sgY4J4fvVM00+67/& rZ.W) BW̌tj෷.9qk[+M]̻e1{MVZuMήoS3R)tE`^+W*j'߶а~yum]^uM`^ Cjj؎|m+`TZPnft)VzȎi5izּs|T` HKwyc8B{xM@q3&\B\5>]񽯳  ܻzE*ɲ\sp|wL$!^z5>vB=Yї?KZY[~{26o:AvmC 6ֳ۾̲]dW.3oT¦SO㲆]IvVZ-̿vUCgEۿ: p™sMyDXNyv~7s_O5"ý+;W̝a¼^:O%}ii]% ɼifBìma^Vc9WC H^4687CFkĻevx%/WJ^y劧67u™&Whz_tN}:sSeBFz0+91RzғDΗmfյVe^`X ߸y"W^ma/O5'JG(r'yKK+eaᄣMEڢ^?-(0;_' ؝/۲j`<}j%;:NQ8`F2}uCY#tTGfخ\mb\.5vз-*4VjC@B()4ү@tY/HYޝ(9_w+<噓+=RyUX9oFA0?# Х1} x 1"Qy3=0/t:sE+t9_;9 t"'L 7'bš j:#=Y II kUhE9vf&̴kr%ïKp]I%E9o.8ށH`uH5- pZGCgN\j \_hmeAW.W'AA@|G:#y_iTc3TCj(L3Tlf[+IԦF8ΐ'(Q N!<{}fߢ\#@BRi{)~iⱙ4laaz`k+| 7''lJk,.U"t8ORӷPSgwLHG@/d[E:Ne:ypgoR@c4 I[Jf?K Sp[V^M4ڿW;e霜~2.wVR#]';z 8\گv)Ll6 Eh3V("Wб O5jqh93W楙m) 6Ξ'8|Fpy=C1D|66#-D뷶[\ zAAs2!|W~qD(E:rɬf}ť7)ZxHʐc8L2|NTfG*&W+/_>> g:w.U؅AUQv|[1il& {!7,+jdD mUdm" kOBFcB=jKJsW.E\˚W.kS &=x r߻zIzd,S^%0tF ec_|l .?R+/ n.J;ntR# M]<ѭW|]yv5u06[NbhUHg=|(-%ƏH(N5 +k|~E!dHO߻J#_JlaTess5rNUm+OoRH3}m_ntă߯&W:?+]R)tF+ͼVۋ8y4ghٲc}st wCq\MŞh}[}Ǒ0R0Ѝ!qX3 GǙ3>AlugCyNs">Ԇ{V tϊDoK);/{AzKff_A GW8iw LZSR`ۮCDX\hhw,Ѳ4ʥy!+I"nx~ u#Iue3h'~/K˫{=;F!9'~{Fz pne}k ,O4榻ΎzC h/`ICWI"Y  n2;UZH{s\?[jr3xZ;*Whh^3 C@6w1Xvjaog*ǹ0_cj19t2 )/D/| ^^1H| DwL dQ erCMxˌ<07b3 q Xmmrs:?s:e6b3mE3xU5tT%cDS,NJLKAt ~Cs: o=yH"Y3nYTGv>Dz>҇;΃6~l_ Z W)j'Y"[NJ~HyDs+]9o^ٚ[֗WѓR̎BÁd W4rQRZÏdÓc`Y$Yf۠e؞bԚ[ϔUɎMÇ1rNz? OO:sr͟}̤yƟs?mNlڢk֠dљ]˓TŋS.`s:*RC{s͟ϪɤeSCyl֟fқ]̔]Ƒ0cy=uP Oy!\7l:n(Z t@y>iG'[!V }At:2 L I' F6 {Bs:(, R*N(5J&>E# s:)s:dx=w<s:_s:#^15[0 Z2d?b@ O,E#I$Vx=M;oQN8kHv<s:g6i=ChGk7[4zVDa?|\u>@v_Ә;ǀ$p&p;{^Ƒ8jw<s: n;_{LNuNo;h7b3[/rn:g6h6)]Zؘifda _ \ Y XZQs:D {B6h,d H E zA t=m:vh٠lifda _ \ Y W Ubv< HU_ ] ZR F y@IiޢKԏKҍKό:ǀ#oa \ Y WT] w? JXE|CxAu?p;k(Z y@oܥYؘWՕUҒSΐOʌEń!j Y WTbu; LLXWURO|KwHgКbڝ_ך]ԗZЕV̐QȌLć:y_]Wv< M{6p^ǒiƗh”fb^0_qfX{6^!yLi<a3e<&mI?|]wANe͗jؠ'udb` _ ]]Fr̞=ot;s:a m:rBSzKt~I q<m9h7d4_2[/f4 yAnաTԓkfdb` _ ] [ Y'kjə:kt:s: s=L pq<l9h6c4c3s:Ne۟jhfdb` _ ] [ Y W,npțIs: x@ v?*[XN F ~C y@ u>p;l9g6p9$Ywߪ&xihfdb` _ ] [ Y W VQYs:s:= {BJ yAX.e J H E }C y@ t>p;k8t;OSؕkihfdb` _ ] [ Y W V]rȝ|Gs:t }C|N^S M J G E }B x@ t=p;s:eΙ=Ӈkihfdb` _ ] [ Y W VThŖ)Zs: E*^UQ O L J G E |B x?s<u=xݪ4с"u$t&u%shb` _ ] [ Y W VTT>nt: F:oN(f)e)c)a&\Q F |Bu=IyUؖQ֒PԐOяMύK͋:%oa ] [ Y W VTE}Lzs: H9pT>w=uLlޥaڜ`ך^ՙ]ӗZѕWΑTˎQȌNƉIÅD)l Yarȝ{Cs:n LE JX]ƑdŔdÒcb`^ZWNQxުg۠fٞdםbԚ_Ҙ[ДY͑UʏRNjMćJF>z`ÑPs:s:2N L)caʔk˛qʝqǜnęmjgdQ{u LF~cʖ|Ц~Υ|̣yȠwŝsok:gv=gЛqܥl٢kנg՝dҚ`ϖ]̔XɐTƌPÈYōq̞0ct:s: MSScʖ~ѧӮϫ̧ȣ{ğvqLFZĎuۧjנk֠hӜdЙ`͖[ʒVǍi˙oɚ/as:s:LO MM 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:1V-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<']=rI}UL?s/c {Cs:s:s:s:vs:b3`2^1\0[0iB6|YDdMkRoLhC}_1nOW6G$E#D"e3s:s:FNrգwاwצvץvեuԤuӣuңsϡXPs:s:s:s:e5-c4a3_2$vMTuZzZyZxVtQoVsXsXrXqNg[:f3s:t:,ioעwۨpףN͍3z&ph!l-sC‚f̘tСrϟq<n:k8h7e5b3_2\0`1s:s:NĈx߫UՔlgfedca` _ ^ ] [ Z Y X9xs̞ax>s:s: r= p<tAT~[ U }B zA w? t=q<n:k8h6e5b3_1n8s:(eyiۢligfedca` _ ^ ] [ Z Y X WMs˞>ss:s:s: u? s> r==m]7h F D |B y@ w? s=p;m:j8g6d5g5s: {Alڢy+{jigfedca` _ ^ ] [ Z Y X W[jȘqɜPs:s: w@ u? S]RK G E D |B y@ v? s=p;m:j8g6o8s:G~yO֑kjigfedca` _ ^ ] [ Z Y X W U-msʝ]t;s:s: {B yA w@R^ [ J I G E D |B y@ v> s=p;m9j8s:u r<o;n9s:#XyUٖlkjigfedca` _ ^ ] [ Z Y X W UT3qrɝ=ms:s: ~D |C4g^-h O M L J H F E ~C {A x@ u> r<q:s:Bxy7҃lkjigfedca` _ ^ ] [ Z Y X W UT[rɝYs:s: E ~DGy^Y Q O M K J H F E ~C {A x@ u>s;s:Ny&xmp!s rnkfdca` _ ^ ] [ Z Y X W UTSkƘes:s: F EP]WWXYVSPK F D }C zA w?s;s:ZŏyMבM֐MԐMӏLҎKЌJϋF̈7~(td` _ ^ ] [ Z Y X W UTSaÑoĘs:s: G FYZ1m0l0j/i.f.e,c+a']TH }B zAt;s:\ȒzSؕRדPՒPԑPӐOюMύLΌJ̊IʉFȆ/vc ^ ] [ Z Y X W UTS_qŚt;s: H GXY:u9t9r9q7n7m5j4i3f1d/b$XGv=s:RyRؔUוT֔TԓRӒRґQЏPώN͍LˋJʉJȇDŃ%n ^ [ Z Y X W UTShŖis:s: I HP_Ðr=pl%Vs:/ey_ۜ\ٚ[ؙ[֘Z՗YԕWҔVВUϑS͏Q̍OʋMȉKƇHąD*o\ X W UT(irɝGws:s: K J2j`ȓOTTSRQPON}L{KyHvFt=ks: xAxުq`ڜ_ٛ_ך^֙]՗[ӖZѕWГVΑT͏RˍOɋNljKćH„F@}$j X UTHrɝ'Xs:s:^ L9 KT`ɔ\Ɛ]Đ]Ž[[[YXVUSR~O{My{Es:ZŎye۟cٝb؜`כ`֚^Ԙ]җ[ѕYϓW͑UˏRɍPNjNʼnKÆHƒFD:ydapɛkÖ v>s:s: M L JN`ɔ_ǓdǕdŔdÒdba`^\ZXU.]s:Pvvߩfڟfٟd؝c֛b՚aә^ї\ЕZΓX̑UʏSȍQƋMćKÅGFB~A|iȘsʝ-bs:s: M KY_˔aȔlʚnʛmȚlŘk×jifdba_W x@s:>|ynܤhڠh٠fםd֜cԛaҙ_З\ϔ[͓XˑUɎSnjPʼnMÇJGDYs˞Ut:s:s:8 N L K9v`ʕdʖv΢v͡u˟tȝsƜqĚo˜nkifd8fs:z@dԛx߫jڢkڡjؠg֞f՝dӛbљ_Ж]Δ[̒XʐTȍRƋOĈL†ILr˞nɚIs:s: M{ LMSċ`ʔl̛ѧ~Ϧ|ͥ{ˣyɠxǟvĝsšqnkfLs:Rvܩyݪmڣn٢kנi֞gԝeқbј_ϖ]͔ZʑWȏTƌQĉM‡^ǒr͟s̞3fs:s:s:N M L \^ɓ`ʔdʗҩҬЩͧ˥~ɣ|ƠxÞvroczCs:!Xlՠxݪtۦmءl֠i՞gӜdљbϗ_͕\˒YɐVǍWǍpΞuΠo˜2es:s:s:BN M LYQŠ`ʔaȔyϣ԰ҮЫͩ˧Ȥ}ơzÞviR| x@s: DONJxܩxڨj֟[ѕWΒcљdЙaΖ[˒\ʓ^ʔpϟtϡ\ÎMs:s:s:cN- M LN9v_ɔ`ȓ^ő^ÑfĔwɠΩzǡoj[][*[r:s:s:+cmҟw٧wاwצnԡkҝnӟsӢuңtѢrΟuFzK~Fx 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-6.0.1/envisage/ui/workbench/images/image_LICENSE.txt000066400000000000000000000004741406265421000237560ustar00rootroot00000000000000These 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-6.0.1/envisage/ui/workbench/preferences.ini000066400000000000000000000000701406265421000225160ustar00rootroot00000000000000[enthought.envisage.ui.workbench] prompt_on_exit = True envisage-6.0.1/envisage/ui/workbench/workbench.py000066400000000000000000000040461406265421000220570ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ 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 envisage-6.0.1/envisage/ui/workbench/workbench_action_manager_builder.py000066400000000000000000000146771406265421000266270ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ 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) envisage-6.0.1/envisage/ui/workbench/workbench_action_set.py000066400000000000000000000157441406265421000242760ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ 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) 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) ########################################################################### # '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") ########################################################################### # 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() #### 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 ) 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) # 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) 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) envisage-6.0.1/envisage/ui/workbench/workbench_application.py000066400000000000000000000137461406265421000244510ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ The entry point for an Envisage Workbench application. """ # Standard library imports. import logging # 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() ########################################################################### # '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() # 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() ########################################################################### # 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) envisage-6.0.1/envisage/ui/workbench/workbench_editor_manager.py000066400000000000000000000033651406265421000251220ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ 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 envisage-6.0.1/envisage/ui/workbench/workbench_plugin.py000066400000000000000000000202571406265421000234370ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ 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) envisage-6.0.1/envisage/ui/workbench/workbench_preferences.py000066400000000000000000000020101406265421000244250ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ 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) envisage-6.0.1/envisage/ui/workbench/workbench_preferences_page.py000066400000000000000000000030771406265421000254370ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ 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") envisage-6.0.1/envisage/ui/workbench/workbench_window.py000066400000000000000000000213161406265421000234450ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ 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, provides # Local imports. from .workbench_action_manager_builder import WorkbenchActionManagerBuilder from .workbench_editor_manager import WorkbenchEditorManager # Logging. logger = logging.getLogger(__name__) @provides(IServiceRegistry, IExtensionPointUser) class WorkbenchWindow(pyface.WorkbenchWindow): """ An extensible workbench window. """ # Extension point Ids. ACTION_SETS = "envisage.ui.workbench.action_sets" VIEWS = "envisage.ui.workbench.views" PERSPECTIVES = "envisage.ui.workbench.perspectives" SERVICE_OFFERS = "envisage.ui.workbench.service_offers" #### 'WorkbenchWindow' interface ########################################## # The application that the window is part of. # # This is equivalent to 'self.workbench.application', and is provided just # as a convenience since windows often want access to the application. application = Delegate("workbench", modify=True) # The action sets that provide the toolbars, menus groups and actions # used in the window. action_sets = List(Instance(ActionSet)) # The service registry for 'per window' services. service_registry = Instance(IServiceRegistry, factory=ServiceRegistry) #### 'IExtensionPointUser' interface ###################################### # The extension registry that the object's extension points are stored in. extension_registry = Property(Instance(IExtensionRegistry)) #### Private interface #################################################### # The workbench menu and tool bar builder. # # The builder is used to create the window's tool bar and menu bar by # combining all of the contributed action sets. _action_manager_builder = Instance(WorkbenchActionManagerBuilder) # Contributed action sets (each contribution is actually a factory). _action_sets = ExtensionPoint(id=ACTION_SETS) # Contributed views (each contribution is actually a factory). _views = ExtensionPoint(id=VIEWS) # Contributed perspectives (each contribution is actually a factory). _perspectives = ExtensionPoint(id=PERSPECTIVES) # Contributed service offers. _service_offers = ExtensionPoint(id=SERVICE_OFFERS) # The Ids of the services that were automatically registered. _service_ids = List ########################################################################### # 'IExtensionPointUser' interface. ########################################################################### def _get_extension_registry(self): """ Trait property getter. """ return self.application ########################################################################### # 'pyface.Window' interface. ########################################################################### #### Trait initializers ################################################### def _menu_bar_manager_default(self): """ Trait initializer. """ return self._action_manager_builder.create_menu_bar_manager("MenuBar") def _status_bar_manager_default(self): """ Trait initializer. """ return StatusBarManager() def _tool_bar_managers_default(self): """ Trait initializer. """ return self._action_manager_builder.create_tool_bar_managers("ToolBar") #### Trait change handlers ################################################ def _opening_changed(self): """ Static trait change handler. """ self._service_ids = self._register_service_offers(self._service_offers) def _closed_changed(self): """ Static trait change handler. """ self._unregister_service_offers(self._service_ids) ########################################################################### # '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) def unregister_service(self, service_id): """ Unregister a service. """ self.service_registry.unregister_service(service_id) ########################################################################### # Private interface. ########################################################################### def __action_manager_builder_default(self): """ Trait initializer. """ action_manager_builder = WorkbenchActionManagerBuilder( window=self, action_sets=self.action_sets ) return action_manager_builder def _register_service_offers(self, service_offers): """ Register all service offers. """ return list(map(self._register_service_offer, service_offers)) def _register_service_offer(self, service_offer): """ Register a service offer. """ # Add the window to the service offer properties (this is so that it # is available to the factory when it is called to create the actual # service). service_offer.properties["window"] = self service_id = self.register_service( protocol=service_offer.protocol, obj=service_offer.factory, properties=service_offer.properties, ) return service_id def _unregister_service_offers(self, service_ids): """ Unregister all service offers. """ # Unregister the services in the reverse order that we registered # them. service_ids_copy = service_ids[:] service_ids_copy.reverse() for service_id in service_ids_copy: self.unregister_service(service_id) envisage-6.0.1/envisage/unknown_extension.py000066400000000000000000000011141406265421000212620ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ The exception raised when an unknown extension is referenced. """ class UnknownExtension(Exception): """ The exception raised when an unknown extension is referenced. """ envisage-6.0.1/envisage/unknown_extension_point.py000066400000000000000000000011351406265421000224760ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ The exception raised when an unknown extension point is referenced. """ class UnknownExtensionPoint(Exception): """ The exception raised when an unknown extension point is referenced. """ envisage-6.0.1/etstool.py000066400000000000000000000422061406265421000153660ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ Tasks for Test Runs =================== This file is intended to be used with a python environment with the click library to automate the process of setting up test environments and running the test within them. This improves repeatability and reliability of tests be removing many of the variables around the developer's particular Python environment. Test environment setup and package management is performed using `EDM `_ To use this to run your tests, you will need to install EDM and click into your working environment. You will also need to have git installed to access required source code from github repositories. You can then do:: python etstool.py install --runtime=... --toolkit=... to create a test environment from the current codebase and:: python etstool.py test --runtime=... --toolkit=... to run tests in that environment. You can remove the environment with:: python etstool.py cleanup --runtime=... --toolkit=... If you make changes you will either need to remove and re-install the environment or manually update the environment using ``edm``, as the install performs a ``python setup.py install`` rather than a ``develop``, so changes in your code will not be automatically mirrored in the test environment. You can update with a command like:: edm run --environment ... -- python setup.py install You can run all three tasks at once with:: python etstool.py test_clean --runtime=... --toolkit=... which will create, install, run tests, and then clean-up the environment. And you can run tests in all supported runtimes and toolkits (with cleanup) using:: python etstool.py test_all The only currently supported runtime value is ``3.6``, and currently supported toolkits are ``pyside2``, ``pyqt5``, ``wx`` and ``null``. Not all combinations of toolkits and runtimes will work, but the tasks will fail with a clear error if that is the case. Tests can still be run via the usual means in other environments if that suits a developer's purpose. Changing This File ------------------ To change the packages installed during a test run, change the dependencies variable below. To install a package from github, or one which is not yet available via EDM, add it to the `ci-src-requirements.txt` file (these will be installed by `pip`). Other changes to commands should be a straightforward change to the listed commands for each task. See the EDM documentation for more information about how to run commands within an EDM enviornment. """ import glob import os import subprocess import sys from shutil import rmtree, copy as copyfile, which from tempfile import mkdtemp from contextlib import contextmanager import click # Python runtime versions supported by this tool. available_runtimes = ["3.6"] # Python runtime used by default. default_runtime = "3.6" # Toolkits supported by this tool. available_toolkits = ["pyside2", "pyqt5", "wx", "null"] # Toolkit used by default. default_toolkit = "null" supported_combinations = { "3.6": {"pyside2", "pyqt5", "wx", "null"}, } dependencies = { "apptools", "coverage", "enthought_sphinx_theme", "flake8", "flake8_ets", "ipykernel", "pyface", "sphinx", "traits", "traitsui", } # Dependencies we install from source for cron tests # Order from packages with the most dependencies to one with the least # dependencies. Packages are forced re-installed in this order. source_dependencies = [ "apptools", "traitsui", "pyface", "traits", ] toolkit_dependencies = { # XXX once pyside2 is available in EDM, we will want it here. For now # we do a pip install. "pyside2": set(), "pyqt5": {"pyqt5"}, # XXX once wxPython 4 is available in EDM, we will want it here "wx": set(), "null": set(), } runtime_dependencies = {} environment_vars = { "pyside2": {"ETS_TOOLKIT": "qt4", "QT_API": "pyside2"}, "pyqt5": {"ETS_TOOLKIT": "qt4", "QT_API": "pyqt5"}, "wx": {"ETS_TOOLKIT": "wx"}, "null": {"ETS_TOOLKIT": "null"}, } github_url_fmt = "git+http://github.com/enthought/{0}.git#egg={0}" # Options shared between different click commands. edm_option = click.option( "--edm", help=( "Path to the EDM executable to use. The default is to use the first " "EDM found in the path. The EDM executable can also be specified " "by setting the ETSTOOL_EDM environment variable." ), envvar="ETSTOOL_EDM", ) runtime_option = click.option( "--runtime", default=default_runtime, type=click.Choice(available_runtimes), show_default=True, help="Python runtime version", ) toolkit_option = click.option( "--toolkit", default=default_toolkit, type=click.Choice(available_toolkits), show_default=True, help="GUI toolkit", ) environment_option = click.option( "--environment", default=None, help=( "EDM environment name to use in place of the " "automatically constructed name" ), ) source_option = click.option( "--source/--no-source", default=False, help="Install ETS packages from source", ) editable_option = click.option( "--editable/--not-editable", default=False, help="Install main package in 'editable' mode? [default: --not-editable]", ) @click.group() def cli(): pass @cli.command() @edm_option @runtime_option @toolkit_option @environment_option @editable_option @source_option def install(edm, runtime, toolkit, environment, editable, source): """ Install project and dependencies into a clean EDM environment. """ parameters = get_parameters(edm, runtime, toolkit, environment) packages = " ".join( dependencies | toolkit_dependencies.get(toolkit, set()) | runtime_dependencies.get(runtime, set()) ) # edm commands to setup the development environment commands = [ "{edm} environments create {environment} --force --version={runtime}", "{edm} install -y -e {environment} " + packages, ( "{edm} run -e {environment} -- " "pip install -r ci-src-requirements.txt --no-dependencies" ), ] # pip install pyside2, because we don't have it in EDM yet if toolkit == "pyside2": commands.append( "{edm} run -e {environment} -- pip install pyside2" ) # install wxPython with pip, because we don't have it in EDM yet elif toolkit == "wx": if sys.platform == "darwin": commands.append( "{edm} run -e {environment} -- python -m pip install wxPython<4.1" # noqa: E501 ) elif sys.platform == "linux": # XXX this is mainly for TravisCI workers; need a generic solution commands.append( "{edm} run -e {environment} -- pip install -f https://extras.wxpython.org/wxPython4/extras/linux/gtk3/ubuntu-16.04/ wxPython<4.1" # noqa: E501 ) else: commands.append( "{edm} run -e {environment} -- python -m pip install wxPython" ) click.echo("Creating environment '{environment}'".format(**parameters)) execute(commands, parameters) if source: # Remove EDM ETS packages and install them from source cmd_fmt = ( "{edm} plumbing remove-package " "--environment {environment} --force " ) commands = [cmd_fmt + source_pkg for source_pkg in source_dependencies] execute(commands, parameters) source_pkgs = [ github_url_fmt.format(pkg) for pkg in source_dependencies ] # Without the --no-dependencies flag such that new dependencies on # master are brought in. commands = [ "python -m pip install --force-reinstall {pkg}".format(pkg=pkg) for pkg in source_pkgs ] commands = [ "{edm} run -e {environment} -- " + command for command in commands ] execute(commands, parameters) # Always install local source at the end to mitigate risk of testing # against a distributed release. if editable: install_cmd = ( "{edm} run -e {environment} -- pip " "install --editable . --no-dependencies" ) else: install_cmd = ( "{edm} run -e {environment} -- pip install . --no-dependencies" ) execute([install_cmd], parameters) click.echo("Done install") @cli.command() @edm_option @runtime_option @toolkit_option @environment_option def shell(edm, runtime, toolkit, environment): """ Create a shell into the EDM development environment (aka 'activate' it). """ parameters = get_parameters(edm, runtime, toolkit, environment) commands = [ "{edm} shell -e {environment}", ] execute(commands, parameters) @cli.command() @edm_option @runtime_option @toolkit_option @environment_option def flake8(edm, runtime, toolkit, environment): """ Run a flake8 check in a given environment. """ parameters = get_parameters(edm, runtime, toolkit, environment) commands = ["{edm} run -e {environment} -- python -m flake8"] execute(commands, parameters) @cli.command() @edm_option @runtime_option @toolkit_option @environment_option def test(edm, runtime, toolkit, environment): """ Run the test suite in a given environment with the specified toolkit. """ parameters = get_parameters(edm, runtime, toolkit, environment) environ = environment_vars.get(toolkit, {}).copy() environ["PYTHONUNBUFFERED"] = "1" commands = [ ( "{edm} run -e {environment} -- python -W default -m " "coverage run -p -m unittest discover -v envisage" ), ] # We run in a tempdir to avoid accidentally picking up wrong envisage # code from a local dir. We need to ensure a good .coveragerc is in # that directory, plus coverage has a bug that means a non-local coverage # file doesn't get populated correctly. click.echo("Running tests in '{environment}'".format(**parameters)) with do_in_tempdir(files=[".coveragerc"], capture_files=["./.coverage*"]): os.environ.update(environ) execute(commands, parameters) click.echo("Done test") @cli.command() @edm_option @runtime_option @toolkit_option @environment_option def cleanup(edm, runtime, toolkit, environment): """ Remove a development environment. """ parameters = get_parameters(edm, runtime, toolkit, environment) commands = [ "{edm} run -e {environment} -- python setup.py clean", "{edm} environments remove {environment} --purge -y", ] click.echo("Cleaning up environment '{environment}'".format(**parameters)) execute(commands, parameters) click.echo("Done cleanup") @cli.command() @edm_option @runtime_option @toolkit_option def test_clean(edm, runtime, toolkit): """ Run tests in a clean environment, cleaning up afterwards """ args = ["--toolkit={}".format(toolkit), "--runtime={}".format(runtime)] if edm is not None: args.append("--edm={}".format(edm)) try: install(args=args, standalone_mode=False) test(args=args, standalone_mode=False) finally: cleanup(args=args, standalone_mode=False) @cli.command() @edm_option @runtime_option @toolkit_option @environment_option @editable_option def update(edm, runtime, toolkit, environment, editable): """ Update/Reinstall package into environment. """ parameters = get_parameters(edm, runtime, toolkit, environment) if editable: install_cmd = ( "{edm} run -e {environment} -- " "pip install --editable . --no-dependencies" ) else: install_cmd = ( "{edm} run -e {environment} -- pip install . --no-dependencies" ) commands = [install_cmd] click.echo("Re-installing in '{environment}'".format(**parameters)) execute(commands, parameters) click.echo("Done update") @cli.command() @edm_option def test_all(edm): """ Run test_clean across all supported environment combinations. """ failed_command = False for runtime, toolkits in supported_combinations.items(): for toolkit in toolkits: args = [ "--toolkit={}".format(toolkit), "--runtime={}".format(runtime), ] if edm is not None: args.append("--edm={}".format(edm)) try: test_clean(args, standalone_mode=True) except SystemExit: failed_command = True if failed_command: sys.exit(1) @cli.command() @edm_option @runtime_option @toolkit_option @environment_option def docs(edm, runtime, toolkit, environment): """ Build HTML documentation. """ parameters = get_parameters(edm, runtime, toolkit, environment) parameters["docs_source"] = "docs/source" parameters["docs_build"] = "docs/build" parameters["docs_source_api"] = docs_source_api = "docs/source/api" parameters["templates_dir"] = "docs/source/api/templates/" # Remove any previously autogenerated API documentation. doc_api_files = os.listdir(docs_source_api) permanent = ['envisage.api.rst', 'templates'] previously_autogenerated = \ [file for file in doc_api_files if file not in permanent] for file in previously_autogenerated: os.remove(os.path.join(docs_source_api, file)) apidoc_command = ( "{edm} run -e {environment} -- python -m sphinx.ext.apidoc --separate " "--no-toc -o {docs_source_api} -t {templates_dir} envisage */tests" ) html_build_command = ( "{edm} run -e {environment} -- python -m sphinx -b html " "{docs_source} {docs_build}" ) commands = [apidoc_command, html_build_command] execute(commands, parameters) # Utility routines def get_parameters(edm, runtime, toolkit, environment): """ Set up parameters dictionary for format() substitution """ if edm is None: edm = locate_edm() if environment is None: environment = "envisage-test-{runtime}-{toolkit}".format( runtime=runtime, toolkit=toolkit ) parameters = { "edm": edm, "runtime": runtime, "toolkit": toolkit, "environment": environment, } if toolkit not in supported_combinations[runtime]: msg = ( "Python {runtime} and toolkit {toolkit} not supported by " + "test environments" ) raise RuntimeError(msg.format(**parameters)) return parameters @contextmanager def do_in_tempdir(files=(), capture_files=()): """ Create a temporary directory, cleaning up after done. Creates the temporary directory, and changes into it. On exit returns to original directory and removes temporary dir. Parameters ---------- files : sequence of filenames Files to be copied across to temporary directory. capture_files : sequence of filenames Files to be copied back from temporary directory. """ path = mkdtemp() old_path = os.getcwd() # send across any files we need for filepath in files: click.echo("copying file to tempdir: {}".format(filepath)) copyfile(filepath, path) os.chdir(path) try: yield path # retrieve any result files we want for pattern in capture_files: for filepath in glob.iglob(pattern): click.echo("copying file back: {}".format(filepath)) copyfile(filepath, old_path) finally: os.chdir(old_path) rmtree(path) def execute(commands, parameters): for command in commands: click.echo("[EXECUTING] {}".format(command.format(**parameters))) try: subprocess.check_call( [arg.format(**parameters) for arg in command.split()] ) except subprocess.CalledProcessError as exc: click.echo(str(exc)) sys.exit(1) def locate_edm(): """ Locate an EDM executable if it exists, else raise an exception. Returns the first EDM executable found on the path. On Windows, if that executable turns out to be the "edm.bat" batch file, replaces it with the executable that it wraps: the batch file adds another level of command-line mangling that interferes with things like specifying version restrictions. Returns ------- edm : str Path to the EDM executable to use. Raises ------ click.ClickException If no EDM executable is found in the path. """ edm = which("edm") if edm is None: raise click.ClickException( "This script requires EDM, but no EDM executable " "was found on the path." ) # Resolve edm.bat on Windows. if sys.platform == "win32" and os.path.basename(edm) == "edm.bat": edm = os.path.join(os.path.dirname(edm), "embedded", "edm.exe") return edm if __name__ == "__main__": cli() envisage-6.0.1/examples/000077500000000000000000000000001406265421000151355ustar00rootroot00000000000000envisage-6.0.1/examples/README.rst000066400000000000000000000003171406265421000166250ustar00rootroot00000000000000Examples have been moved to sit in envisage/examples/demo. The examples remaining here in the legacy directory have known problems. See enthought/envisage#329, enthought/envisage#330, enthought/envisage#381envisage-6.0.1/examples/legacy/000077500000000000000000000000001406265421000164015ustar00rootroot00000000000000envisage-6.0.1/examples/legacy/MOTD_Using_Eggs/000077500000000000000000000000001406265421000212565ustar00rootroot00000000000000envisage-6.0.1/examples/legacy/MOTD_Using_Eggs/README.txt000066400000000000000000000014571406265421000227630ustar00rootroot00000000000000Welcome to the "Message of the Day" (MOTD) example. This directory contains two subdirectories:: dist Which contains the actual runnable application as it might actually be distributed/deployed. src Which contains the source code for the eggs that make up the application. This directory is there to allow easy access to the example code, but would obviously not normally be deployed. To build the eggs required by the application:: (where, '...' refers to the directory that *this* file is in):: ``cd .../src/acme.motd`` ``python setup.py bdist_egg -d ../../dist/eggs`` ``cd .../src/acme.motd.software_quotes`` ``python setup.py bdist_egg -d ../../dist/eggs`` To run the application:: ``cd .../dist`` ``python run.py`` or equivalent, depending on your operating system and shell. envisage-6.0.1/examples/legacy/MOTD_Using_Eggs/dist/000077500000000000000000000000001406265421000222215ustar00rootroot00000000000000envisage-6.0.1/examples/legacy/MOTD_Using_Eggs/dist/run.py000066400000000000000000000045241406265421000234040ustar00rootroot00000000000000""" Run the application. Usually, all you have to do in here is:- 1) Initialise the logging package as you see fit (or not at all ;^)! 2) Set the 'EGG_PATH' variable to be a list of the directories that contain your application's eggs. 3) Edit the 'run' function to do whatever you need to do to start your application. """ # Standard library imports. import logging # Create a log file. logger = logging.getLogger() logger.addHandler(logging.StreamHandler(file('acme_motd.log', 'w'))) logger.setLevel(logging.DEBUG) # A list of the directories that contain the application's eggs (any directory # not specified as an absolute path is treated as being relative to the current # working directory). EGG_PATH = ['eggs'] def run(): """ The function that starts your application. """ # Enthought library imports. # # We do the imports here in case the Enthought eggs are loaded dyanmically # via the 'EGG_PATH'. from envisage.api import Application, EggPluginManager # Create a plugin manager that ignores all eggs except the ones that we # need for this example. plugin_manager = EggPluginManager( include = [ 'envisage.core', 'acme.motd', 'acme.motd.software_quotes' ] ) # Create an application that uses the egg plugin manager to find its # plugins. application = Application(id='acme.motd', plugin_manager=plugin_manager) # Run it! return application.run() ############################################################################### # Usually, there is no need to edit anything below here! ############################################################################### # Standard library imports. from pkg_resources import Environment, working_set # Logging. logger = logging.getLogger(__name__) def main(): """ Run the application. """ # Find all additional eggs. environment = Environment(EGG_PATH) distributions, errors = working_set.find_plugins(environment) if len(errors) > 0: raise SystemError('cannot add eggs %s' % errors) logger.debug('added eggs %s' % distributions) # Add them to the working set. map(working_set.add, distributions) # Create and run the application. return run() if __name__ == '__main__': main() #### EOF ###################################################################### envisage-6.0.1/examples/legacy/MOTD_Using_Eggs/src/000077500000000000000000000000001406265421000220455ustar00rootroot00000000000000envisage-6.0.1/examples/legacy/MOTD_Using_Eggs/src/acme.motd.software_quotes/000077500000000000000000000000001406265421000271455ustar00rootroot00000000000000envisage-6.0.1/examples/legacy/MOTD_Using_Eggs/src/acme.motd.software_quotes/acme/000077500000000000000000000000001406265421000300525ustar00rootroot00000000000000envisage-6.0.1/examples/legacy/MOTD_Using_Eggs/src/acme.motd.software_quotes/acme/__init__.py000066400000000000000000000000771406265421000321670ustar00rootroot00000000000000# Copyright (c) 2007 by Enthought, Inc. # All rights reserved. envisage-6.0.1/examples/legacy/MOTD_Using_Eggs/src/acme.motd.software_quotes/acme/motd/000077500000000000000000000000001406265421000310155ustar00rootroot00000000000000envisage-6.0.1/examples/legacy/MOTD_Using_Eggs/src/acme.motd.software_quotes/acme/motd/__init__.py000066400000000000000000000000771406265421000331320ustar00rootroot00000000000000# Copyright (c) 2007 by Enthought, Inc. # All rights reserved. software_quotes/000077500000000000000000000000001406265421000341705ustar00rootroot00000000000000envisage-6.0.1/examples/legacy/MOTD_Using_Eggs/src/acme.motd.software_quotes/acme/motd__init__.py000066400000000000000000000000771406265421000363050ustar00rootroot00000000000000envisage-6.0.1/examples/legacy/MOTD_Using_Eggs/src/acme.motd.software_quotes/acme/motd/software_quotes# Copyright (c) 2007 by Enthought, Inc. # All rights reserved. messages.py000066400000000000000000000036711406265421000363600ustar00rootroot00000000000000envisage-6.0.1/examples/legacy/MOTD_Using_Eggs/src/acme.motd.software_quotes/acme/motd/software_quotes""" Some messages! """ from acme.motd.api import Message messages = [ Message( author="Bertrand Meyer", text="You can have quality software, or you can have pointer" " arithmetic; but you cannot have both at the same time.", ), Message( author="Edsger W. Dijkstra", text="The effective programmer is keenly aware of the limited size" " of his own head.", ), Message( author="Richard Buckminster-Fuller", text="When I'm working on a problem, I never think about beauty. I" " think only how to solve the problem. But when I have finished, if" " the solution is not beautiful, I know it is wrong.", ), Message( author="Tom Gilb", text="If you don't know what you're doing, don't do it on a large" " scale.", ), Message( author="Arthur Norman", text="The best way to implement hard code is never to know you're" " implementing it.", ), Message( author="Albert Einstein", text="Any intelligent fool can make things bigger, more complex," " and more violent. It takes a touch of genius - and a lot of courage" " - to move in the opposite direction.", ), Message( author="Martin Fowler", text="Any fool can write code that a computer can understand. Good" " programmers write code that humans can understand.", ), Message( author="Chet Hendrickson", text="The rule is, 'Do the simplest thing that could possibly" " work', not the most stupid.", ), Message( author="Ron Jeffries", text="If you're working sixty hour weeks, you're too tired to be" " doing good software.", ), Message( author="Edward Tufte", text="Clutter and confusion are failures of design, not attributes" " of information. There's no such thing as information overload.", ), ] software_quotes_plugin.py000066400000000000000000000016201406265421000413510ustar00rootroot00000000000000envisage-6.0.1/examples/legacy/MOTD_Using_Eggs/src/acme.motd.software_quotes/acme/motd/software_quotes""" The 'Software Quotes' plugin """ # Enthought library imports. from envisage.api import Plugin from traits.api import List class SoftwareQuotesPlugin(Plugin): """ The 'Software Quotes' plugin. """ #### 'IPlugin' interface ################################################## # The plugin's unique identifier. id = "acme.motd.software_quotes" # The plugin's name (suitable for displaying to the user). name = "Software Quotes" #### Extension points offered by this plugin ############################## # None #### Contributions to extension points made by this plugin ################ # Messages for the 'Message Of The Day'. messages = List(contributes_to="acme.motd.messages") def _messages_default(self): """ Trait initializer. """ # Only do imports when you need to! from messages import messages return messages envisage-6.0.1/examples/legacy/MOTD_Using_Eggs/src/acme.motd.software_quotes/setup.py000066400000000000000000000010751406265421000306620ustar00rootroot00000000000000# Major package imports. from setuptools import setup, find_packages setup( name="acme.motd.software_quotes", version="0.1a1", author="Enthought, Inc", author_email="info@enthought.com", license="BSD", zip_safe=True, packages=find_packages(), include_package_data=True, namespace_packages=["acme", "acme.motd", "acme.motd.software_quotes"], install_requires=["acme.motd"], entry_points=""" [envisage.plugins] acme.motd.software_quotes = acme.motd.software_quotes.software_quotes_plugin:SoftwareQuotesPlugin """, ) envisage-6.0.1/examples/legacy/MOTD_Using_Eggs/src/acme.motd/000077500000000000000000000000001406265421000237145ustar00rootroot00000000000000envisage-6.0.1/examples/legacy/MOTD_Using_Eggs/src/acme.motd/acme/000077500000000000000000000000001406265421000246215ustar00rootroot00000000000000envisage-6.0.1/examples/legacy/MOTD_Using_Eggs/src/acme.motd/acme/__init__.py000066400000000000000000000000771406265421000267360ustar00rootroot00000000000000# Copyright (c) 2007 by Enthought, Inc. # All rights reserved. envisage-6.0.1/examples/legacy/MOTD_Using_Eggs/src/acme.motd/acme/motd/000077500000000000000000000000001406265421000255645ustar00rootroot00000000000000envisage-6.0.1/examples/legacy/MOTD_Using_Eggs/src/acme.motd/acme/motd/__init__.py000066400000000000000000000000771406265421000277010ustar00rootroot00000000000000# Copyright (c) 2007 by Enthought, Inc. # All rights reserved. envisage-6.0.1/examples/legacy/MOTD_Using_Eggs/src/acme.motd/acme/motd/api.py000066400000000000000000000001531406265421000267060ustar00rootroot00000000000000from i_message import IMessage from i_motd import IMOTD from message import Message from motd import MOTD envisage-6.0.1/examples/legacy/MOTD_Using_Eggs/src/acme.motd/acme/motd/i_message.py000066400000000000000000000004721406265421000300750ustar00rootroot00000000000000""" The interface for 'Message of the Day' messages. """ # Enthought library imports. from traits.api import Interface, Str class IMessage(Interface): """ The interface for 'Message of the Day' messages. """ # The author of the message. author = Str # The text of the message. text = Str envisage-6.0.1/examples/legacy/MOTD_Using_Eggs/src/acme.motd/acme/motd/i_motd.py000066400000000000000000000005101406265421000274050ustar00rootroot00000000000000""" The 'Message of the Day' interface. """ # Enthought library imports. from traits.api import Interface class IMOTD(Interface): """ The 'Message of the Day' interface. """ def motd(self): """ Return the message of the day. Returns an object that implements the 'IMessage' interface. """ envisage-6.0.1/examples/legacy/MOTD_Using_Eggs/src/acme.motd/acme/motd/message.py000066400000000000000000000006261406265421000275660ustar00rootroot00000000000000""" The default implementation of the 'IMessage' interface. """ # Enthought library imports. from traits.api import HasTraits, Str, provides # Local imports. from i_message import IMessage @provides(IMessage) class Message(HasTraits): """ The default implementation of the 'IMessage' interface. """ # The author of the message. author = Str # The text of the message. text = Str envisage-6.0.1/examples/legacy/MOTD_Using_Eggs/src/acme.motd/acme/motd/motd.py000066400000000000000000000020101406265421000270720ustar00rootroot00000000000000""" The 'Message of the Day' implementation! """ # Standard library imports. from random import choice # Enthought library imports. from traits.api import HasTraits, List, provides # Local imports. from i_message import IMessage from i_motd import IMOTD from message import Message @provides(IMOTD) class MOTD(HasTraits): """ The 'Message of the Day' implementation! """ # The default message is used when there are no other messages! DEFAULT_MESSAGE = Message( author="Anon", text="Work hard and be good to your Mother" ) # The list of possible messages. messages = List(IMessage) ########################################################################### # 'IMOTD' interface. ########################################################################### def motd(self): """ Prints a random message. """ if len(self.messages) > 0: message = choice(self.messages) else: message = self.DEFAULT_MESSAGE return message envisage-6.0.1/examples/legacy/MOTD_Using_Eggs/src/acme.motd/acme/motd/motd_plugin.py000066400000000000000000000066601406265421000304670ustar00rootroot00000000000000""" The 'Message of the Day' plugin """ # In the interest of lazy loading you should only import from the following # packages at the module level of a plugin:: # # - envisage # - traits # # Eveything else should be imported when it is actually required. # Enthought library imports. from envisage.api import ExtensionPoint, Plugin, ServiceOffer from traits.api import Instance, List, on_trait_change class MOTDPlugin(Plugin): """ The 'Message of the Day' plugin. When this plugin is started it prints the 'Message of the Day' to stdout. """ # The Ids of the extension points that this plugin offers. MESSAGES = "acme.motd.messages" # The Ids of the extension points that this plugin contributes to. SERVICE_OFFERS = "envisage.service_offers" #### 'IPlugin' interface ################################################## # The plugin's unique identifier. id = "acme.motd" # The plugin's name (suitable for displaying to the user). name = "MOTD" #### Extension points offered by this plugin ############################## # The messages extension point. # # Notice that we use the string name of the 'IMessage' interface rather # than actually importing it. This makes sure that the import only happens # when somebody actually gets the contributions to the extension point. messages = ExtensionPoint( List(Instance("acme.motd.api.IMessage")), id=MESSAGES, desc=""" This extension point allows you to contribute messages to the 'Message Of The Day'. """, ) #### Contributions to extension points made by this plugin ################ service_offers = List(contributes_to=SERVICE_OFFERS) def _service_offers_default(self): """ Trait initializer. """ # When you register a service offer it is best to specify the protocol # a string name (rather than importing the protocol right now). This # allows the protocol to be lazily loaded. Also, don't specify the # protocol as coming from an 'api.py' file as this is not the actual # module name known to Python. motd_service_offer = ServiceOffer( protocol="acme.motd.i_motd.IMOTD", factory=self._create_motd_service, ) return [motd_service_offer] ########################################################################### # Private interface. ########################################################################### def _create_motd_service(self): """ Factory method for the 'MOTD' service. """ # Only do imports when you need to! This makes sure that the import # only happens when somebody needs an 'IMOTD' service. from motd import MOTD return MOTD(messages=self.messages) # This plugin does all of its work in this method which gets called when # the application has started all of its plugins. @on_trait_change("application:started") def _print_motd(self): """ Print the 'Message of the Day' to stdout! """ # Note that we always offer the service via its name, but look it up # via the actual protocol. from acme.motd.api import IMOTD # Lookup the MOTD service. motd = self.application.get_service(IMOTD) # Get the message of the day... message = motd.motd() # ... and print it. print('\n"%s"\n\n- %s' % (message.text, message.author)) envisage-6.0.1/examples/legacy/MOTD_Using_Eggs/src/acme.motd/setup.py000066400000000000000000000007441406265421000254330ustar00rootroot00000000000000# Major package imports. from setuptools import setup, find_packages setup( name="acme.motd", version="0.1a1", author="Enthought, Inc", author_email="info@enthought.com", license="BSD", zip_safe=True, packages=find_packages(), include_package_data=True, namespace_packages=["acme", "acme.motd"], install_requires=["envisage>=3.0.0a1",], entry_points=""" [envisage.plugins] acme.motd = acme.motd.motd_plugin:MOTDPlugin """, ) envisage-6.0.1/examples/legacy/plugins/000077500000000000000000000000001406265421000200625ustar00rootroot00000000000000envisage-6.0.1/examples/legacy/plugins/tasks/000077500000000000000000000000001406265421000212075ustar00rootroot00000000000000envisage-6.0.1/examples/legacy/plugins/tasks/ipython_kernel/000077500000000000000000000000001406265421000242415ustar00rootroot00000000000000envisage-6.0.1/examples/legacy/plugins/tasks/ipython_kernel/example.py000066400000000000000000000060451406265421000262530ustar00rootroot00000000000000# Enthought library imports. from envisage.api import CorePlugin, Plugin from envisage.plugins.ipython_kernel.api import ( IPythonKernelPlugin, IPYTHON_KERNEL_PROTOCOL, IPythonKernelUIPlugin, ) from envisage.ui.tasks.api import TasksApplication, TaskFactory, TasksPlugin from pyface.qt import QtCore from pyface.tasks.api import TaskWindowLayout from pyface.util.guisupport import get_app_qt4 from traits.api import List # Local imports from example_task import ExampleTask class ExamplePlugin(Plugin): #### 'IPlugin' interface ############################################## # The plugin's unique identifier. id = "example.plugins.ipython_kernel" # The plugin's name (suitable for displaying to the user). name = "IPython Kernel Example Plugin" #### Contributions to extension points made by this plugin ############ kernel_namespace = List(contributes_to="ipython_plugin.namespace") def _kernel_namespace_default(self): namespace = [("app", self.application)] return namespace tasks = List(contributes_to="envisage.ui.tasks.tasks") def _tasks_default(self): print("Default tasks") return [ TaskFactory( id="example_task", name="Example task", factory=ExampleTask ) ] class ExampleApplication(TasksApplication): #### 'IApplication' interface ######################################### # The application's globally unique identifier. id = "example.ipython_kernel" # The application's user-visible name. name = "Example app" always_use_default_layout = True #### 'TasksApplication' interface ##################################### # The default application-level layout for the application. default_layout = [TaskWindowLayout("example_task", size=(800, 600))] def run(self): """ Run the application. Returns ------- bool 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: app = get_app_qt4([""]) # Create windows from the default or saved application layout. self._create_windows() kernel = self.get_service(IPYTHON_KERNEL_PROTOCOL) kernel.init_ipkernel("qt4") app.connect( app, QtCore.SIGNAL("lastWindowClosed()"), app, QtCore.SLOT("quit()"), ) app.aboutToQuit.connect(kernel.cleanup_consoles) gui.set_trait_later(self, "application_initialized", self) kernel.ipkernel.start() return started if __name__ == "__main__": app = ExampleApplication( plugins=[ CorePlugin(), ExamplePlugin(), IPythonKernelPlugin(), IPythonKernelUIPlugin(), TasksPlugin(), ] ) app.run() envisage-6.0.1/examples/legacy/plugins/tasks/ipython_kernel/example_panes.py000066400000000000000000000026161406265421000274410ustar00rootroot00000000000000# Standard library imports. import os.path # Enthought library imports. from pyface.tasks.api import TraitsDockPane from traits.api import Event, File, List, Str from traitsui.api import View, Item, FileEditor class FileBrowserPane(TraitsDockPane): """ A simple file browser pane. """ #### TaskPane interface ################################################### id = "example.file_browser_pane" name = "File Browser" #### FileBrowserPane interface ############################################ # Fired when a file is double-clicked. activated = Event # The list of wildcard filters for filenames. filters = List(Str) # The currently selected file. selected_file = File(os.path.expanduser("~")) # The view used to construct the dock pane's widget. view = View( Item( "selected_file", editor=FileEditor(dclick_name="activated", filter_name="filters"), style="custom", show_label=False, ), resizable=True, ) class PythonScriptBrowserPane(FileBrowserPane): """ A file browser pane restricted to Python scripts. """ #### TaskPane interface ################################################### id = "example.python_script_browser_pane" name = "Script Browser" #### FileBrowserPane interface ############################################ filters = ["*.py"] envisage-6.0.1/examples/legacy/plugins/tasks/ipython_kernel/example_task.py000066400000000000000000000137641406265421000273030ustar00rootroot00000000000000# Enthought library imports. from pyface.tasks.api import ( Task, TaskLayout, PaneItem, IEditor, IEditorAreaPane, SplitEditorAreaPane, ) from pyface.tasks.action.api import ( DockPaneToggleGroup, SMenuBar, SMenu, SToolBar, TaskAction, ) from pyface.api import ( ConfirmationDialog, FileDialog, ImageResource, YES, OK, CANCEL, ) from traits.api import on_trait_change, Property, Instance # Local imports. from example_panes import PythonScriptBrowserPane from python_editor import PythonEditor class ExampleTask(Task): """ A simple task for editing Python code. """ #### Task interface ####################################################### id = "example.example_task" name = "Multi-Tab Editor" active_editor = Property( Instance(IEditor), observe="editor_area.active_editor" ) editor_area = Instance(IEditorAreaPane) menu_bar = SMenuBar( SMenu( TaskAction(name="New", method="new", accelerator="Ctrl+N"), TaskAction(name="Open...", method="open", accelerator="Ctrl+O"), TaskAction(name="Save", method="save", accelerator="Ctrl+S"), id="File", name="&File", ), SMenu(DockPaneToggleGroup(), id="View", name="&View"), ) tool_bars = [ SToolBar( TaskAction( method="new", tooltip="New file", image=ImageResource("document_new"), ), TaskAction( method="open", tooltip="Open a file", image=ImageResource("document_open"), ), TaskAction( method="save", tooltip="Save the current file", image=ImageResource("document_save"), ), image_size=(32, 32), ) ] ########################################################################### # 'Task' interface. ########################################################################### def _default_layout_default(self): return TaskLayout(left=PaneItem("example.python_script_browser_pane")) def activated(self): """ Overriden to set the window's title. """ return filename = self.active_editor.path if self.active_editor else "" self.window.title = filename if filename else "Untitled" def create_central_pane(self): """ Create the central pane: the script editor. """ self.editor_area = SplitEditorAreaPane() return self.editor_area def create_dock_panes(self): """ Create the file browser and connect to its double click event. """ browser = PythonScriptBrowserPane() handler = lambda: self._open_file(browser.selected_file) browser.on_trait_change(handler, "activated") return [browser] ########################################################################### # 'ExampleTask' interface. ########################################################################### def new(self): """ Opens a new empty window """ editor = PythonEditor() self.editor_area.add_editor(editor) self.editor_area.activate_editor(editor) self.activated() def open(self): """ Shows a dialog to open a file. """ dialog = FileDialog(parent=self.window.control, wildcard="*.py") if dialog.open() == OK: self._open_file(dialog.path) def save(self): """ Attempts to save the current file, prompting for a path if necessary. Returns whether the file was saved. """ editor = self.active_editor try: editor.save() except IOError: # If you are trying to save to a file that doesn't exist, open up a # FileDialog with a 'save as' action. dialog = FileDialog( parent=self.window.control, action="save as", wildcard="*.py" ) if dialog.open() == OK: editor.save(dialog.path) else: return False return True ########################################################################### # Protected interface. ########################################################################### def _open_file(self, filename): """ Opens the file at the specified path in the editor. """ editor = PythonEditor(path=filename) self.editor_area.add_editor(editor) self.editor_area.activate_editor(editor) self.activated() def _prompt_for_save(self): """ Prompts the user to save if necessary. Returns whether the dialog was cancelled. """ dirty_editors = dict( [ (editor.name, editor) for editor in self.editor_area.editors if editor.dirty ] ) if not dirty_editors.keys(): return True message = "You have unsaved files. Would you like to save them?" dialog = ConfirmationDialog( parent=self.window.control, message=message, cancel=True, default=CANCEL, title="Save Changes?", ) result = dialog.open() if result == CANCEL: return False elif result == YES: for name, editor in dirty_editors.items(): editor.save(editor.path) return True #### Trait change handlers ################################################ @on_trait_change("window:closing") def _prompt_on_close(self, event): """ Prompt the user to save when exiting. """ close = self._prompt_for_save() event.veto = not close #### Trait property getter/setters ######################################## def _get_active_editor(self): if self.editor_area is not None: return self.editor_area.active_editor return None envisage-6.0.1/examples/legacy/plugins/tasks/ipython_kernel/i_python_editor.py000066400000000000000000000022711406265421000300140ustar00rootroot00000000000000""" A widget for editing Python code. """ # Enthought library imports. from traits.api import Bool, Event, Instance, File, Str from pyface.tasks.i_editor import IEditor # Local imports. from pyface.key_pressed_event import KeyPressedEvent class IPythonEditor(IEditor): """ A widget for editing Python code. """ #### 'IPythonEditor' interface ############################################ # Object being editor is a file obj = Instance(File) # The pathname of the file being edited. path = Str # Should line numbers be shown in the margin? show_line_numbers = Bool(True) #### Events #### # The contents of the editor has changed. changed = Event # A key has been pressed. key_pressed = Event(KeyPressedEvent) ########################################################################### # 'IPythonEditor' interface. ########################################################################### def load(self, path=None): """ Loads the contents of the editor. """ def save(self, path=None): """ Saves the contents of the editor. """ def select_line(self, lineno): """ Selects the specified line. """ envisage-6.0.1/examples/legacy/plugins/tasks/ipython_kernel/images/000077500000000000000000000000001406265421000255065ustar00rootroot00000000000000envisage-6.0.1/examples/legacy/plugins/tasks/ipython_kernel/images/document_new.png000066400000000000000000000030121406265421000306770ustar00rootroot00000000000000PNG  IHDR szzIDATxڭ[lU3nݦaEDJ[ x`@. jb$VbDF. FE`b E߈:ZAHvn-{ݺ IN93;of9< b*P_W؇߳o+}>ЍW5 4Q]p8 '8 ?NAh'!v"^QtO gVʠomDzlZ3P]LNkip.u"*xn{4ŰYA+iFSSX肫z`Ui| 7Z'VB`Tq0p' ` 龢(h>cci<OWlBìO!}ҵ_`B i50d. ˆ@DP8KXub,sP]ԙj@4Zt Z+3@h94@˥ܫ+8h R ĨG9X44a1 kbp(q;EcUb\tg^L<#eASS8me!VhӌnZHJ}q@4@>e4[PtvN;`ɕ9[K[`)h&Xr&@[F;>8FhTOŅ2an^`9CGCgj-,)4Aۣ%0PxE]@L ށ^8+WtST{'>L~ j*ϔ FF㎤ˢ˰>GKNC6TY |^\>׋Q~鷡nDCC\.f ן1J sbs`)b^ɬ>HssNj_H\ N c}9dRw#J&yUZ%EK_!q&*@6r.(iypĀ,@Ӓ+0-OmӧaYBaD"ĭ6[tǶ7x!jhmݨ8xxpċdb@&< N`AScϟ1( #%rf?|W,oJw~#_whouў @#U(f<@#2+ہ]m:MN-))]z9]f_%)VCG葖C:\IENDB`envisage-6.0.1/examples/legacy/plugins/tasks/ipython_kernel/images/document_open.png000066400000000000000000000021001406265421000310440ustar00rootroot00000000000000PNG  IHDR szzsBIT|d pHYsvv}ՂtEXtSoftwarewww.inkscape.org<IDATxŖM\E9Uu?{1a•0;E'vKW#J! H .id:=龷NyN);h4ũy[g]( a L _{M0f(xC9>R}_LJdSO߯F4#& 0ns2NXK6mUsy3q!j<*H#[.N{7@[V2 c.]^3W0P 6/7>no}k `pFF{80oOn?U5X['o:pbNW=?.|FڢCĚ70>WԻ̼րjfO˩L׾{4%ʬX$} CR<\1Ɓ>j 4˚xgc<=fɬjKܰؤ}gu"2- JbW-|ŋy`]N]B"Pfd=d*3B˜i4Pd7 4EH͋,>yW.$* :ΚɌ)TYO&yq*"%$䀔[̘?OMtH@v)^Wv''E뻖3vgd]v{,*G#:јu   \d 9_ɪIENDB`envisage-6.0.1/examples/legacy/plugins/tasks/ipython_kernel/images/document_save.png000066400000000000000000000023571406265421000310570ustar00rootroot00000000000000PNG  IHDR szzsBIT|d pHYsvv}ՂtEXtSoftwarewww.inkscape.org<lIDATxVoEmvbv " 7gԗ T*oE4""Lq[fFzwBM3w775 @_D]λΛ, KxgHlQ {?iֹo~4ý:8߹ۯ:ȍYN,~/S6Hf$IVc웠yOgy~`n3@s -n,zž&D9a4Kh74 a pm V`5ɨC1S o%\(.qRCQә1<:WJP>4$Ȃ PъOtSq1u+ ^]1.Bi5' "ڎZ]Y95}Qw<2-HZMU.BpL5WDd<`u!9 hQt%a 'C׃K/0S]['=jO~z ŠV+8[I}k@X^?):50Mjқο0\`ⱊ/+0.saST, ,_ CZŗtܸyV(>2o3 ON( KS((N)x QKD-(dg0!#jj:   l@G$ +L"({4tࢢ8>|q$ +¢K $9_BdEdM FX_/z`V ǁ1Z2a]4M/I |9ߞv>46˗.}P־RU-E&ωP( Y+2Mv,^lULƣ?766~gx0 9=BjDП>(h*]2ѐC4wh4<"+츮빎(]a'/8"06}۱gxX`:Лap 壻f;4GTOD#K@4յ^xψȝL&5pgߚғCIENDB`envisage-6.0.1/examples/legacy/plugins/tasks/ipython_kernel/images/image_LICENSE.txt000066400000000000000000000013641406265421000304770ustar00rootroot00000000000000The icons are mostly derived work from other icons. As such they are licensed accordingly to the original license: Project License File ---------------------------------------------------------------------------- Enthought BSD 3-Clause LICENSE.txt Oxygen CC-SA 3.0 http://creativecommons.org/licenses/by-sa/3.0/ Unless stated in this file, icons are the work of Enthought, and are released under a 3 clause BSD license. Files and orginal authors: ---------------------------------------------------------------------------- document_new.png | Oxygen document_open.png | Oxygen document_save.png | Oxygen envisage-6.0.1/examples/legacy/plugins/tasks/ipython_kernel/python_editor.py000066400000000000000000000133501406265421000275040ustar00rootroot00000000000000# Standard library imports. from os.path import basename # Major package imports. from pyface.qt import QtCore, QtGui # Enthought library imports. from traits.api import ( Bool, Event, Instance, File, observe, Property, provides, Str, ) from pyface.tasks.api import Editor # Local imports. from i_python_editor import IPythonEditor from pyface.key_pressed_event import KeyPressedEvent @provides(IPythonEditor) class PythonEditor(Editor): """ The toolkit specific implementation of a PythonEditor. See the IPythonEditor interface for the API documentation. """ #### 'IPythonEditor' interface ############################################ obj = Instance(File) path = Str dirty = Bool(False) name = Property(Str, observe="path") tooltip = Property(Str, observe="path") show_line_numbers = Bool(True) #### Events #### changed = Event key_pressed = Event(KeyPressedEvent) def _get_tooltip(self): return self.path def _get_name(self): return basename(self.path) or "Untitled" ########################################################################### # 'PythonEditor' interface. ########################################################################### def create(self, parent): self.control = self._create_control(parent) def load(self, path=None): """ Loads the contents of the editor. """ if path is None: path = self.path # We will have no path for a new script. if len(path) > 0: with open(self.path, "r", encoding="utf-8") as f: text = f.read() else: text = "" self.control.code.setPlainText(text) self.dirty = False def save(self, path=None): """ Saves the contents of the editor. """ if path is None: path = self.path f = file(path, "w") f.write(self.control.code.toPlainText()) f.close() self.dirty = False def select_line(self, lineno): """ Selects the specified line. """ self.control.code.set_line_column(lineno, 0) self.control.code.moveCursor( QtGui.QTextCursor.EndOfLine, QtGui.QTextCursor.KeepAnchor ) ########################################################################### # Trait handlers. ########################################################################### @observe("path") def _reload_data(self, event): if self.control is not None: self.load() @observe("show_line_numbers") def _update_visible_lines(self, event=None): if self.control is not None: self.control.code.line_number_widget.setVisible( self.show_line_numbers ) self.control.code.update_line_number_width() ########################################################################### # Private interface. ########################################################################### def _create_control(self, parent): """ Creates the toolkit-specific control for the widget. """ from pyface.ui.qt4.code_editor.code_widget import AdvancedCodeWidget self.control = control = AdvancedCodeWidget(parent) self._update_visible_lines() # Install event filter to trap key presses. event_filter = PythonEditorEventFilter(self, self.control) self.control.installEventFilter(event_filter) self.control.code.installEventFilter(event_filter) # Connect signals for text changes. control.code.modificationChanged.connect(self._toggle_dirty) control.code.textChanged.connect(self._toggle_changed_trait) # Load the editor's contents. self.load() return control def _toggle_dirty(self, dirty): """ Called whenever a change is made to the dirty state of the document. """ self.dirty = dirty def _toggle_changed_trait(self): """ Called whenever a change is made to the text of the document. """ self.changed = True class PythonEditorEventFilter(QtCore.QObject): """ A thin wrapper around the advanced code widget to handle the key_pressed Event. """ def __init__(self, editor, parent): super().__init__(parent) self.__editor = editor def eventFilter(self, obj, event): """ Reimplemented to trap key presses. """ if ( self.__editor.control and obj == self.__editor.control and event.type() == QtCore.QEvent.FocusOut ): # Hack for Traits UI compatibility. self.__editor.control.emit(QtCore.SIGNAL("lostFocus")) elif ( self.__editor.control and obj == self.__editor.control.code and event.type() == QtCore.QEvent.KeyPress ): # Pyface doesn't seem to be Unicode aware. Only keep the key code # if it corresponds to a single Latin1 character. kstr = event.text() try: kcode = ord(str(kstr)) except: kcode = 0 mods = event.modifiers() self.key_pressed = KeyPressedEvent( alt_down=( (mods & QtCore.Qt.AltModifier) == QtCore.Qt.AltModifier ), control_down=( (mods & QtCore.Qt.ControlModifier) == QtCore.Qt.ControlModifier ), shift_down=( (mods & QtCore.Qt.ShiftModifier) == QtCore.Qt.ShiftModifier ), key_code=kcode, event=event, ) return super().eventFilter(obj, event) envisage-6.0.1/examples/legacy/plugins/workbench/000077500000000000000000000000001406265421000220445ustar00rootroot00000000000000envisage-6.0.1/examples/legacy/plugins/workbench/AcmeLab/000077500000000000000000000000001406265421000233305ustar00rootroot00000000000000envisage-6.0.1/examples/legacy/plugins/workbench/AcmeLab/acme/000077500000000000000000000000001406265421000242355ustar00rootroot00000000000000envisage-6.0.1/examples/legacy/plugins/workbench/AcmeLab/acme/__init__.py000066400000000000000000000000771406265421000263520ustar00rootroot00000000000000# Copyright (c) 2007 by Enthought, Inc. # All rights reserved. envisage-6.0.1/examples/legacy/plugins/workbench/AcmeLab/acme/acmelab/000077500000000000000000000000001406265421000256215ustar00rootroot00000000000000envisage-6.0.1/examples/legacy/plugins/workbench/AcmeLab/acme/acmelab/__init__.py000066400000000000000000000000771406265421000277360ustar00rootroot00000000000000# Copyright (c) 2007 by Enthought, Inc. # All rights reserved. envisage-6.0.1/examples/legacy/plugins/workbench/AcmeLab/acme/acmelab/acmelab.py000066400000000000000000000027051406265421000275630ustar00rootroot00000000000000""" The Acme Lab application. """ # Standard library imports. from logging import DEBUG # Enthought library imports. from envisage.ui.workbench.api import WorkbenchApplication from pyface.api import AboutDialog, ImageResource, SplashScreen class Acmelab(WorkbenchApplication): """ The Acme Lab application. """ #### 'IApplication' interface ############################################# # The application's globally unique Id. id = "acme.acmelab" #### 'WorkbenchApplication' interface ##################################### # Branding information. # # The icon used on window title bars etc. icon = ImageResource("acmelab.ico") # The name of the application (also used on window title bars etc). name = "Acme Lab" ########################################################################### # 'WorkbenchApplication' interface. ########################################################################### def _about_dialog_default(self): """ Trait initializer. """ about_dialog = AboutDialog( parent=self.workbench.active_window.control, image=ImageResource("about"), ) return about_dialog def _splash_screen_default(self): """ Trait initializer. """ splash_screen = SplashScreen( image=ImageResource("splash"), show_log_messages=True, log_level=DEBUG, ) return splash_screen envisage-6.0.1/examples/legacy/plugins/workbench/AcmeLab/acme/acmelab/api.py000066400000000000000000000000341406265421000267410ustar00rootroot00000000000000from acmelab import Acmelab envisage-6.0.1/examples/legacy/plugins/workbench/AcmeLab/acme/acmelab/images/000077500000000000000000000000001406265421000270665ustar00rootroot00000000000000envisage-6.0.1/examples/legacy/plugins/workbench/AcmeLab/acme/acmelab/images/about.png000066400000000000000000000271701406265421000307150ustar00rootroot00000000000000PNG  IHDR,,SF0PLTEn:}~|CCClb;._ pHYs  tIME IDATx}{׹ 2&PjiuHp{bpALC2nrwʼn:B,qQ"3CQZ+6&R&EQ@]'q+$HsμΙɕ܁ ;|Lu;zӹvvwwwv}_7ݝ7v_'F$曺_߻KZ`?N^m껻gv/]ze$X:{wQ zg]BW;XXk?]z1vdXww_xi~vi v/|^$T(!Xv]5䔲{ ATE[[|^~~=LZK'h4%n)LAd% U]6|팳 )VUG"rH D]$?]yaA2RnZBOOi 1,2g<-X^8j1UBJlɍA؀7c8kS|~a\D7|6LC˻ o:̚C97e9\pbw HNmkEWO5-Vj°ڢF1ubHs'ú3xEIC~HZi\we9 8a o-McOڠ4ahGv8P߹ذ޻jhZWȬK4,yˬ:Iڬ׏ kd ,iXV&xz^aq[Xz??f_xzde;RjPIodXn JyK ||Lm*~M0w/<d[/xӤ?0 4Xy81wG*i&  &%͚(֧6#úI.MQ!_,JBu\kWƖnӺðV\@OG9}Sm+Gr)):. !ಫ5u8N.,EM00^ܤ76p_*zh^%|V\͑?2{NaP0r鰺>>6n]B|Mɘ' da~Flq:QF}v(ikJpRZ5Hg79s*՟@3"Y"j{QhiKE6}ܰDS %U5M C/|xכzS[ BOS8?ڶP~#~D9a(|=O{eQ^g!,- L s١i}/}: #PU͹RY&ډss: iwꆔd\y KC&B8^w:ڶ|?ՈxW LVd?y`0g$HN3]}aN=0c*AƧuTs,MOVȄEǵٰ]_ p5P8u G01 b3ɾkvzkGMVĤ7N2dq=sa?7 +@Z$s>>& taE$_`e0d~Ibxy:L%!--a5I^z+qSȄPatC<^1й`r 2 K`2LgzZ\K&NyZ0=D).'Pwh%~oJyz(cHx4O yN` Q 5>c~<L7a5[cp]!P Ԑ݋xǯ'º>[_fI;ALC:O+٥426-,/D挩8`hyZ dMS85Cez]Ʒ8~B!1)AbmC VǗiOLv"bAYZK~u&p"FU@k6E8{ C=+źVխ iX'YOjx;W(qS_ABh$Ulds L@!5Gbq` rUϡ*EަHױ+O9*f+u-Jd6.5qK#?u=D~6#j)!fo nKT`䆯\gHX? #NR|u؝f04MOp_,_Iu qd#^I35R+j |jva lK $(0v/ͤa ǴVZ/U$ ߯Gt9 f.qq/e 2æQI:whRbr30BqbM^5A*Z)> sID];>,>0RX|`‡Ơ1\/U PS-b(XB0T1(\nP+gʖ3@$z`Έi.XX$!C7)>ti רDC-!2b5Bq)!}rR4Q1aNyp*m֙K qM+pNPԸ,ciCmϧr-.G{b^rl (LIyL<3]O!@XPX_i"2zF,!WH!%q,-0uj?c\0J s1s=1s}l߇4 "ԋ(z4btqVWd"!y:):X C4oSDu`=Hj8A8FYe %1lA5 X&U-I#?1-䙬/rȴp =0&#elbL!MCZ+c_MNVO0rXmZE@ҐIxbXPuGzW%\.hؔ]"tp_nNb8'.9Ά>?VBȴLªƸ3{$'8a}%M(e78 Gg 4+ 4A dbY,Ϊ:7cU̕8!w+BE4ƅt a=`{'y^`3elVLwQĢի3yɦgd٤&}:eSM)G Za1`9cW$`+eVʾ|)f1̊}-z.[3bug YtX3,Ŗ=,:LMsu3|3VX`*c 0C\.%OF7DZ ZH}Cv9Pz{+d4]`]/5ԨǛ̹ML\A(%/)|Ջ%5V׼Rt*cgyw~x4pҨ#thg.e1ASY[f:͛hvը HW Ϧe6T@`$\R ")7N)=+2͹ױ+D{M0$-(%omX?E5R?9Mk<"`E< ae 4℥& - OT-4]LHtmy>7Z%s=I)YrE5T'o1:s>1ICc%4Yeg)uGDnZŒ.,$zSPׄ Fh>MXbafw2 ~9Ebp)3<J9SB:\2E(LrH{kvKiRIjqP%`*f=G0j;=#\)fԹavN J%*<USnp|ê=@Z6oZk5 )R)[rP14:D# KEUr#O WɌPW)?%ðh+uà%q~B^&M ʩANe0;Nb5eh$U6>,>Mɔˠ'WZv<%knYk y+8GucJD4FY yt4X\J (>c@V зA,+*UkjtYr6wIø9 D[Vkh"TEvBDy31IkUUwaLUar„錯,)@ZӰHCeTo|ǯ{IvLa[3b>+O#ƜRD͖T SoU__jW[W_1RFMAZުӥ`~htVUXv/m MeK?O~GWk/9kP>,*y_amt:?T@j)qkX^SG/>y[7@piߡwVjlQҒHf*1ӫ߾ U!cdc?J2dߜldA*\yHc+rygBK2ob?%EQ@_Q\kY09?b~Wlmm+[:$Y .X}`S@ a1_Zdq[ޚ~m8a(~WN/X(@T23k)?Z5L?E}aisXJkxh 0?^t˘BsmG}=n泶{=6LUn2#탲jMkr,PdoL?ll+1̢}/MQ۶i8id~KU) ۩sL H׵7oNX 9ݚWZWL=wEo)]֠ogp: hRg^D:9h`Vm"oB2&dN4dQutBw`xs@JGOgT ~B4;F)]-Z4?<ى ʍafJ+NU},f_~Ҏo9!ayo}ǕQZ"dlϘ+"`CB›+$(Vf"R`.Wf/"oq9QϯY%n[,:%8@+͓U"OV -2gA KFeQ1ĉU\X^AWFYJ/~ɛN3Z54Vz~i" EhO$*lsB| ~rTIJ?@)5%d 5yRs %YlHҬ LMD9A`:g|X S4t !]ҬmPD Fh)EOo72%C\%poݻ촟tYMQbpa)?ZBuyI{UUH&~S~ #Rޝm=Y+u* -BZCQ,gAɳOUpTGm>pf hEr`,kX`٦:+P2׵ zڞ8ʾ@3Y+C`5eQ-M&V(Y,wX{#/ 6l_ȗ}ܕ-[֣!,Er@Q"e,I} a=Mm[i%&^G.:wˮ]jh 2VMAiJ8Ld[@wSOdzB3-~w~skv%d~ 3A1U2F檖-g y9#=k]BMQBƱ >jfeCrCKw"B%HP9;ܳ&hXnITͪ:ryAJ gpX|i: 6"2Cw k'de9Q&nK9*fqFLvB*f6*=umsr빼(h( "@C0d14MYR*eW9,wX'sB^c E/iX眶;(,~3PWhmt<3ޛC!y Tt4JHk&=Em l%C~e K,gRl UX-,!ilL|_塚b[Gmdp0چM"vj\ymYD(/⪮$yN4 dB=U5”UczE-;CZ@z2^ge'Smei6Th%,^t?855sIK5 2;ִ uIRR7} X<jF)&Vl PAÛՋynJ a--"8Ͳ&nr )Ȼ}uCD:BH+v*t/$9HyN;WMK{vr pWX9 h@NG h1a}AtNrj)vJF˂YJKzG=۸Uס_ ʶ@BK3Z8L =Ǽwxp-Zx::Kّ'>2}bjcwx0WXa΄i륭"XgSRPm-%넒+dP҈Tiai&pZđC;#4, +cL}[WO<l+ UiQ=)ڨy^5{FKN1ۇ7|8 |j Up68TȦE7\WXlqiYmT5S֣qv ˣ n懼Q(rayrpQp tVnJBdgCQ{P{ox tw-ӈaߏ*"UyOfw:OF Kj)J.@r=֫Z_ CB u8E`.JI?!-:[F DNn"C*ID74S#}B=VLӫwU['VrɣWXz*0qK I >y=١j@5 K1qͪ&~ƿ𘂏 i:#gDو8[ ?1Am3 i;u:buZu1`Orlb،V͡1}UHYQ"M:`7m / '%ĦGuʪjA3KCzkZZ pyZpI (˴$iDU"zJ5-JyQ_D S1:mk, t)"Aħ([(j ֚b'f|V!XGMmKjtTC"^L7!'k IJ}zxW *#ᜦfw 8xt[cA@E3%cN&w= nU(q Mz(i(oY+q1ǬF`ہ5AXez DH V5JhAvma&¢Dzhɛ:V ~atDP;938N8u2LOztx--bvSSNu6g&n>aa =V +DBvE(\SEGCHi{ҁMύuE_ d - jq& #>֨ڠ*#bwd=AZ\a']`="o.ĸKKsf]o]況ʴ{Q^h5IZ͋\'}uX Bu$int vo$S yeB,.} 3B_8go$tA`CG:9qZ lq5^ZAc\V&_wom# Aj&GIF?¨*oy_jcWI9`9RaBs8iyGL#šE 7jmnL|}8V'P69*GJ8%ES S1RZ޳]V.?{dX'%H _VJ_KQ"'nOkr" i)}߁AYps8&gIZWu#xjW/O֣k֫漃ʈa4!M'N?&+T&F(%K^oOփl,%-7b=_XJ$<{aM+Z Hȧ|oQiig1y|XoR -.̖1)yY'% ~X7(U$$NJ튊֩\p΄5Mf=8b9^ E 㟤mCxΆuw7TED:X %y&߈uZWSm e06c.⧯NALoß~aݽWT|HM'į o0y9WJ0?(&_fxYg۝|^$Ϸh.$/\`MA< '?)gmB8{sq4}B~~理/-n{눏9*"6kpz|DiqcA3fʨ:s4elXPH\!s?|'<>ceI (g,^S$amE_}ǽcÂKHpfǿ~Ne$!{'Bc x{?~Ia{;'O\IvIENDB`envisage-6.0.1/examples/legacy/plugins/workbench/AcmeLab/acme/acmelab/images/acmelab.ico000066400000000000000000000462461406265421000311620ustar00rootroot00000000000000hV V00%&(  U,O)1I%@C"s: t;P|?z>s:?s:e6b3mE3xU5tT%cDS,NJLKAt ~Cs: o=yH"Y3nYTGv>Dz>҇;΃6~l_ Z W)j'Y"[NJ~HyDs+]9o^ٚ[֗WѓR̎BÁd W4rQRZÏdÓc`Y$Yf۠e؞bԚ[ϔUɎMÇ1rNz? OO:sr͟}̤yƟs?mNlڢk֠dљ]˓TŋS.`s:*RC{s͟ϪɤeSCyl֟fқ]̔]Ƒ0cy=uP Oy!\7l:n(Z t@y>iG'[!V }At:2 L I' F6 {Bs:(, R*N(5J&>E# s:)s:dx=w<s:_s:#^15[0 Z2d?b@ O,E#I$Vx=M;oQN8kHv<s:g6i=ChGk7[4zVDa?|\u>@v_Ә;ǀ$p&p;{^Ƒ8jw<s: n;_{LNuNo;h7b3[/rn:g6h6)]Zؘifda _ \ Y XZQs:D {B6h,d H E zA t=m:vh٠lifda _ \ Y W Ubv< HU_ ] ZR F y@IiޢKԏKҍKό:ǀ#oa \ Y WT] w? JXE|CxAu?p;k(Z y@oܥYؘWՕUҒSΐOʌEń!j Y WTbu; LLXWURO|KwHgКbڝ_ך]ԗZЕV̐QȌLć:y_]Wv< M{6p^ǒiƗh”fb^0_qfX{6^!yLi<a3e<&mI?|]wANe͗jؠ'udb` _ ]]Fr̞=ot;s:a m:rBSzKt~I q<m9h7d4_2[/f4 yAnաTԓkfdb` _ ] [ Y'kjə:kt:s: s=L pq<l9h6c4c3s:Ne۟jhfdb` _ ] [ Y W,npțIs: x@ v?*[XN F ~C y@ u>p;l9g6p9$Ywߪ&xihfdb` _ ] [ Y W VQYs:s:= {BJ yAX.e J H E }C y@ t>p;k8t;OSؕkihfdb` _ ] [ Y W V]rȝ|Gs:t }C|N^S M J G E }B x@ t=p;s:eΙ=Ӈkihfdb` _ ] [ Y W VThŖ)Zs: E*^UQ O L J G E |B x?s<u=xݪ4с"u$t&u%shb` _ ] [ Y W VTT>nt: F:oN(f)e)c)a&\Q F |Bu=IyUؖQ֒PԐOяMύK͋:%oa ] [ Y W VTE}Lzs: H9pT>w=uLlޥaڜ`ך^ՙ]ӗZѕWΑTˎQȌNƉIÅD)l Yarȝ{Cs:n LE JX]ƑdŔdÒcb`^ZWNQxުg۠fٞdםbԚ_Ҙ[ДY͑UʏRNjMćJF>z`ÑPs:s:2N L)caʔk˛qʝqǜnęmjgdQ{u LF~cʖ|Ц~Υ|̣yȠwŝsok:gv=gЛqܥl٢kנg՝dҚ`ϖ]̔XɐTƌPÈYōq̞0ct:s: MSScʖ~ѧӮϫ̧ȣ{ğvqLFZĎuۧjנk֠hӜdЙ`͖[ʒVǍi˙oɚ/as:s:LO MM 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:1V-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<']=rI}UL?s/c {Cs:s:s:s:vs:b3`2^1\0[0iB6|YDdMkRoLhC}_1nOW6G$E#D"e3s:s:FNrգwاwצvץvեuԤuӣuңsϡXPs:s:s:s:e5-c4a3_2$vMTuZzZyZxVtQoVsXsXrXqNg[:f3s:t:,ioעwۨpףN͍3z&ph!l-sC‚f̘tСrϟq<n:k8h7e5b3_2\0`1s:s:NĈx߫UՔlgfedca` _ ^ ] [ Z Y X9xs̞ax>s:s: r= p<tAT~[ U }B zA w? t=q<n:k8h6e5b3_1n8s:(eyiۢligfedca` _ ^ ] [ Z Y X WMs˞>ss:s:s: u? s> r==m]7h F D |B y@ w? s=p;m:j8g6d5g5s: {Alڢy+{jigfedca` _ ^ ] [ Z Y X W[jȘqɜPs:s: w@ u? S]RK G E D |B y@ v? s=p;m:j8g6o8s:G~yO֑kjigfedca` _ ^ ] [ Z Y X W U-msʝ]t;s:s: {B yA w@R^ [ J I G E D |B y@ v> s=p;m9j8s:u r<o;n9s:#XyUٖlkjigfedca` _ ^ ] [ Z Y X W UT3qrɝ=ms:s: ~D |C4g^-h O M L J H F E ~C {A x@ u> r<q:s:Bxy7҃lkjigfedca` _ ^ ] [ Z Y X W UT[rɝYs:s: E ~DGy^Y Q O M K J H F E ~C {A x@ u>s;s:Ny&xmp!s rnkfdca` _ ^ ] [ Z Y X W UTSkƘes:s: F EP]WWXYVSPK F D }C zA w?s;s:ZŏyMבM֐MԐMӏLҎKЌJϋF̈7~(td` _ ^ ] [ Z Y X W UTSaÑoĘs:s: G FYZ1m0l0j/i.f.e,c+a']TH }B zAt;s:\ȒzSؕRדPՒPԑPӐOюMύLΌJ̊IʉFȆ/vc ^ ] [ Z Y X W UTS_qŚt;s: H GXY:u9t9r9q7n7m5j4i3f1d/b$XGv=s:RyRؔUוT֔TԓRӒRґQЏPώN͍LˋJʉJȇDŃ%n ^ [ Z Y X W UTShŖis:s: I HP_Ðr=pl%Vs:/ey_ۜ\ٚ[ؙ[֘Z՗YԕWҔVВUϑS͏Q̍OʋMȉKƇHąD*o\ X W UT(irɝGws:s: K J2j`ȓOTTSRQPON}L{KyHvFt=ks: xAxުq`ڜ_ٛ_ך^֙]՗[ӖZѕWГVΑT͏RˍOɋNljKćH„F@}$j X UTHrɝ'Xs:s:^ L9 KT`ɔ\Ɛ]Đ]Ž[[[YXVUSR~O{My{Es:ZŎye۟cٝb؜`כ`֚^Ԙ]җ[ѕYϓW͑UˏRɍPNjNʼnKÆHƒFD:ydapɛkÖ v>s:s: M L JN`ɔ_ǓdǕdŔdÒdba`^\ZXU.]s:Pvvߩfڟfٟd؝c֛b՚aә^ї\ЕZΓX̑UʏSȍQƋMćKÅGFB~A|iȘsʝ-bs:s: M KY_˔aȔlʚnʛmȚlŘk×jifdba_W x@s:>|ynܤhڠh٠fםd֜cԛaҙ_З\ϔ[͓XˑUɎSnjPʼnMÇJGDYs˞Ut:s:s:8 N L K9v`ʕdʖv΢v͡u˟tȝsƜqĚo˜nkifd8fs:z@dԛx߫jڢkڡjؠg֞f՝dӛbљ_Ж]Δ[̒XʐTȍRƋOĈL†ILr˞nɚIs:s: M{ LMSċ`ʔl̛ѧ~Ϧ|ͥ{ˣyɠxǟvĝsšqnkfLs:Rvܩyݪmڣn٢kנi֞gԝeқbј_ϖ]͔ZʑWȏTƌQĉM‡^ǒr͟s̞3fs:s:s:N M L \^ɓ`ʔdʗҩҬЩͧ˥~ɣ|ƠxÞvroczCs:!Xlՠxݪtۦmءl֠i՞gӜdљbϗ_͕\˒YɐVǍWǍpΞuΠo˜2es:s:s:BN M LYQŠ`ʔaȔyϣ԰ҮЫͩ˧Ȥ}ơzÞviR| x@s: DONJxܩxڨj֟[ѕWΒcљdЙaΖ[˒\ʓ^ʔpϟtϡ\ÎMs:s:s:cN- M LN9v_ɔ`ȓ^ő^ÑfĔwɠΩzǡoj[][*[r:s:s:+cmҟw٧wاwצnԡkҝnӟsӢuңtѢrΟuFzK~Fx 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-6.0.1/examples/legacy/plugins/workbench/AcmeLab/acme/acmelab/images/image_LICENSE.txt000066400000000000000000000005331406265421000320540ustar00rootroot00000000000000 filename source --------------------------------------------------- about.png Wikipedia: This image is in the public domain because it contains materials that originally came from the United States Central Intelligence Agency's World Factbook. acmelab.ico Gael Varoquaux splash.jpg Bryce Hendrix envisage-6.0.1/examples/legacy/plugins/workbench/AcmeLab/acme/acmelab/images/splash.jpg000066400000000000000000001112071406265421000310640ustar00rootroot00000000000000JFIFHH1ExifII* z(2i< CanonCanon PowerShot SD630HH2008:10:02 11:07:424<0220DXl t|  X |J 0100,    8 2006:08:31 06:38:042006:08:31 06:38:04? _ ." NZt(C   h J\E"F\@C_ Dm?;IMG:PowerShot SD630 JPEGFirmware Version 1.005)) 5^ sM|Y\]Nr;rYI[''`E uwxtwyx% @0111 '*: R980100 @  ( JFIFC    $.' ",#(7),01444'9=82<.342C  2!!22222222222222222222222222222222222222222222222222" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?XeUjp)D ?S0i*{T0RbZvړm&)C1I~LQpB*Mp"".)Qp)1RIw EJE4.+HEHE!DE4B(jZiZb*2T,qd"V sE:F*TҔ(8qڸΡW2ނ[ijK#t\QRzHLbLS31Iw &)1N#4n(M""w2T"4B),BE!))V!+QU)hU)1^hqjvs\rx0w|$t$_7t汿2;ylT y`*aTq^rĖ9>xk5X]Da)QM*Am}~d2qf!\U\V.!)1KINⰘ-!pN4MiqXB)S4p)S!)S%;yEc3@ jft$g'nTRTC~ƹѧ+5KѺ"gP>bin5IܖKΌlۈ>o8 wVq]D$TGLk+@%/B&@3Io=Veº-Oe:ari m?~t豚LA=S\??:P}꟝!z/LDf7E|_΁\ijOM2@\&j/5?:O0zb%&=EqK`XrNqVXH܌J9.A=H! $' A!?C\A,HI*ZͱH}U[2N隦]3҄KlduxJv=k7r{Տ:Gn3UK78}nVNιa BԒ] ?ZQGLXz\OB6uK2=93թ#7k?ޣ8.IO`D hTw$ |V94!ZW>>r>_žiES+zO1MX]: o!<)N|lyi^rO,?#` EO9|4I>) OEwM'9qҋ/'bqIj6'=8sSߝ\=? (6RŊc̒-':5[[ʥ?ZCm)-޸Σ8Kvyk0'*VU.<87} P!bNx7siЊ$?ZD#*,7˜zoг[_?;G?-DH: Zw`%$ǖބƅ^\–upPK9l/o!<Σ'<׆r\[r|O»a*c;z- ؾЙ*F$R@]+MXV0p5.!"P~U.(-XbpH2R6>e-y58ӄp0b3³m0"7?${w)R}:FLEo(`E۰3퉀(hkh'TpZV o3s7i\BdsjnRv.J)}h DR?M.Ĥ3ڢ%P@|  T,f B rF1I(81lP"9buY%ܵl }PP9%1cOQNC!`U:|1持L#nTzsP2̣zLFt7>EOHM1\twJB+ccwZb=Jc|2^w٭"8?ZR LTdVFkDٓ)-6V.`'=抮uiE;1]G_ `~dzƟ$RPpF_1N6JմmIHˏT>n=܀/x p=kOToc"y}+9lʨis5:orymY.z5 ;)'"6cVPˏ0qDJ>;~f_5]x H HOJ} `8Kuϝmm ~tE=(-H0) OƆJ,>@|A{p<6bm ´hG;@P.zr1P&>l( (M N@>HXnЌ40HiDtQ tʀD\g-EŘ9Rc"f;xT%͞Ā* ;]F!6;W7%Rۜ ;T@ %!ڝ|gB<*,ŃE zs:` 8Ce'>"4"6Cd!4'B<2)؁`sbP9" (lL|(B`]1zP mhMTM M1 |˜w@QsM"& "?Z8qR4w GfBysJz~5%#=ϥZNLCR:f$~83TwqIwmI_ɟJIM1+Ҹ)ix}`JISh2 zЙ0i84tB91H) jnZM3M\PJm2D0?:l(A'N Qbf5VMxqCf#淨;~ X#&91C"v\|(hT`qkSpFGʖњf. @(9])1$qbmȲ(#vE k|Sm6*ќt!8N(z> 0Iz,(C ry,[(3yɃ@u+L@g’c1υ$h%6o@P#:PJmL4yйSzc4i&w ˶(l 'ʂc4&dSgU& & אzzSiHتD.|J6kK"5tH3E[c 4kvJ"DYJ kvAuNpzI;AQ\co:,@\li2*+քS2DxzaB9s*.E]6\xQ` …݁N#4M)1ʝ zuߥ 4L %ddžM݀\l˽ !܊`J&vhLfb; &w=)ܽkʣFy ֨>H {H, l>}*hvVIa76!y+F;ؼ}bpA@ M6^]D2lg&~&#ʒGʨBzPqL@[nqbb|:Pߥ1cʞʆW"e΂A Y@?  4]-7a1PmAdP:T)  P$Ӱcv\@9s:O C D8ޙL4 l{ApZBuHyJrd Lmx+Op:Ҽkg_ŭάS"W;w Di7 <#"$ͷ\c:i6OԎvuw7enJ,P WmKG0CHmN]OfnSx[qgDg~նjɪgD_eg u!i0 ޿ Jb͟$ P$޸f1-d4sH-iHBl&;;P4 u1Azdc6vքI1yv'9phb3`L@ [2ؠB M9Z`y–zn6BlQZ^-0pOZV{!H(^U&$ʫB) )sr6…Bn/hrn h X]1p2vs}x`{y9@,06{*2qv&=ͽ!fu_l8}k5g >h\I|0iyYrX Gy\vd'#1k#'˭ReHƆ^tYV<(@mnF2v>U5ktP\qCjkJoMXݧ/KVrFEY="7HŤfC+6>Ƭ*F}3~uziM cB%HBLƄe{P ބ[ @i78@ZA#'0d\@ZO*?0`h xPo`ݥcwzj6iw H A4ɵGi⽍Zİ:ܼ`wsrjX:Oݬl |+rGٷӖp;DH|?swKiZ'D}żybxÎe?CLC{SM5hܐG_SOn3s1ʞsgl(.Ss35jYW;t,$+d`=kf٥}"]3Qԭ2c=l3*Qt':R6%`R{#5'P91 2q'4 d ˱9i#&(&\ZboM_*jbgڛ<CwM\S$&yrzMBiF4źxWz.+<YGttHi-qenÔͅukt]3fQU!x;ex4,=#zvr#ݧ(f@)˘ՄvOhK X)UM&b`E(8'$r AEX6Qu5 >b&zQN ;E}69~T&OvQz0}Zqm:ay94uz77>&$nuqhCgύ\ ` 6i6 f=Z`1 Iӟ0>L{/-_x Q)%au~U㞁qazĖdq<]mr<"#I,:o!XFflDHB_4n&Ṵay8?[yhx&Kyy4\z y;[Cj|o_,Ծp:%!WR}0o-Р=EvEi5]qo"(ӕFƧ?wxuUQ&1>^?E+ -NP`cN8X,X`:jӏnlgia2w; I>9+n폞-Oۆ@`GSֿEێ#)7źAaeTl%HF|i{[Nה+g>(T/_4ж=5"J0oK#>*zӵj;;恝 LQS8ힵ6$Pj7Q"@;n߈_YmqZx]OW(PŮT/z,Y$P+",d||<6!ٽ4 "K9DžsgWx#\hws:NQ(랣m۩۵XY݇4k'?QqYql ݤ iԂ3qM%}dPzخGH+TȦr7=w|ݰErpWB{tb2]s <7WE'6d]kx$v((`9e;i {`❀F@䑝qi1||N.R:d`q9ӹ_C#rss,.T##!؏u?|6h80f(\`L";8DWY<8;?:4k,? \йڀͪFF'3}U$8`=Geٕspf䞦y *#rvW0W%ۚng!#볫OΣP,/ɥ`sܠ푝<#@`y♼l04i0,lvd[6 X~^^_אz`]0`Rʯqܦ :N;Y$tdQiEl8 3;tyl-| & E2w/EYI y3ikPBSB_yY$>CZ%D7gޛh fÌՖ=N[=&>nRo֞Fؿ0sОAp1 m|uvW7Rj:DIunL$l!*yytQY͹4ǔTYƵi[R,2IUnt>&xwuZq_zGQ˶?>h׺ts M䄜x '\6CT*@FOGO$ 9 $N--c.ޔ#Xb 4R/Z1K#s\u>WwTu84*[YMxJ<֔3l)L(HB>8 I0 ' n> يQX/!?n!0&>t'̶)v8OҀsm*Ġ%$ٜ1G !i]Ѐf4iu63@'p'Fp¿\@cy"F"(#8iܯ|~c; Ε,U|Zyۤ [J7FyN HuDө3Wg 7#R/UA-QFeUZ@^H#/1d$O_:ĻA]3u[d~u>11b/D ^k$gn쾯>sh';-b֤ԓ=+1Y=֑dn b*җwkd$G׿˽}K-n$-Vk&# 3@⛦~X]4 j<%l'k$u^M~#jigoj0gce%-y7?J"`FcuյfJ2{8R;u8֙&]]C,0\र۩\+h?u?ߍ1k>y3%33Uh@./"+k#ܜc}eP 2E €.w;:C:CfKF\ه m64cbI#GMHAg @9AaG/IYYFܡ2)=R?UDYr PyT䪲Zܻʐoa}kթihrh7}P̬sy/y- ws[vBMU M6/ iR B9Ðxd-[4P%9$nr}0hI=Ihu۵[ݫjζ]1c;yVQ7jF>iAkdE@S>;zuD4fWQD_#y)ÅK j'Pt~蹑J0U&@`<юa-Le{yhb[R9XLw UB3?KRH߆)Ů+܎xX=3q6,?Ys"&{۩;$ԥ˛ 3`?:D.TP4Kdgt0du؆ڀ/o6ryQSk!Eܞʝ'gaqzR9ăxy՟unиNAvgmPve5^$K%7.~;*v]s<O}C ҳ#Ke_NΓwbXu0c Ki7pq~fv[hg~υMQ/3m"B>m5m$z=w ޙ#\ 13 |ݔv9cm:iB^>yt{MkwR2rz0qKL%vKR>ǣ}"'UuۙeKm2%d^dWFϭ,=XEEG'\;rjFmİ \G&ezUUdN5x#^rlFW)źvz%@4y~<䓟< L^۲*ӻMBR?fҞ.q©`wF$quo&wK~_!=6RiNNJɎvkà8 x.$_^|rb"EP#ߡ-ji,;X$DzY Nu1)_pz)|v㟕58AIDzwi0$prlA"997SW5od^5}XHc:ּ|Mrucnֻ'b˕6o(:tcl濧[MOrrFq oI<6|N(8FXİp+WuҨ0)g.0܏?o!^ ee8M=Н?; .5Y&<(,Q>P( Y$3˃mM׋n*j&xgFe%-<$ Qnka{03s;23']}1uǷ*i15IꭐMqTHWKJ뽊jQˆSziwQJFݝxtt-AyP?AhCai@Iu4!Pc \t5hY>pH 'p%q#F*#PAsY,8"Tc8 O\35NQL:f A8̌r ^_O50Vl`$>| 9,uު35eD{tߩ_&nkXXw9sNrrzʒ]p;#Iq,; \;+#UY,)EZdU txw6ܧQ]ӎ4 gM+۫}.w27$yI88e/m%;o+ j5`j2Oz49bH8)л=n= ]GF2[9m&9@lɲ ޭ_gm頞] =ߗ~타-QS۫oo{yI_P RVd®f5 Nӯ46cʥ\-_$_`d}-6 X^GpXoFȥ T d<ş#ʨ-_˨#E,j;tZ2ltѨ(+;w[gѿL*VUVt87Rr،T$»B˷?2U".6DY?U[+mDs[ܫ6BtKR\鷋+^_{3q [kG,H,qWQ N8k>.YXgktydLүo DWe;dl8_>C[i?u'彻zՎMV#n{K}rEI!G[Xh1o?:sv] F)#t>}1Z՘Y- 'rC|*yI1ac*:G0;ӹ4D?.7[jbp٠>k:Z>2Eap7,!lc㊲𞩩[jQ;19wyN=4wNW < ҲNT7R$~ Xr1=FְRM٥:RY^NK hx;U't3˷uwVVlOP^݄G7 zyՏL*B91+&]ЉPӮD M F1Mӯ^? g\M,j,ܪdd$ۻK0H dn}~8˪ vۅb {@WGI*[{H@9NT0Trnz趓X=G4UZ!Lu@do-7jg(v-vܶjM4I)́=LYlwH:#U-zRה㕶?Ji.6Um/n%Yd!j_iZ*'XgWRN5\O؎fK[/Tz`tYO#H㍍n,7NƴVJ̲}X Z@ggLdȈsxޞX[X)Y{ivP3avֲk@ V~r))/3 thݺ2:e|O-KֺJ?)P}9}~Yqw%q78Tx,?{7~f[;=^ uբʉ]/|\H2BAvîFsNZ*gjl:a~T6g ̍"zlH*{Mtw[=Ą1ힿ5u}]sm+E;P4KɕzWi}z AipѶ r̸#mlL6\u+,b- bZjS,)*2QpNږO4a#% }OB/ua+6BA6eRtv#.xE2ks%Hl#FoQ]z\Ѵtݙq݌{sԍkmQZheko曻j~"U]71pv064ۖ7'_evZ4z[jvzjMa*0Tm<+ƏnځطUe.pZ$ ҿ3s̋'*`WuVV=x;Q+[؋I4У9%-㍼: h<2j) B]WM3sjI4mG웑"up7>Few&3gd~5ĽE [YѳUrއcBhqZp-y$b{mV "+o@u42 1u=IL.9p͹-gk#$ 2GU^ %:l&w(G?NQjtatIs\}Z VSaqR5gMGc mlMMh_Ix3#)x2d]Zrx͸5^qŗ:9GM:uMZ=ޔ۲b|0VkJ+Z}Ey-,#*]ܙ"]'_٥&NUu@--I'SFrWϯʭZ /i72B /~U-(>.!;#lU[K &1=:| %,itW1Nn Kon1Qu֣* 6 :ݱpC?9j=e (^ʩcr D|(GG#>@SL`[!718uV%2n|{F<5H`vѩA,($V_LvI9#RɔqKլdb$r~(kiSHjjUI5ȷ~hۼSvR]1LHI@^6ӳ!2!߼ 09PSHtuhA-Yy'V@Kr|Zd %#ͷ|꣢ÚTJ2NU Ϋ0%.Ŗ-ӆK1u˓$O268$0G|MDKE>B>ZY܎lkjcqO*P,˘WjovJ$L3EV.8 u+qnp!!mejd{h~aͿEzgGWdl5Ƹ\4]r T0 g3v.4hR=3ZCg\t_t=S ʑEy֬V5 ZT{Ր+a `x>\e@c:t{C+Knm:f}Akjm$h(XrmNiem4W-"ȸbNJSYѹ`\0GޅdIZ@?χFV,' p@\@ZF{ -^s;Ox ӈ4b Zd,ȇe\GZoCX3/nbn= j\e}{9<9 uYZHyr=\![TCql҉\t8veK&R:N$ps*hcsIu։*YlS [q -m&-"չI[9Sf)%=TP/#ȭV#6 = I9} :ΩCnXŒw+)dIxe. kwH@v>9߻-tG~H8eP>93P妡1e<"\#G$[U7q[J_OUsPPǁեvVצ>+{)cŒǠ'|Ze't+6Q$žqr`TCHqN^b[IFb^l0@lACi}@;bA;u장z[zVNx1G]9wbn2rycUYz&J{Y0x;;8+ŷ+o#0,Rɷ7={gXYn5lnxv)b`T`fiJKOggԷg7i<-ogZYzӡs4řv9WL}؜~}>ÈuvKD9r3ZG;vh:?k{CiDD$IsOXH"0i џu[O879m3QR0W\TI!sWw1{<>nqd;.~%nu-ԯ$acsgY׏"S3+Znʗ)t qsC2OV[䑝(o3\DXMr90MsU <d=ԙ#))]ze:ǵ KM3X/8&m?]Q\P6V벞u}f g[mI9DͦFD^'V_? g)8+5.}MG!r|[èk..] 3<@kI5VKd7DO}# <Y8c;TܞQT*{j0i7d=0?SQsHwp:tυEC,+ImMuF"`q¨ZԜ)`;'UhZygƲ.'U^;ŴۤH{}ALJj#Dr[)[q遃JN2լQWЪ&LF~Z>CX#;tǞrǃu?~O12[Dz˩3/23~#; ;"4I/nHP 1do!v铹#<0z=sP, [U-&>G?CO'I,4uAs<-j"{y3ة ʣc6P3N3'4W3F')<`wPziZzUيݫSսeN%x=5:1Q) p#Ѱ6ߺrSu°UY $sX܉bħ0v?Jiķ-Qda=vckQXgQΣ|j+e=噈'7㊦wmqlqwɓoN\{-S-'KimHf?WughH ;է{3qi5L  *Ũ-H#A3ϔO(L ir9#ʲRC.Dȏz ;~KyZB<<Ӊ;E֘#)L=SBS#o3W 61=aV;Y2 a|m֊'qC$, 0G/(#5' ]3# q铵CFD e-E$QHU7+#?g"+n4oB1zӌ.>9eVz<2"s@#XW|t-rv|vηJ6S낻wYn!nl_$4 `GF5=FV.xJ26Qb:A+TgR{]zao҈%kR\!S̱ q }6xUm3ңN-MB!'=1H̃8bnATO8~<։W壐: i+5ΖL;Kn'r1қV3[r$$&hQjz<(ge8\s5V]HRp,@ ֭;^TKesd<NEreGKs/6 Jϐ3P\Ķm:ƅbp|N#o )Oֲg<-sj(ݐPIUa}zzdӉ[vPs1َ1CU NB8H2.*f#foajG؏g@mi|ƒ[+$dS\ ۆU^5O'NEiiJI$FF,12ydӠ?gmtmqChtEب6 P[pM"e ;QAdr׳ʪsh&eg(ːEf:V'y2;eh|ԓ 2ݞ'J2X[bH#~auj# DK rFaсUHk=j+Xmc+dcxxU.LPvp| IћVeazdPNved|3O,t# mv2) UhJIGŪBī CpgG2Fղ? ڂ3}UMGCmr\Y:td*䄖GZ;qܐ! Ȏif_tp6/ g@jaF#,Ta6JMz|rWg+. zc.t$UdHVO4Ҵ_d2]}? >g|V)hXղEtϕo(CQafY <Luw +ImR֯ ,i5L+jQ!Kb_ujG_N8Q_iTv ؏6vsOuKa094OTNw TlLzo3X VNd(d Nbո|iXld+8-VhWeN(Pqޝ8YLr~nQӻQH\1{{WVV1XN7 e8~Ma+F[=Ym4o3sll%qtѳ`/2d65A pg-В;FGLXCOšt=.. 8ۜ瘱zyjyfͮm.Zݐ FIٛJ&yvf80>*hTaX9$E, Qe4{=FI"o*QV$F.6 *2ϟC{["j ~b励iWl&vWlңKTyM:2qْ+e1 n>ߕuG}R?}j(Kw@oàgj2"%ʏ;TԤmJ̜C$I>e:&yrYy=ܟvFJK9Q*Y`y2XJO]ǡ?ߍ&I ;챑ܱ0#o;qVVw(g[E\u{]%rAn]2pTr`-+46[Tڮ+XF 8Fxf~4imp Cd}+>6J%N =c?y*vju/k;MXy$Cȑ8`7&ݿ[Gƫ3;1?xOZ=Ir+(- FwVVQ3I J|!ⵋ8 01A)UpK(=pqcֽ49R)S)wj"l7] -3YP ̎rZ2mS2C\g&'5Un9x.bN9:d5ċxAs"A~TI83DIr4aǹ哏ʜZ+:ǁʾFL]\Wr<=jKc&]@Ke8s㍩7wVq \\$ŗۡ ^MHbp2dْx05BM .bi ϶R"5ˌ{}RrIdS k?k٦ɧSYu<D9+)#1O_wǠW >7k^H;+[ K;Krc=۸&ScY_{zfL1 ρ#onJfhQ/ղ|q]+mÊhٗ1\1QߑQ_CUI"Z1C<7R4zy5,.si XW)(>x½݃.Ӆ"4lePŒ̡F9Q_*9:/~ 䆧xe;iE}(35iR%eBMnVYc!o|½Q -S&V:N@۔U#WLGew纎؅;S񯥮8^%lnm^'A& W=lͥpJ r3iA+"nG2Z.1pq S8 @'>`r=HI<d*үG,vg836Yr gW* HuІ'V!I]AL^X]sŸ^_ZqW.:JP)__WEOoo1<G7֑ϥ9Fp?_֛lݯ]Tﺐ 6l gu^Vã_voSP.ZI12enFrI>0SrX`A8tvOh  NVzjsj >Q_?98ƌRRkǒu"2;(|+1B`ʏ"tI*6ԸsOe:\6ỹ%;U^bNN7 S\DU6 DAuI?/ΥnYۨ6"U c&5͆k{H叛%H'`pz3RjOsY'[[aG<=`Y|g~rrBv7bI#~X_"C xm銯p[I6RtK}Hf6[n%V@rɏu[sQ<;5k˗AU2Jk?SO<*lѦ& ;n 3MѻD}+kz|3k-ԄuP`d6$es}'L%mR(G[~D| zm[F~Al,̂Vs*o$`'.O*\(1'/G-՜D9G|&[Namm&A$uSX*\/fѤa< d % PF֐rԏ;f]WWcFbH.#<f\gó3$kUeVЇ-8q '7Lq>=GaΪzA}qef{ q<vֺRٴ+y/3ѭLܓʅX *kFT]{yg (1 ʤ/ɷ0H^@7N漪=aZdڴf{aNSyjçZ̡%+2yֆE0G,r$03q,u*2mB$G#InK}`BV|0irwL`U3X;&ʗhV%ս15cjm4ӄ&9dt889*Ni==X9 m&1k*XjdkO]J2:X$4XXY+ǭynLpDvL_|O֮G-:s KӮ=j[c̚ڸa% &SS0g|c^ŁƝѪޕ@LF`L<I vP1ͦ% 1*)cS |3Rka%)e*dmMҤdp6wRG,RF< oy}굚}CW3X;l~GOΚn9ZDx]g>"M:^Uդ=:*G>###ux~݁ vFxWDmfã-jmmVN\s6/, E$T"U-sxR%q@ Ywݑ5r61~y5pcƗw<%wVq\e݃Dܹ洹 Dc0kҾE:lOq<\(Ụ> rVMov 8w^"Ԧ=oU%,P h6* PXu_EbkѸh{0$<#1#Ql!tG{%IopcBz? vF39D2<}}*xCh5AOZ+$Ɲdݶv;9noR6RF'DUhs<鍾$.xY AE/[fukd7#ިeD۳DeGFK.̉RP+h~B}AP`{ˋt{{̐.zm .|\_,f^S c| =N:QkH4b#L>`xSKdhԬlLvwR}tj jSs4@9~a䳆($ Vgf=ɹk0 )*C;ՏHNi\w`rEjo?xY䍙 }bau9>?hF-3Ƌ.n{H#QBw-eF#ycj+ǽ1u($AMSJ{7OL1Z6M1JHI愵gM{?cVR][b6}2ݖhe#8qUte4avqi|yx"/86&n.oY9]|&PX2.&{V+Q! X:XQm J[frѢvGMWS <)`NbʣmjəR4Wӣ(Vl/=zE4##IFځ+WČ%}s]훲6.-).aE'8W$^lIvM͸Oo%R%1HG0o·m۴(L7 , ͗ p00r6\I F?i-혵H$dBAOA_Jmtȵ.LՇ6=j.lH*EZK-Bۑq? 1_g_gnjzVA[ﭴ[kx>drNIxd%~Zֵ7WKlݼF gШӥv -_ʨq@3p,rqjMæwPҢI3Y m,.WٮGoEwii1V HcĈ{"Y&p|@ MANsz75ln]fvcE6n1!f dҀ+\O " X&B̋>:#OCԔNUf|({&No:{+)Cq+R[}~ȒV;HGvsD,a\EiBIenvisage-6.0.1/examples/legacy/plugins/workbench/AcmeLab/acme/workbench/000077500000000000000000000000001406265421000262175ustar00rootroot00000000000000envisage-6.0.1/examples/legacy/plugins/workbench/AcmeLab/acme/workbench/__init__.py000066400000000000000000000000771406265421000303340ustar00rootroot00000000000000# Copyright (c) 2007 by Enthought, Inc. # All rights reserved. envisage-6.0.1/examples/legacy/plugins/workbench/AcmeLab/acme/workbench/acme_preferences_page.py000066400000000000000000000025041406265421000330540ustar00rootroot00000000000000""" The preferences for the Acme workbench. """ # Enthought library imports. from apptools.preferences.ui.api import PreferencesPage from traits.api import Bool, Color, Int, Float, Font, Str from traitsui.api import View class AcmePreferencesPage(PreferencesPage): """ The preferences page for the Acme workbench. """ #### 'PreferencesPage' interface ########################################## # The page's category (e.g. 'General/Appearance'). The empty string means # that this is a top-level page. category = "General" # 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 = "Acme" # The path to the preference node that contains the preferences. preferences_path = "acme.workbench" #### Preferences ########################################################## # Width. width = Int(100) # Height. height = Int(200) # Ratio. ratio = Float(0.1) # Background color. bgcolor = Color("red") # Text font. font = Font("helvetica") #### Traits UI views ###################################################### trait_view = View("width", "height", "ratio", "font", "bgcolor") envisage-6.0.1/examples/legacy/plugins/workbench/AcmeLab/acme/workbench/acme_workbench_plugin.py000066400000000000000000000037121406265421000331210ustar00rootroot00000000000000""" The AcmeLab Workbench plugin. """ # Enthought library imports. from envisage.api import Plugin from traits.api import List class AcmeWorkbenchPlugin(Plugin): """ The AcmeLab Workbench plugin. This plugin is part of the 'AcmeLab' example application. """ # Extension points Ids. ACTION_SETS = "envisage.ui.workbench.action_sets" PERSPECTIVES = "envisage.ui.workbench.perspectives" PREFERENCES_PAGES = "envisage.ui.workbench.preferences_pages" VIEWS = "envisage.ui.workbench.views" #### 'IPlugin' interface ################################################## # The plugin's unique identifier. id = "acme.workbench" # The plugin's name (suitable for displaying to the user). name = "Acme Workbench" #### Contributions to extension points made by this plugin ################ # Action sets. action_sets = List(contributes_to=ACTION_SETS) def _action_sets_default(self): """ Trait initializer. """ from test_action_set import TestActionSet return [TestActionSet] # Perspectives. perspectives = List(contributes_to=PERSPECTIVES) def _perspectives_default(self): """ Trait initializer. """ from acme.workbench.perspective.api import FooPerspective from acme.workbench.perspective.api import BarPerspective return [FooPerspective, BarPerspective] # Preferences pages. preferences_pages = List(contributes_to=PREFERENCES_PAGES) def _preferences_pages_default(self): """ Trait initializer. """ from acme_preferences_page import AcmePreferencesPage return [AcmePreferencesPage] # Views. views = List(contributes_to=VIEWS) def _views_default(self): """ Trait initializer. """ from acme.workbench.view.api import BlackView, BlueView, GreenView from acme.workbench.view.api import RedView, YellowView return [BlackView, BlueView, GreenView, RedView, YellowView] envisage-6.0.1/examples/legacy/plugins/workbench/AcmeLab/acme/workbench/action/000077500000000000000000000000001406265421000274745ustar00rootroot00000000000000envisage-6.0.1/examples/legacy/plugins/workbench/AcmeLab/acme/workbench/action/__init__.py000066400000000000000000000000001406265421000315730ustar00rootroot00000000000000envisage-6.0.1/examples/legacy/plugins/workbench/AcmeLab/acme/workbench/action/new_view.py000066400000000000000000000020121406265421000316640ustar00rootroot00000000000000""" An action that dynamically creates and adds a view. """ # Enthought library imports. from pyface.action.api import Action class NewViewAction(Action): """ An action that dynamically creates and adds a view. """ #### 'Action' interface ################################################### # A longer description of the action. description = "Create and add a new view" # The action's name (displayed on menus/tool bar tools etc). name = "New View" # A short description of the action used for tooltip text etc. tooltip = "Create and add a new view" ########################################################################### # 'Action' interface. ########################################################################### def perform(self, event): """ Perform the action. """ # Create your view... view = View(id="my.view.fred", name="Fred", position="right") # ... add it to the window! self.window.add_view(view) return envisage-6.0.1/examples/legacy/plugins/workbench/AcmeLab/acme/workbench/action/new_view_action.py000066400000000000000000000024011406265421000332230ustar00rootroot00000000000000""" An action that dynamically creates and adds a view. """ # Enthought library imports. from pyface.api import ImageResource from pyface.action.api import Action from pyface.workbench.api import View class NewViewAction(Action): """ An action that dynamically creates and adds a view. """ #### 'Action' interface ################################################### # A longer description of the action. description = "Create and add a new view" # The action's name (displayed on menus/tool bar tools etc). name = "New View" # A short description of the action used for tooltip text etc. tooltip = "Create and add a new view" ########################################################################### # 'Action' interface. ########################################################################### def perform(self, event): """ Perform the action. """ # You can give the view a position... (it default to 'left')... view = View(id="my.view.fred", name="Fred", position="right") self.window.add_view(view) # or you can specify it on the call to 'add_view'... view = View(id="my.view.wilma", name="Wilma") self.window.add_view(view, position="top") return envisage-6.0.1/examples/legacy/plugins/workbench/AcmeLab/acme/workbench/perspective/000077500000000000000000000000001406265421000305505ustar00rootroot00000000000000envisage-6.0.1/examples/legacy/plugins/workbench/AcmeLab/acme/workbench/perspective/__init__.py000066400000000000000000000000001406265421000326470ustar00rootroot00000000000000envisage-6.0.1/examples/legacy/plugins/workbench/AcmeLab/acme/workbench/perspective/api.py000066400000000000000000000001261406265421000316720ustar00rootroot00000000000000from bar_perspective import BarPerspective from foo_perspective import FooPerspective bar_perspective.py000066400000000000000000000010001406265421000342070ustar00rootroot00000000000000envisage-6.0.1/examples/legacy/plugins/workbench/AcmeLab/acme/workbench/perspective""" An example perspective. """ # Enthought library imports. from pyface.workbench.api import Perspective, PerspectiveItem class BarPerspective(Perspective): """ An example perspective. """ # The perspective's name. name = "Bar" # Should the editor area be shown in this perspective? show_editor_area = False # The contents of the perspective. contents = [ PerspectiveItem(id="Green"), PerspectiveItem(id="Black", position="bottom", relative_to="Green"), ] foo_perspective.py000066400000000000000000000010771406265421000342440ustar00rootroot00000000000000envisage-6.0.1/examples/legacy/plugins/workbench/AcmeLab/acme/workbench/perspective""" An example perspective. """ # Enthought library imports. from pyface.workbench.api import Perspective, PerspectiveItem class FooPerspective(Perspective): """ An example perspective. """ # The perspective's name. name = "Foo" # Should the editor area be shown in this perspective? show_editor_area = True # The contents of the perspective. contents = [ PerspectiveItem(id="Blue", position="left"), PerspectiveItem(id="Red", position="with", relative_to="Blue"), PerspectiveItem(id="Green", position="top"), ] envisage-6.0.1/examples/legacy/plugins/workbench/AcmeLab/acme/workbench/test_action_set.py000066400000000000000000000046651406265421000317730ustar00rootroot00000000000000""" A test action set. """ # Enthought library imports. from envisage.ui.action.api import Action, Group, Menu, ToolBar from envisage.ui.workbench.api import WorkbenchActionSet class TestActionSet(WorkbenchActionSet): """ An action test useful for testing. """ #### 'ActionSet' interface ################################################ # The action set's globally unique identifier. id = "envisage.ui.workbench.test" menus = [ Menu( name="&Test", path="MenuBar", before="Help", groups=["XGroup", "YGroup"], ), Menu(name="Foo", path="MenuBar/Test", groups=["XGroup", "YGroup"]), Menu(name="Bar", path="MenuBar/Test", groups=["XGroup", "YGroup"]), ] groups = [Group(id="Fred", path="MenuBar/Test")] tool_bars = [ ToolBar(name="Fred", groups=["AToolBarGroup"]), ToolBar(name="Wilma"), ToolBar(name="Barney"), ] actions = [ Action( path="MenuBar/Test", group="Fred", class_name="envisage.ui.workbench.action.api:AboutAction", ), Action( path="MenuBar/Test", group="Fred", class_name="acme.workbench.action.new_view_action:NewViewAction", ), Action( path="ToolBar", class_name="envisage.ui.workbench.action.api:AboutAction", ), Action( path="ToolBar", class_name="envisage.ui.workbench.action.api:ExitAction", ), Action( path="ToolBar/Fred", group="AToolBarGroup", class_name="envisage.ui.workbench.action.api:AboutAction", ), Action( path="ToolBar/Wilma", class_name="envisage.ui.workbench.action.api:AboutAction", ), Action( path="ToolBar/Barney", class_name="envisage.ui.workbench.action.api:ExitAction", ), ] #### 'WorkbenchActionSet' interface ####################################### # The Ids of the perspectives that the action set is enabled in. enabled_for_perspectives = ["Foo"] # The Ids of the perspectives that the action set is visible in. visible_for_perspectives = ["Foo", "Bar"] # The Ids of the views that the action set is enabled for. # enabled_for_views = ['Red'] # The Ids of the views that the action set is visible for. # visible_for_views = ['Red'] envisage-6.0.1/examples/legacy/plugins/workbench/AcmeLab/acme/workbench/view/000077500000000000000000000000001406265421000271715ustar00rootroot00000000000000envisage-6.0.1/examples/legacy/plugins/workbench/AcmeLab/acme/workbench/view/__init__.py000066400000000000000000000000001406265421000312700ustar00rootroot00000000000000envisage-6.0.1/examples/legacy/plugins/workbench/AcmeLab/acme/workbench/view/api.py000066400000000000000000000002411406265421000303110ustar00rootroot00000000000000from black_view import BlackView from blue_view import BlueView from green_view import GreenView from red_view import RedView from yellow_view import YellowView envisage-6.0.1/examples/legacy/plugins/workbench/AcmeLab/acme/workbench/view/black_view.py000066400000000000000000000006451406265421000316560ustar00rootroot00000000000000""" A view containing a black panel! """ # Local imports. from color_view import ColorView class BlackView(ColorView): """ A view containing a black panel! """ #### 'IView' interface #################################################### # The view's name. name = "Black" # The default position of the view relative to the item specified in the # 'relative_to' trait. position = "bottom" envisage-6.0.1/examples/legacy/plugins/workbench/AcmeLab/acme/workbench/view/blue_view.py000066400000000000000000000006411406265421000315250ustar00rootroot00000000000000""" A view containing a blue panel! """ # Local imports. from color_view import ColorView class BlueView(ColorView): """ A view containing a blue panel! """ #### 'IView' interface #################################################### # The view's name. name = "Blue" # The default position of the view relative to the item specified in the # 'relative_to' trait. position = "bottom" envisage-6.0.1/examples/legacy/plugins/workbench/AcmeLab/acme/workbench/view/color_view.py000066400000000000000000000046341406265421000317220ustar00rootroot00000000000000""" A view containing a colored panel! """ # Enthought library imports. from traits.etsconfig.api import ETSConfig from pyface.workbench.api import View class ColorView(View): """ A view containing a colored panel! This view is written so that it works with *both* wx and Qt4. Your own views obviously do not have to do this! """ #### 'IView' interface #################################################### # The category that the view belongs to. category = "Color" ########################################################################### # 'IWorkbenchPart' interface. ########################################################################### #### Trait initializers ################################################### def _id_default(self): """ Trait initializer. """ # By making the Id the same as the name, we make it easy to specify # the views in the example perspectives. Note for larger applications # the Id should be globally unique, and by default we use the module # name and class name. return self.name #### Methods ############################################################## 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. """ method = getattr(self, "_%s_create_control" % ETSConfig.toolkit, None) if method is None: raise SystemError("Unknown toolkit %s", ETSConfig.toolkit) color = self.name.lower() return method(parent, color) ########################################################################### # Private interface. ########################################################################### def _wx_create_control(self, parent, color): """ Create a wx version of the control. """ import wx panel = wx.Panel(parent, -1) panel.SetBackgroundColour(color) return panel def _qt4_create_control(self, parent, color): """ Create a Qt4 version of the control. """ from pyface.qt import QtGui widget = QtGui.QWidget(parent) palette = widget.palette() palette.setColor(QtGui.QPalette.Window, QtGui.QColor(color)) widget.setPalette(palette) widget.setAutoFillBackground(True) return widget envisage-6.0.1/examples/legacy/plugins/workbench/AcmeLab/acme/workbench/view/green_view.py000066400000000000000000000006451406265421000317020ustar00rootroot00000000000000""" A view containing a green panel! """ # Local imports. from color_view import ColorView class GreenView(ColorView): """ A view containing a green panel! """ #### 'IView' interface #################################################### # The view's name. name = "Green" # The default position of the view relative to the item specified in the # 'relative_to' trait. position = "bottom" envisage-6.0.1/examples/legacy/plugins/workbench/AcmeLab/acme/workbench/view/red_view.py000066400000000000000000000006351406265421000313530ustar00rootroot00000000000000""" A view containing a red panel! """ # Local imports. from color_view import ColorView class RedView(ColorView): """ A view containing a red panel! """ #### 'IView' interface #################################################### # The view's name. name = "Red" # The default position of the view relative to the item specified in the # 'relative_to' trait. position = "bottom" envisage-6.0.1/examples/legacy/plugins/workbench/AcmeLab/acme/workbench/view/yellow_view.py000066400000000000000000000006511406265421000321120ustar00rootroot00000000000000""" A view containing a yellow panel! """ # Local imports. from color_view import ColorView class YellowView(ColorView): """ A view containing a yellow panel! """ #### 'IView' interface #################################################### # The view's name. name = "Yellow" # The default position of the view relative to the item specified in the # 'relative_to' trait. position = "bottom" envisage-6.0.1/examples/legacy/plugins/workbench/AcmeLab/run.py000066400000000000000000000017651406265421000245170ustar00rootroot00000000000000""" Run the AcmeLab example application. """ # Standard library imports. import logging # Example imports. from acme.acmelab.api import Acmelab # Enthought plugins. from envisage.api import CorePlugin from envisage.ui.workbench.workbench_plugin import WorkbenchPlugin # Example plugins. from acme.workbench.acme_workbench_plugin import AcmeWorkbenchPlugin # Do whatever you want to do with log messages! Here we create a log file. logger = logging.getLogger() # logger.addHandler(logging.StreamHandler(file('acmelab.log', 'w'))) logger.addHandler(logging.StreamHandler()) logger.setLevel(logging.DEBUG) def main(): """ Run the application. """ # Create an application with the specified plugins. acmelab = Acmelab( plugins=[CorePlugin(), WorkbenchPlugin(), AcmeWorkbenchPlugin(),] ) # Run it! This starts the application, starts the GUI event loop, and when # that terminates, stops the application. acmelab.run() return if __name__ == "__main__": main() envisage-6.0.1/examples/legacy/plugins/workbench/AcmeLabUsingEggs/000077500000000000000000000000001406265421000251445ustar00rootroot00000000000000envisage-6.0.1/examples/legacy/plugins/workbench/AcmeLabUsingEggs/README.txt000066400000000000000000000007401406265421000266430ustar00rootroot00000000000000To build the AcmeLab example:- cd .../src/acme.acmelab python setup.py sdist --dist-dir ../../dist/eggs cd .../src/acme.workbench python setup.py bdist_egg --dist-dir ../../dist/eggs cd ../../dist/eggs unzip the acmelab zip file add a '.egg' extension to each unzipped folder Surely there must be a better way to do this? I'm not an egg expert! To run the example:- cd .../envisage.ui.workbench_3.0/examples/AcmeLab/dist python run.py envisage-6.0.1/examples/legacy/plugins/workbench/AcmeLabUsingEggs/dist/000077500000000000000000000000001406265421000261075ustar00rootroot00000000000000envisage-6.0.1/examples/legacy/plugins/workbench/AcmeLabUsingEggs/dist/run.py000066400000000000000000000043461406265421000272740ustar00rootroot00000000000000""" Run the application. Usually, all you have to do in here is:- 1) Initialise the logging package as you see fit (or not at all ;^)! 2) Set the 'EGG_PATH' variable to be a list of the directories that contain your application's eggs. 3) Edit the 'run' function to do whatever you need to do to start your application. """ # Standard library imports. import logging # Create a log file. logger = logging.getLogger() logger.addHandler(logging.StreamHandler(file('acmelab.log', 'w'))) logger.addHandler(logging.StreamHandler()) logger.setLevel(logging.DEBUG) # A list of the directories that contain the application's eggs (any directory # not specified as an absolute path is treated as being relative to the current # working directory). EGG_PATH = ['eggs'] def run(): """ The function that starts your application. """ # We do the imports here because the modules may well be in eggs that get # added to the path in 'main'. from envisage.api import EggPluginManager from acme.acmelab.api import Acmelab # Create an application that uses the egg plugin manager to find its # plugins. acmelab = Acmelab(plugin_manager=EggPluginManager()) # Run it! This starts the application, starts the GUI event loop, and when # that terminates, stops the application. return acmelab.run() ############################################################################### # Usually, there is no need to edit anything below here! ############################################################################### # Standard library imports. import logging from pkg_resources import Environment, working_set # Logging. logger = logging.getLogger(__name__) def main(): """ Run the application. """ # Find all additional eggs. environment = Environment(EGG_PATH) distributions, errors = working_set.find_plugins(environment) if len(errors) > 0: raise SystemError('cannot add eggs %s' % errors) logger.debug('added eggs %s' % distributions) # Add them to the working set. map(working_set.add, distributions) # Create and run the application. return run() if __name__ == '__main__': main() #### EOF ###################################################################### envisage-6.0.1/examples/legacy/plugins/workbench/AcmeLabUsingEggs/src/000077500000000000000000000000001406265421000257335ustar00rootroot00000000000000envisage-6.0.1/examples/legacy/plugins/workbench/AcmeLabUsingEggs/src/acme.acmelab/000077500000000000000000000000001406265421000302235ustar00rootroot00000000000000envisage-6.0.1/examples/legacy/plugins/workbench/AcmeLabUsingEggs/src/acme.acmelab/acme/000077500000000000000000000000001406265421000311305ustar00rootroot00000000000000envisage-6.0.1/examples/legacy/plugins/workbench/AcmeLabUsingEggs/src/acme.acmelab/acme/__init__.py000066400000000000000000000000771406265421000332450ustar00rootroot00000000000000# Copyright (c) 2007 by Enthought, Inc. # All rights reserved. envisage-6.0.1/examples/legacy/plugins/workbench/AcmeLabUsingEggs/src/acme.acmelab/acme/acmelab/000077500000000000000000000000001406265421000325145ustar00rootroot00000000000000__init__.py000066400000000000000000000000771406265421000345520ustar00rootroot00000000000000envisage-6.0.1/examples/legacy/plugins/workbench/AcmeLabUsingEggs/src/acme.acmelab/acme/acmelab# Copyright (c) 2007 by Enthought, Inc. # All rights reserved. acmelab.py000066400000000000000000000027051406265421000343770ustar00rootroot00000000000000envisage-6.0.1/examples/legacy/plugins/workbench/AcmeLabUsingEggs/src/acme.acmelab/acme/acmelab""" The Acme Lab application. """ # Standard library imports. from logging import DEBUG # Enthought library imports. from envisage.ui.workbench.api import WorkbenchApplication from pyface.api import AboutDialog, ImageResource, SplashScreen class Acmelab(WorkbenchApplication): """ The Acme Lab application. """ #### 'IApplication' interface ############################################# # The application's globally unique Id. id = "acme.acmelab" #### 'WorkbenchApplication' interface ##################################### # Branding information. # # The icon used on window title bars etc. icon = ImageResource("acmelab.ico") # The name of the application (also used on window title bars etc). name = "Acme Lab" ########################################################################### # 'WorkbenchApplication' interface. ########################################################################### def _about_dialog_default(self): """ Trait initializer. """ about_dialog = AboutDialog( parent=self.workbench.active_window.control, image=ImageResource("about"), ) return about_dialog def _splash_screen_default(self): """ Trait initializer. """ splash_screen = SplashScreen( image=ImageResource("splash"), show_log_messages=True, log_level=DEBUG, ) return splash_screen api.py000066400000000000000000000000341406265421000335550ustar00rootroot00000000000000envisage-6.0.1/examples/legacy/plugins/workbench/AcmeLabUsingEggs/src/acme.acmelab/acme/acmelabfrom acmelab import Acmelab images/000077500000000000000000000000001406265421000337025ustar00rootroot00000000000000envisage-6.0.1/examples/legacy/plugins/workbench/AcmeLabUsingEggs/src/acme.acmelab/acme/acmelababout.png000066400000000000000000000271701406265421000355310ustar00rootroot00000000000000envisage-6.0.1/examples/legacy/plugins/workbench/AcmeLabUsingEggs/src/acme.acmelab/acme/acmelab/imagesPNG  IHDR,,SF0PLTEn:}~|CCClb;._ pHYs  tIME IDATx}{׹ 2&PjiuHp{bpALC2nrwʼn:B,qQ"3CQZ+6&R&EQ@]'q+$HsμΙɕ܁ ;|Lu;zӹvvwwwv}_7ݝ7v_'F$曺_߻KZ`?N^m껻gv/]ze$X:{wQ zg]BW;XXk?]z1vdXww_xi~vi v/|^$T(!Xv]5䔲{ ATE[[|^~~=LZK'h4%n)LAd% U]6|팳 )VUG"rH D]$?]yaA2RnZBOOi 1,2g<-X^8j1UBJlɍA؀7c8kS|~a\D7|6LC˻ o:̚C97e9\pbw HNmkEWO5-Vj°ڢF1ubHs'ú3xEIC~HZi\we9 8a o-McOڠ4ahGv8P߹ذ޻jhZWȬK4,yˬ:Iڬ׏ kd ,iXV&xz^aq[Xz??f_xzde;RjPIodXn JyK ||Lm*~M0w/<d[/xӤ?0 4Xy81wG*i&  &%͚(֧6#úI.MQ!_,JBu\kWƖnӺðV\@OG9}Sm+Gr)):. !ಫ5u8N.,EM00^ܤ76p_*zh^%|V\͑?2{NaP0r鰺>>6n]B|Mɘ' da~Flq:QF}v(ikJpRZ5Hg79s*՟@3"Y"j{QhiKE6}ܰDS %U5M C/|xכzS[ BOS8?ڶP~#~D9a(|=O{eQ^g!,- L s١i}/}: #PU͹RY&ډss: iwꆔd\y KC&B8^w:ڶ|?ՈxW LVd?y`0g$HN3]}aN=0c*AƧuTs,MOVȄEǵٰ]_ p5P8u G01 b3ɾkvzkGMVĤ7N2dq=sa?7 +@Z$s>>& taE$_`e0d~Ibxy:L%!--a5I^z+qSȄPatC<^1й`r 2 K`2LgzZ\K&NyZ0=D).'Pwh%~oJyz(cHx4O yN` Q 5>c~<L7a5[cp]!P Ԑ݋xǯ'º>[_fI;ALC:O+٥426-,/D挩8`hyZ dMS85Cez]Ʒ8~B!1)AbmC VǗiOLv"bAYZK~u&p"FU@k6E8{ C=+źVխ iX'YOjx;W(qS_ABh$Ulds L@!5Gbq` rUϡ*EަHױ+O9*f+u-Jd6.5qK#?u=D~6#j)!fo nKT`䆯\gHX? #NR|u؝f04MOp_,_Iu qd#^I35R+j |jva lK $(0v/ͤa ǴVZ/U$ ߯Gt9 f.qq/e 2æQI:whRbr30BqbM^5A*Z)> sID];>,>0RX|`‡Ơ1\/U PS-b(XB0T1(\nP+gʖ3@$z`Έi.XX$!C7)>ti רDC-!2b5Bq)!}rR4Q1aNyp*m֙K qM+pNPԸ,ciCmϧr-.G{b^rl (LIyL<3]O!@XPX_i"2zF,!WH!%q,-0uj?c\0J s1s=1s}l߇4 "ԋ(z4btqVWd"!y:):X C4oSDu`=Hj8A8FYe %1lA5 X&U-I#?1-䙬/rȴp =0&#elbL!MCZ+c_MNVO0rXmZE@ҐIxbXPuGzW%\.hؔ]"tp_nNb8'.9Ά>?VBȴLªƸ3{$'8a}%M(e78 Gg 4+ 4A dbY,Ϊ:7cU̕8!w+BE4ƅt a=`{'y^`3elVLwQĢի3yɦgd٤&}:eSM)G Za1`9cW$`+eVʾ|)f1̊}-z.[3bug YtX3,Ŗ=,:LMsu3|3VX`*c 0C\.%OF7DZ ZH}Cv9Pz{+d4]`]/5ԨǛ̹ML\A(%/)|Ջ%5V׼Rt*cgyw~x4pҨ#thg.e1ASY[f:͛hvը HW Ϧe6T@`$\R ")7N)=+2͹ױ+D{M0$-(%omX?E5R?9Mk<"`E< ae 4℥& - OT-4]LHtmy>7Z%s=I)YrE5T'o1:s>1ICc%4Yeg)uGDnZŒ.,$zSPׄ Fh>MXbafw2 ~9Ebp)3<J9SB:\2E(LrH{kvKiRIjqP%`*f=G0j;=#\)fԹavN J%*<USnp|ê=@Z6oZk5 )R)[rP14:D# KEUr#O WɌPW)?%ðh+uà%q~B^&M ʩANe0;Nb5eh$U6>,>Mɔˠ'WZv<%knYk y+8GucJD4FY yt4X\J (>c@V зA,+*UkjtYr6wIø9 D[Vkh"TEvBDy31IkUUwaLUar„錯,)@ZӰHCeTo|ǯ{IvLa[3b>+O#ƜRD͖T SoU__jW[W_1RFMAZުӥ`~htVUXv/m MeK?O~GWk/9kP>,*y_amt:?T@j)qkX^SG/>y[7@piߡwVjlQҒHf*1ӫ߾ U!cdc?J2dߜldA*\yHc+rygBK2ob?%EQ@_Q\kY09?b~Wlmm+[:$Y .X}`S@ a1_Zdq[ޚ~m8a(~WN/X(@T23k)?Z5L?E}aisXJkxh 0?^t˘BsmG}=n泶{=6LUn2#탲jMkr,PdoL?ll+1̢}/MQ۶i8id~KU) ۩sL H׵7oNX 9ݚWZWL=wEo)]֠ogp: hRg^D:9h`Vm"oB2&dN4dQutBw`xs@JGOgT ~B4;F)]-Z4?<ى ʍafJ+NU},f_~Ҏo9!ayo}ǕQZ"dlϘ+"`CB›+$(Vf"R`.Wf/"oq9QϯY%n[,:%8@+͓U"OV -2gA KFeQ1ĉU\X^AWFYJ/~ɛN3Z54Vz~i" EhO$*lsB| ~rTIJ?@)5%d 5yRs %YlHҬ LMD9A`:g|X S4t !]ҬmPD Fh)EOo72%C\%poݻ촟tYMQbpa)?ZBuyI{UUH&~S~ #Rޝm=Y+u* -BZCQ,gAɳOUpTGm>pf hEr`,kX`٦:+P2׵ zڞ8ʾ@3Y+C`5eQ-M&V(Y,wX{#/ 6l_ȗ}ܕ-[֣!,Er@Q"e,I} a=Mm[i%&^G.:wˮ]jh 2VMAiJ8Ld[@wSOdzB3-~w~skv%d~ 3A1U2F檖-g y9#=k]BMQBƱ >jfeCrCKw"B%HP9;ܳ&hXnITͪ:ryAJ gpX|i: 6"2Cw k'de9Q&nK9*fqFLvB*f6*=umsr빼(h( "@C0d14MYR*eW9,wX'sB^c E/iX眶;(,~3PWhmt<3ޛC!y Tt4JHk&=Em l%C~e K,gRl UX-,!ilL|_塚b[Gmdp0چM"vj\ymYD(/⪮$yN4 dB=U5”UczE-;CZ@z2^ge'Smei6Th%,^t?855sIK5 2;ִ uIRR7} X<jF)&Vl PAÛՋynJ a--"8Ͳ&nr )Ȼ}uCD:BH+v*t/$9HyN;WMK{vr pWX9 h@NG h1a}AtNrj)vJF˂YJKzG=۸Uס_ ʶ@BK3Z8L =Ǽwxp-Zx::Kّ'>2}bjcwx0WXa΄i륭"XgSRPm-%넒+dP҈Tiai&pZđC;#4, +cL}[WO<l+ UiQ=)ڨy^5{FKN1ۇ7|8 |j Up68TȦE7\WXlqiYmT5S֣qv ˣ n懼Q(rayrpQp tVnJBdgCQ{P{ox tw-ӈaߏ*"UyOfw:OF Kj)J.@r=֫Z_ CB u8E`.JI?!-:[F DNn"C*ID74S#}B=VLӫwU['VrɣWXz*0qK I >y=١j@5 K1qͪ&~ƿ𘂏 i:#gDو8[ ?1Am3 i;u:buZu1`Orlb،V͡1}UHYQ"M:`7m / '%ĦGuʪjA3KCzkZZ pyZpI (˴$iDU"zJ5-JyQ_D S1:mk, t)"Aħ([(j ֚b'f|V!XGMmKjtTC"^L7!'k IJ}zxW *#ᜦfw 8xt[cA@E3%cN&w= nU(q Mz(i(oY+q1ǬF`ہ5AXez DH V5JhAvma&¢Dzhɛ:V ~atDP;938N8u2LOztx--bvSSNu6g&n>aa =V +DBvE(\SEGCHi{ҁMύuE_ d - jq& #>֨ڠ*#bwd=AZ\a']`="o.ĸKKsf]o]況ʴ{Q^h5IZ͋\'}uX Bu$int vo$S yeB,.} 3B_8go$tA`CG:9qZ lq5^ZAc\V&_wom# Aj&GIF?¨*oy_jcWI9`9RaBs8iyGL#šE 7jmnL|}8V'P69*GJ8%ES S1RZ޳]V.?{dX'%H _VJ_KQ"'nOkr" i)}߁AYps8&gIZWu#xjW/O֣k֫漃ʈa4!M'N?&+T&F(%K^oOփl,%-7b=_XJ$<{aM+Z Hȧ|oQiig1y|XoR -.̖1)yY'% ~X7(U$$NJ튊֩\p΄5Mf=8b9^ E 㟤mCxΆuw7TED:X %y&߈uZWSm e06c.⧯NALoß~aݽWT|HM'į o0y9WJ0?(&_fxYg۝|^$Ϸh.$/\`MA< '?)gmB8{sq4}B~~理/-n{눏9*"6kpz|DiqcA3fʨ:s4elXPH\!s?|'<>ceI (g,^S$amE_}ǽcÂKHpfǿ~Ne$!{'Bc x{?~Ia{;'O\IvIENDB`acmelab.ico000066400000000000000000000462461406265421000357760ustar00rootroot00000000000000envisage-6.0.1/examples/legacy/plugins/workbench/AcmeLabUsingEggs/src/acme.acmelab/acme/acmelab/imageshV V00%&(  U,O)1I%@C"s: t;P|?z>s:?s:e6b3mE3xU5tT%cDS,NJLKAt ~Cs: o=yH"Y3nYTGv>Dz>҇;΃6~l_ Z W)j'Y"[NJ~HyDs+]9o^ٚ[֗WѓR̎BÁd W4rQRZÏdÓc`Y$Yf۠e؞bԚ[ϔUɎMÇ1rNz? OO:sr͟}̤yƟs?mNlڢk֠dљ]˓TŋS.`s:*RC{s͟ϪɤeSCyl֟fқ]̔]Ƒ0cy=uP Oy!\7l:n(Z t@y>iG'[!V }At:2 L I' F6 {Bs:(, R*N(5J&>E# s:)s:dx=w<s:_s:#^15[0 Z2d?b@ O,E#I$Vx=M;oQN8kHv<s:g6i=ChGk7[4zVDa?|\u>@v_Ә;ǀ$p&p;{^Ƒ8jw<s: n;_{LNuNo;h7b3[/rn:g6h6)]Zؘifda _ \ Y XZQs:D {B6h,d H E zA t=m:vh٠lifda _ \ Y W Ubv< HU_ ] ZR F y@IiޢKԏKҍKό:ǀ#oa \ Y WT] w? JXE|CxAu?p;k(Z y@oܥYؘWՕUҒSΐOʌEń!j Y WTbu; LLXWURO|KwHgКbڝ_ך]ԗZЕV̐QȌLć:y_]Wv< M{6p^ǒiƗh”fb^0_qfX{6^!yLi<a3e<&mI?|]wANe͗jؠ'udb` _ ]]Fr̞=ot;s:a m:rBSzKt~I q<m9h7d4_2[/f4 yAnաTԓkfdb` _ ] [ Y'kjə:kt:s: s=L pq<l9h6c4c3s:Ne۟jhfdb` _ ] [ Y W,npțIs: x@ v?*[XN F ~C y@ u>p;l9g6p9$Ywߪ&xihfdb` _ ] [ Y W VQYs:s:= {BJ yAX.e J H E }C y@ t>p;k8t;OSؕkihfdb` _ ] [ Y W V]rȝ|Gs:t }C|N^S M J G E }B x@ t=p;s:eΙ=Ӈkihfdb` _ ] [ Y W VThŖ)Zs: E*^UQ O L J G E |B x?s<u=xݪ4с"u$t&u%shb` _ ] [ Y W VTT>nt: F:oN(f)e)c)a&\Q F |Bu=IyUؖQ֒PԐOяMύK͋:%oa ] [ Y W VTE}Lzs: H9pT>w=uLlޥaڜ`ך^ՙ]ӗZѕWΑTˎQȌNƉIÅD)l Yarȝ{Cs:n LE JX]ƑdŔdÒcb`^ZWNQxުg۠fٞdםbԚ_Ҙ[ДY͑UʏRNjMćJF>z`ÑPs:s:2N L)caʔk˛qʝqǜnęmjgdQ{u LF~cʖ|Ц~Υ|̣yȠwŝsok:gv=gЛqܥl٢kנg՝dҚ`ϖ]̔XɐTƌPÈYōq̞0ct:s: MSScʖ~ѧӮϫ̧ȣ{ğvqLFZĎuۧjנk֠hӜdЙ`͖[ʒVǍi˙oɚ/as:s:LO MM 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:1V-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<']=rI}UL?s/c {Cs:s:s:s:vs:b3`2^1\0[0iB6|YDdMkRoLhC}_1nOW6G$E#D"e3s:s:FNrգwاwצvץvեuԤuӣuңsϡXPs:s:s:s:e5-c4a3_2$vMTuZzZyZxVtQoVsXsXrXqNg[:f3s:t:,ioעwۨpףN͍3z&ph!l-sC‚f̘tСrϟq<n:k8h7e5b3_2\0`1s:s:NĈx߫UՔlgfedca` _ ^ ] [ Z Y X9xs̞ax>s:s: r= p<tAT~[ U }B zA w? t=q<n:k8h6e5b3_1n8s:(eyiۢligfedca` _ ^ ] [ Z Y X WMs˞>ss:s:s: u? s> r==m]7h F D |B y@ w? s=p;m:j8g6d5g5s: {Alڢy+{jigfedca` _ ^ ] [ Z Y X W[jȘqɜPs:s: w@ u? S]RK G E D |B y@ v? s=p;m:j8g6o8s:G~yO֑kjigfedca` _ ^ ] [ Z Y X W U-msʝ]t;s:s: {B yA w@R^ [ J I G E D |B y@ v> s=p;m9j8s:u r<o;n9s:#XyUٖlkjigfedca` _ ^ ] [ Z Y X W UT3qrɝ=ms:s: ~D |C4g^-h O M L J H F E ~C {A x@ u> r<q:s:Bxy7҃lkjigfedca` _ ^ ] [ Z Y X W UT[rɝYs:s: E ~DGy^Y Q O M K J H F E ~C {A x@ u>s;s:Ny&xmp!s rnkfdca` _ ^ ] [ Z Y X W UTSkƘes:s: F EP]WWXYVSPK F D }C zA w?s;s:ZŏyMבM֐MԐMӏLҎKЌJϋF̈7~(td` _ ^ ] [ Z Y X W UTSaÑoĘs:s: G FYZ1m0l0j/i.f.e,c+a']TH }B zAt;s:\ȒzSؕRדPՒPԑPӐOюMύLΌJ̊IʉFȆ/vc ^ ] [ Z Y X W UTS_qŚt;s: H GXY:u9t9r9q7n7m5j4i3f1d/b$XGv=s:RyRؔUוT֔TԓRӒRґQЏPώN͍LˋJʉJȇDŃ%n ^ [ Z Y X W UTShŖis:s: I HP_Ðr=pl%Vs:/ey_ۜ\ٚ[ؙ[֘Z՗YԕWҔVВUϑS͏Q̍OʋMȉKƇHąD*o\ X W UT(irɝGws:s: K J2j`ȓOTTSRQPON}L{KyHvFt=ks: xAxުq`ڜ_ٛ_ך^֙]՗[ӖZѕWГVΑT͏RˍOɋNljKćH„F@}$j X UTHrɝ'Xs:s:^ L9 KT`ɔ\Ɛ]Đ]Ž[[[YXVUSR~O{My{Es:ZŎye۟cٝb؜`כ`֚^Ԙ]җ[ѕYϓW͑UˏRɍPNjNʼnKÆHƒFD:ydapɛkÖ v>s:s: M L JN`ɔ_ǓdǕdŔdÒdba`^\ZXU.]s:Pvvߩfڟfٟd؝c֛b՚aә^ї\ЕZΓX̑UʏSȍQƋMćKÅGFB~A|iȘsʝ-bs:s: M KY_˔aȔlʚnʛmȚlŘk×jifdba_W x@s:>|ynܤhڠh٠fםd֜cԛaҙ_З\ϔ[͓XˑUɎSnjPʼnMÇJGDYs˞Ut:s:s:8 N L K9v`ʕdʖv΢v͡u˟tȝsƜqĚo˜nkifd8fs:z@dԛx߫jڢkڡjؠg֞f՝dӛbљ_Ж]Δ[̒XʐTȍRƋOĈL†ILr˞nɚIs:s: M{ LMSċ`ʔl̛ѧ~Ϧ|ͥ{ˣyɠxǟvĝsšqnkfLs:Rvܩyݪmڣn٢kנi֞gԝeқbј_ϖ]͔ZʑWȏTƌQĉM‡^ǒr͟s̞3fs:s:s:N M L \^ɓ`ʔdʗҩҬЩͧ˥~ɣ|ƠxÞvroczCs:!Xlՠxݪtۦmءl֠i՞gӜdљbϗ_͕\˒YɐVǍWǍpΞuΠo˜2es:s:s:BN M LYQŠ`ʔaȔyϣ԰ҮЫͩ˧Ȥ}ơzÞviR| x@s: DONJxܩxڨj֟[ѕWΒcљdЙaΖ[˒\ʓ^ʔpϟtϡ\ÎMs:s:s:cN- M LN9v_ɔ`ȓ^ő^ÑfĔwɠΩzǡoj[][*[r:s:s:+cmҟw٧wاwצnԡkҝnӟsӢuңtѢrΟuFzK~Fx 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:??image_LICENSE.txt000066400000000000000000000005331406265421000366700ustar00rootroot00000000000000envisage-6.0.1/examples/legacy/plugins/workbench/AcmeLabUsingEggs/src/acme.acmelab/acme/acmelab/images filename source --------------------------------------------------- about.png Wikipedia: This image is in the public domain because it contains materials that originally came from the United States Central Intelligence Agency's World Factbook. acmelab.ico Gael Varoquaux splash.jpg Bryce Hendrix splash.jpg000066400000000000000000001112071406265421000357000ustar00rootroot00000000000000envisage-6.0.1/examples/legacy/plugins/workbench/AcmeLabUsingEggs/src/acme.acmelab/acme/acmelab/imagesJFIFHH1ExifII* z(2i< CanonCanon PowerShot SD630HH2008:10:02 11:07:424<0220DXl t|  X |J 0100,    8 2006:08:31 06:38:042006:08:31 06:38:04? _ ." NZt(C   h J\E"F\@C_ Dm?;IMG:PowerShot SD630 JPEGFirmware Version 1.005)) 5^ sM|Y\]Nr;rYI[''`E uwxtwyx% @0111 '*: R980100 @  ( JFIFC    $.' ",#(7),01444'9=82<.342C  2!!22222222222222222222222222222222222222222222222222" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?XeUjp)D ?S0i*{T0RbZvړm&)C1I~LQpB*Mp"".)Qp)1RIw EJE4.+HEHE!DE4B(jZiZb*2T,qd"V sE:F*TҔ(8qڸΡW2ނ[ijK#t\QRzHLbLS31Iw &)1N#4n(M""w2T"4B),BE!))V!+QU)hU)1^hqjvs\rx0w|$t$_7t汿2;ylT y`*aTq^rĖ9>xk5X]Da)QM*Am}~d2qf!\U\V.!)1KINⰘ-!pN4MiqXB)S4p)S!)S%;yEc3@ jft$g'nTRTC~ƹѧ+5KѺ"gP>bin5IܖKΌlۈ>o8 wVq]D$TGLk+@%/B&@3Io=Veº-Oe:ari m?~t豚LA=S\??:P}꟝!z/LDf7E|_΁\ijOM2@\&j/5?:O0zb%&=EqK`XrNqVXH܌J9.A=H! $' A!?C\A,HI*ZͱH}U[2N隦]3҄KlduxJv=k7r{Տ:Gn3UK78}nVNιa BԒ] ?ZQGLXz\OB6uK2=93թ#7k?ޣ8.IO`D hTw$ |V94!ZW>>r>_žiES+zO1MX]: o!<)N|lyi^rO,?#` EO9|4I>) OEwM'9qҋ/'bqIj6'=8sSߝ\=? (6RŊc̒-':5[[ʥ?ZCm)-޸Σ8Kvyk0'*VU.<87} P!bNx7siЊ$?ZD#*,7˜zoг[_?;G?-DH: Zw`%$ǖބƅ^\–upPK9l/o!<Σ'<׆r\[r|O»a*c;z- ؾЙ*F$R@]+MXV0p5.!"P~U.(-XbpH2R6>e-y58ӄp0b3³m0"7?${w)R}:FLEo(`E۰3퉀(hkh'TpZV o3s7i\BdsjnRv.J)}h DR?M.Ĥ3ڢ%P@|  T,f B rF1I(81lP"9buY%ܵl }PP9%1cOQNC!`U:|1持L#nTzsP2̣zLFt7>EOHM1\twJB+ccwZb=Jc|2^w٭"8?ZR LTdVFkDٓ)-6V.`'=抮uiE;1]G_ `~dzƟ$RPpF_1N6JմmIHˏT>n=܀/x p=kOToc"y}+9lʨis5:orymY.z5 ;)'"6cVPˏ0qDJ>;~f_5]x H HOJ} `8Kuϝmm ~tE=(-H0) OƆJ,>@|A{p<6bm ´hG;@P.zr1P&>l( (M N@>HXnЌ40HiDtQ tʀD\g-EŘ9Rc"f;xT%͞Ā* ;]F!6;W7%Rۜ ;T@ %!ڝ|gB<*,ŃE zs:` 8Ce'>"4"6Cd!4'B<2)؁`sbP9" (lL|(B`]1zP mhMTM M1 |˜w@QsM"& "?Z8qR4w GfBysJz~5%#=ϥZNLCR:f$~83TwqIwmI_ɟJIM1+Ҹ)ix}`JISh2 zЙ0i84tB91H) jnZM3M\PJm2D0?:l(A'N Qbf5VMxqCf#淨;~ X#&91C"v\|(hT`qkSpFGʖњf. @(9])1$qbmȲ(#vE k|Sm6*ќt!8N(z> 0Iz,(C ry,[(3yɃ@u+L@g’c1υ$h%6o@P#:PJmL4yйSzc4i&w ˶(l 'ʂc4&dSgU& & אzzSiHتD.|J6kK"5tH3E[c 4kvJ"DYJ kvAuNpzI;AQ\co:,@\li2*+քS2DxzaB9s*.E]6\xQ` …݁N#4M)1ʝ zuߥ 4L %ddžM݀\l˽ !܊`J&vhLfb; &w=)ܽkʣFy ֨>H {H, l>}*hvVIa76!y+F;ؼ}bpA@ M6^]D2lg&~&#ʒGʨBzPqL@[nqbb|:Pߥ1cʞʆW"e΂A Y@?  4]-7a1PmAdP:T)  P$Ӱcv\@9s:O C D8ޙL4 l{ApZBuHyJrd Lmx+Op:Ҽkg_ŭάS"W;w Di7 <#"$ͷ\c:i6OԎvuw7enJ,P WmKG0CHmN]OfnSx[qgDg~նjɪgD_eg u!i0 ޿ Jb͟$ P$޸f1-d4sH-iHBl&;;P4 u1Azdc6vքI1yv'9phb3`L@ [2ؠB M9Z`y–zn6BlQZ^-0pOZV{!H(^U&$ʫB) )sr6…Bn/hrn h X]1p2vs}x`{y9@,06{*2qv&=ͽ!fu_l8}k5g >h\I|0iyYrX Gy\vd'#1k#'˭ReHƆ^tYV<(@mnF2v>U5ktP\qCjkJoMXݧ/KVrFEY="7HŤfC+6>Ƭ*F}3~uziM cB%HBLƄe{P ބ[ @i78@ZA#'0d\@ZO*?0`h xPo`ݥcwzj6iw H A4ɵGi⽍Zİ:ܼ`wsrjX:Oݬl |+rGٷӖp;DH|?swKiZ'D}żybxÎe?CLC{SM5hܐG_SOn3s1ʞsgl(.Ss35jYW;t,$+d`=kf٥}"]3Qԭ2c=l3*Qt':R6%`R{#5'P91 2q'4 d ˱9i#&(&\ZboM_*jbgڛ<CwM\S$&yrzMBiF4źxWz.+<YGttHi-qenÔͅukt]3fQU!x;ex4,=#zvr#ݧ(f@)˘ՄvOhK X)UM&b`E(8'$r AEX6Qu5 >b&zQN ;E}69~T&OvQz0}Zqm:ay94uz77>&$nuqhCgύ\ ` 6i6 f=Z`1 Iӟ0>L{/-_x Q)%au~U㞁qazĖdq<]mr<"#I,:o!XFflDHB_4n&Ṵay8?[yhx&Kyy4\z y;[Cj|o_,Ծp:%!WR}0o-Р=EvEi5]qo"(ӕFƧ?wxuUQ&1>^?E+ -NP`cN8X,X`:jӏnlgia2w; I>9+n폞-Oۆ@`GSֿEێ#)7źAaeTl%HF|i{[Nה+g>(T/_4ж=5"J0oK#>*zӵj;;恝 LQS8ힵ6$Pj7Q"@;n߈_YmqZx]OW(PŮT/z,Y$P+",d||<6!ٽ4 "K9DžsgWx#\hws:NQ(랣m۩۵XY݇4k'?QqYql ݤ iԂ3qM%}dPzخGH+TȦr7=w|ݰErpWB{tb2]s <7WE'6d]kx$v((`9e;i {`❀F@䑝qi1||N.R:d`q9ӹ_C#rss,.T##!؏u?|6h80f(\`L";8DWY<8;?:4k,? \йڀͪFF'3}U$8`=Geٕspf䞦y *#rvW0W%ۚng!#볫OΣP,/ɥ`sܠ푝<#@`y♼l04i0,lvd[6 X~^^_אz`]0`Rʯqܦ :N;Y$tdQiEl8 3;tyl-| & E2w/EYI y3ikPBSB_yY$>CZ%D7gޛh fÌՖ=N[=&>nRo֞Fؿ0sОAp1 m|uvW7Rj:DIunL$l!*yytQY͹4ǔTYƵi[R,2IUnt>&xwuZq_zGQ˶?>h׺ts M䄜x '\6CT*@FOGO$ 9 $N--c.ޔ#Xb 4R/Z1K#s\u>WwTu84*[YMxJ<֔3l)L(HB>8 I0 ' n> يQX/!?n!0&>t'̶)v8OҀsm*Ġ%$ٜ1G !i]Ѐf4iu63@'p'Fp¿\@cy"F"(#8iܯ|~c; Ε,U|Zyۤ [J7FyN HuDө3Wg 7#R/UA-QFeUZ@^H#/1d$O_:ĻA]3u[d~u>11b/D ^k$gn쾯>sh';-b֤ԓ=+1Y=֑dn b*җwkd$G׿˽}K-n$-Vk&# 3@⛦~X]4 j<%l'k$u^M~#jigoj0gce%-y7?J"`FcuյfJ2{8R;u8֙&]]C,0\र۩\+h?u?ߍ1k>y3%33Uh@./"+k#ܜc}eP 2E €.w;:C:CfKF\ه m64cbI#GMHAg @9AaG/IYYFܡ2)=R?UDYr PyT䪲Zܻʐoa}kթihrh7}P̬sy/y- ws[vBMU M6/ iR B9Ðxd-[4P%9$nr}0hI=Ihu۵[ݫjζ]1c;yVQ7jF>iAkdE@S>;zuD4fWQD_#y)ÅK j'Pt~蹑J0U&@`<юa-Le{yhb[R9XLw UB3?KRH߆)Ů+܎xX=3q6,?Ys"&{۩;$ԥ˛ 3`?:D.TP4Kdgt0du؆ڀ/o6ryQSk!Eܞʝ'gaqzR9ăxy՟unиNAvgmPve5^$K%7.~;*v]s<O}C ҳ#Ke_NΓwbXu0c Ki7pq~fv[hg~υMQ/3m"B>m5m$z=w ޙ#\ 13 |ݔv9cm:iB^>yt{MkwR2rz0qKL%vKR>ǣ}"'UuۙeKm2%d^dWFϭ,=XEEG'\;rjFmİ \G&ezUUdN5x#^rlFW)źvz%@4y~<䓟< L^۲*ӻMBR?fҞ.q©`wF$quo&wK~_!=6RiNNJɎvkà8 x.$_^|rb"EP#ߡ-ji,;X$DzY Nu1)_pz)|v㟕58AIDzwi0$prlA"997SW5od^5}XHc:ּ|Mrucnֻ'b˕6o(:tcl濧[MOrrFq oI<6|N(8FXİp+WuҨ0)g.0܏?o!^ ee8M=Н?; .5Y&<(,Q>P( Y$3˃mM׋n*j&xgFe%-<$ Qnka{03s;23']}1uǷ*i15IꭐMqTHWKJ뽊jQˆSziwQJFݝxtt-AyP?AhCai@Iu4!Pc \t5hY>pH 'p%q#F*#PAsY,8"Tc8 O\35NQL:f A8̌r ^_O50Vl`$>| 9,uު35eD{tߩ_&nkXXw9sNrrzʒ]p;#Iq,; \;+#UY,)EZdU txw6ܧQ]ӎ4 gM+۫}.w27$yI88e/m%;o+ j5`j2Oz49bH8)л=n= ]GF2[9m&9@lɲ ޭ_gm頞] =ߗ~타-QS۫oo{yI_P RVd®f5 Nӯ46cʥ\-_$_`d}-6 X^GpXoFȥ T d<ş#ʨ-_˨#E,j;tZ2ltѨ(+;w[gѿL*VUVt87Rr،T$»B˷?2U".6DY?U[+mDs[ܫ6BtKR\鷋+^_{3q [kG,H,qWQ N8k>.YXgktydLүo DWe;dl8_>C[i?u'彻zՎMV#n{K}rEI!G[Xh1o?:sv] F)#t>}1Z՘Y- 'rC|*yI1ac*:G0;ӹ4D?.7[jbp٠>k:Z>2Eap7,!lc㊲𞩩[jQ;19wyN=4wNW < ҲNT7R$~ Xr1=FְRM٥:RY^NK hx;U't3˷uwVVlOP^݄G7 zyՏL*B91+&]ЉPӮD M F1Mӯ^? g\M,j,ܪdd$ۻK0H dn}~8˪ vۅb {@WGI*[{H@9NT0Trnz趓X=G4UZ!Lu@do-7jg(v-vܶjM4I)́=LYlwH:#U-zRה㕶?Ji.6Um/n%Yd!j_iZ*'XgWRN5\O؎fK[/Tz`tYO#H㍍n,7NƴVJ̲}X Z@ggLdȈsxޞX[X)Y{ivP3avֲk@ V~r))/3 thݺ2:e|O-KֺJ?)P}9}~Yqw%q78Tx,?{7~f[;=^ uբʉ]/|\H2BAvîFsNZ*gjl:a~T6g ̍"zlH*{Mtw[=Ą1ힿ5u}]sm+E;P4KɕzWi}z AipѶ r̸#mlL6\u+,b- bZjS,)*2QpNږO4a#% }OB/ua+6BA6eRtv#.xE2ks%Hl#FoQ]z\Ѵtݙq݌{sԍkmQZheko曻j~"U]71pv064ۖ7'_evZ4z[jvzjMa*0Tm<+ƏnځطUe.pZ$ ҿ3s̋'*`WuVV=x;Q+[؋I4У9%-㍼: h<2j) B]WM3sjI4mG웑"up7>Few&3gd~5ĽE [YѳUrއcBhqZp-y$b{mV "+o@u42 1u=IL.9p͹-gk#$ 2GU^ %:l&w(G?NQjtatIs\}Z VSaqR5gMGc mlMMh_Ix3#)x2d]Zrx͸5^qŗ:9GM:uMZ=ޔ۲b|0VkJ+Z}Ey-,#*]ܙ"]'_٥&NUu@--I'SFrWϯʭZ /i72B /~U-(>.!;#lU[K &1=:| %,itW1Nn Kon1Qu֣* 6 :ݱpC?9j=e (^ʩcr D|(GG#>@SL`[!718uV%2n|{F<5H`vѩA,($V_LvI9#RɔqKլdb$r~(kiSHjjUI5ȷ~hۼSvR]1LHI@^6ӳ!2!߼ 09PSHtuhA-Yy'V@Kr|Zd %#ͷ|꣢ÚTJ2NU Ϋ0%.Ŗ-ӆK1u˓$O268$0G|MDKE>B>ZY܎lkjcqO*P,˘WjovJ$L3EV.8 u+qnp!!mejd{h~aͿEzgGWdl5Ƹ\4]r T0 g3v.4hR=3ZCg\t_t=S ʑEy֬V5 ZT{Ր+a `x>\e@c:t{C+Knm:f}Akjm$h(XrmNiem4W-"ȸbNJSYѹ`\0GޅdIZ@?χFV,' p@\@ZF{ -^s;Ox ӈ4b Zd,ȇe\GZoCX3/nbn= j\e}{9<9 uYZHyr=\![TCql҉\t8veK&R:N$ps*hcsIu։*YlS [q -m&-"չI[9Sf)%=TP/#ȭV#6 = I9} :ΩCnXŒw+)dIxe. kwH@v>9߻-tG~H8eP>93P妡1e<"\#G$[U7q[J_OUsPPǁեvVצ>+{)cŒǠ'|Ze't+6Q$žqr`TCHqN^b[IFb^l0@lACi}@;bA;u장z[zVNx1G]9wbn2rycUYz&J{Y0x;;8+ŷ+o#0,Rɷ7={gXYn5lnxv)b`T`fiJKOggԷg7i<-ogZYzӡs4řv9WL}؜~}>ÈuvKD9r3ZG;vh:?k{CiDD$IsOXH"0i џu[O879m3QR0W\TI!sWw1{<>nqd;.~%nu-ԯ$acsgY׏"S3+Znʗ)t qsC2OV[䑝(o3\DXMr90MsU <d=ԙ#))]ze:ǵ KM3X/8&m?]Q\P6V벞u}f g[mI9DͦFD^'V_? g)8+5.}MG!r|[èk..] 3<@kI5VKd7DO}# <Y8c;TܞQT*{j0i7d=0?SQsHwp:tυEC,+ImMuF"`q¨ZԜ)`;'UhZygƲ.'U^;ŴۤH{}ALJj#Dr[)[q遃JN2լQWЪ&LF~Z>CX#;tǞrǃu?~O12[Dz˩3/23~#; ;"4I/nHP 1do!v铹#<0z=sP, [U-&>G?CO'I,4uAs<-j"{y3ة ʣc6P3N3'4W3F')<`wPziZzUيݫSսeN%x=5:1Q) p#Ѱ6ߺrSu°UY $sX܉bħ0v?Jiķ-Qda=vckQXgQΣ|j+e=噈'7㊦wmqlqwɓoN\{-S-'KimHf?WughH ;է{3qi5L  *Ũ-H#A3ϔO(L ir9#ʲRC.Dȏz ;~KyZB<<Ӊ;E֘#)L=SBS#o3W 61=aV;Y2 a|m֊'qC$, 0G/(#5' ]3# q铵CFD e-E$QHU7+#?g"+n4oB1zӌ.>9eVz<2"s@#XW|t-rv|vηJ6S낻wYn!nl_$4 `GF5=FV.xJ26Qb:A+TgR{]zao҈%kR\!S̱ q }6xUm3ңN-MB!'=1H̃8bnATO8~<։W壐: i+5ΖL;Kn'r1қV3[r$$&hQjz<(ge8\s5V]HRp,@ ֭;^TKesd<NEreGKs/6 Jϐ3P\Ķm:ƅbp|N#o )Oֲg<-sj(ݐPIUa}zzdӉ[vPs1َ1CU NB8H2.*f#foajG؏g@mi|ƒ[+$dS\ ۆU^5O'NEiiJI$FF,12ydӠ?gmtmqChtEب6 P[pM"e ;QAdr׳ʪsh&eg(ːEf:V'y2;eh|ԓ 2ݞ'J2X[bH#~auj# DK rFaсUHk=j+Xmc+dcxxU.LPvp| IћVeazdPNved|3O,t# mv2) UhJIGŪBī CpgG2Fղ? ڂ3}UMGCmr\Y:td*䄖GZ;qܐ! Ȏif_tp6/ g@jaF#,Ta6JMz|rWg+. zc.t$UdHVO4Ҵ_d2]}? >g|V)hXղEtϕo(CQafY <Luw +ImR֯ ,i5L+jQ!Kb_ujG_N8Q_iTv ؏6vsOuKa094OTNw TlLzo3X VNd(d Nbո|iXld+8-VhWeN(Pqޝ8YLr~nQӻQH\1{{WVV1XN7 e8~Ma+F[=Ym4o3sll%qtѳ`/2d65A pg-В;FGLXCOšt=.. 8ۜ瘱zyjyfͮm.Zݐ FIٛJ&yvf80>*hTaX9$E, Qe4{=FI"o*QV$F.6 *2ϟC{["j ~b励iWl&vWlңKTyM:2qْ+e1 n>ߕuG}R?}j(Kw@oàgj2"%ʏ;TԤmJ̜C$I>e:&yrYy=ܟvFJK9Q*Y`y2XJO]ǡ?ߍ&I ;챑ܱ0#o;qVVw(g[E\u{]%rAn]2pTr`-+46[Tڮ+XF 8Fxf~4imp Cd}+>6J%N =c?y*vju/k;MXy$Cȑ8`7&ݿ[Gƫ3;1?xOZ=Ir+(- FwVVQ3I J|!ⵋ8 01A)UpK(=pqcֽ49R)S)wj"l7] -3YP ̎rZ2mS2C\g&'5Un9x.bN9:d5ċxAs"A~TI83DIr4aǹ哏ʜZ+:ǁʾFL]\Wr<=jKc&]@Ke8s㍩7wVq \\$ŗۡ ^MHbp2dْx05BM .bi ϶R"5ˌ{}RrIdS k?k٦ɧSYu<D9+)#1O_wǠW >7k^H;+[ K;Krc=۸&ScY_{zfL1 ρ#onJfhQ/ղ|q]+mÊhٗ1\1QߑQ_CUI"Z1C<7R4zy5,.si XW)(>x½݃.Ӆ"4lePŒ̡F9Q_*9:/~ 䆧xe;iE}(35iR%eBMnVYc!o|½Q -S&V:N@۔U#WLGew纎؅;S񯥮8^%lnm^'A& W=lͥpJ r3iA+"nG2Z.1pq S8 @'>`r=HI<d*үG,vg836Yr gW* HuІ'V!I]AL^X]sŸ^_ZqW.:JP)__WEOoo1<G7֑ϥ9Fp?_֛lݯ]Tﺐ 6l gu^Vã_voSP.ZI12enFrI>0SrX`A8tvOh  NVzjsj >Q_?98ƌRRkǒu"2;(|+1B`ʏ"tI*6ԸsOe:\6ỹ%;U^bNN7 S\DU6 DAuI?/ΥnYۨ6"U c&5͆k{H叛%H'`pz3RjOsY'[[aG<=`Y|g~rrBv7bI#~X_"C xm銯p[I6RtK}Hf6[n%V@rɏu[sQ<;5k˗AU2Jk?SO<*lѦ& ;n 3MѻD}+kz|3k-ԄuP`d6$es}'L%mR(G[~D| zm[F~Al,̂Vs*o$`'.O*\(1'/G-՜D9G|&[Namm&A$uSX*\/fѤa< d % PF֐rԏ;f]WWcFbH.#<f\gó3$kUeVЇ-8q '7Lq>=GaΪzA}qef{ q<vֺRٴ+y/3ѭLܓʅX *kFT]{yg (1 ʤ/ɷ0H^@7N漪=aZdڴf{aNSyjçZ̡%+2yֆE0G,r$03q,u*2mB$G#InK}`BV|0irwL`U3X;&ʗhV%ս15cjm4ӄ&9dt889*Ni==X9 m&1k*XjdkO]J2:X$4XXY+ǭynLpDvL_|O֮G-:s KӮ=j[c̚ڸa% &SS0g|c^ŁƝѪޕ@LF`L<I vP1ͦ% 1*)cS |3Rka%)e*dmMҤdp6wRG,RF< oy}굚}CW3X;l~GOΚn9ZDx]g>"M:^Uդ=:*G>###ux~݁ vFxWDmfã-jmmVN\s6/, E$T"U-sxR%q@ Ywݑ5r61~y5pcƗw<%wVq\e݃Dܹ洹 Dc0kҾE:lOq<\(Ụ> rVMov 8w^"Ԧ=oU%,P h6* PXu_EbkѸh{0$<#1#Ql!tG{%IopcBz? vF39D2<}}*xCh5AOZ+$Ɲdݶv;9noR6RF'DUhs<鍾$.xY AE/[fukd7#ިeD۳DeGFK.̉RP+h~B}AP`{ˋt{{̐.zm .|\_,f^S c| =N:QkH4b#L>`xSKdhԬlLvwR}tj jSs4@9~a䳆($ Vgf=ɹk0 )*C;ՏHNi\w`rEjo?xY䍙 }bau9>?hF-3Ƌ.n{H#QBw-eF#ycj+ǽ1u($AMSJ{7OL1Z6M1JHI愵gM{?cVR][b6}2ݖhe#8qUte4avqi|yx"/86&n.oY9]|&PX2.&{V+Q! X:XQm J[frѢvGMWS <)`NbʣmjəR4Wӣ(Vl/=zE4##IFځ+WČ%}s]훲6.-).aE'8W$^lIvM͸Oo%R%1HG0o·m۴(L7 , ͗ p00r6\I F?i-혵H$dBAOA_Jmtȵ.LՇ6=j.lH*EZK-Bۑq? 1_g_gnjzVA[ﭴ[kx>drNIxd%~Zֵ7WKlݼF gШӥv -_ʨq@3p,rqjMæwPҢI3Y m,.WٮGoEwii1V HcĈ{"Y&p|@ MANsz75ln]fvcE6n1!f dҀ+\O " X&B̋>:#OCԔNUf|({&No:{+)Cq+R[}~ȒV;HGvsD,a\EiBIenvisage-6.0.1/examples/legacy/plugins/workbench/AcmeLabUsingEggs/src/acme.acmelab/setup.py000066400000000000000000000005261406265421000317400ustar00rootroot00000000000000# Major package imports. from setuptools import setup, find_packages setup( name="acme.acmelab", version="0.1a1", author="Enthought, Inc", author_email="info@enthought.com", license="BSD", zip_safe=False, packages=find_packages(), include_package_data=True, namespace_packages=["acme", "acme.acmelab"], ) envisage-6.0.1/examples/legacy/plugins/workbench/AcmeLabUsingEggs/src/acme.workbench/000077500000000000000000000000001406265421000306215ustar00rootroot00000000000000envisage-6.0.1/examples/legacy/plugins/workbench/AcmeLabUsingEggs/src/acme.workbench/acme/000077500000000000000000000000001406265421000315265ustar00rootroot00000000000000__init__.py000066400000000000000000000000771406265421000335640ustar00rootroot00000000000000envisage-6.0.1/examples/legacy/plugins/workbench/AcmeLabUsingEggs/src/acme.workbench/acme# Copyright (c) 2007 by Enthought, Inc. # All rights reserved. envisage-6.0.1/examples/legacy/plugins/workbench/AcmeLabUsingEggs/src/acme.workbench/acme/workbench/000077500000000000000000000000001406265421000335105ustar00rootroot00000000000000__init__.py000066400000000000000000000000771406265421000355460ustar00rootroot00000000000000envisage-6.0.1/examples/legacy/plugins/workbench/AcmeLabUsingEggs/src/acme.workbench/acme/workbench# Copyright (c) 2007 by Enthought, Inc. # All rights reserved. acme_preferences_page.py000066400000000000000000000025041406265421000402660ustar00rootroot00000000000000envisage-6.0.1/examples/legacy/plugins/workbench/AcmeLabUsingEggs/src/acme.workbench/acme/workbench""" The preferences for the Acme workbench. """ # Enthought library imports. from apptools.preferences.ui.api import PreferencesPage from traits.api import Bool, Color, Int, Float, Font, Str from traitsui.api import View class AcmePreferencesPage(PreferencesPage): """ The preferences page for the Acme workbench. """ #### 'PreferencesPage' interface ########################################## # The page's category (e.g. 'General/Appearance'). The empty string means # that this is a top-level page. category = "General" # 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 = "Acme" # The path to the preference node that contains the preferences. preferences_path = "acme.workbench" #### Preferences ########################################################## # Width. width = Int(100) # Height. height = Int(200) # Ratio. ratio = Float(0.1) # Background color. bgcolor = Color("red") # Text font. font = Font("helvetica") #### Traits UI views ###################################################### trait_view = View("width", "height", "ratio", "font", "bgcolor") acme_workbench_plugin.py000066400000000000000000000037121406265421000403330ustar00rootroot00000000000000envisage-6.0.1/examples/legacy/plugins/workbench/AcmeLabUsingEggs/src/acme.workbench/acme/workbench""" The AcmeLab Workbench plugin. """ # Enthought library imports. from envisage.api import Plugin from traits.api import List class AcmeWorkbenchPlugin(Plugin): """ The AcmeLab Workbench plugin. This plugin is part of the 'AcmeLab' example application. """ # Extension points Ids. ACTION_SETS = "envisage.ui.workbench.action_sets" PERSPECTIVES = "envisage.ui.workbench.perspectives" PREFERENCES_PAGES = "envisage.ui.workbench.preferences_pages" VIEWS = "envisage.ui.workbench.views" #### 'IPlugin' interface ################################################## # The plugin's unique identifier. id = "acme.workbench" # The plugin's name (suitable for displaying to the user). name = "Acme Workbench" #### Contributions to extension points made by this plugin ################ # Action sets. action_sets = List(contributes_to=ACTION_SETS) def _action_sets_default(self): """ Trait initializer. """ from test_action_set import TestActionSet return [TestActionSet] # Perspectives. perspectives = List(contributes_to=PERSPECTIVES) def _perspectives_default(self): """ Trait initializer. """ from acme.workbench.perspective.api import FooPerspective from acme.workbench.perspective.api import BarPerspective return [FooPerspective, BarPerspective] # Preferences pages. preferences_pages = List(contributes_to=PREFERENCES_PAGES) def _preferences_pages_default(self): """ Trait initializer. """ from acme_preferences_page import AcmePreferencesPage return [AcmePreferencesPage] # Views. views = List(contributes_to=VIEWS) def _views_default(self): """ Trait initializer. """ from acme.workbench.view.api import BlackView, BlueView, GreenView from acme.workbench.view.api import RedView, YellowView return [BlackView, BlueView, GreenView, RedView, YellowView] perspective/000077500000000000000000000000001406265421000357625ustar00rootroot00000000000000envisage-6.0.1/examples/legacy/plugins/workbench/AcmeLabUsingEggs/src/acme.workbench/acme/workbench__init__.py000066400000000000000000000000001406265421000400610ustar00rootroot00000000000000envisage-6.0.1/examples/legacy/plugins/workbench/AcmeLabUsingEggs/src/acme.workbench/acme/workbench/perspectiveapi.py000066400000000000000000000001261406265421000371040ustar00rootroot00000000000000envisage-6.0.1/examples/legacy/plugins/workbench/AcmeLabUsingEggs/src/acme.workbench/acme/workbench/perspectivefrom bar_perspective import BarPerspective from foo_perspective import FooPerspective bar_perspective.py000066400000000000000000000010001406265421000415000ustar00rootroot00000000000000envisage-6.0.1/examples/legacy/plugins/workbench/AcmeLabUsingEggs/src/acme.workbench/acme/workbench/perspective""" An example perspective. """ # Enthought library imports. from pyface.workbench.api import Perspective, PerspectiveItem class BarPerspective(Perspective): """ An example perspective. """ # The perspective's name. name = "Bar" # Should the editor area be shown in this perspective? show_editor_area = False # The contents of the perspective. contents = [ PerspectiveItem(id="Green"), PerspectiveItem(id="Black", position="bottom", relative_to="Green"), ] foo_perspective.py000066400000000000000000000010771406265421000415350ustar00rootroot00000000000000envisage-6.0.1/examples/legacy/plugins/workbench/AcmeLabUsingEggs/src/acme.workbench/acme/workbench/perspective""" An example perspective. """ # Enthought library imports. from pyface.workbench.api import Perspective, PerspectiveItem class FooPerspective(Perspective): """ An example perspective. """ # The perspective's name. name = "Foo" # Should the editor area be shown in this perspective? show_editor_area = True # The contents of the perspective. contents = [ PerspectiveItem(id="Blue", position="left"), PerspectiveItem(id="Red", position="with", relative_to="Blue"), PerspectiveItem(id="Green", position="top"), ] test_action_set.py000066400000000000000000000043071406265421000371760ustar00rootroot00000000000000envisage-6.0.1/examples/legacy/plugins/workbench/AcmeLabUsingEggs/src/acme.workbench/acme/workbench""" A test action set. """ # Enthought library imports. from envisage.ui.action.api import Action, Group, Menu, ToolBar from envisage.ui.workbench.api import WorkbenchActionSet class TestActionSet(WorkbenchActionSet): """ An action test useful for testing. """ #### 'ActionSet' interface ################################################ # The action set's globally unique identifier. id = "envisage.ui.workbench.test" menus = [ Menu(name="&Test", path="MenuBar", groups=["XGroup", "YGroup"]), Menu(name="Foo", path="MenuBar/Test", groups=["XGroup", "YGroup"]), Menu(name="Bar", path="MenuBar/Test", groups=["XGroup", "YGroup"]), ] groups = [Group(id="Fred", path="MenuBar/Test")] tool_bars = [ ToolBar(name="Fred", groups=["AToolBarGroup"]), ToolBar(name="Wilma"), ToolBar(name="Barney"), ] actions = [ Action( path="MenuBar/Test", group="Fred", class_name="envisage.ui.workbench.action.api:AboutAction", ), Action( path="ToolBar", class_name="envisage.ui.workbench.action.api:AboutAction", ), Action( path="ToolBar", class_name="envisage.ui.workbench.action.api:ExitAction", ), Action( path="ToolBar/Fred", group="AToolBarGroup", class_name="envisage.ui.workbench.action.api:AboutAction", ), Action( path="ToolBar/Wilma", class_name="envisage.ui.workbench.action.api:AboutAction", ), Action( path="ToolBar/Barney", class_name="envisage.ui.workbench.action.api:ExitAction", ), ] #### 'WorkbenchActionSet' interface ####################################### # The Ids of the perspectives that the action set is enabled in. enabled_for_perspectives = ["Foo"] # The Ids of the perspectives that the action set is visible in. visible_for_perspectives = ["Foo", "Bar"] # The Ids of the views that the action set is enabled for. # enabled_for_views = ['Red'] # The Ids of the views that the action set is visible for. # visible_for_views = ['Red'] view/000077500000000000000000000000001406265421000344035ustar00rootroot00000000000000envisage-6.0.1/examples/legacy/plugins/workbench/AcmeLabUsingEggs/src/acme.workbench/acme/workbench__init__.py000066400000000000000000000000001406265421000365020ustar00rootroot00000000000000envisage-6.0.1/examples/legacy/plugins/workbench/AcmeLabUsingEggs/src/acme.workbench/acme/workbench/viewapi.py000066400000000000000000000002411406265421000355230ustar00rootroot00000000000000envisage-6.0.1/examples/legacy/plugins/workbench/AcmeLabUsingEggs/src/acme.workbench/acme/workbench/viewfrom black_view import BlackView from blue_view import BlueView from green_view import GreenView from red_view import RedView from yellow_view import YellowView black_view.py000066400000000000000000000006451406265421000370700ustar00rootroot00000000000000envisage-6.0.1/examples/legacy/plugins/workbench/AcmeLabUsingEggs/src/acme.workbench/acme/workbench/view""" A view containing a black panel! """ # Local imports. from color_view import ColorView class BlackView(ColorView): """ A view containing a black panel! """ #### 'IView' interface #################################################### # The view's name. name = "Black" # The default position of the view relative to the item specified in the # 'relative_to' trait. position = "bottom" blue_view.py000066400000000000000000000006411406265421000367370ustar00rootroot00000000000000envisage-6.0.1/examples/legacy/plugins/workbench/AcmeLabUsingEggs/src/acme.workbench/acme/workbench/view""" A view containing a blue panel! """ # Local imports. from color_view import ColorView class BlueView(ColorView): """ A view containing a blue panel! """ #### 'IView' interface #################################################### # The view's name. name = "Blue" # The default position of the view relative to the item specified in the # 'relative_to' trait. position = "bottom" color_view.py000066400000000000000000000046341406265421000371340ustar00rootroot00000000000000envisage-6.0.1/examples/legacy/plugins/workbench/AcmeLabUsingEggs/src/acme.workbench/acme/workbench/view""" A view containing a colored panel! """ # Enthought library imports. from traits.etsconfig.api import ETSConfig from pyface.workbench.api import View class ColorView(View): """ A view containing a colored panel! This view is written so that it works with *both* wx and Qt4. Your own views obviously do not have to do this! """ #### 'IView' interface #################################################### # The category that the view belongs to. category = "Color" ########################################################################### # 'IWorkbenchPart' interface. ########################################################################### #### Trait initializers ################################################### def _id_default(self): """ Trait initializer. """ # By making the Id the same as the name, we make it easy to specify # the views in the example perspectives. Note for larger applications # the Id should be globally unique, and by default we use the module # name and class name. return self.name #### Methods ############################################################## 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. """ method = getattr(self, "_%s_create_control" % ETSConfig.toolkit, None) if method is None: raise SystemError("Unknown toolkit %s", ETSConfig.toolkit) color = self.name.lower() return method(parent, color) ########################################################################### # Private interface. ########################################################################### def _wx_create_control(self, parent, color): """ Create a wx version of the control. """ import wx panel = wx.Panel(parent, -1) panel.SetBackgroundColour(color) return panel def _qt4_create_control(self, parent, color): """ Create a Qt4 version of the control. """ from pyface.qt import QtGui widget = QtGui.QWidget(parent) palette = widget.palette() palette.setColor(QtGui.QPalette.Window, QtGui.QColor(color)) widget.setPalette(palette) widget.setAutoFillBackground(True) return widget green_view.py000066400000000000000000000006451406265421000371140ustar00rootroot00000000000000envisage-6.0.1/examples/legacy/plugins/workbench/AcmeLabUsingEggs/src/acme.workbench/acme/workbench/view""" A view containing a green panel! """ # Local imports. from color_view import ColorView class GreenView(ColorView): """ A view containing a green panel! """ #### 'IView' interface #################################################### # The view's name. name = "Green" # The default position of the view relative to the item specified in the # 'relative_to' trait. position = "bottom" red_view.py000066400000000000000000000006351406265421000365650ustar00rootroot00000000000000envisage-6.0.1/examples/legacy/plugins/workbench/AcmeLabUsingEggs/src/acme.workbench/acme/workbench/view""" A view containing a red panel! """ # Local imports. from color_view import ColorView class RedView(ColorView): """ A view containing a red panel! """ #### 'IView' interface #################################################### # The view's name. name = "Red" # The default position of the view relative to the item specified in the # 'relative_to' trait. position = "bottom" yellow_view.py000066400000000000000000000006511406265421000373240ustar00rootroot00000000000000envisage-6.0.1/examples/legacy/plugins/workbench/AcmeLabUsingEggs/src/acme.workbench/acme/workbench/view""" A view containing a yellow panel! """ # Local imports. from color_view import ColorView class YellowView(ColorView): """ A view containing a yellow panel! """ #### 'IView' interface #################################################### # The view's name. name = "Yellow" # The default position of the view relative to the item specified in the # 'relative_to' trait. position = "bottom" envisage-6.0.1/examples/legacy/plugins/workbench/AcmeLabUsingEggs/src/acme.workbench/setup.py000066400000000000000000000007351406265421000323400ustar00rootroot00000000000000# Major package imports. from setuptools import setup, find_packages setup( name="acme.workbench", version="0.1a1", author="Enthought, Inc", author_email="info@enthought.com", license="BSD", zip_safe=False, packages=find_packages(), include_package_data=True, namespace_packages=["acme", "acme.workbench"], entry_points=""" [envisage.plugins] acme_workbench = acme.workbench.acme_workbench_plugin:AcmeWorkbenchPlugin """, ) envisage-6.0.1/examples/legacy/plugins/workbench/Lorenz/000077500000000000000000000000001406265421000233155ustar00rootroot00000000000000envisage-6.0.1/examples/legacy/plugins/workbench/Lorenz/acme/000077500000000000000000000000001406265421000242225ustar00rootroot00000000000000envisage-6.0.1/examples/legacy/plugins/workbench/Lorenz/acme/__init__.py000066400000000000000000000000771406265421000263370ustar00rootroot00000000000000# Copyright (c) 2007 by Enthought, Inc. # All rights reserved. envisage-6.0.1/examples/legacy/plugins/workbench/Lorenz/acme/lorenz/000077500000000000000000000000001406265421000255335ustar00rootroot00000000000000envisage-6.0.1/examples/legacy/plugins/workbench/Lorenz/acme/lorenz/__init__.py000066400000000000000000000000771406265421000276500ustar00rootroot00000000000000# Copyright (c) 2007 by Enthought, Inc. # All rights reserved. envisage-6.0.1/examples/legacy/plugins/workbench/Lorenz/acme/lorenz/api.py000066400000000000000000000000601406265421000266520ustar00rootroot00000000000000from lorenz import DataView, Plot2DView, Lorenz envisage-6.0.1/examples/legacy/plugins/workbench/Lorenz/acme/lorenz/images/000077500000000000000000000000001406265421000270005ustar00rootroot00000000000000envisage-6.0.1/examples/legacy/plugins/workbench/Lorenz/acme/lorenz/images/about.png000066400000000000000000000271701406265421000306270ustar00rootroot00000000000000PNG  IHDR,,SF0PLTEn:}~|CCClb;._ pHYs  tIME IDATx}{׹ 2&PjiuHp{bpALC2nrwʼn:B,qQ"3CQZ+6&R&EQ@]'q+$HsμΙɕ܁ ;|Lu;zӹvvwwwv}_7ݝ7v_'F$曺_߻KZ`?N^m껻gv/]ze$X:{wQ zg]BW;XXk?]z1vdXww_xi~vi v/|^$T(!Xv]5䔲{ ATE[[|^~~=LZK'h4%n)LAd% U]6|팳 )VUG"rH D]$?]yaA2RnZBOOi 1,2g<-X^8j1UBJlɍA؀7c8kS|~a\D7|6LC˻ o:̚C97e9\pbw HNmkEWO5-Vj°ڢF1ubHs'ú3xEIC~HZi\we9 8a o-McOڠ4ahGv8P߹ذ޻jhZWȬK4,yˬ:Iڬ׏ kd ,iXV&xz^aq[Xz??f_xzde;RjPIodXn JyK ||Lm*~M0w/<d[/xӤ?0 4Xy81wG*i&  &%͚(֧6#úI.MQ!_,JBu\kWƖnӺðV\@OG9}Sm+Gr)):. !ಫ5u8N.,EM00^ܤ76p_*zh^%|V\͑?2{NaP0r鰺>>6n]B|Mɘ' da~Flq:QF}v(ikJpRZ5Hg79s*՟@3"Y"j{QhiKE6}ܰDS %U5M C/|xכzS[ BOS8?ڶP~#~D9a(|=O{eQ^g!,- L s١i}/}: #PU͹RY&ډss: iwꆔd\y KC&B8^w:ڶ|?ՈxW LVd?y`0g$HN3]}aN=0c*AƧuTs,MOVȄEǵٰ]_ p5P8u G01 b3ɾkvzkGMVĤ7N2dq=sa?7 +@Z$s>>& taE$_`e0d~Ibxy:L%!--a5I^z+qSȄPatC<^1й`r 2 K`2LgzZ\K&NyZ0=D).'Pwh%~oJyz(cHx4O yN` Q 5>c~<L7a5[cp]!P Ԑ݋xǯ'º>[_fI;ALC:O+٥426-,/D挩8`hyZ dMS85Cez]Ʒ8~B!1)AbmC VǗiOLv"bAYZK~u&p"FU@k6E8{ C=+źVխ iX'YOjx;W(qS_ABh$Ulds L@!5Gbq` rUϡ*EަHױ+O9*f+u-Jd6.5qK#?u=D~6#j)!fo nKT`䆯\gHX? #NR|u؝f04MOp_,_Iu qd#^I35R+j |jva lK $(0v/ͤa ǴVZ/U$ ߯Gt9 f.qq/e 2æQI:whRbr30BqbM^5A*Z)> sID];>,>0RX|`‡Ơ1\/U PS-b(XB0T1(\nP+gʖ3@$z`Έi.XX$!C7)>ti רDC-!2b5Bq)!}rR4Q1aNyp*m֙K qM+pNPԸ,ciCmϧr-.G{b^rl (LIyL<3]O!@XPX_i"2zF,!WH!%q,-0uj?c\0J s1s=1s}l߇4 "ԋ(z4btqVWd"!y:):X C4oSDu`=Hj8A8FYe %1lA5 X&U-I#?1-䙬/rȴp =0&#elbL!MCZ+c_MNVO0rXmZE@ҐIxbXPuGzW%\.hؔ]"tp_nNb8'.9Ά>?VBȴLªƸ3{$'8a}%M(e78 Gg 4+ 4A dbY,Ϊ:7cU̕8!w+BE4ƅt a=`{'y^`3elVLwQĢի3yɦgd٤&}:eSM)G Za1`9cW$`+eVʾ|)f1̊}-z.[3bug YtX3,Ŗ=,:LMsu3|3VX`*c 0C\.%OF7DZ ZH}Cv9Pz{+d4]`]/5ԨǛ̹ML\A(%/)|Ջ%5V׼Rt*cgyw~x4pҨ#thg.e1ASY[f:͛hvը HW Ϧe6T@`$\R ")7N)=+2͹ױ+D{M0$-(%omX?E5R?9Mk<"`E< ae 4℥& - OT-4]LHtmy>7Z%s=I)YrE5T'o1:s>1ICc%4Yeg)uGDnZŒ.,$zSPׄ Fh>MXbafw2 ~9Ebp)3<J9SB:\2E(LrH{kvKiRIjqP%`*f=G0j;=#\)fԹavN J%*<USnp|ê=@Z6oZk5 )R)[rP14:D# KEUr#O WɌPW)?%ðh+uà%q~B^&M ʩANe0;Nb5eh$U6>,>Mɔˠ'WZv<%knYk y+8GucJD4FY yt4X\J (>c@V зA,+*UkjtYr6wIø9 D[Vkh"TEvBDy31IkUUwaLUar„錯,)@ZӰHCeTo|ǯ{IvLa[3b>+O#ƜRD͖T SoU__jW[W_1RFMAZުӥ`~htVUXv/m MeK?O~GWk/9kP>,*y_amt:?T@j)qkX^SG/>y[7@piߡwVjlQҒHf*1ӫ߾ U!cdc?J2dߜldA*\yHc+rygBK2ob?%EQ@_Q\kY09?b~Wlmm+[:$Y .X}`S@ a1_Zdq[ޚ~m8a(~WN/X(@T23k)?Z5L?E}aisXJkxh 0?^t˘BsmG}=n泶{=6LUn2#탲jMkr,PdoL?ll+1̢}/MQ۶i8id~KU) ۩sL H׵7oNX 9ݚWZWL=wEo)]֠ogp: hRg^D:9h`Vm"oB2&dN4dQutBw`xs@JGOgT ~B4;F)]-Z4?<ى ʍafJ+NU},f_~Ҏo9!ayo}ǕQZ"dlϘ+"`CB›+$(Vf"R`.Wf/"oq9QϯY%n[,:%8@+͓U"OV -2gA KFeQ1ĉU\X^AWFYJ/~ɛN3Z54Vz~i" EhO$*lsB| ~rTIJ?@)5%d 5yRs %YlHҬ LMD9A`:g|X S4t !]ҬmPD Fh)EOo72%C\%poݻ촟tYMQbpa)?ZBuyI{UUH&~S~ #Rޝm=Y+u* -BZCQ,gAɳOUpTGm>pf hEr`,kX`٦:+P2׵ zڞ8ʾ@3Y+C`5eQ-M&V(Y,wX{#/ 6l_ȗ}ܕ-[֣!,Er@Q"e,I} a=Mm[i%&^G.:wˮ]jh 2VMAiJ8Ld[@wSOdzB3-~w~skv%d~ 3A1U2F檖-g y9#=k]BMQBƱ >jfeCrCKw"B%HP9;ܳ&hXnITͪ:ryAJ gpX|i: 6"2Cw k'de9Q&nK9*fqFLvB*f6*=umsr빼(h( "@C0d14MYR*eW9,wX'sB^c E/iX眶;(,~3PWhmt<3ޛC!y Tt4JHk&=Em l%C~e K,gRl UX-,!ilL|_塚b[Gmdp0چM"vj\ymYD(/⪮$yN4 dB=U5”UczE-;CZ@z2^ge'Smei6Th%,^t?855sIK5 2;ִ uIRR7} X<jF)&Vl PAÛՋynJ a--"8Ͳ&nr )Ȼ}uCD:BH+v*t/$9HyN;WMK{vr pWX9 h@NG h1a}AtNrj)vJF˂YJKzG=۸Uס_ ʶ@BK3Z8L =Ǽwxp-Zx::Kّ'>2}bjcwx0WXa΄i륭"XgSRPm-%넒+dP҈Tiai&pZđC;#4, +cL}[WO<l+ UiQ=)ڨy^5{FKN1ۇ7|8 |j Up68TȦE7\WXlqiYmT5S֣qv ˣ n懼Q(rayrpQp tVnJBdgCQ{P{ox tw-ӈaߏ*"UyOfw:OF Kj)J.@r=֫Z_ CB u8E`.JI?!-:[F DNn"C*ID74S#}B=VLӫwU['VrɣWXz*0qK I >y=١j@5 K1qͪ&~ƿ𘂏 i:#gDو8[ ?1Am3 i;u:buZu1`Orlb،V͡1}UHYQ"M:`7m / '%ĦGuʪjA3KCzkZZ pyZpI (˴$iDU"zJ5-JyQ_D S1:mk, t)"Aħ([(j ֚b'f|V!XGMmKjtTC"^L7!'k IJ}zxW *#ᜦfw 8xt[cA@E3%cN&w= nU(q Mz(i(oY+q1ǬF`ہ5AXez DH V5JhAvma&¢Dzhɛ:V ~atDP;938N8u2LOztx--bvSSNu6g&n>aa =V +DBvE(\SEGCHi{ҁMύuE_ d - jq& #>֨ڠ*#bwd=AZ\a']`="o.ĸKKsf]o]況ʴ{Q^h5IZ͋\'}uX Bu$int vo$S yeB,.} 3B_8go$tA`CG:9qZ lq5^ZAc\V&_wom# Aj&GIF?¨*oy_jcWI9`9RaBs8iyGL#šE 7jmnL|}8V'P69*GJ8%ES S1RZ޳]V.?{dX'%H _VJ_KQ"'nOkr" i)}߁AYps8&gIZWu#xjW/O֣k֫漃ʈa4!M'N?&+T&F(%K^oOփl,%-7b=_XJ$<{aM+Z Hȧ|oQiig1y|XoR -.̖1)yY'% ~X7(U$$NJ튊֩\p΄5Mf=8b9^ E 㟤mCxΆuw7TED:X %y&߈uZWSm e06c.⧯NALoß~aݽWT|HM'į o0y9WJ0?(&_fxYg۝|^$Ϸh.$/\`MA< '?)gmB8{sq4}B~~理/-n{눏9*"6kpz|DiqcA3fʨ:s4elXPH\!s?|'<>ceI (g,^S$amE_}ǽcÂKHpfǿ~Ne$!{'Bc x{?~Ia{;'O\IvIENDB`envisage-6.0.1/examples/legacy/plugins/workbench/Lorenz/acme/lorenz/images/image_LICENSE.txt000066400000000000000000000007271406265421000317730ustar00rootroot00000000000000 filename source --------------------------------------------------- about.png Wikipedia: This image is in the public domain because it contains materials that originally came from the United States Central Intelligence Agency's World Factbook. acmelab.ico Gael Varoquaux splash.jpg Wikipedia: This file is licensed under the Creative Commons Attribution ShareAlike license versions 3.0, 2.5, 2.0, and 1.0 envisage-6.0.1/examples/legacy/plugins/workbench/Lorenz/acme/lorenz/images/lorenz.ico000066400000000000000000000462461406265421000310210ustar00rootroot00000000000000hV V00%&(  U,O)1I%@C"s: t;P|?z>s:?s:e6b3mE3xU5tT%cDS,NJLKAt ~Cs: o=yH"Y3nYTGv>Dz>҇;΃6~l_ Z W)j'Y"[NJ~HyDs+]9o^ٚ[֗WѓR̎BÁd W4rQRZÏdÓc`Y$Yf۠e؞bԚ[ϔUɎMÇ1rNz? OO:sr͟}̤yƟs?mNlڢk֠dљ]˓TŋS.`s:*RC{s͟ϪɤeSCyl֟fқ]̔]Ƒ0cy=uP Oy!\7l:n(Z t@y>iG'[!V }At:2 L I' F6 {Bs:(, R*N(5J&>E# s:)s:dx=w<s:_s:#^15[0 Z2d?b@ O,E#I$Vx=M;oQN8kHv<s:g6i=ChGk7[4zVDa?|\u>@v_Ә;ǀ$p&p;{^Ƒ8jw<s: n;_{LNuNo;h7b3[/rn:g6h6)]Zؘifda _ \ Y XZQs:D {B6h,d H E zA t=m:vh٠lifda _ \ Y W Ubv< HU_ ] ZR F y@IiޢKԏKҍKό:ǀ#oa \ Y WT] w? JXE|CxAu?p;k(Z y@oܥYؘWՕUҒSΐOʌEń!j Y WTbu; LLXWURO|KwHgКbڝ_ך]ԗZЕV̐QȌLć:y_]Wv< M{6p^ǒiƗh”fb^0_qfX{6^!yLi<a3e<&mI?|]wANe͗jؠ'udb` _ ]]Fr̞=ot;s:a m:rBSzKt~I q<m9h7d4_2[/f4 yAnաTԓkfdb` _ ] [ Y'kjə:kt:s: s=L pq<l9h6c4c3s:Ne۟jhfdb` _ ] [ Y W,npțIs: x@ v?*[XN F ~C y@ u>p;l9g6p9$Ywߪ&xihfdb` _ ] [ Y W VQYs:s:= {BJ yAX.e J H E }C y@ t>p;k8t;OSؕkihfdb` _ ] [ Y W V]rȝ|Gs:t }C|N^S M J G E }B x@ t=p;s:eΙ=Ӈkihfdb` _ ] [ Y W VThŖ)Zs: E*^UQ O L J G E |B x?s<u=xݪ4с"u$t&u%shb` _ ] [ Y W VTT>nt: F:oN(f)e)c)a&\Q F |Bu=IyUؖQ֒PԐOяMύK͋:%oa ] [ Y W VTE}Lzs: H9pT>w=uLlޥaڜ`ך^ՙ]ӗZѕWΑTˎQȌNƉIÅD)l Yarȝ{Cs:n LE JX]ƑdŔdÒcb`^ZWNQxުg۠fٞdםbԚ_Ҙ[ДY͑UʏRNjMćJF>z`ÑPs:s:2N L)caʔk˛qʝqǜnęmjgdQ{u LF~cʖ|Ц~Υ|̣yȠwŝsok:gv=gЛqܥl٢kנg՝dҚ`ϖ]̔XɐTƌPÈYōq̞0ct:s: MSScʖ~ѧӮϫ̧ȣ{ğvqLFZĎuۧjנk֠hӜdЙ`͖[ʒVǍi˙oɚ/as:s:LO MM 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:1V-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<']=rI}UL?s/c {Cs:s:s:s:vs:b3`2^1\0[0iB6|YDdMkRoLhC}_1nOW6G$E#D"e3s:s:FNrգwاwצvץvեuԤuӣuңsϡXPs:s:s:s:e5-c4a3_2$vMTuZzZyZxVtQoVsXsXrXqNg[:f3s:t:,ioעwۨpףN͍3z&ph!l-sC‚f̘tСrϟq<n:k8h7e5b3_2\0`1s:s:NĈx߫UՔlgfedca` _ ^ ] [ Z Y X9xs̞ax>s:s: r= p<tAT~[ U }B zA w? t=q<n:k8h6e5b3_1n8s:(eyiۢligfedca` _ ^ ] [ Z Y X WMs˞>ss:s:s: u? s> r==m]7h F D |B y@ w? s=p;m:j8g6d5g5s: {Alڢy+{jigfedca` _ ^ ] [ Z Y X W[jȘqɜPs:s: w@ u? S]RK G E D |B y@ v? s=p;m:j8g6o8s:G~yO֑kjigfedca` _ ^ ] [ Z Y X W U-msʝ]t;s:s: {B yA w@R^ [ J I G E D |B y@ v> s=p;m9j8s:u r<o;n9s:#XyUٖlkjigfedca` _ ^ ] [ Z Y X W UT3qrɝ=ms:s: ~D |C4g^-h O M L J H F E ~C {A x@ u> r<q:s:Bxy7҃lkjigfedca` _ ^ ] [ Z Y X W UT[rɝYs:s: E ~DGy^Y Q O M K J H F E ~C {A x@ u>s;s:Ny&xmp!s rnkfdca` _ ^ ] [ Z Y X W UTSkƘes:s: F EP]WWXYVSPK F D }C zA w?s;s:ZŏyMבM֐MԐMӏLҎKЌJϋF̈7~(td` _ ^ ] [ Z Y X W UTSaÑoĘs:s: G FYZ1m0l0j/i.f.e,c+a']TH }B zAt;s:\ȒzSؕRדPՒPԑPӐOюMύLΌJ̊IʉFȆ/vc ^ ] [ Z Y X W UTS_qŚt;s: H GXY:u9t9r9q7n7m5j4i3f1d/b$XGv=s:RyRؔUוT֔TԓRӒRґQЏPώN͍LˋJʉJȇDŃ%n ^ [ Z Y X W UTShŖis:s: I HP_Ðr=pl%Vs:/ey_ۜ\ٚ[ؙ[֘Z՗YԕWҔVВUϑS͏Q̍OʋMȉKƇHąD*o\ X W UT(irɝGws:s: K J2j`ȓOTTSRQPON}L{KyHvFt=ks: xAxުq`ڜ_ٛ_ך^֙]՗[ӖZѕWГVΑT͏RˍOɋNljKćH„F@}$j X UTHrɝ'Xs:s:^ L9 KT`ɔ\Ɛ]Đ]Ž[[[YXVUSR~O{My{Es:ZŎye۟cٝb؜`כ`֚^Ԙ]җ[ѕYϓW͑UˏRɍPNjNʼnKÆHƒFD:ydapɛkÖ v>s:s: M L JN`ɔ_ǓdǕdŔdÒdba`^\ZXU.]s:Pvvߩfڟfٟd؝c֛b՚aә^ї\ЕZΓX̑UʏSȍQƋMćKÅGFB~A|iȘsʝ-bs:s: M KY_˔aȔlʚnʛmȚlŘk×jifdba_W x@s:>|ynܤhڠh٠fםd֜cԛaҙ_З\ϔ[͓XˑUɎSnjPʼnMÇJGDYs˞Ut:s:s:8 N L K9v`ʕdʖv΢v͡u˟tȝsƜqĚo˜nkifd8fs:z@dԛx߫jڢkڡjؠg֞f՝dӛbљ_Ж]Δ[̒XʐTȍRƋOĈL†ILr˞nɚIs:s: M{ LMSċ`ʔl̛ѧ~Ϧ|ͥ{ˣyɠxǟvĝsšqnkfLs:Rvܩyݪmڣn٢kנi֞gԝeқbј_ϖ]͔ZʑWȏTƌQĉM‡^ǒr͟s̞3fs:s:s:N M L \^ɓ`ʔdʗҩҬЩͧ˥~ɣ|ƠxÞvroczCs:!Xlՠxݪtۦmءl֠i՞gӜdљbϗ_͕\˒YɐVǍWǍpΞuΠo˜2es:s:s:BN M LYQŠ`ʔaȔyϣ԰ҮЫͩ˧Ȥ}ơzÞviR| x@s: DONJxܩxڨj֟[ѕWΒcљdЙaΖ[˒\ʓ^ʔpϟtϡ\ÎMs:s:s:cN- M LN9v_ɔ`ȓ^ő^ÑfĔwɠΩzǡoj[][*[r:s:s:+cmҟw٧wاwצnԡkҝnӟsӢuңtѢrΟuFzK~Fx 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-6.0.1/examples/legacy/plugins/workbench/Lorenz/acme/lorenz/images/splash.jpg000066400000000000000000000630371406265421000310050ustar00rootroot00000000000000JFIFExifII* z(2ihCanonCanon PowerShot SD1102006:04:19 21:31:304<0220DXl t|   |v01002"*<d 2006:04:19 21:31:302006:04:19 21:31:30 ~ Z .n"*2j r  D $\@Z  ZD$ q*rrr000IMG:PowerShot SD110 JPEGFirmware Version 1.00QH @ /E\a@pR980100( JFIFC    $.' ",#(7),01444'9=82<.342C  2!!22222222222222222222222222222222222222222222222222" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?G~ T*xE>ax8*E:  _JoTpyހ*TJ@`*~ =)";zxC߈Bw$;e@[#*KdgD,@fJnFqKž>R,8$3CLc3=e@MC*=Ք8*c-I!`:ChqOY})mۂxb} =<8{I\*99N$}s H΢'}w.8S$Ij|aƕfI(=xK0T N)2U<*ɋ?1%jW07geOn )]6^D6#*z?3\֕{fWζ @Vb 7ZTA8@ 'XFϥQNU6m9\( R-^\$qZB~P998'.ȱ`Tw+QIހ'itɄvI@=:*H`S\_wu$aQ8{³%A)#1]HQz ij M W11I53:Qqf `hE)#֊1OC=`XT~*n!6`ҥ2BU@# 9[v0svh+)H?ҥ303ӽHUª04mQ ٔ "<1}k{jos뭮em=c`+gӱ8lgHW89Ref`v e{c4lny,’dcӷMa2pF:q ֱϼjZbv }WCitrMU88ҘÖR*Tz`5=ƹ=q9=eW\{ mO9+@Jc79, ژB[h,m9~7|uRҴeݴxʫn!ϳz{P4P-HM`ïZEb(J)2(W9 w.:,QbsTflCI M@Ir3%{šAԌ>PT<h<0$47 rH8$4 XEAf5cT9 h*]j@VFxd%ܟVDW}隇2˓m>I]QЯG4r!;qՓsN8b2GjAw`U JXɤ;'vOj$;$c#4o992lҶͣրdQړ~zS0Z}h ~^zIФt4SD4'Ɗ`r(O5" fN;`+!Cnr3M׃@gV:*{~TzP>3UJ jD[x+EQB td/cK@ *j:~;wU?Gar\z{UBpzR`o-vjM9 ȦH0sA%;b9~T.r 7=LQP0F\Ҁ;E"$vcZ(!TJ>4Q@ƒA&EJ/ք(.F֟-Euf)pQE _tzmiiWqonT9S9|IQE 5EI^QL} {EI^6QLC  !"$"$C |"?!1A"Qa2qB#Rb$3r&CDc!1AQaq ? Ag! qQ[r[ & ,h5RL~\evF؁4f>aDP%Lgh< h=S hJ%YN M l{RpJd {T⚛Bi~v \rh6$KZ3ThT;''*twd =* mFڳnˇI4nYwK{zهu|;k޴,n  _}Xk댾*o@w4~91`3Q5 N}l O3VE$^W;> Wl\~|>b1 \1=ͥkYO$V%;G"hVŐ})f1HVY2>__`]iMX; gN~ii,b ҃LsҞT FŲDޝnlcu&nUs3Yp@#5Y1]mx#8Y*T7>J@bK1cDm 3=rDoZ-5̵e{GuayZ9Sb#kA` bqm YՀ1yDwiDG2UN&9RW` `^iGj63RcHp2AT-A13vB5bLNb@C³y1D;~VAp⧻၈zE6?ҕu Z *2G0E)զiVfH  EHT63ބE2g._i(;E^HN_n'VdzU<*  , #YfKnW+Ȑ`>S밬UAQҩ{Pf{>pDsډMh@Xl0cpcpm$K[nn3 I$; ݂}{Rݶ[cޥLwR jc(Y+ҁz}).li1 Z=R^(Ak)26uZ<{S}WQdџ199eT[KnҰ#<zIlR쩒 7n$EUU0NO)$Z䬷!.PhNU۸2'VھK,gqVXDʟm08%qfPdO~,ITk!Xɘڜ׌aa;n۴A4Wzb$rަ7+8Tާ&Z{Zv5spb̧QSDQToP7qJgN$HFi:T2&s{V5cL b*yVHH&LD{W LLqB-'$ĞҲ1R#3F'l`U 8ޠ APd)saWpM< wgqϱ X-T. ;ޤYܻ*e+%Nd4h HlO zIbr{1E.N>՗y`͎qQAuBf7`ch8Q( g$y#N6ن(]J1CHzm/+(l(*;axf$}iEn"s3^H& $x,@PJ!K 칰DD68 ʲ EbE75rյȴʠ4  iD(|sD 5rN4{Mx0R#DGޜ|֟T ڌcҏ`y*`Ф@h7g٦=(%e-7%$~) ak> 4c#C*<48Zդv @l SjB1 n4v1-"i(Slbs pirG hW x;J#frM*VKC.'8'"$0kHfg+ 3@* l0a*[|5 .>d+,} .'r)ڍQ,w+{(Ƞ`>YV؉Hzi(Ʊ< Z B3eUOj6]0>l)'҆Jછ%hzXkgOzFt1rOVieF̳\Qex5kbΡXO) ː$NUEuU/%gY7e*BNy"h DX.[r̫& P;K`vեl{V\yYʒ80ht!7&{b\VGU6=(7x'N$t*F4@MXR܉$bI"1Kcت.X^@S#ڨz fenTCzO#bm64:Pr QJ=1hEFfUXm|g&Q F)sNI1ڰ7Do<Gf 3m[G'^܋(ff$7` R (Y-dSDUZ}B"^`7qVQNSeXb IАE1J\RKxo}`qYVUcp8zHcRBjև\ۉL3=mӶias#XI,&?5ClVm@#j@P;beH M.աDQ6znQeP[_p>d #iVAGִ׵Z,9`?`MHڰ刺cKsDawq$8V@1IjΡ.~e$UNQ΅D>-\ <'&ںhdEidH$֮˲Z.t^֮ƺ"ۻ6$pA P 7;7f@pvF GSjYa 51/n*nZ}H0D㏧.ېy ⓢ/i@yT,S[I.MWUkm7Tr[LTJF1PqM) < =>۫m-0b +s:!:k>%Sn-1_C ѨD&sޟn{p w_Ӷ Vmz*2! sU_5JՋ?--K`'c$w@P?al N_yk?ʖ}21[R&9=kl8*DCBC8A 3=oɥu=F[t{;PK`9<5K[y.``c0yD"K@98 & I`@@=ZEZ8,嗲Fp`?ީWymV{I H*`'~>Ɯxn5:;Z+6u7[ٽb߆˄R*~h4YOڞߙ 2H?|fb=O*@?D`~]@{ϵ Vj ?j L1C7o K!,AXc7$g^$*% s\~ƫe[FfCpH԰849`pvWHRdޡjCE!'޳?)V;0}&ic0 jj]{ۈ1߷Zu \Ϙ`sMгã{#5v6Gj VEpyBDg"q%({wڂ\x,qIm |i~Kn 6qީwzdޢ:á&lڻu#|eUk]ڢXP1?ߥw=$sTlv_fXV!I+#&AWukG>7CtZ.51=`9 Uxܳ䓜vVȼ6hͯS,\f3 ,?j,1Qն,O]oyֿE@n6%HԎ#e$Xw#'lUYxV;Ynݷ&ߋ8y@w?mú_E.az~*4Ԯ[ oǂWlϮ? vvʹ_I$f$[ꧨ[iyXCMA3]&We5:@&]H|c\FpKjI&td;{M=#u4 ^{webIV&躝Mψvz9o!ZXCqɥp!(oҫ3m8ՋQ%m= orU`@?NM:}-^,$sV-ZowہU] Sz/XoNzwKmw-E F݆#9UֲZ4 n0$ [w- T)$I] R۷{V$ >cN^4gFFf% 4F =sXmob~߽/QjPI >l 1I]]{ǻ}Mƹhgf; GW# =6Uv훩kFn!HǻX[eOd@ `7A#/76oM,q&Y]g"SZX{,-ѻPDL=aF]n%IU֣A?Xd5Jܻ֭lӓq]6-]-ΟFGeHrd X F^ԯ.\MčL}< -Ȃ$dy3ZUE4k[U5,IX+K樂w \:b]R Ƞ+̊[P)@MaF~`(!M-0=h`E{>Bb̨$DvSO #|GQ81+"E  d 'UdO#/SrEv-3Zڃ&$f6L884Acp,NL}Є j B#pO+pV\5$D[I<@lp*qXf*.y敦g S |-l1`Xm{t}UoY F_ x8uI;89סI~ ΏGԴf׉umRZ_~'5kuI.,GI)_s_l\z Gެ[_R߆ot:/[!$/s,÷j\W[NѺnbk~к=3⎚\UNp+\o+ y ںkⅽBi77[ cf=5F>U6:G&ZI?|VPڝ7d8} #z7U)rDnJw[MUkw2*.=Ҹ0sg]gT)P1?aaYCPH]FT 6Rk1T\m^=;W^Y!OuZiJV"x$g:UwJ<;:E-mo\ #N#EԼ*zj7-ZR$ncbPzu֐^kLUn0q٩jj:@ꏙT[TX \mxe3JҮ3” c?L>^TE%9Xq4]"{zQ9lcP3 n ̇t2Ғ\yh%lp*6mT'rr@?J CA&Xv>A~-؞Uz.mQeSe``kPi-oilȠ} p[kuŚzŖi_ Mp I"O=>gi=8PAk"ou 6E?DBYկ%-DI d W.ZRm-8;#Y#G/X 4n]w+borV'תx"HQ D}RugЭ,ּajձL}E]h;F Fn5쑸[B0{[S-i7TMH9J-_޾o]ak_K{jOQrvW%V҄R-nssVQ m~7EggPKm8 T$>کT vPhM.\fecm倐}?zgn mtd$_0Z.`}KP/7|a^o:r [rg3h55֐#.%d}}+o5 !,>*izViu t*ȏ1CWJf҄O.Zs7a.ێR5NlWn o'>j-?,q&Һع=RO[ mPzEkWw\Ob/bIǨEy/iO{Suvu_[cLtt` {ELqWmiܾYIPI>+nE_;#=j)W>wګ)5t&~ZcL G~r #}Ga뛨ZUm)2'ң2$H}NTA&x]}ȁdU L(c?8alHhLE \ 叨ٷ51虬5 !>ԖEEێ;Dz x㌚3݆+&wFXMb޳BQo`v1+?{Ͳ\#8?=N҈xozf}3\tJ2`MZg~X ['N\z?z+` HjaxA [oT aC=Dz-kᮠo >mB #fǁ @2'j؁m &F A;kif:u|Q/hrg#BНyTnY]=2L޴!*ض啔g[&]5tdfH1ȭr:%{ޛ^A$Ypwuվ#W7M]Ug(*`q] t?ohnSfƗ`*o3m9 5|OKj\BJ+\28_V|/t;unS]$~n+ >HF xy*g4UReqlqCsN( 3ڴ:*cҺ`ʊ\Iejmn׶C!EAsoQafD}(m7ՔӣU$${HFbYmT(6mbm) (֬GwQ~eΓYf֠!ӱ#ݸ>Z5M"$bdkYtr֣G} DKhtmGU~wANFF$̎#ҭFtn-UixidbZM&9B10k*M`B dFn22 +M_vˆǛ "I35/wcG|0P}x1BcDxfOǦjS#L0dS?> v'!f r{Xvʲr"`#Ф;҆GaL` );0!ijYPOP2HVfLEgr9T|cz*J_LDndAF $8*P[i$BW0T! %Xȟ_UzMp$LHmbB2{UTkGgĵkt?*O:)'|C=i:*IVTsintm4j Q3iEb&j. !o4xV>+wA/j8$~EsCZk*P+yiMm`8ފz_.R-pgi~ ~YSf qV=3-|pGYMFLEl39$NDj+gv')=ک[4y)8Z}H Qu @ pj};'A BAX_z]qfLgTiq0DV!LϡHVpk<R {Z$1 0GQWȕlz-X̫8&z<Ď5 @`sFiW[$ͫ& Wwt-۫sQj -Ӵ3$}jQZ-l#wiu ~~u2Ѝ--lg @ ''}O # zk`\!v%9L]^EoUwU`it(( c_IbדNQmKC G ?[i: @88^. aeDGN,Lb ^.gw1lAHa$NTڱq5$302_(GiwJ!29>`PbG J`c 튨ăpGbL2 lb?qOa |M&ܟl?OpnTE.7:1 ='N}om0GAj[E(A!}$X*k6`&=9YAS[%@Bk,cvW~$QU9ǿgnfg\})A\7 hսT1iT߽mu4t{2y F8zh]mE!\k.V} X&ݽTsڼۅr%qVí p$PMF &]'|@-ܑ& utt: .ۙZ&x\]k.inYeeB ŲL(2&&8,K/:5;:+ꭗ;w( Z65:[.[U0⨶l$b&ޞ^U6b澃 ]k5"ڋJ[,qN3RM\ gxAOuQw׊Ž+5CE., Q9a QtHY-\:. ` <b-."Bݝ$.,OO&KcG{;0cr7L q\_quNIr[$ӽ\4{UzeoMsd]uaϯuoS{u{56-1W-k89G9U`w5[jӺNI=tzV??|705v5כav[QM,{VN&?RXDϯ˶'C$g*ܭ'ѷT)fGLs{Pn GUNP1$z5P cVL< ;b8j cB%p  $n(-'`=H6Ƀ;LUIYǛ5 {jΤ"+mm6&چs$A۶랰yIOZʋ[[E=\ں 99XB BEP}&5Ϙ{5['׽5>g#JbG9xd?FbU$1?ޛ{ Ķ͖ O0 ]/J aj*Aa`Kydw8欚Gf u[`1i142#\u6fs-ܲˬpOqUu7u&6 }V-rw5u{@r wN]mcBЌU㨵]ƩHDTl_B5)ZzGw{6:"{w7L]{*Mn䲫vozgyl c; p?S_.rv8<ǵK'W|CjM"ՕFy#銓v~!ӱ}dC( Hǧ4 _M3Y~]-r\*|i![lme2A3\bՁ$=Y7zmMXvƸ$sIj5:[n g>Q63]޳V46ojK=ˈ+tVzſ Evʯ>cTVD8W{wR$O

0ԫkW|'MiN;n[si!yձiSʪ"])4#*f&}h5uVli͗0;g4kp8t+t,p:!;“Rb+E])rۀ]}9!tjfH*[A`E f1GCp  &?5=[6I8'=dy(&wFN)|X'ֱgҔ2MtvUѿW.U>e`wj* z> KG_Zm6ړiwr$U=|tIW5OPMY3j"1Lf!Mx( "H8k# p6$3CMJjiKւGv0 HoT!xX|޴l\{"|npfg\*-@R2G͓x1-9A}cmdm\@>&<"dW#?Z+˂Gnd\YR33ެ<5Qڠc tSvC2H@'i=Y`reatX) ]mp3*a,j-H$Mx1HN2 9qr;KQ87YC#b@TA#97C 䅏j-Ф2b==nVU jȟj|'I#}A@$`WִVm(T$yDG΃v@xo|C>Kk[/}n 2g9W.)ȭLgrϦN۶m qpjnFhuګ-(pcd9|ߊNkmܴViEn]k~-qj.+w_4w.jz?vARƝ/Um E{;H#|֐.j,% #Fki=X0+TJUl۵[Vd7 Ͻsn)I€I2/kY}ڕd<4@iҹap'$W_sk^QndQOjӱB㴚NwVBj-\Qz?+Am=mwj:^Ҫ:v'Jiuh$`]Yv~V |x*ZTGuՆSoqn s&V^ϹO,jbVI*Iڼq 4Z=P}/lVgk Kwu1(-gX*@KhP}G[W\!}"j`2>bp@93Ce<'NG0 F{[ qݻ?j8(|E d1hP9Xq^$O {E+̹bۼڴ>r.0*/3Ҟl[ ֠<Gۚ):ݥ۸G3?$b_^2Pұl -9Nᙔ̭.y5JŶEHe zEۋgКb_s1j;T)hb~E$$fAzCPx]„o'fmq)-hl,dO֙l$֩+wm'ֲ dV ֓Ymbq@2 *0Gn*.(B00?&a`M1X[C+*9V`+ F̍OSkFLtRgڡq B/py>WT(wO{k2LMmt]MS$g֝VEn]H0c=:ޛ҅[g.ZwQU N޴]{ UӍعs[1$`9V./=ɷ;`Pe'IQ>wDt,[&T_\y #?4D4]0@[=nXXĒHsڷKɃ; *i7'KtAWS Pز-+!9Pm23XkqܒqN2#ڌ\-%ZGR,fN_Z0}" GGo:<; O c% 1c(iE^BvCfz IE]f Ag"dĚRK1;h (לLɤA2cb9^ۆ,Ɣ++j6HɁچYԓ+L*A<)]Rʙly$vyve.D8JY(Lj!h1y QU[ڍn'Q7uyU+YA|[7m?Y-3$|Umמ4B~>@ß_z"\Ǫ*>R M$;lvN]!_k̉OEA1֦f;zڅ7kͫ AN4#3kر.=)t79@ 'DY2p1h+Ө{q&S -\}&sFY~X ș f;Y-'$j P0<׭1{ʪ~)*0 %;_4sR@"Tp\6cAm;G[F&Ahh5X21R[Uz{)YJZw,xDF*p#?^A 2)M>A83R޹p8hڪ<4ۂ`F= MSn DRrrA{,>H$[ VGZ~qQP)GcQx<~w!tZۋRj rI5^ջv6e"1{Ԫ8*yǠ!V{i D̊+d'* YSJ =;4lKPStw)mn-ǵjؙ]=Th0Hz[ތ sOu7iƾ6ު¹[WQpj6MN}k D愪&ƾۗAV 5 +ŀᔑOMS Y`Z \,ؐ ec\ ,ʜ`^9/q;UPx3E5Hs^7#w4 iցՇ1]ۘȏkۗ( ҪU|Gnͳ> bOkjrw S߽yp83 ` bgc9_PF g a!kʖͰG}?@kxpYGnյ}j66mm  1.\u;360c<-0KVl4bbt3ڒM*oQ{MtLʕs)ٯp$ zr^ I]'>눬w+=f(oTh)_6GhFiӚ<"ΤiʯsY7lOɠ:tD$^ݴnt{Em(, ,([q$3`H~٤Z.Kb"Bj-l`rOZZ mҰ\ i/h^Ƌz2U-b* ԖA;Gԕ&4U6©K/7$@Gn I,6T *mrwEQk+<¡_2ygz~0}`qA?PfX>պy] )Kl j+9.j`SFQ[ٺ=簩/!a?֣WSl'3;QCp{ UP.b [1Dn =`BQfc&ʀOcڍZ džDp(x2yǥ].߱qi5406x?浻Csޗ,`"pVY|UCD̎6PEyVYs?ΓCO~O*^pz ͬco8ṉ ǠK"L=|JhT F s5 4b3 z̤1(1bڄ(/!T918Io#-DPp i! b;2TDP [{W ;ehooxl\kl큈uQ71O)}3饷Gf = xCuXw۽gzn7a5pSWz˥[xHLrEfxpHӵ_i@#ҦT%0OjyVC૱77zxZbUwEb1$zfiV"'+6yۑ^Yb( Ъ$JT)w#0$>l`r*i <ARZPJS$MB)1,Y@Y@pw )Zo9T;@گ{ dLM.G+wwvK` 33RwdB"Y&ߝ$ǽWl1D& %o6*v)0Vm[$c߽] ]ęYվ[Q̬Qڍu~'߻ce9Ul꣰em;jvWwEzK֜(jPfBZ@&v0ץx&|cr \ip't㈣ɺIϭ2۔`w?Zz#Ih0Ϙ@^T8pD%APp>I*̳L{yL7Nv퉎wEdnQ&LhYg5p$IPu_+O5AcҐٸ Ɍ怲m0U1@$+);±(81Asʦ(^l ,P(<AjrLLVt%Vᑏ?envisage-6.0.1/examples/legacy/plugins/workbench/Lorenz/acme/lorenz/lorenz.py000066400000000000000000000057431406265421000274270ustar00rootroot00000000000000""" Lorenz example. """ # Major package imports. from scipy import array, arange from scipy.integrate import odeint # Enthought library imports. from chaco.chaco_plot_editor import ChacoPlotItem from traits.api import Array, DelegatesTo, Float, HasTraits, observe from traits.api import Instance, List, Trait from traitsui.api import Item, HGroup, VGroup, View class Lorenz(HasTraits): """ The Lorenz model. """ prandtl = Float(10.0, auto_set=False, enter_set=True) rayleigh = Float(28.0, auto_set=False, enter_set=True) beta = Float(8.0 / 3.0, auto_set=False, enter_set=True) # Give the dtype explicitly in the Array traits; this is a # work-around for trac ticket #1864. init = Array(value=array([0.0, 1.0, 0.0]), dtype=float) time = Array(value=array([0.0, 100.0, 0.01]), dtype=float) timePoints = Array() data3d = Array() output = Trait( "x vs time", {"x vs time": 0, "y vs time": 1, "z vs time": 2} ) data2d = Array() def refresh(self): self.calculatePoints() self.data2d = self.data3d[:, self.output_] def __init__(self): self.refresh() @observe("output,prandtl,rayleigh,beta,init,time") def _refresh_model_data(self, event): self.refresh() def lorenz(self, w, t, prandtl, rayleigh, beta): x, y, z = w return array( [prandtl * (y - x), x * (rayleigh - z) - y, x * y - beta * z] ) def calculatePoints(self): init = self.init.copy() self.timePoints = arange(*self.time) self.data3d = odeint( self.lorenz, init, self.timePoints, args=(self.prandtl, self.rayleigh, self.beta), ) return class DataView(HasTraits): """ The data view. """ # The model that we are a view of. lorenz = Instance(Lorenz) # The view traits. prandtl = DelegatesTo("lorenz") rayleigh = DelegatesTo("lorenz") beta = DelegatesTo("lorenz") init = DelegatesTo("lorenz") time = DelegatesTo("lorenz") traits_ui_view = View( Item("prandtl"), Item("rayleigh"), Item("beta"), Item("init"), Item("time"), id="lorenz.data", resizable=True, ) class Plot2DView(HasTraits): """ The Plot 2D view. """ # The model that we are a view of. lorenz = Instance(Lorenz) # The view traits. output = DelegatesTo("lorenz") timePoints = DelegatesTo("lorenz") data2d = DelegatesTo("lorenz") traits_ui_view = View( Item("output"), ChacoPlotItem( "timePoints", "data2d", show_label=False, resizable=True, orientation="h", title="Plot", x_label="time", y_label="x", color="red", bgcolor="white", border_visible=False, border_width=1, padding_bg_color="lightgray", ), id="lorenz.plot2d", resizable=True, ) envisage-6.0.1/examples/legacy/plugins/workbench/Lorenz/acme/lorenz/lorenz_application.py000066400000000000000000000027271406265421000320110ustar00rootroot00000000000000""" The Lorenz example application. """ # Standard library imports. from logging import DEBUG # Enthought library imports. from envisage.ui.workbench.api import WorkbenchApplication from pyface.api import AboutDialog, ImageResource, SplashScreen class LorenzApplication(WorkbenchApplication): """ The Lorenz example application. """ #### 'IApplication' interface ############################################# # The application's globally unique Id. id = "acme.lorenz" #### 'WorkbenchApplication' interface ##################################### # Branding information. # # The icon used on window title bars etc. icon = ImageResource("lorenz.ico") # The name of the application (also used on window title bars etc). name = "Lorenz" ########################################################################### # 'WorkbenchApplication' interface. ########################################################################### def _about_dialog_default(self): """ Trait initializer. """ about_dialog = AboutDialog( parent=self.workbench.active_window.control, image=ImageResource("about"), ) return about_dialog def _splash_screen_default(self): """ Trait initializer. """ splash_screen = SplashScreen( image=ImageResource("splash"), show_log_messages=True, log_level=DEBUG, ) return splash_screen envisage-6.0.1/examples/legacy/plugins/workbench/Lorenz/acme/lorenz/lorenz_plugin.py000066400000000000000000000017411406265421000307770ustar00rootroot00000000000000""" The Lorenz plugin. """ # Enthought library imports. from envisage.api import Plugin, ServiceOffer from traits.api import List class LorenzPlugin(Plugin): """ The Lorenz plugin. This plugin is part of the 'Lorenz' example application. """ # Extension points Ids. SERVICE_OFFERS = "envisage.service_offers" #### 'IPlugin' interface ################################################## # The plugin's unique identifier. id = "acme.lorenz" # The plugin's name (suitable for displaying to the user). name = "Lorenz" #### Contributions to extension points made by this plugin ################ # Service offers. service_offers = List(contributes_to=SERVICE_OFFERS) def _service_offers_default(self): """ Trait initializer. """ lorenz_service_offer = ServiceOffer( protocol="acme.lorenz.lorenz.Lorenz", factory="acme.lorenz.lorenz.Lorenz", ) return [lorenz_service_offer] envisage-6.0.1/examples/legacy/plugins/workbench/Lorenz/acme/lorenz/lorenz_ui_plugin.py000066400000000000000000000045061406265421000314760ustar00rootroot00000000000000""" The Lorenz UI plugin. """ # Enthought library imports. from envisage.api import Plugin from pyface.workbench.api import Perspective, PerspectiveItem from pyface.workbench.api import TraitsUIView from traits.api import List class LorenzPerspective(Perspective): """ A perspective containing the default Lorenz views. """ name = "Lorenz" show_editor_area = False contents = [ PerspectiveItem(id="lorenz.data"), PerspectiveItem(id="lorenz.plot2d"), ] class LorenzUIPlugin(Plugin): """ The Lorenz UI plugin. This plugin is part of the 'Lorenz' example application. """ # Extension points Ids. PERSPECTIVES = "envisage.ui.workbench.perspectives" VIEWS = "envisage.ui.workbench.views" #### 'IPlugin' interface ################################################## # The plugin's unique identifier. id = "acme.lorenz.ui" # The plugin's name (suitable for displaying to the user). name = "Lorenz UI" #### Contributions to extension points made by this plugin ################ # Perspectives. perspectives = List(contributes_to=PERSPECTIVES) def _perspectives_default(self): """ Trait initializer. """ return [LorenzPerspective] # Views. views = List(contributes_to=VIEWS) def _views_default(self): """ Trait initializer. """ return [self._create_data_view, self._create_plot2d_view] ########################################################################### # Private interface. ########################################################################### def _create_data_view(self, **traits): """ Factory method for the data view. """ from acme.lorenz.api import DataView, Lorenz data_view = TraitsUIView( id="lorenz.data", name="Data", obj=DataView(lorenz=self.application.get_service(Lorenz)), **traits, ) return data_view def _create_plot2d_view(self, **traits): """ Factory method for the plot2D view. """ from acme.lorenz.api import Lorenz, Plot2DView plot2d_view = TraitsUIView( id="lorenz.plot2d", name="Plot 2D", obj=Plot2DView(lorenz=self.application.get_service(Lorenz)), **traits, ) return plot2d_view envisage-6.0.1/examples/legacy/plugins/workbench/Lorenz/run.py000066400000000000000000000022021406265421000244670ustar00rootroot00000000000000""" Run the Lorenz example application. """ # Standard library imports. import logging # Enthought plugins. from envisage.api import CorePlugin from envisage.ui.workbench.workbench_plugin import WorkbenchPlugin # Example imports. from acme.lorenz.lorenz_application import LorenzApplication from acme.lorenz.lorenz_plugin import LorenzPlugin from acme.lorenz.lorenz_ui_plugin import LorenzUIPlugin # Do whatever you want to do with log messages! Here we create a log file. logger = logging.getLogger() # logger.addHandler(logging.StreamHandler(file('lorenz.log', 'w'))) logger.addHandler(logging.StreamHandler()) logger.setLevel(logging.DEBUG) def main(): """ Run the application. """ # Create an application with the specified plugins. lorenz_application = LorenzApplication( plugins=[ CorePlugin(), WorkbenchPlugin(), LorenzPlugin(), LorenzUIPlugin(), ] ) # Run it! This starts the application, starts the GUI event loop, and when # that terminates, stops the application. lorenz_application.run() return if __name__ == "__main__": main() envisage-6.0.1/image_LICENSE.txt000066400000000000000000000030201406265421000162770ustar00rootroot00000000000000The icons are mostly derived work from other icons. As such they are licensed accordingly to the original license: Project License File ---------------------------------------------------------------------------- CP (Crystal Project) LGPL image_LICENSE_CP.txt GV (Gael Varoquaux) BSD-like LICENSE.txt PSF Python Software Fo. see 1) CCL Creative Commons see 2) Unless stated in this file, icons are the work of Enthought, and are released under a 3 clause BSD license. 1) The Python logo is a trademark of the Python Software Foundation. Please refer to: http://www.python.org/psf/trademarks/ 2) Creative Commons Attribution ShareAlike license versions 3.0, 2.5, 2.0, and 1.0 Files and orginal authors: ---------------------------------------------------------------------------- envisage/ui/workbench/images: about.png | PSF application.ico | GV envisage/ui/workbench/action/images: exit.png | CP preferences.png | CP examples/workbench/AcmeLab/acme/acmelab/images: about.png | Public domain acmelab.ico | GV splash.jpg | CCL examples/workbench/AcmeLabUsingEggs/src/acme.acmelab/acme/acmelab/images: about.png | Public domain acmelab.ico | GV splash.jpg | CCL envisage-6.0.1/image_LICENSE_CP.txt000066400000000000000000000467161406265421000167040ustar00rootroot00000000000000License The Crystal Project are released under LGPL. GNU General Public License. 0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you". A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) "Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. 1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a. The modified work must itself be a software library. b. You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. c. You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. d. If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. (For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. This option is useful when you wish to copy part of the code of the Library into a program that is not a library. 4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. 5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. 6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: a. Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) . b. Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with. c. Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. d. If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. e. Verify that the user has already received a copy of these materials or that you have already sent this user a copy. For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. 7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: a. Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. b. Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. 10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License. 11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 13. The Free Software Foundation may publish revised and/or new versions of the Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. No Warranty 15. Because the library is licensed free of charge, there is no warranty for the library, to the extent permitted by applicable law. Except when otherwise stated in writing the copyright holders and/or other parties provide the library "as is" without warranty of any kind, either expressed or implied, including, but not limited to, the implied warranties of merchantability and fitness for a particular purpose. The entire risk as to the quality and performance of the library is with you. Should the library prove defective, you assume the cost of all necessary servicing, repair or correction. 16. In no event unless required by applicable law or agreed to in writing will any copyright holder, or any other party who may modify and/or redistribute the library as permitted above, be liable to you for damages, including any general, special, incidental or consequential damages arising out of the use or inability to use the library (including but not limited to loss of data or data being rendered inaccurate or losses sustained by you or third parties or a failure of the library to operate with any other software), even if such holder or other party has been advised of the possibility of such damages. envisage-6.0.1/install-edm-linux.sh000077500000000000000000000010301406265421000172160ustar00rootroot00000000000000#!/bin/bash set -e install_edm() { local EDM_MAJOR_MINOR="$(echo "$INSTALL_EDM_VERSION" | sed -E -e 's/([[:digit:]]+\.[[:digit:]]+)\..*/\1/')" local EDM_PACKAGE="edm_cli_${INSTALL_EDM_VERSION}_linux_x86_64.sh" local EDM_INSTALLER_PATH="${HOME}/.cache/download/${EDM_PACKAGE}" if [ ! -e "$EDM_INSTALLER_PATH" ]; then curl -o "$EDM_INSTALLER_PATH" -L "https://package-data.enthought.com/edm/rh6_x86_64/${EDM_MAJOR_MINOR}/${EDM_PACKAGE}" fi bash "$EDM_INSTALLER_PATH" -b -p "${HOME}/edm" } install_edm envisage-6.0.1/install-edm-osx.sh000077500000000000000000000010211406265421000166700ustar00rootroot00000000000000#!/bin/bash set -e install_edm() { local EDM_MAJOR_MINOR="$(echo "$INSTALL_EDM_VERSION" | sed -E -e 's/([[:digit:]]+\.[[:digit:]]+)\..*/\1/')" local EDM_PACKAGE="edm_cli_${INSTALL_EDM_VERSION}.pkg" local EDM_INSTALLER_PATH="${HOME}/.cache/download/${EDM_PACKAGE}" if [ ! -e "$EDM_INSTALLER_PATH" ]; then curl -o "$EDM_INSTALLER_PATH" -L "https://package-data.enthought.com/edm/osx_x86_64/${EDM_MAJOR_MINOR}/${EDM_PACKAGE}" fi sudo installer -pkg "$EDM_INSTALLER_PATH" -target / } install_edm envisage-6.0.1/install-edm-windows.cmd000066400000000000000000000013461406265421000177110ustar00rootroot00000000000000@ECHO OFF SETLOCAL EnableDelayedExpansion FOR /F "tokens=1,2,3 delims=." %%a in ("%INSTALL_EDM_VERSION%") do ( SET MAJOR=%%a SET MINOR=%%b SET REVISION=%%c ) SET EDM_MAJOR_MINOR=%MAJOR%.%MINOR% SET EDM_PACKAGE=edm_cli_%INSTALL_EDM_VERSION%_x86_64.msi SET EDM_INSTALLER_PATH=%HOMEDRIVE%%HOMEPATH%\.cache\%EDM_PACKAGE% SET COMMAND="(new-object net.webclient).DownloadFile('https://package-data.enthought.com/edm/win_x86_64/%EDM_MAJOR_MINOR%/%EDM_PACKAGE%', '%EDM_INSTALLER_PATH%')" IF NOT EXIST %EDM_INSTALLER_PATH% CALL powershell.exe -Command %COMMAND% || GOTO error CALL msiexec /qn /i %EDM_INSTALLER_PATH% EDMAPPDIR=c:\Enthought\edm || GOTO error ENDLOCAL @ECHO.DONE EXIT :error: ENDLOCAL @ECHO.ERROR EXIT /b %ERRORLEVEL% envisage-6.0.1/setup.cfg000066400000000000000000000001411406265421000151340ustar00rootroot00000000000000[flake8] exclude = examples ignore = E266, W503 per-file-ignores = */api.py:F401, setup.py:H102, envisage-6.0.1/setup.py000066400000000000000000000250061406265421000150340ustar00rootroot00000000000000# (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! import os import runpy import subprocess from setuptools import setup, find_packages # Version information; update this by hand when making a new bugfix or feature # release. The actual package version is autogenerated from this information # together with information from the version control system, and then injected # into the package source. MAJOR = 6 MINOR = 0 MICRO = 1 PRERELEASE = "" IS_RELEASED = True # If this file is part of a Git export (for example created with "git archive", # or downloaded from GitHub), ARCHIVE_COMMIT_HASH gives the full hash of the # commit that was exported. ARCHIVE_COMMIT_HASH = "92ae3280d782b34ad96ba16ef1089281d5687223" # Templates for version strings. RELEASED_VERSION = "{major}.{minor}.{micro}{prerelease}" UNRELEASED_VERSION = "{major}.{minor}.{micro}{prerelease}.dev{dev}" # Paths to the autogenerated version file and the Git directory. HERE = os.path.abspath(os.path.dirname(__file__)) VERSION_FILE = os.path.join(HERE, "envisage", "version.py") GIT_DIRECTORY = os.path.join(HERE, ".git") # Template for the autogenerated version file. VERSION_FILE_TEMPLATE = '''\ # (C) Copyright 2007-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ Version information for this Envisage distribution. This file is autogenerated by the Envisage setup.py script. """ #: The full version of the package, including a development suffix #: for unreleased versions of the package. version = "{version}" #: The Git revision from which this release was made. git_revision = "{git_revision}" #: Flag whether this is a final release is_released = {is_released} ''' # Git executable to use to get revision information. GIT = "git" def _git_output(args): """ Call Git with the given arguments and return the output as text. """ return subprocess.check_output([GIT] + args).decode("utf-8") def _git_info(commit="HEAD"): """ Get information about the given commit from Git. Parameters ---------- commit : str, optional Commit to provide information for. Defaults to "HEAD". Returns ------- git_count : int Number of revisions from this commit to the initial commit. git_revision : unicode Commit hash for HEAD. Raises ------ EnvironmentError If Git is not available. subprocess.CalledProcessError If Git is available, but the version command fails (most likely because there's no Git repository here). """ count_args = ["rev-list", "--count", "--first-parent", commit] git_count = int(_git_output(count_args)) revision_args = ["rev-list", "--max-count", "1", commit] git_revision = _git_output(revision_args).rstrip() return git_count, git_revision def write_version_file(version, git_revision): """ Write version information to the version file. Overwrites any existing version file. Parameters ---------- version : unicode Package version. git_revision : unicode The full commit hash for the current Git revision. """ with open(VERSION_FILE, "w", encoding="ascii") as version_file: version_file.write( VERSION_FILE_TEMPLATE.format( version=version, git_revision=git_revision, is_released=IS_RELEASED, ) ) def read_version_file(): """ Read version information from the version file, if it exists. Returns ------- version : unicode The full version, including any development suffix. git_revision : unicode The full commit hash for the current Git revision. Raises ------ EnvironmentError If the version file does not exist. """ version_info = runpy.run_path(VERSION_FILE) return (version_info["version"], version_info["git_revision"]) def git_version(): """ Construct version information from local variables and Git. Returns ------- version : unicode Package version. git_revision : unicode The full commit hash for the current Git revision. Raises ------ EnvironmentError If Git is not available. subprocess.CalledProcessError If Git is available, but the version command fails (most likely because there's no Git repository here). """ git_count, git_revision = _git_info() version_template = RELEASED_VERSION if IS_RELEASED else UNRELEASED_VERSION version = version_template.format( major=MAJOR, minor=MINOR, micro=MICRO, prerelease=PRERELEASE, dev=git_count, ) return version, git_revision def archive_version(): """ Construct version information for an archive. Returns ------- version : unicode Package version. git_revision : unicode The full commit hash for the current Git revision. Raises ------ ValueError If this does not appear to be an archive. """ if "$" in ARCHIVE_COMMIT_HASH: raise ValueError("This does not appear to be an archive.") version_template = RELEASED_VERSION if IS_RELEASED else UNRELEASED_VERSION version = version_template.format( major=MAJOR, minor=MINOR, micro=MICRO, prerelease=PRERELEASE, dev="-unknown", ) return version, ARCHIVE_COMMIT_HASH def resolve_version(): """ Process version information and write a version file if necessary. Returns the current version information. Returns ------- version : unicode Package version. git_revision : unicode The full commit hash for the current Git revision. """ if os.path.isdir(GIT_DIRECTORY): # This is a local clone; compute version information and write # it to the version file, overwriting any existing information. version = git_version() print("Computed package version: {}".format(version)) print("Writing version to version file {}.".format(VERSION_FILE)) write_version_file(*version) elif "$" not in ARCHIVE_COMMIT_HASH: # This is a source archive. version = archive_version() print("Archive package version: {}".format(version)) print("Writing version to version file {}.".format(VERSION_FILE)) write_version_file(*version) elif os.path.isfile(VERSION_FILE): # This is a source distribution. Read the version information. print("Reading version file {}".format(VERSION_FILE)) version = read_version_file() print("Package version from version file: {}".format(version)) else: # This is a source archive for an unreleased version. raise RuntimeError( "Unable to determine package version. No local Git clone " "detected, and no version file found at {}." "Please use a source dist or a git clone.".format(VERSION_FILE) ) return version def get_long_description(): """ Read long description from README.txt. """ with open("README.rst", "r", encoding="utf-8") as readme: return readme.read() if __name__ == "__main__": version, git_revision = resolve_version() setup( name="envisage", version=version, url="http://docs.enthought.com/envisage", author="Enthought", author_email="info@enthought.com", maintainer="ETS Developers", maintainer_email="enthought-dev@enthought.com", download_url="https://github.com/enthought/envisage", 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 :: MacOS X Operating System :: Microsoft :: Windows Operating System :: POSIX :: Linux Programming Language :: Python Programming Language :: Python :: 3.6 Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 Programming Language :: Python :: Implementation :: CPython Topic :: Scientific/Engineering Topic :: Software Development Topic :: Software Development :: Libraries Topic :: Software Development :: User Interfaces """.splitlines() if len(c.strip()) > 0 ], description="Extensible application framework", long_description=get_long_description(), long_description_content_type="text/x-rst", entry_points={ "envisage.plugins": [ "envisage.core = envisage.core_plugin:CorePlugin" ], "etsdemo_data": [ "demo_examples = envisage.examples._etsdemo_info:info", ], }, install_requires=["apptools", "setuptools", "traits>=6.2"], extras_require={ "docs": ["enthought-sphinx-theme", "Sphinx>=2.1.0,!=3.2.0"], "ipython": ["ipykernel", "tornado"], "test": ["coverage", "flake8"], "ui": ["pyface", "traitsui"], }, license="BSD", packages=find_packages(), package_data={ "": ["images/*", "*.ini"], "envisage.examples": [ "demo/*", "demo/*/*", "demo/*/*/*", "demo/*/*/*/*", "demo/*/*/*/*/*", "demo/*/*/*/*/*/*", ], "envisage.tests": [ "bad_eggs/*.egg", "eggs/*.egg", "plugins/pear/*.py", "plugins/banana/*.py", "plugins/orange/*.py", ], "envisage.ui.tasks.tests": ["data/*.pkl"], }, python_requires=">=3.6", zip_safe=False, )