pax_global_header00006660000000000000000000000064122015121070014501gustar00rootroot0000000000000052 comment=d1b9c177b1c335653c87ce725194694dac3b662d gaphor-0.17.2/000077500000000000000000000000001220151210700130505ustar00rootroot00000000000000gaphor-0.17.2/.gitignore000066400000000000000000000001111220151210700150310ustar00rootroot00000000000000build dist gaphor/UML/uml2.py gaphor/data po/gaphor.pot html *.swp *.egg gaphor-0.17.2/AUTHORS000066400000000000000000000001671220151210700141240ustar00rootroot00000000000000Arjan Molenaar Artur Wroblewski Jeroen Vloothuis gaphor-0.17.2/COPYING000066400000000000000000000622451220151210700141140ustar00rootroot00000000000000 GNU LIBRARY GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1991 Free Software Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. [This is the first released version of the library GPL. It is numbered 2 because it goes with version 2 of the ordinary GPL.] Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This license, the Library General Public License, applies to some specially designated Free Software Foundation software, and to any other libraries whose authors decide to use it. You can use it for your libraries, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library, or if you modify it. For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link a program with the library, you must provide complete object files to the recipients so that they can relink them with the library, after making changes to the library and recompiling it. And you must show them these terms so they know their rights. Our method of protecting your rights has two steps: (1) copyright the library, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the library. Also, for each distributor's protection, we want to make certain that everyone understands that there is no warranty for this free library. If the library is modified by someone else and passed on, we want its recipients to know that what they have is not the original version, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that companies distributing free software will individually obtain patent licenses, thus in effect transforming the program into proprietary software. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. Most GNU software, including some libraries, is covered by the ordinary GNU General Public License, which was designed for utility programs. This license, the GNU Library General Public License, applies to certain designated libraries. This license is quite different from the ordinary one; be sure to read it in full, and don't assume that anything in it is the same as in the ordinary license. The reason we have a separate public license for some libraries is that they blur the distinction we usually make between modifying or adding to a program and simply using it. Linking a program with a library, without changing the library, is in some sense simply using the library, and is analogous to running a utility program or application program. However, in a textual and legal sense, the linked executable is a combined work, a derivative of the original library, and the ordinary General Public License treats it as such. Because of this blurred distinction, using the ordinary General Public License for libraries did not effectively promote software sharing, because most developers did not use the libraries. We concluded that weaker conditions might promote sharing better. However, unrestricted linking of non-free programs would deprive the users of those programs of all benefit from the free status of the libraries themselves. This Library General Public License is intended to permit developers of non-free programs to use free libraries, while preserving your freedom as a user of such programs to change the free libraries that are incorporated in them. (We have not seen how to achieve this as regards changes in header files, but we have achieved it as regards changes in the actual functions of the Library.) The hope is that this will lead to faster development of free libraries. The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, while the latter only works together with the library. Note that it is possible for a library to be covered by the ordinary General Public License rather than by this special one. GNU LIBRARY GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any software library which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Library 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 compile 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) 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. c) 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. d) 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 source code 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 to 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 Library 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. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Libraries If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307 USA. Also add information on how to contact you by electronic and paper mail. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the library `Frob' (a library for tweaking knobs) written by James Random Hacker. , 1 April 1990 Ty Coon, President of Vice That's all there is to it! gaphor-0.17.2/FAQ000066400000000000000000000005461220151210700134070ustar00rootroot00000000000000Frequently Asked Questions ========================== Q: Does Gaphor work on Windows A: Yes it does. Check out the gaphor-win32-libs package in Gaphor's subversion repository. It contains all the libraries required to get Gaphor up and running. All you need more is a Python 2.4 installation (the libraries are compiled for Python 2.4, not 2.5). gaphor-0.17.2/HACKING000066400000000000000000000027371220151210700140500ustar00rootroot00000000000000Feel free to hack Gaphor. Patches are welcome. Fetching Development Dependencies ================================= Gaphor uses Easy Install to manage in easy way its dependencies. To fetch and install dependencies as non-root user 1. Create ~/.pydistutils.cfg file [install] install_lib = ~/opt/share/python2.5/site-packages install_scripts = ~/opt/bin 2. Create (or extend) PYTHONPATH variable export PYTHONPATH=~/opt/share/python2.5/site-packages 3. Run setup.py script to fetch and install dependencies python setup.py develop Prefix ~/opt can be changed to something more suitable for your setup. Above is based on http://peak.telecommunity.com/DevCenter/EasyInstall#traditional-pythonpath-based-installation Running Tests ============= To run tests on Unix machine Xvfb :2.0 & DISPLAY=:2.0 nosetests gaphor/ 2>&1 | tee tests.log Structure ========= Gaphor contains the following modules: UML --- The UML module contains the UML 2.0 data model. This part is quite stable and it is unlikely that code has to be changed here. NOTE: The code is generated from a Gaphor model: uml2.gaphor. This file can be loaded in gaphor. diagram ------- The diagram module contains items that can be placed in diagrams. In most cases the classes NamedItem and Relationship can serve as bases for your class. ui -- The user interface. This is where most of the work is to be done. misc ---- Some utility stuff, such as Actions and aspects are put in here. gaphor-0.17.2/NEWS000066400000000000000000000111251220151210700135470ustar00rootroot000000000000000.17.1 ------ - Added Russian translation - Pango is used for text rendering, instead of the Cairo toy-api (seriously, that's what the docs say) - Small code improvements 0.17 ---- - New Docking widget layout - Improved componentized services - UIComponent rewrite (due to new interface) - ValueSpecification and subclasses are now treated as attribute. This saves a lot of object construction and Java-ish headaches. 0.16.0 ------ - Guides support from Gaphas 0.7.0 - "hand written" drawing style - Keyboard shortcuts for toolbox selection - Fixed issue in undo transaction handling - Proper dialog when exiting with changed model 0.15.0 ------ - Fixed bugs related to diagrams loading. - Subsystem item is added (part of components). - Partition item, known as swimlanes, is added (part of actions). - Node item can group other nodes, artifacts and components. - Stereotype names shown in stereotype attribute compartments are centered. - Stereotype creating and applying is simplified. - Association ends stereotype editing is supported. - State item supports entry, exit and do activities. 0.14.0 ------ - Reintroduced assembly connections. - Stereotypes can have attributes. - Major speed improvements due to new notification system. 0.13.1 ------ - added desktop file and gaphor icons - update to gaphas 0.4 0.13.0 ------ - Transition to zope events completed. - state machines For a complete set of changes and fixes see: http://gaphor.devjavu.com/query?milestone=Gaphor+0.13.0 0.12.0 ------ - undo/redo fixes - allow to create packages in model tree - improved messages on communication diagrams - improved text align algorithms - tagged values can be reordered and removed (like class attributes and operations) - text entry used to on-diagram editing is much nicer, now 0.11.2 ------ - improved items's connection adapters - fixed comment line, comment, message and lifeline items connection adapters to implement UML specification more closely - items glueing speedup - property page is updated when association or other diagram line connects to appropriate diagram items - removing and reordering of class' attributes and operations is possible again - association name and multiplicity editing improvements 0.11.1 ------ - support gaphas 0.3.0 API - bugfixes 0.11.0 ------ - Sequence diagrams - Non-UML drawing primitives (rectangles, ellipses, lines). - Major performance improvements in Gaphas 0.2.0. - Property editor - Lots of user interface tweaks - Services are loaded through Egg entry points. - New plugin infrastructure, based on setuptools. - gtk.UIManager is now used for menus. 0.10.0 ------ - New undo management system - Setuptools is used for installation and also within Gaphor - Depends on zope.component 3.4 and gaphas 0.1.3 (installed on as-needed basis) - Installable as Python Egg 0.9.0 ----- - New pure-python canvas implementation (gaphas) - Zope Adapters used for item connections - Undo managements temporaly disabled - Added loads of unit tests NOTE: if you have Zope 3 installed it's probably not a good idea to install Gaphor too, since Gaphor tries to install Zope3 packages too. Don't worry, you can run Gaphor perfectly well using the run-gaphor.sh script. 0.8.1 ----- - support for automatic realization dependencies between components and interfaces - provided and required interfaces of component realizing classifiers are visible in Component.{provided,required} derived associations - missing icons added 0.8.0 ----- - actions: flow final, fork, join and object nodes - assembly connector - flows and also initial, flow final and activity final nodes can have names - flows can be splitted and merged using activity edge connectors - object inspector panel added - pdf file export - gaphorconvert utility, which allows easy batch conversion of diagrams into svg or pdf files - user interface fixes - require gtk+ 2.8.x 0.7.1 ----- - Real UML2 associations; - Stereotypes; - Atrifact and Node types; - Settings of open and closed toolboxes as well as the window size are saved; - Diagrams are shown in bold in the tree view, abstract classes are italic; - Added recent files option; - Keyboard shortcuts can be configured at runtime. - Spanish translation - Python2.4 fixes 0.7.0 ----- - XMI export plugin - Item alignment - Full featured undo mechanism - Copy/paste - usability improvements - require DiaCanvas2 0.14.2 0.6.0 ----- - Enhanced Plugin support - Interfaces - Support for Stereotypes 0.5.0 ----- - Support for UseCases - Plugins - Packages can be reordered - Interfaces 0.4.0 ----- - Support for Actions - Support for Components 0.3.0 ----- - New and improved interface gaphor-0.17.2/README000066400000000000000000000010511220151210700137250ustar00rootroot00000000000000Gaphor - The Pythonic UML modelling tool ======================================== Gaphor is a UML modelling tool, written in Python. This makes it very easy to use (and very easy to extend -- and to write ;-) ). Install gaphor simply using easy-install: $ easy-install gaphor You can find the sources at GitHub: http://github.com/amolenaar/gaphor/ For documentation refer to our project website: http://gaphor.sourceforge.net/ Issue tracking is done at Lighthouse: http://gaphor.lighthouseapp.com/ Have fun, Gaphor development team gaphor-0.17.2/doc/000077500000000000000000000000001220151210700136155ustar00rootroot00000000000000gaphor-0.17.2/doc/.gitignore000066400000000000000000000000101220151210700155740ustar00rootroot00000000000000_build/ gaphor-0.17.2/doc/Makefile000066400000000000000000000107561220151210700152660ustar00rootroot00000000000000# 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) . .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man 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 " singlehtml to make a single large HTML file" @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 " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " text to make text files" @echo " man to make manual pages" @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." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 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/Gaphor.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Gaphor.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/Gaphor" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Gaphor" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." make -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 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." gaphor-0.17.2/doc/actions.txt000066400000000000000000000070401220151210700160170ustar00rootroot00000000000000Actions, menu items, toolbars and toolboxes =========================================== The GTK+ structure is like this:: UIManager | ActionGroup | Action The do_activate signal is emitted when an action is activated. Where it should lead: * Actions should be coded closer to the object their working on - main actions - not dependent on GUI components (load/save/new/quit) - main actions dependent on GUI component (sub-windows, console) - Item actions, act either on a diagram, the selected or focused item or no item. - diagram actions (zoom, grid) work on active diagram (tab) - menus and actions for diagram items through adapters * Actions should behave more like adapters. E.g. when a popup menu is created for an Association item, the menu actions should present themselves in the context of that menu item (with toggles set right). - Could be registered as adapters with a name. * Each window has its own action group (every item with a menu?) * One toplevel UIManager per window or one per application/gui_manager? * Normal actions can be modeled as functions. If an action is sensitive or visible depends on the state in the action. Hence we require the update() method. * create services for for "dynamic" menus (e.g. recent files/stereotypes) Solution for simple actions --------------------------- For an action to actually be useful a piece of menu xml is needed. Hence an interface IActionProvider has to be defined:: class IActionProvider(interface.Interface): menu_xml = interface.Attribute("The menu XML") action_group = interface.Attribute("The accompanying ActionGroup") Support for actions can be arranged by decorating actions with an :ref:`@action ` decorator and let the class create an ActionGroup using some actionGroup factory function (no inheritance needed here). Note that ActionGroup is a GTK class and should technically only be used in the gaphor.ui package. Autonom controllers can be defined, which provide a piece of functionality. They can register handlers in order to update their state. Maybe it's nice to configure those through the egg-info system. I suppose gaphor.service will serve well (as they need to be initialized anyway) * also inherit IActionProvider from IService? :: [gaphor.services] xx = gaphor.actions.whatever:SomeActionProviderClass ---- .. _action_doc: .. autoclass:: gaphor.action.action :members: Solution for context sensitive menus ------------------------------------ Context sensitive menus, such as popup menus, should be generated and switched on and off on demand. Technically they should be enabled through services/action-providers. It becomes even tougher when popup menus should act on parts of a diagram item (such as association ends). This should be avoided. It may be a good idea to provide such functionality through a special layer on the canvas, by means of some easy clickable buttons around the "hot spot" (we already have something like that for text around association ends). Scheme: 1. User selects an item and presses the rigth mouse button: a popup menu should be displayed. 2. Find the actual item (this may be a composite item of the element drawn). Use an IItemPicker adapter for that (if no such interface is available, use the item itself). 3. Find a IActionProvider adapters for the selected (focused) item. 4. Update the popup menu context (actions) for the selected item. gaphor-0.17.2/doc/conf.py000066400000000000000000000155131220151210700151210ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # This file is execfile()d with the current directory set to its containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import sys, os # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. #sys.path.insert(0, os.path.abspath('.')) # -- General configuration ----------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. #needs_sphinx = '1.0' # 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.doctest', 'sphinx.ext.todo', 'sphinx.ext.coverage'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.txt' # The encoding of source files. #source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'contents' # General information about the project. project = u'Gaphor' copyright = u'2012, Gaphor development team' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = '0.17.1' # The full version, including alpha/beta/rc tags. release = '0.17.1' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # 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 patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = ['_build'] # 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 # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] # -- Options for HTML output --------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = 'default' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. #html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. #html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = 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 = ['_static'] # 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_domain_indices = True # 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, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. #html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. #html_show_copyright = 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 = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'Gaphordoc' # -- 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, documentclass [howto/manual]). latex_documents = [ ('contents', 'Gaphor.tex', u'Gaphor Documentation', u'Gaphor development team', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # If true, show page references after internal links. #latex_show_pagerefs = False # If true, show URL addresses after external links. #latex_show_urls = 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_domain_indices = True # -- Options for manual page output -------------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('contents', 'gaphor', u'Gaphor Documentation', [u'Gaphor development team'], 1) ] gaphor-0.17.2/doc/connect.txt000066400000000000000000000024271220151210700160140ustar00rootroot00000000000000Connection protocol =================== In Gaphor, if a connection is made on a diagram between an element and a relationship, the connection is also made at semantic level (the model). From a GUI point of view it all starts with a button release event. With "item" I refer to objects in a diagram (graphical), with "element" I refer to symantic (model) objects. Is relation with this element allowed? No: do nothing (not even glue should have happened as the same question is asked there). Yes: connect_handle() Is opposite end connected? No: Do nothing Yes: Does the item already have a subject element relation? Yes: Is the previous item the same as the current? Yes: Do nothing No: Let subject end point to the new element No: Create relation or find existing relation in model Search for an existing relation in the model: Found: Use that relation Nothing: Create new model elements and connect to item The check if a connection is allowed should also check if it is valid to create a relation to/from the same element (like associations, but not generalizations) gaphor-0.17.2/doc/contents.txt000066400000000000000000000026541220151210700162220ustar00rootroot00000000000000###################### Documentation contents ###################### *The source is the documentation.* Well, not for us. Currently, there's not a lot of documentation though. For those of you interested in Gaphor there's: * A :doc:`manual `. It outlines some of the ideas in and behind Gaphor. * The :ref:`tech-section` contains some interesting articles about the technology that drives Gaphor and Gaphas, Gaphor's canvas widget. If you're into writing plug-ins for Gaphor you should have a look at our fabulous `Hello world `_ plug-in. .. toctree:: :maxdepth: 2 manual/index Running Gaphor on different platforms: .. toctree:: :maxdepth: 1 linux win32 custominstall .. _tech-section: Tech section ------------ .. toctree:: :maxdepth: 1 framework model stereotypes datamodel connect actions items services so storage undo xml-format .. toctree:: :maxdepth: 2 External links ^^^^^^^^^^^^^^ * You should definitely check out http://www.agilemodeling.com. * The `UML diagrams `_ (although Gaphor does not see it that black-and-white). * http://www.agilemodeling.com/essays/ * The `official UML metamodel specification `_. This ''is'' our data model. .. # vim:syntax=rst gaphor-0.17.2/doc/custominstall.txt000066400000000000000000000050501220151210700172570ustar00rootroot00000000000000Custom Python Installation Location ################################### This page is based on `custom installation locations `, from the PEAK site. Unix/Linux ---------- #. Create `$HOME/.pydistutils.cfg`: :: [install] install_lib = ~/.py-site-packages install_scripts = ~/bin #. Create (or extend) the `PYTHONPATH` environment variable (for (ba)sh): :: export PYTHONPATH=~/.py-site-packages #. Run `setup.py` script to fetch and install dependencies :: python setup.py install Prefix `~/.py-site-packages` can be changed to something more suitable for your setup. **Note for Linux users:** Make sure you have the `python-dev` package installed for your Python version, as some code needs to be compiled (those are packages Gaphor depends on, not Gaphor itself). **Note for Ubuntu Linux users:** Make sure you have the `build-essential` package installed. This package installs header files and what more, required to compile the C-extensions of `zope.interface`. Windows ------- **NOTE:** For Windows users it may be simpler to just forget about custom installation locations. Just follow the instructions on [wiki:Win32] and you should be set. The Windows installation is almost the same as for Unix. Replace `yourname` with your login name. #. Distutils requires a HOME variable where it can find the configuration file. So in your Control Panel -> System -> Advanced -> Environment Variables add the following: :: HOME=C:\Documents and Settings\yourname\Home #. Create a directory `C:\Documents and Settings\yourname\Home`. Also create `%HOME%\py-site-packages`. #. Eventually add the Python directory to your `PATH` (default is `C:\Python26`) #. Create (or extend) `PYTHONPATH` variable: :: PYTHONPATH=%HOME%\py-site-packages #. Create a file `%HOME%\pydistutils.cfg` with the following content: :: [install] install_lib=$home\py-site-packages install_scripts=$home\bin [build] compiler=mingw32 Now you should be able to do `python setup.py install` from the command line. If you are a developer you should definitely install MinGW from http://mingw.org and add MinGW's `bin` directory to your path. For a good Subversion client for Windows have a look at `TortoiseSVN `. Mac OS X -------- Mac OS X is quite simple: place the following in your `$HOME/.pydistutils.cfg`: :: [install] install_lib = ~/Library/Python/$py_version_short/site-packages install_scripts = ~/bin gaphor-0.17.2/doc/datamodel.txt000066400000000000000000000066311220151210700163160ustar00rootroot00000000000000Description of Gaphors data model ================================= Gaphor is an UML tool. In order to keep as close as possible to the UML specification the data model is based on the UML Metamodel. Since the OMG has an XMI (XML) specification of the metamodel, the easiest way to do that is to generate the code directly from the model. Doing this raises two issues: 1. There are more attributes defined in the data model than we will use. 2. How do we check if the model is consistent? The first point is not such a problem: attributes we don't use don't consume memory. There are no consistency rules in the XML definition, we have to get them from the UML syntax description. It is probably best to create a special consistency module that checks the model and reports errors. In the UML metamodel all classes are derived from :ref:`Element `. So all we have to do is create a substitute for :ref:`Element ` that gives some behaviour to the data objects. The data model is described in Python. Since the Python language doesn't make a difference between classes and objects, we can define the possible attributes that an object of a particular kind can have in a dictionary (name-value map) at class level. If a value is set, the object checks if an attribute exists in the class' dictionary (and the parents dictionary). If it does, the value is assigned, if it doesn't an exception is raised. Bidirectional references ------------------------ But how, you might wonder, do you handle bidirectional references (object one references object two and visa versa)? Well, this is basically the same as the uni-directional reference. Only now we need to add some extra information to the dictionary at class level. We just define and extra field that gives us the name of the opposite reference and viola, we can create bi-directional references. You should check out the code in ``gaphor/UML/element.py`` for more details. Implementation -------------- This will allow the user to assign a value to an instance of ``Element`` with name ``name``. If no value is assigned before the value is requested, it returns and empty string '':: m = Class() print m.name # Returns '' m.name = 'MyName' print m.name # Returns 'MyName' m = Element() c = Comment() print m.comment # Returns an empty list '[]' print c.annotatedElement # Returns an empty list '[]' m.comment = c # Add 'c' to 'm.comment' and add 'm' to 'c.annotatedElement' print m.comment # Returns a list '[c]' print c.annotatedElement # Returns a list '[m]' All this wisdom is defined in the data-models base class: ``Element``. The datamodel itself code is generated. Extensions to the data model ---------------------------- A few changes have been made to Gaphors implementation of the metamodel. First of all some relationships have to be modified since the same name is used for different relationships. Some n:m relationships have been made 1:n. These are all small changed and should not restrict the usability of Gaphors model. The biggest change is the addition of a whole new class: Diagram. Diagram is inherited from Namespace and is used to hold a diagram. It contains a ``gaphas.canvas.Canvas`` object which can be displayed on screen by a ``DiagramView`` class. .. _uml_element: UML.Element ----------- .. autoclass:: gaphor.UML.element.Element :members: gaphor-0.17.2/doc/framework.txt000066400000000000000000000055171220151210700163630ustar00rootroot00000000000000Gaphor's Framework ================== Gaphor is built in a light, service oriented fashion. The application is split in a series of services, such as a file manager, undo manager and GUI manager. Those services are loaded based on entry points defined in Python Eggs (see :doc:`Service oriented design `). Objects communicate with each other through events. Whenever something of importance happens (e.g. an attribute of a model element changes) an event is sent. Whoever is interested (a diagram item for example) receive notification once it has registered an event handler for that event type. Events are emitted though a central broken (zope.component in our case), so you do not have to register on every individual element that can send an event you're interested in (so the diagram item should check if the element that sent the event is actually the event the item is representing). Gaphor is transactional. Transactions work simply by sending an event when a transaction starts and sending another when a transaction ends. E.g. undo management is transactional. It all starts with an Application. Only one Application instance is permitted. The Application will look for services defined as :doc:`gaphor.services `. Those services are loaded and initialized. The most notable services are: gui_manager The GUI manager is one of the major services that have to be loaded. The GUI manager is responsible for loading the main window and displaying it on the screen. This by itself doesn't do a thing though. The Action manager (`action_manager`) is required to maintain all actions users can perform. Actions will be shown as menu entries for example. file_manager Loading and saving a model is done through this service. element_factory The :doc:`data model ` itself is maintained in the element factory. This service is used to create model elements and can be used to lookup elements or query for a set of elements. :doc:`undo_manager ` One of the most appreciated services. It allows users to make a mistake every now and then! The undo manager is transactional. Actions performed by a user are only stored in a transaction is active. If a transaction is completed (committed) a new undo action is stored. Transactions can also be rolled back, in which case all changes are played back directly. element_dispatcher Although Gaphor makes use of a central dispatch engine, this solution is not efficient when it comes to dispatching events of UML model elements. For this purpose the `element_dispatcher` can help out. It maintains a path of elements reaching from the root (e.g. from a diagram item) to the element of interest and will only signal in case this element changes. This makes complex dispatching very efficient. .. autoclass:: gaphor.services.elementdispatcher.ElementDispatcher gaphor-0.17.2/doc/items.gaphor000066400000000000000000001713621220151210700161520ustar00rootroot00000000000000 1 1 0 (1.0, 0.0, 0.0, 1.0, 202.0, 37.0) 130.0 64.0 0 1 1 0 (1.0, 0.0, 0.0, 1.0, 565.0, 473.0) 168.0 67.0 0 0 0 0 (1.0, 0.0, 0.0, 1.0, 185.0, 224.0) 107.0 50.0 0 0 (1.0, 0.0, 0.0, 1.0, 245.0, 101.0) 0 0 [(0.0, 0.0), (-18.613207547169907, 123.0)] 0 (1.0, 0.0, 0.0, 1.0, 307.0, 101.0) 0 1 [(0.0, 0.0), (64.0, 122.0)] 1 1 0 (1.0, 0.0, 0.0, 1.0, 264.0, 377.0) 151.0 60.0 0 0 (1.0, 0.0, 0.0, 1.0, 271.0, 274.0) 0 0 [(0.0, 0.0), (70.009999999999991, 103.0)] 1 1 0 (1.0, 0.0, 0.0, 1.0, 111.0, 376.0) 114.0 54.0 0 0 (1.0, 0.0, 0.0, 1.0, 204.17924528301887, 274.0) 0 0 [(0.0, 0.0), (-44.179245283018872, 102.0)] 1 1 0 (1.0, 0.0, 0.0, 1.0, 593.0, 597.0) 116.0 54.0 0 0 (1.0, 0.0, 0.0, 1.0, 647.0, 540.0) 0 0 [(0.0, 0.0), (3.0, 57.0)] 1 0 0 (1.0, 0.0, 0.0, 1.0, 561.0, 25.0) 135.0 89.0 0 1 0 1 (1.0, 0.0, 0.0, 1.0, 336.0, 223.0) 100.0 73.0 0 1 1 1 (1.0, 0.0, 0.0, 1.0, 518.0, 360.0) 127.0 60.0 0 0 (1.0, 0.0, 0.0, 1.0, 436.0, 262.0) 1 1 [(0.0, 0.0), (136.0, 0.0), (136.0, 98.0)] 1 1 1 (1.0, 0.0, 0.0, 1.0, 288.0, 480.0) 124.0 50.0 0 1 1 1 (1.0, 0.0, 0.0, 1.0, 306.0, 599.0) 100.0 50.0 0 0 (1.0, 0.0, 0.0, 1.0, 341.0, 437.0) 0 0 [(0.0, 0.0), (2.0, 43.0)] 0 (1.0, 0.0, 0.0, 1.0, 350.0, 530.0) 0 0 [(0.0, 0.0), (2.0, 69.0)] 0 (1.0, 0.0, 0.0, 1.0, 412.0, 492.0) 0 0 [(0.0, 0.0), (153.0, 0.0)] 1 1 1 1 (1.0, 0.0, 0.0, 1.0, 59.0, 56.0) 128.0 70.0 0 (1.0, 0.0, 0.0, 1.0, 349.0, 51.0) 116.0 70.0 0 (1.0, 0.0, 0.0, 1.0, 409.0, 162.0) 119.0 78.0 0 (1.0, 0.0, 0.0, 1.0, 266.0, 163.0) 107.0 78.0 0 (1.0, 0.0, 0.0, 1.0, 349.0, 272.0) 131.0 78.0 0 1 1 1 0 (1.0, 0.0, 0.0, 1.0, 403.0, 203.0) 101.0 77.0 0 1 1 0 (1.0, 0.0, 0.0, 1.0, 245.0, 207.0) 100.0 63.0 0 0 (1.0, 0.0, 0.0, 1.0, 345.0, 228.0) 0 0 [(0.0, 0.0), (58.0, -0.55555555555554292)] 1 1 0 (1.0, 0.0, 0.0, 1.0, 239.0, 345.0) 101.0 63.0 0 1 1 0 (1.0, 0.0, 0.0, 1.0, 57.0, 208.0) 120.0 63.0 0 0 (1.0, 0.0, 0.0, 1.0, 286.0, 270.0) 0 0 [(0.0, 0.0), (0.0, 75.0)] 0 (1.0, 0.0, 0.0, 1.0, 245.0, 228.0) 0 0 [(0.0, 0.0), (-68.0, 0.0)] 1 1 0 (1.0, 0.0, 0.0, 1.0, 236.0, 211.0) 100.0 63.0 0 1 1 0 (1.0, 0.0, 0.0, 1.0, 236.0, 333.0) 107.0 63.0 0 0 (1.0, 0.0, 0.0, 1.0, 284.0, 274.0) 0 0 [(0.0, 0.0), (2.0, 59.0)] 1 1 0 (1.0, 0.0, 0.0, 1.0, 232.0, 88.0) 101.0 63.0 0 0 (1.0, 0.0, 0.0, 1.0, 282.0, 211.0) 0 0 [(0.0, 0.0), (-2.0000000000000568, -60.0)] 1 1 1 1 0 (1.0, 0.0, 0.0, 1.0, 290.0, 220.0) 101.0 63.0 0 1 1 0 (1.0, 0.0, 0.0, 1.0, 126.0, 220.0) 100.0 63.0 0 0 (1.0, 0.0, 0.0, 1.0, 226.0, 244.00000000000003) 0 0 [(0.0, 0.0), (64.0, 0.99999999999997158)] 1 1 gaphor-0.17.2/doc/items.txt000066400000000000000000000023771220151210700155100ustar00rootroot00000000000000Gaphor Diagram Items ==================== The diagram items (or in short `items`) represent UML metamodel on a diagram. The following sections present the basic items. DiagramItem ----------- Basic diagram item supporting item style, text elements and stereotypes. .. autoclass:: gaphor.diagram.diagramitem.DiagramItem :members: ElementItem ----------- Rectangular canvas item. .. autoclass:: gaphor.diagram.elementitem.ElementItem NamedItem --------- NamedElement (UML metamodel) representation using rectangular canvas item. .. autoclass:: gaphor.diagram.nameditem.NamedItem CompartmentItem --------------- An item with compartments (i.e. Class or State) .. autoclass:: gaphor.diagram.compartment.CompartmentItem ClassifierItem -------------- Classifer (UML metamodel) representation. .. autoclass:: gaphor.diagram.classifier.ClassifierItem DiagramLine ----------- Line canvas item. .. autoclass:: gaphor.diagram.diagramline.DiagramLine NamedLine --------- NamedElement (UML metamodel) representation using line canvas item. .. autoclass:: gaphor.diagram.diagramline.NamedLine FeatureItem ----------- Diagram representation of UML metamodel classes like property, operation, stereotype attribute, etc. .. autoclass:: gaphor.diagram.compartment.FeatureItem gaphor-0.17.2/doc/linux.txt000066400000000000000000000012221220151210700155120ustar00rootroot00000000000000Gaphor on Linux =============== Examples of Gaphor and Gaphas RPM spec files can be found in `PLD Linux `_ `repository `_: * http://cvs.pld-linux.org/cgi-bin/cvsweb/SPECS/python-gaphas.spec * http://cvs.pld-linux.org/cgi-bin/cvsweb/SPECS/gaphor.spec Please, do not hesistate to contact us if you need help to create Linux package for Gaphor or Gaphas. Dependencies ------------ Gaphor depends on Zope libraries: * component * interface and: * gaphas (of course :) * pygtk Above Zope modules require * deferredimport * deprecation * event * proxy * testing gaphor-0.17.2/doc/make.bat000066400000000000000000000100121220151210700152140ustar00rootroot00000000000000@ECHO OFF REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set BUILDDIR=_build set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . if NOT "%PAPER%" == "" ( set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% ) if "%1" == "" goto help if "%1" == "help" ( :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. singlehtml to make a single large HTML file 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. devhelp to make HTML files and a Devhelp project echo. epub to make an epub echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter echo. text to make text files echo. man to make manual pages echo. changes to make an overview over 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 goto end ) if "%1" == "clean" ( for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i del /q /s %BUILDDIR%\* goto end ) if "%1" == "html" ( %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html echo. echo.Build finished. The HTML pages are in %BUILDDIR%/html. goto end ) if "%1" == "dirhtml" ( %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml echo. echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. goto end ) if "%1" == "singlehtml" ( %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml echo. echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. goto end ) if "%1" == "pickle" ( %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle echo. echo.Build finished; now you can process the pickle files. goto end ) if "%1" == "json" ( %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json echo. echo.Build finished; now you can process the JSON files. goto end ) if "%1" == "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. goto end ) if "%1" == "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\Gaphor.qhcp echo.To view the help file: echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Gaphor.ghc goto end ) if "%1" == "devhelp" ( %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp echo. echo.Build finished. goto end ) if "%1" == "epub" ( %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub echo. echo.Build finished. The epub file is in %BUILDDIR%/epub. goto end ) if "%1" == "latex" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex echo. echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. goto end ) if "%1" == "text" ( %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text echo. echo.Build finished. The text files are in %BUILDDIR%/text. goto end ) if "%1" == "man" ( %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man echo. echo.Build finished. The manual pages are in %BUILDDIR%/man. goto end ) if "%1" == "changes" ( %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes echo. echo.The overview file is in %BUILDDIR%/changes. goto end ) if "%1" == "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. goto end ) if "%1" == "doctest" ( %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest echo. echo.Testing of doctests in the sources finished, look at the ^ results in %BUILDDIR%/doctest/output.txt. goto end ) :end gaphor-0.17.2/doc/manual/000077500000000000000000000000001220151210700150725ustar00rootroot00000000000000gaphor-0.17.2/doc/manual/elementeditor.png000066400000000000000000000665121220151210700204520ustar00rootroot00000000000000‰PNG  IHDR3DKÙ'¡iCCPICC ProfilexíZgT]“¾=‘0䜇$*9 9ç,9gÉ ‚‘¤EP‰Š(IE$ˆ€(H(HF €„mðõûvÏžýµûoí9ÝýLݺuïtu÷­©§¼ì䇸„›j©á­mlñØwÄð‡ˆ8»†©ëÃ*ÿÃö㬠o£"¶h™x8‘£ncUñ߯£Èÿ¡Ó1U0< 1, õüݰËoy€#BƒBaŒìêå ·C%067%Àø9D8ÏCLüæ»bªÙììì ÓWXïìì cfÊߨå‹àpWOØ>3< u€›w,‚ÇÀ(»¹‡¸ÀwÖqs qõ€Ÿ„š¿ Ü.xpM\ƒ‚ᾂðx®|†7çmäz@¿þ·Ìÿ$5{°Òü[Æï ÏM€z¿ek¦‡×¢âqìè¡9§Ûú°¿¿Æ6€Ý¬ýý_eûû»å 'èðs ?Ô¨ƒ ØL€p Ž T€ÐFÀØà ¼€? ăd2A(Åà:¸î€ÐZA'èÏÀïÀ˜ŸÀW°~A„…p5ıC¼ÐH’…”! H2…l 'Èò‡Â (Jƒ²¡¨ª„ê PÔ Ao )h úm#R-‚Á‹EÈ Tº3„=„ˆB$"Î#ò¥ˆjÄ=D;¢ñ1‰ø„XGì#ItH<òR©†4@Ú Ý‘AÈd 2Y‚¬A6#Ÿ GŸ‘›(Š ÅŽFÉ¡4Qæ(gT *•†ºŒª@ÝEu¡†Q“¨UԚ͌@Ë µÐ–hwt(: ƒ.G7¢£GÑ3è5 C‹áÁHa40–OL$&Sˆ©Æ´a1S˜ïX$–Ë•Ãêc°AØDì%l%¶;ˆýˆ]'""b!#R#²$ò!Š%Ê&ª zH4H4Cô“GÌI,M¬GìDFœF\B|øññ ‰‰‰;I I6É-’’×$«¤hR6RIR=RWÒS¤9¤Õ¤IÇI×p8N g‰ ÀÕââFq+d2<™<™9YY YY+Ùk²oä8r~r5òä‘ä9äµäýäó v y KŠ`Š Õ}³”%;¥¥5e8ee=å å2 ••&•U"U)UÕ$Õ5µ"µu4u!u3õ[ê-&9šhšBššqšZ6ZeZGÚxÚ2ÚnÚ9:, ?]&]ÝÝOzfz%z'úDú›ôýô« Ô R 6 §Êz>1R0J2Ú0žf,gìc\a¢a’er`JbªbbúÁÌÆL`öaÎdnbþÀ‚aa1g‰a)gyƲÆÊÂJ`õcÍemcg#g“asbKckd{ÏŽag·eOb¿Ãþ†É!ÂaÍ‘Èq‡ã-…ÇÛáÏáñ8I8e8]9/r¶r.qÑq©qq]ãêçÚäæå6ãŽç®å~σã‘çñæÉçéáÙàåá5çMäm俣æSã æ+çåGòKò»ó_âïåß°8/ðHà« — …à9ÁfÁe!!S¡³B„>a?bz$ùÈÃ#«Â\–ÂiÂíÂë"‚""9"}"»¢ÇD½E‹D_БЩŠEˆÝ›g7Oïß”—ð”(–x}”â¨öÑ3GývLè˜ë±«ÇÆ$)$µ%$[%H‰KùHÝš”f–¶Î”~&ƒ‘Q“‰•i–Ù•õ—­’—ã–s’»&÷NžAÞ\>[þ…™‚žBªB¯"FQ]1Q±K RRQŠSjWÚSVVŽU~¤¼wüøñ¸ã*@EU%A¥[­ª¥š¢Ú¯†S3TËR%Ь WÔ9ÕÝÕ+Õ—5Ä5B5hìhªhžÕì×"×2Ó*Ðú Í­í­]«½¡£ ¯Ó«K®k¡{EwZOHï¤Þ} ¯­Ÿ©ÿÆoàePo°m¨f˜a8fÄnäiToôËXÃø¢ñ[““fS”©‘i¡éœ™„YŒYŸ9­¹“ùmó- M‹l‹IKaËhË^+Z+«:«=k}ëBë%Y›d›1[ÛPÛÇv4v®v öH{3ûrûõê'òNÌ;È8¤:¼svŒurâr uêufrösnw¡vqwivŹ:¹Þu#r³w«wG»Ûº×z d>n>­¾4¾¾¾ýXüBüžùsûÇøˆœ ˜ ”Ì \ Ò * úuÒòd]0i°GpgkHdÈh¨XhzèB!¬$l'Ü&ü~MDPÄ`ä‘ÈÔÈ…(¨òhD´Stû)öS§OMÄ(Æ\‰ùuÚîtK,KlLìDœR\QÜþ§3]ñÜñgãtªÉƒG“¤“ ’vÎ:}œÌŸœžüåœù¹)l)ñ)ó©ú©õiôi1iÓéÚéw2h3NeLŸ×>_{áBì…¹‹ïe²ežÍ\ͲÌjÏÈÎÊÞÎqËÈ•É-Í#Í Ë›º¤{é^>>?=ÿçe—˃ò…Ô…±…Ÿ¯X]é¾*qµøîZÔµ…"‹¢ÇÅÅ%%%§K–KíJŸ•É—U•3—§•o_÷¹>~CÿFk…hEñMÊ› 7×+Ý+ßÜÒ½ÕZ%VUVMWRý«&°fæ¶åígwŽßi¬¨½VGUw®n§þdýBɆÑFÆö»Òwoßã¹wå>õýÔ&¨)ªéÛï›m›Gê=ìnQnijo­jãi+zÄô(§¼=µÕ×±ÝÞù­Ë¿ké±ÇãénÇî‰'6O^õ˜õ ÷ö>ïÓí{úTëiO¿F÷3³®çjÏ;T:Õ»^^<RêÖîÑy>j8:ôÒìå«1ë±ñW¯¦_»¿^zãÿæÛÛ°·ÛïâÆ‘ã)ä9ï™Þ}àùP5)1Ù4¥<õä£þÇÑiÛé韙ï³Ñsˆ¹ôyšù« ¼ µ‹²‹]KúK¯>9}úô9ìóþrÚ ýJéªðêƒ/ê_†¾Ú]úöñýâëZÕºÌú“ ³?ìýÌØdÙ¬Ú’Ûzºm³½ø+r‡h§`Wp÷ážÞÞÄ~ÀßXào,ð7ø üþÆc¿±Àß¼Àß¼Àß¼Àß¼Àß¼Àß¼Àß¼Àÿß¼@s°óa,pÀÊ <<ø~2h† EþoÛó(¿£ €„I¡b ó¼0ëpfV!(šGØ!f)( 4F ëMôŒÄ'M.L±G5B“M§F?ÃÄ4âÇZƶÊ!€wâÌáêäþÊËÁgÀ.P$Ø'ôM˜A丨ŸX¾x¯Äþ1ÉSRÍÒk²¼r¦ò1 %p&sRyG…AUZÍ‚®~E£EsDkZû«Î¦î.œqÜ7Ø7Ü7†LЦäfÌæº–^V)Ö•6}¶íÖìwNì8¬9~túà¼íÊî¦íæqÃóµ7ÎGÕ7įÌ `<°=(ïdh°Cˆi¨a˜Yø‰ŸÈ¨¨¤èÌSE15§ïŶĵžyßP“X‘Tz¶4¹ø\qÊ•ÔÜ´´ô3áçý/x_ôÎôË ÉÏ ÎõÏs½d™¯uYº€¿å õUÜ5Ôµ¢ÉâÞ’êÒŒ2ŸrëB7(nü¨˜¾ù¡rêÖlÕ§ê/5_o/ß™¨}RWVŸÝßxãîý{÷§›~6“\2rfÔþ¥ìÍØê«Á×uo.¿=óÎmœ0Á1ñóýð‡Êɤ)×ZÓ¢3ÔpÎêù\Ù|Èa‘fqf©éÓÅÏAËF+l+/Wý¿€/É_™¿N~[û¾±–·.¾Þ³áýƒéÇçŸk[N¿ºö÷ý¬@ Xp¬C*P>ÌùœBâM¨Sh[Œ 6¾´HÇÉÒ)Œ©Ø¨—hKéµF˜t™+YVØ8Ø…8dðÊœ:\'¸Cx.òÖð=åÿ,H+¤xÄK8Sä‘ègqF ÂQ¿c—$;¤feHdEåtå=b3•Ê”ëáìõ€ê+µ)ÂŒú‚Æ¢æ<|Œë ëöêµé7TeÇ™„™F™%šgXäZ^µ*³®°©´­²«²¯:QãpÛ±Öé®s³K—ë€Û;÷%/¤×¦÷œÏ°o›_µÿ•€ó±A'OzÁwƒu¨Y˜I¸q„i¤U”c´Ç)¿ŸÓ–±Êqghã1ñ[ «‰ IÏN$LyšÚö0½!£âüå ©Ã3C²|³ÝsNäšåi]’˼L_€(X.»ÒzµôZr‘O±~‰X)CI9éu²”Ô7i+ioÑTQWS×Pݦ¼C^KVGZOÒ@ÔHtuwóÞÜý᦮ͯ¶ä·æµå=ºÜ^ÜQÙYÛÕö¸·{ìÉ\Ïrï|ßÄÓÑþ—ÏÆž¿ø08ÿbehmxcdmtååìØè«ö×eoÒÞæ½»3Þ;ñîý§ë“›S¿>þœ^™™œœkž/[H_<¹dõIó³úòñÑU¦ÕÝ/Ó_;¿]ÿž¶²î¾áþ#òçõÍíœùCÿ#P<Àdƒ6ø- 3ƒM" ‰FÖ¢BÑÖOl%Ñ/Ò‹¸çä Je*Mj-ÚctÌt»ô1æ2y0K³°L±6³å±sãÅ8É8?q=á.å‰äÕácâ›å¯ˆT"9rMØODI”RtN¬I·<ª<«}j|oûÞñ¯=Y]ŸÞp½±çîê}Ú&©&Í#Z’[óÛn=jo×±ß%úعûò“¡^dŸÄS«þggŸ|8ô`dk¬èÍÓ‰·ÓªK-bþÿͧ¬ iò—°ƒyvó+\°„ùôBH0&ƒera/œZÒ`ÿ×úÁ d)ðq ”ƒ» Œ‚i˜Þƒ¹gHR„ a®9JŠ »ÐsxuA 8ÊGD¢ñ±‰äBÁÌp5rE†RE…£jP³hv´ :=Œ¡À`Ò1ýXb¬.6;LÄDäJTK´GlD\N¼EbBR ³®Î¤p,¸hÜ{2M²råiÊ]ªXj@Oƒ¤‰§…hÏкz4}#Ã-F5Æi¦4f%æ1o–Ö lœlìÚìãAx,¾”S‰ó=×nCY^fÞu¾^þ<A>ÁU¡¦#g…-DxD~ˆˆÝO—=êrÌLRSJAZT†G–EŽQžYKQDIAYç¸¹Šƒª§Zá´zžF½æ+m #¡ë®wY¿ß`ÛHÐØÝ¤Ö ˜ÛZ´ZñYgÚlÛ9Ù÷8Ð9:8Ýp^v•v;ç>é©çÕçcïûÒ_8 6pè$kp@HG}xhÄ«(èñ˜æØô3ñ«‰Égé“ËRÄS;ÒÍ3æ.$fÉšÌ)ÎóËW. +üzõEQgISYËõÇ/*GªFkúït×½lX¹ÇÐDho)iëlÛ¹ÙÍßãÕ×úŒ{ ãÅöˆãˇ¯~¼uœŸì˜¹?ï³hüÉeåé·àõ{?í·B~Qí8ïŠìnî¥ícöß\@ œáà<ìý&ÐÞÃïQA\$\k`yáëP3\S°„@¾WB8À¾¿…xÄ e‘¾È"ä ‹RD…žŸûÇó#JŒ&3‚¥ÇÚc˱Ÿ‰¤‰‰†a>>Œ¸fßcIÆII‹pœî%™ ìwnòB ŠLJJÊ\*fª2jQêš)Ú0:]&=}=ƒÃ8cÌ[70[0o³”²ê°n°³°osÜÄ[p¢9¹\¹¹¸¿óôðñEð  bg„:Ž ÇŠ8ˆ*‰±‹í‹OJ´-;–"%+!“/[&wK¾Q¡Uñ±Ò€ò»ãŸUvÕÈ Bê^š™Z­Ú_t)õ”ô= .“˜è˜f™½··L¶šµÑ¶½cÏ|â¼Ã®“·ó=W´›9üþÙñ²ô¾ëKçáÿ>P=¨>˜#$+ ñ5Ê7z-æbìѸÁx¿Dâ¤òd¹sÏSÒV2¢/`/dqfßÍUËÌ÷*`+|µ¸È¤]ÚTîsƒ¹b 2¹J£†òölmW}Cãí{íM=ÍÁ-|­ã®u¸vÉt3=Ùë]ºõœtgÈpäÔ˺W+o…ÇåßkL†l˜EÏÛ/>ù̳ûeä;jáÉÏá­ÀíÉú]Ìîû½kp v?ÈÁ*¹ðs?>Á~§‡ŸxdE@9P=4mÀu#šˆD9â ’©‹LE èP¨[¨ 4®à˜ÃÈc²1+X=lülŸ#Ú"$^‚ë*¦IH'qN¸²Hrrò Š]ÊÛTÎÔtÔý4Q´|´Ãt±ô"ôÓ EŒîLrÌ¬Ì ,7Y=ÙøÙÙ«9ñrœXÎ ®Ü ¼t|’üÚ&‚ÖBG|„#ED³ÄnŠ÷H¬c”Ô ‘.’òÊ ‰ŠƒÊlǽTT· Úêš$ZÚ‹º¶z½↹p<š`Fa^eif°i°s8tHpÜvpùèfç>áâÍá3ççë?hô(X8¤0Œ$<1ŠÊ8åqúF\Gü£ÄëgOŸ3IåOÛÎh¿žéœ­š+t‰þ2(øtåŵºâ¼ÒÓå~7lnªÝÂWý¬é¾“QgÞÀÓøó^_S~³s [ëУ¨ÖήÇAOøzûîö§=w”¢^½?õZìÍÔ»‹ ï—&‹?ÚÍðÌnÍO/N~"|ŽY.]i^íúÒþµò[úw‹5ƵÁõð ŽÚr?ºªü¬Ý䨼°¹¶¥»U¶µµm¼}}{öÏ/÷_7~Íïðíxìdí´í,îÒìßõÙÍÛíÜ]ÞcÜÓÞ‹Û»¿·¼Ï½o»Ÿµßwàÿß5Xë !úãõ ê‡_ÿïþ~ap×áF q~†µ]ôð¾è欮ŸYá}/Èï°Ö˜Ý,Ì`Ùp14ú+{kšÂî …ª`F{…›ÿ#ö"ÂËsÝC4þØ)ñqÖ…×F@Ë‚ÃL-`ÌãŽp3 SÂx6ÚËÜêu7wõ䄇·¦Îoµw¨ÎÁX´ð0.ß@½ƒ9Àc!¤¹ƒ0¸âËÀ5aú€Ôÿ9Šà ·„Ãm!ÀÌÃØî÷ „1þ=“höó„ûýW‹x¸¾,íϘ¿GÃÃcþ±é Ü`üGî qÐv0»Gï„ùGãÀÞálīŗÄwþÌ Å‡:Š’B©¡”PÊ(9€GÑ£˜J% ¯÷ÇQ p›Ð³°eÏ?s<°ïßà~90JÞÒ n=øí.Zå¡¶÷¿¾ÿ·ïÁå¦å?3€ë'ká+ 0°Ÿ®& .*TÒÁù?o¡î‘µ‚€ìíéŠW…«&Ý…ñ:®¢Âø£ââà?Ÿ˜5Í&°© pHYs  šœ IDATxí]|SÕþZhKÙ-{oÊÞ{OAAQDPD¶²"²§¢è_ÙK¦€ ÊdÙ{ÏRèüŸsÛ^Ò$Mi’rN·wŸ{ï÷ò¾ÜñòŽOLL DA@ðv|½}ÒA@!3ù‚@²@ ¥³FáCâ .GË9¨NŠ ‚€‡"àÐmu9T.¡1ú<®+äe¤¬¥%Ô'Éä‹€5â2K{\rK™%@`Fâ2†ù²$O¾—NF&<˜A`/nÌ3†‘bs˜Ì,ˆL““5_§ñ%4†-ã–yœ/"Þ€‘Œa™1Îa·å;Lh î™9@b~Ô¡LäüÉ¥ Ç$¥‰Êèë0e›ò9,"ÉMN<2[¤ÅéQäÂÉ]'AÎRb4%4K³KfZIœvK’â8YŽ1cÆ”yùå—{§H‘¢5hÙ‰ ‚€ `‰ñKXTTÔ?¿üòËØ¾}ûî¥ü‹ä˜ÐŒDÈÕTœùÈ¡Ù]fÈÌ’Èø‘NË:jԨʭ[·þöáÇi#""Í‹‚€ `___øùù! àÞ‚ :ôïßU¸BŽÉ‹‰D“û*üXdæ‘1¡å=räÈ”°°°úİA qЊCBBÞ¥šgÈi"Ó>+LЬ.3-ˆŒ±ðLLÏÈØgç^™gd"}Û€¾ðàÁ¥„ãF¡ëÁ› `ŸE‡Ó¦M Þª¼^Í¡U¦ð¶ó ‡„Æ7€ÚC£{Àü&¡ «dFéFa–DÆýÜP ^^ZÑkªÏ%ÂÃýû÷qùÌYü·jœ<‡”ѱ3¹b傹Q¸Y3dË›©S§V7(OAm‰QçÍ3§qsÅJøÿw ±ã óóAxáüjþ‚òæK´Î³—OcýÁU8zÑ)©Ÿ1Ñð‰N¼©ó£Q‰fÈ“Í1¶úo™Î_§OŸFž·+ªÌX‹wK 肞ìûÜÇö°â†/tÏï‡]GöÚÝѵAÞx=8·]߆ ¿ý…J©bpeÃ,Œ8‚Ïß«%ä­'š`Ifš´˜Ð˜‡tœ¿èãlÆ#³¸¡h³ôdÆu$³ëç/àÖ’uy˜å/Ç ‡o(Ò<ŽY·Ó$2Á.qê>‚.Ä`wÆ”8Ne¯—,… 2Äu%¾wûüyd\ó3²‡D#ñ¤ÊÿÈ‹ˆ¹x€ÔHq²G Hùhø¥¢%"•½]¬´]®Ç†“+6Wjd*˜ ©Ó§ÀèË8rsD†á¶Ï5¤Ë€p¿@¤ H¹l±\öû¿çÖS˜x4gΜ¸pá¶mÛ&.Ž7nÜXUâ2¼¬dW»vm¤OŸ^ÍÜì‘YÄÅC˜½~ rÕjƒ¦ÅÒ)=)âíÏE¥óE.JñKía'ÐQ8óûjìÏP ÍÊf³˜O4nl]†õ©ŸA§ ±äõð¡RúZK”oú®€”ôåƒÈûûpì‡ôïFñÚ³Þ Iu>Lfš“˜¸´3ònÕDl:AWÔqKߨÄHd<3ãºþ b^ýc 2?ˆFÕ[)ƒ!Iwê|WüKÈݦÇ墑jëIø_¿IOÜFÁŸÊ܈×ÉRÔ²/¦xÔßk‘>s ŠTJ‡€ i‘2=M£ÏÀ÷êV*Cdu ¾A1Hé›EÒøâþÖû¸Du¢BBL:,Ûþû“ˆÏ¹JgDêtð ˆÁ%ŸÓˆ¼ŽÈ˜HÜñ¿Š€ _dN“iqþú-p¢ùmë´lÃV\c˜2eJ”-[ô¨ fLŸŸ~ú ùòåC‘"EpóæMìÚµ ÅŠCîܹqçε¦ëZÓé{y›wêŒ× ðçä‘DEFã.E£i mOÇ£BÑ´,ö5Íâ­&1£¯¹(‰éþ0AÙ]™ó5‘±¯‰ŒU33žØrÁÇÏ¡ìušuPá D4ôÝ Ú€––Q¤ÆJ3PNNÊç²Á'ÎÙÔÇíd»{yJ¤¡Y&ä†ÖôðÏß äÎÁE! åq.›õÎ!»:oø\BŽ™‘9{0‚‚3"#“dÚ(„¦¼‚û~×*­/‚i¶˜9ˆ\¶`d£²\ÇÖ¸›N(¡#j4jÔõê×ÃÑ£G1þ|5[Û±c‚‚‚P®\9UŽ÷ÏØÙk'†ïx^ÎÇ/—G‡*/&{ÿ7uëÖUî½¶á®Î{x Ó_þßoýsß«‹ú4[¬ÛoΆ]ÅúÑuÑ A4®;ëÏß7µsóÀ ôÓU÷ýØ{'ÒÔÎ__¼Q«va×ϟƵ×óvÆbyfÅ8|Êž0©þ¨µ§M:ÆNŸÎ´ô%ù(íQ·a~ÿØ~5¬;ßo9FŸ®4ˆŠÏ…µãÐjäz5>[íÅÜ?‰%£ß7áñü˜8v?®8<ænX…o¨/Ô]ˆ+;÷‚µ¾JšáºŸÐ•g^aÇÒçο,ä‚h ‘š ÖG=õ¤=²pš]QzLJj;4A¡·p×N_¤y€l¹2"ˆö­ü|áOºüi_Œg1ôAŒöGÕH.U@$"sùâêí»vûi«ÿ–錡qsŸI‹~Q'N`ëÖ­(^¼¸z¸°ø·45™YêÓñè¸Óëóûwã@X¢Â"á—½0BrÒ’“Û¤‚1äsûg|‚¦„£ÿ¬ù(qc»öÃÈì?âÓºY©P\Ùƒ>ÚƒúŽÂä×Oáëþñú³Ô¬&NʈÕ݇à³eÏ¢^粈>³/vƒg?ž„…åý±yHg¼ß7–Mn†4„eص½X5ªV•n‡Ï'Á¥y}ñmï5¨ÿ[;d*ß­± +^z=ä_ÖàøÓ2“‡véô>ì9 ŸÈˆI‘%Jä@ Úøåù®˜–³¾œû²ÞÚ‡9=¿ ‘ÖRcæefĽø¹=Òk­½(\À¢¦ob6Zà³YP8Åiü2è#|Ø4߬k‡Üqx¬¶/õÿ Ci¦À_xñ=3c(ùq&4þ˜rœy'öˆX>¢¨Êc_‰™ñ¦šÎˆó5±±"Mh\‡aßÏ8­§x<ñ @@6"š@Úª ¢_:e “º ä§%Ÿ›ãg«nÞ'2#wûÂîÁ¶Ëìê LߌôŒUp:ø¦'2¤6àOÝ¡§‰•)âa$|‰È˜|oÑO¿ØÕ™Æ?P-3¤J ¿”¾äùRø!Ї‡ƒû´Ô¼O'·hY™" a´wÏ?®Îx`ØH°üB`åÇ4ªU«†Í›7cÍš5hÞ¼y¼^&3{8EÑ Æ²rÜTÈOËîS§‰fIŸµ ìiÉ¥>%¼ôzp ¿LÙŽèz}’šži‹Î†¼ToáïÿàNíÆHCeB)ÞðÓoÑ¿z‚#= Süè+ÃðC§jHé†cÇžã´ÏX ×·/¥GMÔ É@˜…Ÿ{¾_®Çá{Ï¢\`ð'©JOÌÞ ´[€+5êbòß»pþÞ«È’¹JòÍY £hÑ|¬5þi™éË[€+cüš¥ùžBxž¾˜<(+RŸø“)«ýÀ6(““¾ärÖÇ€iw±ù?”^f2¡ùF¥PñTVÚ ?øW¬ŽIQ5é@f¼3ø=,zçl8Ö¯ç„GWƃ…qŒ ÉÿÇG€o`¾áJöù¬yG‡))V˜¯èà±®`KX‰Ñ1¡i§É,¥åh©Ì¯T<'bÂB‘šfޤ'-éO“’Rú+DSG>¤=¦Ž@ˆ(Ã|‘=ƒý~ºl7hÙ/Ž_»vMðllÆŒذa*V¬¨öÐŒÊ,ëšçÅ^ç·§LÀˆ=3U‡njþ,ÄD„+²Â¦1xk“JVÿrÄÍÚ¢©ì=J É–!C:œ`r+BKx_.C3¥´)á]“è¸yþ!tƒ:lQz€@ ¤ˆÓÚ.Mפ8‚ãf3átZÌË@•‡ü)£ýU›c£ö"èã’õ˜Ú2–ðTC¤/\-­  PPJSý½ÜVí™ÑÐiÔ*Ÿ4ÅoÏŸ?ÞÇ Áª ž¥ÇÃCåÈ?' À70;~Þ‹ Mó&2ÍG±^*`”¸»ß˜¤Âº’¥¯•óÕf—àifÊ2Åpåìe„d¢wk‘eÊHŽÂi‰Øxfvn žY±#Bºz÷|J£³î¿uy˜½nÝÛÜDZqõÔò•ggjI33ÖÍáTQÄ‘Qà:6o*Y(Cö\û ÑDb¾DdþH—2fg<óe„i¹Éú"è=šN¯>@¹ åíêTøÇý2|ÁàöíÛøã?Ôf?oøïÞ½Û·oý ï¾û.Ò¤!ìH¸Ž½1EÓÇ¥ˆ/—A4¾äGñƒË”Ðj¿]ÚPˆ‚L8Ü?N%‹¦å6ýSen>ŒÍ#‹Í§öb¢S"=͘Ð ãV¿‡üf—‘6î©.“IÀÃpfEqݤø£ü«qûaª!ËÔžâ'úg9þ與Tú$NÞŠ@åŒüEO“ô{×é?—eýŒ}2â°#MJ—±½úRcçIGtœŽèWY•j/*Gþ9¾`š[4×ÄÝÈf+N‹Gh¶È̲_\Y+×¾jÔÞ2‡•¤©RQ‡Nàl òd! &G¤Òcù±møóŒŠTÑÕùûwU(ªT U¢í‡qK5Æý݇pñÒCäÌDºˆxÔ¬Œ5ÀCäå&ïûÇàâҸO•²Lc<·½(–©N<Øë§ï!8„|ýàãO'¢þ¤’èFHEñ‡ôá¿vê:xYZ,sE„ßã›'iÂê›’þ믿ԯø ^r¶mÛgè!àµkת“ÍçŸ^5ÈuìáÏK*–«§Oâ\ŠÔ¼‚ñ|Ê,9N9WfúæÅ *bõÌþ˜]d<^ª1Dg/û ¤T.õH—! T{Dpª.±ŠÓ²O¡@ñÉ]µÎý N.Šñ¯Ô@pŠ0\:uJ„P˜–·T™‰V÷=ŠøëFÑe mºTs×ã@ã×P€fÞ< ju\¹y§Îѯ%x`üež"=rf-HasZ…Ò¯W…ï‰uøpäOt‡T;Íd~¦Öˆ¹}†È²½T¹+àÖ1{Ê~عq ëfL!‚nŠ*¹RQ½Øñ›ð ²"NAÀ’È4×0ÿ$(ÖÈ̲"ÇÙ±bíëFS꤭–h5€ŒÍàÂß;‘*M²¤£YY*š‰ñì‰ ˆ>Á×ÜÇyšiù׬‚ëDdöôÒÓðÉ÷.žý)ƒÒ!k!žWp׌âƒ+àÒÙP„å}÷ˆÈìédRª‘ý캲·Ò†Òƒ³©hùDßæôKt´èl7ÏÝÃÝ«÷Q7Ç‹ŠÈìé4öÆ^X?[vàÀ\½zLXúÙR¥J©Ç5¦Nªfg PËMž™Ùk?2nµrdo¬4t ×ã0éùhð×I ‚uähÑ_ÄÌÁG#?À]¶VüP";í™EÓ"H0®,ÏÖx')“n? }G!‘?=î“£¾ýãû}®Ë¾VÚüPŸ,(L§Ötm)…—§º.|i”ˆ|ˆ¤¢h/«ø ïàÁ–ièÛn Zþo‰*EüÏ'>¼g¶à t3u–tfêŒ9³ŸÃûÃÞBÇ!ÓÐû÷iôÈϳè9  &Ž<­ÚS¼—2 3sl<ÆZ{9Ñ~ÆpD}2¶›ÛlÎç1hÆÈçK}Œ°À#¶„üO:ÌGš[Œ|Ã7wüáa¸KÄïK3ÏÔjï()ê¢pöDSÐR=÷5!±ý ZÚ@µ‰’ˆªª¨‡<Å›ÙYQf½=:¬ þGÒý•–ú!âZZ¶lÙ•Zø—Òu¬£© í“Ä:½—Æß‡±‹}“P‚-2cBc§™’?‰ÚÑ”J‘]–[¸pá$òmŠžu0¡ÑMºm'üCÃ:nÏì>ý4é!=B‘¶J%:Ô¼¯‚•%ôCs.£tÙ\¥Ÿ!ÆœAÆt¼ŽòÁí;)pß'²”nŒ›¡­3MÆ4Ø{ño\‹<”~c—Eô¨G_A”ËY ¡·BÒÉ}tDxFvöìYõ“*ó5bì³ÏNãÉeø7¯ùóçw¤ )#x<­ZµêNÜCŽ7-™Ìx÷‚ÉL»¸ýu&ÇK&zúˆnŽ8±=ýÑ%MïôTOÇiµ`¶Ãû¨–!ÄË ^:Ý¥ÙXæJ告žåò›y¥¢›7‚nÊ3—/«Y³>Þ'JH¯Iç]ÒY<öÇé)ât¦gB`—®>¦Î»(‘¹ýV²>mÅÅBÄ$Ãäqéì¥Dé4À`3È$Å?]â뢉̲0§³®:H'K=<æÅ+äëpìÿÙì~BdÆŠŒŽsˆÌøF䛎_Ä3¾y7'çé›T—µÙ㸠]ÎÓu&4¯ISÇõ…ÌEJÊyz¢¤‰Œ»läÛ[dfYIÇ-ûj²Ù‚E—7müZäq4±útoÐie¸’$Ð$fäÎÕü£KZÆUº-2³¦@§é†‹>ùè‰/‚€=3Óc´8Í´Of¨ëÐA‘Q±¥rŸÊËõIXÇE ¿"#ÿØÕËL˜X1*·l<±º¤¼ $qËã™nX|A@<!3¹ÒA@H BfIAOê ‚€Ç dæ1—B:"IAÀÞ£IÑ›ìêþ±i]²“ HxÔ®×Ø%Í ™%ÖŽ]z&¢´Kø¹ÔãÇZ&;%.d–Hå!áD&Å8\Eb`Ù3ÓHˆ/^€™W_>é¼ h„Ì4â ‚€W# dæÕ—Ïvçÿþûoõš¥{÷Ø®’ˆ üp)™íÙó/ -‰—Z·5CR§ó뢽Y4aT­ZÕl:“¸J.^¼ˆnݺ!oÞ¼ê’üÖÝ—^zICqU›¢Wðd\Jfzà‡ÆÆM›u4Ùù{öìÁÊ•F“!®âéÓ§•€óçÏcÞ¼yÊò9[nb{ƒvmã¢]ðPÜBf¯½ú ¾ÿM.Z‚çš¿ˆâeP®bôî;€ aÄ.ô,nËÖ¿Ðüù—P¬DY5Ó;sæ,ٓ܃ç_lmJ;wîœYó¾ŸF›)½5k×Ç7“¦&ø:n3FºvíŠ!C†Ø-ýÍ7ß…î¢dϹsçÆgŸ}fê …Q̵‚o¿ýV-™ XøÅ“l+“ ˜°ôèÑC‘ÙÒ¥KQ·n]5;«P¡>ÿüse½I²ø7wî\”)SFµŸ!C¼ñƸs‡mgÅÊìÙ³•~ pŽ9ðÕW_é,ØË3’€ ð„p ™uzû-²>të×ÿfu¸¾¾>8p6ý¶3¦MÁ‘#G1|ÄH³²&LÂçÆâ×UK@„гgoŒþr†}2«W-C*2üɧÃMu¦L&³¡CbóÆuørÌH,Z¼“§L7•qV OŸ>8vì˜\¬Éˆ#0iÒ$Lœ8'OžÄwß}&¬áÃcû[«V-0³mL&­àà`y±`6zR¥Je‡`ùòåà6­IPÛ|‹/LRãÆS³¸+V`ß¾}øðÃUÁ . S§Nxï½÷”•(Ö_¤H‘óâ·")‚À“CÀ-d–!Cz¼ýV|=᫯Ånùâ ¨Q½Í²£r¥Šè×§V¯1ÿùPÿ~}P®\YÈŸ;¿½û`@¿Þ*-þ|èÔ±#xöÆÂ7>“Ö¸±£Q»vMdÏž Õ«UÅ€þ}ðÃ?:m&^½zaèСñÆ&³~øMš4Q³² `̘1˜“ ÷—gj¼œLŠìܹü1xûÃKWÝ~ñâʼnì«+ÂdãÃLd/¾ø¢2øüÈÄÿþ÷?S1^>²…r>d+ä–N÷…ÉêСCX²d -c‰‹gcÓ¦M/kÔ¨¡t¦OŸÍ›77Û 75F›7ãcdzÃ[·n©=ºjÕª©ý0¶cj&ÕfÍš©}5>,øé§ŸLöòŒ:$,ïƒñ ‹ºté¢f‡_~ù¥© /A·lÙ¢öôxÉËÜž ÛË3)€ à¸ufÆãíоÒ¦Mk:ož5‚N—ªÇ(fѬ w錄jç.o£?̘9 =‹çZ´¤G ¾EÖ¬æûu¦Ž8)ðþûïƒgOFéß¿?F­6ýyfV¶lY5³b+æFa¢cÒáSKŽó²Ùr¿ŒÉ‹ÉÇÂËA&^Fò %ï³Y ?P˳-^Zò>#“š±“ð²¥ËÔÆ±bÅÀKà (5öò,Û‘¸ ð$ðá›E -y|(Ì—"Îç™›9ÿ8@~`œKC~ÅÿŽC~²~9#¿Ï̈W²´ Pp"ü  BEJØ/$› IDAT|ŸýZ¨/5·‹\(¹°8Ç? säG’‹&Å>Ý&sûÌŒ: "‚€Ó2s:¤¢PžBfOuiSœŽ€™Ó!…‚€ ð$2{¨K›‚€ àtÜúœ™Ó{ÿºÚ(Ã’4)$ „Ìq §À‰¨%EAÀ™9ˆ2.•Y™ƒ`I1Aà {fOtiRœ€™ó1‚€ ð2{ K“‚€ à|žú=3¶•X¡ß£&¶Š©<Û4hýÊkØ»g‡z5)C‚€ $dfFðñ)¥£ÎQ´ù- L”&ƾÛÑz •Ó^Üe¦ÏÝí%4~Él! df ™$¦/$‹K¥J–Àâ%Öœ$Q}‚Õù ²Æ7õ&XA ^Ž€™ .àyzÃíöm;1þë±ô–؛عëŸx­üþÇÔkð J–.Aƒ?1#ž_,Bíº Q$¤ª×¨‹Ùs¾5Õç%*KÉÒÔÌO/“õ ê·›Ñô¹”ù½«W¯Áž?ÖÃ3Ò9ôbL~ç[Ñb¥Q­F²Ç0ž³”ØjOç‹/x Oýž™+.ÄzÑaÍšÕÔ뺟oñ9YŠJ+˜55‡jê”opçöm¼×ãCeµ©ë;påÊ|Ïߟ_3g.“§LÃÈ/>GzAdPPFz[l¬¿ôÇsd4øÓaÕ¿/F|®*N˜8‰Lòý¨Lö•/_WèuÚGÉÔŸ–„ÚÓåÄž423sÁàý²Vd¶¥u«–X±r•z}·±©=º£XHQz«le¼Ó¥½Ù5ÖìÛ­[·Õl©É3Á¯/]ª$4¨gªš‘ §°dΜ Y²dVΔI>½z¢l™ÒôöÙ|Ê4=3~¼ï6}Æ,|2t ½ÿÿYeêë¾ür+“Ê„Ú3”€ ð„™™“/[Y¿ví:žiÜPi.[¶Œ²Û¹Ž ·hÞÌÔZÉÅMáRdóúx0zõîïp/ýü}?9jÆO½,Ýᤠ à™<úä{fÿ¼ªWluiùÊ•MZø$S ï{µïð¶ÚÒiVVÜ9~ðà!ðž–ž}1¡Õ«[G9¶ÄÞ¥kwEnœïKy,Ú6¨ŠØøÇ$ªÍø«Rü¹ÅTº€ZŠúãï¿·«e¦)ÃHL{†jÜŽ€™!_OKIÞôׯ}µú¢E “ ·å¨\©¢JâçÏ2e Æ;w1uú t|³ƒJçeá.:ý¬]»üÈ–åº ¿)tšèræÈ¡Ê­]·µkÕPÙ•5IÈŒ›ä{û­ŽødØçŠHyI|ýúu²ßyØ´o–˜ö¬õAÒw! dæD¤y‰Ù°~ýxDÆM4iÒX=&¡ÉìÍö¯“ Ëî¸výx“¾s§Žª'iÒ¤Æ"E~<‚gz¥è`ÒÄq¦^òÆ?›Ð9êKôî{Êië ڌߨ1_aæ¬9¨P±œ2ã׫w?“¾{¾O¦ÿÒà ÒwáÂd&KêmÚ´6å'¦=S% O§ÞÔ?§ÅÏZ9*lÏy8ªCÊ ‚@|è^L’©9™™¦±æBãƒ+)‚€ à=<õd&³,ïù°JO{È£öБ³ÅK–› >~ü8ˆ’à ¦L'øÕÔlLwÑ‚Ÿœ¨UT ‚€»ð˜™Ù®vãçÛ“%Kb‰«Fõªxãvªèòå«eªÆË@-lyH/ uZBùºÜ”©3°Ì«,ߪñ]Nû“&OÅ–­!cÆ ÊÀïˆ/FcÙò•:Ûa?¡þ½C&å¦MŸËW®€­9ݺu‹,ŽÏ&»™ ôü°˜ø£¢¢Q¬X˜h׬]çp¤  \p™U­Z?Ì›‹àà lܸQ aŸãœÎù¶$""«Vþª²Ÿ¾žmò Øv$[ߺõoS5ãR°ã›íÕÒИf [Ë׊òäÎÍ›×cÅòÅèúNglÓ Áæë”Ó„4×pP`³¢E†½þ±íÊ-qc]ºäÕ·E ÿ§4¬^³GŽUá³gÏ)¿ÓÛ±lÉlûë|;w–EKž>œFf ÖÄñãЪÕKøä“O”Ïq{DÆõ6mþ·ïÞ…ŸŸš<Ó2¤Gºµ8 K–š/5Ubÿ5oޔ̫¥UZ´=J{*7j È• –Ã,‡±W%Ñyûöï7Õy®yK5ËmÞ¢¥)mï¿ûT¸FªÊçåqý†MУg/\ºtÉTN‚ÀÓŠ€Ó4¡µ{ãÍgdô%K–ª ÏÐjÖŽ%‹‡ª4ž•|þÙ' ÔÅ“ìgÈ!Q:xIçˆh“u÷îÝs¤¸Y]—÷ K—.e–Ç‘ÔdO“eò7ðó/ ñÏîÝØ½û_¬ZµF¹…¿ü6â+"<­8ÌH&´}ÿîtˆ€Ø¢÷†ß6›ð 5…9À¤¶víz¼ðBì£|³óz߬œŽ$”¯Ë%Æ_¿~Þí»]¿a£ªZœö«´¤N÷ïß'#ºUÒæßÿÐYñ|[ýÓÆcûh@_“ås&ø?þØ‚jÕª(]GŽÅ›ÞPîÆ¨R­¶JßOBfñà–„§—ãçèLjÕ¯«©N1wnߪ–˜ÿ7ßê„?ÿü ‹—.5‘Y‘Â…qôØ1|3i ÖÉðÒϸ•P¾ÖÿØÇQ·~cUåÒ¥ËÊï@ɵT­V‰¿›÷þ;övÒ½-±Õ¿jU«€õlû{^}­= *ˆ”4¶“§N«ÓÌ¿¶l“æËmÚ©}È9r˜-/ *d«IIž œºgö8ˆ-;Ŭ\¥¢‘±®F *•[¶ü«W¯©ð§Ÿ ¢“¾"ꔓŸCÛ»7v/IeÒ¿„òu¹ÄøÝ»½ƒš5ªÓéâmE$ú÷1‘+ë:x Ê•+«T^ ý« _µ©ÞVÿxÆ6sÚT¼Óå-äÊ• 'NœÄÚì/^¼>èñž ›W^nM§ªÁ3´›7o©ÙO‡2ÍÜl6,‚@2GÀGïÕð8é†ò! .EœÏ37?rþq.€|Þ¼b—†\E2¢;†|A@’„=ÊÕ—ì"Ç{MaqŽ7ÏÃã\ù‘äx›ŸÙŠ&þŠ!_ÉŸ™éŽˆ/‚@R2K zRW<!3¹ÒA@H BfIAOê ‚€Ç dæ1—B:"IA@È,)èI]A@ð„Ì<æRHGA )™%=©+ƒ€™Ç\ éˆ $!³¤ 'uAÀc2ó˜K!¤ d–ô¤® x Bfs)¤#‚€ „Ì’‚žÔA@ÈÌc.…tD’‚€ËÞ4«;Åææ+ôŽ´ÄV1•ß³ç_´~å5ìݳC½™Õ”‘ÌÆq=zì©s2»„2'#à–™¿?ÍQ—ÐøÎŸ¿€žöE¥*5Q$¤*V¬†WÛu ×koM¨ªGä3 1Ákƒ-ÆNÙË3–ãp©R%Á¯Òvôõä–õ-ã‰iÛ²®ÄO@Àå33g’ ³Õ§¢!E0kæTdÏ–ׯßÀ¶í;pûÎmg6åѺ~)p–,™=ºŸÒ9AÀ¸ef–Ѐ† †M›6%TŒ ™ü‡sçÎcø°OQ–¬›gË– %Jü}®YS³úÿìރ皿ˆâeðRë¶8}úŒ)ŸMÁ}Ø«/J–®€ •ªcô˜±ˆŽŽ5'·vÝzÔªÓÀTvÑâ¥j&¥g~L$¥ÊTÄŽüvßø²pÑS»å*VAï¾`4=ÇK`n[[|×ZlåéYÓo7£és/ X‰²X·nƒªÏV¡´üNVœê5x†t—Ç ÁŸ˜¬ÁëúƲ:MÏmµ­uÏû~>5n¦ð¬Y»>”™jÂŒËü²`j×m¨fËÕkÔÅì9ßêªâ nAà‰“ÙèÑ£qøða”,™ðÞZ¦LÁjF²bå*Ój ¥‰'cÄðaøuÕRø‘•£¡Ÿ32t9†dkrêä X°p1fÍž«ò+U¬@V.›ÌÆ1i¥OŸÞD^VÖ¤˜L­‰¯¯€M¿­ÅŒiS”á‘á#FšŠÎœ1E…ÙB:/Ùi±—Çe&O™†‘_|ŽukV"((HW3ùsˆ@¦NùsfM“òŒ™sLy ìµ=eêt0™ 2PYuÿrÌH,Z¼„ú3]©½rå >8o¼þšÊŸ>}2òçË—P“’/8'Jf“'OÆÉ“'1þ|Z2eIp`Y©Ì°O‡à«qP®BU´%“lc¾‹Ãd©ÈRú÷ë£ìH,P]ºt¶m;U¶Ë¹|Å*|üQ?°íË*U*“E¤Nøùç…*?88XY<Ò3¯í´„}«cµ”å;vìDy2¶ëïÏ6^âKË_@êÕ#Gveû²_Ÿ^`CÆZ2Æ Μ9“Z&—Šöò¸~Ÿ^=ÕŒ4þ|ˆß~ÝQ,¤¨iL h¶ä¨Øj›ív2i;µk×DöìÙP½ZU°…ª~øQ©g«U¼'Úä™ÆÈ™3JÓ~^ƒõTžüÜ…€[÷̾ÿþ{Ô­[yòäÁöíÛ1eÊìÚe}¹f €WÛ¾‚_x^‘ʃñ÷ßÛ0}Æ|6l(Ú¶yÙT­hѦ0Ïèø¦ Ç™3gÕW²dqS~)šž<5F-›|}}Q¹r%줛—cw¯·k‹)S§);¶ïDe"@[²ŒñŽýj²Êõ™¼ô¸ØÆ'›ÚkÙª 7j@DV_ùŒ¥ˆ à.’vw%²—³fÍ»ヒÎ;«Ù€lÎpì© L…:uj)÷n×.˜6}ÆŒkFfÖn$¾µÄZÕÓ1s¿r¥Š˜2m†"Ì Ë);•òçÇž÷bÇ®ðÚkmÍ+ÄÅø¦oßá-4kú,zöx_ÕcÂý gµ,N*™ùù=Þå²6ÖèèGXXL\¢.÷ãüï–Û–ÂF˜Yÿÿ~œ‡?þÜ‚-[þÂGƯ¿®Á×ãÄ ¡%^wnýêܸq#öï߯ÜúõëѺuk§Œ¬ÍXîЦ¾ÞÄ·§4oÞ<êæÛO„µa3jFù 3gÍ/Q{÷î‰^½•7þû÷ë­È´wŸ«ªªþѽ<݆=ÿÍö¯ÓaGw\»~ |ѹSGUœñþzÜ—øxÐ4lÔ+•ÇtWk}öÚ~§ËÛ¢ÓÑÙèÿÑ µ÷R´^}µªž&MjlXÿíŽWûhü@契ã´jñ· àÃ7¨Z2øP˜gk¼bŸÉαã©T`œãéIEºÇoSøY*c6 ÆepôÍPYÉäƒqE_ Ÿ†’ ‹sÉsäG’ã‡B£Ø'n1˜[ffz_…AÀ%¸œÌd–å’ë&JAÀ·X´/QA@œ‚€™S`%‚€ ð¤2{ÒW@Ú§ à²=³BEJ8¥ƒ¢Dž |pg8,tJ'\ù´‚ËÈŒG~üØ£§ì‚„(· ðǦG/GpÖ}lÔéŠAÈ2Ó¨ŠNA@p;Bfn‡\W dæ TE§ ¸!3·C. ‚€+2sª¢SÜŽ@²$3m¬ƒ xÃnGW·!àv2ÓÖŽøÝ[–¢‰G[ 2æÛË3–ã°Ø”´DDâ‚€sðd»µn'³…‹£TÉX¼d©sеÐb´))oë°G¢‚@à{‹íÖÞp_Ù­ýó÷ ˜7o½O°¡GØ­u+™1«o'+Iã¿«^'½“Þ§o{¶må雨”4")aAÀù$Æn­ó[OX£K`Ùü’¥ËP³f5õùç[<‡E‹–‚íTjaÛ:¿«l/Zšr³—ÇõµMI6ºqùò­Òäk›’wèþïõø¹sçF×w:™òíìµÍ6%Óë¤Ù¦d‘"…ÉÊÓ) 4T©{¯{Wh›’}û|ˆæÏ5ÃuzUöÕ+±o˜µ×¦ä ž†€Ñnmû7Úß`ìIâ֙zé%5þÖô~z6zñàÁ¶l7r{yœ/6%AÀu$Æn­ëza[³Ûff»wïÁµk×Õúš»S– é²AÙuôîx60’T›’IEPê  #à¨ÝÚ„59¿„ÛÈŒgelM¼T™GAøù|à 2›’ÎÿpˆFAÀŽØ­µVÏÕin!3¶|½|åJŒ&ËE|’©…÷ÚwxW®^OamÙnäòöò´>{>Û”¬Q½š*b˦dêÔ©UþãØ”,Sº”ÍæµMI¶+Y½ZUtéÚ_åi<›J%CðŒvkŸ¤yA·ÙzZJ²ð¦¿¥Ui¾tér2‹öŒv#kת¡Œõ²ù6{yª@ÿø¹6ÞÀ¼s箲“ÙñÍXÓrF›’l~mBΜ9ÇL›­¶6%yïÍÑEDD`ÿÊ®g÷n]Á6%wÑ©míÚµà—2¥Ø”4CV"Þ„–Ùk‹fÍÀÛ:|HwìØ1r´Ãvk]9^·/1Ö¯Èx`Mš4ÆÂEK™Ù³Ýh/πĦ¤#(IAÀ6β[k»…¤å¸Ìn&¿iÖY/uKÚ¥¶ $~‘bÇ.=Õ›fuk¶,¶%Õn¦[ÍH, R^G2s))'€™G_éœ 8Š€™£HI9A@ðh„Ì<úòHçAÀQ„ÌEJÊ ‚€G#àÒçÌ\m'Ï£‘•Î Éo¹]FfòbÄdòI–a<ÕxÓ}ì22s¶Y÷§ú%ƒ’Îz×Ù3³DDâ‚€ à•™yåe“N ‚€%Bf–ˆH\¼!3¯¼lÒiA@°D@È̉ ‚€W"à²ÓL¯DC:-vxœG5Üõdƒ™ÝK'™‚€ `‰@bÈéqÈϲ=Gã²Ìt))'€™G_éœ 8Š€™£HI9A@ðh„Ì<úòHçAÀQ„ÌEJÊ ‚€G# dæÑ—G:'Ž" dæ(RRN<!3¾<Ò9A@p!3G‘’r‚€ àÑ™yôå‘Î ‚€£ÈÏ™EJÊ ‚€BÀ?QJ äBf‰AKÊ O9‰ù]¦»¡’e¦»—öAÀ%™¹VQ*îF@ÈÌ݈K{‚€ à„Ì\«(w# dænÄ¥=A@p .;Ít•¡O—  JAÀë™™×_B€ 0Bfò9d€Y²¸Œ2A@2“Ï€ $ „Ì’Åe”A‚€™|A Y d–,.£ B„Ìä3 É!³dqe‚€ dæâÏ@á¢%ÁnÎÜï\Ü’¨žn<†Ì"""0mú,4oÑ%J•WŽÃœÆyž,ö«L™Ò`—5kO‚ôMðz\öÛÌÄ ŽöovÂλTµüùò_Í{øÈQr_aã¦Í˜÷í,øùù%Fíc—åþøûû?v}cÅE ~2F%,.BÀ#ffsæÎSDÆ6cúd¬_· ëÖ®ÄÌS©1ÉÍžóh™¦gB&NFï¾P²tT«Q‡Ê|kÓƒ0æË±¨[¯1Š+µêcô˜±`²Ò¢u}=~"ºuï¡f„S§ÍPÙû÷@›6íP¡Ru )…²å« Ã›oãß½ûtuµ„Ô‘á#F©8ëÔ¢õ[.3ÿüs+Ú´}Ý4 å0§E×0qúôûH³JµZ˜ÿãÿLÅöq©nÉÒåÕë5xC†3åK@xZp™mÛ¶Ãt#ë›Ðès¾-Y¶l¹Êz¦I#Ô¯W×T¬^Ý:hòL#_¾|…)]&O™†mÛ¶#((#®]»Ž_ŒÆ²å+u6ÞéÚ–©³qùÊ-R·nÝÂô³Ñ«w?S˜2u¶ý½ äGÊ”±ÖsçÎãŸ=ÿ"sæL(V,D-w·lý:¾Û·ï¨ª¼„Ô’3gµ¤4¦é<£Ï¤õæ[±ëŸÝB¦LÁ*Ìi–„Æõ¾™4U3 7nÜTduúô¥²ç‡}Tݨ¨hÕǰ°0¬Y»ÎØœ„§§‘YÕª•ñü¹êæÜ¸q#Øðû|³r:çÛ’'O©¬Ò%KÅ+RªTlš.c,P¢DqlÞ¸N9M sã6Úÿ&bbâaYºä¬X¾‹ÆÎhV¯Y‡#GU!OîÜØ¼y½*×õÎ*¯bÅ øgç_X»z–/]ˆµkb õÞÝPlÝú—*c\Fv|³=8nL3k$.2‘fZ,åÊ•U}ßôÛZæ´‰ßLfÏLBBŠ‚Ëüòó|SúÎ]ÿ¨ðÙ³ç”ßéíŽX¶d¶ýõ¾;ËTN‚ÀÓ‚€ÓÈŒcš8~Zµz Ÿ|ò‰ò9nÈ’tãF "E å8ÌrèðåïÛ¿_ùüï¹æ-Õ¬‘´ìý÷ÑR‘Óš7oŠ´iÓªl_ßXX†?Ä€ƒQ¹j-U¿^ýgtu\½vÍNl`߃ªJ£†õMýoØ žJãe£¥4nÔP•ËŸ/¯)ëîÝ»*\£FUåóÒ¸~Ã&èѳ.]ºd*'AàiA v=åÄÑjBk÷Æ› ÎÈt³¼´;rôöxD@:o)qKᥕ-Ñ&±x®téø3¾ÔiR›UÍ!ƒYœ#=>è½´?Æ:ŠÑìÈŸ–yg‰ŠŠR~RþEF>Òao,éÒÅ’,·=¾ÉßLÀÏ¿,Ä?»wc÷î±jÕåþò#Ê–-£‹‹/${œ:3Óh1¡íûw§Ã3²çŸo¡ª®]³^\j=›6ÿNû?ëU´E‹æ:Ùä¯_¿A‘ Ëú UzqÚÛbÑÆ7ýGúš–ÿûqÞëÖÕloNU°òXºtŸŸ~2ØJ)(²ãŒÐÐûVó-K—,¡’~ûm£©ÿf)]êÑáJHàß:ñ}³Ã˜ðõWXºøSiÝwS‚dŽ€Ógf¯ÀÀ@LÐïøæØ´qvÐ>Pç.ÝP ~"@ï“UªToulOϱÿŽ£nýÆ*ýÒ¥ËÊïÐþuåW«ZU«U¦Mýxõµö(T¨ RÒÌæä©Óê4ó¯-›‘:µùì̲>4àÇCfÎú›ß‚ëtÈ`MŠ.Œ£ÇŽÑFý¬#‚唽}³÷ßï®öîÛ¯úÏ3¿‹c—†ï¿×ÍZ6Ó^¦ÓVÞ—Ì‘#‡Ùò²P¡B6ëH† pÉÌ,±@ñ3]ßÒsd}ûôBHÑ"8wþ¯¥ì7IDAT<ÎÒI"‡9ÍÖ3fÝ»½ƒš5ªÓ)åmuCèß/¼;Ëc‚˜9m*ÞéòråÊ…'Nâ m–/^ ôx2¤O°›cÆŒTåyvMKÚIߌ·ZçÓOÑii5Ë:@ûaz)jµ0%ÖªUsgÏ@ÅòåÔéäõë7T˜Ó8/1òÊË­‘1cFð íæÍ[ê4öÓ¡ƒP­Z•Ĩ‘²‚€×#à£÷^x$D4R¯ÒæÍ&:ž¹ñ“ªþq.€|žr±KC®âGŒ!ß­Â|° ü¸?øQD¼º¯ûÒ(v‘ %ç’φ²ãŸE’ãÍrÞpŽ&þŠ!_‰GÌÌtgÄAàq2{\䤞 x.;på(iiëJõ¢[¼™™yáE“. ‚@|„Ìâc")‚€ à…™yáE“. ‚@|„Ìâc")‚€ à…™yáE“. ‚@|„Ìâc")‚€ à…™yáE“. ‚@|„Ìâc")‚€ à…™yáE“. ‚@|„Ìâc")‚€ à…™yáE“. ‚@|„Ìâc")‚€ à…¸ü‡æúÝc‰Á&)?$ßC¦áZ¿òöîÙ‘à›dÓ§äZVðJ®Wöé—[ffüþ4G]B—àüù èùa_TªRSæ­X±^m×Áª½É„t=MùLZüÅòð!¿ëî‘”"›ü ñļæüQm žƒ€ËgfÎ*.a«OECŠ`Ö̩Ȟ-+ø•ÓÛ¶ïÀí;·Ù”×èÒV¢Œ–›Óy6xœ%KæÄT‘²‚€G"à–™YB#6l6mÚ”P12òØÊøðaŸ¢,YÏ–-Ø0¿:û¹fMÍêÿ³{ÙË|!ÅËà¥Öm¡-€s¡{÷îáÃ^}Q²tT¨T£ÇŒEt4¿‰X»n=jÕi ÂüoÑâ¥jF£-3y”*S;vòÛ}ã‹=ÝzvÄFˆë5x†Ú/Aƒ?‰g¶nÞ÷óѨq3Õ÷šµë+‹æºZÇo7£és/ X‰²¸zõ.Zbo¹ŠUлï5NÝC^z³ð˜y†¦—ÿZßýû±–¥éÿŸ[¶šÚ²Äö—‹P»nC5k®^£.fÏùVwA|AÀ¥Œ’%cßëoo´™2+³n+V®ŠG–õ&NœŒÇá×UKáGÖ’†~:ÌTdÈÐadämÉ©“'`ÁÂŘ5{®Ê¯DVÌÙÒÓ… UœI+}úô&ò:xè0"##™šötëbsèŸ:åÌ™5]‘猙st¦L&³¡C*kç_’Q•E‹—`ò”é¦2˜}2òçËg­IœŽÀ%³É“'ãäÉ“˜?>-u²$8¸¬TfاCðÕ¸ (W¡*Ú’ ¹1_ŽUæà,+÷ï×GÁ-X ºté„mÛvª"¡¡¡X¾b>þ¨ØÆf•*•É‚S'üüóB•¬,é™×vZ¾ձƒZÊr;v¢<×e‹R–’n]¾Gîʨ°n{ÍfX"""i;µk×DöìÙP½ZU°Õ©~øQWW~Ÿ^=¡æÏŸdœ¸å‹/ Fõjdr.;*“i¾~dÕŠg€Z2Æ9Μ9“ZVZ[Z:Ú[ز•,ÞmòLcäÌ™CÙmРžî‚ø‚€KpëžÙ÷ߺuë"Ož<ؾ};¦L™‚]»¬/×lúÕ¶¯àÅžW¤rààAüý÷6LŸ1Ÿ жm^6U+Z´°)Ì3:&Šððpœ9sVÝp%K7å—¢YáÉScÔRÓ×ו+WÂNš‘±;^½ޮ-͘¦);¶ïDe"@k’n]§$-µpÛ§NŸVmŸ b Ã+mcmê2LÜwžja“yFÙG‹Ç~5ÆýûaJŸ®ÃûbŽˆ£ý·…-Û&-W®,Z¶jƒÆ AƒúÊgLEW#àØ§ÜI½˜5kÞ}÷]tîÜYÍÈ `u†“Ps©P§N-åÞíÚӦϘÑcÍÈÌÚ Ä¤ %ÖªžŽ™û<³™2m†"Ì Ë)»”l˜xÏ¿{•¡â×^kk^Á"fO·EQ³httlÿ~œÿ2ÐÒÖRŒ›ü~~.`ûo¡YÓgѳÇûª¿Lôô죖㎒™n/¡þÛ–ûÇãÿøs ¶lù }<¿þº_s»5B=ñŸ"Üú•¹qãFìß¿_¹õë×£uëÖNºÍRîЦ¾Þ$·§4oÞÚþõ×_1xð`µ×“˜>óRª{žX½z­:¼xñ~ÿýO ÿbžiÜÀDFöt¦I“-š7ÃÈQ_ª½6Þ›:}Ú´yD¬¹Éz6zìƒ÷Öx–ÆÂþ²å+Õé)ë°&ŽèæzèpâÈÑ£êPÛnÕª¥RÇ{_ï¿× ƒ†|Š%K–Ÿ©;uê´"„I“§ZkR¥ñþñì9ß)kð6lÄÌ™¸PÎ9TY>­½qãY?7';Ît´ÿJ‘•|}æ~;lj\yɺnÃoÊš¼þ’°RE’§!ðh­â4• +âoï~ýú%\Тß™iƒ~Üø‰êfáÇ$råÊIDÖЦº£Â‡ƒ‰0ZÑ#L m‰Èx“ß(Uhßl-‘ÊÐ# ,¼ÆËÔ*UbÉÍXÖvD÷›í_§C‰î¸výšÚ¸ïÜ©£IÅ;]ÞFppfÌœþ /CŠÁ«¯¶1•± ą5æ+Ìœ5¼4îÝ»'zõ~„1oü÷ï×[‘xï>W• k¿´p¤ÿ–íëxš4©±aýo´w7^íññ¹“&ŽÓÙâ .EÀ‡oP-´Œñ¡0ÏÖxMÂ>“9>ºc@.0Îñô¤"ÝcÈ·)ü<“± ›ã2¸ Ön²„êyC>?Ó%?µò†+%}|Wô¥vùD0”\XœãŸ¬„ǹòùŒ bŸ¸ÅD`n™™ÙÚë¡Îˆ‚€ à\NfÉu–åôE‰ 8 —“™Ózš ñ3XBîÉàBÊ<·Ÿfz$ Ò)A@ðz„̼þÊA€pÙ2³P‘‚° x1|pg8,tÊH\ù´‚ËÈŒG~üØ£§ì‚„(· ðǦG/)pÖ}lÔéŠAÈ2Ó¨ŠNA@p;Bfn‡\W dæ TE§ ¸!3·C. ‚€+2sª¢SÜŽ€™[ýp Š7#àV2c#!ý DµuP´XiÔ­×£è ±üîyOMZb[Ò®†ôÁðd»µ.}ÎÌx1Ξ=§L¾•+_“¿™ Œuð;ï¿û5¶lý‹^·ü=¢Me¬â´°Ø–t”¢è)F€ï#O¶[ë¶™Ù°á#P²TqLŸ2I½všßŽZ«f ²:ô­²ï8cæ,õ1ѳ£äl[ò)¾dè^Œ@bìÖ>‰aº…Ìx¹iãïèÒémõþ}ã@•ÅåËW“‘\mKš R"‚€!»µObXn!³3´Ääßx±JkR¼X1enÍø;°äj[ÒÚø%Mðc·öIŒÇm{f‰\rµ-iÍxpb±‘ò‚À“BÀQ»µO¢n!³¼yr«ååá#G•…nË>rùÉj“#¯×Ö¦Ò¼Õ¶¤åØ%.xŽØ­}crË2“M˜Õ¯W‡ÌŸÍŽ÷J‘°°˜÷ý|4oÞÔlüÉÙ¶¤Ù@%"x9‰±[ëÊ¡º…Ìxƒ}Œ½û [·ؽ{Øæ%?’ñzûŽH:5ºtîd6Îäj[Òl¼gØ­uåpݲÌää¡¥æ’E?cü„oеÛûd„ö–zÖ¬YÓgÉðí»ñž1K®¶%]y1E· àJœe·ÖU}t™ÝL~Óìã¼ÔMlKºêR‹^AÀqøEŠ»ôTÛBs[kIë´eÔ'©v3ݶ̴68IAÀY™9 IÑ#O·í™9:J±-é(RRNŒÈÌ̈†„AÀk2óÚK'#BfF4$,^‹€K÷Ì\m'ÏkQ—Ž ^„€·ÜÇ.#3G~géE×Sº*<•xÓ}ì223¾Îç©üÈ AÀ*Îz×R¹ì™Y""qA@ðJ„̼ò²I§AÀ!3KD$.^‰€™W^6é´ X" df‰ˆÄAÀ+pÙi¦W¢!»<Σîz²AÈÌLA@°D 1äô8ägÙž£qYf:Š””F@ÈÌ£/tNE@ÈÌQ¤¤œ x4Bf}y¤s‚€ à(BfŽ"%åAÀ£2óèË#G2s))'€™G_éœ 8Š€<4ë(RRN€µlÙ@¸+Dff®@Ut ‚€Û™™Û!—ïFÀÞO”¬ÍÄÜ5Z™™¹ iiGH0YÙs3ÄbÅŠ=Nµxu„ÌâA" ‚€ à.œEdÜ_!3w]5iGÌ(Z´¨Š¯\¹Ò,ýq#Bf‹œÔÇF€‰Ì××Ld… zl=ÆŠBfF4$,.G `Á‚fD欥¦™Ë/4 |ùòÁßßß4#s‘±þÄ<šCå­9ÝO3ßU†>Í‘ˆ xöˆe" 4//ýüüTÑÜ¢}Kü8ݪØ"3c­TûѤ‰Ãì³AÀ*LTéÒ¥³º´ÔDÆKÍ8Ñœ¢ùEsŽöu9ŽÇ[d¦ êJFe6š.+¾ &˜¬ôž˜‘¸ôãÆü¸JF^±Å=&ý–GŒJucšÐ8."‚@<Ž?n––Pœ 3ŸXrŒ‘ÌôYF|Œ? ¥úPž­±Ÿ"Î1áñ»prã‡ÃâÜ}òCã‡÷Ä¥? ŸËsÙˆ8§uê½µhâ/Ýž‰T¨l<áB–N3'+cw˜‘Ƽ4™iBÓ„È>“¥vBf†ˆ Œ`ÎÐ<Á>“ûšœ4™qÜHjLfš[´Kâ¸UÑ3$c&6’Œ¥2݈nTw–uñ¬KëÔuyVÇåu¾nר—ïF@“ ósß÷š#ô,‹IŒ NÇ5ÙiNÑ£ùCûTÅ$ºS‚&S‚E@W`_7 ;h중È4Aq.Ãåy0\†‰Nûº%‰‚@2B@ó…&4öÙiòbß8K³¶Œ4ò‘‡lÂd‹Ì¸²vZ©î fOÝAÝIË™‘ôxFƳ3]†}&3!4ADHFXòó“ó†&-&3KgäÍ1\WóŽký”e.¶ÈŒKq%]™•i§ãpäd,£ó¹î¼%™Q’ˆ $3ŒÄ|À|¡ÉÊHhÖKNsÍ-šGØ×DA/qØLÌÈŒOâN4u!#¡•ëÎir2ΰtgŒDÆ33í¸¬&?ÝŽø‚€ |ÐdƾæÍšŒ¤Æׄfä#™P2ždr¢™Å•Ò5A±R³ã07ÆbIHzœ¯;ÌúÙqY½ù¯ëiý”%"ÉæÍÌLdš;Œü yÂ8CÓùìs££¨­_Ç•oÌtÝ&]™;ÅÂDÄÐÂùšY'wDÏÄ´Ïz4‘±Ï"„‹ƒü’ Ì,LBšC8¬ùAûš´Œ32ÖeØ×Ü£uiý”e.öÈL—ÔJtœ}# é|ö¹qÖ© LûšÄ¸žSÐLÇEAÀ»Ðdcä6’‡™ÐØgÓä¦Ó8]“ &FJ²-¶ÈLwH×ä8;n€EûÖyì3QqûLdìkò2†5jŸŠ‰‚@2@€y€Eó‘&#ÖDÅÄÅišÈ8];Nç°ÖcéS–¹Ä#3+‡\ƒ±r&$öµè8®ó¹š¸´oœ‘qXHŒ’/š4whŸyB;æ Ö32Žët]‡}3±ÜüçÌxdf¨OåqCFÑeØgââÙ˜&3M`œ® L“˜¥OEDA  `äǵÓÄÅq³¯‰ËHbºœ.£ëkŸªÅ{d¦K³£àtÎ×30ÎÓÄeIb:ë°hB‹ÉA@H.9CÑ7r‡µã2šÜŒåuØ.>VḚ́Ôd%L:ì[ w€EçsœÃÖHL—Ó¾#!"$_Œ¤ùC§iŸ9Ã2lLÓyì³(ßÚ“3Í^Ä F1<@«É‡}Ö¤ÅU8Ì¢ÓŒåtycZliù/ÉMH§ÃÆ:°Ed¬Ü.™©±ï8SAþGbIN¶âº¬1_)‚€ ðT!`$$f8¬­¸N·Kd\(A2S…šŠò?K’â¸N3æs˜Å^^l ù/ÉKÒ2ŽË2OÇmù +wˆÌTAsBSIü/Nì•1‹[ƵñA y! ÉIÊ^ܘg ;DdÜ€Ãd¦{£*Ù'6]THK#!¾ 0#«¸ ³4{{cFEÆðc‘™™‚øÄfÌ6†…ÜŒhHXxz0#*[Ã~3êJ2™•IX'…€~¤âIµ/í ‚€ à„Ìœ£('Àÿ©Cg‘;@#IEND®B`‚gaphor-0.17.2/doc/manual/gaphor-treeview.png000066400000000000000000000112021220151210700207040ustar00rootroot00000000000000‰PNG  IHDRšðdrq pHYs  šœtIMEØ9-þÚÁ;!IDATxÚí]yxM×ÚÿmÌ•‰! bžJ´yRIÐö1 ÅÖ˜¸ý %ZD$ÑšZ¡EИåRó}„¯·*nIT¤hd(DšI¤ø¨ädZßdËpÎÙkïs"'ñþ<û±×^{ïo½ïÞ“-Ü/¸Ã@ Ô1ŒNÿûõ¡î‰#&Ï¢ž ÔŽïÝ‚&Ô „"ˆFhds4}£­«ÜzôÖÏPòè>b¢¿‚««+A IÑäíGoL_®õ]ŸG %åœñÊ+¯$ÈtÖ Ê+ÊÊÊÀmãUÂØÄ¬NÞ¥9L§.¸™v±«—h}ÇÀƒ‡qààáçs·ŠrìØkëjG°Ÿ¯/Nœøw­9±‰JTE$͆@´ ~Ĉ‘£¸?v^^žjã<<<°ö‹(Ù•y{¨?’JƵñÖ6ÖˆÞ²ÁAA$¹†:GóòòÄñcGamm¸¸8Ü¿¿Ökkk­$APf‘KT*<~ôXcü—Ö#:z+nܸ¡yîW^Ž¥KCѺ#¬š·ÀÄI“ñèÑ£§‹”Î]‘‘‘!¾»o_¬xŸžž·Î]5·¶n݆Ž:ò™zõî‹óç°o_,ºvëËfVè×ßiiibšââbÌ †­=llí1ûŸB¥R‰ñ*• Ó¦Ï@ó׬áèè„/ÖEq·£Á/¼¼<±=æk"11±Ú‹‰‰‰ Äö˜¯µ’¬.aee…Í›6bÊû ´´Tí;«V¯Á•+Wq)é"r²³`ff†ÅK–üü|qþ| ;;sçÍÇ“'OçΟ‡¿ŸŸÆ²ÏÄÇ#þÌiäçÝA@À8 1'O¾=…ü¼;1bf‹ï‡…-ÇÜ;ÈÈHCzz*2of"<šÏ…,T×¶m[–zíg1œÅìíìX‰ªˆ=rˆ;†•¨ŠØŠ‘ÌÖÖ†mùš•¨ŠØèѣر£‡5–}'÷¶~p¿Pí3###1ܪU+–‘ž*†ÓRS˜ƒƒƒvpp`¿d¤‰áŒôTîvÔl³¡_c¢˜ZWU³­Y³¦Þ5YM,_†ÓßÄÄ µârssѽG/›˜ÁØÄ ŽŽNÈ/(x{{ãâÅ$Àþý°mÛVìÞ³”t ÞÞÞˬº@177Wû¬¬¬L ççç£}ûöbØÙÙùùùb8//íÚµÃUï¥ÚѨöÑ*ɶråJƒ"cçŽí ž]kÞÒ²eKÜÊü%ª"ñR?5–––pnßGŽE³f–x{Ø0<~ü'O‚«‹ ,,,ôVG;;;ܺuK gffÂÎÎN ÛÛÛW‹ÏÊÊânG£Û°õòòÄŸY™E²JtîÜ3fLÃÜyó«=Ÿ1c:‚‚fãæÍ›(++CZZ&Nš,ÆûùûaÁÂL˜00~üxÌ™3~þ~z­ß¸±c°`á"ÜC~AæÏ_€ñãÄø÷&ŒÇ‚…‹pï^! îaÁ‚YíhTDSSSƒ­üìà`äÝÍ«ö,dáx ð‚ŸÿPX5o¾‘#F<'š¯/òòò0fô@@À8ܾ}~¾ƒõZ·ˆˆpØÚÚÀ­stîÜ­Û´Æòåabü²e¡°¶n×èÓ§/(« ÂÁ˜(¦oÇÇÉ`Íç_ÈNçëãåaË0fÌhÚxjD8¾wKÝœ äÝÍ…ÿ`TTTÈJgjjJìuB´èÍ›ššVmÆU##tïÞ¤BDニ‹ \\\¨w ü‹ˆF ¢D4Bý,r3oPOêžhä·Ox!DðB4ˆh„—Øtð…ÝzÉNWòW.V†/S[G’0™Ni¸õì#ùCdu2¿\ÿNŽmH¤Ñêå(//oíríÞ¿§^"¢5H0öìjÕeD´úD~n’Î~';]Yi)˜†vuìéA^ˆÙUë‡È{zà×”‹zmCæ­,¬ûj .&_Æ“'EèÔÑÓ§LÂP¿·^>¢Á@W-,ÍÐÉîUÙéš65zÖ&õízí5+Ä8ŒIƨÓ/z«ÿŸÙ·1ij0‚§OAdè"˜››ã—¿"fW,†úùÔI™¤Ñ uë6xçÝwe§[·v•h>ÕaÙ'aÜäéðtÎíÔš±òŠ ¬ß¸‡À“'Eðñ„Ë>†……9|ß‹Íë× ƒËÓ_9?ñ¿ñîPÀo¿ßDð¼E8}â6lþSßÃ{Ï=†{të‚/?ÿ´ZÝ*ï³srñÙÚõ¸˜|eeeèß·7VG†ÂÆÆp.áG¬‰ÚˆÌ¬lØÙÚ`Ö´@Œ.ghRÙPC¼tœ¦iÌÓÒÂáKC°pI8JKK«ÅUÞo‰Ù…´Œë8º'ÏÄÁÄÄk×oc =Ý‘|ù*cȽs‘«Ö¡¨¨Œ1$ýtƒ¼<ÀÃ…¤d ó,ÙÆÊûYs"pb.ÄŸDâ™88·o‡ÏÖnãC–Fàài¸’ø=bwlFjZWœ!ȱI©ÜUÁ*‘ì© ¹†|Ÿµ·_ïžðè×_FÇÔŠc8|,KC梥-,ÌͰàÃYø.þ€1 ôì䟮ŒáÄ©ï`lÜß~0†äËW1г?À1»cqàÈq<ø¿‡P•”àZzæ„„ª­J¥‚±IS7ENn.B#WW‹_¸47³²PRZò|Sš#ÎäØˆ½7˜ævÕx>qÜHÌœR-nZàlß|4ù…pn×3§NãxôÆÍ1ðì 0†a~>øbC4zôßqrlÑQذ9Q·¡¸XWçv˜8¡zžÝö1V­Ûˆ¹!ËÐÊÞïOoOŸãßàÙs?ANî´wrÄꈥ\q†á`LëÒ£—AR%rÝ6…¦Ó ÁÓŸ’€Pïȸö³aÏÑÏÿ€®\QQ!¯~­ZÚCh"C§Á ¨@Â>ž‡»yù(—ù‹wcctru&‡N:àƒûë½uÜ´%¢Ñ/!ÑÈ•›ð2ÏÑd: ùDëÚïMê Bá·ë¿ÐŸD ¼ÑD4Bc[u(tý&%}ñŽˆÆ¬œ\EéœÚ8t‰húƒ:B)%'ˆ¦±çRÄû‰oô¬wSO&»MK>O’$¢Õí¼Í©æM Ãë—Ϋ·z‘&ÓŒ—n{£ò³„Û·ï¿(ܧo?œ˜:­Zš¿ÿþ?_½Œ÷ ;waÞü$ËJHLÄ¥¤ñ¯Ø}¸~ý:fÎ|ù¾ _'Ó§ö©4“rW—NmÔš²Js;'¶¶6(--……åÓ¿ï‘{;;TTTÀÔÌ‚ hüFfyy9ÌÌ-accÜÛ9j•á[™ÀÁÁA,K[¾Ç÷niX-ö\Šxé [[@Ó¦MÅgöÏ>ÜÚ¤IåøŸ{µœ?ŸŸ·ÃÆÖ&¦æ03·þ%Y–ƒƒCµ²^Fo™µ¨ÏÕåĉ“p7/ÇŽŸ¯/ÊÊÊ`Õ¼¹X5F¢Õçê²’PV¯Z¡¸¸ááÄZuê»vï„››üü‡ Wï¾èСuJcÕhú8¿T·@àyö–®¥\­ölæÌZÓð–ED3 Ðù%Íൈ&ÛÌh1@ ÑD4@ ¢ˆh@D#Ñ"ˆF ÑD4@ ¢ˆh"@D#Ñ"ˆF ¢D4@ ¢ˆh"@D#Ñ"ˆF ¢D4@ ¢ˆh"@D#ÑD4ˆFh0PÒHÛ&`2ž¿èz4fÔjsM¢½¨NѵáÙÿLFžUãå¼[5¬- ³>ÚòP—–'?]ÊÔ—tÖhU;:DÛ;‚–4P($¹„ѧˆ®/!ê“|Jòân“&¢ÕýP(0¡Ê=ãÐ.ºCJëÈÑêò$—>ûg0ê¢y4) må*•“€(ÕñÚžÉ13Rš“× ZT©y•#h¹‘3X CùRíѦti»H4çœDjt×ì0h¨¸Rò˜`&c.'hè,©zñ AKÛ!Q/¹TŠ <ƒW›FGû-ý‡ªM*ÈÐ^¼EàÈ'金ۑRsVh1?<gõÇàÓ–ŽGÖšä ù¢&ÑTZ˜ -ŒdÄABHRD85£T]˜ÌÀcR ¡É•©¼˜+Â8IÎ+pÈFÒêU] ÈQé‚‹,hé(( 3´t88òÖ–"7dÔOÛ@„Œ¢Ë $”Š”Õâ•—h:y5´¨~"ñ‹IBŠàròŒ:ñhA³lž¥¤‡,¥Ú ¨ÆÈ»ÆkxÎ3Idj/HÌ}”j]¦ <ïñÔYšR É!C¾’õR7GӶ᪯BæÈƒÌ†Ë5U‚‚zëÒVÞÅt6Óã “;xuD+Õs'냄\v_‡ùj"¹sC]‰©ÔÌÖ7jÕÇÕ_D'Õ— ú »"H<ôÿ~]ÕÇP¼U¸ˆÆ»³­ïoÞã¦ú"oúðà0‚èãÀ^¨ºê,åØQÚyúpÒ¶SÒiJqêBÊíßútJœ4j4ž32Þ£ 9ؼSΜã/¹îJ¼g†r³'§Áªd‡_/D–" ý¼Ž— "ÏÙ$S(P€ÿ°§Ž’›“v½áñNáÄÚ6ÁyªÜƒvȬ“¤FSârÃëõ!Ef@Þ‰ƒ´™¦mÞ¡màñ˜?“ÄÚÚʧ‹éøx‘˜¾*Ѥæf€¼³@žŠÉwñºXó¸,)=O•r9’òø y£T9Úö•zŸðn}0u¦“§Cäj^óÁ»[.µÓ­äÀ_©yã9˜–ãÏÇ3Px‰*ÇêðÈG{KàšGP¼*T›°yœål*=ô–³‚•Týà÷zàÝåqU¢•´‘EÎ-¯ÆyWç&Äë™#׿JÎjL‰°x„ BðzPðjRp¦—C¹ñr” ÷Ô¤êY'¯?–\í=uÏ"…‡8‘NPØȰ¼u–C ÞÅÅЋƬë³N%jV›y‘ÛÑJµ–Ü9«’©…÷'#¬ÔOn¿ðL M¶†ŠºtOÒ…´ú>,ç%ÒA¤D3ê:@ ’húœcÔ¡ ­l} ½¤QB4}þòY)«WI£õ–y‘MŸ›¢¾©c19ï)q`¨‹óR¹2¬õÞÿŽÁÍ–fÃIEND®B`‚gaphor-0.17.2/doc/manual/index.txt000066400000000000000000000005411220151210700167420ustar00rootroot00000000000000The Gaphor manual ===================== This section of the site is dedicated to some user documentation. Although most users will have previous experience with Gaphor, it is evident that some aspects of the application need a little more explanation. .. toctree:: :maxdepth: 2 introduction working modelling plugins stereotypes gaphor-0.17.2/doc/manual/introduction.txt000066400000000000000000000017031220151210700203550ustar00rootroot00000000000000Introduction ============ Welcome to the Gaphor! Gaphor is a UML modelling application written in Python. It is designed to be easy to use while not substituting completeness, that is, Gaphor implements a fully-compliant UML 2 data model. You can use Gaphor to quickly visualize different aspects of a system as well as create complete, highly complex models. Gaphor is designed around the following principles: Consistency UML is a graphical modelling language, so every aspect should be represented in a diagram [#f1]_ . Simplicity The application should be easy to use. Only some basic knowledge of UML is required. Workability The application should not bother the user every time (s)he does something non-UML-ish. This manual serves as a reference for all Gaphor has to offer. So, you may read it from start to finish, or jump to a section that interests you. .. [#f1] We take this quite literally: even stereotypes are modelled in diagrams. gaphor-0.17.2/doc/manual/modelling.txt000066400000000000000000000037631220151210700176160ustar00rootroot00000000000000Creating models =============== Once Gaphor is started a new empty model is automatically created. The `main` diagram is already open in the :doc:`Diagram section `. Select an element you want to place (e.g. a class) by clicking on the icon in the :doc:`Toolbox ` and click on the diagram. This will place a new Class item instance on the diagram and add a new Class to the model (it shows up in the :doc:`Navigation `). The selected tool will reset itself to the Pointer tool if the option ''Diagram -> Reset tool'' is selected. .. image:: oneclass.png It's simple to add elements to a diagram. Some elements are not directly visible. The section in the toolbox is collapsed and needs to be clicked first to reveal its contents. Gaphor does not make any assumptions about which elements should be placed on a diagram. A diagram is a diagram. UML defines all different kinds of diagrams, such as Class diagrams, Component diagrams, Action diagrams, Sequence diagrams. But Gaphor does not restrict the user. Creating new diagrams ===================== .. image:: navpopup.png To create a new diagram, use the Navigation. Select an element that can contain a diagram (a Package or Profile) and right-click [1]_. Select `New diagram` and a new diagram is created. Copy and paste ============== Items in a diagram can be copied and pasted (in the same diagram or another). A paste will create new items in the diagrams, the items they represent (e.g. the Class that's shown in the Naviagtion) is *not* copied (call it a shallow copy if you like). Drag and drop ============= Adding an existing element to a diagram is simple: drag the element from the Navigation section onto a diagram. Not all element that show up in the Navigation can be added: Diagrams and attribute/operations of a Class can not be added. Elements can also be dragged within the Navigation section. This way classes and packages can be rearranged for example. .. [1] Command-Click for MacOS X users running Gaphor in X11. gaphor-0.17.2/doc/manual/navpopup.png000066400000000000000000000231371220151210700174560ustar00rootroot00000000000000‰PNG  IHDRȨ¥¬¡ÄiCCPICC Profilexœ•Wy8Ôo×?ß3ö}_Ê5k„Ù¥d'K3˜ 3ÆØ“"E"²E“ÊVÈš„T’¥,-$¢P$%"¢æù£åw]ïó>ïs½ç¯Ï9×çœÏ¹ïs_×}ž …„€àÕÁÒçêæŽc®@@ÛÇ/Œblgg ÿÑV^ðTŇB!ýgÞÿj\TW7wD~a#ðý…@ ’F¡ àèƒ@€2ÕÉÁ)®€_øpùþ€+Â/€€ `ùBðÄæXCðã,ƒ±~™ÁøQ €¸Kò §Fü¾/y ðßü_gþmh€Æ2…cY˜3Y¥ØèRœÜ†<|¢üçÙ…òEdE¯ˆËIl” Ã}”öÜ2$k/×­`¤Øª¤¥œ¯òYÍE½]C[“®Å©§3¥»O¯lç'kÃR#c‚I›™„yŒÅËÝÚVn{Ê­aŸ«M¥bïépà œ\ò÷Ï»éºö¨80ä…x+ÜïsÄ—î÷?ãÏ èDL8t%¨›´"DÖ¡8…’©Ùa ´þð‘,Q ÑV1„ØØÃùqÕGÄý’€I;®šdxÂæ¤W29娩¤Ô § ÓJÒ«Ï4g<:;xnèü«Ì7Y3ÙŸ/,ç¬çA>äÿ,`¢ŠÐEk—&é—+Š“®ø_ÝsM¥„»d¾´³ìJù±Šƒ× nˆÞX®ì«ºV}Ó¦F¦f£¶¿® >¤Á¨‘·ñõ­ò¦ÈÛ&ÍÍ/î´øµ*¶~j«¹Kk×iÿ~ïÎýèÚ–:ª’:;g]ëòêêîëIé5é]{\ùß'Ñ÷¼?mÀd`iðÒSó§ïŸ%=—yÞñ?„*¶žyyjDuäù«ÈQ±Ñ¶1ükÖ×7ÆÇ¿O\ycófõmñä¾ÉÕ©«ÓŽïPïjÞûÍÎ<œý°íÃô\ÁGÇyŽùGŸ?ïZ€…¹/e‹Ä%­¯"_ß._[‰þæ¸*·:·Öò½pº¡÷ƒéÇÚÏf†ƒ€faêÆ&³àX›Ù=9–¸òyvòŽòS6„NŠH‰Ö‰Ktm¶–ì•r“~%ã(ûDÞJ¡m«†R¹Š¸j’Úò6–í ZGµGw˜êæé}Ö77È5ü`´Íø¨I—·¹³ÅEËwVê{‚÷–Z¿³‘· ´+¶ŸtÄ9y8g¹ôºbÜ Üƒ=²4{Ž{£*øìñõ÷KÀÓ wü‡V‰Â‡¶9‘¨Á§CèäzJWèu™† ŠÔ‹²>ãs8>.ñȹøì£¹ÇŠJKŽ—$•ž(;YšLO¹t*75ãtJZRú±3G2(gñçÜÎÛgZfégk\ËÎÅæ®äÍæt],+L,ò¹¤O¥¯^~YÜxåÂÕkV%r¥L¥oËÚË/UÄ^w¾¡ZÉ\9Uu¯ºð&µfo­|R7\£!¥ñà-í&ަ··ëšÓïøµh·²¶ŽµÕÜ=Þî|OöÞòýûÎwêt²t¾xDï"wö°÷<ï-|L|¢ÕÇÔ÷¬?wÀs78þ4ã™É³•ç/¼‡†z†ã^ª¿œÉe7н3F}­ðztüì„ùÄú›š·ÄIÜäÐTê´ÑôÊ»ê÷3’3/g3>ìžCÍ5} Wœû©ð³Û‚àÂã/É‹¦‹?—n\Þ¾üi¥ê[Ȫòꇵ«ß}×qëc?Ü ý|Âb0PLh&#Œ6‚9•å2ëm¶öbŽÎë\ÍÜ&—€JOl=~))öÄ“úÉbÉË)ON•¦&ŸH3NMÿzf0£òì‰sÞçõ22ç³:³‹.DäØç*å¡óFòk Ò. õ‹ø‹>\j£_¸Zl}EöÊÆÕÁke%‰¥eêå˜òWµ×Óo+wU W­TÝl¯)¯MªÃ×›4lnøÖøøÖ妘۶ÍÒÍ_ït¶´ÒÚ¬ïÊÜÝhºWy?ýAH‡íÃíRø»Xº6º¿õlÔB/LÁODÑF2’ŽT!ýÈJeˆ" Î¢šQÓh>ô.4 îAo0©1ù0e1õ`Ð#L<¦³ŠÕƆak±ËÌ:ÌÑÌ­,k–,–iV]Ö$Öçl²ll]ì›ÙÃØ»8pQœÊœœ?¸<¹Z¹q܉Ü3<¶<µ¼›xOò.òyñõóïâ¯Ø"'(&xQHZ¨\XC¸Yd·È°è!Ñ ± qyñ g‰ÙMI›¥6·HzHþÀÑ¥¬¤¾Jçl1ÞòE†.ë Ç$W/  ©0ªxn«ƒ’ Òså\UEÕ/j­êg¶ÔÐÔdÖÝÞª•¥©ã¾ÃPWZ«÷qçKýnƒfÃë» 2SMM™%™§Xœ¶Lßgu{ÏœµÐ>g›TÛ»vkºŽ‰N½.‚û½\«Ü×^’ÞI?ø:ù=%Xú·»ƒìIS!Áк0×p¦ˆ†(bŒdìû¸Êø¸cÖ‰âÇçO &7*8š~<#ö930›–“7zQ·è }þŠÅµ²2– ÏÕ25§ê]›î¶FÝ»¯ß‘Ó¹ÒmÓ[ñdcÀùiùóõaË‘¬Ññ×ÍoX'í§w¼'ÎVÍÕÍ÷,,ê~ÕX‰^mÿNÿÁÄ`ü?8€Ä@Ô`'ì†ý‘P 0 _.D1C"qHrCJµEAe£ZQ3h´1:}ýÍ`Ò`"0å1 `Ø0¦˜8ÌmÌv6ÛÌ ÌæÌ)̃,,,õ¬hVGÖ«¬ël¶lרìîì üŽ>N5ÎLÎu.?®îíÜ…<<Ñ<3¼n¼½|F|üjü%ÒtAYÁr!M¡a+áç"‘5Ñ3b2b-â®â‹i›¶nêÞL”d—¼‰ób–jÆoáßÒ)/«#» W.WS˜S,ß¡d®Ì¡üZ¥V5QÍ]]{›À¶%gšÛ‹µNiGëvxëÚë™íÜ¡¯g°ÓP—‘‰±•‰½©·Y˜y²EeÕîn«ï{µ¬Iû®ÛÌØIÙû:\v\r6p9±ÐMÒ=Ä£ÃSÊë¨÷˜Žož•@ö ÜMl R!•†È‹C•¨%´mám‘¶Q¯ch‡yãªâ®%ä7JZ8Y’âžÊ}z ='Ãóܦó£Yõ2sãós/VµÓ_º&Xº½ÜùzLåÅê‡5+õŠÞMgšï·lÜ5¼wìÁ­‡?»ôzN<~Ô/8HxVûbã¥å«¢±… «·§¾¼·Ÿ½4·þiÿBÛ’ôrÊ·ùï6^1¿§Ï ü€50ý@†(ƒøŠˆ!&H’<@–Q (/T9j -öA_FO3)1Q˜nc˜1a˜‡X1l¶“Yš9Žù‹>K«2kÂFfgwdïä0à¨ãÔäœàòášâäþÌÅ‹æÍàÃñÕñ[ñOÜ$xW(H˜_¸M„,*#:"v^ÜV‚Mâᦛ-$Y$ûpùRAÒ;·°oy#Ó,›%*o© ¢È«ømë¤R¿r»JƒêU5ºzѶB"ÍâíÕZ Ú]:£;ô˜vŠèëxÝuŨßxÑTÖì€y–EŸåO+=Á{/[ØØÚÚÅÛ·;bl/¸Œ¸*¹ÑÜïñ ózzPÑ'ÑwïH¨)žÜrÊ=•‘vãŒÇY칊L—lÎ ·sò R MŠèW‹}¯*^[)m)?{Ti^-qósíýúÂFJÓ¾f‘;ï[ëï†ß“¿ÿ¢#¹SóQO·ÏÆãø'ký¡SOퟵ¼" 7Œ _™Æ¥½N/šèx3=É:%4µ4ýöÝ·d–ù÷2×û±p>÷Ó¹Ï'¾¸.*,Î.Õ~½°³âúMk•{õÝÚ¥ïîëÒë‹Ý?*~62¿ö%`3%“ÈTœµ©ÙÿsÙûoL ÿ£Á Dš•Àª…˜Àpˆ¯-ð b„0sÇßXÙŸha<ˆ!…fç"ˆML Ó~à@üùì±ûi!$k@ð3óß¹9Aä½À€”BœÿÔ¯ ‹püÃyˆ÷1Û È‹˜@S›ßüE°S0øH@*¡ ü€ >8˜øˆ4ðpˆ€¡DÀÂ~ç‡ T° B@å·Â¿ë¸ÀPø0ˆ€²71\ã‘CŽÖs T¯PŸUßÜoöÞ¿Šù[é—ºïÿÉÇÆ3LÿVÇýeOþG*à>@… @Á; B°71áŸ<øµ{`y ¶ÜÚGý·wB#DÑLÉ”h*1 †3¦PHœ)9˜N#P•qV!~ªÊ8 uu-€ófÒçö5IDATxœí!xªÞÿÇß÷ÿükÐ$²† ›DÖ¤v‰¬±&mF×fíKô6m³]"kÒF»DlФí˜L§¢STœŸ×óì¹WÃqóÍ9Ÿs>ç}~ýû÷ö®ëâîîq(Þß߆Aá9‚ n<ç˜üÿ×7þûïùõ ~8Š¢œº ;ñ§®AT™¥¤,:.ê yíñ)€$ ¡ë-Ôjü¡ªA{q0Ô2ŒN·ð§k! '$â¨\_×ñïßÛVçž´‹5MOywâRyyl}.Å ÄÙp}]‡m;Ð4 ²¬Àu]ض Uý|=c8BU5ÜܨPU¾ÿ92¦ªÚR™«ÊØÅz{èw­Âs8ˆ¢Ãa”¿†!LÓÃ0K熉0 ả…÷Ç.¯âfÛúžÛç:<Ïa0À÷躎n·ƒ——ìµeYùh™¢´p{{ ‚–ÕYÛrð<÷Q†Ëj/Œ¸­Hèõ6ÿqÚm¢(®<&Ë2lûû`I–!$iu¹<σçyDQ´ò8ñ³iµZI‘¦i.I†a~^èvŸ$ ‚pey‹eJÃɱ•Eí¶ Çéc0,(Êu]hšÃÐ׊°¢ØŠ)¦ëƒß÷ÐhÈ+ŸWW EÍ[ÏsEêõ†Áxì¡^o Ù”à8@× ôûÎRY†aÂ÷_!"†ç¹à8‚ ä¯gŽã>~G €®ë"Žã¹cÙ ŠÿHëêK¬gUÏb¦Ù†m÷ IÒ4E½Þة̵1ˆ(Š0 š¦åý²­Åq@ÞÞ0 A–Ž5› Æã1ƒ>\×…,gÂŽ¢<Ÿ”Õj5$I ŽãÀó<’$Y{¯4~”5‚¢´¦qþZ–?‡°eYA¼a0è#Þ Ë͹cMAV§$‰·ª/±?I’€çk€~û ü+…1ȼHLÓ„mÛ'ÇŒñ8kEæ›TÇȾ¼³'CG`Y@öD‚1x>k ¾>Õçy{Ë»ÙS}þ5ðùyµZ ®û ‹¡šMeî˜?`²c›ëKìO·ÛÁí­Žãò.Ô.l Òg"y||,Œ9ŽI†¨×¨×—ë2 0]1~œ$YW+ŽcDÑ’ÔÄÕÕÆcïU^˺úË|»(z}{{›Ç'`šÆÊó6•¹Õ(–(Šx~~ªÔn<öò¾ýŒ0 !ŠuŒÇcYk1‹2QHð}qƒã¸¤¸Ï?KàüšÈ9ÿúë1]¿ÛéØºò‰ÃR4i¸õ ã7j5išÂ¶ð<—gåñ5Å}ž%hš~ÐÊœ§˜°óý­V¶y2‰0¦Pž7†®kˆã¶í Ñhäó:ŠÒËf£iš%L&IúqLA­– ˶ȲŒ0 ¦@³Ù„ \vW¸èoL-H‰ã‚ b2Iò4–eçrÀ²ìQÑjµ†Æã1TUÁtšÂu=Ȳ Q”$ úý>tý6/K(Š‚4á8ÐhyÆÁ%òþþ¾öXe"Š"„ÆúåµÄ1lÛ^™í{N0 ƒétùK;ŸÑ Š"Ò4˲Ã,Ëc: E\׃ëzK¦¨Õø<¹S’$$ÉÓiŠ8ŽÁ²µ‹H•È6¦«èè*‚ 8kp·”©<™D™¿‹¤é4ÿÿ¼xLÓü2Ÿ´8¹D‚ØŽ9“>N7ŸTa$IÄp8ÄìK¦)<ï²,/,& ‚àãß0_ïd«.=ÏÃd! ³Ñ0J•Ùʶ —Œ ¸ºb>D’~ÓšMi!Á2ëJ¹HÓ¿ßæâ™ô¯¯¯yyŠÒ¤´ú¨¬@¢(„·!¡pE­ÇlñL¿ß_x¯ŠÝZ‡aÔÁ²5Ù;I’…º¶Z-Ȳœ^M&Yv3Ã0PU,ËçÝ®4M0F ë",½&©¬@X†¸Ã˜ÿÕÕUáññxŒñxŒÆ.GÀ¶—×ÇeþKÇVé{:M1®^-ùµ»EݯõTV ¼ @ÓÔo_×ë=ïv»èt:–[§8Žqww—¯3y~>¾‘·i[eÓ“ÿ8TV ‡BÓ4ôz=¸®»ä8~Ó4¡( ‚ €aœæK¸ížü‡§ºùnð7˜µ"ž·¸ÝuÝ…áÕ*Æ&Äq©¬@<ßßé ¾Í¯¢(`Yv!XŸáºîÂ|ª¹âr¨¬@äfv¯÷ýëäõ[.ÌÓív¡ë‹ygŠ¢Àq˜¦ K“uÄåñ#' ·¡Ñh,‰éùù®ëæÇVµ0ÄeQÙä|)ÇÉ-H¬;5,š>ôvhňŸÃE ä\¸¾®C¤i6é×ívv^ßOìGeâ¹îN ‡<Ïoœ,<^^2+S×õ`Yüýûrâ]&•ˆmÛ;93 S {Ô²P÷÷Ÿ¿‡$I`šmL&“Ö¥›oq}]G»ÝÆh4BÇèv;ù\Ïp8„ãô‘¦)èvŸ–®sÝ¢(».ŒFîR9E÷ÿ‰TV çºmpÙŒF£…µèN÷÷šMyåÆ0ë6ƒÙ´¡Ì¶ÓlºÿO£²¹tTUEO1™L¾€®ë}™Ì\œ÷Y·̦ e¶Ý˜fÓý$Š2‹A{èõìÙÁ`°Ö+yÝû›6”ùŽ÷rÑýK!׌jñðІ,+ðý’$BQ²ù™Y–A†[ f”µ¡Ì®÷?W.v¢ðœ°, OOÙòãn·‹×W77*nnÔEU›™m(£ªZáw›ËÙíþçʯÿÞÞ,éîî¾Ðáá˜üúõk¯ëËú¿~ýÊË¢ìÙŸÅ6ßùJÇ »nh9¿>› ö¡ÒÙÄ*!Ð.±D™œµ@ ï}nŸ¥ËõÖ„ø‰œ•@VµãŒb[ÎJ ÀbŠçyXÆçšŽ^Ç:E•ˆÌE óþúõ <ÏC×u\]]AÓ4üùóÇã¸#Û¶i5!qþáy>ÿÙ†Édžç!Ë2†Ãa¾>=IXÖg $Ërî\H\.gÑÅúj« ¬Þz†ÁÓÓÇëº0M¦i¢Óé,ä±,‹n·Ø¸h_ ¢:†¹óÖY(o´êk*ͺnÔÍÍÍVkÒ Ãܹ.Dõ9{´j›ÖIQš]ÿáœM bzþs žŸŸ)q“8”Áûû{Þ2†÷÷wüþý@æ§5ï©e—½ë‘q6],ʯ"NÁY„ò«ˆSQiT½Õ¸¾Þ/÷ëß¿·Í''¥²©Êº”MPJþϦ²ù)PJþyC9”’¾@J¦Œ”üëë:EYp2¹¾®%f™¿Ï±îYeH  Œ”ü···ÜÉäTüdC¸m¹¨‰Âs²¬µÉ’I’@× ÜܨPU ¾Ÿ¥»8N½ž øó§¿°Qi£±zß”É$‚ªjPU ‹Nöªªåÿ‡PUíãžj~O ³þ™•ÑëõF÷®¯ëèõl¨ª†ÑhTXÎõu¶í@Ó4Ȳ×uaÛÙµ³×džr¾›’··-¤i²òK1³ÿüû÷OOYº>Èr#ßVÎó^Áó‚ €ïûh4V·DÎ# CÇËË`Áâô+ŠÒÂËËàãžOù=³2ºy«<²ÇËË­V«°àÓÕ¶mÜß[àùìZÛ¶—| ¨‹Ue¦äÏh·ðøø¸äS¼ÎþS3›Ð4M†! À瑦 šÍÕ-ˆçyx~~ÙZVgåyEÖ¥¾ïÃqìµeÌlM7•3î& ÔcA)‘²G«E†ã°+ÍÙÖÙ6 ‡‚Yn¢Û}DOÑÙs9ò&ëÒ"æëY¦ê1 .V‰ŒÝQþSNOO‹ûµÏì?gÌ?Y›M ÏÏdYB­Æ# #A°vKY–ò¥Æ«öŽŸQd]*IÛ•±©œ*B)‘C¤ä‹¢¸lÅöŸ²,c2™ä]*AÖÆ³²§UÕ–º;‹ç­·.ív;p'/£¨(ËõXTÖz´*Y^_×óƒçù¥áÝ¢øƒçù3ÇmÊ“‰b4rá8ƒêo€zöÖ£çFùU› «(ª0 aYV¾§âÌhû'@)‰²ò«ª(€Mˆ¢˜ïgòÓ ì eåþlH {pŽO{â{Ð(A@!ˆH Q „   }dYA}M9L¤q„§nçGïû“!ìA½!ÃèOŠ9Ý‚ œ)ÔÅ"ˆH Qu±öàmì¡ß-^gÁ Kà>W¦iŒ§§ÞZsìSš6ì˹Ôs[H { Ë2lÛþöuŠ¢`<~ƒ¢¬ð«`Ú@Pk?v\ý–¦)Ò4)<çX¦ ™©B/7]˜L>“.‹ ’$ÁÝÝýÊ5)³ÏxwgÀqœe™>¬û¬Ç‚RQŽeÚë± .V…9†i‰È :sÃÖE ®ë.¬û˜_E¨i:LÓD«¥lUV‘éúÏz,H æÔ¦ »5Ȳ×u²éúÏz ¨‹UqmÚ`ÁpA–¥üý"ƒEQà8Ÿu˜__þðÐÃ0 -Á®¦EŸõ@*ΡM€Ï Ùqú ›Œ|ßσî¯_ìn·†A.’]MŠ>ë1 Ó† ™6X.ìÞ÷ÝþdYF»m.¶§¢ ó§2} Óâ,¨²é d¢0€,¯ZGö´¬†ƒà©[ Ú¦$=èvŸ€ï 1…Aó±(²ª‚pª d‡ œ¯« ‰`34ŠE@¢A@!ˆH QbíÁ)6•$¾a˜KYÛBÙÃ0O]â€@ö@Q”ŸLÄy@1A@!ˆH Q „   ½$’$Åx<Æýý=dYÃ0ˆãI’  }ð| µÚù&7^"$ð}š–íÞí¶Ñ6M€á¦)‚à OOùñÿþû¯Ð0ލ$=ñ}†aÂóúÀxq]hÚ‚0Ë2д´V š–-¯Õõ;xÞ_jIÎÈžt:]xÞ s…«+J L&/ˆ§1€l9i­ÆggŽOåZ‹q&@ö$ð†å¤hÈ,ê q!I&ˆ¢<ËW ¦qŒ·ñæ‰ÅëëúŠCÃÐq{{»U}¶5a¨‚YC•ê±H 8}¦a€ay0`¨±&Iˆ$ 1IB„aÇq`Û6ü×1Â0‚0Ííó·X–ýðú´ÁÙÖÐyÝy™U‘ªªÐuý[e^šÙ5µ {Òn›ea#Bø¯>ffã0E%£èà ´ÛßKpœyîΘ:7›2‚ €euògžuç=<´á8΂“ȶe3³ë6†Ã!:Ç|Eiå­Ü×2ff׳sçÍãff׺®å×o*kÖíü*´ï|Žm ”Ç]Á² ôûCxãñÇÖLþ¥f@%¤i‚$‰÷¾ß¶†Îß1~þι—dvM) ¦iÂuGh4$ŒÇ>|?³÷}4ÆãÀÕ·K^µ 趆Îß1~Þ×$ú'š]S R sQÁó5H’œÍš³í=×Ykêº4M¡iZn¯“Ù„ÚPU £Ñ¨Ð–ÓqÜÜ(PU½^o¡¶9ˆÈ²rqé5Ô‚TœÑhQü4[g­Ùï;¸¾®/­Õ>·Þ¼¿¿_kËÙëÙð<,Ë.Ùìð<‡——|߇eµ¡( .HEQUq<Åd2Yð–ý®µf«õ¹¸èZY–aYmÜÞj ×Ì—!IÂp²Û:SH eÖ<>öÐëÙ¹é3ð=kͯ筻ö¿ÿžáû>úýÁdz¶ŒK‚bŠóðÐÆÛÛ[/ìc­YtídA’$t:ÖÖ£f—ÀR BÙ©Õò,<=uÑïgVŸ–ÕÆŸ?™¥f«¥ ÝnoUNѵ¦i!M¤iç Ö£´¶a™m¬G‰óä[Ö£´¶¡MÌÚ÷¡ ½bªéQÀÊdß=A»ýpê*ÈÚ.Öün?§¦ A2Åg—Ia âyUJ3Û0’¸<(Hß‚»»ûSW8$ ¬Uš?¯J׫ÊuúÎÈœ ˆà2[’@ŽÀ×ìØ]¸ä|¨SB9××u°,[ZyI’Àóþ¢VãK+sFGÃ7„áqƒã8Ôj5ÔëZ-¢(^¤HI ‚eY˜¦ žçìûÅJÑétE“R†!z=A@’DHRŠÂƒe³íã¢(ÛWѲÚ¦yI’6üƒ |†a¶·y ̺T<Ï¡ÑÀ0v}ø¦)¦1i:]Øy\×ÅãcͦÃ0P«ÕÀó<8ŽÃ\}Ü{Š8N†!\×…i¶qÛÛÖÅ´&¥ D²<¢··1®®Îçøúê¡Ý¶`ÛvÉOH Ãewÿ]0 $\‰uúÇïß·‚ €çù_zµZ¶*Q4"žŸŸLq{«]„HJH­VC§Óaè‚W^üP¸®‡NÇ‚®àùZéå—ñ*ó{†a.Žz]„(nŽ•† à8 s…§§gÔjÂEìÚ[z.Öãã#†ÃxžG'e_*¯¯:4Mƒ,7À²å>©«H¯g£Ù” ÂVâ>»Œ,ËBQ(ŠDZ1™D®<’¬hYÂ0ð•‰ïû0M š¦BQ”‹¥ ‚A ^o@„­GÙæ/,ËB×5„a„ñØ/e»Ê,›WÓ´ÿ1˜N«õK|}õ`&t]¿qÀp8‚$‰y@¾+‚ @–eŒF. dnnnÀó<Ò4­ŒH\×C»Ý®kPùbÄaøös{}f†a>üºBÄq\b «ÇÿÐñ>…AÚõIEND®B`‚gaphor-0.17.2/doc/manual/oneclass.png000066400000000000000000000320211220151210700174050ustar00rootroot00000000000000‰PNG  IHDR¬F¹u<iCCPICC Profilexœ•Wy8Ôo×?ß3ö}_Ê5k„Ù¥d'K3˜ 3ÆØ“"E"²E“ÊVÈš„T’¥,-$¢P$%"¢æù£åw]ïó>ïs½ç¯Ï9×çœÏ¹ïs_×}ž …„€àÕÁÒçêæŽc®@@ÛÇ/Œblgg ÿÑV^ðTŇB!ýgÞÿj\TW7wD~a#ðý…@ ’F¡ àèƒ@€2ÕÉÁ)®€_øpùþ€+Â/€€ `ùBðÄæXCðã,ƒ±~™ÁøQ €¸Kò §Fü¾/y ðßü_gþmh€Æ2…cY˜3Y¥ØèRœÜ†<|¢üçÙ…òEdE¯ˆËIl” Ã}”öÜ2$k/×­`¤Øª¤¥œ¯òYÍE½]C[“®Å©§3¥»O¯lç'kÃR#c‚I›™„yŒÅËÝÚVn{Ê­aŸ«M¥bïépà œ\ò÷Ï»éºö¨80ä…x+ÜïsÄ—î÷?ãÏ èDL8t%¨›´"DÖ¡8…’©Ùa ´þð‘,Q ÑV1„ØØÃùqÕGÄý’€I;®šdxÂæ¤W29娩¤Ô § ÓJÒ«Ï4g<:;xnèü«Ì7Y3ÙŸ/,ç¬çA>äÿ,`¢ŠÐEk—&é—+Š“®ø_ÝsM¥„»d¾´³ìJù±Šƒ× nˆÞX®ì«ºV}Ó¦F¦f£¶¿® >¤Á¨‘·ñõ­ò¦ÈÛ&ÍÍ/î´øµ*¶~j«¹Kk×iÿ~ïÎýèÚ–:ª’:;g]ëòêêîëIé5é]{\ùß'Ñ÷¼?mÀd`iðÒSó§ïŸ%=—yÞñ?„*¶žyyjDuäù«ÈQ±Ñ¶1ükÖ×7ÆÇ¿O\ycófõmñä¾ÉÕ©«ÓŽïPïjÞûÍÎ<œý°íÃô\ÁGÇyŽùGŸ?ïZ€…¹/e‹Ä%­¯"_ß._[‰þæ¸*·:·Öò½pº¡÷ƒéÇÚÏf†ƒ€faêÆ&³àX›Ù=9–¸òyvòŽòS6„NŠH‰Ö‰Ktm¶–ì•r“~%ã(ûDÞJ¡m«†R¹Š¸j’Úò6–í ZGµGw˜êæé}Ö77È5ü`´Íø¨I—·¹³ÅEËwVê{‚÷–Z¿³‘· ´+¶ŸtÄ9y8g¹ôºbÜ Üƒ=²4{Ž{£*øìñõ÷KÀÓ wü‡V‰Â‡¶9‘¨Á§CèäzJWèu™† ŠÔ‹²>ãs8>.ñȹøì£¹ÇŠJKŽ—$•ž(;YšLO¹t*75ãtJZRú±3G2(gñçÜÎÛgZfégk\ËÎÅæ®äÍæt],+L,ò¹¤O¥¯^~YÜxåÂÕkV%r¥L¥oËÚË/UÄ^w¾¡ZÉ\9Uu¯ºð&µfo­|R7\£!¥ñà-í&ަ··ëšÓïøµh·²¶ŽµÕÜ=Þî|OöÞòýûÎwêt²t¾xDï"wö°÷<ï-|L|¢ÕÇÔ÷¬?wÀs78þ4ã™É³•ç/¼‡†z†ã^ª¿œÉe7н3F}­ðztüì„ùÄú›š·ÄIÜäÐTê´ÑôÊ»ê÷3’3/g3>ìžCÍ5} Wœû©ð³Û‚àÂã/É‹¦‹?—n\Þ¾üi¥ê[Ȫòꇵ«ß}×qëc?Ü ý|Âb0PLh&#Œ6‚9•å2ëm¶öbŽÎë\ÍÜ&—€JOl=~))öÄ“úÉbÉË)ON•¦&ŸH3NMÿzf0£òì‰sÞçõ22ç³:³‹.DäØç*å¡óFòk Ò. õ‹ø‹>\j£_¸Zl}EöÊÆÕÁke%‰¥eêå˜òWµ×Óo+wU W­TÝl¯)¯MªÃ×›4lnøÖøøÖ妘۶ÍÒÍ_ït¶´ÒÚ¬ïÊÜÝhºWy?ýAH‡íÃíRø»Xº6º¿õlÔB/LÁODÑF2’ŽT!ýÈJeˆ" Î¢šQÓh>ô.4 îAo0©1ù0e1õ`Ð#L<¦³ŠÕƆak±ËÌ:ÌÑÌ­,k–,–iV]Ö$Öçl²ll]ì›ÙÃØ»8pQœÊœœ?¸<¹Z¹q܉Ü3<¶<µ¼›xOò.òyñõóïâ¯Ø"'(&xQHZ¨\XC¸Yd·È°è!Ñ ± qyñ g‰ÙMI›¥6·HzHþÀÑ¥¬¤¾Jçl1ÞòE†.ë Ç$W/  ©0ªxn«ƒ’ Òså\UEÕ/j­êg¶ÔÐÔdÖÝÞª•¥©ã¾ÃPWZ«÷qçKýnƒfÃë» 2SMM™%™§Xœ¶Lßgu{ÏœµÐ>g›TÛ»vkºŽ‰N½.‚û½\«Ü×^’ÞI?ø:ù=%Xú·»ƒìIS!Áк0×p¦ˆ†(bŒdìû¸Êø¸cÖ‰âÇçO &7*8š~<#ö930›–“7zQ·è }þŠÅµ²2– ÏÕ25§ê]›î¶FÝ»¯ß‘Ó¹ÒmÓ[ñdcÀùiùóõaË‘¬Ññ×ÍoX'í§w¼'ÎVÍÕÍ÷,,ê~ÕX‰^mÿNÿÁÄ`ü?8€Ä@Ô`'ì†ý‘P 0 _.D1C"qHrCJµEAe£ZQ3h´1:}ýÍ`Ò`"0å1 `Ø0¦˜8ÌmÌv6ÛÌ ÌæÌ)̃,,,õ¬hVGÖ«¬ël¶lרìîì üŽ>N5ÎLÎu.?®îíÜ…<<Ñ<3¼n¼½|F|üjü%ÒtAYÁr!M¡a+áç"‘5Ñ3b2b-â®â‹i›¶nêÞL”d—¼‰ób–jÆoáßÒ)/«#» W.WS˜S,ß¡d®Ì¡üZ¥V5QÍ]]{›À¶%gšÛ‹µNiGëvxëÚë™íÜ¡¯g°ÓP—‘‰±•‰½©·Y˜y²EeÕîn«ï{µ¬Iû®ÛÌØIÙû:\v\r6p9±ÐMÒ=Ä£ÃSÊë¨÷˜Žož•@ö ÜMl R!•†È‹C•¨%´mám‘¶Q¯ch‡yãªâ®%ä7JZ8Y’âžÊ}z ='Ãóܦó£Yõ2sãós/VµÓ_º&Xº½ÜùzLåÅê‡5+õŠÞMgšï·lÜ5¼wìÁ­‡?»ôzN<~Ô/8HxVûbã¥å«¢±… «·§¾¼·Ÿ½4·þiÿBÛ’ôrÊ·ùï6^1¿§Ï ü€50ý@†(ƒøŠˆ!&H’<@–Q (/T9j -öA_FO3)1Q˜nc˜1a˜‡X1l¶“Yš9Žù‹>K«2kÂFfgwdïä0à¨ãÔäœàòášâäþÌÅ‹æÍàÃñÕñ[ñOÜ$xW(H˜_¸M„,*#:"v^ÜV‚Mâᦛ-$Y$ûpùRAÒ;·°oy#Ó,›%*o© ¢È«ømë¤R¿r»JƒêU5ºzѶB"ÍâíÕZ Ú]:£;ô˜vŠèëxÝuŨßxÑTÖì€y–EŸåO+=Á{/[ØØÚÚÅÛ·;bl/¸Œ¸*¹ÑÜïñ ózzPÑ'ÑwïH¨)žÜrÊ=•‘vãŒÇY칊L—lÎ ·sò R MŠèW‹}¯*^[)m)?{Ti^-qósíýúÂFJÓ¾f‘;ï[ëï†ß“¿ÿ¢#¹SóQO·ÏÆãø'ký¡SOퟵ¼" 7Œ _™Æ¥½N/šèx3=É:%4µ4ýöÝ·d–ù÷2×û±p>÷Ó¹Ï'¾¸.*,Î.Õ~½°³âúMk•{õÝÚ¥ïîëÒë‹Ý?*~62¿ö%`3%“ÈTœµ©ÙÿsÙûoL ÿ£Á Dš•Àª…˜Àpˆ¯-ð b„0sÇßXÙŸha<ˆ!…fç"ˆML Ó~à@üùì±ûi!$k@ð3óß¹9Aä½À€”BœÿÔ¯ ‹püÃyˆ÷1Û È‹˜@S›ßüE°S0øH@*¡ ü€ >8˜øˆ4ðpˆ€¡DÀÂ~ç‡ T° B@å·Â¿ë¸ÀPø0ˆ€²71\ã‘CŽÖs T¯PŸUßÜoöÞ¿Šù[é—ºïÿÉÇÆ3LÿVÇýeOþG*à>@… @Á; B°71áŸ<øµ{`y ¶ÜÚGý·wB#DÑLÉ”h*1 †3¦PHœ)9˜N#P•qV!~ªÊ8 uu-€ófÒçö5 IDATxœíÝ-t£JÃðé{1qà‚œuÄ rêˆ ®HÖQ—¸ÌºÄw‘Ô¥®q‹$.¸â™8p·¯ —›M¿Ò„|•ÿïÜsž”˜dwÿÏ|1sõû÷z`LMÓäÈ7ýö­…¿cpÎþïÔ€¯, ÃSàÿ+þY¿Îù©‹p1þW¾úþýö„倪üùóçøm^€Ëõ¿õþüùsªr@%Ÿ…>A¨µÿ}|ÊGEiéÆËã+BH–Á?Œ±ýïp„`K7œÁðÕ_ùÃAÇÁ¯_àË8xsXqè[Àñ=>NN]€j O°î¾}kyžoY–að0 =Ïëtž_'<<Nâ8î÷{˜¢g3Ã0¼¾¾~ëì_¿~½ü[n†çyÛÜŒsE‘išÇã8ŽãXÓ´m.•+þD4MBy§ijš¦ÅoÓ4GyžSJ’$}ûíZš.Xj€jl6‡9ç¿~ý’$éׯ_þUyýÿç)ÝòfBˆW»‡Ã¡ëº—Ëe§ÓiµZº®ÇqLǃÁ€âyž,ËÅiŠ¢l¼ñêêj<ëºþíÛ·étúóçÏòuqBš¦†a´Z-Ã0–ËeyP×u]×ûýÿjµ/Ëð%Ñwÿ]·7ôýz,këŸz;À™{¥Os>™L,Ë*ºxÂ0´,k2™®¥sss“çyR×u{½ÞÓÓ“ïûŽã+‹ÄK’d6›éºþò‚’$ÍçóâS0Ɗ׷·ÏÄÜÞÞ:Žóôôä8Nqåâ ëºóù\UÕwÊPCyž+J“ /èõ>Á2]×õ<ï  XFý~½¥Ë2òÜàJ…I’ôz½0 ó<µ`¶mç !nnnŠ×‹Å¢øm†E¥Æ¶í2×~ÿþý­2ÔÐp8èvmY–_öc|oŒ9x}}ýf+¸R¦iŽÇãûûûõƒóù|£©¥ëºïûªªrÎûý~žç£ÑèåÕ^m í6Yçe¾˜õé~¯¾îv»EG!!Äuß¾õv€KñÞÎùjµ:Úxßh4êõzå¦i–ã-euŒs>9猱4MŸžžvNáœÖA”Ÿnýàûe€¯äƒy‚Ǭišfÿ=~çû~†­V«Õjù¾_äœ/—Ë"¹TU}µCðC¾ïû¾_\¶¼òÝÝçyº®¯‡Ý«e€¯¤‚y‚{ZŸs·>þ(IÒãããÆÉš¦•ç¯WÙ޺૯›ÍfEï*ÆOŠ×eûÕ2|±I‚5‡'F Ö*¨ æË%ç|›a‡¯=—¨‚ü矶ŸHŒÇBà¬T‚Œ1,–¯âœÿþýĘúñ©'rúøò°è?œ3„`í`Á+€u…`žç§*v(¡&XS˜íP8AªªÊ¶|Ò#Ë<Ïè Î Bð™6 ìN’$A8œsbdµZºð•{Ô šÃ‹E…ÓÏ{£X¬îY®ž ËòÎËN&“ñxLBp΋uö¹ \œ„ D©ú÷†soi4¯ŸÏçóù|·u´JÓéÔó¼ÇÇÇb»¬”PO'A…1ËêlsæxüóÕãÃáp0lìI’eÙ÷ïß‹5ôïîît]÷ŸßÞÞK‡Ã§§'Y–ËUs^€ rЬ",ŠÊàúÚ¨a–[åzªªËD§iêºnEY–m¿[@š¦¶mOѬ¯ïÿýû÷››˲Þ:ä!Åñ–ÛW¾3?†s.IÒÆâÒan,YhF±1“aƒÁ ˲áðyŠ¢ªªQ½S´mÛ÷}Ã0„åîÆADQÁýý}±öË#pAN‚F»íÇ[ùnsu8ÍÕçÜ÷ýb÷4M‹)Ö†a›y]‹Å¢l÷ûýÁ`0™LÊ‘h.³o}Ìd±X†¡ªj«Õzë\‹ì,èºnÆÃÃCñãÝÝÝ÷ïß7Æ(Úíö`0(7f*+t„Ó4W«U§Ó!ÿN‘Ù¸þp84MSQ”õývmÛB!Ê}H^€ rUì†á÷ï·Ç™ç¸îö5Á?~ ¯m{Y¶`L}ký¾âWÅŸ5P(à‰¨µS Œ„á–k"(ŠòÖ|i€Jœ =Ï+¦4ˆRªªØžè!¸ýL=€CCŸ ÔBjí¯æ°´Ýâ.p¶²l«ÎV(¡&µv‚‘«««Oi½p8§ylnË)2„õÝ*w^ÏoDÞöY °›ó ABH=/l‹²ÀÁ>7jóíö`ÚeYŠ¢ÜÝÝèúp‰N‚d­Ù«(Jßy^"p<èW{¬x /]Þ™««+EQlÛn4–eÝßß˲,Ër¹ï’çyÅ‘F£aFÇëo\}{{Ûh4E™Íf§ù0pjg‚Ê¿Þ9g¹\*ŠR¬¨Zl6’çy¿ÿ\s4 £ØSéééi6›½µ”ÿr¹Bôz½årYìÇ5tÊæp™tÅ‹Åb±ýê2£ÑÈ÷ý0 ]×u]w0”»,I’Tl°YlÿVÖ_^äŸþ!„üüù³|/Ô͉ûw ^¼¯Ü8¸t}}¦iÍf3 Ãm.µuâ<ÄXpQ̲ì(œ¸O°ïØÅ^óîîN’¤~¿ßl6QÝ€÷Å™O)%v§ôXß¡xýx¹Ë0ùûä·^@Ýœ8ñh0œÖ)CÀÉ&Q€3q‚Dœ³{bà˜‚PkA¨5„ ÔBj !µ†€ZC@­! Ö‚PkA¨5„ ÔBj !µ†€ZC@­! Ö‚PkA¨5„ ÔBj !µ†€ZC@­! Ö‚PkA¨5„ ÔBj !µ†€ZC@­! Ö‚PkA¨5„ ÔBj !µ†€ZC@­! Ö‚PkA¨5„ ÔBj !µ†€ZC@­! Ö‚PkA¨5„ ÔBj !µ†€ZC@­! Ö‚PkA¨5„ ÔBj !µ†€ZC@­! Ö‚PkA¨5„ ÔBj !µ†€ZC@­! Ö‚Pkÿ;uþre]½z\"R6ÉŽ\˜3‡ïªA8;æÄ|y0²¢ã—äüá»ÚšÃPk¨ ™JÒ$MÒÿ~^««×[õe’étZ¼d*S™zÚâ\(ÔáLý•€ð|];CMΚi>÷yE~”ýAgÿ_®¬«âû)냰Ô Ö‚PkhÃy‘ˆô<ÃcA!‘ÙoÂwU „ œ—r–o1Œ~Àw໪šÃPkA¨µ¿šÃišœª'ñ_þùóç„å8!Dã a”ÒS—¢îžCsŽj |UQ…aøóçÏv»=‹ƒ××׿~ýÚáj³ÙÌ÷ýÕjE1 ÃuÝÝ®†!!„s¾C B†ZàœÇã4MF»Ý&„PJw ét:™Læó¹$I„ß÷‹‹ìv58ǽ^oã`žçN§ÕjéºÇqqšçy„~¿}}M ÃжmBÈp8ô<¯H@Bˆã8Wó}_×õõ«üöí[«Õê÷ûå¶mwÓÚ¬ ><<óö½Þ£Ýëêêê˜ýžÇ¿ÝïßOG»Ý…º¹¹‡Óé´|$™â8N¯×ãœ'IbÛö|>7 c4¹®E‘‚2›ÍŠŠ^’$EEò-–eÉX^2Ó4•$©¸Zñcñ+8¹WšÃýþà8÷.2â8}‘EÿË—¿|h4õûýõ Ã0Ižÿ˜Šj·Ûq !†®ë³Ù, û»»m®Ÿ$É`0X,„4}^Ù…snÛ¶ã8Ýn·üÑ0Œ÷óŽãõ>Á(Ú¥Ãàü™¦9}ß_?8ŸÏ7Fi›Í¦ïûív[×õ0 ŸžžTU%„¨ª:›ÍÞ /Û¶ƒ h·ÛBY–‹ƒ“ɤNñ}ÿññ±øq8Ý‹ø”ð è„ÚFƒÁÍÎyÙ7WV 9çÃásÎ9¿»»3 £8> z½^žçÅaJɲŒ1¶ñ«årÙn·G£Q1S§øÑuÝòvpBA¨MÓ Ã(Z¾„ß÷Ã0lµZ­V+‚â ç|¹\†!IR£Ñ(CÐ4ÍÛÛ[Îyq~9ôQFš¦éº^%!¤Ûí¶Z­¢«±üÑqœbz œ¦Èü§üW±=Ìt½ Å̾Âd2)_K’ôøø¸q2ç¼×úýû÷ú¯nnnnnnÞº¸ã8åñÏã~Sµ‹Ñ“{&‚äÛ·V9ãá³ò<¢_ͦ²O’$yx˜¦éSš.³,“e¹Ùl¶ZÌ4;ªª"g+q†‰†!¦ž„ ‘$Éu]E‘ ùl܈Á`°X,wÁ4MÇc/IMS5Mç\‘$*„X,I’öû=ƘëÞjš¶Ûõ¡P6fÏ çü< V7_$Ó4-z£?«h+Ьë¥òöµ.!ˆ!Tˆ•b‡úZ†?ŽÛmÍqœf³©(Š,SJ„!VY&Ò4 ÃÐu{···Ý®‰*áÎð8¼c÷d¬Eyzš7'þÇ9›E½^ßó¼=jL”RY’>ñA(%y.ïz»ç¼¹é2ÆcŠ¢üqR³IST•éºzwwGȪ۵ƒ•Û=›Íæ`0p;IYÞ±Oma }Ûv¥¹Ïuvˆ—)MÓ"[-UUß쑤”2ÆdY¦´1Ý5›Œs´ž*¶×™Ÿ?>°ÿ×6P_ÞGÅ«È!Z­–iZår’U)gÃ\tBšÍæb±bõñ©KÓô³=‰ð¡*k‚ån[’$…á¬Û•«šD]̆±íKí\×j±$I³L4?9¹{6‹E¡ôdóÒ¾¤ j‚åªDœó‡‡‡0 ƒA…ƒ˜—;æU¦ÙI’yš¦Ÿj/—‹0 £-ËŸîL€wì‚£Ñ(M“b©ÈÛÛ[Ã0–Ë%!Â0ôJÒê¢güJUUÆX†Å6[ ‚I’Tµõ¾€³²WþøñƒR:™<ÛzžwssC)Õu]Ó´ýVøw6Ì…=ò>J©ëÞFQ4›Eë‹¿#ŽãÉdbY&cìk| çc÷LÓÔ0Œ  COÓ¤X(|<kš&Ī’¥e|ÿÞ¶¯”€MÓnooïîü0 ?ÌÁ8ŽûýeuÚmcçÅ_à-»Œ$I†¡aè†a$Irwwçy^¿ßÿóçÏÏŸƒfSÙ?G£‘™$cHTˆÝW…ÙA·k²îæó'Û¶^­â-—‹ x˜L&–Õ1MóSÙÀ–vAY–Š…*‹!à<Ï‹}ã8žÏMÓ÷AI¢„ã_¾Bˆ,ÏwXTuG”Òn×bŒÝÝyŽã†Á¹Á“eeµÊÒ4Íâ0 %Iº½uÚmãÅ‚ƒupph{M‘YŸEát:Ͳ̲,]W÷.Ø‘PJó<»=ê—ç9¥Ýâ‰RÚnŒ±ù<žNCß÷‹§â(¥Š"+Šâ87ªÚúp±ØGeóeYšN'SUeœó.³úYQôk±Xî0{™BicÏÅ\›M…sÙ0Œ,[ ‘óf(¥”J²Ü ”Ö³XÂ#Û@}yUN–fŒÙ¶Mþ®!ž¿fSi6•Ýe«$¡Š¤“$é8mXWñ³Ã—ëj^á¨­ŠŸ¸,A¨µÓï;†!n§òJs<îÈ£Z_ûv_Õõï—fþõ£D¤l‚­þ‚瀞!Øëý8Ú½¯®®Ž¸¿?ív°3sbB¦Ó)!Ä4Íâ`dE§,Ó¹Âwµ¿Wj‚išáÆe;ñÈ·Xwú>A€wuBY {ásíû]atÎS«ß¦æ Ã×µ3ÔáL©LUÙ¡G~”ýAgÿ_®¬«²vöq~ûÖÚþ~Jéx<Ô´j–•þ!D'Œ5³ô| ‡`³Éâx«Á¦lµò777¶m3‰ò<î†Ã¡$QÃ0ƒ°Š›Ãš¦ǽ÷ýFƒt»GÊÁ<Ï}ߟN§Œ±étÊy»Xê·†ÊID -ỪDõ}‚çb%|ß§T6M~è0ÊóÜ÷ƒÉäQQE‘TUÕ4 ð].Ìòݾ«Jb`„òŽIÄÊóŸG/×”.›Ãy.$‰B’$é÷“â]QJ’$Äó&ë/ÜÞÞÚöM»m¼ÿÆÂ^¢ÆqÜ4M>õÆÔ4MÂ0üþýöÏŸ?*ÀeùDŸ ï{–e'I¢ªªï{{Þ8M“áp”ç9¥$IÒâ aý~¯ÛµÊŠçË#a¥éóùY¶zë´’ã¸{¾°OÔ !yž»nÏóÆe;÷-–e}MÓ6Ž—5AÃàž7Ö4MÑjée1â8‚Éb±(kšGÞªc¾|#l@Mà¥ÏM‘‘$©ëxŸã8ƒÁ°Ü£îþþaã„<Ï¥I ‚Iyp¹\hš6ôã8~ëçF<³”U—§lãPSdL“ !lÛ)§ÈÒ]?a8t»¶,Ëë X×í ‘ AƒÁ[G†Ãa¿ß+RÕ4y¯×{õ4€m|®9  Ía€—ðÄÔBj !µ†€ZC@­! Ö‚PkA¨5„ ÔBjmóÙaÆÔ£Ýûêêê˜o}ùÛá H€¼²€Âg—êÜM¹Ü)nWáíà³Ð€ZC@­! Ö>^TõÛ·Ö˵ì·'„ˆ¢_’$ïs‘Ãùö­E·X(›¡iÚx&^Ú9¶ò\xž7ŸÏeY&„¬V«<ÏmÛ¾¹é~øÞO¯ÜÁ}Œ±ûûÀ4;UUÐ$IÒ4-Žc×u)¥†a0¦<<,5MÓ4 Õ@€Êm1Ef¿˜Ø­#?ÏE¿ß—$i41Öj4H¶ZÅq< ³,³m«ª8سjI) ÃY»mTOq;ÎsÚ¶íy^³ÙTUµÕÂ@õδOÐóF’Ô†F£8"7Ü0XàÛ–%DnÛ7Íæ"ˆãØqJ%Î Û¶ó\„a¨ªªaè_òóœÜ9>6—çyÎúýÛ2KLQ\×¢ù|sUìÙ¬¨JœsÛ¶UUÏç”RUÅÌ€C9ÇŒ¢¹ªªÍæëƒ¦i.‹(Š‹…xÍ‘K»3Ïó,ËžÍââÇÙ,*FB:ç$„X–9ôL³‹™1rŽÍá<_P*½¨>“e9Ïóûû‡b6ÉK±¶¨"&”Ò,Ë!³Yä8·’$u:››n9 Ül*77öIK ðÅ}‚{Ö­vx»$)BÄ«y5³,“$IQ]×k¾å¾uÍårQí5 ¶µ”–" £bÅPÛ¶+ÜþÍ÷ƒ0 mÛ*–c©äšP[Õ‡`žçÓiA’$„UUã86 ½’ÀŠã$‚F£áû¢4Ûmcÿk@U‚BˆÅbñðð0†«ÕJ–eB¥T’¨ª²J Íó|4u:n·ë8ÎãcˆÕ˜`O„ $ŽãÉdEQ£Ñh6›F£ÉšÅN²¬ì³jéº ˜!Ü^O‘eÇq|ßoµØÍ]‡Õ–:©IDATÉÅ ž*ñ}ÏuÝ8Ž[-Ön·Ež?ÍóE¾ZeŒ5UUÝgaêÒl?<<ôû}E– !¶ã´Z­ÇÇ0Žãý/µUAªªfY&çín×bŒ…a¤·ô,Ë Y’dÃÐ÷o çy~wwÇ9çœGäF£×ë¥i“Ê _Z.s]7lÛAæ|1„ aèÝ®å8Ža„Îf³¹\.c²¬(Jsÿ[A „p]wý ®ëŽãÄq\lã»ÿ]Þ"„˜L&ÅŽïýþÀóöZŽÎJ!Xì 7ŸÇ®Û+6dLY­V”RMS÷¸ˆãøáaÚï÷_6«mÛaŒÁd±8ìÌÁ"õƒáp8 ÃÈu{³ª„_A5“¥§Óé`0l4qœ¨ª:›ÅŒ±bWÉý/>ݵÛmMkg«ÕÆ”×í-‹étz¸Ê঄sîû~«Õ žçŸ•1•±j¦¾¤JF‡…çùªªJ’dšb>Ÿ†Á«dH$I’8ŽÞ)@Í-«{œé2Š¢ ‡Ã0 G£QEý~_ÓÎ:eÒ4ùö­…u½^µo&q²È²ápEs]W5M¢ˆ1F)ÕõV%¤§ÓédòßèÇ|>ã˜ó6cŒFqpç±Ïó !BJ©e™„ÉdZþ¶8óù|ãœsUÓ¼ñØu]Û¶]×Ù­‡V$à©Kp¾ö ÁÙ,êõº®Ûv×¶­"‰8ç„P!DU­°fSr›üÛ,õ}?IUmÙ¶S†,¥;neéy!„R*„("¯8R(Žø¾_¤äÆ{Yîv»a†ax¶!HA໇`š¦½Þ@QJ)¥õº˜aè¤Ò v‹K×û÷²T’èþ·p]—!Ä¥u]w£{Ñqì$yŠ¢¿FB²Õ*ð}ß÷eY¶mkÏbÀ©ì‚yžçy®ë:¥”s¾1rAë”5¸2øŠZç:Ûv|ÿn=çóùx<~zzâœw:\Óôã”vE}ü8ëi\¢]B0I’Á`è8¶iš²L5­šÅNë@)):)!Y¶ ß÷}ÆØí­Ã9?ÿ‡—10ðŽO‡`’$ý~ŸÇÉÍM—1vˆb§8N:vØ_ ªò‰¥´Î$áS°”Àû»ù:œt ¼!øÅ!Þ÷JŸ`†Ç,Á×¾Ý9ÀÀÀ;6Cðêê꘷ÿÚ·€ó·‚¨5|1xbà}èüúÐ-ð„à‡x_»ÍÁ™CÀ;P€ZC~qxbà}ÿT¾RO÷wIEND®B`‚gaphor-0.17.2/doc/manual/plugins.txt000066400000000000000000000041161220151210700173160ustar00rootroot00000000000000Writing plugins and services ============================ There is little difference in writing a plugin or a service. Accessing model related data ---------------------------- The datamodel classes are located in the `gaphor.UML` module. Data objects can be accessed through the ElementFactory. This is a special class for creating and managing data objects. Items can be queried using this element factory. It's registered in the application as `element_factory`. When writing a service or plugin the element factory can be injected into the service like this:: class MyThing(object): element_factory = inject('element_factory') def do_something(self): items = element_factory.select() .. note:: In the console window services can be retrieved by using the `service()` function. E.g.:: ef = service('element_factory') Querying the data model ----------------------- Two methods are used for querying: * `select(query=None)` -> returns an iterator * `lselect(query=None)` -> returns a list query is a lambda function with the element as parameter. E.g.:: element_factory.select(lambda e: e.isKindOf(UML.Class)) will fetch all Class instances from the element factory. Traversing data instances ------------------------- Once some classes are obtained It's common to traverse through a few related objects in order to obtain the required information. For example: to iterate through all parameters related to class' operations, one can write:: for o in classes.ownedOperation: for p in o.ownedParameter: do_something(p) Using the ``[:]`` operator items can be traversed more easily [1]_:: for o in classes.ownedOperation[:].ownedParameter: do_something(p) It's also possible to provide a query as part of the selection:: for o in classes.ownedOperation['it.returnParameter'].ownedParameter: do_something(p) The variable ``it`` in the query refers to the evaluated object (in this case all operations with a return parameter are taken into account). .. [1] See http://github.com/amolenaar/gaphor/blob/master/gaphor/misc/listmixins.py gaphor-0.17.2/doc/manual/simplestereotype.png000066400000000000000000000250121220151210700212150ustar00rootroot00000000000000‰PNG  IHDR¦͉n¡iCCPICC ProfilexíZgT]“¾=‘0䜇$*9 9ç,9gÉ ‚‘¤EP‰Š(IE$ˆ€(H(HF €„mðõûvÏžýµûoí9ÝýLݺuïtu÷­©§¼ì䇸„›j©á­mlñØwÄð‡ˆ8»†©ëÃ*ÿÃö㬠o£"¶h™x8‘£ncUñ߯£Èÿ¡Ó1U0< 1, õüݰËoy€#BƒBaŒìêå ·C%067%Àø9D8ÏCLüæ»bªÙììì ÓWXïìì cfÊߨå‹àpWOØ>3< u€›w,‚ÇÀ(»¹‡¸ÀwÖqs qõ€Ÿ„š¿ Ü.xpM\ƒ‚ᾂðx®|†7çmäz@¿þ·Ìÿ$5{°Òü[Æï ÏM€z¿ek¦‡×¢âqìè¡9§Ûú°¿¿Æ6€Ý¬ýý_eûû»å 'èðs ?Ô¨ƒ ØL€p Ž T€ÐFÀØà ¼€? ăd2A(Åà:¸î€ÐZA'èÏÀïÀ˜ŸÀW°~A„…p5ıC¼ÐH’…”! H2…l 'Èò‡Â (Jƒ²¡¨ª„ê PÔ Ao )h úm#R-‚Á‹EÈ Tº3„=„ˆB$"Î#ò¥ˆjÄ=D;¢ñ1‰ø„XGì#ItH<òR©†4@Ú Ý‘AÈd 2Y‚¬A6#Ÿ GŸ‘›(Š ÅŽFÉ¡4Qæ(gT *•†ºŒª@ÝEu¡†Q“¨UԚ͌@Ë µÐ–hwt(: ƒ.G7¢£GÑ3è5 C‹áÁHa40–OL$&Sˆ©Æ´a1S˜ïX$–Ë•Ãêc°AØDì%l%¶;ˆýˆ]'""b!#R#²$ò!Š%Ê&ª zH4H4Cô“GÌI,M¬GìDFœF\B|øññ ‰‰‰;I I6É-’’×$«¤hR6RIR=RWÒS¤9¤Õ¤IÇI×p8N g‰ ÀÕââFq+d2<™<™9YY YY+Ùk²oä8r~r5òä‘ä9äµäýäó v y KŠ`Š Õ}³”%;¥¥5e8ee=å å2 ••&•U"U)UÕ$Õ5µ"µu4u!u3õ[ê-&9šhšBššqšZ6ZeZGÚxÚ2ÚnÚ9:, ?]&]ÝÝOzfz%z'úDú›ôýô« Ô R 6 §Êz>1R0J2Ú0žf,gìc\a¢a’er`JbªbbúÁÌÆL`öaÎdnbþÀ‚aa1g‰a)gyƲÆÊÂJ`õcÍemcg#g“asbKckd{ÏŽag·eOb¿Ãþ†É!ÂaÍ‘Èq‡ã-…ÇÛáÏáñ8I8e8]9/r¶r.qÑq©qq]ãêçÚäæå6ãŽç®å~σã‘çñæÉçéáÙàåá5çMäm俣æSã æ+çåGòKò»ó_âïåß°8/ðHà« — …à9ÁfÁe!!S¡³B„>a?bz$ùÈÃ#«Â\–ÂiÂíÂë"‚""9"}"»¢ÇD½E‹D_БЩŠEˆÝ›g7Oïß”—ð”(–x}”â¨öÑ3GývLè˜ë±«ÇÆ$)$µ%$[%H‰KùHÝš”f–¶Î”~&ƒ‘Q“‰•i–Ù•õ—­’—ã–s’»&÷NžAÞ\>[þ…™‚žBªB¯"FQ]1Q±K RRQŠSjWÚSVVŽU~¤¼wüøñ¸ã*@EU%A¥[­ª¥š¢Ú¯†S3TËR%Ь WÔ9ÕÝÕ+Õ—5Ä5B5hìhªhžÕì×"×2Ó*Ðú Í­í­]«½¡£ ¯Ó«K®k¡{EwZOHï¤Þ} ¯­Ÿ©ÿÆoàePo°m¨f˜a8fÄnäiToôËXÃø¢ñ[““fS”©‘i¡éœ™„YŒYŸ9­¹“ùmó- M‹l‹IKaËhË^+Z+«:«=k}ëBë%Y›d›1[ÛPÛÇv4v®v öH{3ûrûõê'òNÌ;È8¤:¼svŒurâr uêufrösnw¡vqwivŹ:¹Þu#r³w«wG»Ûº×z d>n>­¾4¾¾¾ýXüBüžùsûÇøˆœ ˜ ”Ì \ Ò * úuÒòd]0i°GpgkHdÈh¨XhzèB!¬$l'Ü&ü~MDPÄ`ä‘ÈÔÈ…(¨òhD´Stû)öS§OMÄ(Æ\‰ùuÚîtK,KlLìDœR\QÜþ§3]ñÜñgãtªÉƒG“¤“ ’vÎ:}œÌŸœžüåœù¹)l)ñ)ó©ú©õiôi1iÓéÚéw2h3NeLŸ×>_{áBì…¹‹ïe²ežÍ\ͲÌjÏÈÎÊÞÎqËÈ•É-Í#Í Ë›º¤{é^>>?=ÿçe—˃ò…Ô…±…Ÿ¯X]é¾*qµøîZÔµ…"‹¢ÇÅÅ%%%§K–KíJŸ•É—U•3—§•o_÷¹>~CÿFk…hEñMÊ› 7×+Ý+ßÜÒ½ÕZ%VUVMWRý«&°fæ¶åígwŽßi¬¨½VGUw®n§þdýBɆÑFÆö»Òwoßã¹wå>õýÔ&¨)ªéÛï›m›Gê=ìnQnijo­jãi+zÄô(§¼=µÕ×±ÝÞù­Ë¿ké±ÇãénÇî‰'6O^õ˜õ ÷ö>ïÓí{úTëiO¿F÷3³®çjÏ;T:Õ»^^<RêÖîÑy>j8:ôÒìå«1ë±ñW¯¦_»¿^zãÿæÛÛ°·ÛïâÆ‘ã)ä9ï™Þ}àùP5)1Ù4¥<õä£þÇÑiÛé韙ï³Ñsˆ¹ôyšù« ¼ µ‹²‹]KúK¯>9}úô9ìóþrÚ ýJéªðêƒ/ê_†¾Ú]úöñýâëZÕºÌú“ ³?ìýÌØdÙ¬Ú’Ûzºm³½ø+r‡h§`Wp÷ážÞÞÄ~ÀßXào,ð7ø üþÆc¿±Àß¼Àß¼Àß¼Àß¼Àß¼Àß¼Àß¼Àÿß¼@s°óa,pÀÊ <<ø~2h† EþoÛó(¿£ €„I¡b ó¼0ëpfV!(šGØ!f)( 4F ëMôŒÄ'M.L±G5B“M§F?ÃÄ4âÇZƶÊ!€wâÌáêäþÊËÁgÀ.P$Ø'ôM˜A丨ŸX¾x¯Äþ1ÉSRÍÒk²¼r¦ò1 %p&sRyG…AUZÍ‚®~E£EsDkZû«Î¦î.œqÜ7Ø7Ü7†LЦäfÌæº–^V)Ö•6}¶íÖìwNì8¬9~túà¼íÊî¦íæqÃóµ7ÎGÕ7įÌ `<°=(ïdh°Cˆi¨a˜Yø‰ŸÈ¨¨¤èÌSE15§ïŶĵžyßP“X‘Tz¶4¹ø\qÊ•ÔÜ´´ô3áçý/x_ôÎôË ÉÏ ÎõÏs½d™¯uYº€¿å õUÜ5Ôµ¢ÉâÞ’êÒŒ2ŸrëB7(nü¨˜¾ù¡rêÖlÕ§ê/5_o/ß™¨}RWVŸÝßxãîý{÷§›~6“\2rfÔþ¥ìÍØê«Á×uo.¿=óÎmœ0Á1ñóýð‡Êɤ)×ZÓ¢3ÔpÎêù\Ù|Èa‘fqf©éÓÅÏAËF+l+/Wý¿€/É_™¿N~[û¾±–·.¾Þ³áýƒéÇçŸk[N¿ºö÷ý¬@ Xp¬C*P>ÌùœBâM¨Sh[Œ 6¾´HÇÉÒ)Œ©Ø¨—hKéµF˜t™+YVØ8Ø…8dðÊœ:\'¸Cx.òÖð=åÿ,H+¤xÄK8Sä‘ègqF ÂQ¿c—$;¤feHdEåtå=b3•Ê”ëáìõ€ê+µ)ÂŒú‚Æ¢æ<|Œë ëöêµé7TeÇ™„™F™%šgXäZ^µ*³®°©´­²«²¯:QãpÛ±Öé®s³K—ë€Û;÷%/¤×¦÷œÏ°o›_µÿ•€ó±A'OzÁwƒu¨Y˜I¸q„i¤U”c´Ç)¿ŸÓ–±Êqghã1ñ[ «‰ IÏN$LyšÚö0½!£âüå ©Ã3C²|³ÝsNäšåi]’˼L_€(X.»ÒzµôZr‘O±~‰X)CI9éu²”Ô7i+ioÑTQWS×Pݦ¼C^KVGZOÒ@ÔHtuwóÞÜý᦮ͯ¶ä·æµå=ºÜ^ÜQÙYÛÕö¸·{ìÉ\Ïrï|ßÄÓÑþ—ÏÆž¿ø08ÿbehmxcdmtååìØè«ö×eoÒÞæ½»3Þ;ñîý§ë“›S¿>þœ^™™œœkž/[H_<¹dõIó³úòñÑU¦ÕÝ/Ó_;¿]ÿž¶²î¾áþ#òçõÍíœùCÿ#P<Àdƒ6ø- 3ƒM" ‰FÖ¢BÑÖOl%Ñ/Ò‹¸çä Je*Mj-ÚctÌt»ô1æ2y0K³°L±6³å±sãÅ8É8?q=á.å‰äÕácâ›å¯ˆT"9rMØODI”RtN¬I·<ª<«}j|oûÞñ¯=Y]ŸÞp½±çîê}Ú&©&Í#Z’[óÛn=jo×±ß%úعûò“¡^dŸÄS«þggŸ|8ô`dk¬èÍÓ‰·ÓªK-bþÿͧ¬ iò—°ƒyvó+\°„ùôBH0&ƒera/œZÒ`ÿ×úÁ d)ðq ”ƒ» Œ‚i˜Þƒ¹gHR„ a®9JŠ »ÐsxuA 8ÊGD¢ñ±‰äBÁÌp5rE†RE…£jP³hv´ :=Œ¡À`Ò1ýXb¬.6;LÄDäJTK´GlD\N¼EbBR ³®Î¤p,¸hÜ{2M²råiÊ]ªXj@Oƒ¤‰§…hÏкz4}#Ã-F5Æi¦4f%æ1o–Ö lœlìÚìãAx,¾”S‰ó=×nCY^fÞu¾^þ<A>ÁU¡¦#g…-DxD~ˆˆÝO—=êrÌLRSJAZT†G–EŽQžYKQDIAYç¸¹Šƒª§Zá´zžF½æ+m #¡ë®wY¿ß`ÛHÐØÝ¤Ö ˜ÛZ´ZñYgÚlÛ9Ù÷8Ð9:8Ýp^v•v;ç>é©çÕçcïûÒ_8 6pè$kp@HG}xhÄ«(èñ˜æØô3ñ«‰Égé“ËRÄS;ÒÍ3æ.$fÉšÌ)ÎóËW. +üzõEQgISYËõÇ/*GªFkúït×½lX¹ÇÐDho)iëlÛ¹ÙÍßãÕ×úŒ{ ãÅöˆãˇ¯~¼uœŸì˜¹?ï³hüÉeåé·àõ{?í·B~Qí8ïŠìnî¥ícöß\@ œáà<ìý&ÐÞÃïQA\$\k`yáëP3\S°„@¾WB8À¾¿…xÄ e‘¾È"ä ‹RD…žŸûÇó#JŒ&3‚¥ÇÚc˱Ÿ‰¤‰‰†a>>Œ¸fßcIÆII‹pœî%™ ìwnòB ŠLJJÊ\*fª2jQêš)Ú0:]&=}=ƒÃ8cÌ[70[0o³”²ê°n°³°osÜÄ[p¢9¹\¹¹¸¿óôðñEð  bg„:Ž ÇŠ8ˆ*‰±‹í‹OJ´-;–"%+!“/[&wK¾Q¡Uñ±Ò€ò»ãŸUvÕÈ Bê^š™Z­Ú_t)õ”ô= .“˜è˜f™½··L¶šµÑ¶½cÏ|â¼Ã®“·ó=W´›9üþÙñ²ô¾ëKçáÿ>P=¨>˜#$+ ñ5Ê7z-æbìѸÁx¿Dâ¤òd¹sÏSÒV2¢/`/dqfßÍUËÌ÷*`+|µ¸È¤]ÚTîsƒ¹b 2¹J£†òölmW}Cãí{íM=ÍÁ-|­ã®u¸vÉt3=Ùë]ºõœtgÈpäÔ˺W+o…ÇåßkL†l˜EÏÛ/>ù̳ûeä;jáÉÏá­ÀíÉú]Ìîû½kp v?ÈÁ*¹ðs?>Á~§‡ŸxdE@9P=4mÀu#šˆD9â ’©‹LE èP¨[¨ 4®à˜ÃÈc²1+X=lülŸ#Ú"$^‚ë*¦IH'qN¸²Hrrò Š]ÊÛTÎÔtÔý4Q´|´Ãt±ô"ôÓ EŒîLrÌ¬Ì ,7Y=ÙøÙÙ«9ñrœXÎ ®Ü ¼t|’üÚ&‚ÖBG|„#ED³ÄnŠ÷H¬c”Ô ‘.’òÊ ‰ŠƒÊlǽTT· Úêš$ZÚ‹º¶z½↹p<š`Fa^eif°i°s8tHpÜvpùèfç>áâÍá3ççë?hô(X8¤0Œ$<1ŠÊ8åqúF\Gü£ÄëgOŸ3IåOÛÎh¿žéœ­š+t‰þ2(øtåŵºâ¼ÒÓå~7lnªÝÂWý¬é¾“QgÞÀÓøó^_S~³s [ëУ¨ÖήÇAOøzûîö§=w”¢^½?õZìÍÔ»‹ ï—&‹?ÚÍðÌnÍO/N~"|ŽY.]i^íúÒþµò[úw‹5ƵÁõð ŽÚr?ºªü¬Ý䨼°¹¶¥»U¶µµm¼}}{öÏ/÷_7~Íïðíxìdí´í,îÒìßõÙÍÛíÜ]ÞcÜÓÞ‹Û»¿·¼Ï½o»Ÿµßwàÿß5Xë !úãõ ê‡_ÿïþ~ap×áF q~†µ]ôð¾è欮ŸYá}/Èï°Ö˜Ý,Ì`Ùp14ú+{kšÂî …ª`F{…›ÿ#ö"ÂËsÝC4þØ)ñqÖ…×F@Ë‚ÃL-`ÌãŽp3 SÂx6ÚËÜêu7wõ䄇·¦Îoµw¨ÎÁX´ð0.ß@½ƒ9Àc!¤¹ƒ0¸âËÀ5aú€Ôÿ9Šà ·„Ãm!ÀÌÃØî÷ „1þ=“höó„ûýW‹x¸¾,íϘ¿GÃÃcþ±é Ü`üGî qÐv0»Gï„ùGãÀÞálīŗÄwþÌ Å‡:Š’B©¡”PÊ(9€GÑ£˜J% ¯÷ÇQ p›Ð³°eÏ?s<°ïßà~90JÞÒ n=øí.Zå¡¶÷¿¾ÿ·ïÁå¦å?3€ë'ká+ 0°Ÿ®& .*TÒÁù?o¡î‘µ‚€ìíéŠW…«&Ý…ñ:®¢Âø£ââà?Ÿ˜5Í&°© pHYs  šœIDATxíwÌÅÇÄŠ¨QcÁ†ˆŠ ÄòS4±·Ø[b@ÁFH `CCT{GÄMì±F£D"X£XJUàµÞßGŸd²îíÝíÝíîÍí~ïÍìì̳3Ÿyî»ÓîvÅöööôbèPŒjª–" "ðIžü@D @$yjlUUD@’'(I^[UäÉD@ D@’W ÆVUE@$yò䨱UUIž|@D @$yjlUUD@’'(I^[UäÉD@ D@’W ÆVUE@$yò䨱UUIž|@D @$yjlUUD@’'(I^[UäÉD@ D@’W ÆVUE@$yò䨱UUIž|@D @$yjlUUD@’'(I^[U ¸ä’KVÔ'M1oúéŠíííM/„ M!À—½)÷-ÔM}S˜t{yzŠÖôõð‘X„/'ßI}Ò à§ó¤ÛËÓS´ÖVÇójÍ¢ôu0ÿóºVÎè'ÞŽ• ÈU¹TŒz<Ä¡¤4"Ð t¶ NÙE@D Y’¼dyÊšˆ€×$y^7 '",I^²úè£ÇOxùòåK—.%à²D¦ïÚµë?ü@²¶¶6Ž¡Ó]vÙeÚ´i{ì±G®Öô)ÇŠ‚Ý{ï½ë¬³NÈc¾øâ‹šì+q)rÌKSƉ)uÚ{8×ÂZ¤»®¼òÊS¦L±{E&8øàƒï¼óNp<äC,¥3‹¢!mDÒIÜl³ÍxLæ‹Sš%Òx#Þk% “Å2^÷é u猓±î:Óyî¹çxùå—Û:wîüûï¿[˜Vü믿,¼ÆkXQ óeÚÛ"7púöÛoïµ×^tèHÃãÔ¬½öÚË–-³p(Kdzº„¸~‰“>t:gΜ-¶Øâ‘G9âˆ#ž}ö٠٪áHV³gÏÞgŸ}ìRèÈC¾ªM%¨L ’yå,®–:mÈ=‚Þé®8¶ùw‰L€†þüóÏ\%Y·nݬ0A³xËŒ3øîyä‘v›¥Y"7â½v¯Ð1Y¼!ãuŸfñO*¡ïjœS°Ì[Ñ]zë­·P:ô®C‡4žåå´t¦ƒÞ"…–•³ 'Lž<™Áo¿ý†ëX² ÿ_™þñǧ÷ðÃßsÏ=”0tÊЀ5–é öìÙ³\IâÄÿùçŸW_}5ÿ Gõ#Ó;‘W™=R§ ¹G°H‘îŠW3‚±d‘ ‚"ÃC‡e04oÞ¼SO=52Aã zo…[7ÿRÝb'£U/NÊÒ4·ß~û„ 8^sÍ5\ >Ê\ÏŽxf|íµ×š›$Œ´±|a‘ë­·Þ×_Mxâĉè…EÒÃ/7°Lo}+ç$B§{î¹çüùóéKr4ûñAVh7ó5•cÉ’%ñ+e$ óȵF†œ6äAoŒt× “G&`0{÷ÝwS*Žn`4Ë’¹fæ¸Ý0W·,<¤=ôP«Q¤ñF¼7Tâx#ïRk¤§[«†uÈíô's$saTæÀìõï‡mfEz[‡v§4ùÆoÌ4]§`®ý÷ߟL¶|A2w£ÈôŒµIÉèø®»î"qè4X`+@ü£ùó‰Ã† «,vvU’Ÿm¹”i|'ƒ>r 7Fº«ó= ™€Ù:löîÝ›#a«WÐ,1¬é³^a—8b“Sñ˜æcíËâ#Kî²7Ho#å±¼9ߤG;~î¢h¤øV³!Ùä­¶ÚÊìàµîÓj¤Á¼~âõt./®¦ð3Ï<3vìXÖdùž3ü裒ôôËÒØ;RSÙâ$þé§Ÿâ$# c¨˜)c&£ç‹P–j%ÒI$2j  ”V[m5lVY vìØø¡Œd·‰Hâí[³„ùKY}d‹šÖá´Ìç0te¬ãô.įQ®$ïùçŸgŒåZv$1ûËbEã€2¶ðÁ°ÿîÕW_­|_:tÌH’æ?þ`‹/¶@6G¾ÛôL¹—놴’ʚȆ´=%%y¼Ù¬¿©3+‹¬Ë˜M+ÜåÁäêˆ#æÎka—˜S«…‹©à¹þù矇’Ñú,…Y?t©˜§¹زË ï'Ÿ|Âf=š“§¥=EƒLfèØ¥É’_³ã?á  Ã:É•W^É>&&û®¸âŠO<‘ì‘‘éy‰•2#Ö¬Õ^tÑExj¹ÛñU7E+— ŽxÓJäÏ´mÈ%v¸JÁBZÙÈŒÿˆìòö%KÿYfÖM|Y«š¬£¨Éf±^U²6k²ÆÜœ-¸s–0SÒìzã7X£{á…ÍœsÎ9¬¼‘ŒguÖO<Á3oðàÁtÿ™!Þr¡ƒl×Ç©‚Ƴ ;—Îæv1ï’+ÉãQ¿á†º-æAÐ{زd󾯿þ:S=ö{èÈȆ˜“O>™%]~Ý’72ÒÙL<ò÷ߟb ß‘7b>ŽUãÈK-©•ˆ,ßRs¨N©V²KœxtÓæBêLFÓr‘†àôVKHõåa|饗R©³Ï>c9˜þ8»óŽ;zˆÈ=zÜpà ˆ£ýŽÈ<Š9_Ö.Î<óÌì#äÒÙ úŽ´wz»ezöC–±<Šä”bð¬³xìmþÞÊÆ׈gsòFm4dÈ›o¾Ù~\Q.ÒŒ¤q,eÅ7?¶øÐ‘Þhe(šÍRæM!€sR’Э‰qNKØZ|ë­·&L¿›]&˜ž&Wé^T4x°•Êzè!ãÙœR>ÙÜ+þ]üý­ñªéȺiŸ}ö™å‚BiösÏ=‡`/1ƒG®ÒÅàÈ/ÕH2ß1|øpv'Y®ÈÈRƒéÅÐÍdR’QÌlÞ]d¹%ج%“0”–0«Il¸JgŠCÝ—þý¼øâ‹œZ¥Ö_}KÙu̦¹’¼‹/¾jü¹À­·ÞzË-· 0 "`Ÿ'Ÿ|Ò]Eép&“¦ûÝ~d¤Ë’Y`¿ýöûðÃùýPð޶1%£pa ð¯<ÝÙ(Êö¦wß}—‡ºMÂH•ŠÇïÖ‘Òî]Gƺ³ð'+ü:š%9DÁ\SÃð ’<ýØ®Lü˜1cH°÷ÿö¶]lŒ‹ù/»{d¤]JãX•Cr[¸$%¿ÍH£ E³Y•y6@â lÍ]J~5DáYß§„Œs™°[wÝuéÜ1?Ãâ›Ø:·Ï¦"¡»x‚7Tª\-_â=Ú`„æ©P~z 't«ÒüýAV…ĺT•@æU(A9~âÍÕÀ¶úÜijšÌ?©±äb}ÒÜÔK̨——ê*7ªé‘È¿À³§ŠE]®F &æÕŒéz˜€Ÿx%yávjÖ¹ŸþÑ,ÙÜWÌSåì'^ lSmtð‹€$ϯöPiD@R% ÉK¯Œ‹€øE@’çW{¨4" ©䥊WÆE@ü" Éó«=TT dñ¡¶Vj5d\ê& ÿ¬]+fT/¯[Me¨“@½¼Ê¿­³à¹Ë¦¾F³šTþ™y?]Z½¼”š[fE@|$ Éó±UT&”HòR+³" >äùØ**“çX¡„ãÆ«pµ¾K¥/¸¨ÏŽréŸT|ñ›ëÕTz–íQ™9*|Zü‚ÅÏÈ»xìuUÇ·YÕTf *ãͬ¡©—¢S¨NÀõ¹\uÕUýúõã¼p‡œ£GæÕt¼G‘÷pº`Á^ÆÂ÷÷íÛ×ý‘5¹FÅ»VvÛm·›nºÉî7vìX{Õ¤I“0اO²LŸ>½zi”¢&t+ÒûXIÒ³Ÿ'Ëb•}kVfÞ¥K—rEr—:uêd/Kyçwxa¼¥wW9å%¼*À¬Y³Ð8KÀ»ô¦L™BxÆŒ¼@Ù"{õêõí·ßæ-½3sæL´ÏÂA›ãÿ±2Þf•?‹}y~nϱöÐQBÎ;ï¼iÓ¦i5¼çsâĉ¡dvŠoÛ›wú÷ï?wîÜÒ4S§Nuï_okks¹ì•uôãæÏŸÏ›É.\Èë•yY- >ýôSú€tykÕìÙ³Km*¦YÌå5R¾¢ååÑW´*7±¾•'›*LŸ¹K.@-x! /#ŒìÖ­Ûœ9sˆ V3˜€0/'›7o^÷î݇ F²ž={Nž3< u€›w,‚ÇÀ(»¹‡¸ÀwÖqs qõ€Ÿ„š¿ Ü.xpM\ƒ‚ᾂðx®|†7çmäz@¿þ·Ìÿ$5{°Òü[Æï ÏM€z¿ek¦‡×¢âqìè¡9§Ûú°¿¿Æ6€Ý¬ýý_eûû»å 'èðs ?Ô¨ƒ ØL€p Ž T€ÐFÀØà ¼€? ăd2A(Åà:¸î€ÐZA'èÏÀïÀ˜ŸÀW°~A„…p5ıC¼ÐH’…”! H2…l 'Èò‡Â (Jƒ²¡¨ª„ê PÔ Ao )h úm#R-‚Á‹EÈ Tº3„=„ˆB$"Î#ò¥ˆjÄ=D;¢ñ1‰ø„XGì#ItH<òR©†4@Ú Ý‘AÈd 2Y‚¬A6#Ÿ GŸ‘›(Š ÅŽFÉ¡4Qæ(gT *•†ºŒª@ÝEu¡†Q“¨UԚ͌@Ë µÐ–hwt(: ƒ.G7¢£GÑ3è5 C‹áÁHa40–OL$&Sˆ©Æ´a1S˜ïX$–Ë•Ãêc°AØDì%l%¶;ˆýˆ]'""b!#R#²$ò!Š%Ê&ª zH4H4Cô“GÌI,M¬GìDFœF\B|øññ ‰‰‰;I I6É-’’×$«¤hR6RIR=RWÒS¤9¤Õ¤IÇI×p8N g‰ ÀÕââFq+d2<™<™9YY YY+Ùk²oä8r~r5òä‘ä9äµäýäó v y KŠ`Š Õ}³”%;¥¥5e8ee=å å2 ••&•U"U)UÕ$Õ5µ"µu4u!u3õ[ê-&9šhšBššqšZ6ZeZGÚxÚ2ÚnÚ9:, ?]&]ÝÝOzfz%z'úDú›ôýô« Ô R 6 §Êz>1R0J2Ú0žf,gìc\a¢a’er`JbªbbúÁÌÆL`öaÎdnbþÀ‚aa1g‰a)gyƲÆÊÂJ`õcÍemcg#g“asbKckd{ÏŽag·eOb¿Ãþ†É!ÂaÍ‘Èq‡ã-…ÇÛáÏáñ8I8e8]9/r¶r.qÑq©qq]ãêçÚäæå6ãŽç®å~σã‘çñæÉçéáÙàåá5çMäm俣æSã æ+çåGòKò»ó_âïåß°8/ðHà« — …à9ÁfÁe!!S¡³B„>a?bz$ùÈÃ#«Â\–ÂiÂíÂë"‚""9"}"»¢ÇD½E‹D_БЩŠEˆÝ›g7Oïß”—ð”(–x}”â¨öÑ3GývLè˜ë±«ÇÆ$)$µ%$[%H‰KùHÝš”f–¶Î”~&ƒ‘Q“‰•i–Ù•õ—­’—ã–s’»&÷NžAÞ\>[þ…™‚žBªB¯"FQ]1Q±K RRQŠSjWÚSVVŽU~¤¼wüøñ¸ã*@EU%A¥[­ª¥š¢Ú¯†S3TËR%Ь WÔ9ÕÝÕ+Õ—5Ä5B5hìhªhžÕì×"×2Ó*Ðú Í­í­]«½¡£ ¯Ó«K®k¡{EwZOHï¤Þ} ¯­Ÿ©ÿÆoàePo°m¨f˜a8fÄnäiToôËXÃø¢ñ[““fS”©‘i¡éœ™„YŒYŸ9­¹“ùmó- M‹l‹IKaËhË^+Z+«:«=k}ëBë%Y›d›1[ÛPÛÇv4v®v öH{3ûrûõê'òNÌ;È8¤:¼svŒurâr uêufrösnw¡vqwivŹ:¹Þu#r³w«wG»Ûº×z d>n>­¾4¾¾¾ýXüBüžùsûÇøˆœ ˜ ”Ì \ Ò * úuÒòd]0i°GpgkHdÈh¨XhzèB!¬$l'Ü&ü~MDPÄ`ä‘ÈÔÈ…(¨òhD´Stû)öS§OMÄ(Æ\‰ùuÚîtK,KlLìDœR\QÜþ§3]ñÜñgãtªÉƒG“¤“ ’vÎ:}œÌŸœžüåœù¹)l)ñ)ó©ú©õiôi1iÓéÚéw2h3NeLŸ×>_{áBì…¹‹ïe²ežÍ\ͲÌjÏÈÎÊÞÎqËÈ•É-Í#Í Ë›º¤{é^>>?=ÿçe—˃ò…Ô…±…Ÿ¯X]é¾*qµøîZÔµ…"‹¢ÇÅÅ%%%§K–KíJŸ•É—U•3—§•o_÷¹>~CÿFk…hEñMÊ› 7×+Ý+ßÜÒ½ÕZ%VUVMWRý«&°fæ¶åígwŽßi¬¨½VGUw®n§þdýBɆÑFÆö»Òwoßã¹wå>õýÔ&¨)ªéÛï›m›Gê=ìnQnijo­jãi+zÄô(§¼=µÕ×±ÝÞù­Ë¿ké±ÇãénÇî‰'6O^õ˜õ ÷ö>ïÓí{úTëiO¿F÷3³®çjÏ;T:Õ»^^<RêÖîÑy>j8:ôÒìå«1ë±ñW¯¦_»¿^zãÿæÛÛ°·ÛïâÆ‘ã)ä9ï™Þ}àùP5)1Ù4¥<õä£þÇÑiÛé韙ï³Ñsˆ¹ôyšù« ¼ µ‹²‹]KúK¯>9}úô9ìóþrÚ ýJéªðêƒ/ê_†¾Ú]úöñýâëZÕºÌú“ ³?ìýÌØdÙ¬Ú’Ûzºm³½ø+r‡h§`Wp÷ážÞÞÄ~ÀßXào,ð7ø üþÆc¿±Àß¼Àß¼Àß¼Àß¼Àß¼Àß¼Àß¼Àÿß¼@s°óa,pÀÊ <<ø~2h† EþoÛó(¿£ €„I¡b ó¼0ëpfV!(šGØ!f)( 4F ëMôŒÄ'M.L±G5B“M§F?ÃÄ4âÇZƶÊ!€wâÌáêäþÊËÁgÀ.P$Ø'ôM˜A丨ŸX¾x¯Äþ1ÉSRÍÒk²¼r¦ò1 %p&sRyG…AUZÍ‚®~E£EsDkZû«Î¦î.œqÜ7Ø7Ü7†LЦäfÌæº–^V)Ö•6}¶íÖìwNì8¬9~túà¼íÊî¦íæqÃóµ7ÎGÕ7įÌ `<°=(ïdh°Cˆi¨a˜Yø‰ŸÈ¨¨¤èÌSE15§ïŶĵžyßP“X‘Tz¶4¹ø\qÊ•ÔÜ´´ô3áçý/x_ôÎôË ÉÏ ÎõÏs½d™¯uYº€¿å õUÜ5Ôµ¢ÉâÞ’êÒŒ2ŸrëB7(nü¨˜¾ù¡rêÖlÕ§ê/5_o/ß™¨}RWVŸÝßxãîý{÷§›~6“\2rfÔþ¥ìÍØê«Á×uo.¿=óÎmœ0Á1ñóýð‡Êɤ)×ZÓ¢3ÔpÎêù\Ù|Èa‘fqf©éÓÅÏAËF+l+/Wý¿€/É_™¿N~[û¾±–·.¾Þ³áýƒéÇçŸk[N¿ºö÷ý¬@ Xp¬C*P>ÌùœBâM¨Sh[Œ 6¾´HÇÉÒ)Œ©Ø¨—hKéµF˜t™+YVØ8Ø…8dðÊœ:\'¸Cx.òÖð=åÿ,H+¤xÄK8Sä‘ègqF ÂQ¿c—$;¤feHdEåtå=b3•Ê”ëáìõ€ê+µ)ÂŒú‚Æ¢æ<|Œë ëöêµé7TeÇ™„™F™%šgXäZ^µ*³®°©´­²«²¯:QãpÛ±Öé®s³K—ë€Û;÷%/¤×¦÷œÏ°o›_µÿ•€ó±A'OzÁwƒu¨Y˜I¸q„i¤U”c´Ç)¿ŸÓ–±Êqghã1ñ[ «‰ IÏN$LyšÚö0½!£âüå ©Ã3C²|³ÝsNäšåi]’˼L_€(X.»ÒzµôZr‘O±~‰X)CI9éu²”Ô7i+ioÑTQWS×Pݦ¼C^KVGZOÒ@ÔHtuwóÞÜý᦮ͯ¶ä·æµå=ºÜ^ÜQÙYÛÕö¸·{ìÉ\Ïrï|ßÄÓÑþ—ÏÆž¿ø08ÿbehmxcdmtååìØè«ö×eoÒÞæ½»3Þ;ñîý§ë“›S¿>þœ^™™œœkž/[H_<¹dõIó³úòñÑU¦ÕÝ/Ó_;¿]ÿž¶²î¾áþ#òçõÍíœùCÿ#P<Àdƒ6ø- 3ƒM" ‰FÖ¢BÑÖOl%Ñ/Ò‹¸çä Je*Mj-ÚctÌt»ô1æ2y0K³°L±6³å±sãÅ8É8?q=á.å‰äÕácâ›å¯ˆT"9rMØODI”RtN¬I·<ª<«}j|oûÞñ¯=Y]ŸÞp½±çîê}Ú&©&Í#Z’[óÛn=jo×±ß%úعûò“¡^dŸÄS«þggŸ|8ô`dk¬èÍÓ‰·ÓªK-bþÿͧ¬ iò—°ƒyvó+\°„ùôBH0&ƒera/œZÒ`ÿ×úÁ d)ðq ”ƒ» Œ‚i˜Þƒ¹gHR„ a®9JŠ »ÐsxuA 8ÊGD¢ñ±‰äBÁÌp5rE†RE…£jP³hv´ :=Œ¡À`Ò1ýXb¬.6;LÄDäJTK´GlD\N¼EbBR ³®Î¤p,¸hÜ{2M²råiÊ]ªXj@Oƒ¤‰§…hÏкz4}#Ã-F5Æi¦4f%æ1o–Ö lœlìÚìãAx,¾”S‰ó=×nCY^fÞu¾^þ<A>ÁU¡¦#g…-DxD~ˆˆÝO—=êrÌLRSJAZT†G–EŽQžYKQDIAYç¸¹Šƒª§Zá´zžF½æ+m #¡ë®wY¿ß`ÛHÐØÝ¤Ö ˜ÛZ´ZñYgÚlÛ9Ù÷8Ð9:8Ýp^v•v;ç>é©çÕçcïûÒ_8 6pè$kp@HG}xhÄ«(èñ˜æØô3ñ«‰Égé“ËRÄS;ÒÍ3æ.$fÉšÌ)ÎóËW. +üzõEQgISYËõÇ/*GªFkúït×½lX¹ÇÐDho)iëlÛ¹ÙÍßãÕ×úŒ{ ãÅöˆãˇ¯~¼uœŸì˜¹?ï³hüÉeåé·àõ{?í·B~Qí8ïŠìnî¥ícöß\@ œáà<ìý&ÐÞÃïQA\$\k`yáëP3\S°„@¾WB8À¾¿…xÄ e‘¾È"ä ‹RD…žŸûÇó#JŒ&3‚¥ÇÚc˱Ÿ‰¤‰‰†a>>Œ¸fßcIÆII‹pœî%™ ìwnòB ŠLJJÊ\*fª2jQêš)Ú0:]&=}=ƒÃ8cÌ[70[0o³”²ê°n°³°osÜÄ[p¢9¹\¹¹¸¿óôðñEð  bg„:Ž ÇŠ8ˆ*‰±‹í‹OJ´-;–"%+!“/[&wK¾Q¡Uñ±Ò€ò»ãŸUvÕÈ Bê^š™Z­Ú_t)õ”ô= .“˜è˜f™½··L¶šµÑ¶½cÏ|â¼Ã®“·ó=W´›9üþÙñ²ô¾ëKçáÿ>P=¨>˜#$+ ñ5Ê7z-æbìѸÁx¿Dâ¤òd¹sÏSÒV2¢/`/dqfßÍUËÌ÷*`+|µ¸È¤]ÚTîsƒ¹b 2¹J£†òölmW}Cãí{íM=ÍÁ-|­ã®u¸vÉt3=Ùë]ºõœtgÈpäÔ˺W+o…ÇåßkL†l˜EÏÛ/>ù̳ûeä;jáÉÏá­ÀíÉú]Ìîû½kp v?ÈÁ*¹ðs?>Á~§‡ŸxdE@9P=4mÀu#šˆD9â ’©‹LE èP¨[¨ 4®à˜ÃÈc²1+X=lülŸ#Ú"$^‚ë*¦IH'qN¸²Hrrò Š]ÊÛTÎÔtÔý4Q´|´Ãt±ô"ôÓ EŒîLrÌ¬Ì ,7Y=ÙøÙÙ«9ñrœXÎ ®Ü ¼t|’üÚ&‚ÖBG|„#ED³ÄnŠ÷H¬c”Ô ‘.’òÊ ‰ŠƒÊlǽTT· Úêš$ZÚ‹º¶z½↹p<š`Fa^eif°i°s8tHpÜvpùèfç>áâÍá3ççë?hô(X8¤0Œ$<1ŠÊ8åqúF\Gü£ÄëgOŸ3IåOÛÎh¿žéœ­š+t‰þ2(øtåŵºâ¼ÒÓå~7lnªÝÂWý¬é¾“QgÞÀÓøó^_S~³s [ëУ¨ÖήÇAOøzûîö§=w”¢^½?õZìÍÔ»‹ ï—&‹?ÚÍðÌnÍO/N~"|ŽY.]i^íúÒþµò[úw‹5ƵÁõð ŽÚr?ºªü¬Ý䨼°¹¶¥»U¶µµm¼}}{öÏ/÷_7~Íïðíxìdí´í,îÒìßõÙÍÛíÜ]ÞcÜÓÞ‹Û»¿·¼Ï½o»Ÿµßwàÿß5Xë !úãõ ê‡_ÿïþ~ap×áF q~†µ]ôð¾è欮ŸYá}/Èï°Ö˜Ý,Ì`Ùp14ú+{kšÂî …ª`F{…›ÿ#ö"ÂËsÝC4þØ)ñqÖ…×F@Ë‚ÃL-`ÌãŽp3 SÂx6ÚËÜêu7wõ䄇·¦Îoµw¨ÎÁX´ð0.ß@½ƒ9Àc!¤¹ƒ0¸âËÀ5aú€Ôÿ9Šà ·„Ãm!ÀÌÃØî÷ „1þ=“höó„ûýW‹x¸¾,íϘ¿GÃÃcþ±é Ü`üGî qÐv0»Gï„ùGãÀÞálīŗÄwþÌ Å‡:Š’B©¡”PÊ(9€GÑ£˜J% ¯÷ÇQ p›Ð³°eÏ?s<°ïßà~90JÞÒ n=øí.Zå¡¶÷¿¾ÿ·ïÁå¦å?3€ë'ká+ 0°Ÿ®& .*TÒÁù?o¡î‘µ‚€ìíéŠW…«&Ý…ñ:®¢Âø£ââà?Ÿ˜5Í&°© pHYs  šœ ¼IDATxígˆËÇïš³b@Åœ}†gV\ó5‡*Fý¤  ˆ ¨˜3æˆÓ[Ã'3>³`BEň bPÌéþ®G‹ffvv¦ºg»k·æCíéê:çTÿÿ}ª«Ê{O%ýüùó/û3 ,FõÖvö_,gæ½–3Ë™y˜×cg–3ó0¯Ç6Î,gæ!`^mœYÎÌCÀ¼Û8³œ™‡€=f8öß”)S |ÄÀucÇ<¼eU±?SRRRìmË(Ä{ˆl!×±\ºñ‹ýŒÝÆý{oç æ½!–3Ë™y˜×cg–3ó0¯Ç6Î,gæ!`^mœYΉÀÌ™3•ùä¦ÉJÎl‚ÎÞ•_û yòäùðჷ }ÿþ=kÖ¬ÞÚŒnMöAÜ`豑»q㆔“&Múøñc:uzôè(ð'Ð 7®X±âž={¦OŸÞ¤I“òåËïÝ»W<þ¼K—.µjÕªW¯ÞÅ‹•Ö¸qã4hpâĉëׯS)¥Ü z "±ÿäaboï¦% õë×oóæÍ½{÷Þºu+¦rçέ *9GŽ7n¤>råʵmÛ6‘«W¯.ûöí{äÈä«W¯B’TfÏž}Ë–-"7lØðèÑ£-[¶¤”š„–î1ü+®þ¹÷»»öíÛ8p 99yÆŒ¢¥xâRÉ9sæüüù³4€¿oß¾‰œ?~Š)òß?¿ *¨–¼"?|ø°råÊ;vìèÙ³çþýû¥2q¥{ uöõÅk¢K†»Ã‡óúŸ>}Và&¢Ç,Y²@•Ü¢MøÇéÇgΜQc©´¤A)2£"a‡üêÕ«J•*IeËà~ϯjÕª/^¼{÷îË—/D(!8âE³]»v«W¯­›7o†«ó9<~üøŠ+¶oß^µjÕð«‰kÞÇ¥â²ñû÷ï± åØ±c‰(¤FJ R‡NùÙ³g]»výÏ¯ßøñã¹ÅÏ©åt!wZºÇФ¹~àÞw­e„¹>ÏÀoÉ’%‚rÍš5µÐø­´oß¾Fñ‘+X°`¯^½¼2ë¦KÞêå{ÆGÅ“õòÁƒ»uëöòåËÅ‹cóÁƒÞâkqÝÒã¸TÒlŒÍ¼yóRΙ3‡Æ5jԭׯ_8¯³¾I=¢žom˜M0D˜5k•… fU€@)wÅ“Fœfׯ__²dIºP¡BÇ—åÁ‚ Š+FeÙ²eYŠVÄJ¹¥]Ò~Úê(b®_®\9x‚³aÆÉ#IÉeJJÊÈ‘#K—.½téÒ;v¬E‹Ü={ö,ŒŠpûöm¦é¬ ¸deÍ& j±"8Ë%JL˜0‡?yò$ÓŶmÛ²2c[ ƒ ºwïëi±ÒiÊ9œñäÓ¦MÛµk×Â… (ìÞ½›Kõ©c ûúõ+‘œuìØ‘Õ›œpéTOM~úô)œ å´a{ ÎXW °•U¿~ýÎ;‹nÄÊÔ̦_}\A*ÝŠK%ÍÆØ”ÁP†Au‰"c&ô0§øÿ¯ß¡C‡àŒzvÙ?,Uª›Š¬Ž ´Þ¼yÃ-‰<ñql,P –YÍŸ?ÅÉ“'ÓøÅ‹+W®$²Yk×®][Ô#VÊ-íü´ÕQ Ê„Çà¿®e©ôë‰~lÓÅuëÖ=~üøüùó£Fúôé÷Ä®]»Fe«V­àíDöŽ™%rkâĉ”l ¯Zµ š6múÛ–ãC\±ıÜ=z4ô³!ÉŽÉÝ»w£T:,ù$ÆE¸ô1.•4cSM:† â¼|ûöíСC‹-J´Uâ—/_0È(J36‘ùÚ!1B9bwŸmßlÙ²åË—OXç–Ó,/óvXd:#qöw›¿‰?šñ9Tsˆ•Ê‘ž€ ~zº¢e×Ô‚aú•2-}m—µŸ!³)ZÎÌcÜrf93ózlãÌrfæõØÆ™åÌ<Ìë±3ó8ÓÙ×—•¼yÏšQzlãÌ<&uâÌÍ^™yyÝc÷£”3¯9I¼=ËYâ1öÚƒåÌkDoÏr–xŒ½ö`9óÑÄÛ³œ%c¯=XμF4ñötÖgîW‰®ŒìÁÆ™yìêÄ™ÝqóûQÊÆ™üýѵœùƒ»¯–37èù£k9ów7^-gnÐóG×ræîn¼ZÎÜ ç®ÎúÌý ßgÍ(^mœ™Ç¤NœÙ}7<»¥lœ¹Áß]Ë™?¸»ñj9sƒž?ºp6oÞ<ÒðãK ÂìÙ³A+Ó¦‘6éÿ' „'9±B¢#ÓHËÄÍ<. qÆ#©ìÑ!«K•útd™(4èÄþ¼bo¯×2<{´Øq&ʤF]š•FÚ=†ñ%qï/óGG猬s¥‘v¡ÎšZ¼&®Œ1{´ê@fK#ÄïYxöhEžÑÒHÇ2X©6™ºL¨žÚY}ÀįºTõÁO#íC“æúzA4­ ;×ÐêO¿g(€ñ€³è{H¬v=lµšŽÝ²¨H†FÑ"m#Iá9*aÙ²eRCÎ:$µS§N¤ŽŒRn-öžxÐ2®y„ø‹KÅ9Aˆ®¨Žç‰ÞŒ»±ÛT¦DEÍPÖ®];xð`v­h@[iFªTÉ=>wîÜ1cÆD© ±¦¼Ä"h`bÖƒ5µBÌéä(%7™‚ñ$™fIh*†p é™y»ëÖ­{áÂé Z*Bæfy奒 `kyÆ Ô“}ÒÛ*b/åD¦*Uªˆ KÀ[·n…¨“-œ3š¨$k.ñ'w#V†X ±ý2Xœ±‡$YaÏ;§ŽSŒò$Ñ"»t鉆åìþýûÈdç–š+W®p:…ÈN›RÓ¼yó?çeýþý03IÞ'^²×Þ¹sGŒ Wåœ&õ~”J¹¥W‹3ö$_0£F!'¾iEFnt>¸|ù²p€ò°·nÝš„ÅPÁWD`rÚÔ޼ßrfùûá[Œ˜Â™—{Wì!ÉAbò*…—¼Åp2ƒpEÖ§OòC“ÿ¾ÿþ¢Î9›6mjÖ¬¿y÷ÃmJ ÉõÕ¬Aj-ZÔ¦M›ÔÚsX¾¸Ë2’¸™4ÕŒŠ”Ož<á  ÑX™šÙô©÷`Þ½£ÎCËÒÜCbðLù_ÊÎ;9R̾{÷ŽC,׬YC4¤æëÔ©S žÎ_Â0B2wÄCà` ‚[Ìò­åhIdBQ*å–oe\c‹ô2DETJ ‡–¥yŠÌP8ã@¹`R¦LÒÞO:UÙt:R-ã˜ÖsC.S†bÑå\ÓþaA}G#VÆå+¤qD CÚD¿´{Wé-vï*½‚¿„Ï‚ð¬–3óµœYÎÌCÀ¼Û833}™­š÷¬¥ÇñÅÛíåÁý|—0Æ·¦öóA­ï?Äg´ì_?°œù‰¾žoË™n~jYÎüD_Ï·åL7?µ,g~¢¯çÛr¦‡›ŸZ–3?Ñ×óm9ÓÃÍO-Ë™Ÿèëù¶œéáæ§–åÌOôõ|[ÎôpóSËræ'úz¾-gz¸ù©e9ó}=ß–3=ÜüÔ²œù‰¾žoË™n~jYÎüD_Ï÷?”ߌq¤H”IEND®B`‚gaphor-0.17.2/doc/manual/stereotypeedit.png000066400000000000000000000776011220151210700206640ustar00rootroot00000000000000‰PNG  IHDR3r™\=—¡iCCPICC ProfilexíZgT]“¾=‘0䜇$*9 9ç,9gÉ ‚‘¤EP‰Š(IE$ˆ€(H(HF €„mðõûvÏžýµûoí9ÝýLݺuïtu÷­©§¼ì䇸„›j©á­mlñØwÄð‡ˆ8»†©ëÃ*ÿÃö㬠o£"¶h™x8‘£ncUñ߯£Èÿ¡Ó1U0< 1, õüݰËoy€#BƒBaŒìêå ·C%067%Àø9D8ÏCLüæ»bªÙììì ÓWXïìì cfÊߨå‹àpWOØ>3< u€›w,‚ÇÀ(»¹‡¸ÀwÖqs qõ€Ÿ„š¿ Ü.xpM\ƒ‚ᾂðx®|†7çmäz@¿þ·Ìÿ$5{°Òü[Æï ÏM€z¿ek¦‡×¢âqìè¡9§Ûú°¿¿Æ6€Ý¬ýý_eûû»å 'èðs ?Ô¨ƒ ØL€p Ž T€ÐFÀØà ¼€? ăd2A(Åà:¸î€ÐZA'èÏÀïÀ˜ŸÀW°~A„…p5ıC¼ÐH’…”! H2…l 'Èò‡Â (Jƒ²¡¨ª„ê PÔ Ao )h úm#R-‚Á‹EÈ Tº3„=„ˆB$"Î#ò¥ˆjÄ=D;¢ñ1‰ø„XGì#ItH<òR©†4@Ú Ý‘AÈd 2Y‚¬A6#Ÿ GŸ‘›(Š ÅŽFÉ¡4Qæ(gT *•†ºŒª@ÝEu¡†Q“¨UԚ͌@Ë µÐ–hwt(: ƒ.G7¢£GÑ3è5 C‹áÁHa40–OL$&Sˆ©Æ´a1S˜ïX$–Ë•Ãêc°AØDì%l%¶;ˆýˆ]'""b!#R#²$ò!Š%Ê&ª zH4H4Cô“GÌI,M¬GìDFœF\B|øññ ‰‰‰;I I6É-’’×$«¤hR6RIR=RWÒS¤9¤Õ¤IÇI×p8N g‰ ÀÕââFq+d2<™<™9YY YY+Ùk²oä8r~r5òä‘ä9äµäýäó v y KŠ`Š Õ}³”%;¥¥5e8ee=å å2 ••&•U"U)UÕ$Õ5µ"µu4u!u3õ[ê-&9šhšBššqšZ6ZeZGÚxÚ2ÚnÚ9:, ?]&]ÝÝOzfz%z'úDú›ôýô« Ô R 6 §Êz>1R0J2Ú0žf,gìc\a¢a’er`JbªbbúÁÌÆL`öaÎdnbþÀ‚aa1g‰a)gyƲÆÊÂJ`õcÍemcg#g“asbKckd{ÏŽag·eOb¿Ãþ†É!ÂaÍ‘Èq‡ã-…ÇÛáÏáñ8I8e8]9/r¶r.qÑq©qq]ãêçÚäæå6ãŽç®å~σã‘çñæÉçéáÙàåá5çMäm俣æSã æ+çåGòKò»ó_âïåß°8/ðHà« — …à9ÁfÁe!!S¡³B„>a?bz$ùÈÃ#«Â\–ÂiÂíÂë"‚""9"}"»¢ÇD½E‹D_БЩŠEˆÝ›g7Oïß”—ð”(–x}”â¨öÑ3GývLè˜ë±«ÇÆ$)$µ%$[%H‰KùHÝš”f–¶Î”~&ƒ‘Q“‰•i–Ù•õ—­’—ã–s’»&÷NžAÞ\>[þ…™‚žBªB¯"FQ]1Q±K RRQŠSjWÚSVVŽU~¤¼wüøñ¸ã*@EU%A¥[­ª¥š¢Ú¯†S3TËR%Ь WÔ9ÕÝÕ+Õ—5Ä5B5hìhªhžÕì×"×2Ó*Ðú Í­í­]«½¡£ ¯Ó«K®k¡{EwZOHï¤Þ} ¯­Ÿ©ÿÆoàePo°m¨f˜a8fÄnäiToôËXÃø¢ñ[““fS”©‘i¡éœ™„YŒYŸ9­¹“ùmó- M‹l‹IKaËhË^+Z+«:«=k}ëBë%Y›d›1[ÛPÛÇv4v®v öH{3ûrûõê'òNÌ;È8¤:¼svŒurâr uêufrösnw¡vqwivŹ:¹Þu#r³w«wG»Ûº×z d>n>­¾4¾¾¾ýXüBüžùsûÇøˆœ ˜ ”Ì \ Ò * úuÒòd]0i°GpgkHdÈh¨XhzèB!¬$l'Ü&ü~MDPÄ`ä‘ÈÔÈ…(¨òhD´Stû)öS§OMÄ(Æ\‰ùuÚîtK,KlLìDœR\QÜþ§3]ñÜñgãtªÉƒG“¤“ ’vÎ:}œÌŸœžüåœù¹)l)ñ)ó©ú©õiôi1iÓéÚéw2h3NeLŸ×>_{áBì…¹‹ïe²ežÍ\ͲÌjÏÈÎÊÞÎqËÈ•É-Í#Í Ë›º¤{é^>>?=ÿçe—˃ò…Ô…±…Ÿ¯X]é¾*qµøîZÔµ…"‹¢ÇÅÅ%%%§K–KíJŸ•É—U•3—§•o_÷¹>~CÿFk…hEñMÊ› 7×+Ý+ßÜÒ½ÕZ%VUVMWRý«&°fæ¶åígwŽßi¬¨½VGUw®n§þdýBɆÑFÆö»Òwoßã¹wå>õýÔ&¨)ªéÛï›m›Gê=ìnQnijo­jãi+zÄô(§¼=µÕ×±ÝÞù­Ë¿ké±ÇãénÇî‰'6O^õ˜õ ÷ö>ïÓí{úTëiO¿F÷3³®çjÏ;T:Õ»^^<RêÖîÑy>j8:ôÒìå«1ë±ñW¯¦_»¿^zãÿæÛÛ°·ÛïâÆ‘ã)ä9ï™Þ}àùP5)1Ù4¥<õä£þÇÑiÛé韙ï³Ñsˆ¹ôyšù« ¼ µ‹²‹]KúK¯>9}úô9ìóþrÚ ýJéªðêƒ/ê_†¾Ú]úöñýâëZÕºÌú“ ³?ìýÌØdÙ¬Ú’Ûzºm³½ø+r‡h§`Wp÷ážÞÞÄ~ÀßXào,ð7ø üþÆc¿±Àß¼Àß¼Àß¼Àß¼Àß¼Àß¼Àß¼Àÿß¼@s°óa,pÀÊ <<ø~2h† EþoÛó(¿£ €„I¡b ó¼0ëpfV!(šGØ!f)( 4F ëMôŒÄ'M.L±G5B“M§F?ÃÄ4âÇZƶÊ!€wâÌáêäþÊËÁgÀ.P$Ø'ôM˜A丨ŸX¾x¯Äþ1ÉSRÍÒk²¼r¦ò1 %p&sRyG…AUZÍ‚®~E£EsDkZû«Î¦î.œqÜ7Ø7Ü7†LЦäfÌæº–^V)Ö•6}¶íÖìwNì8¬9~túà¼íÊî¦íæqÃóµ7ÎGÕ7įÌ `<°=(ïdh°Cˆi¨a˜Yø‰ŸÈ¨¨¤èÌSE15§ïŶĵžyßP“X‘Tz¶4¹ø\qÊ•ÔÜ´´ô3áçý/x_ôÎôË ÉÏ ÎõÏs½d™¯uYº€¿å õUÜ5Ôµ¢ÉâÞ’êÒŒ2ŸrëB7(nü¨˜¾ù¡rêÖlÕ§ê/5_o/ß™¨}RWVŸÝßxãîý{÷§›~6“\2rfÔþ¥ìÍØê«Á×uo.¿=óÎmœ0Á1ñóýð‡Êɤ)×ZÓ¢3ÔpÎêù\Ù|Èa‘fqf©éÓÅÏAËF+l+/Wý¿€/É_™¿N~[û¾±–·.¾Þ³áýƒéÇçŸk[N¿ºö÷ý¬@ Xp¬C*P>ÌùœBâM¨Sh[Œ 6¾´HÇÉÒ)Œ©Ø¨—hKéµF˜t™+YVØ8Ø…8dðÊœ:\'¸Cx.òÖð=åÿ,H+¤xÄK8Sä‘ègqF ÂQ¿c—$;¤feHdEåtå=b3•Ê”ëáìõ€ê+µ)ÂŒú‚Æ¢æ<|Œë ëöêµé7TeÇ™„™F™%šgXäZ^µ*³®°©´­²«²¯:QãpÛ±Öé®s³K—ë€Û;÷%/¤×¦÷œÏ°o›_µÿ•€ó±A'OzÁwƒu¨Y˜I¸q„i¤U”c´Ç)¿ŸÓ–±Êqghã1ñ[ «‰ IÏN$LyšÚö0½!£âüå ©Ã3C²|³ÝsNäšåi]’˼L_€(X.»ÒzµôZr‘O±~‰X)CI9éu²”Ô7i+ioÑTQWS×Pݦ¼C^KVGZOÒ@ÔHtuwóÞÜý᦮ͯ¶ä·æµå=ºÜ^ÜQÙYÛÕö¸·{ìÉ\Ïrï|ßÄÓÑþ—ÏÆž¿ø08ÿbehmxcdmtååìØè«ö×eoÒÞæ½»3Þ;ñîý§ë“›S¿>þœ^™™œœkž/[H_<¹dõIó³úòñÑU¦ÕÝ/Ó_;¿]ÿž¶²î¾áþ#òçõÍíœùCÿ#P<Àdƒ6ø- 3ƒM" ‰FÖ¢BÑÖOl%Ñ/Ò‹¸çä Je*Mj-ÚctÌt»ô1æ2y0K³°L±6³å±sãÅ8É8?q=á.å‰äÕácâ›å¯ˆT"9rMØODI”RtN¬I·<ª<«}j|oûÞñ¯=Y]ŸÞp½±çîê}Ú&©&Í#Z’[óÛn=jo×±ß%úعûò“¡^dŸÄS«þggŸ|8ô`dk¬èÍÓ‰·ÓªK-bþÿͧ¬ iò—°ƒyvó+\°„ùôBH0&ƒera/œZÒ`ÿ×úÁ d)ðq ”ƒ» Œ‚i˜Þƒ¹gHR„ a®9JŠ »ÐsxuA 8ÊGD¢ñ±‰äBÁÌp5rE†RE…£jP³hv´ :=Œ¡À`Ò1ýXb¬.6;LÄDäJTK´GlD\N¼EbBR ³®Î¤p,¸hÜ{2M²råiÊ]ªXj@Oƒ¤‰§…hÏкz4}#Ã-F5Æi¦4f%æ1o–Ö lœlìÚìãAx,¾”S‰ó=×nCY^fÞu¾^þ<A>ÁU¡¦#g…-DxD~ˆˆÝO—=êrÌLRSJAZT†G–EŽQžYKQDIAYç¸¹Šƒª§Zá´zžF½æ+m #¡ë®wY¿ß`ÛHÐØÝ¤Ö ˜ÛZ´ZñYgÚlÛ9Ù÷8Ð9:8Ýp^v•v;ç>é©çÕçcïûÒ_8 6pè$kp@HG}xhÄ«(èñ˜æØô3ñ«‰Égé“ËRÄS;ÒÍ3æ.$fÉšÌ)ÎóËW. +üzõEQgISYËõÇ/*GªFkúït×½lX¹ÇÐDho)iëlÛ¹ÙÍßãÕ×úŒ{ ãÅöˆãˇ¯~¼uœŸì˜¹?ï³hüÉeåé·àõ{?í·B~Qí8ïŠìnî¥ícöß\@ œáà<ìý&ÐÞÃïQA\$\k`yáëP3\S°„@¾WB8À¾¿…xÄ e‘¾È"ä ‹RD…žŸûÇó#JŒ&3‚¥ÇÚc˱Ÿ‰¤‰‰†a>>Œ¸fßcIÆII‹pœî%™ ìwnòB ŠLJJÊ\*fª2jQêš)Ú0:]&=}=ƒÃ8cÌ[70[0o³”²ê°n°³°osÜÄ[p¢9¹\¹¹¸¿óôðñEð  bg„:Ž ÇŠ8ˆ*‰±‹í‹OJ´-;–"%+!“/[&wK¾Q¡Uñ±Ò€ò»ãŸUvÕÈ Bê^š™Z­Ú_t)õ”ô= .“˜è˜f™½··L¶šµÑ¶½cÏ|â¼Ã®“·ó=W´›9üþÙñ²ô¾ëKçáÿ>P=¨>˜#$+ ñ5Ê7z-æbìѸÁx¿Dâ¤òd¹sÏSÒV2¢/`/dqfßÍUËÌ÷*`+|µ¸È¤]ÚTîsƒ¹b 2¹J£†òölmW}Cãí{íM=ÍÁ-|­ã®u¸vÉt3=Ùë]ºõœtgÈpäÔ˺W+o…ÇåßkL†l˜EÏÛ/>ù̳ûeä;jáÉÏá­ÀíÉú]Ìîû½kp v?ÈÁ*¹ðs?>Á~§‡ŸxdE@9P=4mÀu#šˆD9â ’©‹LE èP¨[¨ 4®à˜ÃÈc²1+X=lülŸ#Ú"$^‚ë*¦IH'qN¸²Hrrò Š]ÊÛTÎÔtÔý4Q´|´Ãt±ô"ôÓ EŒîLrÌ¬Ì ,7Y=ÙøÙÙ«9ñrœXÎ ®Ü ¼t|’üÚ&‚ÖBG|„#ED³ÄnŠ÷H¬c”Ô ‘.’òÊ ‰ŠƒÊlǽTT· Úêš$ZÚ‹º¶z½↹p<š`Fa^eif°i°s8tHpÜvpùèfç>áâÍá3ççë?hô(X8¤0Œ$<1ŠÊ8åqúF\Gü£ÄëgOŸ3IåOÛÎh¿žéœ­š+t‰þ2(øtåŵºâ¼ÒÓå~7lnªÝÂWý¬é¾“QgÞÀÓøó^_S~³s [ëУ¨ÖήÇAOøzûîö§=w”¢^½?õZìÍÔ»‹ ï—&‹?ÚÍðÌnÍO/N~"|ŽY.]i^íúÒþµò[úw‹5ƵÁõð ŽÚr?ºªü¬Ý䨼°¹¶¥»U¶µµm¼}}{öÏ/÷_7~Íïðíxìdí´í,îÒìßõÙÍÛíÜ]ÞcÜÓÞ‹Û»¿·¼Ï½o»Ÿµßwàÿß5Xë !úãõ ê‡_ÿïþ~ap×áF q~†µ]ôð¾è欮ŸYá}/Èï°Ö˜Ý,Ì`Ùp14ú+{kšÂî …ª`F{…›ÿ#ö"ÂËsÝC4þØ)ñqÖ…×F@Ë‚ÃL-`ÌãŽp3 SÂx6ÚËÜêu7wõ䄇·¦Îoµw¨ÎÁX´ð0.ß@½ƒ9Àc!¤¹ƒ0¸âËÀ5aú€Ôÿ9Šà ·„Ãm!ÀÌÃØî÷ „1þ=“höó„ûýW‹x¸¾,íϘ¿GÃÃcþ±é Ü`üGî qÐv0»Gï„ùGãÀÞálīŗÄwþÌ Å‡:Š’B©¡”PÊ(9€GÑ£˜J% ¯÷ÇQ p›Ð³°eÏ?s<°ïßà~90JÞÒ n=øí.Zå¡¶÷¿¾ÿ·ïÁå¦å?3€ë'ká+ 0°Ÿ®& .*TÒÁù?o¡î‘µ‚€ìíéŠW…«&Ý…ñ:®¢Âø£ââà?Ÿ˜5Í&°© pHYs  šœ IDATxì]xTE= )„z¯RBï½WQª Š‚ "ÒUDAAiRíþJo"½HAzï½×Iþ{'™åe³-dw³›ÜÉ÷2ïM¹3sÞ¾³SÞÎñ‰ŠŠ‚8A@¼_oo€Ô_F@ÈL>‚€ $ðsV+|È9hËÑtš“d‚€ à¡84‡ES]¥³×FŸ§µc¼,‘”¥0{u’xA@HºX"®XaOKnñ"3;f$.ã9ß{×I÷ÖIËä‰@,‚"l]ãŒçˆ±9LffD¦ÉÉ’¯ÃøÏͯÍã8^œ x?FB2žsËŒ×|®¯­ùšÝ93HÌŸ*”‰Ž:RÐÁ$¥‰ÊèësŠ6Åó¹8A@Hzhrâ–Y#- ã×é§ÃÜEi²×K³IfÚHŒus’âk&²£G.ýÒK/õK‘"Ey*0ȼ6r-‚€9Ä/aÿþùçŸc °‡â/ÒÁ„f$BΦ®™lšÍa¦Ì̉Œ_éà°¬_~ùe¥6mÚüððáÃ4ááሌŒäÂÅ ‚€ `___øûû#00ðÞìÙ³;4h;e¸B“‰&5öÕùS‘™DÆ„–÷ðáÓÂÂÂêÃÒ¥8A@â‡è´&$$¤åü=zT‰{dâžú¶}!àÁƒÊ_Ýžûìôyš4iÀS•âïE€GsDh•¨žÜФ]·˜¹vî™1™9¡qœÅ»çSL“—ÑiRã®’>4ƒúÇT˜>Ö9?tGV®ÄÙ‹ñL`Ï…Ÿ}YZ5CTD$®Í]ˆ£ þÆ;q–Ò¡¥x[Ã'¶yeÍr¤Ÿÿ'2çK<åÒ!}íH]³|¨WsãBÜZ»gvÝÂ5JsÅ/|>¾¨_¦±M›±€°sÁÆ$~îÜ9ìÙ³Y³fÅsÏ=Ç“£¦œÜƒ;zô(xx™>}zEø¶ðºµ Ó¿^üã]I‰zï÷BÍT'ñY¼>ôGª _‹m‰ðMMßÂåáG_ž‘‘QxºGI‡GoÓu”åY®…„:&3ÍIL\ú0ò.ÕDl:@gÔ׿¾Ñˆ‘ȸgÆyì­b^ݰ™D¢Ê­ÈA¯¤=u¾‹ÿ£WänÓër‘H¹é$®ß¤7n#@inF‚óä )b^ÓuÄ–åH—9 …+¦E`pø¥£.cäø^ÝDiˆÌ"NÁ7Cü|Ó¡pj_„n Å%Êb²a~²õØ?D|þÈU*=R¥ „o`.ùœÆã{ð8ê1î\E`_dNi‚‚pþú-pž"ù­Û4/ÃÚµÆÐÏÏeÊ”½ê‚©ßOÅï¿ÿŽ|ùò¡pá¸yó&vî܉¢E‹"wîܸs玚?Óy-Ù~ì}{›uéŠW ðç䉋x‰»tICh[6žäxrIÃb_ÃÂçg2vŽ{„Ã|Ž‘½§£qÉÌ–MúD iê¶Çë-óÇJcl‹®—Ÿ/±G!’ê…,ÏÆüç(è1]ÓûšvË‹U‚\¸^Íâ­&1£¯¹(‰éú0AYs:3Çk"c_ªzfÜ;°vd<~e®S¯ƒÑÐw'h ˜XtX‘ ¦˜œÏi3ž8gÕ—“íîAä)žšzd™˜##²¦C@ú(ø^ ã\³ÑQ§á´Yï´ió†Ï%ä(ž™³gD†Œé‘žI2Mîû]A¨ÿ5¤L㋌Ô[ÌœŽl‘ÒrkíŽo8A -Q£aƨ[¯.Ž9‚_ýUõÖ¶oߎ 2 lÙ²*ÏŸña«œ(~âÉñp>nº˜8ZÜPqQ÷±ç£P§Nuôúe+î긇GñýKïâçMÿ`V¯:¨G½Å:gãlØU¬Uõë×G£:#°ò|¨©œ›ûc@Œ­:ïLÅž;Målþü|¹t'vþ1,¦¼NøiG4–gÅ0®ô·#Еò¹ü´Éæ“6PÛéÓ™†¾$Ÿ„=9ÇíƒøuPt½Ôù?oŸSKkª6ó03üÞ!ÜÜ–GÙµT^.`îóo`šãÓéƒQ(Åiü9ä}¼÷ü}|·¢=rÇà±døn¼8èS|L=å@þÂÐÀ‹ÿ´èžCɯ;0¡ñÇ”¯™w¢_ˆæ#ºTqì+‹ÌxRMGÄøšØØ&4ÎÃ…°ïoìÖÓuˆÀlD4A4U›~éL+uÁä§!Ÿ‹ãw«n†™ÑqûÃîÁŸ¦ËlÚ J ßôôŽUÆ´ðMGdHe €ªCo+G¤ˆ‡áKDÆ„ä{‹~úôÀ¦ÍÔAjøœ2üý|Ä/ò¥ðG73 ¡4Ô ¥•Û4,{œ"a4w/ ܦÍ8`X 0ÿB`å×4ªV­ŠuëÖáï¿ÿF³fÍâ¬ð2™ÙÂ)‚0vKÆNÆþü4ì>u ™¨—ôisž†\êSÂC¯Gñç¤mˆ¬;!©è¶ÈlÈKùæ¬ÿwj5BjJsŸ® ûƒªå 8Ò¡]yy8~éR~>a8Jר}œæKâú¶ôᨚ!Á„P¨ésðýj%Ý{eƒ"ð€?I•ûà×M@³¸R½&nÙ‰ó÷^A–Ì…QœŒ¯ËZEŠäc«qÛHÃL_ž\2ãöS/Í÷倉C²"Õ‰ ˜HQ>l‹Ò9éK.g= žrëÞÚ ìð0“ Í7"…ºNi¡¼G6GÛ˜ÐUò dÆ[õÂÜ·¾Ãª£ÍñZþ'xtg<Ø1ŽÑgòÿéà˜8†’}þkÞÑç혯èà±rœÁšc#ƃ MšÌüÌDscþ% àáÁóôÃ'ú-:õ¢h¬FMrÑ0N¹{aDfw€tܾ‡‡Wh2¿PÎ¸ßÆÃÒeÇ#_øÐªR1¡Ó|hÎI¹Çt‚ŒHÁ‡Æ#hÑóتkŽt9v©¨çDvÒýÔ~´à Mõø!Í1=ÀýÈp„ˆ2ÌÙƒm×ÓPe›§æõâëk×®©EîM:«V­B… ԚјyÞØqÑ÷ùÍIßâ%Ü™ÊC5;þ,D…?Rd…µ£Ñy­ VÿrÄôÚ")í= É!-N0¹¦!¼/§¡žRš pîIäÜ<‡:ˆ!7*;@¢8RÄØM—¦m\ cz3hµ˜‡*>*ùSFó«VÛFå…ÓÇ%ë[ƒ1¹U4á©‚ÈÞ#5´.€‚üLùÃõp[•OdFM§V«x²·¼þx@ƒ eTEp/=*Fþ9~€ùà÷½˜Ð4ßh"Ó|ýá¥FóôƒÔ¹Îdîkã|·ù°»šéWº(®œ½Œìho "²Léé ó4DlÜ3»G÷¬ø Bºz÷|J¥µ®¿e÷0{Yܺ·¹‰´bò©á+÷ÎÔ8“zfl›ÏSFGF€óX}8(eÁàì¾¶‘Db¾DdHëD½3îù2Â4Üd{áôI+‚W lp9›6UFþq½ _0¸}û66lØ &ûyÂ×®]ضmè¹èÑ£R§&ìÈq[mФ]„"¾]§ñ%?‚_\¦¨ÀÖ_bÞ›¥ ‰è” ‡ëÇ¡Db‘4ܦ*Í͇ÑqÄ`ÑñT^T¤ÒQ9M0vY/äuiâžò2™>|¤ÎÙPL5éúIüÕ˜ù0Uù?*Oñý3odøCJ}'o…£Rzþ¢§Nú½ëôŸÓ²}Æ>1Ø‘%eËX^8}©±ód#2ÆF䃫lJ•#ÿœ€ß0Í-škbäX+‹ChÖÈ̼^œY×¾*ÔÖ0‡¤®\Oàlòd!ËH½J¯åG—À=*2EsTçCï ¢`V.O£Dë/ã•l„Ð]qñÒCäÌD¶ˆxT¯Œ5ÀMäá&ÏDáâŇ%‚ò+ÝYÍT'ìÂõÓ÷1„|è@+¢d’èAHI×éÃíÔuð°´hæ xtž„9ÆP?”üBðæÍ›Õ¯ø r¶k×gè%àåË—«•Í-Z¨9-üyHÅîêé“8—"„àC<ï—%²Ñª1ǪÕLß¼hÙ¥–M„…ÇáÅò9Epö²BJæR¯4pÚ(Â@•G§ò ©kö)ˆ R!¹«4ÁÃY_㽉E0îåêȘ" —N]Epñ:§á-ef¢Õu þã¼tÛiš.嬕ØßèU žw ƒÊcWnžÆ©sôk n™§H‡œYŸ¡—0€Y¿/E©×ªÀ÷Ä ¼÷Åïô„T‹YÍd~¦Òˆ¹|†È¼¼”¹Ë£%Û˜±eÞk€Ü¸„S'A?ʹRR¾èö›ð ´âœ‚€9‘i®aþ±ë,‘™yF¾æƒ k_ê§?ÖJ¢ÑÒ7© [v eê@dIK½ &²”ÔãÞ}‚¯=ÅyêiÔ¨ŒëDd¶ìÒÛðÉ×Ïþ¿ i‘µ ÷+¸jFçƒ+àÒÙûËÛ÷ˆÈlÙdRªž½%v^Y€[iîÓ‹³)iøDßæô »ÈHÐÚnž»‡»WCQ'Ç ŠÈlÙ4ÖÆÖ¹&2~·lÿþý¸zõ*˜°ô ³%K–T¯kLžxÌÅŸc>øY(3oÞ¼Éä[uüs§Ì™3#ðâ%¤ØsY @¦tÁR?Wò¡·ëiŽåÎm\;qá%Bð0Gv5_Äï\YsÚ&®Eê«Éf:dÈžAi©ŠDa÷Ãqóâ]\=~÷ÓײvØæõ¨ËøïÖ_È–!2ÒÜ^ªÔÔT"{¡pýúz«$ å3>L>ÙìÚ´Vópú]NÑä|ݺuÕO”ø×ü3%=ôdŸ‡žË–-·çÎø7™<-^¼¸¹¹]û< Ã]"~_êy¦RsG 1{4'š‚†êA<¯]ú ¥ R“(ñȪ’êvøÓÂSœžc–ˣŠªÿcz¾ÒP=ĹV­Zu§þ£ƒVi‚:ú ® Í“Dz.¿£ú!¡kdƄƇfJþ$ꃺTŠÌøë²ìœ9s&oÕé^ZzHïoÝ€ûaH3gJ?MzH¯P¤©\‘5CA°1{?4ç4Ê&‘ÍUúRPÔ¤OËã(ܾ“¡>y¥T#ܼÿ Þ6S§O=·àÚãðó=,¢W=2ø?ƒ²9«âþ­ûÙä::â¸GvöìYõ“/*ó=bì>gŸ'§áß¼æÏŸß‘"$ àñ´nݺ'Ur7ºFâ ‚€ `@@÷Ì4ÇX"-3Í“ò:´Pd4lnܧR…ÒF{r.‚ÀÓ"‡_È‘lÚe&Œ¯37/<¾¶$½ qËÓ™.X|A@<!3¹RA@HBf AOò ‚€Ç dæ1·B*" AÀÖ« ±›äònX»"ɵI$$µê6rI±Bfñ€µS·>ñH-IAÀ~/õøÑæÁN¹2‹'Œò’p<“ä‚@ ®"1 °Ì™i$įF@ÈÌ«oŸT^4Bf ñAÀ«2óêÛg½ò[¶lQÛ,ݻǺJ⤀KÉl÷îÿP¨H ¼Ø¦],$u8oíÍNF•*Ub5C‡³8‰«ÜÅ‹ñöÛo#oÞ¼jIÞu÷Å_Tb(®*Sì žŒ€KÉL7üàÁCX³v¾LrþîÝ»±d‰Q2ĵM<}ú´Ò8þ<~úé'¥|ÎÊM¬ðÑG¹¶p±.x(n!³W_yߌûÎ*sæÎGÓf/ ¤Xi”­Pý &!Œèá‘îÅmÜ´ÍZ¼ˆ¢Å˨žÞ™3gIOr7Z¼ÐÆvîܹXeüôó¯hب‰²[£V=|7a²Ýí¸cpð¢{÷î:t¨ÍÔß}÷)t!<äΟ~ú©©.$ £̵~øA ™ ØñÆ“¬•É&ìz÷î­ÈlÁ‚¨S§Žê•/_Ÿ}ö™RoR‰ÌþÍš5 ¥K—Våãõ×_Ç;¬íf̘¡ìð.À9räÀ×_­£`+ΔHNDFÀ-dÖåÍΤ>t+W®¶Ø\__|øá`¬]½S§LÂáÃG0bä±Ò~ûí|6ücüµt‰úôé‡Q_ÅðO>²¥ ‘’Ä€?6”gÒäïÁdöñбnÍ |5ú Ì7'}oJ㬓þýûãèÑ£`r±äFމ &`üøñ8yò$~üñG0a]ßš5k‚‰˜µ1Ù1ie̘ÑD^,Ì¢'•+WV:‹-—iÉeÈÀšoq“ÔØ±cU/nñâÅØ»w/Þ{ï=•ðÂ… èÒ¥ zõê¥T¢Ø~áÂ…íÆÅ-EBÄCÀ-dœovîˆo¾ýÎâ¶Ø­^h‰êÕªR ;*U¬€ýûbÙß±>4h`”-[òçG×®obϾý<°Ÿ ËŸ?ºtêî½±ãŸIkì˜Q¨U«²gφjU«`ð þøå—ßœŽ6Hß¾}ññÇÇiߣGÀdöË/¿ qãÆªWV¿~}Œ='NTuÉ’% BBBLäµnÝ:"ë>X¿~½ŠgŸçåXGóĉªŒ%JÄ«:t@ƒ TÅ…¿ø‚È}î\eãÆÊ&Ϲñ\ÅŠѼys»qñª€$\Œ€Û~Ðé˜9ëGü½|%²g#ÉjƒÛKÄ4æë±8pà‰ž„©á“+"iW¤H!}J½–èÞÛ´ã0&1Îw’4(ÃÂÂðr»×t´òùí}m÷iDb4\p/gܸqŠ råÊeŠ9|ø0÷¾ŒŽ…Hx„ëÌCÏÚµk+2kذ!.]º¤&÷™¹¾ÜSãádBÜŽ;ðÁ€ç÷¸>‡¦Í[Ñ+? kÖØóu¦Š8éäwÞ÷žŒnРA5j”šôçžY™2eTÏŠUÌŽ‰ŽI‡W-Ùñ5›Íç˘¼˜ ¹-<dâáa$¯Pò<›¹ãj¹·ÅCKžgdR3¦c^¸`¡šø/Z´(x<{öleÆVœy9r-$&>ü°hGC:g‚KãsÏÍŸŽ€˜#ü ˜#5ùŽÙ?šü$CÌ#H IDATïxsFÞÏ̈W’o´4Pp"¼PÁÂÅ­îgF¿@Åí¤ã>a1ÿLèQÌNþc:"éˆ`ŸžG¹½gF'‚€Ó2s:¤bP!³Ä@]ʧ# dætHÅ  $Bf‰º”)NGÀ­ï™9½ö‰`ÐÕ¢ ‰Ð$)RH™Åã6Vã‘K’ ‚€;2se.•^™ƒ`I2A 9³D]Šç# dæ|LÅ¢ $Bf‰º)ÎG ÙÏ™±zT|ý5¾YLéYÓ Í˯bÏîíjk S„œ‚@‚žÁÇ«”ŽŽ¢Í»€0Q~;>zklGóÙK§^Ü%Óçîòìµ_âk™YC&ásHq©d‰â˜7ß²ÈIÍÛÍÎ;Èwêµ›A^Ž€™ nàyÚávÛÖ÷ÍÚ%ö&vìü7N)ë7lDÝúÏ¢D©ròÑ'±ˆçÏÙsQ«N)‰jÕë`ÆÌLùyˆÊ®D©òªç§‡ÉºµzÍ:<ß´¥’ß»zõlÉø±î‘Τ1yÏ·"EK¡jõÚ¤Ç0Ž£”³VžŽ_ð’ýœ™+nÄ|Úè°Fªj»îÍ›’ÈÉT¬P>VQ3‰ &OúwnßF¯Þï)Õ¦îouÁ•+WðÁ‡C1 ÿ{hÖ´ ®Ó–ÙW¯<ÙâzÚÔIèÒµ‡’Ï àmæb»‰“¦à‹Ï?C0m™!CzÚ-6ZƯmàxŽDƒ‡ ¡dü>ù™Êøíø $É÷›’ì+W®,®ÐvÚGHêO;{åétâ ‰€ôÌ\px¾¬5ɶ±kÓº/Yª¶ï6Õ»wO )B»ÊVÂ[ݺÐήѲo·nÝV½¥ÆÏ6o)^ªd Ô¯_×”5= §°Ëœ9²dɬS$ôïÛeJ—¢Ýgó)i:[2~<ïöýÔéøäãiÿÿç”Ôç}é¥Ö&“öÊ3%”A ‘ž™“o«¬_»vÏ6j ,—)SZév® äæÍš˜J+Q¼˜é¼$i`ž"ÁVr*Xð%ìÒªu[4jXŸˆ¬žò}-¨S™ NŠ+j¸lÉø$a懑Ì\ÕXyäBðF¤gæä»Æ½2–v+Yº‚šóây¯'N:¼Àÿûí'|=æK¥%ðþ¡o¿A×ÒßÿÉ÷“£2~j³t‡K„‚€g"ðä“ï™õóªZ±êÒ¢%K0ŠZx%S;ž÷êÐñM5¥Ã<¤TÜùúÀƒà9-ÝûbB«[§¶:X‰½[÷žŠÜ8Þ—âØimPua哨–ñ˘1£JµáŸ¦ÔÔP4[¶lSÃLS„á$>å²É© àv„ÌœùJJ²ãI-î«Í)\ˆ$Ü¡RÅ *ˆß?Ë”)#îܹ‹ÉßOE§7:ªpî¤ÕÏZµjŸ´,W¬Z­$è4ÑåÌ‘C¥[¾b%jÕ¬®YáÊ’³'ãÇ’|ovî„O†¦ˆ”‡Äׯ_'ýÎC¦y³ø”g©&¸ !3'"ÍCÌõêÅ!2.¢qãFê5 Mfotx4,{âÚõkàIú®]:©š¤N «ˆùõî镤€ ãÇšjÉÿ,¡÷Å—_¡_ÿèUNk¿HÐ2~_ŽþÓ¦ÏDù e•Œ_ß~MöÞëóIÿ¥ÆçdïÂ… ÈLJêmÛ¶1ÅǧfÎúÑtó,…™"åD< "³oÆ7 Û6~¾¹E ìŒ½x‹F)°4I¬ñ‘5kkIœþ´õsJábDHÂxÔ~fóæ/2A}üø ìß%  ¦H'žðÖÔ,¦;wöïN´*¦AÀÝxLÏlç¿»pž·y×WÇe7áÂXxp¯F»#¿4õât˜#ñœ†{€o÷ìâ%Ëaò”©*»­K¶ ü¡’«Z½6f¹ÑYÊk-Lç³Tÿ`ôWcP§n#)Z ÕkÖèÑc”€ÎÇ‚'mÛ½†¥Ê©4uë?‹¡×Ñâ ɧ‘ÙÖ­ÛMä¢d£Ïñ¶ÜüùÑÄU½Z¼þz{•tÑ¢¥ˆˆˆ0eãa v¬<¤‡†:Ì^¼N7iòTl%yµg ä·(>¢ÓiÂÄÉØ¸i3Ò§V¿#?……‹–èh‡}{õ{‹$å¦|?—¯\«9ݺu‹Çgnæ’>ïõDD$Š íßËW8\I($UœFfUªTÂ/?ÍBÆŒ°fÍ%Â>_s8Ç[sáááXºä/Ý¢Es<×øY°v$+ƒoÚ´Å”Í8ìôF544†Ï-ÅkCyrçÆºu+±xÑûôéä öƒƒƒãeƒ‡tŽ8-YwïÞ=G’ÇJ£óòœa©R%cÅñE*ÒÓd7ñ»oñÇŸsðï®]صë?,]ú·:æüùXÄWœ \p:™1Lh{ÿÛá±¢÷ªÕëLøß¿ßtÎ'LjË—¯D˖ѯjðÃÎþýû¡±Òé {ñ:]|ü•+W¡G÷èáèÊUkTÖb4_¥]ªT©J"ºUкõtTßZý4qÛÞ<À¤|οaÃFT­ZYÙ:|øÞèøº:nܸÊUk©ð}´0 dn HF¸„Ì?G{RKÿZ†Ç«UÌÛ6©!¦ÆÿÎ]ðÏ?›1oÁ™.TGŽÅw&a‘ ýŒsQöâµíøøGGzT–K—.+¿#)’kW¥j%¬!Bþñ§_pìè1ì  zkÎZýªV© ¶³uËv¼òj,ø ü¨m'OV«™›7®“æKmÛ«yÈ9rÄ^,XÐZ‘.$ œ:gö4ˆ-ˆYŬT¹B,"c[ 4P&7nÜ‚«W¯©óaŸ ¡•¾Âj•“ßCÛ³'z.IEÒ?{ñ:]|üžo¿…Õ«ÑêâmE$ƒõ7‘+Ûùø£Q¶leòÍ_}ûÍ«æ­Õ{lÓ¦LÆ[Ý:#W®\8qâ$ÎÐd±bEñnï^&l^~© ­ª¦÷ÐnÞ¼¥Vd‡}<ÄÔs³Z°DI=WÃí¤ʇ<&¸1>÷Üüéˆ9ÉçÉ+>RÓQDtG“/N!@¯r ;é๦°˜ƒ'ÏÅáä?¦ƒ'±ù­Hâ¯(ò•Kôž™®ˆø‚€ $!³„ 'yAÀc2ó˜[!„ d–ô$¯ x Bfs+¤"‚€ „Ì‚žäA@ÈÌcn…TD‚€YBГ¼‚€ à1™yÌ­Š‚@B2Kz’W<!3¹RA@HBf AOò ‚€Ç dæ1·B*" A@È,!èI^A@ð„Ì<æVHEA !¸l§Y])–›‹¯£=Òâ›Å”~÷îÿÐæåW±g÷vµ3«)"‰ÛyäÈÑdÑæ$v ¥9NFÀ-=3Þ?ÍÑÃ^ûΟ¿€>ï @ÅÊ5P8¤$*T¨ŠWÚw¤íµ7ÙËêñLBLðZ°ÅX)[qÆt|^²d ðVÚŽnOnžßü:>e›ç•kAÀpyÏÌ™dA`V}*RÓ§MFölYqýú lݶ·ïÜvfQm‹qàM³dÉìÑõ”Ê îDÀ-=3{ >|8Ö®]k/ ™ùsç1bø0”!uólÙ²¡xñb`ÁߦMž•ÿß]»Ñ´Ù )V/¶i‡Ó§Ï˜âY î½¾P¢Ty”¯X £FAdd´œÜò+Q³v}Sڹ󨞔îù1‘”,]Ûwðî¾qÝœ¹óMå–­Pý †QzއÀì¸l­ø®­X‹Ó½¦ÕkÖáù¦-Q´x¬X±JågU(íÖ“ŠSÝúÏ’íròÑ'&5xߘV‡é¢µ²µíŸ~þ 5QxÖ¨Ue&›0ã4Ξ‹Zu¨Þrµêu0cæ:«ø‚€[Ht25j:„%ìÏ­eÊ”QõH/YjzP­¡4~üDŒ1-]R9úxØpSÒ¡'A£˜MZ““'~‹ÙsæaúŒY*¾b…ò¤ztÙ$Ǥ•.]:y8xH©I1™Zr¾¾>øðÃÁX»z9¦N™¤„GFŒü”tÚÔIêœÒy˜È‡v¶â8ÍÄISðÅçŸaÅßK!CÍäÏ$™<é;Ìœþ=˜”§N›iгwb«ìI“¿“ÙÇC?Tªî_þsçͧú|¯Ì^¹r|8¯¿öªŠÿþû‰ÈŸ/Ÿ½"%^p*‰Jf'NÄÉ“'ñ믿Ò)‹Ý†e¥4Ç Å×c¿EÙòUÐŽ$ÙF5‡H©ÈÜ Ø_éH>S ºuë‚­[w¨$¬Ë¹hñR|ðþ@°öeåÊ•H© þøcŽŠÏ˜1£R<Ò=¯m4„íÜ©£Êr‚íÛw ‰í°ÆK\×ê…–¨^­*räÈ®´/öï 2Ö.}ŒqæÌ™Ô0Ñ8T´Çùû÷í£z¤ùóçC``Üò{÷!ELmšM½%Gµ²Y·“Ikì˜Q¨U«²gφjU«€ª~ùå7ežU«xN´ñ³3g”¢ù¼úõëª8ù'¸ ·Î™ýüóϨS§òäɃmÛ¶aÒ¤IعÓòpͯ´{/´l¡Heÿزe+¾Ÿ:ŸÿíÚ¾dÊV¤H!Ó9÷èø¡|ôèΜ9«¼%Š™âKR¯ðä©ÑjØäëë‹J•*bõÈX^Ž%î^kß“&OQ6¶oÛJD€ÖÜ^ãóõX8pˆ„ÔM.—µAýü7ËÎÙr%hÈ­·éÔéÓ±†‚:.>þ ú² ÃËížè„r~&/Ý.Öød©½V­Û¢QÃúDdõ”ÏXŠÜ…@ž®xÖrúôéèÑ£ºvíªzdƒ¶Úñe:((%j×®©ŽÝ»aÊ÷Ó1zÔ˜XdféAâP»hU=}Û¯T±&M™ª³|…²J§²@þüØýßlßù/^}µ]ì 1WüÐwèØMž}z¿£ò1á¾Û§¿'”ÌüýŸîvYjkdä,,6&&P§ûí×LÃmsÇ"Ìlÿ¿ý„ ÿlÄÆ›ñþ᯿þÆ7cE…Ð/¹vnýê\³f öíÛ§Ž•+W¢M›6NiYqê±Ü¡I}=‰oËhÞ¼yÔ÷„µ;pà ˜¬4V¬TÇŸPŠé•+VTÉx8úûïàöíÛ(_¾¬ÎË?qâ$îÞ½‡~}ßU=Þ¸q3V_zøÙi’0FÚŠ3¦³vÎóyÚÛ”&M|ãÆ £ÇŽ™ÎùÄZÙÏÈ”)S⽓/_Þ8‡&J&µºujãÃá«QŸƒç5¹±*!‚@p+™q=óÑÄð_ý…>úˆæ}XSØqÇC¸ž½û`Ù²åjuòâÅKX¿þŒøüK<Û¨¾‰ŒlYL:5š7k‚/¾üJ͵ñÜØäï§¢mÛ'Äš›ųÑk<·Æ½4vì/\´D­ž² KŽç‹˜gÌüçΟǪUk0mZìIøœ9r¨¬Ïïù:uZ‘Õ„‰“•}¾/³~ø ljÌy¿bÕj¥Ê®¿Ìë)ׂ€+xºqKkÂß⌷~Ø3ÓýØqãÕCïIäÊ•“ˆ¬!Þ¥ÉoG/"|DgkzeƒÖvDd<Éot•iÞl) •JǬZò<S+WŽ&7cZ}Î+Œ£¿‰/GiÓg‚‡¨ýúõAß~OÚÊÿƒöSdÚ¯ÿU•UÿâÁVœ.ÖÿF‡×h±£'®]¿^ˆèÚ¥“JÎx3ö+|0d(4|*–ûïöT/k{¶Ê~«Û›È˜1­ŽÎÀ ÷‡¨¹¿"…ñÊ+mUöÔ©SaÕÊÕ4W8NÍ£ñ ½ÆÕ¦ÅÜ‚€? ÚÑÁ‡Î¹·Æc!ö™ìüéà¥3>¸+sp÷¤=ˆ£É·êø]*cVÆDpôÃm/­Ä ‚@ÒA€¸bµ†WïÓs<$ÿQÌNþc:ø¥Ðö‰[Læ–ž™žW¡ÂÅ ‚€ à\NfÒËrÉ}£‚€ `†€ÛÌÊ—KA@œ‚€™S`#‚€ Ø™%öòAÀ)¸lά`áâN© ÄA€î ‹…N©„+ßVp™qË}ò–½S#‚€ à6¬}²9‚³žc£MW4B†™®@Ul ‚€Û2s;äR  ¸!3W *6AÀ홹r)P\€™+P›‚€ àv’$™i±ð0ž»])P܆€ÛÉL«ñÞ[æNV 2ÆÛŠ3¦ãsÑ”4GD®ç àɺµn'³9óæ¡d‰â˜7sÐ5³bÔ””Ý:ÌÀ‘KA ð³Åºµ¡B•ní?ëWá§ŸfÒ~‚ ‚€ $2K·Q!Bfò$€Y’¸ÒA@2“Ï€ $ „Ì’Äm”F‚€™|A I d–$n£4B„Ì\ü(T¤ø˜9ëG—$æä€KÈlëÖíñF5<<S¾ŸŽfÍ[¡xÉrêàsã8Ov¶«téRà#kÖ,žÜ©› àõ¸ä·™í_¿ü4 UªTr G¡Ã]°cÇN•>¾|à­y>BÇ×X³v~úa:üýý²—ÐD\Ÿ€€€„šQùçÎþÝ)vĈ ØFÀ%=3.òw߃£=´™³~RDÆ6õû‰X¹b)V,_‚iS')Rc’›1óÉ0M÷„¾?ý F‰RåQµzmJóC¬Ö>x𣿃:u¡HÑR¨^³F&+í´­oÆÇÛ={«áä)SUô¾}ûѶm{”¯X …CJ¢L¹ÊèøÆ›øoÏ^] !õň‘_ªk¶©¶o>ÌüçŸMhÛî5S/”Ï9ÌètÞoÇO@ÿï«vV®Z¿þö?S²½\GÊ[¢T9ÕÆºõŸÅЇ›âåDH.¸ŒÌæÌ™ë0¡-\¸HáýlㆨW·Ž ûºuj£ñ³ Õõ¢E‹Máúdâ¤)D˜Û!Cz\»v#?……‹–èh¼Õ½' Sgàò•+(R¸nݺ…ï§Î@ß~MiôɤÉS±uË6£Löy¯¿Ê©ê†¿—¯0'ç‚@²@ÀedV·n]¼óNoð3,ìM0Oœ<¥âK•(']É’Ña:1AñâŰnÍ uh™3Ѿ…ˆ‰‰‡Ý‚ùbñ¢y˜;'ºG³ìï8|äˆÑòäÎuëVªtÝßêªâ*T(wlÆòe‹±hÁ,ÿ;šPïݽM›6«4Æad§7:€¯a± ‰¹O=-veË–Qu_»z¹:ç°ñßMd/– )Nó翚ÂwìüWŸ={Nù]Þì„…ógcëæ øaÖtS:9’ .#³µk×büøoÕÜYPPJ—àÙ¨a}¤H‘B|Îîà¡ÃÊß»oŸòù_Óf­Ôð´Ûóß“¡"‡5kö<Ò¤I£¢}}£ayøè!ø*U©©ò×­÷¬Îޫ׮™Îã{²wÿ•¥aƒz¦ú7¨_W…ñ°ÑÜ5jØ@¥ËŸ/¯)êîÝ»ê¼zõ*Êç¡q½Ñ»O_\ºtÉ”NNä‚€K¼Ö­_ÄøqcZà¡Ýá#G±wÿÒ7`_ )qsÇC+kNKbñ<\©Rq{|©R§Š•5888Ö5_ô~·öÐüÛ(J½£æñ5»ˆˆå'äßãÇOlØjKÚ´Ñ$ËÄ­nßÄï¾ÅÎÁ¿»va×®ÿ°téßê˜óço(S¦´N.¾ äpYÏÌQ"c„[´h®€^þ÷Jµr©Q_»n=Íÿ¬T—Í›7ÓÁ&åÊUŠT˜XV®Z£Â‹ÑÜ;M`üп?x€iø÷¿ß~B¯·»Çš›S,üãvݺvRÃÏaŸ|d!ÙqÄýû¡ãÍK•(®‚V¯^cª?Ÿ³+UòÉâ °óï0­ø¾Ññu|ûÍ×X0ïOSj]wS€œI—ôÌâóZãÛé×±vÍZl§y ®ÝÞFüù‰ =OV±btîÔ!έ8zì8êÔk¤Â/]º¬üŽ^S~Õ*•Q¥j%šÔߎW^퀂ŸõlNž:­V37o\‡T©b÷ÎÌ àE~=dÚô°nýF\§EK®p¡B8rô(MÔO "XîAÙš7{çžj`ÏÞ}ªþÜó»x1zhøN¯·-a5ì%ZmåE„9rÄ^,XÐj‰’".é™9ú~™”ßéúø8U’ IDATÞ#п/BŠƹóçq–VùœÃ¬½cÖóí·P£z5Z¥¼­èÁƒú£eËè^Ä´)“ñV·ÎÈ•+Nœ8‰34Y^¬XQ¼Û»‚ƒÓéâ­ú£G¡Òsï.’†´¾g1í°O†ÐjiaÕËÚOóaz(j11Ö¬Y³fLE…reÕêäõë7Ô9‡q\|ÜË/µAúôéÁ=´›7o©ÕØaAÕª•ãcFÒ ^€ž{á–PHm¥Í“3LtÜsã7UbŽ@òƒbŽÔäW8vdÿhòÝêô{\~0ˆzuq{ln­Œ&NA€žëdh'÷é‹9’Ï/†òÁ?zLO–ó„s$ñWùʹ¤g¦‹/‚€»2sÒRŽ ¸—,¸´Ædœ†¶®.Bì ‚€—! =3/»aR]A@°Œ€™e\$T¼ !3/»aR]A@°Œ€™e\$T¼ !3/»aR]A@°Œ€™e\$T¼ !3/»aR]A@°Œ€GïŽ:üÓ‘¨V½Ži{êæ-[ƒ·Å6:½´ùÔÆ4žrîMuõ̤‚@Bðˆ—f?¢=ëçÏ_¨ÚÁ;f°pÉÚߌ7IìýNüv‘ˆ/Î/‰oÙ’^œ‡€GôÌ–-[®ZÔ¹SG%d²tÉ|ìþw+ ìgj©þq9XI¨x‰#ù¹lG…H8-;c]¹Œ²*«]kZ ,Ra,œòø1ÿŽêšÛì Ñ=QÊ¿$Ž€ÓÈŒ•˜ôÐÊ’oK©)z³`ó–­à ïÝ»‡   T¯VÕ¿ÞãŸ, ‡$T¼Ä‘üŽ ‘X«kÊ”)ñB‹ªMóD÷Dù‚÷@c׬éó&1@ÿœ!Ú"¢'Mñ“2N#3ÞÃŒ7eä׬YÞ™ƒ}¾¶·YãKm^TùÄa€!oIÍ{Ök1‘Ý$ñÖ©s7Úö”Ý[PñGóÇWˆÄRÅYK€{nLö‹/Q R<üäÞf…òåâdq†h‹ˆžÄU’ N_ЄUs&°ö¯¶S?Øóh1࣡à î5ñ¢€-§÷gãáªÞûߘޞxI|ó;*Db¬ƒñüêñn´sç-0Õ·ièá¶1­-¡Gë-¢'FDå<©"àt2c ˜Ðöþ·CÍ{9«Ž¿úÊ+´½uNð¼RÍO¶ŽN™’7·vü°ól.¢ ŒãX¼¤i° çìm!íh~"Ù¹k7X|¤G÷hmMkB$ÖêÊõjÖ´ >ýìs"Y¼“1š¡-- 19-‹¶è²ì‰¶Xk·=aá“7n rÕZl,z" N ù—p ™1.<ï¨cÕq>rå̉t´7ÿ‰'TV&¶Z5kšÌXI¨x‰£ùã#Db­®ÜÖmõBsüòëÿÔœ= “xŠ%ç Ñ=±„¬„%5œ:gö´àôëû.*Uªˆ»´ŠÉ‹)Rø¡& •üòó,dΜÉdÖšp÷‚"^âhþø‘X««nL»¶Ñ |­EXtœÑw†h‹ˆž•󤊀W š$…›Á¯Ÿð{e¹|$¥ñE ©<ÔŽýÐÜ+zfBdñýxKúä„€<ÑwÛ+ÈLºÒÉéÑ”¶Æy>¢sùÍŸhóoók[7›…PXÙiÏîíH•*•­¤'XEÀø9bAjOþLÅçù°Úà$á–žïÍïèa S#!ž?}Þ€Š•k pHIT¨P¯´ï¨Äy-åõ¤0~@¸-,/çIÎSëõ4Ùj‹­8ó²J–,Í×ÅkûwsÆëø”mÌçȹñùp$}RMãòž™3€Óß<`Õ§"!…1}ÚddÏ–ׯßÀÖmÛqûÎmgå56 v)R¤ðš:{KE[ÞJ=K–Ì^Qeý|xEe]XI·ôÌìÕøðáX»v­Õdú›çÈÑc8wîcŠç-«ßë;%J•W[W=FíúÊ –¯X‰šµë›Ò²—Ïjæìøƒ^²tlß±Ó”Æxòç침U§ê5V«^3fþ`Šæ¡ ;.—mêvé?ýü+6j¢ê\£V=|7a²©^ú›}õšux¾iK-^F‰s^[ù8ÞV{9ÞZ½&LœŒ_|¢UÀiïß¿Oõ/V¨×uZö÷ Ô­ÿ¬ òÑ' #N«½úÙÂLÛÐþœ¹óM÷µl…Êè7`°jŸŽ·ÖŽ·§ÛaÄvÅŠUêþ„††jÓXOJ_–Ú©óÓê0Ý ·V¶6žŒÌ?GÚfró½g6jÔ(’\;„=zXÅ^ódÊ”Q}c.^²^oo³W2~üDŒ1iÓ¥%ù¹!øxØpÌš1M•1ôãá8|ø(fÿùîÞ¹ƒ^½ßC† еKgT¬P—.]Æ… •0/“Vºtéy± É\yüø±"Só _¹r|8ú¿§ä䮓¬ÛÕ+WMɦM¤Û×­Y€€S8ŸLšü½Ò ýxè‡J©‰Å?ò±JÓ«gwSÚ‰“¦à‹Ï?C0Õ)C†ôå³Õ^6l­^­^h‰±ßŒÇ‰“'ñLªL\™2eBåÊñß{TØL"ìÉ“¾ÃÛ·–¹sçF÷·º¨8{í²‡™2bøçëëƒI4šµTÏ?aÃG`ÄÈ/ðùÈÏT*kmáH[qoÄöòå+ËÙjg¬„.l•PŒôóa¡Ød”¨=³‰'’näIüúë¯Ô¥ÏbxýÍ“•Ò 6_ýeËWA»W;€57>'ï ý•&$?„ݺu¡žÄ•†{‹/ÅïD±¢!ôPVÂ[ÿÇsT|ÆŒéÁÍoêym£!lçNÕP–lß¾åHyÝœŒ8îÖ­Ûjn°ñ³–¢y—úõëRL´K¬NXqЇ0zÃúž'}±cF¡V­J ZÕ*<¨?~ùå7]ùýûöQDš?>¥þn/Ÿ½ö²Qkõb•õêTyóšê0wþ|¼Ð²E,ÁâÞ½{‚åò4–³©wÊΑvÙÃÌTpÌ lõjU‘#Gv¥:°_0Ájg­-o+ŽãØÆþ²áxkíä8{ÎZÙÎÀH?öêÔãÝÚ3ûùçŸQ§Näɓ۶mäI“°s§åášxã7«óÃĤ²ÿÀR<ߊï§ÎħÃ?F»¶/™²)RÈtÎ=:þнÖì¦aÕöÿâÕWÛYÌÊ“ñÿûí'løg#6nÜŒ÷?øýõ7¾;Úbz]¯ß~ýQ u¸ö“üþþOnY|òÙj¯.Ç’ÿ,õ2?¢aù6"ñ]»w«Þ.ñqŽÔëå(fLÜ:vF“çŸCŸÞï¨ûÂ_hïö靿éJfFliŸNc [ÝvÆš¯ÓÙº÷ö02>¬••Ôß<nhéš5kh"þ4˜Ì6oÞŒßÿÝ¡RDf)Cqê­Ü¡I}Ö ´çòæÍ£†Hûö@ÍÕUò*²ÒX±Rø>V¬\…Ê+ª4<„úý÷?p›æ…Ê—/kµ&žºuj«ƒ‡ŠÝº÷Ä×c¾TCBߘ•GýÖFxXËêí赓ҥJê`»¾#ùi¯µzqø‹£YÓ&˜¿`vþ»;Äî=ržG䡟:7`éHý8-̔ј'NœÄÝ»÷À¢Ñ<ÀŽ¿8ŒÎV[lÅmX;·ÖÎ4iÒ¨,7hŽT¿ÛxôرXf¬•í Œì=±*’„/ÜJfŒc¾|ù¨·òÆŒƒÀÀ@‡ Õß<<ĘüýT4oÒ<$ày«£´Â9ò‹Qx¶Q}Eö ¦NÍ›5Á_~…¯FAÇ]e“'ÿµË+­”fUsk¿þtÓ¦Í4&­¶ØŠ‹eÄÊ…µvòüe–Ì™1îÛ‰x÷Ýž8uê´ÃõrFúù°Rídìv2cdù›xàÀƒ¬¿yøÃ˜™¾‘ÇŽ¯æ¾ø5‰\¹r‘5Ä»4 í¨ãE„ˆ8ZÓ+üaj×¶šä7æ¯LófKiˆXš^aÇóh{íµU/.³Bùr çâÅBœ.N5Þ ÞZ·n=qíú5ð}×.LiìÕÏf&CtÂÄ?úË‘ørôט6}&x  _¿>èÛïÉgÉV[lÅ˱vn­üyþfìWø`ÈP4hø<*T,§H_îÖÎVÙ ÅH†tYÉÕwù®ü­aœ«²4û›ßù汇škã¹—X¥ZmE$ Ô3ÆïRñûSòÓ1$‰r’Tžj³?¯Þ§#,æàŸË<Š9ÂÉçÕ0žOâ·Æ#‰[Láné™1A%Ä™“[BlIÞø!Àó@¿Ñ\aªTA¨[·vü2Kj· ÏG4Ì.'3gT¾yÜòÉvr!•«ÖRóA_~1ÂæKÊN.VÌÅy>¢Ár9™ÅãžXMê B´j\"l"` {~§ÎV¼MÃé4äDCiûmN§Á0CüÍ#N,# ÏG4.^AfüÍ£o˜øÑÄ.8üóç@zfÑdæ²afÁÂÅ£KpÒ½h!~ôâàà™8ðÇ?ûî¾?NzÌ\nÆÒÛ Î*ÔedÆ<~ô€³ê)vG`ÃÚ'?x—Ï~ÜÛeÄ'nlÂC¼b˜™ðfŠA@Hê™%õ;,í’ BfÉäFK3¤Ž€YR¿ÃÒ>A ™ d–Ln´4SHê™%õ;ì†öiñô0ž»¡è$_„àéø-v™9¢w©oœV´q¼®Mé©õzå•×±pÑ›×êR¼—¹³Õ.[qæv¼IcҼîÒµz½Û×b1_‹çš´°'ñGÀ-dÆûޱÞeèƒP¥wùÏúUøé§™´Y—é]r™|$Uwûöì¢m¼yûp[nμy(Y¢8)?-°•ì©ãc~’ÅYº;ÊSWƒ3¾Øª¥ÚãŽ7®4:ÞyÞü…hýâ Æ`9On!3Gõ.¢-¨{FíC#agO“ÐÓu%-ÝßõþQ*MZõÇRî o#UªqߌÁ7±ƒvÁ5:[x[‹³„³§iLÛ˜Øç hÿ·À”økÙß±ª²™„x®\¹Š–-›Ãž¨Î¨±·¥ÏÉií}Þµ½¤æ»ô,Gõ.¢-¨Ë2j&]IÝ.síÚuJýÉ<Üx=ÁBÔ¨Q•¶*ϋ͛bîÜJT§±…·­8ÎoÄÙ“4&uÛ<Å猛7eìç£íËmLÕâ^Y­Z5Àò‰ö´@M™8±§Áé€ ¯Mâ–ž™£z—º—Á[ ‹®dl½Lã'Œ‡vkÖ®G=;›%ò|Yë_TYÛd‹'³Æ€vÖðæx[qï©“\7Os<ÔÜùï.œ;wNUU¦Xµ‹·ggO T%ràïlOGÕ3^›Ä-=3FÇQ½KKH:¢-¨óµÉç麒–äÓXò.MšÔ(BÖÜ®]»qíÚu5/ÉiÊpqöìÙHqjµt±–ÏÑp#Ζò$–Ƥ%¼,ÕÏaåÊ•U½ãyó‘pM-^®6ºdmUv¶´@ãSOG>O|Úh+­ÛÈŒ+áˆÞ¥¥Êji6[Ú‚:ŸQû0>ùžvòÚÕº’º]FÍšµ¨W¯®1(Î9÷ÊXͼdé',¼“/°:UBçøØ²„³¾OöìètŽ|ìÙrw<÷Îøž0™Í£á>«\±¼ =-Pc=íaçÍøÛù´çn%3óJõ.YB,¡Ú‚æöÑ$ô]Ióv­^½ƒõ36]³Â÷¢%K0Š”Œx%S»ë´Ÿ‡ŽoâÊÕ«Ñs5Vt<9½µ{¡mÙóScÒ^Ý#þ…–-0ö›ñjx¹‰&ÿ·—ªÆ‰öµ@u}íés:òy×¶’¢ï2sTïÒš®¡#Ú‚–nŽ#ù<]WÒ¼].\Äé3§QµJeó(ÓõJJ²ãIóaE‘Â…°`Á"’ƒëœ$5&M xØ K"V©Z ïùyòä6-Ä8¢ª›bOŸÓ‘Ï»¶•}·™£z— Ñ´vsìir>OÖ•4o×ZŬ^£šMeÎ4¨W/‘±­Æ©W˜Ìlám+μN–®KcÒR]<%¬u«Vغe;:¿ñ†©JŽhêÄŽès:òy×ö’šï2ÝLÞm3¹lPçN]I~£œß]âqž…o>Ø©[µËlrùìÇçh|¬móM[€{¾nf|ìmiÝ­+Y¹rE4jØÀÛ`’ú .GÀ-ÃL—·" p·®d·®o&bk¥hAÀs2KཱÖef³¢+™@p%» Üò €xÔG’ ‚€ ðT™=l’I< !3O»#RA@x*\:gæj¼§j±dÜ€€|öݲY.#3K¿#3+[.$‰€|ö綺ŒÌ´<}â4KJOEÀU/Ëœ™§Þq©— Ä !³xÁ%‰AÀS2óÔ;#õx! d/¸$± x*Bfžzg¤^‚€ /¼†Ìx¹;¾G¼0$Þ²e‹*‹%èÄ ‚€w à²W3\Ñüø¼îaë][q\oÞvúâÅ‹à]hÅ ‚€w àUdæ,H™¨´û÷ßÑ”t O:eÚ½Õßߟ”Œ²ë$nñ?~¬zƒ¼›¨8A@ˆ?.f®]»Ö¡¡!§s†>|8±ÅD¥Œ3ª¢³eËf 33õùÊ•+iKŸ²@•*UpüøqlÞ¼åË—7…;ókhç•e5‘FT÷îÝcÅÛJoLH½«(NKú…¦`v÷îÝ(}¾qãFSüÂ… Uê]Å £^›º~øðaÍ·EíܹÓÏ'sç΢¡ ÓvW¯^+\IK|ÁaÌ't0¯0¿0Ï0ß0ï0ÿ011/1?1O) ÂKu’Ü2gÆ=¯Ù³g£M›6ª÷ÂÃ/¾~ÚÙÏ?ÿŒ:uêdWlÛ¶ “&MµÍuއ‚ÚeÉ’E–*ÅxG;ã…"29rD ðÖ¬YSG+Ÿ‡˜Ï(ÚñÐUœ $·WSZ=’@£žÙSÛš>}:zôè®]»ª!ÜàÁƒÕÐã\åX¤ØÜY ão =/¶nÝ:°”˜¹3jYò|š8A@H8n#3®*¿»•ÐW˜ OŸ>­ÈŒ†køý÷ߎ„-„„„¨94®c¥J•œhYL ‚€5ÜJf\‰„™nH¾|ùHêþ/Œ3ÆôJ…ŽKl?eÊ”:t(h4‡Úµk«!(…;†!C†$v¥|A É!àv2s&‚üNÖÀiÒi¶ žG=z4:wî¬^Ïày7&8q‚€ à|\ªhîÌêò[û<娋ozGíJ:A@HÖ6gLVŠæö~†”0ˆ%· x3^3ÌŒO¯Ì›oˆÔ]ž¸ï<É%‚@¢" d–¨ðKá‚€ à,„Ìœ…¤ØDE@È,Qá—ÂAÀY™9 I±#‰Š€ËV3Ç3*Q&… ‚€g"Фi3—TLzf.UŒ ‚€»2s7âRž ¸!3—À*FAÀÝ™¹q)O\‚€™K`£‚€ àn’ ™eÏ‘ïôH[ñðöáÞç¼½þÞ‡¸ÔØÛpÙ«žÄ•Ë1}êÚÿ‘CUcòx©ík˜øÝ׈ MKq‚@B(X¨ªV«‰ô2’FÄ=¬Yµ§OPfS¤ðEíº Q$¤¸ú¼ý»søÐÎ^¼N—œýdCf‘‘Q½?Q/ïŦ䥥|)4ñÈ›/?êÖk„µk–ãòåKH“:M,a›JUj OÞü˜;û7¤L„&Í^À­[7qâøQUy{ñ‰ßÂįA²!3ÝÓš<ád"}JîuÍŸûjÖ®G¢#qõê,_¶·éÄŽãٽݫ¯òùŸñEàÒeÊ£LÙòH›.˜¶Æž½ÿaǶÍjI]Ö¢sP½fd̘ ÿý»y <ƒ_~œn²Ç$×¹k/¬]½ÇŽF±â%Q¶|%UV8?uòÅ­P[n›2ɉW"P­zmlüg-Ž‹&§{wïÆjGÉ’eðφ5¸zå² ß·w7J•.k"3{ñ±Œ%Ó‹dCf–îoåªÕ±få2%ÿÖ Ñóê›sÁ¼?TÒE f£yË6˜5c2"""be¯X¹*BŠ–Àºµ«pãú55l¨ßàY•fûÖM¦´•*Wêá჊äÊ”¯HDšׯ]UirçÉ?¿DZÇÕ5ïÙöϺոyëÒIò°£VʆɨœxÜÓÊš-;|Há«}‡7©ç•R‘Ôzº×<…‘6m:‘ˆôÅ‹Lm»tñ<Š—ˆ–2´oÊ”ÌO’Í€¥û¼iÃ:\ºt7oÞÀÎ[‘+WnS²D@ìBCCÕðTQyî¢BŪªwæôIR›º‹sgO«o]þ&5ºÍ›Öã2ÙçáÂíÛ·páü9.RÔ”¤p‘b8yü¸ÆîÐÁý8K¶ø[›ÓnÞ¸ 6¥—ïD mºtªâÅ‹—ÂÊåKÕg'þ‚¨^£¶ O”JùaaO¦AÂBÃÔp“wW¶ï¨8¿Öɺgvýzt‰aå¡b ??0YEDDZE:}†LJ£³õK¯ÆJÃ:ÖÃä¡£vW¯\ѧÊ?rä ÊÓ0r˦ *ÕŠ¿—šÒð·7G²dÉ ??øøøÆ±iJ,'^‡ÀÆõkh¾ì¢ª÷–-ÿ MAl Þ™8ç ¬ÉÌ¢> `Ì´ÁüÙ¿#ìatïÍx+x¡A;-¬¯Ó¼X:fÉš$÷XiÞgNŸPÑ,ü‹/«¹³­ôA@uœî¹&-hxÂJôâ¼0êݳ»q뺩 ·oÞDõÈøóJ_¤ì‚‚RãÑÃèÕö TAxð LMOØ‹7Mæ'ÉšÌlÞûRò!Â1:’òëihèpéhô·¬1ÞÖ9khò´ 5S™8vÄÔ äåúÀÀ”4´Ü z‰l'o¾¶ÌIœ— ÀSü*FppÓº`^8Š&«»wï€ /{öœ¦(>¿B«žììÅ{ .¯f²ž3³…îú€±ã¡ ƒ©kž°Ý¾u3êÖE‹• ÕÌtHŸ>½T <áoÏ9|…)í3 ãÈ‘C¦ä÷¨<îÉ•¥Ežü/ðL!”« jè&€¼üdïžÝ¨M‹9¼²={T®Z‡ì3µjß¾ÿ(ìÿíOkU‡ïFTÚ3ø¬ÜbIDATЬºU°. BÔØ–ÒºðƒøÍŒBp¡H[%\,´Huá¦K+"ˆç™ä™¾3™¹¹M“ÆÜü^8yÏŸ÷¼gî¯úôÜ™ÉéûÝn|£=zëíwÚòŸWï/p%;³™?ügÏþ\Üùá»Åæ[‹On\ê¾øj ¸ÇvíÝ÷mßì ÄSÍúßLÚÅ£G~|£{Ýâ÷ß~íÃø[úÛÝoºW9®µûj<ͺ×î­Ý¸y:g?õ §òJ¸¿woñf{ªùY{凯–y°¸{÷û~í½Ÿîti~úùÝ¡½½û×8:j¼OtÆ~Ëæ¬ìÔþà¯w¾:«Ï”u£@8#^f ò_¶Ë¾ß uÿ:(7ÏDÊ?­ð蟛ڼ/õo{©¿I¯™M‘Xˆç_ÀìüÿæDs­ÀÆÆÆ‰\`v"2&IˆÇQàöíÛí5¥KÇ™zhN’$Q ¼ nݺµxüøñbggçD–ËÎìDdL’(^D ²«W¯¾ÈÔÙØÀlVš D(p loo÷;2@vR÷Ìò5ó4þ´’3 DI¶¶¶Úát_-ÙYÝ3ã}Ž©2yѧõ}N.–Î(þ÷ ŒAvýúõÅ“'Oê=³)¾ôï‘õWùšé5—}+/T'§¢ÀÅR`ÈÊ=³9®Ø¿T´e0›•IõÃS —.•Á(.¢›››ƒ¯–uGÈ®\éÏ„'²E?–ŒþI›ƒY`R=¿J@O‰E(&\OŸ>íï‘AFûòeŽÃêL¦È™£7Žö!›ƒ™NªÉ¨W ¢@蘗÷ÈØ‘ÕñƒI•+sìéó+«<ͬI]L ÑŽE()°»»;è;ªÝ‚áɘ1•?ƒ|ãÆÜ©œHÈñ¦€÷ÚAá_Ð}½•7Zá€/½çs~B~бÄS'Åœ­::ùžXˆçYáÃéÜÃS8õ‚0<ÃS18f—S2(Ô9Ä1Žq6–Ó2<1ƒœÞ[œš±lgÆE‹ä4áƒÀ*Ä€—0hÏW[Ëð×6‹Qà\+3ä "áØí 5`&[Ì1fíI›‚Á2ãd.â¢^,¹Øu *t.;;âwݺñ±(ηÂ.À þ¿—žIÄœma'SdŒüз)½¹Nß!TúŽQÅ xðëEV (æC<†@§7®uÅ¢@X#ä…@ÃS„¾îÒ€ÙÐàGåPkNÛ̘lb^ ôô½ÈñެB»3cðÀ,@k"Ä¢À)0æTpCh³q©<‘1‚¬2Èü-ÅÐæ`F“0'›ïb\Xád1޳?†YëŠE(°f À 9 /„Uu¿rR'F¶Ôù2¨ ÷\¢>°Ì8O»Ã]wKh5¹ NuŽSAÆÎÌB¬ð\LQ ¬… /d†\¨Pc£c[ UÞTõÕóÿéÀì ʉФÔ)ÔY ÉÀ¸L~ ±ÞüwžùÛP, D5RȘÈdG僜¨;4ÇñÌ©¥5;3¿íÎOÁÌ/ø8™‹Â 1.ÉÉ…¸Ó“Gá±m_‡üŒë¢,À€ ¡.ôB«îȬƒ—=æ2Ú2˜iÛø !Çñ,NN¦b̳ުƒ<´cQ œo„Må‚õ )ê Ä„›}ô AÁغæmf^3iSXÓSw ¨Ã2¼ðªua¨oa±(Ö@8€É@$Œ¬ *ÀEŸ £ßB?uóŒ}Ú!˜M<`‰Hðš Ðvœ \úº#£ˆ¡X, ¬¯²Avèá„VXwGFÛ~çà6¾ùÏà!˜•‡´1ªf p±fŒ~&ÄÆ¾…Ä¢@X*ø8´-‚‹6u¼àª3Îçë۴ö fF“ Z]€~ÆÝ1&¸Æ³Ÿ9˜@Ûoågˆë¢@e†ª¾2„º…áVã­/Õgfå«&I€~l\æ8mêS3N¡D, ¬¯@òÃ>=Ì×kŸcx¬óS_1DGµò­ðÁ[ZL¡ŽÙW㌯}ûÑù¢Àº+ øœJö»)ÒÓ_ëuÎbd$_ ³.àùoT(uCÌ?(SíÚg‹Qàb)Pd¨[æÚö/AG¬ z´®ÉfspÛ}>>Õv®cñQ ¬—chÕO7³=çÉW‚Y8Z×Å«pªu†j›#> DõR@8ù©–µëX­¯2Xf^M7i9Ø CÌþø(.¶XH1è[volNºcÁ¬&k\[Z«ÆÕô©G(pþ€jîã`5×Kì&K= D(pV øJÅY­Ÿu£@ˆ'¢@`v"2&Iˆg­À|‹³ó’™×èIEND®B`‚gaphor-0.17.2/doc/manual/stereotypes.txt000066400000000000000000000023651220151210700202270ustar00rootroot00000000000000Extending models ================ The UML method to extend UML (basic) components with a special meaning is by using stereotypes. A stereotype defines a special usage of a model element. For example: a class that's used as a controller can be assigned a stereotype:: «controller» Creating a stereotype starts by the creation of a Profile normally. Although stereotypes can be created in every package, it's a good habit to use Profiles for that. Next a Metaclass has to be created. The metaclass will tell the stereotype on which kind of elements it is applicable. A Stereotype can be connected to the Metaclass by means of an Extension relationship. .. image:: simplestereotype.png Stereotypes can be applied to basically all elements in a model [1]_. .. image:: stereotypedclass.png Stereotypes can contain attributes, as shown in the diagram above. Those attributes can be filled in the Element Editor. This allows for enormous flexibility. In most cases, especially if some sort of program logic has to be generated from the models, it is very handy to define special behaviours to classes and other elements by means of stereotypes. .. image:: stereotypeedit.png .. [1] If for some reason the stereotype doesn't show in the diagram, raise a ticket ;) gaphor-0.17.2/doc/manual/working.txt000066400000000000000000000057751220151210700173310ustar00rootroot00000000000000Working with Gaphor ################### Once Gaphor is launched, it provides you an almost empty user interface. A new model is already created and the diagram is opened. The layout of the Gaphor interface is divided into four sections, namely: * Navigation * Main Canvas Diagram * Diagram Element Toolbox * Properties pane Each section has it's own specific function. Navigation ========== The navigation section of the interface displays a hierarchical view of your model. Every model element you create will be inserted into the navigation section. This view acts as a tree where you can expand and collapse different elements of your model. This provides an easy way to view the elements of your model from an elided perspective. That is, you can collapse those model elements that are irrelevant to the task at hand. .. image:: gaphor-treeview.png In the figure on the left, you will see that there are three elements in the navigation view. The root element, `New Model` is a package. Notice the small arrow beside `New Model` that is pointing downward. This indicates that the element is expanded. You will also notice the two sub-elements are slightly indented in relation to `New Model`. The `NewClass` element is a class and the `main` element is a diagram. In the navigation view, you can also right-click the model elements to get a context menu. This context menu allows you to delete model elements, and to refresh the navigation view. Double clicking on a diagram element will open it in the Diagram section. Elements such as classes and packages can be dragged from the tree view on the diagrams. Diagram section =============== The diagram section takes up the most space. Multiple diagrams can be opened at once: they are shown in tabs. Tabs can be closed from the file menu (Close) and by pressing `Ctrl+w`. Most elements have `hot zones`, shown as gray rectangles that are only visible when the item is selected. Double clicking on those rectangles allows you to directly edit the item. E.g. change it's name or change the name of an association end. Toolbox ======= The toolbox is mainly used to add new diagram items to a diagram. Select the element you want to add by clicking on it. When you click on the diagram, the selected element is created. The arrow is selected again, so the element can be manipulated. Tools can be selected by simply clicking on them. By degault the pointer tool is selected after every item placement. This can be changed by disabling the Diagram > Reset tool option. Tools can also be selected by a keyboard shortcut. The actual character is displayed as part of the tooltip. Finally it is also possible to drag elements on the canvas from the toolbox. Element Editor ============== The Element editor is not really part of the main screen, it's a utility window that shows all characteristics of the selected element. Things like name, attributes and stereotypes. It can be opened with `Ctrl+e`. .. image:: elementeditor.png The properties shown depend on the element that is selected. gaphor-0.17.2/doc/model.txt000066400000000000000000000022021220151210700154520ustar00rootroot00000000000000UML datamodel ============= Gaphor uses the UML metamodel specs as guidelines for its data storage. In fact, the datamodel is generated for a model file. The model is built using smart properties (descriptors). Those descriptors fire events when they're changed. This allows the rest of the application (visuals, undo system) to update their state accordingly. The events are send using Zope (3)'s signalling mechanism, called Handlers. Model details ------------- Pay attention to the following changes/additions with respect to the official model: * Additions to the model have been put in the package AuxilaryConstructs.Presentations and .Stereotypes. * A Diagram element is added in order to model the diagrams. * A special construct has been put into place in order to apply stereotypes to model elements. The current specs (2.2) are not clear on that subject. * The Slot.value reference is singular. .. note:: ValueSpecification is generated as if it were a normal attribute. As a result, its subclasses (Expression, OpaqueExpression, InstanceValue, LiteralSpecification and its Literal* subclasses) are not available. gaphor-0.17.2/doc/services.txt000066400000000000000000000033441220151210700162050ustar00rootroot00000000000000Services ======== Gaphor is modeled around the concept of Services. Each service can be registered with the application and then be used by other services or other objects living within the application. Since Gaphor already uses the `zope.component` adapters, it seems like a good choise to use `zope.interface` too as starting point for services. Each service should implement the IService interface. This interface defines one method:: init(application) where `application` is the Application object for Gaphor (which is a service itself and therefore implements `IService` too. Each service is allowed to define its own interface, as long as `IService` is implemented too. Services should be defined as `entry_points` in the Egg info file. Typically a service does some work in the background. Example: ElementFactory *********************** A nice example is the ElementFactory. Currently it is tightly bound to the gaphor.UML module. A default factory is created in __init__.py. It depends on the undo_manager. However, on important events events are emited. (That is when an element is created/destroyed). What you want to do is create an event handler for ElementFactory that stores the add/remove signals in the undo system. The same goes for UML.Elements. Those classes (or more specific the properties) send notifications every time their state changes. But.. where to put such information? .. autoclass:: gaphor.UML.elementfactory.ElementFactory :members: Plugins ======= Currently a plugin is defined by an XML file. This will change as plugins should be distributable as Eggs too. A plugin will contain user interface information along with its service definition. .. seealso:: Writing plugins :ref:`` gaphor-0.17.2/doc/so.txt000066400000000000000000000133321220151210700150010ustar00rootroot00000000000000Service Oriented Architecture ============================= Gaphor has a service oriented architecture. What does this mean? Well, Gaphor is build as a set of small islands (services). Each island provides a specific piece of functionality. For example: a service is responsible for loading/saving models, another for the menu structure, yet another service handles the undo system. Service are defined as `Egg entry points `_. With entry points applications can register functionality for specific purposes. Entry points are grouped in so called *entry point groups*. For example the `console_scripts` entry point group is used to start an application from the command line. Entry points, as well as all major components in Gaphor, are defined around `Zope interfaces `_. An interface defines specific methods and attributes that should be implemented by the class. Entry point groups ------------------ Gaphor defines two entry point groups: * gaphor.services * gaphor.uicomponents Services are used to add functionality to the application. Plug-ins should also be defined as services. E.g.:: entry_points = { 'gaphor.services': [ 'xmi_export = gaphor.plugins.xmiexport:XMIExport', ], }, There is a thin line between a service and a plug-in. A service typically performs some basic need for the applications (such as the element factory or the undo mechanism). A plug-in is more of an add-on. For example a plug-in can depend on external libraries or provide a cross-over function between two applications. Interfaces ---------- Each service (and plug-in) should implement the `gaphor.interfaces.IService` interface: .. autoclass:: gaphor.interfaces.IService :members: UI components is another piece of cake. The ``gaphor.uicomponents`` entry point group is used to define windows and user interface functionality. A UI component should implement the `gaphor.ui.interfaces.IUIComponent` interface: .. autoclass:: gaphor.ui.interfaces.IUIComponent :members: Typically a service and UI component would like to present some actions to the user, by means of menu entries. Every service and UI component can advertise actions by implementing the ``gaphor.interfaces.IActionProvider`` interface: .. autoclass:: gaphor.interfaces.IActionProvider :members: Example plugin ~~~~~~~~~~~~~~ A small example is provided by means of the `Hello world plugin`. Take a look at the files at `Github `_. The `setup.py `_ file contains an entry point:: entry_points = { 'gaphor.services': [ 'helloworld = gaphor.plugins.helloworld:HelloWorldPlugin', ] } This refers to the class ``HelloWorldPlugin`` in package/module `gaphor.plugins.helloworld `_. Also notice that, since the package is defined as ``gaphor.plugins``, which is also a package in the default gaphor distribution, each package level contains a special statement in its ``__init__.py`` that tells setuptools its namespace declaration::: __import__('pkg_resources').declare_namespace(__name__) Here is a stripped version of the hello world class:: from zope import interface from gaphor.interfaces import IService, IActionProvider from gaphor.core import _, inject, action, build_action_group class HelloWorldPlugin(object): interface.implements(IService, IActionProvider) # 1. gui_manager = inject('gui_manager') # 2. menu_xml = """ # 3. """ def __init__(self): self.action_group = build_action_group(self) # 4. def init(self, app): # 5. self._app = app def shutdown(self): # 6. pass @action(name='helloworld', # 7. label=_('Hello world'), tooltip=_('Every application ...')) def helloworld_action(self): main_window = self.gui_manager.main_window # 8. pass # gtk code left out 1. As stated before, a plugin should implement the ``IService`` interface. It also implements ``IActionProvider``, saying it has some actions to be performed by the user. 2. The plugin depends on the ``gui_manager`` service. The ``gui_manager`` can be referenced as a normal attribute. The first time it's accessed the dependency to the ``gui_manager`` is resolved. 3. As part of the ``IActionProvider`` interface an attribute ``menu_xml`` should be defined that contains some menu xml (see http://developer.gnome.org/doc/API/2.0/gtk/GtkUIManager.html#XML-UI). 4. ``IActionProvider`` also requires an ``action_group`` attribute (containing a ``gtk.ActionGroup``). This action group is created on instantiation. The actions itself are defined with an ``action`` decorator (see 7). 5. Each ``IService`` has an ``init(app)`` method... 6. ... and a ``shutdown()`` method. Those can be used to create and detach event handlers for example. 7. The action that can be invoked. The action is defined and will be picked up by ``build_action_group()`` (see 4.) 8. The main window is retrieved from the ``gui_manager``. At this point the "inject'ed" gui-manager reference is resolved. gaphor-0.17.2/doc/stereotypes.txt000066400000000000000000000043041220151210700167450ustar00rootroot00000000000000Stereotypes ----------- Stereotypes is quite another story. In order to create a stereotype one should create a Profile. Within this profile a Diagram can be created. This diagram should accept only items that are useful within a profile: - Classes, which will function as <>. - Stereotype, which wil be the defined <>. - Extensions, connecting metaclasses and stereotypes. and of course the usual: Comment, Association, Generalization and Dependency. Thoughts: - Profiles are reusable and its common to share them with different models. - Stereotypes can only be owned by Profiles, not by (normal) Packages. - We have to do a lookup if a MetaClass is actually part of the model. - a stereotype can contain an image, that can be used in stead of its name - Profiles should be saved with the model too. And iit should be possible to "update" a profile within a model. Maybe it would be nice to create Stereotypes without creating the diagrams. Via a dialog once can select which class (Operation, Class, etc.) is stereotyped, which extra constraints apply and/or if you inherit from an already existing stereotype. This way it's easy to save your stereotypes apart from the model (and possibly in the model too) so they can be reused in other models. I could create a special diagram window too that can be used to create profiles. Profiles should be added to packages within the model. This window should contain: 1. Name of the stereotype 2. Metaclass it applies to (Class, Operation, etc.) 3. If it is a subclass of an already existing metaclass 4. Constraints 5. Description 6. The profile it belongs to. When a stereotype is used, an instance is created of the Stereotype (meta)class. This is not really possible for our application. Gaphor should figure out another way to do this. Should check XMI and see how they do it (maybe a property is enough). It looks like the stereotypes are more a concept than something that is implementable in an application. The point is that the stereotypes you define (instances of Stereotype) should be instantiated in your model when you create a stereotyped class. .. note:: There is no way to connect a stereotype with a class other than an Association. gaphor-0.17.2/doc/storage.txt000066400000000000000000000036651220151210700160340ustar00rootroot00000000000000Saving and loading Gaphor diagrams ================================== Everything interesting is an ancestor of the Gaphor root tag. The idea is to keep the file format as simple and extensible as possible: UML elements (including Diagram) are at toplevel, no nesting. A UML element can have two tags: ``Reference`` and ``Value``. Reference is used to point to other UML elements, Value has a value inside (an integer or astring). Diagram is a special case. Since this element contains a diagram canvas inside, it may become pretty big (with lots of nested elements). This is handled by the load and save function of the Diagram class. All elements inside a canvas have a tag ``Item``. .. code-block:: xml gaphor-0.17.2/doc/undo.txt000066400000000000000000000110311220151210700153170ustar00rootroot00000000000000UndoManager =========== Fine grained undo: undo specific properties. Add undo-operation, e.g. Item.request_update() should be executed as part of an undo action. Such actions are normally called after the properties have been set right though. This is not a problem for idle tasks, but for directly executed tasks it is. Should only need to save the originals, values calculated, e.g. during an update shouldn't have to be calculated -> use undo-operations to trigger updates. To update: Handle: x, y (solvable) connectable (attr) visible (attr) movable (attr) connection status (solver?) Item: matrix canvas is managed from Canvas Element: handles width, height min_width, min_height (solvable?) Line: handles line_width fuzzyness (attr) orthogonal (boolean) horizontal (boolean) Canvas: tree: add() remove() request_update() (should be performed as part of undo action when called) Solver (?): add_constraint() remove_constraint() Variable state In Gaphor, connecting two diagram items is considered an atomic task, performed by a IConnect adapter. This operation results in a set of primitive tasks (properties set and a constraint created). For methods, it should be possible to create a decorator (@reversible) that schedules a method with the same signature as the calling operation, but with the inverse effect (e.g. the gaphas.tree module):: class Tree(object): @reversable(lambda s, n, p: s.remove(n)) def add(self, node, parent=None): ... add @reversable(add, self='self', node='node', parent='self.get_parent(node)') def remove(self, node): ... remove Okay, so the second case is tougher... So what we did: Add a StateManager to gaphas. All changes are sent to the statemanager. Gaphor should implement its own state manager. * all state changes can easily be recorded * fix it in one place * reusable throughout Gaphas and subtypes. Transactions ------------ Gaphor's Undo manager works transactionally. Typically, methods can be decorated with @transactional and undo data is stored in the current transaction. A new tx is created when none exists. Although undo functionality is at the core of Gaphor (diagram items and model elements have been adapted to provide proper undo information), the UndoManager itself is just a service. Transaction support though is a real core functionality. By letting elements and items emit event notifications on important changed other (yet to be defined) services can take benefit of those events. The UML module already works this way. Gaphas (the Gaphor canvas) also emits state changes. When state changes happen in model elements and diagram items an event is emitted. Those events are handled by special handlers that produce "reverse-events". Reverse events are functions that perform exactly the opposite operation. Those functions are stored in a list (which technically is the Transaction). When an undo request is done the list is executed in LIFO order. To start a transaction: 1. A Transaction object has been created. 2. This results in the emission of a TransactionBegin event. 3. The TransactionBegin event is a trigger for the UndoManager to start listening for IUndoEvent actions. Now, that should be done when a model element or diagram item sends a state change: 1. The event is handled by the "reverse-handler" 2. Reverse handler generates a IUndoEvent signal 3. The signal is received and stored as part of the undo-transaction. (Maybe step 2 and 3 can be merged, since only one function is not of any interest to the rest of the application - creates nasty dependencies) If nested transaction are created a simple counter is incremented. When the topmost Transaction is committed: 1. A TransactionCommit event is emitted 2. This triggers the UndoManager to close and store the transaction. When a transaction is rolled back: 1. The main transaction is marked for rollback 2. When the toplevel tx is rolled back or committed a TransactionRollback event is emitted 3. This triggers the UndoManager to play back all recorded actions and stop listening. References ---------- A Framework for Undoing Actions in Collaborative Systems http://www.eecs.umich.edu/~aprakash/papers/undo-tochi94.pdf Undoing Actions in Collaborative Work: Framework and Experience https://www.eecs.umich.edu/techreports/cse/94/CSE-TR-196-94.pdf Implementing a Selective Undo Framework in Python http://www.python.org/workshops/1997-10/proceedings/zukowski.html gaphor-0.17.2/doc/win32.txt000066400000000000000000000056451220151210700153320ustar00rootroot00000000000000Gaphor on Windows ================= Gaphor even runs on windows. .. warning :: The installer is currently broken. For now, use the alternative way as described below (see #128). Download the installer: http://downloads.sourceforge.net/gaphor/. This installer will download all required files and install them (you will require an internet connection when running the installer). Alternative ----------- Another way is to install the `all-in-one installer `_ from `Alberto Ruiz `_. Running the PyGTK installer with default settings will install Python, GTK+ and PyGTK in ``C:\Program Files\PyGTK``. After installing Python, PyGTK and the GTK+ runtime environment, you need to install `easy_install `_. Download the latest version from `ez_setup.py `_ (place it in ``C:\Program Files\PyGTK`` for example) and run it: Open a command shell (''Start'' -> ''Run...'', type ``cmd`` (enter)):: C:\>"c:\Program Files\PyGTK\Python\python.exe" "c:\Program Files\PyGTK\ez_setup.py" After installing ``easy_install``, first install [http://pypi.python.org/pypi/zope.interface zope.interface]:: C:\>"c:\Program Files\PyGTK\Python\Scripts\easy_install.exe" zope.interface==3.3.0 Here version 3.3.0 is installed (instead of the latest version). This is due to the fact that no binary distribution is available for the latest ``zope.interface`` module. No problem. Gaphor will work with an older version of ``zope.interface`` too. After a successful installation of ``zope.interface`` Gaphor should be installed by executing:: C:\>"c:\Program Files\PyGTK\Python\Scripts\easy_install.exe" gaphor Now you should be able to start Gaphor my executing ``C:\Program Files\PyGTK\Python\Scripts\gaphor.py``. This work from within the explorer. If you're a developer and already have Python 2.4 installed you can, as an alternative, check out the ``gaphor-win32-libs`` module from Gaphors subversion repository or download the zip file from http://svn.devjavu.com/gaphor/gaphor-win32-libs/zips/gaphor-win32-libs.zip. Follow the instructions in the ``README.txt`` file. Troubleshooting --------------- You're getting an error message like this:: error: Setup script exited with error: Python was built with Visual Studio version 7.1, and extensions need to be built with the same version of the compiler, but it isn't installed. This is due to the fact that no binary distribution is available for the latest ``zope.interface`` module. Try to install an older version of zope.interface (see above). My error is more like this:: error: Setup script exited with error: command 'gcc' failed: No such file or directory Same reason as described above, you just performed the steps described on CustomInstallationLocation. gaphor-0.17.2/doc/xml-format.txt000066400000000000000000000040101220151210700164370ustar00rootroot00000000000000Gaphor XML format ================= This format is ment to be a shorter and more obvious version of Gaphor's file format. The current format makes it pretty hard to do some decent XSLT parsing. In the current file format one has to compare the @name attribute with the model element name one wishes. Since the data model is generated from a Gaphor (0.2) model it would be a piece of cake to generate a DTD too. These are the things that should be distinguished: - model elements - associations with other model elements (referenced by ID): - 0..1 relations - 0..* relations - attributes (always have a multiplicity of 0..1) - diagrams - one canvas - several canvas items - derived attributes and associations are not saved of course. Model elements should have their class name as tag name, e.g.:: ... ... Associations are in two flavors: single and multiple:: ... Associations contain primitive data, this can always be displayed as strings:: 4 Canvas is the tag in which all canvas related stuff is placed. This is the same way it is done now:: 100.0 Most of the time you do not want to have anything to do with the canvas. The data stored there is specific to Gaphor. The model elements however, are interesting for other things such as code generators and conversion tools. Gaphor has export filters for SVG graphics, so diagrams can be exported in a independant way. gaphor-0.17.2/examples/000077500000000000000000000000001220151210700146665ustar00rootroot00000000000000gaphor-0.17.2/examples/list_classes.py000066400000000000000000000022261220151210700177320ustar00rootroot00000000000000#!/usr/bin/python """This script list classes and optionally attributes from UML model created with Gaphor.""" import sys import optparse from gaphor import Application import gaphor.UML as UML #Setup command line options. usage = 'usage: %prog [options] file.gaphor' parser = optparse.OptionParser(usage=usage) parser.add_option('-a',\ '--attributes',\ dest='attrs',\ action='store_true',\ help='Print class attributes') (options, args) = parser.parse_args() if len(args) != 1: parser.print_help() sys.exit(1) #The model file to load. model = args[0] #Create the Gaphor application object. Application.init() #Get services we need. element_factory = Application.get_service('element_factory') file_manager = Application.get_service('file_manager') #Load model from file. file_manager.load(model) #Find all classes using factory select. for cls in element_factory.select(lambda e: e.isKindOf(UML.Class)): print 'Found class %s' % cls.name if options.attrs: for attr in cls.ownedAttribute: print ' Attribute: %s' % attr.name gaphor-0.17.2/ez_setup.py000066400000000000000000000240551220151210700152660ustar00rootroot00000000000000#!python """Bootstrap setuptools installation If you want to use setuptools in your package's setup.py, just include this file in the same directory with it, and add this to the top of your setup.py:: from ez_setup import use_setuptools use_setuptools() If you want to require a specific version of setuptools, set a download mirror, or use an alternate download directory, you can do so by supplying the appropriate options to ``use_setuptools()``. This file can also be run as a script to install or upgrade setuptools. """ import sys DEFAULT_VERSION = "0.6c11" DEFAULT_URL = "http://pypi.python.org/packages/%s/s/setuptools/" % sys.version[:3] md5_data = { 'setuptools-0.6b1-py2.3.egg': '8822caf901250d848b996b7f25c6e6ca', 'setuptools-0.6b1-py2.4.egg': 'b79a8a403e4502fbb85ee3f1941735cb', 'setuptools-0.6b2-py2.3.egg': '5657759d8a6d8fc44070a9d07272d99b', 'setuptools-0.6b2-py2.4.egg': '4996a8d169d2be661fa32a6e52e4f82a', 'setuptools-0.6b3-py2.3.egg': 'bb31c0fc7399a63579975cad9f5a0618', 'setuptools-0.6b3-py2.4.egg': '38a8c6b3d6ecd22247f179f7da669fac', 'setuptools-0.6b4-py2.3.egg': '62045a24ed4e1ebc77fe039aa4e6f7e5', 'setuptools-0.6b4-py2.4.egg': '4cb2a185d228dacffb2d17f103b3b1c4', 'setuptools-0.6c1-py2.3.egg': 'b3f2b5539d65cb7f74ad79127f1a908c', 'setuptools-0.6c1-py2.4.egg': 'b45adeda0667d2d2ffe14009364f2a4b', 'setuptools-0.6c10-py2.3.egg': 'ce1e2ab5d3a0256456d9fc13800a7090', 'setuptools-0.6c10-py2.4.egg': '57d6d9d6e9b80772c59a53a8433a5dd4', 'setuptools-0.6c10-py2.5.egg': 'de46ac8b1c97c895572e5e8596aeb8c7', 'setuptools-0.6c10-py2.6.egg': '58ea40aef06da02ce641495523a0b7f5', 'setuptools-0.6c11-py2.3.egg': '2baeac6e13d414a9d28e7ba5b5a596de', 'setuptools-0.6c11-py2.4.egg': 'bd639f9b0eac4c42497034dec2ec0c2b', 'setuptools-0.6c11-py2.5.egg': '64c94f3bf7a72a13ec83e0b24f2749b2', 'setuptools-0.6c11-py2.6.egg': 'bfa92100bd772d5a213eedd356d64086', 'setuptools-0.6c2-py2.3.egg': 'f0064bf6aa2b7d0f3ba0b43f20817c27', 'setuptools-0.6c2-py2.4.egg': '616192eec35f47e8ea16cd6a122b7277', 'setuptools-0.6c3-py2.3.egg': 'f181fa125dfe85a259c9cd6f1d7b78fa', 'setuptools-0.6c3-py2.4.egg': 'e0ed74682c998bfb73bf803a50e7b71e', 'setuptools-0.6c3-py2.5.egg': 'abef16fdd61955514841c7c6bd98965e', 'setuptools-0.6c4-py2.3.egg': 'b0b9131acab32022bfac7f44c5d7971f', 'setuptools-0.6c4-py2.4.egg': '2a1f9656d4fbf3c97bf946c0a124e6e2', 'setuptools-0.6c4-py2.5.egg': '8f5a052e32cdb9c72bcf4b5526f28afc', 'setuptools-0.6c5-py2.3.egg': 'ee9fd80965da04f2f3e6b3576e9d8167', 'setuptools-0.6c5-py2.4.egg': 'afe2adf1c01701ee841761f5bcd8aa64', 'setuptools-0.6c5-py2.5.egg': 'a8d3f61494ccaa8714dfed37bccd3d5d', 'setuptools-0.6c6-py2.3.egg': '35686b78116a668847237b69d549ec20', 'setuptools-0.6c6-py2.4.egg': '3c56af57be3225019260a644430065ab', 'setuptools-0.6c6-py2.5.egg': 'b2f8a7520709a5b34f80946de5f02f53', 'setuptools-0.6c7-py2.3.egg': '209fdf9adc3a615e5115b725658e13e2', 'setuptools-0.6c7-py2.4.egg': '5a8f954807d46a0fb67cf1f26c55a82e', 'setuptools-0.6c7-py2.5.egg': '45d2ad28f9750e7434111fde831e8372', 'setuptools-0.6c8-py2.3.egg': '50759d29b349db8cfd807ba8303f1902', 'setuptools-0.6c8-py2.4.egg': 'cba38d74f7d483c06e9daa6070cce6de', 'setuptools-0.6c8-py2.5.egg': '1721747ee329dc150590a58b3e1ac95b', 'setuptools-0.6c9-py2.3.egg': 'a83c4020414807b496e4cfbe08507c03', 'setuptools-0.6c9-py2.4.egg': '260a2be2e5388d66bdaee06abec6342a', 'setuptools-0.6c9-py2.5.egg': 'fe67c3e5a17b12c0e7c541b7ea43a8e6', 'setuptools-0.6c9-py2.6.egg': 'ca37b1ff16fa2ede6e19383e7b59245a', } import sys, os try: from hashlib import md5 except ImportError: from md5 import md5 def _validate_md5(egg_name, data): if egg_name in md5_data: digest = md5(data).hexdigest() if digest != md5_data[egg_name]: print >>sys.stderr, ( "md5 validation of %s failed! (Possible download problem?)" % egg_name ) sys.exit(2) return data def use_setuptools( version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir, download_delay=15 ): """Automatically find/download setuptools and make it available on sys.path `version` should be a valid setuptools version number that is available as an egg for download under the `download_base` URL (which should end with a '/'). `to_dir` is the directory where setuptools will be downloaded, if it is not already available. If `download_delay` is specified, it should be the number of seconds that will be paused before initiating a download, should one be required. If an older version of setuptools is installed, this routine will print a message to ``sys.stderr`` and raise SystemExit in an attempt to abort the calling script. """ was_imported = 'pkg_resources' in sys.modules or 'setuptools' in sys.modules def do_download(): egg = download_setuptools(version, download_base, to_dir, download_delay) sys.path.insert(0, egg) import setuptools; setuptools.bootstrap_install_from = egg try: import pkg_resources except ImportError: return do_download() try: pkg_resources.require("setuptools>="+version); return except pkg_resources.VersionConflict, e: if was_imported: print >>sys.stderr, ( "The required version of setuptools (>=%s) is not available, and\n" "can't be installed while this script is running. Please install\n" " a more recent version first, using 'easy_install -U setuptools'." "\n\n(Currently using %r)" ) % (version, e.args[0]) sys.exit(2) else: del pkg_resources, sys.modules['pkg_resources'] # reload ok return do_download() except pkg_resources.DistributionNotFound: return do_download() def download_setuptools( version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir, delay = 15 ): """Download setuptools from a specified location and return its filename `version` should be a valid setuptools version number that is available as an egg for download under the `download_base` URL (which should end with a '/'). `to_dir` is the directory where the egg will be downloaded. `delay` is the number of seconds to pause before an actual download attempt. """ import urllib2, shutil egg_name = "setuptools-%s-py%s.egg" % (version,sys.version[:3]) url = download_base + egg_name saveto = os.path.join(to_dir, egg_name) src = dst = None if not os.path.exists(saveto): # Avoid repeated downloads try: from distutils import log if delay: log.warn(""" --------------------------------------------------------------------------- This script requires setuptools version %s to run (even to display help). I will attempt to download it for you (from %s), but you may need to enable firewall access for this script first. I will start the download in %d seconds. (Note: if this machine does not have network access, please obtain the file %s and place it in this directory before rerunning this script.) ---------------------------------------------------------------------------""", version, download_base, delay, url ); from time import sleep; sleep(delay) log.warn("Downloading %s", url) src = urllib2.urlopen(url) # Read/write all in one block, so we don't create a corrupt file # if the download is interrupted. data = _validate_md5(egg_name, src.read()) dst = open(saveto,"wb"); dst.write(data) finally: if src: src.close() if dst: dst.close() return os.path.realpath(saveto) def main(argv, version=DEFAULT_VERSION): """Install or upgrade setuptools and EasyInstall""" try: import setuptools except ImportError: egg = None try: egg = download_setuptools(version, delay=0) sys.path.insert(0,egg) from setuptools.command.easy_install import main return main(list(argv)+[egg]) # we're done here finally: if egg and os.path.exists(egg): os.unlink(egg) else: if setuptools.__version__ == '0.0.1': print >>sys.stderr, ( "You have an obsolete version of setuptools installed. Please\n" "remove it from your system entirely before rerunning this script." ) sys.exit(2) req = "setuptools>="+version import pkg_resources try: pkg_resources.require(req) except pkg_resources.VersionConflict: try: from setuptools.command.easy_install import main except ImportError: from easy_install import main main(list(argv)+[download_setuptools(delay=0)]) sys.exit(0) # try to force an exit else: if argv: from setuptools.command.easy_install import main main(argv) else: print "Setuptools version",version,"or greater has been installed." print '(Run "ez_setup.py -U setuptools" to reinstall or upgrade.)' def update_md5(filenames): """Update our built-in md5 registry""" import re for name in filenames: base = os.path.basename(name) f = open(name,'rb') md5_data[base] = md5(f.read()).hexdigest() f.close() data = [" %r: %r,\n" % it for it in md5_data.items()] data.sort() repl = "".join(data) import inspect srcfile = inspect.getsourcefile(sys.modules[__name__]) f = open(srcfile, 'rb'); src = f.read(); f.close() match = re.search("\nmd5_data = {\n([^}]+)}", src) if not match: print >>sys.stderr, "Internal error!" sys.exit(2) src = src[:match.start(1)] + repl + src[match.end(1):] f = open(srcfile,'w') f.write(src) f.close() if __name__=='__main__': if len(sys.argv)>2 and sys.argv[1]=='--md5update': update_md5(sys.argv[2:]) else: main(sys.argv[1:]) gaphor-0.17.2/gaphor.desktop000066400000000000000000000004631220151210700157260ustar00rootroot00000000000000[Desktop Entry] Name=Gaphor Comment=UML modelling environment Comment[nl]=UML modelleer omgeving environment Comment[pl]=Åšrodowisko modelowania UML Exec=gaphor Icon=gaphor-48x48.png Terminal=false MultipleArgs=false Type=Application Categories=GTK;GNOME;Development;ProjectManagement; # vi: encoding=utf-8 gaphor-0.17.2/gaphor/000077500000000000000000000000001220151210700143305ustar00rootroot00000000000000gaphor-0.17.2/gaphor/UML/000077500000000000000000000000001220151210700147655ustar00rootroot00000000000000gaphor-0.17.2/gaphor/UML/__init__.py000066400000000000000000000003671220151210700171040ustar00rootroot00000000000000from gaphor.UML.collection import collection from gaphor.UML.uml2 import * from gaphor.UML.elementfactory import ElementFactory from gaphor.UML import modelfactory as model from gaphor.UML.umlfmt import format from gaphor.UML.umllex import parse gaphor-0.17.2/gaphor/UML/collection.py000066400000000000000000000147671220151210700175110ustar00rootroot00000000000000""" 1:n and n:m relations in the data model are saved using a collection. """ import inspect from event import AssociationChangeEvent from gaphor.misc.listmixins import querymixin, recursemixin, recurseproxy, getslicefix class collectionlist(recursemixin, querymixin, getslicefix, list): """ >>> c = collectionlist() >>> c.append('a') >>> c.append('b') >>> c.append('c') >>> c ['a', 'b', 'c'] It should work with the datamodel too: >>> from gaphor.UML import * >>> c = Class() >>> c.ownedOperation = Operation() >>> c.ownedOperation # doctest: +ELLIPSIS [] >>> c.ownedOperation[0] # doctest: +ELLIPSIS >>> c.ownedOperation = Operation() >>> c.ownedOperation[0].formalParameter = Parameter() >>> c.ownedOperation[0].formalParameter = Parameter() >>> c.ownedOperation[0].formalParameter[0].name = 'foo' >>> c.ownedOperation[0].formalParameter[0].name 'foo' >>> c.ownedOperation[0].formalParameter[1].name = 'bar' >>> list(c.ownedOperation[0].formalParameter[:].name) ['foo', 'bar'] >>> c.ownedOperation[:].formalParameter.name # doctest: +ELLIPSIS >>> list(c.ownedOperation[:].formalParameter.name) ['foo', 'bar'] >>> c.ownedOperation[0].formalParameter['it.name=="foo"', 0].name 'foo' >>> c.ownedOperation[:].formalParameter['it.name=="foo"', 0].name 'foo' """ class collection(object): """ Collection (set-like) for model elements' 1:n and n:m relationships. """ def __init__(self, property, object, type): self.property = property self.object = object self.type = type self.items = collectionlist() def __len__(self): return len(self.items) def __setitem__(self, key, value): raise RuntimeError, 'items should not be overwritten.' def __delitem__(self, key): self.remove(key) def __getitem__(self, key): return self.items.__getitem__(key) def __contains__(self, obj): return self.items.__contains__(obj) def __iter__(self): return iter(self.items) def __str__(self): return str(self.items) __repr__ = __str__ def __nonzero__(self): return self.items!=[] def append(self, value): if isinstance(value, self.type): self.property._set(self.object, value) else: raise TypeError, 'Object is not of type %s' % self.type.__name__ def remove(self, value): if value in self.items: self.property.__delete__(self.object, value) else: raise ValueError, '%s not in collection' % value def index(self, key): """ Given an object, return the position of that object in the collection. """ return self.items.index(key) # OCL members (from SMW by Ivan Porres, http://www.abo.fi/~iporres/smw) def size(self): return len(self.items) def includes(self,o): return o in self.items def excludes(self,o): return not self.includes(o) def count(self,o): c=0 for x in self.items: if x==o: c=c+1 return c def includesAll(self,c): for o in c: if o not in self.items: return 0 return 1 def excludesAll(self,c): for o in c: if o in self.items: return 0 return 1 def select(self,f): result=list() for v in self.items: if f(v): result.append(v) return result def reject(self,f): result=list() for v in self.items: if not f(v): result.append(v) return result def collect(self,f): result=list() for v in self.items: result.append(f(v)) return result def isEmpty(self): return len(self.items)==0 def nonEmpty(self): return not self.isEmpty() def sum(self): r=0 for o in self.items: r=r+o return o def forAll(self,f): if not self.items or not inspect.getargspec(f)[0]: return True nargs=len(inspect.getargspec(f)[0]) if inspect.getargspec(f)[3]: nargs=nargs-len(inspect.getargspec(f)[3]) assert(nargs>0) nitems=len(self.items) index=[0]*nargs while 1: args=[] for x in index: args.append(self.items[x]) if not apply(f,args): return False c=len(index)-1 index[c]=index[c]+1 while index[c]==nitems: index[c]=0 c=c-1 if c<0: return True else: index[c]=index[c]+1 if index[c]==nitems-1: c=c-1 return False def exist(self,f): if not self.items or not inspect.getargspec(f)[0]: return False nargs=len(inspect.getargspec(f)[0]) if inspect.getargspec(f)[3]: nargs=nargs-len(inspect.getargspec(f)[3]) assert(nargs>0) nitems=len(self.items) index=[0]*nargs while 1: args=[] for x in index: args.append(self.items[x]) if apply(f,args): return True c=len(index)-1 index[c]=index[c]+1 while index[c]==nitems: index[c]=0 c=c-1 if c<0: return False else: index[c]=index[c]+1 if index[c]==nitems-1: c=c-1 return False def swap(self, item1, item2): """ Swap two elements. Return true if swap was successful. """ try: i1 = self.items.index(item1) i2 = self.items.index(item2) self.items[i1], self.items[i2] = self.items[i2], self.items[i1] # send a notification that this list has changed factory = self.object.factory if factory: factory._handle(AssociationChangeEvent(self.object, self.property)) return True except IndexError, ex: return False except ValueError, ex: return False # vi:sw=4:et:ai gaphor-0.17.2/gaphor/UML/diagram.py000066400000000000000000000067721220151210700167570ustar00rootroot00000000000000# vim: sw=4 """This module contains a model element Diagram which is the abstract representation of a UML diagram. Diagrams can be visualized and edited. The DiagramCanvas class extends the gaphas.Canvas class.""" import gaphas import uuid from uml2 import Namespace, PackageableElement class DiagramCanvas(gaphas.Canvas): """DiagramCanvas extends the gaphas.Canvas class. Updates to the canvas can be blocked by setting the block_updates property to true. A save function can be applied to all root canvas items. Canvas items can be selected with an optional expression filter.""" def __init__(self, diagram): """Initialize the diagram canvas with the supplied diagram. By default, updates are not blocked.""" super(DiagramCanvas, self).__init__() self._diagram = diagram self._block_updates = False diagram = property(lambda s: s._diagram) def _set_block_updates(self, block): """Sets the block_updates property. If false, the diagram canvas is updated immediately.""" self._block_updates = block if not block: self.update_now() block_updates = property(lambda s: s._block_updates, _set_block_updates) def update_now(self): """Update the diagram canvas, unless block_updates is true.""" if self._block_updates: return super(DiagramCanvas, self).update_now() def save(self, save_func): """Apply the supplied save function to all root diagram items.""" for item in self.get_root_items(): save_func(None, item) def postload(self): """Called after the diagram canvas has loaded. Currently does nothing. """ pass def select(self, expression=lambda e: True): """Return a list of all canvas items that match expression.""" return filter(expression, self.get_all_items()) class Diagram(Namespace, PackageableElement): """Diagrams may contain model elements and can be owned by a Package. A diagram is a Namespace and a PackageableElement.""" def __init__(self, id=None, factory=None): """Initialize the diagram with an optional id and element factory. The diagram also has a canvas.""" super(Diagram, self).__init__(id, factory) self.canvas = DiagramCanvas(self) def save(self, save_func): """Apply the supplied save function to this diagram and the canvas.""" super(Diagram, self).save(save_func) save_func('canvas', self.canvas) def postload(self): """Handle post-load functionality for the diagram canvas.""" super(Diagram, self).postload() self.canvas.postload() def create(self, type, parent=None, subject=None): """Create a new canvas item on the canvas. It is created with a unique ID and it is attached to the diagram's root item. The type parameter is the element class to create. The new element also has an optional parent and subject.""" assert issubclass(type, gaphas.Item) obj = type(str(uuid.uuid1())) if subject: obj.subject = subject self.canvas.add(obj, parent) return obj def unlink(self): """Unlink all canvas items then unlink this diagram.""" for item in self.canvas.get_all_items(): try: item.unlink() except: pass super(Diagram, self).unlink() gaphor-0.17.2/gaphor/UML/element.py000066400000000000000000000071111220151210700167700ustar00rootroot00000000000000#!/usr/bin/env python """ Base class for UML model elements. """ __all__ = [ 'Element' ] import threading import uuid from properties import umlproperty class Element(object): """ Base class for UML data classes. """ def __init__(self, id=None, factory=None): """ Create an element. As optional parameters an id and factory can be given. Id is a serial number for the element. The default id is None and will result in an automatic creation of an id. An existing id (such as an int or string) can be provided as well. An id False will result in no id being given (for "transient" or helper classes). Factory can be provided to refer to the class that maintains the lifecycle of the element. """ self._id = id or (id is not False and str(uuid.uuid1()) or False) # The factory this element belongs to. self._factory = factory self._unlink_lock = threading.Lock() id = property(lambda self: self._id, doc='Id') factory = property(lambda self: self._factory, doc="The factory that created this element") def umlproperties(self): """ Iterate over all UML properties """ umlprop = umlproperty class_ = type(self) for propname in dir(class_): if not propname.startswith('_'): prop = getattr(class_, propname) if isinstance(prop, umlprop): yield prop def save(self, save_func): """ Save the state by calling save_func(name, value). """ for prop in self.umlproperties(): prop.save(self, save_func) def load(self, name, value): """ Loads value in name. Make sure that for every load postload() should be called. """ try: prop = getattr(type(self), name) except AttributeError, e: raise AttributeError, "'%s' has no property '%s'" % \ (type(self).__name__, name) else: prop.load(self, value) def postload(self): """ Fix up the odds and ends. """ for prop in self.umlproperties(): prop.postload(self) def unlink(self): """Unlink the element. All the elements references are destroyed. The unlink lock is acquired while unlinking this elements properties to avoid recursion problems.""" if self._unlink_lock.locked(): return with self._unlink_lock: for prop in self.umlproperties(): prop.unlink(self) if self._factory: self._factory._unlink_element(self) # OCL methods: (from SMW by Ivan Porres (http://www.abo.fi/~iporres/smw)) def isKindOf(self, class_): """ Returns true if the object is an instance of class_. """ return isinstance(self, class_) def isTypeOf(self, other): """ Returns true if the object is of the same type as other. """ return type(self) == type(other) def __getstate__(self): d = dict(self.__dict__) try: del d['_factory'] except KeyError: pass return d def __setstate__(self, state): self._factory = None self.__dict__.update(state) try: import psyco except ImportError: pass else: psyco.bind(Element) # vim:sw=4:et gaphor-0.17.2/gaphor/UML/elementfactory.py000066400000000000000000000173311220151210700203650ustar00rootroot00000000000000# vim: sw=4 """ Factory for and registration of model elements. """ from zope import interface from zope import component import uuid from gaphor.core import inject from gaphor.misc import odict from gaphor.interfaces import IService, IEventFilter from gaphor.UML.interfaces import IElementCreateEvent, IElementDeleteEvent, \ IFlushFactoryEvent, IModelFactoryEvent, \ IElementChangeEvent, IElementEvent from gaphor.UML.event import ElementCreateEvent, ElementDeleteEvent, \ FlushFactoryEvent, ModelFactoryEvent from gaphor.UML.element import Element from gaphor.UML.diagram import Diagram class ElementFactory(object): """ The ElementFactory is used to create elements and do lookups to elements. Notifications are send with as arguments (name, element, *user_data). The following names are used: create - a new model element is created (element is newly created element) remove - a model element is removed (element is to be removed element) model - a new model has been loaded (element is None) flush - model is flushed: all element are removed from the factory (element is None) """ def __init__(self): self._elements = odict.odict() self._observers = list() def create(self, type): """ Create a new model element of type ``type``. """ obj = self.create_as(type, str(uuid.uuid1())) return obj def create_as(self, type, id): """ Create a new model element of type 'type' with 'id' as its ID. This method should only be used when loading models, since it does not emit an ElementCreateEvent event. """ assert issubclass(type, Element) obj = type(id, self) self._elements[id] = obj return obj def bind(self, element): """ Bind an already created element to the element factory. The element may not be bound to another factory already. """ if hasattr(element, '_factory') and element._factory: raise AttributeError, "element is already bound" if self._elements.get(element.id): raise AttributeError, "an element already exists with the same id" element._factory = self self._elements[element.id] = element def size(self): """ Return the amount of elements currently in the factory. """ return len(self._elements) def lookup(self, id): """ Find element with a specific id. """ return self._elements.get(id) __getitem__ = lookup def __contains__(self, element): return self.lookup(element.id) is element def select(self, expression=None): """ Iterate elements that comply with expression. """ if expression is None: for e in self._elements.itervalues(): yield e else: for e in self._elements.itervalues(): if expression(e): yield e def lselect(self, expression=None): """ Like select(), but returns a list. """ return list(self.select(expression)) def keys(self): """ Return a list with all id's in the factory. """ return self._elements.keys() def iterkeys(self): """ Return a iterator with all id's in the factory. """ return self._elements.iterkeys() def values(self): """ Return a list with all elements in the factory. """ return self._elements.values() def itervalues(self): """ Return a iterator with all elements in the factory. """ return self._elements.itervalues() def is_empty(self): """ Returns True if the factory holds no elements. """ return bool(self._elements) def flush(self): """Flush all elements (remove them from the factory). Diagram elements are flushed first. This is so that canvas updates are blocked. The remaining elements are then flushed. """ flush_element = self._flush_element for element in self.lselect(lambda e: isinstance(e, Diagram)): element.canvas.block_updates = True flush_element(element) for element in self.lselect(): flush_element(element) def _flush_element(self, element): element.unlink() def _unlink_element(self, element): """ NOTE: Invoked from Element.unlink() to perform an element unlink. """ try: del self._elements[element.id] except KeyError: pass def swap_element(self, element, new_class): assert element in self._elements.values() if element.__class__ is not new_class: element.__class__ = new_class def _handle(self, event): """ Handle events coming from elements. """ # Invoke default handler, so properties get updated. component.handle(event) class ElementFactoryService(ElementFactory): """ Service version of the ElementFctory. """ interface.implements(IService) component_registry = inject('component_registry') def init(self, app): pass def shutdown(self): self.flush() def create(self, type): """ Create a new model element of type ``type``. """ obj = super(ElementFactoryService, self).create(type) self.component_registry.handle(ElementCreateEvent(self, obj)) return obj def flush(self): """Flush all elements (remove them from the factory). First test if the element factory has a Gaphor application instance. If yes, the application will handle a FlushFactoryEvent and will register a ElementChangedEventBlocker adapter. Diagram elements are flushed first. This is so that canvas updates are blocked. The remaining elements are then flushed. Finally, the ElementChangedEventBlocker adapter is unregistered if the factory has an application instance.""" self.component_registry.handle(FlushFactoryEvent(self)) self.component_registry.register_subscription_adapter(ElementChangedEventBlocker) try: super(ElementFactoryService, self).flush() finally: self.component_registry.unregister_subscription_adapter(ElementChangedEventBlocker) def notify_model(self): """ Send notification that a new model has been loaded by means of the ModelFactoryEvent event from gaphor.UML.event. """ self.component_registry.handle(ModelFactoryEvent(self)) def _unlink_element(self, element): """ NOTE: Invoked from Element.unlink() to perform an element unlink. """ self.component_registry.handle(ElementDeleteEvent(self, element)) super(ElementFactoryService, self)._unlink_element(element) def _handle(self, event): """ Handle events coming from elements (used internally). """ self.component_registry.handle(event) class ElementChangedEventBlocker(object): """ Blocks all events of type IElementChangeEvent. This filter is placed when the the element factory flushes it's content. """ component.adapts(IElementChangeEvent) interface.implements(IEventFilter) def __init__(self, event): self._event = event def filter(self): """ Returns something that evaluates to `True` so events are blocked. """ return 'Blocked by ElementFactory.flush()' # vim:sw=4:et gaphor-0.17.2/gaphor/UML/event.py000066400000000000000000000171321220151210700164640ustar00rootroot00000000000000"""The core UML metamodel events.""" from interfaces import * from zope import interface class AttributeChangeEvent(object): """A UML attribute has changed value.""" interface.implements(IAttributeChangeEvent) def __init__(self, element, attribute, old_value, new_value): """Constructor. The element parameter is the element with the changing attribute. The attribute parameter is the parameter element that changed. The old_value is the old value of the attribute and the new_value is the new value of the attribute.""" self.element = element self.property = attribute self.old_value = old_value self.new_value = new_value class AssociationChangeEvent(object): """An association UML element has changed.""" interface.implements(IAssociationChangeEvent) def __init__(self, element, association): """Constructor. The element parameter is the element the association is changing from. The association parameter is the changed association element.""" self.element = element self.property = association class AssociationSetEvent(AssociationChangeEvent): """An association element has been set.""" interface.implements(IAssociationSetEvent) def __init__(self, element, association, old_value, new_value): """Constructor. The element parameter is the element setting the association element. The association parameter is the association element being set. The old_value parameter is the old association and the new_value parameter is the new association.""" AssociationChangeEvent.__init__(self, element, association) self.old_value = old_value self.new_value = new_value class AssociationAddEvent(AssociationChangeEvent): """An association element has been added.""" interface.implements(IAssociationAddEvent) def __init__(self, element, association, new_value): """Constructor. The element parameter is the element the association has been added to. The association parameter is the association element being added.""" AssociationChangeEvent.__init__(self, element, association) self.new_value = new_value class AssociationDeleteEvent(AssociationChangeEvent): """An association element has been deleted.""" interface.implements(IAssociationDeleteEvent) def __init__(self, element, association, old_value): """Constructor. The element parameter is the element the association has been deleted from. The association parameter is the deleted association element.""" AssociationChangeEvent.__init__(self, element, association) self.old_value = old_value class DerivedChangeEvent(AssociationChangeEvent): """A derived property has changed.""" pass class DerivedSetEvent(DerivedChangeEvent): """A generic derived set event.""" interface.implements(IAssociationSetEvent) def __init__(self, element, association, old_value, new_value): """Constructor. The element parameter is the element to which the derived set belongs. The association parameter is the association of the derived set.""" AssociationChangeEvent.__init__(self, element, association) self.old_value = old_value self.new_value = new_value class DerivedAddEvent(DerivedChangeEvent): """A derived property has been added.""" interface.implements(IAssociationAddEvent) def __init__(self, element, association, new_value): """Constructor. The element parameter is the element to which the derived property belongs. The association parameter is the association of the derived property.""" AssociationChangeEvent.__init__(self, element, association) self.new_value = new_value class DerivedDeleteEvent(DerivedChangeEvent): """A derived property has been deleted.""" interface.implements(IAssociationDeleteEvent) def __init__(self, element, association, old_value): """Constructor. The element parameter is the element to which the derived property belongs. The association parameter is the association of the derived property.""" AssociationChangeEvent.__init__(self, element, association) self.old_value = old_value class RedefineSetEvent(AssociationChangeEvent): """A redefined property has been set.""" interface.implements(IAssociationSetEvent) def __init__(self, element, association, old_value, new_value): """Constructor. The element parameter is the element to which the property belongs. The association parameter is association of the property.""" AssociationChangeEvent.__init__(self, element, association) self.old_value = old_value self.new_value = new_value class RedefineAddEvent(AssociationChangeEvent): """A redefined property has been added.""" interface.implements(IAssociationAddEvent) def __init__(self, element, association, new_value): """Constructor. The element parameter is the element to which the property belongs. The association parameter is the association of the property.""" AssociationChangeEvent.__init__(self, element, association) self.new_value = new_value class RedefineDeleteEvent(AssociationChangeEvent): """A redefined property has been deleted.""" interface.implements(IAssociationDeleteEvent) def __init__(self, element, association, old_value): """Constructor. The element parameter is the element to which the property belongs. The association parameter is the association of the property.""" AssociationChangeEvent.__init__(self, element, association) self.old_value = old_value class DiagramItemCreateEvent(object): """A diagram item has been created.""" interface.implements(IElementCreateEvent) def __init__(self, element): """Constructor. The element parameter is the element being created.""" self.element = element class ElementCreateEvent(object): """An element has been created.""" interface.implements(IElementCreateEvent, IElementFactoryEvent) def __init__(self, service, element): """Constructor. The service parameter is the service responsible for creating the element. The element parameter is the element being created.""" self.service = service self.element = element class ElementDeleteEvent(object): """An element has been deleted.""" interface.implements(IElementDeleteEvent, IElementFactoryEvent) def __init__(self, service, element): """Constructor. The service parameter is the service responsible for deleting the element. The element parameter is the element being deleted.""" self.service = service self.element = element class ModelFactoryEvent(object): """A generic element factory event.""" interface.implements(IModelFactoryEvent) def __init__(self, service): """Constructor. The service parameter is the service the emitted the event.""" self.service = service class FlushFactoryEvent(object): """The element factory has been flushed.""" interface.implements(IFlushFactoryEvent) def __init__(self, service): """Constructor. The service parameter is the service responsible for flushing the factory.""" self.service = service gaphor-0.17.2/gaphor/UML/interfaces.py000066400000000000000000000040031220151210700174570ustar00rootroot00000000000000""" UML events emited on a change in the data model. """ from zope import interface from gaphor.interfaces import IServiceEvent class IElementEvent(interface.Interface): """Generic event fired when element state changes. """ element = interface.Attribute("The changed element") class IElementCreateEvent(IElementEvent): """A new element has been created. """ class IElementDeleteEvent(IElementEvent): """An element is deleted from the model. """ class IElementChangeEvent(IElementEvent): """ Generic event fired when element state changes. """ property = interface.Attribute("The property that changed") old_value = interface.Attribute("The property value before the change") new_value = interface.Attribute("The property value after the change") class IAttributeChangeEvent(IElementChangeEvent): """ An attribute has changed. """ class IAssociationChangeEvent(IElementChangeEvent): """ An association hs changed. This event may be fired for both ends of the association. """ class IAssociationSetEvent(IAssociationChangeEvent): """ An association with [0..1] multiplicity has been changed. """ class IAssociationAddEvent(IAssociationChangeEvent): """ An association with [0..*] multiplicity has been changed: a new entry is added. ``new_value`` contains the property being added. """ class IAssociationDeleteEvent(IAssociationChangeEvent): """ An association with [0..*] multiplicity has been changed: an entry has been removed. ``old_value`` contains the property that has been removed. """ class IElementFactoryEvent(IServiceEvent): """ Events related to individual model elements. """ class IModelFactoryEvent(IElementFactoryEvent): """ A new model is loaded into the ElementFactory. """ class IFlushFactoryEvent(IElementFactoryEvent): """ All elements are removed from the ElementFactory. This event is emitted before the factory is emptied. """ # vim: sw=4:et gaphor-0.17.2/gaphor/UML/modelfactory.py000066400000000000000000000216451220151210700200370ustar00rootroot00000000000000""" UML model support functions. Functions collected in this module allow to - create more complex UML model structures - perform specific searches and manipulations """ import itertools from gaphor.UML.uml2 import * # '<<%s>>' STEREOTYPE_FMT = '\xc2\xab%s\xc2\xbb' def stereotypes_str(element, stereotypes=()): """ Identify stereotypes of an UML metamodel instance and return coma separated stereotypes as string. :Parameters: element Element having stereotypes, can be None. stereotypes List of additional stereotypes, can be empty. """ # generate string with stereotype names separated by coma if element: applied = (stereotype_name(st) for st in get_applied_stereotypes(element)) else: applied = () s = ', '.join(itertools.chain(stereotypes, applied)) if s: return STEREOTYPE_FMT % s else: return '' def stereotype_name(stereotype): """ Return stereotype name suggested by UML specification. First will be character lowercase unless the second character is uppercase. :Parameters: stereotype Stereotype UML metamodel instance. """ name = stereotype.name if not name: return '' elif len(name) > 1 and name[1].isupper(): return name else: return name[0].lower() + name[1:] def apply_stereotype(factory, element, stereotype): """ Apply a stereotype to an element. :Parameters: factory UML metamodel factory. element UML metamodel class instance. stereotype UML metamodel stereotype instance. """ obj = factory.create(InstanceSpecification) obj.classifier = stereotype element.appliedStereotype = obj return obj def find_instances(factory, element): """ Find instance specification which extend classifier `element`. """ return factory.select(lambda e: e.isKindOf(InstanceSpecification) \ and e.classifier and e.classifier[0] == element) def remove_stereotype(element, stereotype): """ Remove a stereotype from an element. :Parameters: element UML metamodel element instance. stereotype UML metamodel stereotype instance. """ for obj in element.appliedStereotype: if obj.classifier and obj.classifier[0] is stereotype: del element.appliedStereotype[obj] obj.unlink() break def get_stereotypes(factory, element): """ Get sorted collection of possible stereotypes for specified element. """ # UML specs does not allow to extend stereotypes with stereotypes if isinstance(element, Stereotype): return () cls = type(element) # find out names of classes, which are superclasses of element class names = set(c.__name__ for c in cls.__mro__ if issubclass(c, Element)) # find stereotypes that extend element class classes = factory.select(lambda e: e.isKindOf(Class) and e.name in names) stereotypes = set(ext.ownedEnd.type for cls in classes for ext in cls.extension) return sorted(stereotypes, key=lambda st: st.name) def get_applied_stereotypes(element): """ Get collection of applied stereotypes to an element. """ return element.appliedStereotype[:].classifier def create_extension(factory, element, stereotype): """ Extend an element with a stereotype. """ ext = factory.create(Extension) p = factory.create(Property) ext_end = factory.create(ExtensionEnd) ext.memberEnd = p ext.memberEnd = ext_end ext.ownedEnd = ext_end ext_end.type = stereotype ext_end.aggregation = 'composite' p.type = element p.name = 'baseClass' stereotype.ownedAttribute = p return ext extend_with_stereotype = create_extension def add_slot(factory, instance, definingFeature): """ Add slot to instance specification for an attribute. """ slot = factory.create(Slot) slot.definingFeature = definingFeature instance.slot = slot return slot def create_dependency(factory, supplier, client): dep = factory.create(Dependency) dep.supplier = supplier dep.client = client return dep def create_realization(factory, realizingClassifier, abstraction): dep = factory.create(Realization) dep.realizingClassifier = realizingClassifier dep.abstraction = abstraction return dep def create_generalization(factory, general, specific): gen = factory.create(Generalization) gen.general = general gen.specific = specific return gen def create_implementation(factory, contract, implementatingClassifier): impl = factory.create(Implementation) impl.contract = contract impl.implementatingClassifier = implementatingClassifier return impl def create_association(factory, type_a, type_b): """ Create an association between two items. """ assoc = factory.create(Association) end_a = factory.create(Property) end_b = factory.create(Property) assoc.memberEnd = end_a assoc.memberEnd = end_b end_a.type = type_a end_b.type = type_b # set default navigability (unknown) set_navigability(assoc, end_a, None) set_navigability(assoc, end_b, None) return assoc def set_navigability(assoc, end, nav): """ Set navigability of an association end (property). There are free possible values for ``nav`` parameter True association end is navigable False association end is not navigable None association end navigability is unkown There are two ways of specifing than an end is navigable - an end is in Association.navigableOwnedEnd collection - an end is class (interface) attribute (stored in Class.ownedAttribute collection) Let's consider the graph:: A -----> B y x There two association ends A.x and B.y, A.x is navigable. Therefore navigable association ends are constructed in following way - if A is a class or an interface, then A.x is an attribute owned by A - if A is other classifier, then association is more general relationship; it may mean that participating instance of B can be "accessed efficiently" - i.e. when A is a Component, then association may be some compositing relationship - when A and B are instances of Node class, then it is a communication path Therefore navigable association end may be stored as one of - {Class,Interface}.ownedAttribute due to their capabilities of editing owned members - Association.navigableOwnedEnd When an end has unknown (unspecified) navigability, then it is owned by association (but not by classifier). When an end is non-navigable, then it is just member of an association. """ # remove "navigable" and "unspecified" navigation indicators first if type(end.type) in (Class, Interface): owner = end.opposite.type if end in owner.ownedAttribute: owner.ownedAttribute.remove(end) if end in assoc.ownedEnd: assoc.ownedEnd.remove(end) if end in assoc.navigableOwnedEnd: assoc.navigableOwnedEnd.remove(end) assert end not in assoc.ownedEnd assert end not in assoc.navigableOwnedEnd if nav is True: if type(end.type) in (Class, Interface): owner = end.opposite.type owner.ownedAttribute = end else: assoc.navigableOwnedEnd = end elif nav is None: assoc.ownedEnd = end # elif nav is False, non-navigable def dependency_type(client, supplier): """ Determine dependency type between client (tail) and supplier (arrowhead). There can be different dependencies detected automatically - usage when supplier is an interface - realization when client is component and supplier is a classifier If none of above is detected then standard dependency is determined. """ dt = Dependency # test interface first as it is a classifier if isinstance(supplier, Interface): dt = Usage elif isinstance(client, Component) and isinstance(supplier, Classifier): dt = Realization return dt def create_message(factory, msg, inverted=False): """ Create new message based on speciied message. If inverted is set to True, then inverted message is created. """ message = factory.create(Message) send = None receive = None if msg.sendEvent: send = factory.create(MessageOccurrenceSpecification) sl = msg.sendEvent.covered send.covered = sl if msg.receiveEvent: receive = factory.create(MessageOccurrenceSpecification) rl = msg.receiveEvent.covered receive.covered = rl if inverted: # inverted message goes in different direction, than original # message message.sendEvent = receive message.receiveEvent = send else: message.sendEvent = send message.receiveEvent = receive return message #vim:sw=4:et:ai gaphor-0.17.2/gaphor/UML/properties.py000066400000000000000000000646131220151210700175450ustar00rootroot00000000000000""" Properties used to create the UML 2.0 data model. The logic for creating and destroying connections between UML objects is implemented in Python property classes. These classes are simply instantiated like this: class Class(Element): pass class Comment(Element): pass Class.ownedComment = association('ownedComment', Comment, 0, '*', 'annotatedElement') Comment.annotatedElement = association('annotatedElement', Element, 0, '*', 'ownedComment') Same for attributes and enumerations. Each property type (association, attribute and enumeration) has three specific methods: _get(): return the value _set(value): set the value or add it to a list _del(value=None): delete the value. 'value' is used to tell which value is to be removed (in case of associations with multiplicity > 1). load(value): load 'value' as the current value for this property save(save_func): send the value of the property to save_func(name, value) """ __all__ = [ 'attribute', 'enumeration', 'association', 'derivedunion', 'redefine' ] from zope import component from collection import collection, collectionlist from event import AttributeChangeEvent, AssociationSetEvent, \ AssociationAddEvent, AssociationDeleteEvent from event import DerivedChangeEvent, DerivedSetEvent, \ DerivedAddEvent, DerivedDeleteEvent from event import RedefineSetEvent, RedefineAddEvent, RedefineDeleteEvent from interfaces import IElementChangeEvent, \ IAssociationChangeEvent, IAssociationSetEvent, \ IAssociationAddEvent, IAssociationDeleteEvent class umlproperty(object): """ Superclass for attribute, enumeration and association. The subclasses should define a ``name`` attribute that contains the name of the property. Derived properties (derivedunion and redefine) can be connected, they will be notified when the value changes. In some cases properties call out and delegate actions to the ElementFactory, for example in the case of event handling. """ def __get__(self, obj, class_=None): if obj: return self._get(obj) return self def __set__(self, obj, value): self._set(obj, value) def __delete__(self, obj, value=None): self._del(obj, value) def save(self, obj, save_func): if hasattr(obj, self._name): save_func(self.name, self._get(obj)) def load(self, obj, value): self._set(obj, value) def postload(self, obj): pass def unlink(self, obj): """ This is called from the Element to denote the element is unlinking. """ pass def handle(self, event): factory = event.element.factory if factory: factory._handle(event) else: component.handle(event) class attribute(umlproperty): """ Attribute. Element.attr = attribute('attr', types.StringType, '') """ # TODO: check if lower and upper are actually needed for attributes def __init__(self, name, type, default=None, lower=0, upper=1): self.name = intern(name) self._name = intern('_' + name) self.type = type self.default = default self.lower = lower self.upper = upper def load(self, obj, value): """Load the attribute value.""" try: setattr(obj, self._name, self.type(value)) except ValueError: error_msg = 'Failed to load attribute %s of type %s with value %s'\ % (self._name, self.type, value) raise TypeError(error_msg) def __str__(self): if self.lower == self.upper: return '' % (self.name, self.type, self.lower, self.default) else: return '' % (self.name, self.type, self.lower, self.upper, self.default) def _get(self, obj): try: return getattr(obj, self._name) except AttributeError: return self.default def _set(self, obj, value): if value is not None and not isinstance(value, self.type): raise AttributeError, 'Value should be of type %s' % hasattr(self.type, '__name__') and self.type.__name__ or self.type if value == self._get(obj): return #undoattributeaction(self, obj, self._get(obj)) old = self._get(obj) if value == self.default and hasattr(obj, self._name): delattr(obj, self._name) else: setattr(obj, self._name, value) self.handle(AttributeChangeEvent(obj, self, old, value)) def _del(self, obj, value=None): old = self._get(obj) try: #undoattributeaction(self, obj, self._get(obj)) delattr(obj, self._name) except AttributeError: pass else: self.handle(AttributeChangeEvent(obj, self, old, self.default)) class enumeration(umlproperty): """ Enumeration Element.enum = enumeration('enum', ('one', 'two', 'three'), 'one') An enumeration is a special kind of attribute that can only hold a predefined set of values. Multiplicity is always `[0..1]` """ # All enumerations have a type 'str' type = property(lambda s: str) def __init__(self, name, values, default): self.name = intern(name) self._name = intern('_' + name) self.values = values self.default = default self.lower = 0 self.upper = 1 def __str__(self): return '' % (self.name, self.values, self.default) def _get(self, obj): try: return getattr(obj, self._name) except AttributeError: return self.default def load(self, obj, value): if not value in self.values: raise AttributeError, 'Value should be one of %s' % str(self.values) setattr(obj, self._name, value) def _set(self, obj, value): if not value in self.values: raise AttributeError, 'Value should be one of %s' % str(self.values) old = self._get(obj) if value == old: return if value == self.default: delattr(obj, self._name) else: setattr(obj, self._name, value) self.handle(AttributeChangeEvent(obj, self, old, value)) def _del(self, obj, value=None): old = self._get(obj) try: delattr(obj, self._name) except AttributeError: pass else: self.handle(AttributeChangeEvent(obj, self, old, self.default)) class association(umlproperty): """ Association, both uni- and bi-directional. Element.assoc = association('assoc', Element, opposite='other') A listerer is connected to the value added to the association. This will cause the association to be ended if the element on the other end of the association is unlinked. If the association is a composite relationship, the association will unlink all elements attached to if it is unlinked. """ def __init__(self, name, type, lower=0, upper='*', composite=False, opposite=None): self.name = intern(name) self._name = intern('_' + name) self.type = type self.lower = lower self.upper = upper self.composite = composite self.opposite = opposite and intern(opposite) self.stub = None def load(self, obj, value): if not isinstance(value, self.type): raise AttributeError, 'Value for %s should be of type %s (%s)' % (self.name, self.type.__name__, type(value).__name__) self._set(obj, value, do_notify=False) def postload(self, obj): """ In the postload step, ensure that bi-directional associations are bi-directional. """ values = self._get(obj) if not values: return if self.upper == 1: values = [ values ] for value in values: if not isinstance(value, self.type): raise AttributeError, 'Error in postload validation for %s: Value %s should be of type %s' % (self.name, value, self.type.__name__) def __str__(self): if self.lower == self.upper: s = '' or '', self.opposite) return s + '>' def _get(self, obj): #print '_get', self, obj # TODO: Handle lower and add items if lower > 0 try: return getattr(obj, self._name) except AttributeError: if self.upper == 1: return None else: # Create the empty collection here since it might be used to # add c = collection(self, obj, self.type) setattr(obj, self._name, c) return c def _set(self, obj, value, from_opposite=False, do_notify=True): """ Set a new value for our attribute. If this is a collection, append to the existing collection. This method is called from the opposite association property. """ #print '__set__', self, obj, value, self._get(obj) if not (isinstance(value, self.type) or \ (value is None and self.upper == 1)): raise AttributeError, 'Value should be of type %s' % self.type.__name__ # Remove old value only for uni-directional associations if self.upper == 1: old = self._get(obj) # do nothing if we are assigned our current value: # Still do your thing, since undo handlers expect that. if value is old: return if old: self._del(obj, old, from_opposite=from_opposite, do_notify=False) if do_notify: event = AssociationSetEvent(obj, self, old, value) if value is None: if do_notify: self.handle(event) return setattr(obj, self._name, value) else: # Set the actual value c = self._get(obj) if not c: c = collection(self, obj, self.type) setattr(obj, self._name, c) elif value in c: return c.items.append(value) if do_notify: event = AssociationAddEvent(obj, self, value) if not from_opposite and self.opposite: opposite = getattr(type(value), self.opposite) if not opposite.opposite: opposite.stub = self opposite._set(value, obj, from_opposite=True, do_notify=do_notify) elif not self.opposite: if not self.stub: self.stub = associationstub(self) setattr(self.type, 'UML_associationstub_%x' % id(self), self.stub) self.stub._set(value, obj) if do_notify: self.handle(event) def _del(self, obj, value, from_opposite=False, do_notify=True): """ Delete is used for element deletion and for removal of elements from a list. """ #print '__delete__', self, obj, value if not value: if self.upper > 1: raise Exception, 'Can not delete collections' old = value = self._get(obj) if value is None: return if not from_opposite and self.opposite: getattr(type(value), self.opposite)._del(value, obj, from_opposite=True) elif not self.opposite: if self.stub: self.stub._del(value, obj, from_opposite=True) event = None if self.upper == 1: try: delattr(obj, self._name) except: pass else: if do_notify: event = AssociationSetEvent(obj, self, value, None) else: c = self._get(obj) if c: items = c.items try: items.remove(value) except: pass else: if do_notify: event = AssociationDeleteEvent(obj, self, value) # Remove items collection if empty if not items: delattr(obj, self._name) if do_notify and event: self.handle(event) def unlink(self, obj): values = self._get(obj) composite = self.composite if values: if self.upper == 1: values = [values] for value in list(values): # TODO: make normal unlinking work through this method. self.__delete__(obj, value) if composite: value.unlink() class AssociationStubError(Exception): pass class associationstub(umlproperty): """ An association stub is an internal thingy that ensures all associations are always bi-directional. This helps the application when one end of the association is unlinked. On unlink() of an element all `umlproperty`'s are iterated and called by their unlink() method. """ def __init__(self, association): self.association = association self._name = intern('_stub_%x' % id(self)) def __get__(self, obj, class_=None): if obj: raise AssociationStubError, 'getting values not allowed' return self def __set__(self, obj, value): raise AssociationStubError, 'setting values not allowed' def __delete__(self, obj, value=None): raise AssociationStubError, 'deleting values not allowed' def save(self, obj, save_func): pass def load(self, obj, value): pass def unlink(self, obj): try: values = getattr(obj, self._name) except AttributeError: pass else: for value in set(values): self.association.__delete__(value, obj) def _set(self, obj, value): try: getattr(obj, self._name).add(value) except AttributeError: setattr(obj, self._name, set([value])) def _del(self, obj, value, from_opposite=False): try: c = getattr(obj, self._name) except AttributeError: pass else: c.discard(value) class unioncache(object): """ Small cache helper object for derivedunions. """ def __init__(self, data, version): self.data = data self.version = version class derived(umlproperty): """ Base class for derived properties, both derived unions and custom properties. Note that, although this derived property sends DerivedAddEvent, -Delete- and Set events, this gives just an assumption that something may have changed. If something actually changed depends on the filter applied to the derived property. """ def __init__(self, name, type, lower, upper, *subsets): self.name = intern(name) self._name = intern('_' + name) self.version = 1 self.type = type self.lower = lower self.upper = upper self.subsets = set(subsets) self.single = len(subsets) == 1 component.provideHandler(self._association_changed) def load(self, obj, value): raise ValueError, 'Derivedunion: Properties should not be loaded in a derived union %s: %s' % (self.name, value) def postload(self, obj): self.version += 1 def save(self, obj, save_func): pass def __str__(self): return '' % (self.name, str(map(str, self.subsets))[1:-1]) def filter(self, obj): """ Filter should return something iterable. """ raise NotImplementedError, 'Implement this in the property.' def _update(self, obj): """ Update the list of items. Returns a unioncache instance. """ u = self.filter(obj) if self.upper <= 1: assert len(u) <= 1, 'Derived union %s of item %s should have length 1 %s' % (self.name, obj.id, tuple(u)) # maybe code below is better instead the assertion above? #if len(u) > 1: # log.warning('Derived union %s of item %s should have length 1 %s' % (self.name, obj.id, tuple(u))) if u: u = iter(u).next() else: u = None uc = unioncache(u, self.version) setattr(obj, self._name, uc) return uc def _get(self, obj): try: uc = getattr(obj, self._name) if uc.version != self.version: uc = self._update(obj) except AttributeError: uc = self._update(obj) return uc.data def _set(self, obj, value): raise AttributeError, 'Can not set values on a union' def _del(self, obj, value=None): raise AttributeError, 'Can not delete values on a union' @component.adapter(IElementChangeEvent) def _association_changed(self, event): """ Re-emit state change for the derived properties as Derived*Event's. TODO: We should fetch the old and new state of the namespace item in stead of the old and new values of the item that changed. If multiplicity is [0..1]: send DerivedSetEvent if len(union) < 2 if multiplicity is [*]: send DerivedAddEvent and DerivedDeleteEvent if value not in derived union and """ if event.property in self.subsets: # Make sure unions are created again self.version += 1 if not IAssociationChangeEvent.providedBy(event): return # mimic the events for Set/Add/Delete if self.upper == 1: # This is a [0..1] event # TODO: This is an error: [0..*] associations may be used for updating [0..1] associations assert IAssociationSetEvent.providedBy(event) old_value, new_value = event.old_value, event.new_value self.handle(DerivedSetEvent(event.element, self, old_value, new_value)) else: if IAssociationSetEvent.providedBy(event): old_value, new_value = event.old_value, event.new_value # Do a filter? Change to self.handle(DerivedDeleteEvent(event.element, self, old_value)) self.handle(DerivedAddEvent(event.element, self, new_value)) elif IAssociationAddEvent.providedBy(event): new_value = event.new_value self.handle(DerivedAddEvent(event.element, self, new_value)) elif IAssociationDeleteEvent.providedBy(event): old_value = event.old_value self.handle(DerivedDeleteEvent(event.element, self, old_value)) elif IAssociationChangeEvent.providedBy(event): self.handle(DerivedChangeEvent(event.element, self)) else: log.error('Don''t know how to handle event ' + str(event) + ' for derived union') class derivedunion(derived): """ Derived union Element.union = derivedunion('union', subset1, subset2..subsetn) The subsets are the properties that participate in the union (Element.name). """ def _union(self, obj, exclude=None): """ Returns a union of all values as a set. """ if self.single: return iter(self.subsets).next().__get__(obj) else: u = set() for s in self.subsets: if s is exclude: continue tmp = s.__get__(obj) if tmp: try: u.update(tmp) except TypeError: # [0..1] property u.add(tmp) return collectionlist(u) # Filter is our default filter filter = _union @component.adapter(IElementChangeEvent) def _association_changed(self, event): """ Re-emit state change for the derived union (as Derived*Event's). TODO: We should fetch the old and new state of the namespace item in stead of the old and new values of the item that changed. If multiplicity is [0..1]: send DerivedSetEvent if len(union) < 2 if multiplicity is [*]: send DerivedAddEvent and DerivedDeleteEvent if value not in derived union and """ if event.property in self.subsets: # Make sure unions are created again self.version += 1 if not IAssociationChangeEvent.providedBy(event): return values = self._union(event.element, exclude=event.property) if self.upper == 1: assert IAssociationSetEvent.providedBy(event) old_value, new_value = event.old_value, event.new_value # This is a [0..1] event if self.single: # Only one subset element, so pass the values on self.handle(DerivedSetEvent(event.element, self, old_value, new_value)) else: new_values = set(values) if new_value: new_values.add(new_value) if len(new_values) > 1: # In an in-between state. Do not emit notifications return if values: new_value = iter(values).next() self.handle(DerivedSetEvent(event.element, self, old_value, new_value)) else: if IAssociationSetEvent.providedBy(event): old_value, new_value = event.old_value, event.new_value if old_value and old_value not in values: self.handle(DerivedDeleteEvent(event.element, self, old_value)) if new_value and new_value not in values: self.handle(DerivedAddEvent(event.element, self, new_value)) elif IAssociationAddEvent.providedBy(event): new_value = event.new_value if new_value not in values: self.handle(DerivedAddEvent(event.element, self, new_value)) elif IAssociationDeleteEvent.providedBy(event): old_value = event.old_value if old_value not in values: self.handle(DerivedDeleteEvent(event.element, self, old_value)) elif IAssociationChangeEvent.providedBy(event): self.handle(DerivedChangeEvent(event.element, self)) else: log.error('Don''t know how to handle event ' + str(event) + ' for derived union') class redefine(umlproperty): """ Redefined association Element.redefine = redefine(Element, 'redefine', Class, Element.assoc) If the redefine eclipses the original property (it has the same name) it ensures that the original values are saved and restored. """ def __init__(self, decl_class, name, type, original): self.decl_class = decl_class self.name = intern(name) self._name = intern('_' + name) self.type = type self.original = original component.provideHandler(self._association_changed) upper = property(lambda s: s.original.upper) lower = property(lambda s: s.original.lower) opposite = property(lambda s: s.original.opposite) def load(self, obj, value): if self.original.name == self.name: self.original.load(obj, value) def postload(self, obj): if self.original.name == self.name: self.original.postload(obj) def save(self, obj, save_func): if self.original.name == self.name: self.original.save(obj, save_func) def unlink(self, obj): if self.original.name == self.name: self.original.unlink(obj) def __str__(self): return '' % (self.name, self.type.__name__, str(self.original)) def __get__(self, obj, class_=None): # No longer needed if not obj: return self return self.original.__get__(obj, class_) def __set__(self, obj, value): # No longer needed if not isinstance(value, self.type): raise AttributeError, 'Value should be of type %s' % self.type.__name__ self.original.__set__(obj, value) def __delete__(self, obj, value=None): # No longer needed self.original.__delete__(obj, value) def _get(self, obj): return self.original._get(obj) def _set(self, obj, value, from_opposite=False): return self.original._set(obj, value, from_opposite) def _del(self, obj, value, from_opposite=False): return self.original._del(obj, value, from_opposite) @component.adapter(IAssociationChangeEvent) def _association_changed(self, event): if event.property is self.original and isinstance(event.element, self.decl_class): # mimic the events for Set/Add/Delete if IAssociationSetEvent.providedBy(event): self.handle(RedefineSetEvent(event.element, self, event.old_value, event.new_value)) elif IAssociationAddEvent.providedBy(event): self.handle(RedefineAddEvent(event.element, self, event.new_value)) elif IAssociationDeleteEvent.providedBy(event): self.handle(RedefineDeleteEvent(event.element, self, event.old_value)) else: log.error('Don''t know how to handle event ' + str(event) + ' for redefined association') try: import psyco except ImportError: pass else: psyco.bind(umlproperty) psyco.bind(attribute) psyco.bind(enumeration) psyco.bind(association) psyco.bind(derivedunion) psyco.bind(redefine) # vim:sw=4:et:ai gaphor-0.17.2/gaphor/UML/tests/000077500000000000000000000000001220151210700161275ustar00rootroot00000000000000gaphor-0.17.2/gaphor/UML/tests/__init__.py000066400000000000000000000000151220151210700202340ustar00rootroot00000000000000# unit tests gaphor-0.17.2/gaphor/UML/tests/test_collection.py000066400000000000000000000005521220151210700216750ustar00rootroot00000000000000""" Test if the collection's list supports all trickery. """ import unittest from gaphor.UML.collection import collectionlist class CollectionlistTestCase(unittest.TestCase): def test_listing(self): c = collectionlist() c.append('a') c.append('b') c.append('c') assert str(c) == "['a', 'b', 'c']" # vim:sw=4:et:ai gaphor-0.17.2/gaphor/UML/tests/test_elementfactory.py000066400000000000000000000061641220151210700225700ustar00rootroot00000000000000 import unittest from gaphor.UML import * from gaphor.UML.interfaces import * import gc import weakref, sys class ElementFactoryTestCase(unittest.TestCase): def setUp(self): self.factory = ElementFactory() def tearDown(self): del self.factory def testCreate(self): ef = self.factory p = ef.create(Parameter) assert len(ef.values()) == 1 def testFlush(self): ef = self.factory p = ef.create(Parameter) #wp = weakref.ref(p) assert len(ef.values()) == 1 ef.flush() del p gc.collect() #assert wp() is None assert len(ef.values()) == 0, ef.values() def testWithoutApplication(self): ef = ElementFactory() p = ef.create(Parameter) assert ef.size() == 1, ef.size() ef.flush() assert ef.size() == 0, ef.size() p = ef.create(Parameter) assert ef.size() == 1, ef.size() p.unlink() assert ef.size() == 0, ef.size() def testUnlink(self): ef = self.factory p = ef.create(Parameter) assert len(ef.values()) == 1 p.unlink() assert len(ef.values()) == 0, ef.values() p = ef.create(Parameter) p.defaultValue = 'l' assert len(ef.values()) == 1 p.unlink() del p assert len(ef.values()) == 0, ef.values() from zope import component from gaphor.application import Application # Event handlers are registered as persisting top level handlers, since no # unsubscribe functionality is provided. handled = False events = [] last_event = None @component.adapter(IServiceEvent) def handler(event): global handled, events, last_event handled = True events.append(event) last_event = event component.provideHandler(handler) class ElementFactoryServiceTestCase(unittest.TestCase): def setUp(self): Application.init(['element_factory']) self.factory = Application.get_service('element_factory') def tearDown(self): del self.factory self.clearEvents() Application.shutdown() def clearEvents(self): global handled, events, last_event handled = False events = [ ] last_event = None def testCreateEvent(self): ef = self.factory global handled p = ef.create(Parameter) self.assertTrue(IElementCreateEvent.providedBy(last_event) ) self.assertTrue(handled) def testRemoveEvent(self): ef = self.factory global handled p = ef.create(Parameter) self.assertTrue(IElementCreateEvent.providedBy(last_event) ) self.assertTrue(handled) self.clearEvents() p.unlink() self.assertTrue(IElementDeleteEvent.providedBy(last_event) ) def testModelEvent(self): ef = self.factory global handled ef.notify_model() self.assertTrue(IModelFactoryEvent.providedBy(last_event) ) def testFlushEvent(self): ef = self.factory global handled ef.flush() self.assertTrue(IFlushFactoryEvent.providedBy(last_event) ) # vim:sw=4:et:ai gaphor-0.17.2/gaphor/UML/tests/test_modelfactory.py000066400000000000000000000265601220151210700222410ustar00rootroot00000000000000from gaphor import UML from gaphor.application import Application from gaphor.UML.modelfactory import STEREOTYPE_FMT as fmt import unittest class TestCaseBase(unittest.TestCase): def setUp(self): self.factory = UML.ElementFactory() class StereotypesTestCase(TestCaseBase): def test_stereotype_name(self): """Test stereotype name """ stereotype = self.factory.create(UML.Stereotype) stereotype.name = 'Test' self.assertEquals('test', UML.model.stereotype_name(stereotype)) stereotype.name = 'TEST' self.assertEquals('TEST', UML.model.stereotype_name(stereotype)) stereotype.name = 'T' self.assertEquals('t', UML.model.stereotype_name(stereotype)) stereotype.name = '' self.assertEquals('', UML.model.stereotype_name(stereotype)) stereotype.name = None self.assertEquals('', UML.model.stereotype_name(stereotype)) def test_stereotypes_conversion(self): """Test stereotypes conversion """ s1 = self.factory.create(UML.Stereotype) s2 = self.factory.create(UML.Stereotype) s3 = self.factory.create(UML.Stereotype) s1.name = 's1' s2.name = 's2' s3.name = 's3' cls = self.factory.create(UML.Class) UML.model.apply_stereotype(self.factory, cls, s1) UML.model.apply_stereotype(self.factory, cls, s2) UML.model.apply_stereotype(self.factory, cls, s3) self.assertEquals(fmt % 's1, s2, s3', UML.model.stereotypes_str(cls)) def test_no_stereotypes(self): """Test stereotypes conversion without applied stereotypes """ cls = self.factory.create(UML.Class) self.assertEquals('', UML.model.stereotypes_str(cls)) def test_additional_stereotypes(self): """Test additional stereotypes conversion """ s1 = self.factory.create(UML.Stereotype) s2 = self.factory.create(UML.Stereotype) s3 = self.factory.create(UML.Stereotype) s1.name = 's1' s2.name = 's2' s3.name = 's3' cls = self.factory.create(UML.Class) UML.model.apply_stereotype(self.factory, cls, s1) UML.model.apply_stereotype(self.factory, cls, s2) UML.model.apply_stereotype(self.factory, cls, s3) result = UML.model.stereotypes_str(cls, ('test',)) self.assertEquals(fmt % 'test, s1, s2, s3', result) def test_just_additional_stereotypes(self): """Test additional stereotypes conversion without applied stereotypes """ cls = self.factory.create(UML.Class) result = UML.model.stereotypes_str(cls, ('test',)) self.assertEquals(fmt % 'test', result) def test_getting_stereotypes(self): """Test getting possible stereotypes """ cls = self.factory.create(UML.Class) cls.name = 'Class' st1 = self.factory.create(UML.Stereotype) st1.name = 'st1' st2 = self.factory.create(UML.Stereotype) st2.name = 'st2' # first extend with st2, to check sorting UML.model.extend_with_stereotype(self.factory, cls, st2) UML.model.extend_with_stereotype(self.factory, cls, st1) c1 = self.factory.create(UML.Class) result = tuple(st.name for st in UML.model.get_stereotypes(self.factory, c1)) self.assertEquals(('st1', 'st2'), result) def test_getting_stereotypes_unique(self): """Test if possible stereotypes are unique """ cls1 = self.factory.create(UML.Class) cls1.name = 'Class' cls2 = self.factory.create(UML.Class) cls2.name = 'Component' st1 = self.factory.create(UML.Stereotype) st1.name = 'st1' st2 = self.factory.create(UML.Stereotype) st2.name = 'st2' # first extend with st2, to check sorting UML.model.extend_with_stereotype(self.factory, cls1, st2) UML.model.extend_with_stereotype(self.factory, cls1, st1) UML.model.extend_with_stereotype(self.factory, cls2, st1) UML.model.extend_with_stereotype(self.factory, cls2, st2) c1 = self.factory.create(UML.Component) result = tuple(st.name for st in UML.model.get_stereotypes(self.factory, c1)) self.assertEquals(('st1', 'st2'), result) def test_finding_stereotype_instances(self): """Test finding stereotype instances """ s1 = self.factory.create(UML.Stereotype) s2 = self.factory.create(UML.Stereotype) s1.name = 's1' s2.name = 's2' c1 = self.factory.create(UML.Class) c2 = self.factory.create(UML.Class) UML.model.apply_stereotype(self.factory, c1, s1) UML.model.apply_stereotype(self.factory, c1, s2) UML.model.apply_stereotype(self.factory, c2, s1) result = [e.classifier[0].name for e in UML.model.find_instances(self.factory, s1)] self.assertEquals(2, len(result)) self.assertTrue('s1' in result, result) self.assertFalse('s2' in result, result) class AssociationTestCase(TestCaseBase): """ Association tests. """ def test_creation(self): """Test association creation """ c1 = self.factory.create(UML.Class) c2 = self.factory.create(UML.Class) assoc = UML.model.create_association(self.factory, c1, c2) types = [p.type for p in assoc.memberEnd] self.assertTrue(c1 in types, assoc.memberEnd) self.assertTrue(c2 in types, assoc.memberEnd) c1 = self.factory.create(UML.Interface) c2 = self.factory.create(UML.Interface) assoc = UML.model.create_association(self.factory, c1, c2) types = [p.type for p in assoc.memberEnd] self.assertTrue(c1 in types, assoc.memberEnd) self.assertTrue(c2 in types, assoc.memberEnd) class AssociationEndNavigabilityTestCase(TestCaseBase): """ Association navigability changes tests. """ def test_attribute_navigability(self): """Test navigable attribute of a class or an interface """ c1 = self.factory.create(UML.Class) c2 = self.factory.create(UML.Class) assoc = UML.model.create_association(self.factory, c1, c2) end = assoc.memberEnd[0] assert end.type is c1 assert end.type is c1 UML.model.set_navigability(assoc, end, True) # class/interface navigablity, Association.navigableOwnedEnd not # involved self.assertTrue(end not in assoc.navigableOwnedEnd) self.assertTrue(end not in assoc.ownedEnd) self.assertTrue(end in c2.ownedAttribute) self.assertTrue(end.navigability is True) # uknown navigability UML.model.set_navigability(assoc, end, None) self.assertTrue(end not in assoc.navigableOwnedEnd) self.assertTrue(end in assoc.ownedEnd) self.assertTrue(end not in c2.ownedAttribute) self.assertTrue(end.owner is assoc) self.assertTrue(end.navigability is None) # non-navigability UML.model.set_navigability(assoc, end, False) self.assertTrue(end not in assoc.navigableOwnedEnd) self.assertTrue(end not in assoc.ownedEnd) self.assertTrue(end not in c2.ownedAttribute) self.assertTrue(end.owner is None) self.assertTrue(end.navigability is False) # check other navigability change possibilities UML.model.set_navigability(assoc, end, None) self.assertTrue(end not in assoc.navigableOwnedEnd) self.assertTrue(end in assoc.ownedEnd) self.assertTrue(end not in c2.ownedAttribute) self.assertTrue(end.owner is assoc) self.assertTrue(end.navigability is None) UML.model.set_navigability(assoc, end, True) self.assertTrue(end not in assoc.navigableOwnedEnd) self.assertTrue(end not in assoc.ownedEnd) self.assertTrue(end in c2.ownedAttribute) self.assertTrue(end.owner is c2) self.assertTrue(end.navigability is True) def test_relationship_navigability(self): """Test navigable relationship of a classifier """ n1 = self.factory.create(UML.Node) n2 = self.factory.create(UML.Node) assoc = UML.model.create_association(self.factory, n1, n2) end = assoc.memberEnd[0] assert end.type is n1 UML.model.set_navigability(assoc, end, True) # class/interface navigablity, Association.navigableOwnedEnd not # involved self.assertTrue(end in assoc.navigableOwnedEnd) self.assertTrue(end not in assoc.ownedEnd) self.assertTrue(end.navigability is True) # uknown navigability UML.model.set_navigability(assoc, end, None) self.assertTrue(end not in assoc.navigableOwnedEnd) self.assertTrue(end in assoc.ownedEnd) self.assertTrue(end.navigability is None) # non-navigability UML.model.set_navigability(assoc, end, False) self.assertTrue(end not in assoc.navigableOwnedEnd) self.assertTrue(end not in assoc.ownedEnd) self.assertTrue(end.navigability is False) # check other navigability change possibilities UML.model.set_navigability(assoc, end, None) self.assertTrue(end not in assoc.navigableOwnedEnd) self.assertTrue(end in assoc.ownedEnd) self.assertTrue(end.navigability is None) UML.model.set_navigability(assoc, end, True) self.assertTrue(end in assoc.navigableOwnedEnd) self.assertTrue(end not in assoc.ownedEnd) self.assertTrue(end.navigability is True) class DependencyTypeTestCase(TestCaseBase): """ Tests for automatic dependency discovery """ def test_usage(self): """Test automatic dependency: usage """ cls = self.factory.create(UML.Class) iface = self.factory.create(UML.Interface) dt = UML.model.dependency_type(cls, iface) self.assertEquals(UML.Usage, dt) def test_usage_by_component(self): """Test automatic dependency: usage (by component) """ c = self.factory.create(UML.Component) iface = self.factory.create(UML.Interface) dt = UML.model.dependency_type(c, iface) # it should be usage not realization (interface is classifier as # well) self.assertEquals(UML.Usage, dt) def test_realization(self): """Test automatic dependency: realization """ c = self.factory.create(UML.Component) cls = self.factory.create(UML.Class) dt = UML.model.dependency_type(c, cls) self.assertEquals(UML.Realization, dt) class MessageTestCase(TestCaseBase): """ Tests for interaction messages. """ def test_create(self): """Test message creation """ m = self.factory.create(UML.Message) send = self.factory.create(UML.MessageOccurrenceSpecification) receive = self.factory.create(UML.MessageOccurrenceSpecification) sl = self.factory.create(UML.Lifeline) rl = self.factory.create(UML.Lifeline) send.covered = sl receive.covered = rl m.sendEvent = send m.receiveEvent = receive m1 = UML.model.create_message(self.factory, m, False) m2 = UML.model.create_message(self.factory, m, True) self.assertTrue(m1.sendEvent.covered is sl) self.assertTrue(m1.receiveEvent.covered is rl) self.assertTrue(m2.sendEvent.covered is rl) self.assertTrue(m2.receiveEvent.covered is sl) # vim:sw=4:et gaphor-0.17.2/gaphor/UML/tests/test_properties.py000066400000000000000000000463111220151210700217410ustar00rootroot00000000000000#!/usr/bin/env python import unittest from zope import component from gaphor.application import Application from gaphor.UML.properties import * from gaphor.UML.element import Element from gaphor.UML.interfaces import IAssociationChangeEvent class PropertiesTestCase(unittest.TestCase): def test_association_1_x(self): # # 1:- # class A(Element): pass class B(Element): pass A.one = association('one', B, 0, 1, opposite='two') B.two = association('two', A, 0, 1) a = A() b = B() a.one = b assert a.one is b assert b.two is a a.one = None assert a.one is None assert b.two is None a.one = b assert a.one is b assert b.two is a del a.one assert a.one is None assert b.two is None def test_association_n_x(self): # # n:- # class A(Element): pass class B(Element): pass class C(Element): pass A.one = association('one', B, 0, '*', opposite='two') B.two = association('two', A, 0, 1) a = A() b = B() a.one = b assert b in a.one assert b.two is a def test_association_1_1(self): # # 1:1 # class A(Element): pass class B(Element): pass class C(Element): pass A.one = association('one', B, 0, 1, opposite='two') B.two = association('two', A, 0, 1, opposite='one') a = A() b = B() a.one = b a.one = b assert a.one is b assert b.two is a #assert len(a._observers.get('__unlink__')) == 0 #assert len(b._observers.get('__unlink__')) == 0 a.one = B() assert a.one is not b assert b.two is None #assert len(a._observers.get('__unlink__')) == 0 #assert len(b._observers.get('__unlink__')) == 0 c = C() try: a.one = c except Exception, e: pass #ok print 'exception caught:', e else: assert a.one is not c del a.one assert a.one is None assert b.two is None #assert len(a._observers.get('__unlink__')) == 0 #assert len(b._observers.get('__unlink__')) == 0 def test_association_1_n(self): # # 1:n # class A(Element): pass class B(Element): pass class C(Element): pass A.one = association('one', B, lower=0, upper=1, opposite='two') B.two = association('two', A, lower=0, upper='*', opposite='one') a1 = A() a2 = A() b1 = B() b2 = B() b1.two = a1 assert len(b1.two) == 1, 'len(b1.two) == %d' % len(b1.two) assert a1 in b1.two assert a1.one is b1, '%s/%s' % (a1.one, b1) b1.two = a1 b1.two = a1 assert len(b1.two) == 1, 'len(b1.two) == %d' % len(b1.two) assert a1 in b1.two assert a1.one is b1, '%s/%s' % (a1.one, b1) #assert len(a1._observers.get('__unlink__')) == 0 #assert len(b1._observers.get('__unlink__')) == 0 b1.two = a2 assert a1 in b1.two assert a2 in b1.two assert a1.one is b1 assert a2.one is b1 try: del b1.two except Exception: pass #ok else: assert b1.two != [] assert a1 in b1.two assert a2 in b1.two assert a1.one is b1 assert a2.one is b1 b1.two.remove(a1) assert len(b1.two) == 1 assert a1 not in b1.two assert a2 in b1.two assert a1.one is None assert a2.one is b1 #assert len(a1._observers.get('__unlink__')) == 0 #assert len(b1._observers.get('__unlink__')) == 0 a2.one = b2 assert len(b1.two) == 0 assert len(b2.two) == 1 assert a2 in b2.two assert a1.one is None assert a2.one is b2 try: del b1.two[a1] except ValueError: pass #ok else: assert 0, 'should not be removed' def test_association_n_n(self): # # n:n # class A(Element): pass class B(Element): pass class C(Element): pass A.one = association('one', B, 0, '*', opposite='two') B.two = association('two', A, 0, '*', opposite='one') a1 = A() a2 = A() b1 = B() b2 = B() a1.one = b1 assert b1 in a1.one assert a1 in b1.two assert not a2.one assert not b2.two a1.one = b2 assert b1 in a1.one assert b2 in a1.one assert a1 in b1.two assert a1 in b2.two assert not a2.one #assert len(a1._observers.get('__unlink__')) == 0 #assert len(b1._observers.get('__unlink__')) == 0 a2.one = b1 assert len(a1.one) == 2 assert len(a2.one) == 1 assert len(b1.two) == 2 assert len(b2.two) == 1 assert b1 in a1.one assert b2 in a1.one assert a1 in b1.two assert a1 in b2.two assert b1 in a2.one assert a2 in b1.two del a1.one[b1] assert len(a1.one) == 1 assert len(a2.one) == 1 assert len(b1.two) == 1 assert len(b2.two) == 1 assert b1 not in a1.one assert b2 in a1.one assert a1 not in b1.two assert a1 in b2.two assert b1 in a2.one assert a2 in b1.two #assert len(a1._observers.get('__unlink__')) == 0 #assert len(b1._observers.get('__unlink__')) == 0 def test_association_swap(self): class A(Element): pass class B(Element): pass class C(Element): pass A.one = association('one', B, 0, '*') a = A() b1 = B() b2 = B() a.one = b1 a.one = b2 assert a.one.size() == 2 assert a.one[0] is b1 assert a.one[1] is b2 events = [] @component.adapter(IAssociationChangeEvent) def handler(event, events=events): events.append(event) # Application.register_handler(handler) # try: a.one.swap(b1, b2) # assert len(events) == 1 # assert events[0].property is A.one # assert events[0].element is a # finally: # Application.unregister_handler(handler) assert a.one.size() == 2 assert a.one[0] is b2 assert a.one[1] is b1 def test_association_unlink_1(self): # # unlink # class A(Element): pass class B(Element): pass class C(Element): pass A.one = association('one', B, 0, '*') a1 = A() a2 = A() b1 = B() b2 = B() a1.one = b1 a1.one = b2 assert b1 in a1.one assert b2 in a1.one a2.one = b1 #assert len(a1._observers.get('__unlink__')) == 0 #assert len(b1._observers.get('__unlink__')) == 0 # remove b1 from all elements connected to b1 # also the signal should be removed b1.unlink() #assert len(a1._observers.get('__unlink__')) == 1, a1._observers.get('__unlink__') #assert len(b1._observers.get('__unlink__')) == 0, b1._observers.get('__unlink__') assert b1 not in a1.one assert b2 in a1.one def test_association_unlink_2(self): # # unlink # class A(Element): pass class B(Element): pass class C(Element): pass A.one = association('one', B, 0, '*', opposite='two') B.two = association('two', A, 0, '*') a1 = A() a2 = A() b1 = B() b2 = B() a1.one = b1 a1.one = b2 assert b1 in a1.one assert b2 in a1.one assert a1 in b1.two assert a1 in b2.two a2.one = b1 #assert len(a1._observers.get('__unlink__')) == 0 #assert len(b1._observers.get('__unlink__')) == 0 # remove b1 from all elements connected to b1 # also the signal should be removed b1.unlink() #assert len(a1._observers.get('__unlink__')) == 1, a1._observers.get('__unlink__') #assert len(b1._observers.get('__unlink__')) == 0, b1._observers.get('__unlink__') assert b1 not in a1.one assert b2 in a1.one assert a1 not in b1.two assert a1 in b2.two def test_attributes(self): import types class A(Element): pass A.a = attribute('a', types.StringType, 'default') a = A() assert a.a == 'default', a.a a.a = 'bar' assert a.a == 'bar', a.a del a.a assert a.a == 'default' try: a.a = 1 except AttributeError: pass #ok else: assert 0, 'should not set integer' def test_enumerations(self): import types class A(Element): pass A.a = enumeration('a', ('one', 'two', 'three'), 'one') a = A() assert a.a == 'one' a.a = 'two' assert a.a == 'two' a.a = 'three' assert a.a == 'three' try: a.a = 'four' except AttributeError: assert a.a == 'three' else: assert 0, 'a.a could not be four' del a.a assert a.a == 'one' def skiptest_notify(self): import types class A(Element): notified=None def notify(self, name, pspec): self.notified = name A.assoc = association('assoc', A) A.attr = attribute('attr', types.StringType, 'default') A.enum = enumeration('enum', ('one', 'two'), 'one') a = A() assert a.notified == None a.assoc = A() assert a.notified == 'assoc', a.notified a.attr = 'newval' assert a.notified == 'attr', a.notified a.enum = 'two' assert a.notified == 'enum', a.notified a.notified = None a.enum = 'two' # should not notify since value hasn't changed. assert a.notified == None def test_derivedunion(self): class A(Element): pass A.a = association('a', A) A.b = association('b', A, 0, 1) A.u = derivedunion('u', object, 0, '*', A.a, A.b) a = A() assert len(a.a) == 0, 'a.a = %s' % a.a assert len(a.u) == 0, 'a.u = %s' % a.u a.a = b = A() a.a = c = A() assert len(a.a) == 2, 'a.a = %s' % a.a assert b in a.a assert c in a.a assert len(a.u) == 2, 'a.u = %s' % a.u assert b in a.u assert c in a.u a.b = d = A() assert len(a.a) == 2, 'a.a = %s' % a.a assert b in a.a assert c in a.a assert d == a.b assert len(a.u) == 3, 'a.u = %s' % a.u assert b in a.u assert c in a.u assert d in a.u def skiptest_deriveduntion_notify(self): class A(Element): pass class E(Element): notified=False def notify(self, name, pspec): if name == 'u': self.notified = True E.a = association('a', A) E.u = derivedunion('u', A, 0, '*', E.a) e = E() assert e.notified == False e.a = A() assert e.notified == True def test_derivedunion_listmixins(self): class A(Element): pass A.a = association('a', A) A.b = association('b', A) A.u = derivedunion('u', A, 0, '*', A.a, A.b) A.name = attribute('name', str, 'default') a = A() a.a = A() a.a = A() a.b = A() a.a[0].name = 'foo' a.a[1].name = 'bar' a.b[0].name = 'baz' assert list(a.a[:].name) == ['foo', 'bar'] assert list(a.u[:].name) == ['foo', 'bar', 'baz'] def test_composite(self): class A(Element): is_unlinked = False def unlink(self): self.is_unlinked = True Element.unlink(self) A.comp = association('comp', A, composite=True, opposite='other') A.other = association('other', A, composite=False, opposite='comp') a = A() a.name = 'a' b = A() b.name = 'b' a.comp = b assert b in a.comp assert a in b.other a.unlink() assert a.is_unlinked assert b.is_unlinked def skiptest_derivedunion(self): class A(Element): is_unlinked = False def unlink(self): self.is_unlinked = True Element.unlink(self) A.a = association('a', A, upper=1) A.b = association('b', A) A.derived_a = derivedunion('derived_a', A, 0, 1, A.a) A.derived_b = derivedunion('derived_b', A, 0, '*', A.b) events = [] @component.adapter(IAssociationChangeEvent) def handler(event, events=events): events.append(event) Application.register_handler(handler) try: a = A() a.a = A() assert len(events) == 2, events assert events[0].property is A.derived_a assert events[1].property is A.a finally: Application.unregister_handler(handler) def skiptest_derivedunion_events(self): from zope import component from gaphor.UML.event import DerivedSetEvent, DerivedAddEvent, DerivedDeleteEvent class A(Element): is_unlinked = False def unlink(self): self.is_unlinked = True Element.unlink(self) A.a1 = association('a1', A, upper=1) A.a2 = association('a2', A, upper=1) A.b1 = association('b1', A, upper='*') A.b2 = association('b2', A, upper='*') A.b3 = association('b3', A, upper=1) A.derived_a = derivedunion('derived_a', object, 0, 1, A.a1, A.a2) A.derived_b = derivedunion('derived_b', object, 0, '*', A.b1, A.b2, A.b3) events = [] @component.adapter(IAssociationChangeEvent) def handler(event, events=events): events.append(event) Application.register_handler(handler) try: a = A() a.a1 = A() assert len(events) == 2 assert events[0].property is A.derived_a assert events[1].property is A.a1 assert a.derived_a is a.a1 a.a1 = A() assert len(events) == 4, len(events) assert a.derived_a is a.a1 a.a2 = A() # Should not emit DerivedSetEvent assert len(events) == 5, len(events) assert events[4].property is A.a2 del events[:] old_a1 = a.a1 del a.a1 assert len(events) == 2, len(events) assert events[0].property is A.derived_a assert events[0].new_value is a.a2, '%s %s %s' % (a.a1, a.a2, events[3].new_value) assert events[0].old_value is old_a1, '%s %s %s' % (a.a1, a.a2, events[3].old_value) assert events[1].property is A.a1 del events[:] old_a2 = a.a2 del a.a2 assert len(events) == 2, len(events) assert events[0].property is A.derived_a assert events[0].new_value is None, '%s %s %s' % (a.a1, a.a2, events[5].new_value) assert events[0].old_value is old_a2, '%s %s %s' % (a.a1, a.a2, events[5].old_value) assert events[1].property is A.a2 del events[:] assert len(events) == 0, len(events) a.b1 = A() assert len(events) == 2 assert events[0].property is A.derived_b assert events[1].property is A.b1 a.b2 = A() assert len(events) == 4 assert events[2].property is A.derived_b assert events[3].property is A.b2 a.b2 = A() assert len(events) == 6 assert events[4].property is A.derived_b assert events[5].property is A.b2 a.b3 = A() assert len(events) == 8, len(events) assert events[6].property is A.derived_b assert events[7].property is A.b3 # Add b3's value to b2, should not emit derived union event a.b2 = a.b3 assert len(events) == 9, len(events) assert events[8].property is A.b2 # Remove b3's value to b2 del a.b2[a.b3] assert len(events) == 10, len(events) assert events[9].property is A.b2 a.b3 = A() assert len(events) == 13, len(events) assert events[10].property is A.derived_b assert type(events[10]) is DerivedDeleteEvent, type(events[10]) assert events[11].property is A.derived_b assert type(events[11]) is DerivedAddEvent, type(events[11]) assert events[12].property is A.b3 del a.b3 assert len(events) == 15, len(events) assert events[13].property is A.derived_b assert type(events[13]) is DerivedDeleteEvent, type(events[10]) assert events[14].property is A.b3 finally: Application.unregister_handler(handler) def skiptest_redefine(self): from zope import component from gaphor.application import Application class A(Element): is_unlinked = False def unlink(self): self.is_unlinked = True Element.unlink(self) A.a = association('a', A, upper=1) A.a = redefine(A, 'a', A, A.a) events = [] @component.adapter(IAssociationChangeEvent) def handler(event, events=events): events.append(event) Application.register_handler(handler) try: a = A() a.a = A() assert len(events) == 2 assert events[0].property is A.a, events[0].property assert events[1].property is A.a.original, events[1].property finally: Application.unregister_handler(handler) def skiptest_redefine_subclass(self): from zope import component class A(Element): is_unlinked = False def unlink(self): self.is_unlinked = True Element.unlink(self) A.a = association('a', A, upper=1) class B(A): pass B.b = redefine(B, 'b', A, A.a) events = [] @component.adapter(IAssociationChangeEvent) def handler(event, events=events): events.append(event) Application.register_handler(handler) try: a = A() a.a = A() # Only a.a changes, no B class involved assert len(events) == 1 assert events[0].property is A.a, events[0].property #assert events[1].property is A.a.original, events[1].property del events[:] a = B() a.a = A() # Now events are sent for both association and redefine assert len(events) == 2 assert events[0].property is B.b, events[0].property assert events[1].property is B.b.original, events[1].property finally: Application.unregister_handler(handler) if __name__ == '__main__': unittest.main() # vim:sw=4:et:ai gaphor-0.17.2/gaphor/UML/tests/test_uml2.py000066400000000000000000000311651220151210700204250ustar00rootroot00000000000000 import unittest import gaphor.UML as UML class ClassesTestCase(unittest.TestCase): def setUp(self): self.factory = UML.ElementFactory() def tearDown(self): del self.factory def test_association(self): """Testing Association elements in the meta-model""" try: element = self.factory.create(UML.Association) except AttributeError: self.fail('Association elements are not part of the meta-model') self.assertFalse(element.isDerived, 'The isDerived property should default to False - %s' % element.isDerived) property1 = self.factory.create(UML.Property) property2 = self.factory.create(UML.Property) element.memberEnd = property1 element.memberEnd = property2 element.ownedEnd = property1 element.navigableOwnedEnd = property1 self.assertTrue(property1 in element.member, 'Namespace.member does not contain memberEnd - %s' % element.member) self.assertTrue(property2 in element.member, 'Namespace.member does not contain memberEnd - %s' % element.member) self.assertTrue(property1 in element.feature, 'Classifier.feature does not contain ownedEnd - %s' % element.feature) self.assertTrue(property1 in element.ownedMember, 'Namespace.ownedMember does not contain ownedEnd - %s' % element.ownedEnd) self.assertTrue(property1 in element.ownedEnd, 'Association.ownedEnd does not contain navigableOwnedEnd - %s' % element.ownedEnd) # def test_association_class(self): # try: # element = self.factory.create(UML.AssociationClass) # except AttributeError: # self.fail('AssociationClass elements are not part of the meta-model') def test_class(self): """Testing Class elements in the meta-model""" try: element = self.factory.create(UML.Class) except AttributeError: self.fail('Class elements are not part of the meta-model') property1 = self.factory.create(UML.Property) operation1 = self.factory.create(UML.Operation) element.ownedAttribute = property1 element.ownedOperation = operation1 self.assertTrue(property1 in element.attribute, 'Classifier.attribute does not contain ownedAttribute - %s' % element.attribute) self.assertTrue(property1 in element.ownedMember, 'Namespace.ownedMember does not contain ownedAttribute - %s' % element.ownedMember) self.assertTrue(operation1 in element.feature, 'Classifier.feature does not contain ownedOperation - %s' % element.feature) self.assertTrue(operation1 in element.ownedMember, 'Namespace.ownedMember does not contain ownedOperation' % element.ownedMember) def test_comment(self): """Testing Comment elements in the meta-model""" try: element = self.factory.create(UML.Comment) except AttributeError: self.fail('Comment elements are not part of the meta-model') element.body = 'Comment body' self.assertTrue(element.body == 'Comment body', 'Incorrect comment body - %s' % element.body) annotatedElement = self.factory.create(UML.Class) element.annotatedElement = annotatedElement self.assertTrue(annotatedElement in element.annotatedElement, 'Incorrect annotated element - %s' % element.annotatedElement) def test_constraint(self): """Testing Constraint elements in the meta-model""" try: element = self.factory.create(UML.Constraint) except AttributeError: self.fail('Constraint elements are not part of the meta-model') constrainedElement = self.factory.create(UML.Class) element.constrainedElement = constrainedElement element.specification = 'Constraint specification' self.assertTrue(constrainedElement in element.constrainedElement, 'Constraint.constrainedElement does not contain the correct element - %s' % element.constrainedElement) self.assertTrue(element.specification == 'Constraint specification', 'Constraint.specification is incorrect - %s' % element.specification) def test_dependency(self): """Testing Dependency elements in the meta-model""" try: element = self.factory.create(UML.Dependency) except AttributeError: self.fail('Dependency elements are not part of the meta-model') client = self.factory.create(UML.Package) supplier = self.factory.create(UML.Package) element.client = client element.supplier = supplier self.assertTrue(client in element.source, 'DirectedRelationship.source does not contain client - %s' % element.client) self.assertTrue(supplier in element.target, 'DirectedRelationship.target does not contain supplier - %s' % element.supplier) def test_element_import(self): try: element = self.factory.create(UML.ElementImport) except AttributeError: self.fail('ElementImport elements are not part of the meta-model') def test_enumeration(self): try: element = self.factory.create(UML.Enumeration) except AttributeError: self.fail('Enumeration elements are not part of the meta-model') def test_generalization(self): try: element = self.factory.create(UML.Generalization) except AttributeError: self.fail('Generalization elements are not part of the meta-model') def test_interface(self): try: element = self.factory.create(UML.Interface) except AttributeError: self.fail('Interface elements are not part of the meta-model') def test_namespace(self): try: element = self.factory.create(UML.Namespace) except AttributeError: self.fail('Namespace elements are not part of the meta-model') def test_operation(self): try: element = self.factory.create(UML.Operation) except AttributeError: self.fail('Operation elements are not part of the meta-model') def test_package(self): try: element = self.factory.create(UML.Package) except AttributeError: self.fail('Package elements are not part of the meta-model') def test_parameter(self): try: element = self.factory.create(UML.Parameter) except AttributeError: self.fail('Parameter elements are not part of the meta-model') def test_property(self): try: element = self.factory.create(UML.Property) except AttributeError: self.fail('Property elements are not part of the meta-model') def test_realization(self): try: element = self.factory.create(UML.Realization) except AttributeError: self.fail('Realization elements are not part of the meta-model') class Uml2TestCase(unittest.TestCase): def test_ids(self): factory = UML.ElementFactory() c = factory.create(UML.Class) assert c.id p = factory.create_as(UML.Class, id=False) assert p.id is False, p.id def test1(self): factory = UML.ElementFactory() c = factory.create(UML.Class) p = factory.create(UML.Package) c.package = p self.assertEquals(c.package, p) self.assertEquals(c.namespace, p) self.failUnless(c in p.ownedElement) def testOwnedMember_Unlink(self): factory = UML.ElementFactory() c = factory.create(UML.Class) p = factory.create(UML.Package) c.package = p c.unlink() self.assertEquals([p], factory.lselect()) # def test_lower_upper(self): # """ # Test MultiplicityElement.{lower|upper} # """ # assert UML.MultiplicityElement.lowerValue in UML.MultiplicityElement.lower.subsets # # e = UML.MultiplicityElement() # e.lowerValue = '2' # assert e.lower == '2', e.lower # # assert UML.MultiplicityElement.upperValue in UML.MultiplicityElement.upper.subsets # # e.upperValue = 'up' # assert UML.MultiplicityElement.upper.version == 4, UML.MultiplicityElement.upper.version # assert e.upper == 'up' # e.upperValue = 'down' # assert UML.MultiplicityElement.upper.version == 5, UML.MultiplicityElement.upper.version # assert e.upper == 'down', e.upper # # # TODO: test signal handling def test_property_is_composite(self): p = UML.Property() assert p.isComposite == False, p.isComposite p.aggregation = 'shared' assert p.isComposite == False, p.isComposite p.aggregation = 'composite' assert p.isComposite == True, p.isComposite def test_association_endType(self): factory = UML.ElementFactory() c1 = UML.Class() c2 = UML.Class() a = UML.Association() a.memberEnd = UML.Property() a.memberEnd = UML.Property() a.memberEnd[0].type = c1 a.memberEnd[1].type = c2 c1.ownedAttribute = a.memberEnd[0] c2.ownedAttribute = a.memberEnd[1] assert c1 in a.endType assert c2 in a.endType c3 = UML.Class() a.memberEnd[1].type = c3 assert c1 in a.endType assert c3 in a.endType def test_property_navigability(self): factory = UML.ElementFactory() p = factory.create(UML.Property) assert p.navigability is None c1 = factory.create(UML.Class) c2 = factory.create(UML.Class) a = UML.model.create_association(factory, c1, c2) assert a.memberEnd[0].navigability is None assert a.memberEnd[1].navigability is None UML.model.set_navigability(a, a.memberEnd[0], True) assert a.memberEnd[0].navigability is True assert a.memberEnd[1].navigability is None UML.model.set_navigability(a, a.memberEnd[0], False) assert a.memberEnd[0].navigability is False assert a.memberEnd[1].navigability is None def test_namedelement_qualifiedname(self): factory = UML.ElementFactory() p = factory.create(UML.Package) p.name = 'Package' c = factory.create(UML.Class) c.name = 'Class' self.assertEquals(('Class',), c.qualifiedName) p.ownedClassifier = c self.assertEquals(('Package', 'Class'), c.qualifiedName) def test_extension_metaclass(self): factory = UML.ElementFactory() c = factory.create(UML.Class) c.name = 'Class' s = factory.create(UML.Stereotype) s.name = 'Stereotype' e = UML.model.create_extension(factory, c, s) self.assertEquals(c, e.metaclass) def test_metaclass_extension(self): factory = UML.ElementFactory() c = factory.create(UML.Class) c.name = 'Class' s = factory.create(UML.Stereotype) s.name = 'Stereotype' self.assertEquals([], c.extension) self.assertEquals([], s.extension) e = UML.model.create_extension(factory, c, s) print e.memberEnd self.assertEquals([e], c.extension) self.assertEquals([], s.extension) assert e.ownedEnd.type is s def test_operation_parameter_deletion(self): factory = UML.ElementFactory() self.assertEquals(0, len(factory.lselect())) c = factory.create(UML.Class) c.name = 'Class' o = factory.create(UML.Operation) c.ownedOperation = o UML.parse(o, 'a(x: int, y: int)') c.unlink() self.assertEquals(0, len(factory.lselect()), factory.lselect()) # vim:sw=4:et:ai gaphor-0.17.2/gaphor/UML/tests/test_umlfmt.py000066400000000000000000000012601220151210700210430ustar00rootroot00000000000000""" Formatting of UML model elements into text tests. """ import unittest from gaphor.application import Application from gaphor.UML.elementfactory import ElementFactory from gaphor.UML.umlfmt import format import gaphor.UML.uml2 as UML factory = ElementFactory() class AttributeTestCase(unittest.TestCase): def setUp(self): pass def tearDown(self): factory.flush() def test_simple_format(self): """Test simple attribute formatting """ a = factory.create(UML.Property) a.name = 'myattr' self.assertEquals('+ myattr', format(a)) a.typeValue = 'int' self.assertEquals('+ myattr: int', format(a)) gaphor-0.17.2/gaphor/UML/tests/test_umllex.py000066400000000000000000000167071220151210700210610ustar00rootroot00000000000000""" Parsing of UML model elements from string tests. """ import unittest from gaphor.application import Application from gaphor.UML.elementfactory import ElementFactory from gaphor.UML.umllex import parse from gaphor.UML.umllex import attribute_pat, operation_pat, parameter_pat from gaphor import UML def dump_prop(prop): m = attribute_pat.match(prop) #print m.groupdict() def dump_oper(oper): m = operation_pat.match(oper) if m: g = m.group else: # set name to oper return #print g('vis'), g('name'), g('type'), g('mult_l'), g('mult_u'), g('tags') if g('params'): params = g('params') while params: m = parameter_pat.match(params) g = m.group #print ' ', g('dir') or 'in', g('name'), g('type'), g('mult_l'), g('mult_u'), g('default'), g('tags') params = g('rest') dump_prop('#/name') dump_prop('+ / name : str[1..*] = "aap" { static }') dump_prop('+ / name : str[*] = "aap" { static }') dump_oper('myfunc(aap:str = "aap", out two): type') dump_oper(' myfunc2 ( ): type') dump_oper('myfunc(aap:str[1] = "aap" { tag1, tag2 }, out two {tag3}): type') factory = ElementFactory() class AttributeTestCase(unittest.TestCase): """ Parsing an attribute tests. """ def setUp(self): pass def tearDown(self): factory.flush() def test_parse_property_simple(self): """Test simple property parsing """ a = factory.create(UML.Property) UML.parse(a, 'myattr') self.assertFalse(a.isDerived) self.assertEquals('myattr', a.name) self.assertTrue(a.typeValue is None, a.typeValue) self.assertTrue(a.lowerValue is None, a.lowerValue) self.assertTrue(a.upperValue is None, a.upperValue) self.assertTrue(a.defaultValue is None, a.defaultValue) def test_parse_property_complex(self): """Test complex property parsing """ a = factory.create(UML.Property) UML.parse(a,'+ / name : str[0..*] = "aap" { static }') self.assertEquals('public', a.visibility) self.assertTrue(a.isDerived) self.assertEquals('name', a.name) self.assertEquals('str', a.typeValue) self.assertEquals('0', a.lowerValue) self.assertEquals('*', a.upperValue) self.assertEquals('"aap"', a.defaultValue) def test_parse_property_invalid(self): """Test parsing property with invalid syntax """ a = factory.create(UML.Property) UML.parse(a, '+ name = str[*] = "aap" { static }') self.assertEquals('+ name = str[*] = "aap" { static }', a.name) self.assertFalse(a.isDerived) self.assertTrue(not a.typeValue) self.assertTrue(not a.lowerValue) self.assertTrue(not a.upperValue) self.assertTrue(not a.defaultValue) class AssociationEndTestCase(unittest.TestCase): """ Parsing association end tests. """ def setUp(self): pass def tearDown(self): factory.flush() def test_parse_association_end(self): """Test parsing of association end """ a = factory.create(UML.Association) p = factory.create(UML.Property) p.association = a UML.parse(p, 'end') self.assertEquals('end', p.name) self.assertTrue(not p.typeValue) self.assertTrue(not p.lowerValue) self.assertTrue(not p.upperValue) self.assertTrue(not p.defaultValue) def test_parse_multiplicity(self): """Test parsing of multiplicity """ a = factory.create(UML.Association) p = factory.create(UML.Property) p.association = a UML.parse(p, '0..2 { tag }') self.assertTrue(p.name is None) self.assertTrue(not p.typeValue) self.assertEquals('0', p.lowerValue) self.assertEquals('2', p.upperValue) self.assertTrue(not p.defaultValue) def test_parse_multiplicity2(self): """Test parsing of multiplicity with multiline constraints """ a = factory.create(UML.Association) p = factory.create(UML.Property) p.association = a UML.parse(p, '0..2 { tag1, \ntag2}') self.assertTrue(p.name is None) self.assertTrue(not p.typeValue) self.assertEquals('0', p.lowerValue) self.assertEquals('2', p.upperValue) self.assertTrue(not p.defaultValue) def test_parse_derived_end(self): """Test parsing derived association end """ a = factory.create(UML.Association) p = factory.create(UML.Property) p.association = a UML.parse(p, '-/end[*] { mytag}') self.assertEquals('private', p.visibility) self.assertTrue(p.isDerived) self.assertEquals('end', p.name) self.assertTrue(not p.typeValue) self.assertTrue(not p.lowerValue) self.assertEquals('*', p.upperValue) self.assertTrue(not p.defaultValue) class OperationTestCase(unittest.TestCase): """ Operation parsing tests. """ def setUp(self): factory.flush() def tearDown(self): factory.flush() def test_parse_operation(self): """Test parsing simple operation """ o = factory.create(UML.Operation) UML.parse(o, 'myfunc()') self.assertEquals('myfunc', o.name) self.assertTrue(not o.returnResult[0].typeValue) self.assertFalse(o.formalParameter) def test_parse_operation_return(self): """Test parsing operation with return value """ o = factory.create(UML.Operation) UML.parse(o, '+ myfunc(): int') self.assertEquals('myfunc', o.name) self.assertEquals('int', o.returnResult[0].typeValue) self.assertEquals('public', o.visibility) self.assertTrue(not o.formalParameter) def test_parse_operation_2_params(self): """Test parsing of operation with two parameters """ o = factory.create(UML.Operation) UML.parse(o, '# myfunc2 (a: str, b: int = 3 { static}): float') self.assertEquals('myfunc2', o.name) self.assertEquals('float', o.returnResult[0].typeValue) self.assertEquals('protected', o.visibility) self.assertEquals(2, len(o.formalParameter)) self.assertEquals('a', o.formalParameter[0].name) self.assertEquals('str', o.formalParameter[0].typeValue) self.assertTrue(o.formalParameter[0].defaultValue is None) self.assertEquals('b', o.formalParameter[1].name) self.assertEquals('int', o.formalParameter[1].typeValue) self.assertEquals('3', o.formalParameter[1].defaultValue) def test_parse_operation_1_param(self): """Test parsing of operation with one parameter """ o = factory.create(UML.Operation) UML.parse(o, '- myfunc2 (a: node): double') self.assertEquals('myfunc2', o.name) self.assertEquals('double', o.returnResult[0].typeValue) self.assertEquals('private', o.visibility) self.assertEquals(1, len(o.formalParameter)) self.assertEquals('a', o.formalParameter[0].name) self.assertEquals('node', o.formalParameter[0].typeValue) self.assertTrue(o.formalParameter[0].defaultValue is None) def test_parse_operation_invalid_syntax(self): """Test operation parsing with invalid syntax """ o = factory.create(UML.Operation) UML.parse(o, '- myfunc2: myType2') self.assertEquals('- myfunc2: myType2', o.name) # vim:sw=4:et:ai gaphor-0.17.2/gaphor/UML/uml2.gaphor000077500000000000000000046017571220151210700170750ustar00rootroot00000000000000 0 0 0 AssociationClasses composite ownedOperation * public extension 1 public EnumerationLiteral owningInstance 1 public Expression 0 {All ends of CommunicationPath are typed by Nodes} 0 PackageMerge 0 value Integer Extension Multiplicities 0 0 0 (1.0, 0.0, 0.0, 1.0, 189.0, 37.0) 100.0 59.0 0 1 0 0 (1.0, 0.0, 0.0, 1.0, 129.0, 172.0) 238.0 131.0 0 0 0 0 (1.0, 0.0, 0.0, 1.0, 611.0, 180.0) 174.0 93.0 0 0 (1.0, 0.0, 0.0, 1.0, 611.0, 212.0) 0 1 [(0.0, 0.0), (-244.0, -2.0)] 0 0 (1.0, 0.0, 0.0, 1.0, 611.0, 258.99999999999989) 0 1 [(0.0, 0.0), (-244.0, 1.0000000000001137)] 0 0 (1.0, 0.0, 0.0, 1.0, 239.0, 96.0) 0 1 [(0.0, 0.0), (0.0, 76.0)] 1 0 0 (1.0, 0.0, 0.0, 1.0, 169.0, 437.0) 140.0 59.0 0 0 (1.0, 0.0, 0.0, 1.0, 239.73051224928787, 380.66507177033492) 0 1 [(0.0, 0.0), (-1.7305122492878695, 56.334928229665081)] 0 (1.0, 0.0, 0.0, 1.0, 309.0, 464.0) 0 1 [(0.0, 0.0), (140.0, 5.6843418860808015e-14)] 0 1 0 0 (1.0, 0.0, 0.0, 1.0, 449.0, 435.0) 100.0 56.0 0 0 0 0 (1.0, 0.0, 0.0, 1.0, 161.52475247524751, 330.66507177033492) 166.475247525 50.0 0 0 0 0 (1.0, 0.0, 0.0, 1.0, 420.52475247524751, 330.66507177033492) 195.0 62.0 0 0 (1.0, 0.0, 0.0, 1.0, 496.0, 392.66507177033492) 0 1 [(0.0, 0.0), (0.99999999999988631, 42.334928229665081)] 0 0 0 (1.0, 0.0, 0.0, 1.0, 150.0, 611.44394618834076) 174.0 56.0 0 0 (1.0, 0.0, 0.0, 1.0, 238.00000000000028, 496.0) 0 1 [(0.0, 0.0), (-0.35555555555708906, 115.44394618834076)] 0 (1.0, 0.0, 0.0, 1.0, 300.0, 520.0) 201.0 67.0 0 0 (1.0, 0.0, 0.0, 1.0, 237.84783121346643, 545.40708963648672) 0 1 [(0.0, 0.0), (62.152168786533565, -9.4070896364867167)] 0 abstraction 1 public Actor composite 0 upperValue 1 public redefinedProperty * public Reception 0 0 0 (1.0, 0.0, 0.0, 1.0, 544.0, 197.5) 100.0 62.0 0 0 0 0 (1.0, 0.0, 0.0, 1.0, 522.0, 96.0) 134.0 50.0 0 0 (1.0, 0.0, 0.0, 1.0, 593.0, 146.0) 0 1 [(0.0, 0.0), (0.0, 51.5)] (1.0, 0.0, 0.0, 1.0, 13.0, 14.0) 538.0 42.0 0 0 0 1 (1.0, 0.0, 0.0, 1.0, 314.5, 96.0) 123.0 50.0 0 0 0 1 (1.0, 0.0, 0.0, 1.0, 106.0, 96.0) 118.0 50.0 0 0 0 1 (1.0, 0.0, 0.0, 1.0, 106.0, 368.0) 114.0 50.0 0 0 0 1 (1.0, 0.0, 0.0, 1.0, 543.0, 290.0) 102.0 50.0 0 0 0 1 (1.0, 0.0, 0.0, 1.0, 113.0, 203.0) 100.0 51.0 0 0 0 1 (1.0, 0.0, 0.0, 1.0, 322.0, 209.0) 100.0 117.0 0 0 (1.0, 0.0, 0.0, 1.0, 374.0, 146.0) 0 0 [(0.0, 0.0), (0.0, 63.0)] 0 (1.0, 0.0, 0.0, 1.0, 165.0, 146.0) 0 0 [(0.0, 0.0), (0.0, 57.0)] 0 (1.0, 0.0, 0.0, 1.0, 164.0, 254.0) 0 0 [(0.0, 0.0), (0.0, 114.0)] 0 0 (1.0, 0.0, 0.0, 1.0, 213.0, 229.0) 0 0 [(0.0, 0.0), (109.0, 0.0)] 0 0 (1.0, 0.0, 0.0, 1.0, 422.0, 224.0) 0 0 [(0.0, 0.0), (122.0, 0.0)] 0 0 (1.0, 0.0, 0.0, 1.0, 422.0, 313.0) 0 0 [(0.0, 0.0), (121.0, 0.0)] 0 0 symbol String 1 Kernel 0 composite specification 1 public composite action * composite literal * public MergeNode body String composite manifestation * public ownedComment * public Feature Components 0 0 0 (1.0, 0.0, 0.0, 1.0, 9.0, 139.0) 102.0 103.0 0 1 0 0 (1.0, 0.0, 0.0, 1.0, 248.0, 136.0) 305.0 118.0 0 0 0 0 (1.0, 0.0, 0.0, 1.0, 338.0, 30.0) 100.0 62.0 0 0 (1.0, 0.0, 0.0, 1.0, 385.99999999999994, 92.0) 0 1 [(0.0, 0.0), (0.34285714285744007, 44.0)] 0 (1.0, 0.0, 0.0, 1.0, 111.0, 167.0) 0 1 [(0.0, 0.0), (137.0, 0.0)] 0 0 (1.0, 0.0, 0.0, 1.0, 111.0, 225.0) 0 1 [(0.0, 0.0), (137.0, -2.8421709430404007e-14)] 0 0 0 0 (1.0, 0.0, 0.0, 1.0, 327.0, 355.0) 115.0 62.0 0 0 (1.0, 0.0, 0.0, 1.0, 383.99999999999994, 355.0) 0 1 [(0.0, 0.0), (0.1469387755103071, -101.0)] 0 0 0 0 (1.0, 0.0, 0.0, 1.0, 307.13148788927333, 523.0) 156.0 72.0 0 0 (1.0, 0.0, 0.0, 1.0, 385.04297328597187, 523.0) 0 1 [(0.0, 0.0), (-0.042973285971925179, -106.0)] 0 0 0 0 (1.0, 0.0, 0.0, 1.0, 785.58241758241752, 148.0) 195.0 76.0 0 0 (1.0, 0.0, 0.0, 1.0, 785.58241758241752, 179.92000000000004) 0 1 [(0.0, 0.0), (-232.58241758241752, 1.0799999999999841)] 0 composite presentation * public value String Artifacts 1 0 0 (1.0, 0.0, 0.0, 1.0, 131.0, 209.0) 144.0 71.0 0 0 0 0 (1.0, 0.0, 0.0, 1.0, 120.0, 47.0) 156.0 72.0 0 0 (1.0, 0.0, 0.0, 1.0, 196.81818181818181, 119.0) 0 1 [(0.0, 0.0), (-0.46743554952507793, 90.0)] 0 0 0 (1.0, 0.0, 0.0, 1.0, 522.0, 220.0) 136.0 50.0 0 0 (1.0, 0.0, 0.0, 1.0, 275.0, 236.00000000000003) 0 1 [(0.0, 0.0), (247.0, 1.0)] 0 0 0 0 (1.0, 0.0, 0.0, 1.0, 530.38013698630141, 47.0) 121.0 62.0 0 0 (1.0, 0.0, 0.0, 1.0, 589.99999999999989, 109.0) 0 1 [(0.0, 0.0), (1.1368683772161603e-13, 111.0)] Actions 0 0 0 (1.0, 0.0, 0.0, 1.0, 253.0, 51.0) 100.0 50.0 0 1 0 0 (1.0, 0.0, 0.0, 1.0, 187.0, 172.0) 108.0 56.0 0 1 0 0 (1.0, 0.0, 0.0, 1.0, 320.0, 173.0) 100.0 56.0 0 0 (1.0, 0.0, 0.0, 1.0, 276.0, 101.0) 0 1 [(0.0, 0.0), (-39.000000000000028, 71.0)] 0 (1.0, 0.0, 0.0, 1.0, 321.0, 101.0) 0 1 [(0.0, 0.0), (50.0, 72.0)] 1 0 0 (1.0, 0.0, 0.0, 1.0, 610.0, 170.0) 100.0 56.0 0 0 0 0 (1.0, 0.0, 0.0, 1.0, 573.0, 323.0) 174.0 64.0 0 0 (1.0, 0.0, 0.0, 1.0, 660.0, 226.0) 0 1 [(0.0, 0.0), (0.64444444444552573, 97.0)] 0 0 (1.0, 0.0, 0.0, 1.0, 420.0, 201.0) 0 1 [(0.0, 0.0), (190.0, -1.9999999999999716)] 1 0 0 (1.0, 0.0, 0.0, 1.0, 246.0, 317.0) 119.0 71.0 0 0 (1.0, 0.0, 0.0, 1.0, 237.75999999999999, 228.0) 0 1 [(0.0, 0.0), (50.342189781021887, 89.0)] 0 0 (1.0, 0.0, 0.0, 1.0, 370.0, 229.0) 0 1 [(0.0, 0.0), (-59.41605839416053, 88.0)] 0 1 Relationship useCase 1 public 1 0 lower Integer 1 composite nestedInterface * public 1 importedMember * public ObjectFlow 0 1 1 target * public No inheritance, since it is already done by BehavioredClassifier. PowerTypes are not implemented yet. 0 ownerReturnParam 1 composite ownedDiagram * public LiteralSpecification ActivityFinalNode PowerTypes (1.0, 0.0, 0.0, 1.0, 46.0, 51.0) 239.0 86.0 0 importedPackage 1 public in direction ParameterDirectionKind nodeContents * 1 extensionLocation * public RedefinableElement owningProfile 1 composite ownedRule * public 0 * 0 CommunicationPath Classes 0 0 0 (1.0, 0.0, 0.0, 1.0, 332.0, 26.0) 156.0 58.0 0 1 0 0 (1.0, 0.0, 0.0, 1.0, 333.0, 159.0) 100.0 264.0 0 1 1 0 (1.0, 0.0, 0.0, 1.0, 796.0, 114.0) 294.0 294.0 0 0 (1.0, 0.0, 0.0, 1.0, 385.0, 93.0) 0 1 [(0.0, 0.0), (-1.7053025658242404e-13, 66.0)] 1 0 0 (1.0, 0.0, 0.0, 1.0, 1328.0, 354.0) 162.0 111.0 0 0 (1.0, 0.0, 0.0, 1.0, 433.0, 185.0) 0 1 [(0.0, 0.0), (363.0, 0.77926421404680468)] 0 0 0 0 (1.0, 0.0, 0.0, 1.0, 845.63333333333321, 19.199999999999989) 168.0 60.0 0 0 (1.0, 0.0, 0.0, 1.0, 930.33671497584578, 79.199999999999989) 0 1 [(0.0, 0.0), (0.32268540084851338, 34.799999999999997)] 0 (1.0, 0.0, 0.0, 1.0, 433.0, 247.0) 1 1 [(0.0, 0.0), (102.0, 0.0), (102.0, 50.0), (0.0, 50.0)] 0 0 (1.0, 0.0, 0.0, 1.0, 796.0, 236.90969899665549) 1 1 [(0.0, 0.0), (-164.0, 0.0), (-164.0, 53.096989966555213), (0.0, 53.096989966555213)] 0 0 (1.0, 0.0, 0.0, 1.0, 796.0, 330.32107023411368) 1 1 [(0.0, 0.0), (-164.0, 0.0), (-164.0, 53.096989966555213), (0.0, 53.096989966555213)] 0 0 (1.0, 0.0, 0.0, 1.0, 488.0, 51.519999999999982) 1 1 [(0.0, 0.0), (211.0, 0.0), (211.0, 77.229163879598673), (308.0, 77.229163879598673)] 0 0 (1.0, 0.0, 0.0, 1.0, 333.0, 217.88551185910802) 1 1 [(0.0, 0.0), (-196.0, 0.0), (-196.0, -173.32551185910802), (-1.0, -173.32551185910802)] 0 0 0 0 (1.0, 0.0, 0.0, 1.0, 331.90045248868779, 473.20000000000005) 178.0 133.0 0 0 (1.0, 0.0, 0.0, 1.0, 333.0, 368.88551185910796) 1 1 [(0.0, 0.0), (-214.0, 0.0), (-214.0, 160.88571808741614), (-1.0995475113122097, 160.88571808741614)] 0 1 0 0 (1.0, 0.0, 0.0, 1.0, 1392.0, 115.0) 205.548769629 128.0 0 0 (1.0, 0.0, 0.0, 1.0, 1090.0, 131.69899665551839) 0 1 [(0.0, 0.0), (302.0, 0.22662317919235875)] 0 0 0 0 (1.0, 0.0, 0.0, 1.0, 1361.4027149321266, 13.0) 129.0 50.0 0 0 0 0 (1.0, 0.0, 0.0, 1.0, 1498.1656184486374, 14.0) 156.0 58.0 0 0 (1.0, 0.0, 0.0, 1.0, 1428.532778280543, 63.0) 0 1 [(0.0, 0.0), (0.8837576931057356, 52.0)] 0 (1.0, 0.0, 0.0, 1.0, 1576.7984978422126, 72.0) 0 1 [(0.0, 0.0), (-20.581478846756681, 43.0)] 0 (1.0, 0.0, 0.0, 1.0, 1597.5487696286978, 179.56637168141589) 0 1 [(0.0, 0.0), (135.39229127503495, -17.673514538558692)] 0 0 (1.0, 0.0, 0.0, 1.0, 1090.0, 190.69565217391306) 0 1 [(0.0, 0.0), (302.0, 2.5853395616241244)] 0 0 0 0 (1.0, 0.0, 0.0, 1.0, 1393.156186612576, 249.0) 174.0 50.0 0 0 (1.0, 0.0, 0.0, 1.0, 1393.156186612576, 271.99999999999989) 0 1 [(0.0, 0.0), (-303.15618661257599, -0.67558528428082809)] 0 0 (1.0, 0.0, 0.0, 1.0, 1090.0, 322.45484949832775) 1 1 [(0.0, 0.0), (36.0, 0.0), (36.0, 51.130434782608745), (0.0, 51.130434782608745)] 0 0 0 0 (1.0, 0.0, 0.0, 1.0, 1732.9410609037327, 145.0) 100.0 50.0 0 (1.0, 0.0, 0.0, 1.0, 403.0, 97.0) 158.0 63.0 0 0 (1.0, 0.0, 0.0, 1.0, 1392.0, 237.49462365591398) 0 0 [(0.0, 0.0), (-302.0, 0.50537634408601662)] 0 composite implementation * public contract 1 public 1 provided * public 1 1 endType * public composite 1 input * 1 parameter * public 0 visibility VisibilityKind 1 public 0 0 namespace_ 1 public 0 importedProfile 1 public Permission true isUnique Boolean 0 1 isOrdered Boolean none composite slot * public mergedPackage 1 public FinalNode 1 0 default String 1 isAbstract Boolean 0 substitutingClassifier 1 public extensionPoint * public composite ownedMember * public composite 0 lowerValue 1 public Realization parameter 1 composite 0 specification 1 public isSubstitutable Boolean general 1 public language String Deployments composite generalization * public 0 Pin Generalization 0 value object false isQuery Boolean 0 preContext 1 public 0 decisionInput 1 0 ObjectNode CommonBehaviors 0 0 0 (1.0, 0.0, 0.0, 1.0, 85.0, 80.0) 156.0 72.0 0 1 0 0 (1.0, 0.0, 0.0, 1.0, 40.0, 195.0) 194.0 122.0 0 0 (1.0, 0.0, 0.0, 1.0, 162.24271844660194, 152.0) 0 1 [(0.0, 0.0), (-26.895779671091731, 43.0)] 0 0 0 (1.0, 0.0, 0.0, 1.0, 507.0, 75.0) 100.0 62.0 0 1 0 0 (1.0, 0.0, 0.0, 1.0, 478.0, 193.0) 174.0 338.0 0 0 (1.0, 0.0, 0.0, 1.0, 554.99999999999989, 137.0) 0 1 [(0.0, 0.0), (0.25714285714298057, 56.0)] 0 (1.0, 0.0, 0.0, 1.0, 234.0, 230.6529680365297) 0 1 [(0.0, 0.0), (244.0, -1.4711498547115411)] 0 0 (1.0, 0.0, 0.0, 1.0, 478.0, 450.5454545454545) 1 1 [(0.0, 0.0), (-152.0, 0.0), (-152.0, 50.0), (0.0, 50.0)] 0 1 0 0 (1.0, 0.0, 0.0, 1.0, 510.0, 599.0) 143.0 91.0 0 0 (1.0, 0.0, 0.0, 1.0, 566.1214285714284, 531.0) 0 1 [(0.0, 0.0), (0.22857142857151302, 68.0)] 1 0 0 (1.0, 0.0, 0.0, 1.0, 56.0, 364.87351778656125) 172.0 72.0 0 0 (1.0, 0.0, 0.0, 1.0, 228.0, 394.05265842152238) 0 1 [(0.0, 0.0), (250.0, -2.0526584215224375)] 0 composite value_ 1 body String public composite ownedOperation * public 0 Device none aggregation AggregationKind composite 0 metamodelReference * public 1 fileName String public 0 postContext 1 public composite precondition * public StructuralFeature 0 0 interface_ 1 NamedElement addition 1 public subsets ownedElement 1 0 owner 1 public 1 0 context 1 public raisedException * public UML UseCase LiteralNull composite returnResult * public ActivityNode InputPin Manifestation composite ownedStereotype * public composite 1 ownedMember * public ControlNode none 0 composite 0 defaultValue 1 public composite ownedMember * public Component composite nestedClassifier * public Interface composite elementImport * public 0 composite ownedAttribute * public 1 Element UseCases constrainedElement * public 0 subsets target OpaqueExpression Dependency composite 0 defaultValue 1 public PowerTypes 0 composite formalParameter * public includingCase 1 public Features 0 0 0 (1.0, 0.0, 0.0, 1.0, 357.0, 58.0) 186.0 58.0 0 0 0 0 (1.0, 0.0, 0.0, 1.0, 39.0, 202.0) 156.0 58.0 0 1 0 0 (1.0, 0.0, 0.0, 1.0, 338.0, 198.0) 197.0 71.0 0 0 (1.0, 0.0, 0.0, 1.0, 195.0, 232.15999999999994) 0 1 [(0.0, 0.0), (143.0, 0.29588235294119158)] 0 0 (1.0, 0.0, 0.0, 1.0, 452.54098360655746, 116.0) 0 1 [(0.0, 0.0), (-14.292364822027082, 82.0)] 1 0 0 (1.0, 0.0, 0.0, 1.0, 180.0, 412.0) 223.0 71.0 0 0 0 0 (1.0, 0.0, 0.0, 1.0, 224.0, 297.0) 140.0 50.0 0 0 (1.0, 0.0, 0.0, 1.0, 278.7071428571428, 347.0) 0 1 [(0.0, 0.0), (2.2928571428572013, 65.0)] 0 (1.0, 0.0, 0.0, 1.0, 399.20441988950301, 269.0) 0 1 [(0.0, 0.0), (-77.682680759068148, 143.0)] 1 0 0 (1.0, 0.0, 0.0, 1.0, 483.0, 413.0) 172.0 250.0 0 0 0 0 (1.0, 0.0, 0.0, 1.0, 575.45664739884387, 284.0) 121.0 50.0 0 0 (1.0, 0.0, 0.0, 1.0, 630.83054196927037, 334.0) 0 1 [(0.0, 0.0), (-37.187267115469126, 79.0)] 0 (1.0, 0.0, 0.0, 1.0, 464.00000000000028, 269.0) 0 1 [(0.0, 0.0), (83.999999999999716, 144.0)] 1 0 0 (1.0, 0.0, 0.0, 1.0, 664.0, 71.0) 218.0 131.0 0 1 0 0 (1.0, 0.0, 0.0, 1.0, 1035.0, 413.0) 294.0 184.0 0 0 0 0 (1.0, 0.0, 0.0, 1.0, 1019.045871559633, 281.0) 140.0 50.0 0 0 (1.0, 0.0, 0.0, 1.0, 1084.0, 331.0) 0 1 [(0.0, 0.0), (50.107913669064601, 82.0)] 0 (1.0, 0.0, 0.0, 1.0, 1274.1961073184823, 331.0) 0 1 [(0.0, 0.0), (-52.336395088266499, 82.0)] 0 (1.0, 0.0, 0.0, 1.0, 655.0, 447.0) 0 1 [(0.0, 0.0), (380.0, -1.0)] 0 0 (1.0, 0.0, 0.0, 1.0, 655.0, 504.0) 0 1 [(0.0, 0.0), (380.0, 0.0)] 0 0 (1.0, 0.0, 0.0, 1.0, 655.0, 565.0) 0 1 [(0.0, 0.0), (380.0, 0.0)] 0 0 (1.0, 0.0, 0.0, 1.0, 655.0, 638.0) 0 1 [(0.0, 0.0), (305.625, 2.7759878670707394)] 0 0 0 0 (1.0, 0.0, 0.0, 1.0, 1502.1196581196582, 492.0) 174.0 82.0 0 0 (1.0, 0.0, 0.0, 1.0, 1329.0, 514.0) 0 1 [(0.0, 0.0), (173.1196581196582, -0.3529227883763042)] 0 0 0 0 (1.0, 0.0, 0.0, 1.0, 66.0, 297.0) 141.0 50.0 0 0 (1.0, 0.0, 0.0, 1.0, 137.52173913043475, 347.0) 0 1 [(0.0, 0.0), (83.36231884057969, 65.0)] 0 0 0 (1.0, 0.0, 0.0, 1.0, 1179.7485893246762, 281.0) 174.876410675 50.0 0 0 0 0 (1.0, 0.0, 0.0, 1.0, 960.625, 624.47079037800688) 100.0 50.0 0 Association 0 inout classifier * public Include True isIndirectlyInstantiated Boolean public 0 Interfaces annotatedElement * public Profiles 0 0 0 (1.0, 0.0, 0.0, 1.0, 41.0, 121.0) 100.0 64.0 0 0 0 0 (1.0, 0.0, 0.0, 1.0, 350.0, 13.0) 151.0 51.0 0 0 0 0 (1.0, 0.0, 0.0, 1.0, 381.0, 484.0) 146.0 50.0 0 0 0 0 (1.0, 0.0, 0.0, 1.0, 545.0, 120.0) 100.0 62.0 0 1 0 0 (1.0, 0.0, 0.0, 1.0, 43.0, 290.0) 100.0 236.0 0 0 (1.0, 0.0, 0.0, 1.0, 87.000000000000028, 185.0) 0 1 [(0.0, 0.0), (0.99999999999994316, 105.0)] 1 0 0 (1.0, 0.0, 0.0, 1.0, 346.0, 121.0) 168.0 56.0 0 0 (1.0, 0.0, 0.0, 1.0, 141.0, 151.72) 0 1 [(0.0, 0.0), (205.0, 0.28000000000000114)] 0 0 (1.0, 0.0, 0.0, 1.0, 408.36363636363649, 177.0) 0 1 [(0.0, 0.0), (-0.36363636363648766, 132.0), (-265.36363636363649, 133.0)] 0 1 0 0 (1.0, 0.0, 0.0, 1.0, 540.0, 346.0) 118.0 62.0 0 0 (1.0, 0.0, 0.0, 1.0, 591.00000000000011, 182.0) 0 1 [(0.0, 0.0), (-0.26000000000033197, 164.0)] 0 0 0 (1.0, 0.0, 0.0, 1.0, 824.8669136550875, 22.0) 158.125760338 50.0 0 1 0 0 (1.0, 0.0, 0.0, 1.0, 821.0, 121.0) 166.0 71.0 0 0 (1.0, 0.0, 0.0, 1.0, 645.0, 151.0) 0 1 [(0.0, 0.0), (176.0, -0.22580645161289681)] 0 0 (1.0, 0.0, 0.0, 1.0, 898.85801593364158, 72.0) 0 1 [(0.0, 0.0), (0.00889772144591916, 49.0)] 1 0 0 (1.0, 0.0, 0.0, 1.0, 1070.0, 120.0) 136.0 56.0 0 0 0 0 (1.0, 0.0, 0.0, 1.0, 1072.0, 20.0) 131.866913655 58.0 0 0 (1.0, 0.0, 0.0, 1.0, 1137.9273592177235, 78.0) 0 1 [(0.0, 0.0), (-0.56287323641458897, 42.0)] 0 (1.0, 0.0, 0.0, 1.0, 987.0, 146.2258064516129) 0 1 [(0.0, 0.0), (83.0, -0.22580645161289681)] 0 0 (1.0, 0.0, 0.0, 1.0, 1124.0, 176.0) 0 1 [(0.0, 0.0), (2.0, 195.0), (-466.0, 197.6785714285715)] 0 0 (1.0, 0.0, 0.0, 1.0, 143.0, 369.0) 0 1 [(0.0, 0.0), (397.0, 3.5714285714284983)] 0 0 0 0 (1.0, 0.0, 0.0, 1.0, 382.8901098901099, 415.0) 145.10989011 54.0 0 0 (1.0, 0.0, 0.0, 1.0, 143.0, 435.0) 0 1 [(0.0, 0.0), (239.8901098901099, -0.55999999999994543)] 0 0 (1.0, 0.0, 0.0, 1.0, 143.0, 501.0) 0 1 [(0.0, 0.0), (238.0, -1.3199999999999932)] 0 0 (1.0, 0.0, 0.0, 1.0, 423.45204138702468, 64.0) 0 1 [(0.0, 0.0), (-1.0884050233881339, 57.0)] source 1 Profiles LiteralInteger 0 importingNamespace 1 public 0 ProfileApplication composite 1 ownedElement * public composite ownedUseCase * public ExtensionPoint 0 class_ 1 public edgeContents * composite group * false isAbstract Boolean Expressions 1 0 0 (1.0, 0.0, 0.0, 1.0, 361.14285714285717, 145.57142857142858) 174.0 67.0 0 1 0 0 (1.0, 0.0, 0.0, 1.0, 97.142857142857167, 318.57142857142844) 170.0 79.4285714286 0 1 0 0 (1.0, 0.0, 0.0, 1.0, 360.14285714285717, 417.57142857142844) 180.0 83.0 0 1 0 0 (1.0, 0.0, 0.0, 1.0, 534.14285714285711, 317.57142857142844) 141.0 80.0 0 0 (1.0, 0.0, 0.0, 1.0, 361.14285714285717, 207.00000000000006) 0 1 [(0.0, 0.0), (-175.0, 111.57142857142838)] 0 (1.0, 0.0, 0.0, 1.0, 451.704260651629, 212.57142857142858) 0 1 [(0.0, 0.0), (0.48405103668278571, 204.99999999999986)] 0 (1.0, 0.0, 0.0, 1.0, 535.14285714285711, 212.57142857142858) 0 1 [(0.0, 0.0), (64.000000000000114, 104.99999999999986)] 0 0 0 (1.0, 0.0, 0.0, 1.0, 825.14285714285711, 322.57142857142844) 198.0 50.0 0 0 (1.0, 0.0, 0.0, 1.0, 675.14285714285711, 343.57142857142844) 0 1 [(0.0, 0.0), (150.0, 0.99999999999977263)] 0 1 0 0 (1.0, 0.0, 0.0, 1.0, 140.0, 607.0) 142.0 71.0 0 1 0 0 (1.0, 0.0, 0.0, 1.0, 234.85714285714289, 689.85714285714278) 137.0 71.0 0 1 0 0 (1.0, 0.0, 0.0, 1.0, 377.28571428571428, 607.0) 126.0 71.0 0 1 0 0 (1.0, 0.0, 0.0, 1.0, 447.71428571428572, 691.71428571428578) 208.0 71.0 0 1 0 0 (1.0, 0.0, 0.0, 1.0, 568.14285714285711, 606.0) 108.0 67.0 0 0 (1.0, 0.0, 0.0, 1.0, 378.11363636363632, 500.57142857142844) 0 1 [(0.0, 0.0), (-170.39935064935062, 106.42857142857156)] 0 (1.0, 0.0, 0.0, 1.0, 414.63961038961031, 500.57142857142844) 0 1 [(0.0, 0.0), (-112.63961038961037, 189.28571428571433)] 0 (1.0, 0.0, 0.0, 1.0, 445.3214285714285, 500.57142857142844) 0 1 [(0.0, 0.0), (-13.321428571428612, 106.42857142857156)] 0 (1.0, 0.0, 0.0, 1.0, 489.15259740259751, 500.57142857142844) 0 1 [(0.0, 0.0), (59.990259740259489, 191.14285714285734)] 0 (1.0, 0.0, 0.0, 1.0, 540.14285714285711, 500.57142857142844) 0 1 [(0.0, 0.0), (79.476635514018881, 105.42857142857156)] 0 0 0 (1.0, 0.0, 0.0, 1.0, 394.52173913043475, 23.0) 140.0 50.0 0 0 (1.0, 0.0, 0.0, 1.0, 451.0, 73.0) 0 1 [(0.0, 0.0), (0.99999999999994316, 72.571428571428584)] 0 (1.0, 0.0, 0.0, 1.0, 97.142857142857167, 369.1761670185316) 1 1 [(0.0, 0.0), (-72.142857142857167, 0.0), (-72.142857142857167, -193.1761670185316), (264.0, -193.1761670185316)] 0 1 0 0 (1.0, 0.0, 0.0, 1.0, 285.0, 320.0) 147.0 77.0 0 0 (1.0, 0.0, 0.0, 1.0, 416.96240601503769, 212.57142857142858) 0 1 [(0.0, 0.0), (-60.956941534163434, 107.42857142857142)] (1.0, 0.0, 0.0, 1.0, 620.0, 447.0) 143.0 52.0 0 0 (1.0, 0.0, 0.0, 1.0, 540.14285714285711, 457.25396627770527) 0 1 [(0.0, 0.0), (79.85714285714289, 11.025535667904364)] BasicBehaviors composite mapping 1 public Usage 0 name String 1 public subsettedProperty * public true isOrdered Boolean 0 0 association 1 public 0 BasicActivities 0 1 member * public TypedElement composite ownedAttribute * public 0 type 1 public composite realization * public composite substitution * public Of this diagram only this inheritance relationship is modeled. This diagram is not complete. 1 PackageableElement 0 * 0 ElementImport 1 0 context_ 1 subject * public supplierDependency * public visibility VisibilityKind composite ownedOperation * public 0 owningAssociation 1 public effect String 0 0 0 false isReadOnly Boolean Constraints 0 0 0 (1.0, 0.0, 0.0, 1.0, 497.0, 45.0) 195.0 62.0 0 1 0 0 (1.0, 0.0, 0.0, 1.0, 521.0, 173.0) 149.0 111.0 0 0 (1.0, 0.0, 0.0, 1.0, 591.97872340425511, 107.0) 0 1 [(0.0, 0.0), (-1.9787234042551063, 66.0)] 0 0 0 (1.0, 0.0, 0.0, 1.0, 89.0, 174.0) 121.0 92.0 0 0 (1.0, 0.0, 0.0, 1.0, 210.0, 196.0) 0 1 [(0.0, 0.0), (311.0, 0.0)] 0 0 (1.0, 0.0, 0.0, 1.0, 210.0, 250.0) 0 1 [(0.0, 0.0), (311.0, 0.99999999999997158)] 0 0 0 0 (1.0, 0.0, 0.0, 1.0, 879.55555555555554, 177.0) 100.0 50.0 0 0 0 0 (1.0, 0.0, 0.0, 1.0, 879.55555555555554, 234.0) 174.0 50.0 0 0 (1.0, 0.0, 0.0, 1.0, 670.0, 196.99999999999997) 0 1 [(0.0, 0.0), (209.55555555555554, 1.0000000000000284)] 0 0 (1.0, 0.0, 0.0, 1.0, 670.0, 250.99999999999997) 0 1 [(0.0, 0.0), (209.55555555555554, 2.8421709430404007e-14)] 0 public visibility VisibilityKind public InstanceSpecification composite postcondition * public composite ownedEnd * public 0 bodyContext 1 public shared Components 0 0 0 language String 1 0 0 class_ 1 public ExtensionEnd 0 enumeration 1 public redefinedClassifier * public Enumeration 0 1 isUnique Boolean 1 redefinedElement * public 0 package 1 public 0 specific 1 public composite operand * public redefinedOperation * public Slot extendedCase 1 public composite ownedClassifier * public ExecutableNode method * 0 clientDependency * public visibility VisibilityKind 0 0 private realizingClassifier 1 public 0 activity 1 composite packageImport * public 0 0 1 1 extension * public InitialNode 0 0 classifier 1 composite 2 memberEnd * public Control nodes 0 0 0 (1.0, 0.0, 0.0, 1.0, 307.0, 60.0) 129.0 50.0 0 1 0 0 (1.0, 0.0, 0.0, 1.0, 22.0, 200.0) 115.0 54.0 0 1 0 0 (1.0, 0.0, 0.0, 1.0, 144.0, 200.0) 107.0 54.0 0 1 0 0 (1.0, 0.0, 0.0, 1.0, 504.0, 200.0) 121.0 54.0 0 1 0 0 (1.0, 0.0, 0.0, 1.0, 656.0, 239.0) 138.0 54.0 0 1 0 0 (1.0, 0.0, 0.0, 1.0, 43.0, 355.0) 165.0 54.0 0 0 0 0 (1.0, 0.0, 0.0, 1.0, 672.0, 372.0) 129.0 72.0 0 0 (1.0, 0.0, 0.0, 1.0, 168.0, 254.0) 0 0 [(0.0, 0.0), (-43.870229007633611, 100.0), (-41.870229007633611, 101.0)] 0 (1.0, 0.0, 0.0, 1.0, 728.0, 293.0) 0 1 [(0.0, 0.0), (2.1081081081083539, 79.0)] 0 0 (1.0, 0.0, 0.0, 1.0, 372.99999999999994, 110.0) 1 0 [(0.0, 0.0), (0.0, 41.0), (-299.99999999999994, 41.0), (-299.99999999999994, 90.0)] 0 (1.0, 0.0, 0.0, 1.0, 372.99999999999994, 110.0) 1 0 [(0.0, 0.0), (0.0, 41.0), (-175.49999999999994, 41.0), (-175.49999999999994, 90.0)] 0 (1.0, 0.0, 0.0, 1.0, 372.99999999999994, 110.0) 1 0 [(0.0, 0.0), (0.0, 41.0), (181.00000000000017, 41.0), (181.00000000000017, 90.0)] 0 (1.0, 0.0, 0.0, 1.0, 372.99999999999994, 110.0) 1 0 [(0.0, 0.0), (0.0, 41.0), (334.00000000000006, 41.0), (334.00000000000006, 129.0)] 1 0 0 (1.0, 0.0, 0.0, 1.0, 232.0, 355.0) 138.0 54.0 0 0 (1.0, 0.0, 0.0, 1.0, 223.0, 254.0) 0 0 [(0.0, 0.0), (58.0, 101.0)] 1 0 0 (1.0, 0.0, 0.0, 1.0, 366.0, 270.0) 262.0 64.0 0 0 (1.0, 0.0, 0.0, 1.0, 372.99999999999994, 110.0) 1 0 [(0.0, 0.0), (0.0, 41.0), (86.000000000000057, 41.0), (86.000000000000057, 160.0)] 1 0 0 (1.0, 0.0, 0.0, 1.0, 264.0, 200.0) 101.0 57.0 0 0 (1.0, 0.0, 0.0, 1.0, 372.99999999999994, 110.0) 1 0 [(0.0, 0.0), (0.0, 41.0), (-52.999999999999943, 41.0), (-52.999999999999943, 90.0)] Stereotype Diagram 1 0 superGroup 1 composite 0 ownedEnd 1 public 0 class_ 1 public DataType Dependencies 0 activity 1 value Boolean 0 DataTypes 0 0 0 (1.0, 0.0, 0.0, 1.0, 96.0, 47.0) 156.0 58.0 0 1 0 0 (1.0, 0.0, 0.0, 1.0, 99.0, 144.0) 141.0 139.0 0 1 0 0 (1.0, 0.0, 0.0, 1.0, 23.0, 396.0) 135.0 55.0 0 0 (1.0, 0.0, 0.0, 1.0, 123.0, 283.0) 0 1 [(0.0, 0.0), (0.0, 113.0)] 1 0 0 (1.0, 0.0, 0.0, 1.0, 197.0, 396.0) 129.0 55.0 0 0 (1.0, 0.0, 0.0, 1.0, 215.00000000000003, 283.0) 0 1 [(0.0, 0.0), (0.0, 113.0)] 0 0 0 (1.0, 0.0, 0.0, 1.0, 613.0, 137.0) 183.0 71.0 0 0 (1.0, 0.0, 0.0, 1.0, 240.0, 162.00000000000003) 0 1 [(0.0, 0.0), (373.0, 0.55999999999991701)] 0 0 0 0 (1.0, 0.0, 0.0, 1.0, 617.0, 229.0) 171.0 64.0 0 0 (1.0, 0.0, 0.0, 1.0, 240.0, 257.0) 0 1 [(0.0, 0.0), (377.0, 0.15999999999996817)] 0 0 (1.0, 0.0, 0.0, 1.0, 168.17910447761199, 105.0) 0 1 [(0.0, 0.0), (-0.17910447761198611, 39.0)] 0 0 0 (1.0, 0.0, 0.0, 1.0, 583.0, 321.0) 198.0 50.0 0 1 0 0 (1.0, 0.0, 0.0, 1.0, 594.0, 423.0) 178.0 59.0 0 0 (1.0, 0.0, 0.0, 1.0, 679.9795918367347, 371.0) 0 1 [(0.0, 0.0), (0.020408163265301482, 52.0)] 0 (1.0, 0.0, 0.0, 1.0, 326.0, 417.99999999999983) 1 1 [(0.0, 0.0), (167.0, 0.0), (167.0, 28.000000000000284), (268.0, 28.000000000000284)] 0 ActivityEdge true isLeaf Boolean value UnlimitedNatural AggregationKind 0 0 composite ownedAttribute * public 1 0 default String 1 0 composite 0 composite include * public raisedException * public composite packageExtension * public 0 operation 1 public 1 metaclass 1 public Artifact in 0 subject 1 public composite formalParameter * public composite 0 bodyCondition 1 public 0 1 inheritedMember * public LiteralUnlimitedNatural Operations 0 0 0 (1.0, 0.0, 0.0, 1.0, 111.0, 62.0) 172.0 58.0 0 1 1 0 (1.0, 0.0, 0.0, 1.0, 90.0, 195.0) 238.0 419.0 0 0 (1.0, 0.0, 0.0, 1.0, 192.25153374233116, 120.0) 0 1 [(0.0, 0.0), (1.1691711034838193, 75.0)] 0 0 0 (1.0, 0.0, 0.0, 1.0, 729.0, 212.0) 223.0 50.0 0 0 (1.0, 0.0, 0.0, 1.0, 729.0, 245.0) 1 1 [(0.0, 0.0), (-121.0, 0.0), (-121.0, -20.0), (-401.0, -20.0)] 0 0 0 0 (1.0, 0.0, 0.0, 1.0, 729.42592592592587, 276.0) 112.0 177.0 0 0 (1.0, 0.0, 0.0, 1.0, 328.0, 315.0) 0 1 [(0.0, 0.0), (401.42592592592587, -2.9999999999999432)] 0 0 (1.0, 0.0, 0.0, 1.0, 328.0, 370.0) 0 1 [(0.0, 0.0), (401.42592592592587, -3.0)] 0 0 (1.0, 0.0, 0.0, 1.0, 328.0, 427.99999999999994) 0 1 [(0.0, 0.0), (401.42592592592587, -2.9999999999999432)] 0 0 (1.0, 0.0, 0.0, 1.0, 328.0, 492.0) 0 1 [(0.0, 0.0), (403.9681697612732, -3.0)] 0 0 (1.0, 0.0, 0.0, 1.0, 328.0, 551.0) 1 1 [(0.0, 0.0), (160.0, 0.0), (160.0, 48.0), (0.0, 48.0)] 0 0 0 0 (1.0, 0.0, 0.0, 1.0, 731.9681697612732, 474.0) 110.031830239 75.2181818182 0 0 (1.0, 0.0, 0.0, 1.0, 328.0, 532.0) 0 1 [(0.0, 0.0), (403.9681697612732, -3.0000000000001137)] 0 false isDerivedUnion Boolean 0 alias String 1 instance 1 public composite extend * public Flows 0 0 0 (1.0, 0.0, 0.0, 1.0, 66.0, 121.0) 132.0 94.0 0 0 0 0 (1.0, 0.0, 0.0, 1.0, 50.0, 265.0) 174.0 64.0 0 1 0 0 (1.0, 0.0, 0.0, 1.0, 371.0, 121.0) 127.0 189.0 0 0 (1.0, 0.0, 0.0, 1.0, 224.0, 293.16000000000003) 0 1 [(0.0, 0.0), (147.0, -0.16000000000002501)] 0 0 (1.0, 0.0, 0.0, 1.0, 198.0, 142.0) 0 1 [(0.0, 0.0), (173.0, -1.0)] 0 0 (1.0, 0.0, 0.0, 1.0, 198.0, 195.0) 0 1 [(0.0, 0.0), (173.0, 0.0)] 0 0 0 0 (1.0, 0.0, 0.0, 1.0, 346.0, 24.0) 186.0 72.0 0 0 (1.0, 0.0, 0.0, 1.0, 431.0, 96.0) 0 1 [(0.0, 0.0), (-1.0, 25.0)] 0 0 0 (1.0, 0.0, 0.0, 1.0, 772.0, 139.0) 115.0 88.0 0 0 (1.0, 0.0, 0.0, 1.0, 498.0, 171.0) 0 1 [(0.0, 0.0), (274.0, -0.32000000000005002)] 0 0 (1.0, 0.0, 0.0, 1.0, 498.0, 249.0) 1 1 [(0.0, 0.0), (124.0, 0.0), (124.0, 42.0), (0.0, 42.0)] 0 1 0 0 (1.0, 0.0, 0.0, 1.0, 433.0, 420.0) 118.0 56.0 0 1 0 0 (1.0, 0.0, 0.0, 1.0, 295.0, 421.0) 125.0 56.0 0 0 (1.0, 0.0, 0.0, 1.0, 388.26213592233012, 310.0) 0 1 [(0.0, 0.0), (-0.76213592233011695, 111.0)] 0 (1.0, 0.0, 0.0, 1.0, 456.00000000000011, 310.0) 0 1 [(0.0, 0.0), (-1.7053025658242404e-13, 110.0)] MultiplicityElement 1 0 upper UnlimitedNatural 1 Groups 0 0 0 (1.0, 0.0, 0.0, 1.0, 313.0, 51.0) 100.0 62.0 0 1 0 0 (1.0, 0.0, 0.0, 1.0, 294.0, 186.0) 137.0 144.0 0 0 0 0 (1.0, 0.0, 0.0, 1.0, 707.0, 172.0) 115.0 88.0 0 0 (1.0, 0.0, 0.0, 1.0, 367.0, 113.0) 0 1 [(0.0, 0.0), (-0.10091743119249941, 73.0)] 0 (1.0, 0.0, 0.0, 1.0, 294.0, 209.14285714285714) 1 1 [(0.0, 0.0), (-175.0, 0.0), (-175.0, 46.857142857142975), (0.0, 46.857142857142975)] 0 0 (1.0, 0.0, 0.0, 1.0, 431.0, 213.0) 0 1 [(0.0, 0.0), (276.0, -0.51999999999995339)] 0 0 0 0 (1.0, 0.0, 0.0, 1.0, 566.0, 300.0) 129.0 50.0 0 0 0 0 (1.0, 0.0, 0.0, 1.0, 20.0, 299.0) 127.0 50.0 0 0 (1.0, 0.0, 0.0, 1.0, 294.0, 321.0) 0 1 [(0.0, 0.0), (-147.0, 5.6843418860808015e-14)] 0 0 (1.0, 0.0, 0.0, 1.0, 431.0, 322.0) 0 1 [(0.0, 0.0), (135.0, 0.0)] 0 0 Root 0 0 0 (1.0, 0.0, 0.0, 1.0, 301.0, 158.0) 122.0 123.0 0 0 (1.0, 0.0, 0.0, 1.0, 423.0, 186.55357142857139) 1 1 [(0.0, 0.0), (133.0, 0.0), (133.0, -72.553571428571388), (-32.264462809917347, -72.553571428571388), (-32.264462809917347, -28.553571428571388)] 0 0 0 0 (1.0, 0.0, 0.0, 1.0, 200.0, 378.0) 155.0 50.0 0 0 (1.0, 0.0, 0.0, 1.0, 339.31404958677695, 281.0) 0 1 [(0.0, 0.0), (-47.314049586776946, 97.0)] 0 (1.0, 0.0, 0.0, 1.0, 200.0, 399.3125) 1 1 [(0.0, 0.0), (-64.0, 0.0), (-64.0, -123.80357142857144), (101.0, -123.80357142857144)] 0 1 0 0 (1.0, 0.0, 0.0, 1.0, 392.0, 377.0) 134.0 71.0 0 0 (1.0, 0.0, 0.0, 1.0, 393.76033057851248, 281.0) 0 1 [(0.0, 0.0), (58.239669421487463, 96.0)] 0 (1.0, 0.0, 0.0, 1.0, 423.0, 249.0) 1 1 [(0.0, 0.0), (270.0, 0.0), (270.0, 157.953125), (103.0, 157.953125)] 0 0 0 0 (1.0, 0.0, 0.0, 1.0, 166.0, 508.0) 219.0 50.0 0 0 (1.0, 0.0, 0.0, 1.0, 277.0, 428.0) 0 1 [(0.0, 0.0), (-5.6843418860808015e-14, 80.0)] 0 (1.0, 0.0, 0.0, 1.0, 166.0, 523.09375) 1 1 [(0.0, 0.0), (-101.0, 0.0), (-101.0, -301.73011363636363), (135.0, -301.73011363636363)] 0 0 (1.0, 0.0, 0.0, 1.0, 166.0, 540.34374999999989) 1 1 [(0.0, 0.0), (-141.0, 0.0), (-141.0, -377.95089285714278), (135.0, -377.95089285714278)] 0 0 importingNamespace 1 public 0 0 ActivityParameterNode 0 datatype 1 public PrimitiveType composite 0 value 1 InstanceValue 0 package 1 public DecisionNode mergingPackage 1 public Presentations are what you see in a diagram. 0 Profile 1 0 lower Integer 1 0 composite 0 constraint 1 public 0 Activity false isDerived Boolean redefinedInterface * public 1 isComposite Boolean 1 DirectedRelationship 1 Classifier Node 0 0 0 (1.0, 0.0, 0.0, 1.0, 217.0, 57.0) 100.0 62.0 0 1 0 0 (1.0, 0.0, 0.0, 1.0, 218.0, 210.0) 100.0 59.0 0 0 (1.0, 0.0, 0.0, 1.0, 260.99999999999994, 119.0) 0 1 [(0.0, 0.0), (5.6843418860808015e-14, 91.0)] 1 0 0 (1.0, 0.0, 0.0, 1.0, 121.0, 377.0) 100.0 59.0 0 0 (1.0, 0.0, 0.0, 1.0, 249.00000000000003, 269.0) 0 1 [(0.0, 0.0), (-77.000000000000028, 108.0)] 1 0 0 (1.0, 0.0, 0.0, 1.0, 254.0, 378.0) 206.0 57.0 0 0 (1.0, 0.0, 0.0, 1.0, 279.0, 269.0) 0 1 [(0.0, 0.0), (72.0, 109.0)] 0 0 0 (1.0, 0.0, 0.0, 1.0, 565.15573770491801, 111.03846153846155) 157.0 72.0 0 1 0 0 (1.0, 0.0, 0.0, 1.0, 550.0, 242.0) 186.0 59.0 0 0 (1.0, 0.0, 0.0, 1.0, 640.56255346641365, 183.03846153846155) 0 1 [(0.0, 0.0), (0.43744653358646701, 58.961538461538453)] (1.0, 0.0, 0.0, 1.0, 551.0, 359.0) 326.0 67.0 0 0 (1.0, 0.0, 0.0, 1.0, 644.0, 359.0) 0 1 [(0.0, 0.0), (-1.0, -58.0)] 0 (1.0, 0.0, 0.0, 1.0, 236.0, 210.0) 0 0 [(0.0, 0.0), (0.0, -57.0), (-127.0, -55.0), (-127.0, 35.0), (-18.0, 34.0)] 0 composite guard 1 1 feature * typeValue is added for Gaphor target 1 isReentrant Boolean inGroup * 0 datatype 1 public composite metaclassReference * public 0 implementatingClassifier 1 public ValueSpecification importedElement 1 public 0 LiteralBoolean composite 0 appliedProfile * public 1 supplier * public outgoing * Interfaces 0 0 0 (1.0, 0.0, 0.0, 1.0, 432.0, 48.0) 156.0 72.0 0 1 0 0 (1.0, 0.0, 0.0, 1.0, 458.0, 173.0) 102.0 143.0 0 0 (1.0, 0.0, 0.0, 1.0, 507.21428571428578, 120.0) 0 1 [(0.0, 0.0), (0.27571428571400247, 53.0)] 0 0 0 (1.0, 0.0, 0.0, 1.0, 131.0, 169.0) 135.0 50.0 0 0 0 0 (1.0, 0.0, 0.0, 1.0, 133.0, 232.0) 100.0 88.0 0 1 0 0 (1.0, 0.0, 0.0, 1.0, 434.0, 429.0) 153.0 56.0 0 0 0 0 (1.0, 0.0, 0.0, 1.0, 178.0, 421.0) 115.0 62.0 0 0 0 0 (1.0, 0.0, 0.0, 1.0, 402.0, 586.0) 194.0 62.0 0 0 (1.0, 0.0, 0.0, 1.0, 266.0, 193.0) 0 1 [(0.0, 0.0), (192.0, 0.0)] 0 0 (1.0, 0.0, 0.0, 1.0, 233.0, 268.95999999999981) 0 1 [(0.0, 0.0), (225.0, 0.040000000000190994)] 0 0 (1.0, 0.0, 0.0, 1.0, 560.0, 187.0) 0 1 [(0.0, 0.0), (159.0, 0.0), (159.0, 43.0), (0.0, 42.999999999999915)] 0 0 (1.0, 0.0, 0.0, 1.0, 560.0, 259.00000000000006) 0 1 [(0.0, 0.0), (161.0, 0.0), (161.0, 35.999999999999943), (0.0, 35.999999999999886)] 0 0 (1.0, 0.0, 0.0, 1.0, 505.99999999999983, 316.0) 0 1 [(0.0, 0.0), (1.7053025658242404e-13, 113.0)] 0 0 (1.0, 0.0, 0.0, 1.0, 293.0, 454.21428571428572) 0 1 [(0.0, 0.0), (141.0, 0.78571428571427759)] 0 (1.0, 0.0, 0.0, 1.0, 496.0, 586.0) 0 1 [(0.0, 0.0), (0.0, -101.0)] 0 (1.0, 0.0, 0.0, 1.0, 698.0, 507.0) 161.0 40.0 0 (1.0, 0.0, 0.0, 1.0, 643.0, 583.0) 112.0 40.0 0 (1.0, 0.0, 0.0, 1.0, 655.0, 340.0) 110.0 42.0 0 Implementation Class contract 1 public out Packages 0 0 0 (1.0, 0.0, 0.0, 1.0, 74.0, 67.0) 121.0 50.0 0 0 0 0 (1.0, 0.0, 0.0, 1.0, 228.0, 68.0) 197.0 50.0 0 1 0 0 (1.0, 0.0, 0.0, 1.0, 133.0, 194.0) 154.0 354.0 0 0 (1.0, 0.0, 0.0, 1.0, 138.0, 117.0) 0 1 [(0.0, 0.0), (20.000000000000028, 77.0)] 0 (1.0, 0.0, 0.0, 1.0, 275.0, 118.0) 0 1 [(0.0, 0.0), (-20.0, 76.0)] 0 (1.0, 0.0, 0.0, 1.0, 287.0, 194.0) 1 1 [(0.0, 0.0), (392.0, 0.0), (392.0, -104.0), (138.0, -104.0)] 0 0 (1.0, 0.0, 0.0, 1.0, 287.0, 277.0) 0 1 [(0.0, 0.0), (337.39606741573039, -2.4004860923575961)] 0 0 0 0 (1.0, 0.0, 0.0, 1.0, 580.32510288065851, 318.0) 198.0 50.0 0 1 0 0 (1.0, 0.0, 0.0, 1.0, 606.0, 396.0) 146.0 83.0 0 0 (1.0, 0.0, 0.0, 1.0, 674.96137348400703, 368.0) 0 1 [(0.0, 0.0), (1.9822884878240075, 28.0)] 0 (1.0, 0.0, 0.0, 1.0, 287.0, 415.0) 0 1 [(0.0, 0.0), (319.0, 0.0)] 0 0 (1.0, 0.0, 0.0, 1.0, 606.0, 461.21428571428623) 0 1 [(0.0, 0.0), (-319.0, -0.21428571428538135)] 0 0 (1.0, 0.0, 0.0, 1.0, 287.0, 513.0) 1 1 [(0.0, 0.0), (160.0, 0.0), (160.0, 78.0), (-60.999999999999915, 78.0), (-60.999999999999915, 35.0)] 0 0 0 0 (1.0, 0.0, 0.0, 1.0, 624.39606741573039, 250.91304347826087) 100.0 50.0 0 1 BehavioredClassifier Node Parameter Presentation 1 required * public 0 1 0 opposite 1 public Operation ControlFlow Property 1 Namespace false isDerived Boolean Substitution value is added specially for Gaphor 1 0 upper UnlimitedNatural 1 1 general * public return redefinedElement * composite 1 subgroup * composite 0 typeValue 1 public Instances 0 0 0 (1.0, 0.0, 0.0, 1.0, 65.0, 86.0) 195.0 62.0 0 1 0 0 (1.0, 0.0, 0.0, 1.0, 61.0, 218.0) 198.0 204.0 0 1 0 0 (1.0, 0.0, 0.0, 1.0, 504.0, 256.0) 100.0 100.0 0 0 (1.0, 0.0, 0.0, 1.0, 259.0, 247.99999999999991) 1 1 [(0.0, 0.0), (163.0, 0.0), (163.0, 25.000000000000085), (245.0, 25.000000000000085)] 0 0 (1.0, 0.0, 0.0, 1.0, 154.87234042553183, 148.0) 0 1 [(0.0, 0.0), (-1.9437689969604435, 70.0)] 0 0 0 (1.0, 0.0, 0.0, 1.0, 501.0, 374.0) 174.0 50.0 0 0 (1.0, 0.0, 0.0, 1.0, 259.0, 384.0) 1 1 [(0.0, 0.0), (119.0, 0.0), (119.0, 8.6363636363636829), (242.0, 8.6363636363636829)] 0 1 1 0 (1.0, 0.0, 0.0, 1.0, 501.01273885350321, 432.0) 217.0 81.0 0 0 (1.0, 0.0, 0.0, 1.0, 501.01273885350321, 474.30508474576271) 1 1 [(0.0, 0.0), (-310.70661640452363, 0.0), (-310.70661640452363, -52.305084745762713)] 0 0 0 0 (1.0, 0.0, 0.0, 1.0, 506.51273885350315, 86.0) 100.0 59.0 0 0 (1.0, 0.0, 0.0, 1.0, 555.00000000000011, 145.0) 0 1 [(0.0, 0.0), (-1.1368683772161603e-13, 111.0)] 0 (1.0, 0.0, 0.0, 1.0, 604.0, 262.0) 0 1 [(0.0, 0.0), (224.78899082568807, 3.3388235294117408)] 0 0 (1.0, 0.0, 0.0, 1.0, 604.0, 331.0) 1 1 [(0.0, 0.0), (146.0, 0.0), (146.0, 65.363636363636317), (71.0, 65.363636363636317)] 0 0 0 0 (1.0, 0.0, 0.0, 1.0, 828.78899082568807, 245.30588235294118) 168.0 60.0 0 (1.0, 0.0, 0.0, 1.0, 675.95642201834858, 121.0) 163.0 86.0 0 0 (1.0, 0.0, 0.0, 1.0, 643.96147757123572, 262.59355363042829) 0 0 [(0.0, 0.0), (66.994944447112914, -55.593553630428289)] Behavior false isStatic Boolean LiteralString OutputPin subsets source ValuePin Dependencies 0 0 0 (1.0, 0.0, 0.0, 1.0, 47.0, 139.0) 166.0 98.0 0 1 0 0 (1.0, 0.0, 0.0, 1.0, 476.0, 139.0) 168.0 67.0 0 0 0 0 (1.0, 0.0, 0.0, 1.0, 363.0, 17.0) 197.0 59.0 0 0 0 0 (1.0, 0.0, 0.0, 1.0, 581.0, 20.0) 194.0 55.0 0 0 (1.0, 0.0, 0.0, 1.0, 213.0, 154.0) 0 1 [(0.0, 0.0), (263.0, -1.0)] 0 0 (1.0, 0.0, 0.0, 1.0, 213.0, 196.0) 0 1 [(0.0, 0.0), (263.0, -1.0)] 0 0 (1.0, 0.0, 0.0, 1.0, 467.46969696969688, 76.0) 0 1 [(0.0, 0.0), (34.530303030303116, 63.0)] 0 (1.0, 0.0, 0.0, 1.0, 661.5846153846154, 75.0) 0 1 [(0.0, 0.0), (-60.584615384615404, 64.0)] 1 0 0 (1.0, 0.0, 0.0, 1.0, 371.0, 341.0) 121.0 54.0 0 0 0 0 (1.0, 0.0, 0.0, 1.0, 50.0, 328.0) 135.0 76.0 0 1 0 0 (1.0, 0.0, 0.0, 1.0, 511.0, 341.0) 100.0 56.0 0 1 0 0 (1.0, 0.0, 0.0, 1.0, 652.0, 341.0) 117.0 56.0 0 1 0 0 (1.0, 0.0, 0.0, 1.0, 372.0, 451.0) 115.0 54.0 0 0 (1.0, 0.0, 0.0, 1.0, 421.0, 395.0) 0 1 [(0.0, 0.0), (-5.6843418860808015e-14, 56.0)] 1 0 0 (1.0, 0.0, 0.0, 1.0, 358.0, 568.0) 125.0 111.0 0 0 (1.0, 0.0, 0.0, 1.0, 420.99999999999994, 505.0) 0 1 [(0.0, 0.0), (-0.49999999999994316, 63.0)] 0 0 0 (1.0, 0.0, 0.0, 1.0, 810.49838187702267, 561.55970149253733) 156.0 116.440298507 0 0 (1.0, 0.0, 0.0, 1.0, 483.0, 585.83928571428578) 0 1 [(0.0, 0.0), (327.49838187702267, 0.37242803579511019)] 0 0 (1.0, 0.0, 0.0, 1.0, 483.0, 667.0) 0 1 [(0.0, 0.0), (327.49838187702267, 0.0)] 0 0 (1.0, 0.0, 0.0, 1.0, 185.0, 369.04000000000002) 0 1 [(0.0, 0.0), (186.0, -2.0399999999999068)] 0 0 (1.0, 0.0, 0.0, 1.0, 561.0, 206.0) 0 1 [(0.0, 0.0), (0.0, 135.0)] 0 (1.0, 0.0, 0.0, 1.0, 507.0, 206.0) 0 1 [(0.0, 0.0), (-86.0, 135.0)] 0 (1.0, 0.0, 0.0, 1.0, 644.0, 205.99999999999997) 0 1 [(0.0, 0.0), (58.000000000000114, 135.00000000000003)] 0 useCase * public 0 BehavioralFeature composite edge * ParameterDirectionKind Package 1 1 featuringClassifier * public 1 redefinitionContext * public redefinedBehavior * 0 Action Presentations 1 0 0 (1.0, 0.0, 0.0, 1.0, 231.0, 178.0) 127.0 66.0 0 (1.0, 0.0, 0.0, 1.0, 231.0, 266.0) 289.0 42.0 0 0 (1.0, 0.0, 0.0, 1.0, 290.69000000000005, 244.0) 0 1 [(0.0, 0.0), (0.10562043795613363, 22.0)] 0 (1.0, 0.0, 0.0, 1.0, 382.0, 134.0) 0 1 [(0.0, 0.0), (-45.314685314685278, 44.0)] 0 0 0 (1.0, 0.0, 0.0, 1.0, 277.0, 58.0) 195.0 76.0 0 0 0 0 (1.0, 0.0, 0.0, 1.0, 135.0, 56.0) 121.0 64.0 0 0 (1.0, 0.0, 0.0, 1.0, 190.0, 120.0) 0 1 [(0.0, 0.0), (41.0, 58.000000000000028)] 0 0 0 (1.0, 0.0, 0.0, 1.0, 665.0, 171.0) 100.0 64.0 0 0 (1.0, 0.0, 0.0, 1.0, 358.0, 199.0) 0 1 [(0.0, 0.0), (307.0, -3.6800000000000068)] 0 0 0 0 (1.0, 0.0, 0.0, 1.0, 659.88, 451.0) 100.0 62.0 0 1 0 0 (1.0, 0.0, 0.0, 1.0, 205.0, 454.0) 131.0 56.0 0 0 (1.0, 0.0, 0.0, 1.0, 336.0, 476.00000000000011) 0 1 [(0.0, 0.0), (323.88, -0.19999999999998863)] 0 (1.0, 0.0, 0.0, 1.0, 229.0, 552.0) 276.0 42.0 0 0 (1.0, 0.0, 0.0, 1.0, 243.63265306122452, 552.0) 0 1 [(0.0, 0.0), (10.492346938775427, -42.0)] 0 0 0 (1.0, 0.0, 0.0, 1.0, 218.88, 331.0) 100.0 62.0 0 0 (1.0, 0.0, 0.0, 1.0, 263.0, 393.0) 0 0 [(0.0, 0.0), (0.0, 61.0)] Namespaces 0 0 0 (1.0, 0.0, 0.0, 1.0, 387.66666666666663, 72.666666666666657) 122.0 64.0 0 1 0 0 (1.0, 0.0, 0.0, 1.0, 340.66666666666663, 200.66666666666666) 222.0 111.0 0 0 (1.0, 0.0, 0.0, 1.0, 448.66666666666663, 136.66666666666666) 0 1 [(0.0, 0.0), (0.37714285714281459, 64.0)] 1 0 0 (1.0, 0.0, 0.0, 1.0, 648.66666666666652, 43.666666666666657) 159.0 131.0 0 1 0 0 (1.0, 0.0, 0.0, 1.0, 180.66666666666663, 419.66666666666663) 218.0 71.0 0 0 (1.0, 0.0, 0.0, 1.0, 340.66666666666663, 311.66666666666663) 0 1 [(0.0, 0.0), (-54.0, 108.0)] 1 0 0 (1.0, 0.0, 0.0, 1.0, 566.66666666666652, 421.33333333333331) 147.0 182.666666667 0 0 (1.0, 0.0, 0.0, 1.0, 562.66666666666663, 311.66666666666657) 0 1 [(0.0, 0.0), (78.999999999999773, 109.66666666666674)] 0 (1.0, 0.0, 0.0, 1.0, 398.66666666666663, 445.07352941176481) 0 1 [(0.0, 0.0), (167.99999999999989, -1.0895294118056995)] 0 0 (1.0, 0.0, 0.0, 1.0, 562.66666666666663, 260.23691460055085) 1 1 [(0.0, 0.0), (196.00000000000011, 0.0), (196.00000000000011, 184.55819243304603), (150.99999999999989, 184.55819243304603)] 0 0 (1.0, 0.0, 0.0, 1.0, 562.66666666666663, 210.30303030303028) 1 1 [(0.0, 0.0), (269.99999999999989, 0.0), (269.99999999999989, 284.76730608827381), (150.99999999999989, 284.76730608827381)] 0 0 0 0 (1.0, 0.0, 0.0, 1.0, 520.87445887445881, 955.7841530054643) 219.0 64.0 0 1 0 0 (1.0, 0.0, 0.0, 1.0, 374.33333333333326, 744.66666666666663) 178.0 91.0 0 0 (1.0, 0.0, 0.0, 1.0, 600.00000000000011, 955.7841530054643) 0 1 [(0.0, 0.0), (-112.00000000000017, -120.11748633879768)] 0 (1.0, 0.0, 0.0, 1.0, 374.33333333333326, 764.88888888888846) 1 1 [(0.0, 0.0), (-193.66666666666663, 0.0), (-193.66666666666663, -274.22222222222189)] 0 1 0 0 (1.0, 0.0, 0.0, 1.0, 717.33333333333337, 756.33333333333326) 179.0 71.0 0 0 (1.0, 0.0, 0.0, 1.0, 739.87445887445881, 955.78415300546442) 0 1 [(0.0, 0.0), (42.125541125540622, -128.45081967213116)] 0 0 0 (1.0, 0.0, 0.0, 1.0, 833.6666666666664, 935.33333333333326) 124.0 50.0 0 0 (1.0, 0.0, 0.0, 1.0, 896.33333333333337, 827.33333333333326) 0 1 [(0.0, 0.0), (-17.000000000000341, 108.0)] 0 0 (1.0, 0.0, 0.0, 1.0, 566.66666666666652, 604.00000000033333) 0 1 [(0.0, 0.0), (-95.666666666666572, 16.99999999966667), (-125.66666666666669, 140.6666666663333)] 0 0 (1.0, 0.0, 0.0, 1.0, 713.66666666666652, 587.55999999969993) 0 1 [(0.0, 0.0), (89.333333333333485, 31.440000000300074), (118.33333333333337, 168.77333333363333)] 0 VisibilityKind AssociationClasses 0 body String 1 Comment package ExecutionEnvironment composite nestedPackage * public redefinedElement * Extend 1 0 qualifiedName String 1 public 1 attribute * public ActivityGroup 0 0 interface_ 1 0 specification 1 0 0 incoming * 0 none definingFeature 1 0 false isReadOnly Boolean 1 0 type 1 public Constraint protected 1 superClass * public UseCases 0 0 0 (1.0, 0.0, 0.0, 1.0, 367.0, 75.0) 194.0 50.0 0 0 0 0 (1.0, 0.0, 0.0, 1.0, 754.0, 75.0) 165.0 72.0 0 0 0 0 (1.0, 0.0, 0.0, 1.0, 50.0, 84.0) 186.0 72.0 0 0 0 0 (1.0, 0.0, 0.0, 1.0, 439.0, 551.0) 198.0 64.0 0 0 0 0 (1.0, 0.0, 0.0, 1.0, 82.0, 517.0) 112.0 62.0 0 1 0 0 (1.0, 0.0, 0.0, 1.0, 812.0, 221.0) 100.0 61.0 0 1 0 0 (1.0, 0.0, 0.0, 1.0, 68.0, 231.0) 147.0 56.0 0 1 0 0 (1.0, 0.0, 0.0, 1.0, 381.0, 218.0) 287.0 99.0 0 1 0 0 (1.0, 0.0, 0.0, 1.0, 379.0, 406.0) 100.0 56.0 0 1 0 0 (1.0, 0.0, 0.0, 1.0, 558.0, 403.0) 100.0 56.0 0 0 (1.0, 0.0, 0.0, 1.0, 194.0, 546.7600000000001) 1 1 [(0.0, 0.0), (201.0, 0.0), (201.0, -84.760000000000105)] 0 0 (1.0, 0.0, 0.0, 1.0, 490.81208053691296, 551.0) 0 1 [(0.0, 0.0), (-58.812080536912958, -89.0)] 0 (1.0, 0.0, 0.0, 1.0, 536.00000000000023, 551.0) 0 1 [(0.0, 0.0), (71.999999999999773, -92.0)] 0 (1.0, 0.0, 0.0, 1.0, 379.0, 453.0) 1 1 [(0.0, 0.0), (-240.03448275862073, 0.0), (-240.03448275862073, -166.0)] 0 0 (1.0, 0.0, 0.0, 1.0, 215.0, 251.0) 0 1 [(0.0, 0.0), (166.0, 0.58928571428560872)] 0 0 (1.0, 0.0, 0.0, 1.0, 381.0, 298.20253164556959) 1 1 [(0.0, 0.0), (-117.0, 0.0), (-117.0, 121.79746835443041), (-2.0, 121.79746835443041)] 0 0 (1.0, 0.0, 0.0, 1.0, 410.99999999999994, 406.0) 0 1 [(0.0, 0.0), (5.6843418860808015e-14, -89.0)] 0 0 (1.0, 0.0, 0.0, 1.0, 668.0, 317.0) 0 1 [(0.0, 0.0), (-45.0, 86.0)] 0 0 (1.0, 0.0, 0.0, 1.0, 658.0, 419.00000000000017) 1 1 [(0.0, 0.0), (132.0, 0.0), (132.0, -121.00000000000017), (10.0, -121.00000000000017)] 0 0 (1.0, 0.0, 0.0, 1.0, 461.0, 125.0) 0 1 [(0.0, 0.0), (1.0, 93.0)] 0 (1.0, 0.0, 0.0, 1.0, 754.0, 102.36) 1 1 [(0.0, 0.0), (-162.99999999999989, 0.0), (-162.99999999999989, 115.64)] 0 0 (1.0, 0.0, 0.0, 1.0, 858.0, 147.0) 0 1 [(0.0, 0.0), (-0.10000000000002274, 74.0)] 0 (1.0, 0.0, 0.0, 1.0, 668.0, 248.00000000000003) 1 1 [(0.0, 0.0), (116.0, 0.0), (116.0, -101.00000000000003)] 0 0 (1.0, 0.0, 0.0, 1.0, 143.64137931034483, 156.0) 0 1 [(0.0, 0.0), (-0.87413793103448256, 75.0)] 0 package 1 public Nodes 0 0 0 (1.0, 0.0, 0.0, 1.0, 52.0, 166.0) 115.0 88.0 0 0 0 0 (1.0, 0.0, 0.0, 1.0, 443.0, 54.0) 186.0 72.0 0 1 0 0 (1.0, 0.0, 0.0, 1.0, 466.0, 163.0) 129.0 123.0 0 0 (1.0, 0.0, 0.0, 1.0, 531.57142857142856, 126.0) 0 1 [(0.0, 0.0), (0.42857142857144481, 37.0)] 0 (1.0, 0.0, 0.0, 1.0, 466.0, 239.0) 1 1 [(0.0, 0.0), (-160.0, 0.0), (-160.0, 34.0), (0.0, 34.0)] 0 1 0 0 (1.0, 0.0, 0.0, 1.0, 625.0, 367.0) 129.0 56.0 0 1 0 0 (1.0, 0.0, 0.0, 1.0, 389.0, 444.0) 255.0 80.2810441287 0 0 0 0 (1.0, 0.0, 0.0, 1.0, 379.0, 347.0) 140.0 64.0 0 0 (1.0, 0.0, 0.0, 1.0, 455.936936936937, 411.0) 0 1 [(0.0, 0.0), (0.063063063062998026, 33.0)] 0 (1.0, 0.0, 0.0, 1.0, 555.0, 286.0) 0 1 [(0.0, 0.0), (-1.0, 158.0)] 0 (1.0, 0.0, 0.0, 1.0, 575.15384615384619, 286.0) 0 1 [(0.0, 0.0), (67.906153846153757, 81.0)] 1 0 0 (1.0, 0.0, 0.0, 1.0, 165.0, 434.0) 157.0 56.0 0 0 (1.0, 0.0, 0.0, 1.0, 475.92307692307691, 286.0) 0 1 [(0.0, 0.0), (-0.92307692307690559, 46.0), (-254.92307692307691, 44.0), (-254.92307692307693, 148.0)] 0 0 0 (1.0, 0.0, 0.0, 1.0, 178.0, 567.0) 100.0 60.0 0 0 (1.0, 0.0, 0.0, 1.0, 226.0, 490.0) 0 1 [(0.0, 0.0), (0.0, 77.0)] 0 0 0 (1.0, 0.0, 0.0, 1.0, 150.0, 684.3943661971831) 156.0 72.0 0 0 (1.0, 0.0, 0.0, 1.0, 227.0, 627.0) 0 1 [(0.0, 0.0), (-0.5294117647058556, 57.394366197183103)] 0 0 (1.0, 0.0, 0.0, 1.0, 178.0, 593.39999999999998) 1 1 [(0.0, 0.0), (-71.0, 0.0), (-71.0, -339.39999999999998)] 0 1 0 0 (1.0, 0.0, 0.0, 1.0, 366.0, 575.0) 100.0 56.0 0 0 (1.0, 0.0, 0.0, 1.0, 426.62295081967216, 524.28104412870005) 0 1 [(0.0, 0.0), (-0.62295081967215538, 50.718955871299954)] 1 0 0 (1.0, 0.0, 0.0, 1.0, 485.0, 575.0) 211.0 56.0 0 0 (1.0, 0.0, 0.0, 1.0, 586.0, 524.28104412870005) 0 1 [(0.0, 0.0), (1.0, 50.718955871299954)] 0 0 0 (1.0, 0.0, 0.0, 1.0, 479.0, 690.0) 224.0 84.0 0 0 (1.0, 0.0, 0.0, 1.0, 589.1639344262295, 690.0) 0 1 [(0.0, 0.0), (0.064981236421544963, -59.0)] 0 0 (1.0, 0.0, 0.0, 1.0, 167.0, 202.0) 0 1 [(0.0, 0.0), (299.0, 1.0)] 0 isRequired Boolean 1 0 namespace 1 public 1 Type Presentations none 1 1 source * public 1 1 relatedElement * public 0 type 1 public composite node * Classifiers 0 0 0 (1.0, 0.0, 0.0, 1.0, 275.0, 56.0) 166.0 84.0 0 1 0 0 (1.0, 0.0, 0.0, 1.0, 256.0, 188.0) 186.0 92.0 0 0 (1.0, 0.0, 0.0, 1.0, 353.99310344827552, 140.0) 0 1 [(0.0, 0.0), (-8.5504804974557373, 48.0)] 1 0 0 (1.0, 0.0, 0.0, 1.0, 433.0, 390.0) 217.0 237.0 0 0 0 0 (1.0, 0.0, 0.0, 1.0, 467.92617449664431, 188.0) 121.0 59.0 0 0 (1.0, 0.0, 0.0, 1.0, 659.0, 245.0) 0 1 [(0.0, 0.0), (-76.59701492537306, 145.0)] 0 (1.0, 0.0, 0.0, 1.0, 524.88286582917442, 247.0) 0 1 [(0.0, 0.0), (2.1469849170943007, 143.0)] 0 (1.0, 0.0, 0.0, 1.0, 346.4590163934426, 280.0) 0 1 [(0.0, 0.0), (122.06337166625872, 110.0)] 0 (1.0, 0.0, 0.0, 1.0, 433.0, 458.0) 1 1 [(0.0, 0.0), (-151.59016393442624, 0.0), (-151.59016393442624, -178.0)] 0 0 (1.0, 0.0, 0.0, 1.0, 256.0, 259.5555555555556) 1 1 [(0.0, 0.0), (-188.0, 0.0), (-188.0, -53.382716049382822), (0.0, -53.382716049382822)] 0 0 (1.0, 0.0, 0.0, 1.0, 650.0, 518.0) 1 1 [(0.0, 0.0), (153.0, 0.0), (153.0, 57.0), (0.0, 57.0)] 0 0 (1.0, 0.0, 0.0, 1.0, 650.0, 608.0) 1 1 [(0.0, 0.0), (24.0, 0.0), (24.0, 78.0), (-34.164179104477512, 78.0), (-34.164179104477512, 19.0)] 0 0 (1.0, 0.0, 0.0, 1.0, 433.0, 532.72727272727263) 1 1 [(0.0, 0.0), (-382.09090909090912, 0.0), (-382.09090909090912, -447.09818181818173), (-158.0, -447.09818181818173)] 0 1 0 0 (1.0, 0.0, 0.0, 1.0, 962.72727272727286, 396.36363636363632) 198.0 100.0 0 0 (1.0, 0.0, 0.0, 1.0, 650.0, 412.72727272727275) 0 1 [(0.0, 0.0), (312.72727272727286, -5.6843418860808015e-14)] 0 0 (1.0, 0.0, 0.0, 1.0, 650.0, 457.27272727272725) 1 1 [(0.0, 0.0), (189.09090909090901, 0.0), (189.09090909090901, 18.636363636363626), (312.72727272727286, 18.636363636363626)] 0 0 0 0 (1.0, 0.0, 0.0, 1.0, 955.05594405594388, 197.11384876805434) 198.0 50.0 0 0 (1.0, 0.0, 0.0, 1.0, 1053.2336182336185, 247.11384876805434) 0 1 [(0.0, 0.0), (4.4387094387088837, 149.24978759558198)] 0 0 0 (1.0, 0.0, 0.0, 1.0, 610.12587412587413, 189.0) 100.0 56.0 0 PackageImport 0 composite 1 output * 0 context 1 inGroup * 0 ownerFormalParam 1 Abstraction 0 1 client * public composite slot * 0 composite ownedBehavior * Diagram contains a diacanvas.Canvas instance Stereotypes Stereotypes 0 0 0 (1.0, 0.0, 0.0, 1.0, 74.0, 150.0) 126.0 64.0 0 (1.0, 0.0, 0.0, 1.0, 61.0, 41.0) 232.0 64.0 0 0 0 0 (1.0, 0.0, 0.0, 1.0, 370.0, 156.0) 100.0 62.0 0 0 (1.0, 0.0, 0.0, 1.0, 228.0, 284.01694915254234) 0 1 [(0.0, 0.0), (142.0, -106.01694915254234)] 0 1 1 1 (1.0, 0.0, 0.0, 1.0, 71.0, 253.0) 157.0 61.0 0 This diagram contains some additional relations that allow some elements to have a stereotype UseCases_extra 0 0 0 (1.0, 0.0, 0.0, 1.0, 195.0, 77.0) 100.0 56.0 0 0 0 0 (1.0, 0.0, 0.0, 1.0, 470.0, 85.0) 100.0 56.0 0 0 0 0 (1.0, 0.0, 0.0, 1.0, 136.0, 255.0) 223.0 68.0 0 0 0 0 (1.0, 0.0, 0.0, 1.0, 415.0, 255.0) 222.0 68.0 0 0 (1.0, 0.0, 0.0, 1.0, 241.41818181818184, 255.0) 0 1 [(0.0, 0.0), (0.58181818181816425, -122.0)] 0 0 (1.0, 0.0, 0.0, 1.0, 521.0, 141.0) 0 1 [(0.0, 0.0), (0.96363636363639671, 114.0)] 0 composite ownedAttribute * 0 0 useCase 1 0 0 actor 1 composite ownedAttribute * main (1.0, 0.0, 0.0, 1.0, 34.0, 62.0) 121.0 70.0 0 (1.0, 0.0, 0.0, 1.0, 197.0, 265.0) 210.0 70.0 0 (1.0, 0.0, 0.0, 1.0, 197.0, 160.0) 210.0 70.0 0 (1.0, 0.0, 0.0, 1.0, 439.0, 161.0) 132.0 70.0 0 (1.0, 0.0, 0.0, 1.0, 197.0, 62.0) 160.0 70.0 0 (1.0, 0.0, 0.0, 1.0, 437.0, 62.0) 163.0 70.0 0 (1.0, 0.0, 0.0, 1.0, 34.0, 160.0) 138.0 70.0 0 (1.0, 0.0, 0.0, 1.0, 446.0, 266.0) 155.0 70.0 0 (1.0, 0.0, 0.0, 1.0, 39.0, 372.0) 228.0 70.0 0 (1.0, 0.0, 0.0, 1.0, 448.0, 374.0) 133.0 70.0 0 (1.0, 0.0, 0.0, 1.0, 307.0, 372.0) 100.0 70.0 0 Classes AuxilaryConstructs CommonBehaviors CommonBehaviors (1.0, 0.0, 0.0, 1.0, 161.0, 127.0) 179.0 70.0 0 (1.0, 0.0, 0.0, 1.0, 211.0, 311.0) 190.0 70.0 0 Communications Activities Activities (1.0, 0.0, 0.0, 1.0, 62.0, 64.0) 173.0 70.0 0 (1.0, 0.0, 0.0, 1.0, 93.0, 344.0) 198.0 70.0 0 0 (1.0, 0.0, 0.0, 1.0, 164.0, 134.0) 0 1 [(0.0, 0.0), (1.0, 81.0)] 1 (1.0, 0.0, 0.0, 1.0, 80.0, 215.0) 224.0 70.0 0 0 (1.0, 0.0, 0.0, 1.0, 180.0, 285.0) 0 1 [(0.0, 0.0), (-2.0, 59.0)] 1 Classes (1.0, 0.0, 0.0, 1.0, 188.0, 46.0) 212.0 70.0 0 (1.0, 0.0, 0.0, 1.0, 264.0, 192.0) 173.0 70.0 0 (1.0, 0.0, 0.0, 1.0, 210.0, 329.0) 140.0 70.0 0 0 (1.0, 0.0, 0.0, 1.0, 304.00000000000006, 262.0) 0 1 [(0.0, 0.0), (-12.000000000000057, 67.0)] 1 (1.0, 0.0, 0.0, 1.0, 100.0, 184.0) 120.0 70.0 0 0 (1.0, 0.0, 0.0, 1.0, 194.50000000000011, 184.0) 0 1 [(0.0, 0.0), (32.999999999999886, -68.0)] 1 0 (1.0, 0.0, 0.0, 1.0, 220.0, 221.62000000000003) 0 1 [(0.0, 0.0), (44.0, -0.12000000000006139)] 1 (1.0, 0.0, 0.0, 1.0, 19.0, 41.0) 157.0 70.0 0 0 (1.0, 0.0, 0.0, 1.0, 141.99999999999991, 184.0) 0 1 [(0.0, 0.0), (-48.179999999999907, -73.0)] 1 AuxilaryConstructs (1.0, 0.0, 0.0, 1.0, 243.0, 113.0) 170.0 70.0 0 (1.0, 0.0, 0.0, 1.0, 201.0, 228.0) 120.0 70.0 0 (1.0, 0.0, 0.0, 1.0, 130.0, 342.0) 157.0 70.0 0 0 (1.0, 0.0, 0.0, 1.0, 254.0, 298.0) 0 1 [(0.0, 0.0), (-2.9999999999999432, 44.0)] 1 (1.0, 0.0, 0.0, 1.0, 78.0, 95.0) 120.0 70.0 0 0 (1.0, 0.0, 0.0, 1.0, 198.0, 138.55999999999995) 0 1 [(0.0, 0.0), (45.0, -0.55999999999994543)] 1 Interactions interactions (1.0, 0.0, 0.0, 1.0, 185.0, 193.0) 195.0 70.0 0 (1.0, 0.0, 0.0, 1.0, 202.0, 339.0) 143.0 70.0 0 (1.0, 0.0, 0.0, 1.0, 188.0, 31.0) 179.0 70.0 0 0 (1.0, 0.0, 0.0, 1.0, 263.99999999999994, 101.0) 0 1 [(0.0, 0.0), (5.6843418860808015e-14, 92.0)] 1 (1.0, 0.0, 0.0, 1.0, 15.0, 232.0) 173.0 70.0 0 0 (1.0, 0.0, 0.0, 1.0, 266.0, 263.0) 0 1 [(0.0, 0.0), (0.0, 76.0)] 1 0 (1.0, 0.0, 0.0, 1.0, 115.00000000000003, 302.0) 0 1 [(0.0, 0.0), (86.999999999999972, 73.000000000000057)] 1 BasicInteractions Fragments Interaction 0 0 0 (1.0, 0.0, 0.0, 1.0, -10.0, 164.0) 140.0 72.0 0 0 0 0 (1.0, 0.0, 0.0, 1.0, 365.0, 35.0) 166.0 98.0 0 1 1 0 (1.0, 0.0, 0.0, 1.0, 355.0, 167.0) 191.0 59.0 0 1 1 0 (1.0, 0.0, 0.0, 1.0, 134.0, 355.0) 117.0 59.0 0 0 (1.0, 0.0, 0.0, 1.0, 50.0, 236.0) 0 1 [(0.0, 0.0), (100.00000000000003, 119.0)] 0 (1.0, 0.0, 0.0, 1.0, 375.0, 226.0) 0 1 [(0.0, 0.0), (-158.0, 129.0)] 0 (1.0, 0.0, 0.0, 1.0, 433.8843537414968, 133.0) 0 1 [(0.0, 0.0), (0.11564625850326138, 34.0)] 0 (1.0, 0.0, 0.0, 1.0, 192.0, 355.0) 1 0 [(0.0, 0.0), (0.0, -161.0), (163.0, -161.0)] 0 1 1 0 (1.0, 0.0, 0.0, 1.0, 273.0, 355.0) 192.0 59.0 0 0 (1.0, 0.0, 0.0, 1.0, 408.0, 226.0) 0 1 [(0.0, 0.0), (-60.0, 129.0)] 1 1 0 (1.0, 0.0, 0.0, 1.0, 662.0, 354.0) 142.0 59.0 0 0 0 0 (1.0, 0.0, 0.0, 1.0, 672.43636363636369, 474.0) 112.0 62.0 0 0 (1.0, 0.0, 0.0, 1.0, 720.00000000000011, 413.0) 0 1 [(0.0, 0.0), (0.0, 61.0)] 0 0 (1.0, 0.0, 0.0, 1.0, 546.0, 226.0) 0 1 [(0.0, 0.0), (172.0, 128.0)] 1 1 0 (1.0, 0.0, 0.0, 1.0, 477.56571428571431, 356.0) 167.0 59.0 0 0 (1.0, 0.0, 0.0, 1.0, 454.99999999999994, 226.0) 0 1 [(0.0, 0.0), (79.999999999999943, 130.0)] InteractionFragment Interaction 0 enclosingInteraction 1 fragment * 1 ExecutionOccurence StateInvariant composite invariant 1 Lifeline 0 0 0 (1.0, 0.0, 0.0, 1.0, 266.0, 165.0) 117.0 56.0 0 1 1 0 (1.0, 0.0, 0.0, 1.0, 219.0, 333.0) 274.0 97.0 0 0 0 0 (1.0, 0.0, 0.0, 1.0, 695.0, 358.0) 191.0 56.0 0 0 (1.0, 0.0, 0.0, 1.0, 695.0, 381.99999999999994) 0 1 [(0.0, 0.0), (-202.0, -1.3220338983051079)] 0 0 (1.0, 0.0, 0.0, 1.0, 322.00000000000011, 221.0) 0 1 [(0.0, 0.0), (-1.1368683772161603e-13, 112.0)] 0 0 0 0 (1.0, 0.0, 0.0, 1.0, -4.0, 337.0) 166.0 98.0 0 0 (1.0, 0.0, 0.0, 1.0, 162.0, 376.19999999999987) 0 1 [(0.0, 0.0), (57.0, -0.45423728813545949)] 0 0 0 (1.0, 0.0, 0.0, 1.0, 414.0, 512.0) 121.0 76.0 0 0 (1.0, 0.0, 0.0, 1.0, 462.0, 430.0) 0 1 [(0.0, 0.0), (-1.4615384615384528, 82.0)] 0 (1.0, 0.0, 0.0, 1.0, 3.0, 465.0) 384.0 42.6421471173 0 0 (1.0, 0.0, 0.0, 1.0, 252.00000000000003, 430.0) 0 1 [(0.0, 0.0), (-161.78510028653298, 35.0)] Lifeline coveredBy * covered 1 interaction 1 composite lifeline * composite 0 discriminator 1 Add relationship to connectableElement from InternalStructures. Messages 0 0 0 (1.0, 0.0, 0.0, 1.0, 298.0, 51.0) 180.0 60.0 0 1 1 0 (1.0, 0.0, 0.0, 1.0, 316.0, 273.0) 227.0 97.0 0 0 (1.0, 0.0, 0.0, 1.0, 372.69387755102076, 111.0) 0 1 [(0.0, 0.0), (0.30612244897923802, 162.0)] 0 0 0 (1.0, 0.0, 0.0, 1.0, -43.5, 282.0) 174.0 64.0 0 0 (1.0, 0.0, 0.0, 1.0, 130.5, 315.14285714285722) 0 1 [(0.0, 0.0), (185.5, -1.1428571428572241)] 0 1 0 0 (1.0, 0.0, 0.0, 1.0, 64.0, 413.0) 135.0 125.0 0 1 0 0 (1.0, 0.0, 0.0, 1.0, 64.0, 554.0) 133.0 125.0 0 0 0 0 (1.0, 0.0, 0.0, 1.0, 369.0, 456.0) 166.0 98.0 0 0 (1.0, 0.0, 0.0, 1.0, 446.91836734693896, 456.0) 0 1 [(0.0, 0.0), (-0.91836734693907829, -86.0)] 0 0 0 0 (1.0, 0.0, 0.0, 1.0, 1157.0, 147.0) 191.0 50.0 0 0 (1.0, 0.0, 0.0, 1.0, 485.53560411311048, 195.0) 0 1 [(0.0, 0.0), (-0.53560411311048028, 78.0)] 0 1 0 0 (1.0, 0.0, 0.0, 1.0, 791.0, 282.0) 129.0 72.0 0 0 (1.0, 0.0, 0.0, 1.0, 543.0, 294.99999999999994) 0 1 [(0.0, 0.0), (248.0, 3.0000000000000568)] 0 0 (1.0, 0.0, 0.0, 1.0, 543.0, 340.00000000000011) 0 1 [(0.0, 0.0), (248.0, 2.9999999999998863)] 0 1 0 0 (1.0, 0.0, 0.0, 1.0, 1117.0, 290.0) 167.0 56.0 0 0 0 0 (1.0, 0.0, 0.0, 1.0, 440.55526992287923, 145.0) 117.0 50.0 0 0 (1.0, 0.0, 0.0, 1.0, 1204.8049029622064, 197.0) 0 1 [(0.0, 0.0), (0.19509703779408483, 93.0)] 0 (1.0, 0.0, 0.0, 1.0, 478.0, 97.046511627906995) 1 1 [(0.0, 0.0), (359.0, 0.0), (359.0, 184.95348837209286)] 1 0 0 (1.0, 0.0, 0.0, 1.0, 1482.0, 294.0) 162.0 56.0 0 0 (1.0, 0.0, 0.0, 1.0, 478.0, 55.883720930232556) 1 1 [(0.0, 0.0), (1107.0000000000005, 0.0), (1107.0000000000005, 238.11627906976744)] 0 (1.0, 0.0, 0.0, 1.0, 1348.0, 165.0) 1 1 [(0.0, 0.0), (147.0, 0.0), (147.0, 129.0)] 0 0 (1.0, 0.0, 0.0, 1.0, 1284.0, 299.0) 0 1 [(0.0, 0.0), (198.0, 2.0)] 0 0 (1.0, 0.0, 0.0, 1.0, 1284.0, 340.0) 0 1 [(0.0, 0.0), (198.0, 0.0)] 0 0 0 0 (1.0, 0.0, 0.0, 1.0, 1107.1148936170212, 456.0) 192.0 56.0 0 0 (1.0, 0.0, 0.0, 1.0, 1133.0, 346.0) 0 1 [(0.0, 0.0), (-1.0, 110.0)] 0 0 (1.0, 0.0, 0.0, 1.0, 1219.0, 346.0) 0 1 [(0.0, 0.0), (-1.0, 110.0)] 0 0 0 0 (1.0, 0.0, 0.0, 1.0, 1113.1148936170212, 595.0) 129.0 72.0 0 0 (1.0, 0.0, 0.0, 1.0, 1177.5560253480721, 595.0) 0 1 [(0.0, 0.0), (-0.55602534807235315, -83.0)] 0 0 0 1 (1.0, 0.0, 0.0, 1.0, 840.0, 461.0) 231.0 51.0 0 0 (1.0, 0.0, 0.0, 1.0, 872.0, 354.0) 0 0 [(0.0, 0.0), (0.8382352941176805, 107.0)] 0 (1.0, 0.0, 0.0, 1.0, 1117.0, 306.0) 1 1 [(0.0, 0.0), (-93.558823529411711, 0.0), (-93.558823529411711, 155.0)] (1.0, 0.0, 0.0, 1.0, 89.0, 209.0) 190.0 54.0 0 0 (1.0, 0.0, 0.0, 1.0, 195.0, 263.0) 0 0 [(0.0, 0.0), (3.0724187084353218, 51.726546515466111)] Message 1 messageKind MessageKind messageSort MessageSort composite 0 argument 1 CompositeStructures CompositeStructures (1.0, 0.0, 0.0, 1.0, 79.0, 79.0) 204.0 70.0 0 (1.0, 0.0, 0.0, 1.0, 361.0, 78.0) 100.0 70.0 0 (1.0, 0.0, 0.0, 1.0, 125.0, 253.0) 137.0 70.0 0 (1.0, 0.0, 0.0, 1.0, 361.0, 253.0) 101.0 70.0 0 InternalStructures MessageKind complete lost found unknown MessageSort synchCall asynchCall asynchSignal 0 signature 1 MessageEnd 0 sendMessage 1 composite 0 sendEvent 1 0 receiveMessage 1 composite 0 receiveEvent 1 1 OccurrenceSpecification interaction 1 composite message * GeneralOrdering composite generalOrdering * before 1 toAfter * after 1 toBefore * finish 1 finishExec * start 1 startExec * behavior * parse return s String render return String StructuredClassifer 0 0 0 (1.0, 0.0, 0.0, 1.0, 94.0, 64.0) 156.0 72.0 0 0 1 0 (1.0, 0.0, 0.0, 1.0, 54.0, 177.0) 189.0 333.0 0 0 (1.0, 0.0, 0.0, 1.0, 174.27184466019418, 136.0) 0 1 [(0.0, 0.0), (-26.271844660194176, 41.0)] 0 1 0 (1.0, 0.0, 0.0, 1.0, 640.0, 190.0) 196.0 57.0 0 0 1 0 (1.0, 0.0, 0.0, 1.0, 691.0, 470.0) 128.0 58.0 0 0 (1.0, 0.0, 0.0, 1.0, 733.0, 135.0) 0 1 [(0.0, 0.0), (1.0000000000003411, 55.0)] 0 (1.0, 0.0, 0.0, 1.0, 243.0, 177.0) 0 1 [(0.0, 0.0), (397.0, 54.999999999999943)] 0 0 (1.0, 0.0, 0.0, 1.0, 243.0, 308.0) 0 1 [(0.0, 0.0), (406.0, 2.6635514018690856)] 0 0 (1.0, 0.0, 0.0, 1.0, 243.0, 408.99999999999989) 0 1 [(0.0, 0.0), (406.0, 28.387096774193708)] 0 0 (1.0, 0.0, 0.0, 1.0, 691.0, 493.40350877192969) 0 1 [(0.0, 0.0), (-198.0, 0.59649122807030608), (-198.0, -35.403508771929694), (-448.0, -35.403508771929694)] 0 0 (1.0, 0.0, 0.0, 1.0, 819.0, 519.85964912280701) 0 1 [(0.0, 0.0), (142.0, -0.85964912280701355), (142.0, -43.859649122807014), (0.0, -42.736842105263349)] 0 0 0 0 (1.0, 0.0, 0.0, 1.0, 668.96800000000007, 71.0) 134.0 64.0 0 0 0 0 (1.0, 0.0, 0.0, 1.0, 649.0, 299.0) 223.0 156.0 0 0 (1.0, 0.0, 0.0, 1.0, 727.0, 247.0) 0 1 [(0.0, 0.0), (17.200411045531041, 52.0)] 1 StructuredClassifier 1 ConnectableElement Connector * 1 role * composite ownedConnector * 0 1 redefinedConnector * * Connectors 1 1 0 (1.0, 0.0, 0.0, 1.0, 760.0, 213.0) 156.0 74.0 0 0 0 0 (1.0, 0.0, 0.0, 1.0, 330.0, 78.0) 140.0 50.0 0 0 0 0 (1.0, 0.0, 0.0, 1.0, 743.0, 93.0) 175.0 72.0 0 0 0 0 (1.0, 0.0, 0.0, 1.0, 731.0, 354.0) 190.0 72.0 0 0 (1.0, 0.0, 0.0, 1.0, 823.0, 165.0) 0 1 [(0.0, 0.0), (0.50442477876106295, 48.0)] 0 (1.0, 0.0, 0.0, 1.0, 827.64601769911508, 287.0) 0 1 [(0.0, 0.0), (-0.64601769911485007, 67.0)] 0 0 1 0 (1.0, 0.0, 0.0, 1.0, 328.0, 201.0) 149.0 74.0 0 0 (1.0, 0.0, 0.0, 1.0, 398.40909090909088, 128.0) 0 1 [(0.0, 0.0), (-5.3527528809218552, 73.0)] 0 (1.0, 0.0, 0.0, 1.0, 395.15492957746483, 275.0) 0 1 [(0.0, 0.0), (-2.2977867203219375, 58.0)] 0 0 (1.0, 0.0, 0.0, 1.0, 477.0, 227.0) 0 1 [(0.0, 0.0), (283.0, -1.1051805337519625)] 0 0 0 0 (1.0, 0.0, 0.0, 1.0, 1053.0246305418718, 213.0) 161.0 72.0 0 0 (1.0, 0.0, 0.0, 1.0, 916.0, 231.0) 0 1 [(0.0, 0.0), (137.02463054187183, 6.9230769230769624)] 0 1 1 0 (1.0, 0.0, 0.0, 1.0, 1023.0, 351.0) 138.0 91.0 0 1 0 0 (1.0, 0.0, 0.0, 1.0, 15.900928792569658, 210.0) 181.0 56.0 0 0 (1.0, 0.0, 0.0, 1.0, 196.90092879256966, 237.00000000000003) 0 1 [(0.0, 0.0), (131.09907120743034, -2.0000000000001421)] 0 0 (1.0, 0.0, 0.0, 1.0, 117.0, 266.0) 0 1 [(0.0, 0.0), (0.0, 66.0)] 0 0 0 (1.0, 0.0, 0.0, 1.0, 63.0, 332.0) 109.0 62.0 0 0 0 0 (1.0, 0.0, 0.0, 1.0, 345.0, 333.0) 100.0 50.0 0 * 0 type 1 1 ConnectorEnd composite 2 end * 1 kind ConnectorKind * contract * ConnectorKind assembly delegation 0 role 1 end * * 1 0 definingEnd 1 0 1 composite ownedAttribute * 0 1 1 part * FlowFinalNode JoinNode ForkNode CompleteActivities IntermediateActivities Object nodes 1 0 0 (1.0, 0.0, 0.0, 1.0, 92.0, 126.0) 264.0 90.0 0 1 0 0 (1.0, 0.0, 0.0, 1.0, 483.0, 318.0) 176.0 115.0 0 0 0 0 (1.0, 0.0, 0.0, 1.0, 597.0, 134.0) 165.0 64.0 0 0 (1.0, 0.0, 0.0, 1.0, 356.0, 156.0) 0 1 [(0.0, 0.0), (241.0, 2.7017543859649038)] 0 0 0 0 (1.0, 0.0, 0.0, 1.0, 234.0, 310.49769585253455) 171.0 72.0 0 0 (1.0, 0.0, 0.0, 1.0, 312.0, 216.0) 0 1 [(0.0, 0.0), (0.0, 94.497695852534548)] 0 FIFO ordering ObjectOrderingKind ObjectOrderingKind unordered ordered LIFO FIFO false isControlType Boolean 0 1 composite upperBound 1 * 0 selection 1 Control nodes 1 0 0 (1.0, 0.0, 0.0, 1.0, 8.0, 140.0) 262.0 76.0 0 1 1 0 (1.0, 0.0, 0.0, 1.0, 516.0, 142.0) 165.0 72.0 0 0 (1.0, 0.0, 0.0, 1.0, 270.0, 161.375) 0 1 [(0.0, 0.0), (246.0, -0.42763157894739834)] 0 true isCombineDuplicate Boolean composite joinSpec 1 State BehaviorStateMachines 0 0 0 (1.0, 0.0, 0.0, 1.0, 897.0, 15.0) 157.0 72.0 0 1 0 0 (1.0, 0.0, 0.0, 1.0, 909.0, 133.0) 130.0 70.0 0 1 0 0 (1.0, 0.0, 0.0, 1.0, 1461.0, 56.0) 134.0 98.0 0 1 1 0 (1.0, 0.0, 0.0, 1.0, 1638.0, 54.0) 156.0 227.0 0 0 (1.0, 0.0, 0.0, 1.0, 969.99999999999989, 87.0) 0 1 [(0.0, 0.0), (5.6843418860808015e-13, 46.0)] 1 0 0 (1.0, 0.0, 0.0, 1.0, 885.0, 335.0) 152.0 57.0 0 0 (1.0, 0.0, 0.0, 1.0, 966.06666666666649, 335.0) 0 1 [(0.0, 0.0), (-0.066666666666492347, -132.0)] 0 0 0 0 (1.0, 0.0, 0.0, 1.0, 1461.0, 255.0) 166.0 98.0 0 0 0 0 (1.0, 0.0, 0.0, 1.0, 244.0, 217.0) 116.0 64.0 0 0 (1.0, 0.0, 0.0, 1.0, 360.0, 240.84313725490196) 1 1 [(0.0, 0.0), (535.63999999999999, 0.0), (535.63999999999999, 94.156862745098039)] 1 0 0 (1.0, 0.0, 0.0, 1.0, 1370.0, 455.0) 238.0 74.0 0 0 (1.0, 0.0, 0.0, 1.0, 1536.4545454545453, 353.0) 0 1 [(0.0, 0.0), (-10.454545454545269, 102.0)] 0 (1.0, 0.0, 0.0, 1.0, 1037.0, 370.99999999999994) 1 1 [(0.0, 0.0), (359.0, 0.0), (359.0, 84.000000000000057)] 0 1 0 0 (1.0, 0.0, 0.0, 1.0, 225.0, 455.0) 173.0 68.0 0 0 0 0 (1.0, 0.0, 0.0, 1.0, 182.0, 292.82539682539681) 166.0 98.0 0 0 (1.0, 0.0, 0.0, 1.0, 368.48212461695607, 455.0) 1 0 [(0.0, 0.0), (0.0, -80.0), (516.51787538304393, -80.0)] 0 0 (1.0, 0.0, 0.0, 1.0, 247.00699300699307, 390.82539682539681) 0 1 [(0.0, 0.0), (0.47411015950336832, 64.174603174603192)] 0 (1.0, 0.0, 0.0, 1.0, 398.0, 482.39999999999986) 0 1 [(0.0, 0.0), (972.0, -2.3999999999998636)] 0 0 (1.0, 0.0, 0.0, 1.0, 398.0, 521.0) 0 1 [(0.0, 0.0), (972.0, -2.6000000000001364)] 0 0 0 0 (1.0, 0.0, 0.0, 1.0, 833.0, 527.82539682539687) 116.0 64.0 0 1 0 0 (1.0, 0.0, 0.0, 1.0, 214.0, 608.0) 166.0 64.0 0 1 1 0 (1.0, 0.0, 0.0, 1.0, 230.0, 751.0) 225.0 57.0 0 0 (1.0, 0.0, 0.0, 1.0, 294.0, 523.0) 0 1 [(0.0, 0.0), (-0.12030075187965394, 85.0)] 0 (1.0, 0.0, 0.0, 1.0, 398.0, 523.0) 0 0 [(0.0, 0.0), (-0.51889683350356108, 228.0)] 0 (1.0, 0.0, 0.0, 1.0, 287.0, 751.0) 0 1 [(0.0, 0.0), (-26.819548872180462, -79.0)] 0 0 (1.0, 0.0, 0.0, 1.0, 380.0, 672.0) 0 1 [(0.0, 0.0), (-18.0, 79.0)] 0 0 (1.0, 0.0, 0.0, 1.0, 909.0, 182.12280701754389) 0 1 [(0.0, 0.0), (-782.0, -0.12280701754389156), (-787.50357507660874, 452.01039377768473), (-695.0, 451.87719298245599)] 0 1 1 0 (1.0, 0.0, 0.0, 1.0, 857.0, 634.0) 281.0 198.0 0 0 (1.0, 0.0, 0.0, 1.0, 398.0, 523.0) 1 0 [(0.0, 0.0), (0.0, 111.0), (489.90999999999997, 111.0), (489.90999999999997, 111.0)] 0 (1.0, 0.0, 0.0, 1.0, 920.4811031664965, 591.82539682539687) 0 1 [(0.0, 0.0), (2.5188968335035042, 42.174603174603135)] 0 (1.0, 0.0, 0.0, 1.0, 977.99999999999966, 392.0) 0 1 [(0.0, 0.0), (3.0000000000003411, 242.0)] 0 0 (1.0, 0.0, 0.0, 1.0, 380.0, 655.39761431411534) 0 1 [(0.0, 0.0), (477.0, -1.3976143141123885)] 0 0 (1.0, 0.0, 0.0, 1.0, 455.0, 768.99999999999989) 0 1 [(0.0, 0.0), (402.0, 1.0000000000001137)] 0 1 0 0 (1.0, 0.0, 0.0, 1.0, 827.0, 931.0) 104.0 57.0 0 0 (1.0, 0.0, 0.0, 1.0, 881.00000000000011, 832.0) 0 1 [(0.0, 0.0), (-2.0000000000001137, 99.0)] 0 0 0 (1.0, 0.0, 0.0, 1.0, 1387.7, 632.45569620253161) 157.0 197.544303797 0 0 (1.0, 0.0, 0.0, 1.0, 1138.0, 680.0) 0 1 [(0.0, 0.0), (249.70000000000005, 0.0)] 0 0 (1.0, 0.0, 0.0, 1.0, 1138.0, 741.99999999999989) 0 1 [(0.0, 0.0), (249.70000000000005, 1.1368683772161603e-13)] 0 0 (1.0, 0.0, 0.0, 1.0, 1138.0, 809.00000000000034) 0 1 [(0.0, 0.0), (249.70000000000005, -3.4106051316484809e-13)] 0 0 (1.0, 0.0, 0.0, 1.0, 1465.9999999999998, 632.45569620253161) 0 1 [(0.0, 0.0), (0.99999999999954525, -103.45569620253161)] 0 0 0 0 (1.0, 0.0, 0.0, 1.0, 1002.7, 998.45103857566767) 106.0 62.0 0 0 (1.0, 0.0, 0.0, 1.0, 1054.9999999999998, 998.45103857566767) 0 1 [(0.0, 0.0), (-0.99999999999977263, -166.45103857566767)] 0 0 (1.0, 0.0, 0.0, 1.0, 1108.7, 1030.1317507418396) 0 1 [(0.0, 0.0), (748.82706843718074, 2.9994619818185129), (748.29999999999995, -549.1317507418396), (499.29999999999995, -549.1317507418396)] 0 0 (1.0, 0.0, 0.0, 1.0, 909.0, 139.14035087719299) 0 1 [(0.0, 0.0), (-887.0, -0.14035087719298645), (-890.0, 686.85964912280701), (-52.0, 684.85964912280713)] 0 StateMachine TransitionKind internal local external PseudostateKind initial deepHistory shallowHistory join fork junction choice entryPoint exitPoint terminate Region composite 1 region * 0 stateMachine 1 Transition container 1 composite transition kind TransitionKind Vertex composite subvertex * 0 container 1 source 1 outgoing * target 1 incoming * Pseudostate ConnectionPointReference kind PseudostateKind 0 1 entry * exit * 0 1 0 stateMachine 1 composite connectionPoint * State composite region * 0 state 1 composite 0 connectionPoint * 0 state 1 composite connection * 0 state 1 1 isComposite Boolean 1 isOrthogonal Boolean 1 isSimple Boolean 1 isSubmachineState Boolean FinalState 0 1 composite 0 entry 1 0 1 composite 0 exit 1 0 1 composite 0 doActivity 1 composite 0 effect 1 0 1 composite 0 statevariant 1 0 owningState 1 composite 0 guard 1 0 1 0 submachine 1 submachineState * StateMachineRedifinitions 0 0 0 (1.0, 0.0, 0.0, 1.0, 130.0, 331.0) 163.0 276.0 0 1 0 0 (1.0, 0.0, 0.0, 1.0, 584.0, 185.0) 179.0 72.0 0 1 0 0 (1.0, 0.0, 0.0, 1.0, 572.81465517241372, 325.0) 100.0 80.0 0 0 1 0 (1.0, 0.0, 0.0, 1.0, 484.81465517241372, 708.0) 130.0 57.0 0 0 (1.0, 0.0, 0.0, 1.0, 293.0, 331.0) 0 1 [(0.0, 0.0), (267.0, 22.0)] 0 0 (1.0, 0.0, 0.0, 1.0, 614.81465517241372, 765.0) 0 1 [(0.0, 0.0), (-14.814655172413723, 58.0), (-102.31465517241372, 58.000000000000057), (-102.81465517241372, 0.0)] 0 0 (1.0, 0.0, 0.0, 1.0, 609.00000000000011, 257.0) 0 1 [(0.0, 0.0), (-1.1368683772161603e-13, 68.0)] 0 1 0 (1.0, 0.0, 0.0, 1.0, 774.15503875968989, 433.0) 176.0 95.0 0 0 (1.0, 0.0, 0.0, 1.0, 659.99999999999989, 257.0) 1 1 [(0.0, 0.0), (187.36764459241169, 0.0), (187.36764459241169, 176.0), (187.36764459241169, 176.0)] 0 1 0 (1.0, 0.0, 0.0, 1.0, 1045.1550387596899, 544.0) 146.0 74.0 0 0 (1.0, 0.0, 0.0, 1.0, 763.0, 257.0) 1 1 [(0.0, 0.0), (336.99999999999977, 0.0), (336.99999999999977, 287.0), (336.99999999999977, 287.0)] 0 (1.0, 0.0, 0.0, 1.0, 293.0, 594.0) 0 1 [(0.0, 0.0), (752.15503875968989, -5.0)] 0 0 (1.0, 0.0, 0.0, 1.0, 293.0, 478.0) 0 1 [(0.0, 0.0), (465.0, -1.0)] 0 0 (1.0, 0.0, 0.0, 1.0, 950.15503875968989, 441.01204819277115) 0 1 [(0.0, 0.0), (28.844961240310113, -2.0120481927711467), (28.844961240310113, 68.987951807228853), (0.0, 81.265060240963919)] 0 0 (1.0, 0.0, 0.0, 1.0, 672.81465517241372, 397.0) 0 1 [(0.0, 0.0), (76.185344827586277, -1.0), (78.185344827586277, -64.0), (0.0, -64.0)] 0 0 (1.0, 0.0, 0.0, 1.0, 1191.1550387596899, 616.0) 0 1 [(0.0, 0.0), (86.844961240310113, 0.0), (85.844961240310113, -62.0), (0.0, -62.0)] 0 0 1 0 extendedStateMachine 1 1 redefintionContext 1 * 0 redefinedState 1 0 1 extendedRegion 0 1 0 redefinedTransition 1 0 1 createMessage deleteMessage reply Ports Ports 0 0 1 (1.0, 0.0, 0.0, 1.0, 34.0, 42.0) 136.0 50.0 0 0 0 1 (1.0, 0.0, 0.0, 1.0, 32.0, 150.0) 159.0 50.0 0 0 (1.0, 0.0, 0.0, 1.0, 95.910447761194021, 92.0) 0 0 [(0.0, 0.0), (6.756218905472636, 58.0)] 0 0 1 (1.0, 0.0, 0.0, 1.0, 44.0, 239.0) 128.0 50.0 0 0 0 1 (1.0, 0.0, 0.0, 1.0, 49.0, 343.0) 108.0 50.0 0 0 (1.0, 0.0, 0.0, 1.0, 109.06666666666666, 289.0) 0 0 [(0.0, 0.0), (-3.0666666666666629, 54.0)] 0 0 0 1 (1.0, 0.0, 0.0, 1.0, 520.0, 40.0) 103.0 50.0 0 1 0 1 (1.0, 0.0, 0.0, 1.0, 504.0, 118.0) 131.0 62.0 0 0 (1.0, 0.0, 0.0, 1.0, 569.0, 90.0) 0 0 [(0.0, 0.0), (-0.80999999999994543, 28.0)] 0 (1.0, 0.0, 0.0, 1.0, 191.0, 164.0) 0 0 [(0.0, 0.0), (92.0, -17.0), (313.0, -18.100000000000023)] 0 EncapsulatedClassifer 0 * 0 partWithPort 1 Port isBehavior Boolean isService Boolean 0 1 composite 0 ownedPort * appliedStereotype extended Profiles Profiles diagram (1.0, 0.0, 0.0, 1.0, 76.0, 49.0) 100.0 70.0 0 Gaphor Gaphor diagram 1 1 1 (1.0, 0.0, 0.0, 1.0, 110.0, 44.0) 100.0 60.0 0 1 0 1 (1.0, 0.0, 0.0, 1.0, 98.0, 204.0) 119.0 62.0 0 0 (1.0, 0.0, 0.0, 1.0, 157.0, 104.0) 0 0 [(0.0, 0.0), (0.5, 100.0)] 1 0 1 (1.0, 0.0, 0.0, 1.0, 407.0, 203.0) 147.0 115.0 0 1 1 1 (1.0, 0.0, 0.0, 1.0, 408.0, 55.0) 147.0 69.0 0 0 (1.0, 0.0, 0.0, 1.0, 477.0, 124.0) 0 0 [(0.0, 0.0), (0.0, 79.0)] Class SimpleAttribute baseClass composite composite nestedNode * 0 1 StructuredClasses StructuredClasses 0 0 1 (1.0, 0.0, 0.0, 1.0, 232.0, 171.0) 159.0 50.0 0 0 0 1 (1.0, 0.0, 0.0, 1.0, 249.0, 279.0) 100.0 50.0 0 0 (1.0, 0.0, 0.0, 1.0, 309.26056338028172, 221.0) 0 0 [(0.0, 0.0), (-8.2605633802817238, 58.0)] Deployments 0 0 1 (1.0, 0.0, 0.0, 1.0, 725.0, 263.0) 102.0 62.0 0 0 0 1 (1.0, 0.0, 0.0, 1.0, 710.0, 144.0) 118.0 50.0 0 0 (1.0, 0.0, 0.0, 1.0, 765.94827586206895, 194.0) 0 0 [(0.0, 0.0), (0.051724137931046243, 69.0)] 0 0 1 (1.0, 0.0, 0.0, 1.0, 711.0, 42.0) 111.0 50.0 0 0 (1.0, 0.0, 0.0, 1.0, 768.08571428571429, 92.0) 0 0 [(0.0, 0.0), (-3.5554112554112862, 52.0)] 0 0 1 (1.0, 0.0, 0.0, 1.0, 414.0, 140.0) 100.0 51.0 0 0 0 1 (1.0, 0.0, 0.0, 1.0, 414.0, 38.0) 104.0 61.0 0 0 (1.0, 0.0, 0.0, 1.0, 472.56310679611653, 99.0) 0 0 [(0.0, 0.0), (-2.5631067961165286, 41.0)] 0 0 1 (1.0, 0.0, 0.0, 1.0, 40.0, 144.0) 127.0 51.0 0 0 0 1 (1.0, 0.0, 0.0, 1.0, 54.0, 255.0) 100.0 50.0 0 0 (1.0, 0.0, 0.0, 1.0, 103.5, 195.0) 0 0 [(0.0, 0.0), (-1.5, 60.0)] 0 (1.0, 0.0, 0.0, 1.0, 167.0, 158.0) 0 0 [(0.0, 0.0), (247.0, -3.0)] 0 0 (1.0, 0.0, 0.0, 1.0, 514.0, 156.0) 0 0 [(0.0, 0.0), (196.0, 1.0)] 0 0 0 1 (1.0, 0.0, 0.0, 1.0, 36.0, 26.0) 136.0 50.0 0 0 (1.0, 0.0, 0.0, 1.0, 104.0, 76.0) 0 0 [(0.0, 0.0), (0.0, 68.0)] 1 DeployedArtifact Deployment 1 DeploymentTarget location 1 composite deployment * * deployedArtifact * Partitions 1 0 1 (1.0, 0.0, 0.0, 1.0, 285.0, 258.0) 177.0 63.0 0 0 0 1 (1.0, 0.0, 0.0, 1.0, 194.0, 120.0) 109.0 50.0 0 0 (1.0, 0.0, 0.0, 1.0, 254.91176470588235, 170.0) 0 0 [(0.0, 0.0), (85.810457516339937, 88.0)] 0 0 1 (1.0, 0.0, 0.0, 1.0, 436.0, 415.0) 109.0 50.0 0 0 (1.0, 0.0, 0.0, 1.0, 443.42592592592592, 321.0) 0 0 [(0.0, 0.0), (38.525054466230927, 94.0)] 0 0 (1.0, 0.0, 0.0, 1.0, 299.20370370370375, 258.0) 0 0 [(0.0, 0.0), (-47.203703703703752, -38.0), (-106.20370370370375, 1.0), (-62.203703703703752, 59.0), (-14.203703703703752, 57.0)] 0 0 0 1 (1.0, 0.0, 0.0, 1.0, 500.0, 183.0) 100.0 50.0 0 0 0 1 (1.0, 0.0, 0.0, 1.0, 344.0, 122.0) 115.0 50.0 0 0 (1.0, 0.0, 0.0, 1.0, 396.0, 172.0) 0 0 [(0.0, 0.0), (-6.7983870967742064, 86.0)] 0 (1.0, 0.0, 0.0, 1.0, 500.0, 207.0) 0 0 [(0.0, 0.0), (-38.0, 73.0)] 0 (1.0, 0.0, 0.0, 1.0, 103.0, 382.0) 231.0 64.0 0 ActivityPartition false isExternal Boolean false isDimension Boolean inPartition * node * 0 represents 1 * superPartition * shared subpartition * ActivitiyPartition.subpartition cannot be composite, so we can rearrange swimlanes 0 1 navigableOwnedEnd * Tagged Property baseClass composite subsets str feature, ownedMember source owner source, owner, client ownedElement redefinedElement ownedElement node ordered bool true ownedMember ownedElement, clientDependency ownedElement members relatedElement namespace ownedMember target true namespace ownedMember redefines str NamedElement.clientDependency Dependency.supplier ownedElement true member context importedPackage target client, source feature, ownedMember Namespace.ownedMember ownedElement ownedElement target ownedElement context, namespace feature, ownedMember packageImport context, namespace ownedMember, ownedRule classifier, namespace target true parameter, ownedMember ownedMember member, ownedElement ownedElement Namespace.ownedMember ownedMember ownedElement attribute, ownedMember true ownedElement true parameter, ownedMember source source, owner ownedMember classifier, namespace, featuringClassifier ownedElement ownedElement attribute, ownedMember ownedElement, clientDependency true feature, ownedMember namespace, featuringClassifier ownedMember feature, ownedMember context, namespace namespace, redefinitionContext, featuringClassifier namespace redefinedElement namespace source, owner ownedElement redefinedElement target ownedMember supplier, target owner ownedElement redefinitionContext true member owner namespace, redefinitionContext owner attribute, ownedMember ownedElement Parameter.ownerFormalParam BehavioralFeature.formalParameter ownedMember member source, owner namespace, redefinitionContext, featuringClassifier true ownedElement namespace source, owner ownedMember redefinedElement ownedElement namespace, featuringClassifier, classifier elementImport Dependency.client target packageImport ownedElement, target RedefinableElement.redefinedElement ownedElement ownedElement ownedElement redefinedElement ownedMember RedefinableElement.redefinedElement feature namespace, featuringClassifier Classifier.general namespace owner relatedElement ownedElement ownedElement namespace ownedMember attribute, ownedMember classifier, namespace classifier, namespace attribute, ownedMember ownedElement ownedMember namespace ownedMember ownedElement ownedElement namespace ownedMember ownedElement union bool true member feature, subsets ownedMember redefinitionContext redefinedElement true ownedElement true true ownedElement classifier true role, subsets attribute, subsets ownedMember ownedElement ownedElement ownedMember namespace namespace ownedMember ownedMember namespace namespace ownedMember ownedMember namespace ownedElement owner ownedMember namespace ownedElement ownedElement ownedElement ownedElement ownedElement ownedElement redefinitionContext RedefinableElement.redefinedElement RedefinableElement.redefinedElement RedefinableElement.redefinedElement redefinitionContext ownedAttribute ownedMember client ownedElement, subsets clientDependency supplier inGroup containedNode superGroup subgroup ownedEnd 1 navigability Boolean Made this relationship an aggregate so Slots are removed as soon as the definingFeature is removed. MessageOccurrenceSpecification Multiplicity was '*'. Changed to accomodate simpleAttribute Actions Actions (1.0, 0.0, 0.0, 1.0, 334.0, 141.0) 116.0 70.0 0 (1.0, 0.0, 0.0, 1.0, 151.0, 148.0) 100.0 70.0 0 CompleteActions Accept event actions 0 0 1 (1.0, 0.0, 0.0, 1.0, 266.0, 143.0) 102.0 73.0 0 1 0 1 (1.0, 0.0, 0.0, 1.0, -4.0, 314.0) 168.0 52.0 0 0 (1.0, 0.0, 0.0, 1.0, 315.0, 216.0) 0 0 [(0.0, 0.0), (-237.625, 98.0)] 0 0 1 (1.0, 0.0, 0.0, 1.0, 359.0, 315.0) 100.0 51.0 0 0 0 1 (1.0, 0.0, 0.0, 1.0, 508.0, 316.0) 120.0 50.0 0 0 0 1 (1.0, 0.0, 0.0, 1.0, 109.0, 437.0) 116.0 51.0 0 0 (1.0, 0.0, 0.0, 1.0, 131.8125, 366.0) 0 0 [(0.0, 0.0), (1.1875, 71.0)] 0 0 1 (1.0, 0.0, 0.0, 1.0, 164.0, 553.0) 102.0 61.0 0 0 (1.0, 0.0, 0.0, 1.0, 17.0, 366.0) 1 0 [(0.0, 0.0), (0.0, 221.0), (147.0, 221.0)] 0 0 (1.0, 0.0, 0.0, 1.0, 603.0, 366.0) 1 0 [(0.0, 0.0), (0.0, 228.0), (-337.0, 228.0)] 0 0 (1.0, 0.0, 0.0, 1.0, 195.0, 488.0) 0 0 [(0.0, 0.0), (0.0, 65.0)] 0 0 0 1 (1.0, 0.0, 0.0, 1.0, 339.0, 503.0) 120.0 50.0 0 0 (1.0, 0.0, 0.0, 1.0, 569.0, 366.0) 1 0 [(0.0, 0.0), (0.0, 166.0), (-110.0, 166.0)] 0 0 (1.0, 0.0, 0.0, 1.0, 320.0, 216.0) 0 0 [(0.0, 0.0), (88.0, 99.0)] 0 (1.0, 0.0, 0.0, 1.0, 352.0, 216.0) 0 0 [(0.0, 0.0), (211.0, 100.0)] 0 0 1 (1.0, 0.0, 0.0, 1.0, 373.0, 424.54098360655735) 102.0 50.0 0 0 (1.0, 0.0, 0.0, 1.0, 531.0, 366.0) 1 0 [(0.0, 0.0), (0.0, 87.22950819672127), (-56.0, 87.22950819672127)] 0 0 (1.0, 0.0, 0.0, 1.0, 359.0, 348.0) 1 1 [(0.0, 0.0), (-62.0, 0.0), (-62.0, 100.31147540983602), (14.0, 100.31147540983602)] 0 0 (1.0, 0.0, 0.0, 1.0, 428.0, 366.0) 0 0 [(0.0, 0.0), (0.0, 58.540983606557347)] 0 BasicActions Basic actions 1 1 1 (1.0, 0.0, 0.0, 1.0, 138.0, 112.0) 100.0 63.0 0 AcceptEventAction ReplyAction UnmarshallAction AcceptCallAction false isUnmarshall Boolean composite result * composite result * composite returnInformation 1 unmarshallType 1 composite object 1 composite 0 replyValue 1 composite returnInformation 1 Basic invocation actions 0 0 1 (1.0, 0.0, 0.0, 1.0, 208.0, 84.0) 100.0 50.0 0 0 0 1 (1.0, 0.0, 0.0, 1.0, 200.5, 209.0) 116.0 50.0 0 0 (1.0, 0.0, 0.0, 1.0, 257.0, 134.0) 0 0 [(0.0, 0.0), (0.49130434782608745, 75.0)] 0 0 1 (1.0, 0.0, 0.0, 1.0, 386.0, 356.0) 118.0 51.0 0 0 (1.0, 0.0, 0.0, 1.0, 290.7782608695652, 259.0) 0 0 [(0.0, 0.0), (147.2217391304348, 97.0)] 0 0 1 (1.0, 0.0, 0.0, 1.0, 348.0, 518.0) 102.0 50.0 0 0 (1.0, 0.0, 0.0, 1.0, 407.0, 407.0) 0 0 [(0.0, 0.0), (0.0, 111.0)] 0 InvocationAction SendSignalAction composite target input Collaborations Collaboration 0 0 1 (1.0, 0.0, 0.0, 1.0, 288.0, 127.0) 132.0 60.0 0 0 0 1 (1.0, 0.0, 0.0, 1.0, 394.5, 241.0) 100.0 50.0 0 0 0 1 (1.0, 0.0, 0.0, 1.0, 464.0, 127.0) 136.0 60.0 0 0 (1.0, 0.0, 0.0, 1.0, 405.89312977099235, 187.0) 0 0 [(0.0, 0.0), (-0.89312977099234558, 54.0)] 0 (1.0, 0.0, 0.0, 1.0, 478.20895522388059, 187.0) 0 0 [(0.0, 0.0), (-0.20895522388059362, 54.0)] 0 0 1 (1.0, 0.0, 0.0, 1.0, 62.0, 241.0) 136.0 60.0 0 0 (1.0, 0.0, 0.0, 1.0, 198.0, 262.0) 0 0 [(0.0, 0.0), (196.5, 0.0)] 0 0 0 1 (1.0, 0.0, 0.0, 1.0, 74.0, 362.0) 100.0 50.0 0 0 (1.0, 0.0, 0.0, 1.0, 122.0, 301.0) 0 0 [(0.0, 0.0), (0.0, 61.0)] Collaboration collaborationRole * * role Parameter Events 0 0 1 (1.0, 0.0, 0.0, 1.0, 530.0, 101.0) 114.0 50.0 0 0 0 1 (1.0, 0.0, 0.0, 1.0, 237.0, 247.0) 106.0 50.0 0 0 0 1 (1.0, 0.0, 0.0, 1.0, 396.0, 247.0) 100.0 50.0 0 0 0 1 (1.0, 0.0, 0.0, 1.0, 540.0, 247.0) 100.0 50.0 0 0 0 1 (1.0, 0.0, 0.0, 1.0, 718.0, 247.0) 117.0 50.0 0 0 0 1 (1.0, 0.0, 0.0, 1.0, 218.0, 352.0) 135.0 50.0 0 0 0 1 (1.0, 0.0, 0.0, 1.0, 396.0, 352.0) 113.0 51.0 0 0 0 1 (1.0, 0.0, 0.0, 1.0, 544.0, 351.0) 152.0 51.0 0 0 0 1 (1.0, 0.0, 0.0, 1.0, 735.0, 351.0) 130.0 51.0 0 0 (1.0, 0.0, 0.0, 1.0, 530.0, 121.0) 0 0 [(0.0, 0.0), (-237.0, 0.0), (-237.0, 126.0)] 0 (1.0, 0.0, 0.0, 1.0, 530.0, 121.0) 0 0 [(0.0, 0.0), (-81.0, 0.0), (-81.0, 126.0)] 0 (1.0, 0.0, 0.0, 1.0, 592.0, 151.0) 0 0 [(0.0, 0.0), (0.0, 96.0)] 0 (1.0, 0.0, 0.0, 1.0, 644.0, 122.0) 0 0 [(0.0, 0.0), (136.0, 0.0), (136.0, 125.0)] 0 (1.0, 0.0, 0.0, 1.0, 587.0, 297.0) 0 0 [(0.0, 0.0), (0.0, 27.0), (-297.0, 27.0), (-297.0, 55.0)] 0 (1.0, 0.0, 0.0, 1.0, 587.0, 297.0) 0 0 [(0.0, 0.0), (0.0, 27.0), (-134.5, 27.0), (-134.0, 55.0)] 0 (1.0, 0.0, 0.0, 1.0, 587.0, 297.0) 0 0 [(0.0, 0.0), (1.0, 54.0)] 0 (1.0, 0.0, 0.0, 1.0, 587.0, 297.0) 0 0 [(0.0, 0.0), (0.0, 27.0), (216.0, 27.0), (216.0, 54.0)] 0 0 1 (1.0, 0.0, 0.0, 1.0, 235.5, 453.0) 100.0 50.0 0 0 0 1 (1.0, 0.0, 0.0, 1.0, 570.0, 453.0) 100.0 50.0 0 0 0 1 (1.0, 0.0, 0.0, 1.0, 395.0, 453.0) 114.0 50.0 0 0 0 1 (1.0, 0.0, 0.0, 1.0, 743.0, 453.0) 114.0 50.0 0 0 (1.0, 0.0, 0.0, 1.0, 284.0, 402.0) 0 0 [(0.0, 0.0), (0.0, 51.0)] 0 0 (1.0, 0.0, 0.0, 1.0, 451.0, 403.0) 0 0 [(0.0, 0.0), (0.0, 50.0)] 0 0 (1.0, 0.0, 0.0, 1.0, 618.0, 402.0) 0 0 [(0.0, 0.0), (0.0, 51.0)] 0 0 (1.0, 0.0, 0.0, 1.0, 800.0, 402.0) 0 0 [(0.0, 0.0), (0.0, 51.0)] 0 Triggers 0 0 1 (1.0, 0.0, 0.0, 1.0, 319.0, 108.0) 105.0 50.0 0 0 0 1 (1.0, 0.0, 0.0, 1.0, 509.0, 108.0) 138.0 50.0 0 0 0 1 (1.0, 0.0, 0.0, 1.0, 4.0, 231.0) 136.0 50.0 0 0 0 1 (1.0, 0.0, 0.0, 1.0, 324.0, 230.0) 100.0 51.0 0 0 0 1 (1.0, 0.0, 0.0, 1.0, 527.5, 234.0) 100.0 50.0 0 0 (1.0, 0.0, 0.0, 1.0, 579.51094890510944, 158.0) 0 0 [(0.0, 0.0), (-0.51094890510944424, 76.0)] 0 (1.0, 0.0, 0.0, 1.0, 373.0, 158.0) 0 0 [(0.0, 0.0), (0.0, 72.0)] 0 (1.0, 0.0, 0.0, 1.0, 140.0, 256.0) 0 0 [(0.0, 0.0), (184.0, 0.0)] 0 0 (1.0, 0.0, 0.0, 1.0, 424.0, 257.0) 0 0 [(0.0, 0.0), (103.5, 0.0)] 0 Trigger 1 Event 0 1 composite ownedTrigger * ownedMember * event 1 ExecutionEvent CreationEvent 1 MessageEvent DestructionEvent SendOperationEvent SendSignalEvent ReceiveOperationEvent ReceiveSignalEvent Signal Reception 0 owningSignal 1 composite ownedAttribute * 0 signal 1 * composite ownedReception composite ownedReception namespace, classifier, featuringClassifier attribute, ownedMember true ownedMember, feature feature, ownedMember * operation 1 * signal 1 * operation 1 * signal 1 gaphor-0.17.2/gaphor/UML/uml2.override000066400000000000000000000215451220151210700174140ustar00rootroot00000000000000comment vim:sw=4:et:syntax=python This is a file with custom definitions for Gaphors data model. Parts are separated by '%%' (no training spaces) on a line. Comment parts start with 'comment' on the line belowe the percentage symbols, 'override' is used to define a overridden variable. Overrides may in their turn derive from other properties, in that case the 'derives' keyword may be used. It's only useful to declare the associations (and other derived properties) an overridden value depends on, since attributes have been written anyway. Note that no smart things wrt inheritance is done. %% override Element from element import Element %% override Diagram from diagram import Diagram %% override MultiplicityElement.lower derives MultiplicityElement.lowerValue MultiplicityElement.lower = derived('lower', object, 0, 1, MultiplicityElement.lowerValue) MultiplicityElement.lower.filter = lambda obj: [ obj.lowerValue ] #MultiplicityElement.lower = MultiplicityElement.lowerValue %% override MultiplicityElement.upper derives MultiplicityElement.upperValue MultiplicityElement.upper = derived('upper', object, 0, 1, MultiplicityElement.upperValue) MultiplicityElement.upper.filter = lambda obj: [ obj.upperValue ] #MultiplicityElement.upper = MultiplicityElement.upperValue %% override NamedElement.qualifiedName def namedelement_qualifiedname(self): """ Returns the qualified name of the element as a tuple """ if self.namespace: return self.namespace.qualifiedName + (self.name,) else: return (self.name,) NamedElement.qualifiedName = property(namedelement_qualifiedname, doc=namedelement_qualifiedname.__doc__) del namedelement_qualifiedname %% override Association.endType derives Association.memberEnd Property.type # References the classifiers that are used as types of the ends of the # association. Association.endType = derived('endType', Type, 0, '*', Association.memberEnd, Property.type) Association.endType.filter = lambda self: [end.type for end in self.memberEnd if end] %% override Class.extension derives Extension.metaclass def class_extension(self): return list(self._factory.select(lambda e: e.isKindOf(Extension) and self is e.metaclass)) # TODO: use those as soon as Extension.metaclass can be used. #Class.extension = derived('extension', Extension, 0, '*', Extension.metaclass) #Class.extension.filter = class_extension Class.extension = property(class_extension, doc=\ """References the Extensions that specify additional properties of the metaclass. The property is derived from the extensions whose memberEnds are typed by the Class.""") del class_extension %% override Extension.metaclass derives Extension.ownedEnd Association.memberEnd def extension_metaclass(self): ownedEnd = self.ownedEnd metaend = [e for e in self.memberEnd if e is not ownedEnd] if metaend: return metaend[0].type # Don't use derived now, as derived() does not properly propagate the events # NOTE: let function return a list once this can be turned on #Extension.metaclass = derived('metaclass', Class, 0, 1, Extension.ownedEnd, Association.memberEnd) #Extension.metaclass.filter = extension_metaclass Extension.metaclass = property(extension_metaclass, doc=\ """References the Class that is extended through an Extension. The property is derived from the type of the memberEnd that is not the ownedEnd.""") del extension_metaclass %% override Classifier.inheritedMember Classifier.inheritedMember = derivedunion('inheritedMember', NamedElement, 0, '*') %% override Classifier.general def classifier_general(self): return [g.general for g in self.generalization] Classifier.general = property(classifier_general, doc=""" Return a list of all superclasses for class (iterating the Generalizations. """) del classifier_general %% override Class.superClass Class.superClass = Classifier.general %% override Namespace.importedMember Namespace.importedMember = derivedunion('importedMember', PackageableElement, 0, '*') %% override Property.opposite def property_opposite(self): """ In the case where the property is one navigable end of a binary association with both ends navigable, this gives the other end. For Gaphor the property on the other end is returned regardless the navigability. """ if self.association is not None and len(self.association.memberEnd) == 2: return self.association.memberEnd[0] is self \ and self.association.memberEnd[1] \ or self.association.memberEnd[0] return None Property.opposite = property(property_opposite, doc=property_opposite.__doc__) del property_opposite %% override Property.isComposite derives Property.aggregation #Property.isComposite = property(lambda self: self.aggregation == intern('composite')) Property.isComposite = derivedunion('isComposite', bool, 0, 1, Property.aggregation) Property.isComposite.filter = lambda obj: [obj.aggregation == intern('composite')] %% override Constraint.context Constraint.context = derivedunion('context', Namespace, 0, 1) %% override Property.navigability def property_navigability(self): """ Get navigability of an association end. If no association is related to the property, then unknown navigability is assumed. """ assoc = self.association if not assoc or not self.opposite: return None # assume unknown owner = self.opposite.type if owner and ((type(self.type) in (Class, Interface) \ and self in owner.ownedAttribute) \ or self in assoc.navigableOwnedEnd): return True elif self in assoc.ownedEnd: return None else: return False Property.navigability = property(property_navigability, doc=property_navigability.__doc__) del property_navigability %% override Operation.type Operation.type = derivedunion('type', DataType, 0, 1) %% override Lifeline.parse from umllex import parse_lifeline Lifeline.parse = parse_lifeline del parse_lifeline %% override Lifeline.render from umllex import render_lifeline Lifeline.render = render_lifeline del render_lifeline %% override Extenstion.metaclass def extension_metaclass(self): """ References the Class that is extended through an Extension. The property is derived from the type of the memberEnd that is not the ownedEnd. """ for m in self.memberEnd: if m not in self.ownedEnd: return m return None Extenstion.metaclass = property(extension_metaclass, doc=extension_metaclass.__doc__) del extension_metaclass %% override Component.provided import itertools def _pr_interface_deps(classifier, dep_type): """ Return all interfaces, which are connected to a classifier with given dependency type. """ return (dep.supplier[0] for dep in classifier.clientDependency \ if dep.isKindOf(dep_type) and dep.supplier[0].isKindOf(Interface)) def _pr_rc_interface_deps(component, dep_type): """ Return all interfaces, which are connected to realizing classifiers of specified component. Returned interfaces are connected to realizing classifiers with given dependency type. Generator of generators is returned. Do not forget to flat it later. """ return (_pr_interface_deps(r.realizingClassifier, dep_type) for r in component.realization) def component_provided(self): implementations = (impl.contract[0] for impl in self.implementation if impl.isKindOf(Implementation)) realizations = _pr_interface_deps(self, Realization) # realizing classifiers realizations # this generator of generators, so flatten it later rc_realizations = _pr_rc_interface_deps(self, Realization) return tuple(set(itertools.chain(implementations, realizations, *rc_realizations))) Component.provided = property(component_provided, doc=""" Interfaces provided to component environment. """) del component_provided %% override Component.required def component_required(self): usages = _pr_interface_deps(self, Usage) # realizing classifiers usages # this generator of generators, so flatten it later rc_usages = _pr_rc_interface_deps(self, Usage) return tuple(set(itertools.chain(usages, *rc_usages))) Component.required = property(component_required, doc=""" Interfaces required by component. """) del component_required %% override Message.messageKind def message_messageKind(self): kind = 'unknown' if self.sendEvent: kind = 'lost' if self.receiveEvent: kind = 'complete' elif self.receiveEvent: kind = 'found' return kind Message.messageKind = property(message_messageKind, doc=""" MessageKind """) del message_messageKind %% override StructuredClassifier.part def structuredclassifier_part(self): return tuple(a for a in self.ownedAttribute if a.isComposite) StructuredClassifier.part = property(structuredclassifier_part, doc=""" Properties owned by a classifier by composition. """) del structuredclassifier_part gaphor-0.17.2/gaphor/UML/umlfmt.py000066400000000000000000000134751220151210700166550ustar00rootroot00000000000000""" Formatting of UML elements like attributes, operations, stereotypes, etc. """ import re from cStringIO import StringIO from simplegeneric import generic from gaphor.UML import uml2 as UML @generic def format(el, pattern=None): """ Format an UML element. """ raise NotImplementedError('Format routine for type %s not implemented yet' \ % type(el)) @format.when_type(UML.Property) def format_property(el, pattern=None, *args, **kwargs): """ Format property or an association end. """ if el.association and not (args or kwargs): return format_association_end(el) else: return format_attribute(el, *args, **kwargs) def compile(regex): return re.compile(regex, re.MULTILINE | re.S) # Do not render if the name still contains a visibility element no_render_pat = compile(r'^\s*[+#-]') vis_map = { 'public': '+', 'protected': '#', 'package': '~', 'private': '-' } def format_attribute(el, visibility=False, is_derived=False, type=False, multiplicity=False, default=False, tags=False): """ Create a OCL representation of the attribute, Returns the attribute as a string. If one or more of the parameters (visibility, is_derived, type, multiplicity, default and/or tags) is set, only that field is rendered. Note that the name of the attribute is always rendered, so a parseable string is returned. Note that, when some of those parameters are set, parsing the string will not give you the same result. """ name = el.name if not name: name = '' if no_render_pat.match(name): name = '' # Render all fields if they all are set to False if not (visibility or is_derived or type or multiplicity or default): visibility = is_derived = type = multiplicity = default = True s = StringIO() if visibility: s.write(vis_map[el.visibility]) s.write(' ') if is_derived: if el.isDerived: s.write('/') s.write(name) if type and el.typeValue: s.write(': %s' % el.typeValue) if multiplicity and el.upperValue: if el.lowerValue: s.write('[%s..%s]' % (el.lowerValue, el.upperValue)) else: s.write('[%s]' % el.upperValue) if default and el.defaultValue: s.write(' = %s' % el.defaultValue) if tags: slots = [] for slot in el.appliedStereotype[:].slot: if slot: slots.append('%s=%s' % (slot.definingFeature.name, slot.value)) if slots: s.write(' { %s }' % ', '.join(slots)) s.reset() return s.read() def format_association_end(el): """ Format association end. """ name = '' n = StringIO() if el.name: n.write(vis_map[el.visibility]) n.write(' ') if el.isDerived: n.write('/') if el.name: n.write(el.name) n.reset() name = n.read() m = StringIO() if el.upperValue: if el.lowerValue: m.write('%s..%s' % (el.lowerValue, el.upperValue)) else: m.write('%s' % el.upperValue) slots = [] for slot in el.appliedStereotype[:].slot: if slot: slots.append('%s=%s' % (slot.definingFeature.name, slot.value)) if slots: m.write(' { %s }' % ',\n'.join(slots)) m.reset() mult = m.read() return name, mult @format.when_type(UML.Operation) def format_operation(el, pattern=None, visibility=False, type=False, multiplicity=False, default=False, tags=False, direction=False): """ Create a OCL representation of the operation, Returns the operation as a string. """ name = el.name if not name: return '' if no_render_pat.match(name): return name # Render all fields if they all are set to False if not (visibility or type or multiplicity or default or tags or direction): visibility = type = multiplicity = default = tags = direction = True s = StringIO() if visibility: s.write(vis_map[el.visibility]) s.write(' ') s.write(name) s.write('(') for p in el.formalParameter: if direction: s.write(p.direction) s.write(' ') s.write(p.name) if type and p.typeValue: s.write(': %s' % p.typeValue) if multiplicity and p.upperValue: if p.lowerValue: s.write('[%s..%s]' % (p.lowerValue, p.upperValue)) else: s.write('[%s]' % p.upperValue) if default and p.defaultValue: s.write(' = %s' % p.defaultValue) #if p.taggedValue: # tvs = ', '.join(filter(None, map(getattr, p.taggedValue, # ['value'] * len(p.taggedValue)))) # s.write(' { %s }' % tvs) if p is not el.formalParameter[-1]: s.write(', ') s.write(')') rr = el.returnResult and el.returnResult[0] if rr: if type and rr.typeValue: s.write(': %s' % rr.typeValue) if multiplicity and rr.upperValue: if rr.lowerValue: s.write('[%s..%s]' % (rr.lowerValue, rr.upperValue)) else: s.write('[%s]' % rr.upperValue) #if rr.taggedValue: # tvs = ', '.join(filter(None, map(getattr, rr.taggedValue, # ['value'] * len(rr.taggedValue)))) # if tvs: # s.write(' { %s }' % tvs) s.reset() return s.read() @format.when_type(UML.Slot) def format_slot(el, pattern=None): return '%s = "%s"' % (el.definingFeature.name, el.value) @format.when_type(UML.NamedElement) def format_namedelement(el, pattern='%s'): """ Format named element. """ return pattern % el.name # vim:sw=4:et:ai gaphor-0.17.2/gaphor/UML/umllex.py000066400000000000000000000226571220151210700166610ustar00rootroot00000000000000""" Lexical analizer for attributes and operations. In this module some parse functions are added for attributes and operations. The regular expressions are constructed based on a series of "sub-patterns". This makes it easy to identify the autonomy of an attribute/operation. """ __all__ = [ 'parse_property', 'parse_operation', ] import re from simplegeneric import generic #from gaphor.UML import uml2 as UML import uml2 as UML @generic def parse(el, text): """ Parser for an UML element. """ raise NotImplementedError('Parsing routine for type %s not implemented yet' \ % type(el)) # Visibility (optional) ::= '+' | '-' | '#' vis_subpat = r'\s*(?P[-+#])?' # Derived value (optional) ::= [/] derived_subpat = r'\s*(?P/)?' # name (required) ::= name name_subpat = r'\s*(?P[a-zA-Z_]\w*)' # Multiplicity (added to type_subpat) ::= '[' [mult_l ..] mult_u ']' mult_subpat = r'\s*(\[\s*((?P[0-9]+)\s*\.\.)?\s*(?P([0-9]+|\*))\s*\])?' multa_subpat = r'\s*(\[?((?P[0-9]+)\s*\.\.)?\s*(?P([0-9]+|\*))\]?)?' # Type and multiplicity (optional) ::= ':' type [mult] type_subpat = r'\s*(:\s*(?P\w+)\s*' + mult_subpat + r')?' # default value (optional) ::= '=' default default_subpat = r'\s*(=\s*(?P\S+))?' # tagged values (optional) ::= '{' tags '}' tags_subpat = r'\s*(\{\s*(?P.*?)\s*\})?' # Parameters (required) ::= '(' [params] ')' params_subpat = r'\s*\(\s*(?P[^)]+)?\)' # Possible other parameters (optional) ::= ',' rest rest_subpat = r'\s*(,\s*(?P.*))?' # Direction of a parameter (optional, default in) ::= 'in' | 'out' | 'inout' dir_subpat = r'\s*((?Pin|out|inout)\s)?' # Some trailing garbage => no valid syntax... garbage_subpat = r'\s*(?P.*)' def compile(regex): return re.compile(regex, re.MULTILINE | re.S) # Attribute: # [+-#] [/] name [: type[\[mult\]]] [= default] [{ tagged values }] attribute_pat = compile(r'^' + vis_subpat + derived_subpat + name_subpat + type_subpat + default_subpat + tags_subpat + garbage_subpat) # Association end name: # [[+-#] [/] name [\[mult\]]] [{ tagged values }] association_end_name_pat = compile(r'^' + '(' + vis_subpat + derived_subpat + name_subpat + mult_subpat + ')?' + tags_subpat + garbage_subpat) # Association end multiplicity: # [mult] [{ tagged values }] association_end_mult_pat = compile(r'^' + multa_subpat + tags_subpat + garbage_subpat) # Operation: # [+|-|#] name ([parameters]) [: type[\[mult\]]] [{ tagged values }] operation_pat = compile(r'^' + vis_subpat + name_subpat + params_subpat + type_subpat + tags_subpat + garbage_subpat) # One parameter supplied with an operation: # [in|out|inout] name [: type[\[mult\]] [{ tagged values }] parameter_pat = compile(r'^' + dir_subpat + name_subpat + type_subpat + default_subpat + tags_subpat + rest_subpat) # Lifeline: # [name] [: type] lifeline_pat = compile('^' + name_subpat + type_subpat + garbage_subpat) def _set_visibility(el, vis): if vis == '+': el.visibility = 'public' elif vis == '#': el.visibility = 'protected' elif vis == '~': el.visibility = 'package' elif vis == '-': el.visibility = 'private' else: try: del el.visibility except AttributeError: pass def parse_attribute(el, s): """ Parse string s in the property. Tagged values, multiplicity and stuff like that is altered to reflect the data in the property string. """ m = attribute_pat.match(s) if not m or m.group('garbage'): el.name = s del el.visibility del el.isDerived if el.typeValue: el.typeValue = None if el.lowerValue: el.lowerValue = None if el.upperValue: el.upperValue = None if el.defaultValue: el.defaultValue = None else: g = m.group create = el._factory.create _set_visibility(el, g('vis')) el.isDerived = g('derived') and True or False el.name = g('name') el.typeValue = g('type') el.lowerValue = g('mult_l') el.upperValue = g('mult_u') el.defaultValue = g('default') # Skip tags: should do something with stereotypes? #tags = g('tags') #if tags: # for t in map(str.strip, tags.split(',')): # tv = create(UML.LiteralSpecification) # tv.value = t # el.taggedValue = tv def parse_association_end(el, s): """ Parse the text at one end of an association. The association end holds two strings. It is automattically figured out which string is fed to the parser. """ create = el._factory.create # if no name, then clear as there could be some garbage # due to previous parsing (i.e. '[1' m = association_end_name_pat.match(s) if m and not m.group('name'): el.name = None # clear also multiplicity if no characters in ``s`` m = association_end_mult_pat.match(s) if m and not m.group('mult_u'): if el.upperValue: el.upperValue = None if m and m.group('mult_u') or m.group('tags'): g = m.group el.lowerValue = g('mult_l') el.upperValue = g('mult_u') #tags = g('tags') #if tags: # for t in map(str.strip, tags.split(',')): # tv = create(UML.LiteralSpecification) # tv.value = t # el.taggedValue = tv else: m = association_end_name_pat.match(s) g = m.group if g('garbage'): el.name = s del el.visibility del el.isDerived else: _set_visibility(el, g('vis')) el.isDerived = g('derived') and True or False el.name = g('name') # Optionally, the multiplicity and tagged values may be defined: if g('mult_l'): el.lowerValue = g('mult_l') if g('mult_u'): if not g('mult_l'): el.lowerValue = None el.upperValue = g('mult_u') #tags = g('tags') #if tags: # while el.taggedValue: # el.taggedValue[0].unlink() # for t in map(str.strip, tags.split(',')): # tv = create(UML.LiteralSpecification) # tv.value = t # el.taggedValue = tv @parse.when_type(UML.Property) def parse_property(el, s): if el.association: parse_association_end(el, s) else: parse_attribute(el, s) @parse.when_type(UML.Operation) def parse_operation(el, s): """ Parse string s in the operation. Tagged values, parameters and visibility is altered to reflect the data in the operation string. """ m = operation_pat.match(s) if not m or m.group('garbage'): el.name = s del el.visibility map(UML.Parameter.unlink, list(el.returnResult)) map(UML.Parameter.unlink, list(el.formalParameter)) else: g = m.group create = el._factory.create _set_visibility(el, g('vis')) el.name = g('name') if not el.returnResult: el.returnResult = create(UML.Parameter) p = el.returnResult[0] p.direction = 'return' p.typeValue = g('type') p.lowerValue = g('mult_l') p.upperValue = g('mult_u') # FIXME: Maybe add to Operation.ownedRule? #tags = g('tags') #if tags: # for t in map(str.strip, tags.split(',')): # tv = create(UML.LiteralSpecification) # tv.value = t # p.taggedValue = tv pindex = 0 params = g('params') while params: m = parameter_pat.match(params) if not m: break g = m.group try: p = el.formalParameter[pindex] except IndexError: p = create(UML.Parameter) p.direction = g('dir') or 'in' p.name = g('name') p.typeValue = g('type') p.lowerValue = g('mult_l') p.upperValue = g('mult_u') p.defaultValue = g('default') #tags = g('tags') #if tags: # for t in map(str.strip, tags.split(',')): # tv = create(UML.LiteralSpecification) # tv.value = t # p.taggedValue = tv el.formalParameter = p # Do the next parameter: params = g('rest') pindex += 1 # Remove remaining parameters: for fp in el.formalParameter[pindex:]: fp.unlink() def parse_lifeline(el, s): """ Parse string s in a lifeline. If a class is defined and can be found in the datamodel, then a class is connected to the lifelines 'represents' property. """ m = lifeline_pat.match(s) g = m.group if not m or g('garbage'): el.name = s if hasattr(el, 'represents'): del el.represents else: el.name = g('name') + ": " t = g('type') if t: el.name += ': ' + t # In the near future the data model should be extended with # Lifeline.represents: ConnectableElement def render_lifeline(el): """ """ return el.name @parse.when_type(UML.NamedElement) def parse_namedelement(el, text): """ Parse named element by simply assigning text to its name. """ el.name = text # vim:sw=4:et:ai gaphor-0.17.2/gaphor/__init__.py000066400000000000000000000056001220151210700164420ustar00rootroot00000000000000#!/usr/bin/env python """This is Gaphor, a Python+GTK based UML modelling tool. This module allows Gaphor to be launched from the command line. The main() function sets up the command-line options and arguments and passes them to the main Application instance.""" __all__ = [ 'main' ] from optparse import OptionParser import logging import pygtk from gaphor.application import Application pygtk.require('2.0') LOG_FORMAT = '%(name)s %(levelname)s %(message)s' def launch(model=None): """Start the main application by initiating and running Application. The file_manager service is used here to load a Gaphor model if one was specified on the command line. Otherwise, a new model is created and the Gaphor GUI is started.""" # Make sure gui is loaded ASAP. # This prevents menu items from appearing at unwanted places. Application.essential_services.append('main_window') Application.init() main_window = Application.get_service('main_window') main_window.open() file_manager = Application.get_service('file_manager') if model: file_manager.load(model) else: file_manager.action_new() Application.run() def main(): """Start Gaphor from the command line. This function creates an option parser for retrieving arguments and options from the command line. This includes a Gaphor model to load. The application is then initialized, passing along the option parser. This provides and plugins and services with access to the command line options and may add their own.""" parser = OptionParser() parser.add_option('-p',\ '--profiler',\ action='store_true',\ help='Run in profiler') parser.add_option('-q', "--quiet", dest='quiet', help='Quiet output', default=False, action='store_true') parser.add_option('-v', '--verbose', dest='verbose', help='Verbose output', default=False, action="store_true") options, args = parser.parse_args() if options.verbose: logging.basicConfig(level=logging.DEBUG, format=LOG_FORMAT) elif options.quiet: logging.basicConfig(level=logging.WARNING, format=LOG_FORMAT) else: logging.basicConfig(level=logging.INFO, format=LOG_FORMAT) try: model = args[0] except IndexError: model = None if options.profiler: import cProfile import pstats cProfile.run('import gaphor; gaphor.launch()',\ 'gaphor.prof') profile_stats = pstats.Stats('gaphor.prof') profile_stats.strip_dirs().sort_stats('time').print_stats(50) else: launch(model) # TODO: Remove this. import __builtin__ __builtin__.__dict__['log'] = logging.getLogger('Gaphor') # vim:sw=4:et:ai gaphor-0.17.2/gaphor/action.py000066400000000000000000000156131220151210700161650ustar00rootroot00000000000000""" Support for actions in generic files. See also gaphor/service/actionmanager.py for the management module. """ from __future__ import absolute_import from .application import Application class action(object): """ Decorator. Turns a regular function (/method) into a full blown Action class. >>> class A(object): ... @action(name="my_action", label="my action") ... def myaction(self): ... print 'action called' >>> a = A() >>> a.myaction() action called >>> is_action(a.myaction) True >>> for method in dir(A): ... if is_action(getattr(A, method)): ... print method myaction >>> A.myaction.__action__.name 'my_action' >>> A.myaction.__action__.label 'my action' """ def __init__(self, name, label=None, tooltip=None, stock_id=None, accel=None, **kwargs): self.name = name self.label = label self.tooltip = tooltip self.stock_id = stock_id self.accel = accel self.__dict__.update(kwargs) def __call__(self, func): func.__action__ = self return func class toggle_action(action): """ A toggle button can be switched on and off. An extra 'active' attribute is provided than gives the initial status. """ def __init__(self, name, label=None, tooltip=None, stock_id=None, accel=None, active=False): super(toggle_action, self).__init__(name, label, tooltip, stock_id, accel=accel, active=active) class radio_action(action): """ Radio buttons take a list of names, a list of labels and a list of tooltips (and optionally, a list of stock_ids). The callback function should have an extra value property, which is given the index number of the activated radio button action. """ def __init__(self, names, labels=None, tooltips=None, stock_ids=None, accels=None, active=0): super(radio_action, self).__init__(names[0], names=names, labels=labels, tooltips=tooltips, stock_ids=stock_ids, accels=accels, active=active) def open_action(name, label=None, tooltip=None, stock_id=None, accel=None, **kwargs): """ Special action used to indicate the action that is used to open (show) a UI component. >>> class A(object): ... @open_action(name="my_action", label="my action") ... def myaction(self): ... print 'action called' >>> a = A() >>> a.myaction() action called >>> is_action(a.myaction) True >>> for method in dir(A): ... if is_action(getattr(A, method)): ... print method myaction >>> A.myaction.__action__.name 'my_action' >>> A.myaction.__action__.label 'my action' >>> A.myaction.__action__.opening True """ return action(name, label, tooltip, stock_id, accel, opening=True) def is_action(func): return bool(getattr(func, '__action__', False)) def build_action_group(obj, name=None): """ Build actions and a gtk.ActionGroup for each Action instance found in obj() (that's why Action is a class ;) ). This function requires GTK+. >>> class A(object): ... @action(name='bar') ... def bar(self): print 'Say bar' ... @toggle_action(name='foo') ... def foo(self, active): print 'Say foo', active ... @radio_action(names=('baz', 'beer'), labels=('Baz', 'Beer')) ... def baz(self, value): ... print 'Say', value, (value and 'beer' or 'baz') >>> group = build_action_group(A()) Say 0 baz >>> len(group.list_actions()) 4 >>> a = group.get_action('bar') >>> a.activate() Say bar >>> group.get_action('foo').activate() Say foo True >>> group.get_action('beer').activate() Say 1 beer >>> group.get_action('baz').activate() Say 0 baz """ import gtk group = gtk.ActionGroup(name or obj) objtype = type(obj) for attrname in dir(obj): try: # Fetch the methods from the object's type instead of the object # itself. This prevents some desciptors (mainly gaphor.core.inject) # from executing. # Otherwise stuff like dependency resolving (=inject) may kick in # too early. method = getattr(objtype, attrname) except: continue act = getattr(method, '__action__', None) if isinstance(act, radio_action): actgroup = None if not act.labels: act.labels = [None] * len(act.names) if not act.tooltips: act.tooltips = [None] * len(act.names) if not act.stock_ids: act.stock_ids = [None] * len(act.names) if not act.accels: act.accels = [None] * len(act.names) assert len(act.names) == len(act.labels) assert len(act.names) == len(act.tooltips) assert len(act.names) == len(act.stock_ids) assert len(act.names) == len(act.accels) for i, n in enumerate(act.names): gtkact = gtk.RadioAction(n, act.labels[i], act.tooltips[i], act.stock_ids[i], value=i) if not actgroup: actgroup = gtkact else: gtkact.props.group = actgroup group.add_action_with_accel(gtkact, act.accels[i]) actgroup.connect('changed', _radio_action_changed, obj, attrname) actgroup.set_current_value(act.active) elif isinstance(act, toggle_action): gtkact = gtk.ToggleAction(act.name, act.label, act.tooltip, act.stock_id) gtkact.set_property('active', act.active) gtkact.connect('activate', _toggle_action_activate, obj, attrname) group.add_action_with_accel(gtkact, act.accel) elif isinstance(act, action): gtkact = gtk.Action(act.name, act.label, act.tooltip, act.stock_id) try: activate = act.opening and _action_opening or _action_activate except AttributeError: activate = _action_activate gtkact.connect('activate', activate, obj, attrname) group.add_action_with_accel(gtkact, act.accel) elif act is not None: raise TypeError, 'Invalid action type: %s' % action return group def _action_activate(action, obj, name): method = getattr(obj, name) method() def _action_opening(action, obj, name): """ Open a new UI component if it is returned from the opening action. Otherwise do nothing. """ method = getattr(obj, name) ui_comp = method() if ui_comp: Application.get_service('main_window').create_item(ui_comp) def _toggle_action_activate(action, obj, name): method = getattr(obj, name) method(action.props.active) def _radio_action_changed(action, current_action, obj, name): method = getattr(obj, name) method(current_action.props.value) if __name__ == '__main__': import doctest doctest.testmod() # vim:sw=4:et:ai gaphor-0.17.2/gaphor/adapters/000077500000000000000000000000001220151210700161335ustar00rootroot00000000000000gaphor-0.17.2/gaphor/adapters/__init__.py000066400000000000000000000011661220151210700202500ustar00rootroot00000000000000 import gaphor.adapters.connectors import gaphor.adapters.editors import gaphor.adapters.actions.flowconnect import gaphor.adapters.actions.partitionpage import gaphor.adapters.classes.classconnect import gaphor.adapters.classes.interfaceconnect import gaphor.adapters.components.connectorconnect import gaphor.adapters.interactions.messageconnect import gaphor.adapters.profiles.extensionconnect import gaphor.adapters.profiles.stereotypespage import gaphor.adapters.profiles.metaclasseditor import gaphor.adapters.usecases.usecaseconnect import gaphor.adapters.states.vertexconnect import gaphor.adapters.states.propertypages gaphor-0.17.2/gaphor/adapters/actions/000077500000000000000000000000001220151210700175735ustar00rootroot00000000000000gaphor-0.17.2/gaphor/adapters/actions/__init__.py000066400000000000000000000000001220151210700216720ustar00rootroot00000000000000gaphor-0.17.2/gaphor/adapters/actions/flowconnect.py000066400000000000000000000174071220151210700224770ustar00rootroot00000000000000""" Flow item adapter connections. """ from gaphor.adapters.connectors import UnaryRelationshipConnect, RelationshipConnect from zope import interface, component from gaphor import UML from gaphor.diagram import items from gaphor.diagram.interfaces import IConnect class FlowConnect(UnaryRelationshipConnect): """ Connect FlowItem and Action/ObjectNode, initial/final nodes. """ def allow(self, handle, port): line = self.line subject = self.element.subject if handle is line.head and isinstance(subject, UML.FinalNode) \ or handle is line.tail and isinstance(subject, UML.InitialNode): return None return super(FlowConnect, self).allow(handle, port) def reconnect(self, handle, port): line = self.line log.debug('Reconnection of %s (guard %s)' % (line.subject, line.subject.guard)) old_flow = line.subject # Secure properties before old_flow is removed: name = old_flow.name guard_value = old_flow.guard self.connect_subject(handle) relation = line.subject if old_flow: relation.name = name if guard_value: relation.guard = guard_value log.debug('unlinking old flow instance %s' % old_flow) #old_flow.unlink() def connect_subject(self, handle): line = self.line element = self.element # TODO: connect opposite side again (in case it's a join/fork or # decision/merge node) c1 = self.get_connected(line.head) c2 = self.get_connected(line.tail) if isinstance(c1, items.ObjectNodeItem) or isinstance(c2, items.ObjectNodeItem): relation = self.relationship_or_new(UML.ObjectFlow, UML.ObjectFlow.source, UML.ObjectFlow.target) else: relation = self.relationship_or_new(UML.ControlFlow, UML.ControlFlow.source, UML.ControlFlow.target) line.subject = relation opposite = line.opposite(handle) otc = self.get_connected(opposite) if opposite and isinstance(otc, (items.ForkNodeItem, items.DecisionNodeItem)): adapter = component.queryMultiAdapter((otc, line), IConnect) adapter.combine_nodes() def disconnect_subject(self, handle): log.debug('Performing disconnect for handle %s' % handle) super(FlowConnect, self).disconnect_subject(handle) line = self.line opposite = line.opposite(handle) otc = self.get_connected(opposite) if opposite and isinstance(otc, (items.ForkNodeItem, items.DecisionNodeItem)): adapter = component.queryMultiAdapter((otc, line), IConnect) adapter.decombine_nodes() component.provideAdapter(factory=FlowConnect, adapts=(items.ActionItem, items.FlowItem)) component.provideAdapter(factory=FlowConnect, adapts=(items.ActivityNodeItem, items.FlowItem)) component.provideAdapter(factory=FlowConnect, adapts=(items.ObjectNodeItem, items.FlowItem)) component.provideAdapter(factory=FlowConnect, adapts=(items.SendSignalActionItem, items.FlowItem)) component.provideAdapter(factory=FlowConnect, adapts=(items.AcceptEventActionItem, items.FlowItem)) class FlowForkDecisionNodeConnect(FlowConnect): """ Abstract class with common behaviour for Fork/Join node and Decision/Merge node. """ def allow(self, handle, port): # No cyclic connect is possible on a Flow/Decision node: head, tail = self.line.head, self.line.tail subject = self.element.subject hct = self.get_connected(head) tct = self.get_connected(tail) if handle is head and tct and tct.subject is subject \ or handle is tail and hct and hct.subject is subject: return None return super(FlowForkDecisionNodeConnect, self).allow(handle, port) def combine_nodes(self): """ Combine join/fork or decision/methe nodes into one diagram item. """ fork_node_cls = self.fork_node_cls join_node_cls = self.join_node_cls line = self.line element = self.element subject = element.subject if len(subject.incoming) > 1 and len(subject.outgoing) < 2: self.element_factory.swap_element(subject, join_node_cls) element.request_update() elif len(subject.incoming) < 2 and len(subject.outgoing) > 1: self.element_factory.swap_element(subject, fork_node_cls) element.request_update() elif not element.combined and len(subject.incoming) > 1 and len(subject.outgoing) > 1: join_node = subject # determine flow class: if [ f for f in join_node.incoming if isinstance(f, UML.ObjectFlow) ]: flow_class = UML.ObjectFlow else: flow_class = UML.ControlFlow self.element_factory.swap_element(join_node, join_node_cls) fork_node = self.element_factory.create(fork_node_cls) for flow in list(join_node.outgoing): flow.source = fork_node flow = self.element_factory.create(flow_class) flow.source = join_node flow.target = fork_node element.combined = fork_node def decombine_nodes(self): """ Decombine join/fork or decision/merge nodes. """ fork_node_cls = self.fork_node_cls join_node_cls = self.join_node_cls line = self.line element = self.element if element.combined: join_node = element.subject cflow = join_node.outgoing[0] # combining flow fork_node = cflow.target assert fork_node is element.combined assert isinstance(join_node, join_node_cls) assert isinstance(fork_node, fork_node_cls) if len(join_node.incoming) < 2 or len(fork_node.outgoing) < 2: # Move all outgoing edges to the first node (the join node): for f in list(fork_node.outgoing): f.source = join_node cflow.unlink() fork_node.unlink() # swap subject to fork node if outgoing > 1 if len(join_node.outgoing) > 1: assert len(join_node.incoming) < 2 self.element_factory.swap_element(join_node, fork_node_cls) element.combined = None def connect_subject(self, handle): """ In addition to a subject connect, the subject of the element may be changed. For readability, parameters are named afther the classes used by Join/Fork nodes. """ super(FlowForkDecisionNodeConnect, self).connect_subject(handle) # Switch class for self.element Join/Fork depending on the number # of incoming/outgoing edges. self.combine_nodes() def disconnect_subject(self, handle): super(FlowForkDecisionNodeConnect, self).disconnect_subject(handle) if self.element.combined: self.decombine_nodes() class FlowForkNodeConnect(FlowForkDecisionNodeConnect): """ Connect Flow to a ForkNode. """ component.adapts(items.ForkNodeItem, items.FlowItem) fork_node_cls=UML.ForkNode join_node_cls=UML.JoinNode component.provideAdapter(FlowForkNodeConnect) class FlowDecisionNodeConnect(FlowForkDecisionNodeConnect): """ Connect Flow to a DecisionNode """ component.adapts(items.DecisionNodeItem, items.FlowItem) fork_node_cls = UML.DecisionNode join_node_cls = UML.MergeNode component.provideAdapter(FlowDecisionNodeConnect) # vim:sw=4:et:ai gaphor-0.17.2/gaphor/adapters/actions/partitionpage.py000066400000000000000000000031161220151210700230140ustar00rootroot00000000000000""" Activity partition property page. """ import gtk from gaphor.core import _, inject, transactional from gaphor.ui.interfaces import IPropertyPage from gaphor.diagram import items from zope import interface, component from gaphor import UML from gaphor.adapters.propertypages import NamedItemPropertyPage class PartitionPropertyPage(NamedItemPropertyPage): """ Partition property page. """ interface.implements(IPropertyPage) component.adapts(items.PartitionItem) element_factory = inject('element_factory') def construct(self): item = self.item page = super(PartitionPropertyPage, self).construct() if item.subject: if not item._toplevel: hbox = gtk.HBox(spacing=12) button = gtk.CheckButton(_('External')) button.set_active(item.subject.isExternal) button.connect('toggled', self._on_external_change) hbox.pack_start(button) page.pack_start(hbox, expand=False) else: pass #hbox = gtk.HBox(spacing=12) #button = gtk.CheckButton(_('Dimension')) #button.set_active(item.subject.isDimension) #button.connect('toggled', self._on_dimension_change) return page @transactional def _on_external_change(self, button): item = self.item if item.subject: item.subject.isExternal = button.get_active() item.request_update() component.provideAdapter(PartitionPropertyPage, name='Properties') # vim:sw=4:et:ai gaphor-0.17.2/gaphor/adapters/actions/tests/000077500000000000000000000000001220151210700207355ustar00rootroot00000000000000gaphor-0.17.2/gaphor/adapters/actions/tests/__init__.py000066400000000000000000000000001220151210700230340ustar00rootroot00000000000000gaphor-0.17.2/gaphor/adapters/actions/tests/test_flow.py000066400000000000000000000416321220151210700233230ustar00rootroot00000000000000""" Flow item connection adapters tests. """ from gaphor.tests import TestCase from gaphor import UML from gaphor.diagram import items class FlowItemBasicNodesConnectionTestCase(TestCase): """ Tests for flow item connecting to basic activity nodes. """ def test_initial_node_glue(self): """Test flow item glueing to initial node item """ flow = self.create(items.FlowItem) node = self.create(items.InitialNodeItem, UML.InitialNode) # tail may not connect to initial node item allowed = self.allow(flow, flow.tail, node) self.assertFalse(allowed) allowed = self.allow(flow, flow.head, node) self.assertTrue(allowed) def test_flow_final_node_glue(self): """Test flow item glueing to flow final node item """ flow = self.create(items.FlowItem) node = self.create(items.FlowFinalNodeItem, UML.FlowFinalNode) # head may not connect to flow final node item allowed = self.allow(flow, flow.head, node) self.assertFalse(allowed) allowed = self.allow(flow, flow.tail, node) self.assertTrue(allowed) def test_activity_final_node_glue(self): """Test flow item glueing to activity final node item """ flow = self.create(items.FlowItem) node = self.create(items.ActivityFinalNodeItem, UML.ActivityFinalNode) # head may not connect to activity final node item glued = self.allow(flow, flow.head, node) self.assertFalse(glued) glued = self.allow(flow, flow.tail, node) self.assertTrue(glued) class FlowItemObjectNodeTestCase(TestCase): """ Flow item connecting to object node item tests. """ def test_glue(self): """Test glueing to object node """ flow = self.create(items.FlowItem) onode = self.create(items.ObjectNodeItem, UML.ObjectNode) glued = self.allow(flow, flow.head, onode) self.assertTrue(glued) def test_connection(self): """Test connection to object node """ flow = self.create(items.FlowItem) anode = self.create(items.ActionItem, UML.Action) onode = self.create(items.ObjectNodeItem, UML.ObjectNode) self.connect(flow, flow.head, anode) self.connect(flow, flow.tail, onode) self.assertTrue(flow.subject) self.assertTrue(isinstance(flow.subject, UML.ObjectFlow)) self.disconnect(flow, flow.head) self.disconnect(flow, flow.tail) # opposite connection self.connect(flow, flow.head, onode) self.connect(flow, flow.tail, anode) self.assertTrue(flow.subject) self.assertTrue(isinstance(flow.subject, UML.ObjectFlow)) def test_reconnection(self): """Test object flow reconnection """ flow = self.create(items.FlowItem) a1 = self.create(items.ActionItem, UML.Action) o1 = self.create(items.ObjectNodeItem, UML.ObjectNode) o2 = self.create(items.ObjectNodeItem, UML.ObjectNode) # connect: a1 -> o1 self.connect(flow, flow.head, a1) self.connect(flow, flow.tail, o1) f = flow.subject f.name = 'tname' f.guard = 'tguard' # reconnect: a1 -> o2 self.connect(flow, flow.tail, o2) self.assertEquals(0, len(a1.subject.incoming)) self.assertEquals(1, len(a1.subject.outgoing)) # no connections to o1 self.assertEquals(0, len(o1.subject.incoming)) self.assertEquals(0, len(o1.subject.outgoing)) # connections to o2 instead self.assertEquals(1, len(o2.subject.incoming)) self.assertEquals(0, len(o2.subject.outgoing)) self.assertEquals(1, len(self.kindof(UML.ObjectFlow))) # one guard self.assertEquals('tname', flow.subject.name) self.assertEquals('tguard', flow.subject.guard) def test_control_flow_reconnection(self): """Test control flow becoming object flow due to reconnection """ flow = self.create(items.FlowItem) a1 = self.create(items.ActionItem, UML.Action) a2 = self.create(items.ActionItem, UML.Action) o1 = self.create(items.ObjectNodeItem, UML.ObjectNode) # connect with control flow: a1 -> a2 self.connect(flow, flow.head, a1) self.connect(flow, flow.tail, a2) f = flow.subject f.name = 'tname' f.guard = 'tguard' # reconnect with object flow: a1 -> o1 self.connect(flow, flow.tail, o1) self.assertEquals(0, len(a1.subject.incoming)) self.assertEquals(1, len(a1.subject.outgoing)) # no connections to a2 self.assertEquals(0, len(a2.subject.incoming)) self.assertEquals(0, len(a2.subject.outgoing)) # connections to o1 instead self.assertEquals(1, len(o1.subject.incoming)) self.assertEquals(0, len(o1.subject.outgoing)) self.assertEquals(0, len(self.kindof(UML.ControlFlow))) self.assertEquals(1, len(self.kindof(UML.ObjectFlow))) # one guard, not changed self.assertEquals('tname', flow.subject.name) self.assertEquals('tguard', flow.subject.guard) class FlowItemActionTestCase(TestCase): """ Flow item connecting to action item tests. """ def test_glue(self): """Test flow item glueing to action items """ flow = self.create(items.FlowItem) a1 = self.create(items.ActionItem, UML.Action) a2 = self.create(items.ActionItem, UML.Action) glued = self.allow(flow, flow.head, a1) self.assertTrue(glued) self.connect(flow, flow.head, a1) glued = self.allow(flow, flow.tail, a2) self.assertTrue(glued) def test_connect(self): """Test flow item connecting to action items """ flow = self.create(items.FlowItem) a1 = self.create(items.ActionItem, UML.Action) a2 = self.create(items.ActionItem, UML.Action) self.connect(flow, flow.head, a1) self.connect(flow, flow.tail, a2) self.assertTrue(isinstance(flow.subject, UML.ControlFlow)) self.assertEquals(0, len(a1.subject.incoming)) self.assertEquals(1, len(a2.subject.incoming)) self.assertEquals(1, len(a1.subject.outgoing)) self.assertEquals(0, len(a2.subject.outgoing)) self.assertTrue(flow.subject in a1.subject.outgoing) self.assertTrue(flow.subject.source is a1.subject) self.assertTrue(flow.subject in a2.subject.incoming) self.assertTrue(flow.subject.target is a2.subject) def test_disconnect(self): """Test flow item disconnection from action items """ flow = self.create(items.FlowItem) a1 = self.create(items.ActionItem, UML.Action) a2 = self.create(items.ActionItem, UML.Action) self.connect(flow, flow.head, a1) self.connect(flow, flow.tail, a2) self.disconnect(flow, flow.head) self.assertTrue(flow.subject is None) self.assertEquals(0, len(a1.subject.incoming)) self.assertEquals(0, len(a2.subject.incoming)) self.assertEquals(0, len(a1.subject.outgoing)) self.assertEquals(0, len(a2.subject.outgoing)) def test_reconnect(self): """Test flow item reconnection """ flow = self.create(items.FlowItem) a1 = self.create(items.ActionItem, UML.Action) a2 = self.create(items.ActionItem, UML.Action) a3 = self.create(items.ActionItem, UML.Action) # a1 -> a2 self.connect(flow, flow.head, a1) self.connect(flow, flow.tail, a2) f = flow.subject f.name = 'tname' f.guard = 'tguard' # reconnect: a1 -> a3 self.connect(flow, flow.tail, a3) self.assertEquals(0, len(a1.subject.incoming)) self.assertEquals(1, len(a1.subject.outgoing)) # no connections to a2 self.assertEquals(0, len(a2.subject.incoming)) self.assertEquals(0, len(a2.subject.outgoing)) # connections to a3 instead self.assertEquals(1, len(a3.subject.incoming)) self.assertEquals(0, len(a3.subject.outgoing)) self.assertEquals(1, len(self.kindof(UML.ControlFlow))) # one guard self.assertEquals('tname', flow.subject.name) self.assertEquals('tguard', flow.subject.guard) def test_object_flow_reconnection(self): """Test object flow becoming control flow due to reconnection """ flow = self.create(items.FlowItem) a1 = self.create(items.ActionItem, UML.Action) a2 = self.create(items.ActionItem, UML.Action) o1 = self.create(items.ObjectNodeItem, UML.ObjectNode) # connect with control flow: a1 -> o1 self.connect(flow, flow.head, a1) self.connect(flow, flow.tail, o1) f = flow.subject f.name = 'tname' f.guard = 'tguard' # reconnect with object flow: a1 -> a2 self.connect(flow, flow.tail, a2) self.assertEquals(0, len(a1.subject.incoming)) self.assertEquals(1, len(a1.subject.outgoing)) # no connections to o1 self.assertEquals(0, len(o1.subject.incoming)) self.assertEquals(0, len(o1.subject.outgoing)) # connections to a2 instead self.assertEquals(1, len(a2.subject.incoming)) self.assertEquals(0, len(a2.subject.outgoing)) self.assertEquals(1, len(self.kindof(UML.ControlFlow))) self.assertEquals(0, len(self.kindof(UML.ObjectFlow))) # one guard, not changed self.assertEquals('tname', flow.subject.name) self.assertEquals('tguard', flow.subject.guard) class FlowItemDesisionAndForkNodes: """ Base class for flow connecting to decision and fork nodes. See `FlowItemDecisionNodeTestCase` and `FlowItemForkNodeTestCase` test cases. Not tested yet - If a join node has an incoming object flow, it must have an outgoing object flow, otherwise, it must have an outgoing control flow. - The edges coming into and out of a fork node must be either all object flows or all control flows. """ item_cls = None fork_node_cls = None join_node_cls = None def test_glue(self): """Test decision/fork nodes glue """ flow = self.create(items.FlowItem) action = self.create(items.ActionItem, UML.Action) node = self.create(self.item_cls, self.join_node_cls) glued = self.allow(flow, flow.head, node) self.assertTrue(glued) self.connect(flow, flow.head, action) glued = self.allow(flow, flow.tail, node) self.assertTrue(glued) def test_node_class_change(self): """ Test node incoming edges Connection scheme is presented below:: head tail [ a1 ]--flow1--> | [ jn ] --flow3--> [ a3 ] [ a2 ]--flow2--> | Node class changes due to two incoming edges and one outgoing edge. """ flow1 = self.create(items.FlowItem) flow2 = self.create(items.FlowItem) flow3 = self.create(items.FlowItem) a1 = self.create(items.ActionItem, UML.Action) a2 = self.create(items.ActionItem, UML.Action) a3 = self.create(items.ActionItem, UML.Action) jn = self.create(self.item_cls, self.fork_node_cls) assert type(jn.subject) is self.fork_node_cls # connect actions first self.connect(flow1, flow1.head, a1) self.connect(flow2, flow2.head, a2) self.connect(flow3, flow3.tail, a2) # connect to the node self.connect(flow1, flow1.tail, jn) self.connect(flow2, flow2.tail, jn) self.connect(flow3, flow3.head, jn) # node class changes self.assertTrue(type(jn.subject) is self.join_node_cls) def test_outgoing_edges(self): """Test outgoing edges Connection scheme is presented below:: head tail | --flow2-->[ a2 ] [ a1 ] --flow1--> [ jn ] | --flow3-->[ a3 ] """ flow1 = self.create(items.FlowItem) flow2 = self.create(items.FlowItem) flow3 = self.create(items.FlowItem) a1 = self.create(items.ActionItem, UML.Action) a2 = self.create(items.ActionItem, UML.Action) a3 = self.create(items.ActionItem, UML.Action) jn = self.create(self.item_cls, self.join_node_cls) # connect actions first self.connect(flow1, flow1.head, a1) self.connect(flow2, flow2.tail, a2) self.connect(flow3, flow3.tail, a2) # connect to the node self.connect(flow1, flow1.tail, jn) self.assertTrue(type(jn.subject) is self.join_node_cls) self.connect(flow2, flow2.head, jn) self.assertTrue(type(jn.subject) is self.join_node_cls) self.assertEquals(1, len(jn.subject.incoming)) self.assertEquals(1, len(jn.subject.outgoing)) self.assertTrue(flow1.subject in jn.subject.incoming) self.assertTrue(flow2.subject in jn.subject.outgoing) self.connect(flow3, flow3.head, jn) self.assertEquals(2, len(jn.subject.outgoing)) self.assertTrue(type(jn.subject) is self.fork_node_cls, '%s' % jn.subject) def test_combined_nodes_connection(self): """Test combined nodes connection Connection scheme is presented below:: head tail | --flow2--> [ a2 ] [ a1 ] --flow1--> [ jn ] [ a4 ] --flow4--> | --flow3--> [ a3 ] Flow `flow4` will force the node to become a combined node. """ flow1 = self.create(items.FlowItem) flow2 = self.create(items.FlowItem) flow3 = self.create(items.FlowItem) flow4 = self.create(items.FlowItem) a1 = self.create(items.ActionItem, UML.Action) a2 = self.create(items.ActionItem, UML.Action) a3 = self.create(items.ActionItem, UML.Action) a4 = self.create(items.ActionItem, UML.Action) jn = self.create(self.item_cls, self.join_node_cls) # connect actions first self.connect(flow1, flow1.head, a1) self.connect(flow2, flow2.tail, a2) self.connect(flow3, flow3.tail, a2) self.connect(flow4, flow4.head, a4) # connect to the node self.connect(flow1, flow1.tail, jn) self.connect(flow2, flow2.head, jn) self.connect(flow3, flow3.head, jn) self.connect(flow4, flow4.tail, jn) self.assertTrue(type(jn.subject) is self.join_node_cls) self.assertTrue(jn.combined is not None) # check node combination self.assertTrue(1, len(jn.subject.outgoing)) self.assertTrue(1, len(jn.combined.incoming)) self.assertTrue(jn.subject.outgoing[0] is jn.combined.incoming[0]) def test_combined_node_disconnection(self): """Test combined nodes disconnection Connection scheme is presented below:: head tail | --flow2--> [ a2 ] [ a1 ] --flow1--> [ jn ] [ a4 ] --flow4--> | --flow3--> [ a3 ] Flow `flow4` will force the node to become a combined node. """ canvas = self.diagram.canvas flow1 = self.create(items.FlowItem) flow2 = self.create(items.FlowItem) flow3 = self.create(items.FlowItem) flow4 = self.create(items.FlowItem) a1 = self.create(items.ActionItem, UML.Action) a2 = self.create(items.ActionItem, UML.Action) a3 = self.create(items.ActionItem, UML.Action) a4 = self.create(items.ActionItem, UML.Action) jn = self.create(self.item_cls, self.join_node_cls) # connect actions first self.connect(flow1, flow1.head, a1) self.connect(flow2, flow2.tail, a2) self.connect(flow3, flow3.tail, a2) self.connect(flow4, flow4.head, a4) # connect to the node self.connect(flow1, flow1.tail, jn) self.connect(flow2, flow2.head, jn) self.connect(flow3, flow3.head, jn) self.connect(flow4, flow4.tail, jn) # needed for tests below cflow = jn.subject.outgoing[0] cnode = jn.combined assert cflow in self.kindof(UML.ControlFlow) assert cnode in self.kindof(self.fork_node_cls) # test disconnection self.disconnect(flow4, flow4.head) assert self.get_connected(flow4.head) is None self.assertTrue(jn.combined is None) flows = self.kindof(UML.ControlFlow) nodes = self.kindof(self.fork_node_cls) self.assertTrue(cnode not in nodes, '%s in %s' % (cnode, nodes)) self.assertTrue(cflow not in flows, '%s in %s' % (cflow, flows)) class FlowItemForkNodeTestCase(FlowItemDesisionAndForkNodes, TestCase): item_cls = items.ForkNodeItem fork_node_cls = UML.ForkNode join_node_cls = UML.JoinNode class FlowItemDecisionNodeTestCase(FlowItemDesisionAndForkNodes, TestCase): item_cls = items.DecisionNodeItem fork_node_cls = UML.DecisionNode join_node_cls = UML.MergeNode # vim:sw=4:et:ai gaphor-0.17.2/gaphor/adapters/classes/000077500000000000000000000000001220151210700175705ustar00rootroot00000000000000gaphor-0.17.2/gaphor/adapters/classes/__init__.py000066400000000000000000000000001220151210700216670ustar00rootroot00000000000000gaphor-0.17.2/gaphor/adapters/classes/classconnect.py000066400000000000000000000161731220151210700226310ustar00rootroot00000000000000""" Classes related (dependency, implementation) adapter connections. """ from gaphor.adapters.connectors import UnaryRelationshipConnect, RelationshipConnect from zope import interface, component from gaphor import UML from gaphor.diagram import items class DependencyConnect(RelationshipConnect): """ Connect two NamedItem elements using a Dependency """ component.adapts(items.NamedItem, items.DependencyItem) def allow(self, handle, port): line = self.line element = self.element # Element should be a NamedElement if not element.subject or \ not isinstance(element.subject, UML.NamedElement): return False return super(DependencyConnect, self).allow(handle, port) def reconnect(self, handle, port): line = self.line dep = line.subject if handle is line.head: for s in dep.supplier: del dep.supplier[s] elif handle is line.tail: for c in dep.client: del dep.client[c] self.reconnect_relationship(handle, line.dependency_type.supplier, line.dependency_type.client) def connect_subject(self, handle): """ TODO: cleck for existing relationships (use self.relation()) """ line = self.line if line.auto_dependency: canvas = line.canvas opposite = line.opposite(handle) if handle is line.head: client = self.get_connected(opposite).subject supplier = self.element.subject else: client = self.element.subject supplier = self.get_connected(opposite).subject line.dependency_type = UML.model.dependency_type(client, supplier) relation = self.relationship_or_new(line.dependency_type, line.dependency_type.supplier, line.dependency_type.client) line.subject = relation component.provideAdapter(DependencyConnect) class GeneralizationConnect(RelationshipConnect): """ Connect Classifiers with a Generalization relationship. """ # FixMe: Both ends of the generalization should be of the same type? component.adapts(items.ClassifierItem, items.GeneralizationItem) def reconnect(self, handle, port): self.reconnect_relationship(handle, UML.Generalization.general, UML.Generalization.specific) def connect_subject(self, handle): log.debug('connect_subject: ' % handle) relation = self.relationship_or_new(UML.Generalization, UML.Generalization.general, UML.Generalization.specific) log.debug('found: ' % relation) self.line.subject = relation component.provideAdapter(GeneralizationConnect) class AssociationConnect(UnaryRelationshipConnect): """ Connect association to classifier. """ component.adapts(items.ClassifierItem, items.AssociationItem) def allow(self, handle, port): element = self.element # Element should be a Classifier if not isinstance(element.subject, UML.Classifier): return None return super(AssociationConnect, self).allow(handle, port) def connect_subject(self, handle): element = self.element line = self.line c1 = self.get_connected(line.head) c2 = self.get_connected(line.tail) if c1 and c2: head_type = c1.subject tail_type = c2.subject # First check if we do not already contain the right subject: if line.subject: end1 = line.subject.memberEnd[0] end2 = line.subject.memberEnd[1] if (end1.type is head_type and end2.type is tail_type) \ or (end2.type is head_type and end1.type is tail_type): return # Create new association relation = UML.model.create_association(self.element_factory, head_type, tail_type) relation.package = element.canvas.diagram.namespace line.head_end.subject = relation.memberEnd[0] line.tail_end.subject = relation.memberEnd[1] # Do subject itself last, so event handlers can trigger line.subject = relation def reconnect(self, handle, port): line = self.line c = self.get_connected(handle) if handle is line.head: end = line.tail_end oend = line.head_end elif handle is line.tail: end = line.head_end oend = line.tail_end else: raise ValueError('Incorrect handle passed to adapter') nav = oend.subject.navigability UML.model.set_navigability(line.subject, end.subject, None) # clear old data oend.subject.type = c.subject UML.model.set_navigability(line.subject, oend.subject, nav) def disconnect_subject(self, handle): """ Disconnect model element. Disconnect property (memberEnd) too, in case of end of life for Extension """ opposite = self.line.opposite(handle) c1 = self.get_connected(handle) c2 = self.get_connected(opposite) if c1 and c2: old = self.line.subject del self.line.subject del self.line.head_end.subject del self.line.tail_end.subject if old and len(old.presentation) == 0: for e in list(old.memberEnd): e.unlink() old.unlink() component.provideAdapter(AssociationConnect) class ImplementationConnect(RelationshipConnect): """ Connect Interface and a BehavioredClassifier using an Implementation. """ component.adapts(items.NamedItem, items.ImplementationItem) def allow(self, handle, port): line = self.line element = self.element # Element at the head should be an Interface if handle is line.head and \ not isinstance(element.subject, UML.Interface): return None # Element at the tail should be a BehavioredClassifier if handle is line.tail and \ not isinstance(element.subject, UML.BehavioredClassifier): return None return super(ImplementationConnect, self).allow(handle, port) def reconnect(self, handle, port): line = self.line impl = line.subject if handle is line.head: for s in impl.contract: del impl.contract[s] elif handle is line.tail: for c in impl.implementatingClassifier: del impl.implementatingClassifier[c] self.reconnect_relationship(handle, UML.Implementation.contract, UML.Implementation.implementatingClassifier) def connect_subject(self, handle): """ Perform implementation relationship connection. """ relation = self.relationship_or_new(UML.Implementation, UML.Implementation.contract, UML.Implementation.implementatingClassifier) self.line.subject = relation component.provideAdapter(ImplementationConnect) # vim:sw=4:et:ai gaphor-0.17.2/gaphor/adapters/classes/interfaceconnect.py000066400000000000000000000056761220151210700234720ustar00rootroot00000000000000""" Interface item related connections. The connectors implemented in this module check if connection is possible to folded interface, see `gaphor.diagram.classes.interface` documentation for details. """ from zope import interface, component from gaphor import UML from gaphor.diagram import items from gaphor.adapters.classes.classconnect import DependencyConnect, ImplementationConnect class ImplementationInterfaceConnect(ImplementationConnect): """ Connect interface item and a behaviored classifier using an implementation. """ component.adapts(items.InterfaceItem, items.ImplementationItem) def connect(self, handle, port): """ Implementation item can be changed to draw in solid mode, when connected to folded interface. """ super(ImplementationInterfaceConnect, self).connect(handle, port) if handle is self.line.head: self.line._solid = self.element.folded != self.element.FOLDED_NONE def disconnect(self, handle): """ If implementation item is no longer connected to an interface, then draw it in non-solid mode. """ super(ImplementationInterfaceConnect, self).disconnect(handle) if handle is self.line.head: self.line._solid = False component.provideAdapter(ImplementationInterfaceConnect) class DependencyInterfaceConnect(DependencyConnect): """ Connect interface item with dependency item. """ component.adapts(items.InterfaceItem, items.DependencyItem) def connect(self, handle, port): """ Dependency item is changed to draw in solid mode, when connected to folded interface. """ super(DependencyInterfaceConnect, self).connect(handle, port) line = self.line # connecting to the interface, which is supplier - assuming usage # dependency if handle is line.head: if self.element.folded != self.element.FOLDED_NONE: line._solid = True self.element.folded = self.element.FOLDED_REQUIRED # change interface angle even when it is unfolded, this way # required interface will be rotated properly when folded by # user self.element._angle = port.angle def disconnect(self, handle): """ If dependency item is no longer connected to an interface, then draw it in non-solid mode. Interface's folded mode changes to provided (ball) notation. """ super(DependencyInterfaceConnect, self).disconnect(handle) if handle is self.line.head: iface = self.element self.line._solid = False # don't change folding notation when interface is unfolded, see # test_unfolded_interface_disconnection as well if iface.folded: iface.folded = iface.FOLDED_PROVIDED component.provideAdapter(DependencyInterfaceConnect) # vim:sw=4:et:ai gaphor-0.17.2/gaphor/adapters/classes/tests/000077500000000000000000000000001220151210700207325ustar00rootroot00000000000000gaphor-0.17.2/gaphor/adapters/classes/tests/__init__.py000066400000000000000000000000001220151210700230310ustar00rootroot00000000000000gaphor-0.17.2/gaphor/adapters/classes/tests/test_class.py000066400000000000000000000304651220151210700234600ustar00rootroot00000000000000""" Classes related adapter connection tests. """ from gaphor.tests import TestCase from gaphor import UML from gaphor.diagram import items class DependencyTestCase(TestCase): """ Dependency item connection adapter tests. """ def test_dependency_glue(self): """Test dependency glue to two actor items """ actor1 = self.create(items.ActorItem, UML.Actor) actor2 = self.create(items.ActorItem, UML.Actor) dep = self.create(items.DependencyItem) glued = self.allow(dep, dep.head, actor1) self.assertTrue(glued) self.connect(dep, dep.head, actor1) glued = self.allow(dep, dep.tail, actor2) self.assertTrue(glued) def test_dependency_connect(self): """Test dependency connecting to two actor items """ actor1 = self.create(items.ActorItem, UML.Actor) actor2 = self.create(items.ActorItem, UML.Actor) dep = self.create(items.DependencyItem) self.connect(dep, dep.head, actor1) self.connect(dep, dep.tail, actor2) self.assertTrue(dep.subject is not None) self.assertTrue(isinstance(dep.subject, UML.Dependency)) self.assertTrue(dep.subject in self.element_factory.select()) hct = self.get_connected(dep.head) tct = self.get_connected(dep.tail) self.assertTrue(hct is actor1) self.assertTrue(tct is actor2) self.assertTrue(actor1.subject in dep.subject.supplier) self.assertTrue(actor2.subject in dep.subject.client) def test_dependency_reconnection(self): """Test dependency reconnection """ a1 = self.create(items.ActorItem, UML.Actor) a2 = self.create(items.ActorItem, UML.Actor) a3 = self.create(items.ActorItem, UML.Actor) dep = self.create(items.DependencyItem) # connect: a1 -> a2 self.connect(dep, dep.head, a1) self.connect(dep, dep.tail, a2) d = dep.subject # reconnect: a1 -> a3 self.connect(dep, dep.tail, a3) self.assertSame(d, dep.subject) self.assertEquals(1, len(dep.subject.supplier)) self.assertEquals(1, len(dep.subject.client)) self.assertTrue(a1.subject in dep.subject.supplier) self.assertTrue(a3.subject in dep.subject.client) self.assertTrue(a2.subject not in dep.subject.client, dep.subject.client) def test_dependency_disconnect(self): """Test dependency disconnecting using two actor items """ actor1 = self.create(items.ActorItem, UML.Actor) actor2 = self.create(items.ActorItem, UML.Actor) dep = self.create(items.DependencyItem) self.connect(dep, dep.head, actor1) self.connect(dep, dep.tail, actor2) dep_subj = dep.subject self.disconnect(dep, dep.tail) self.assertTrue(dep.subject is None) self.assertTrue(self.get_connected(dep.tail) is None) self.assertTrue(dep_subj not in self.element_factory.select()) self.assertTrue(dep_subj not in actor1.subject.supplierDependency) self.assertTrue(dep_subj not in actor2.subject.clientDependency) def test_dependency_reconnect(self): """Test dependency reconnection using two actor items """ actor1 = self.create(items.ActorItem, UML.Actor) actor2 = self.create(items.ActorItem, UML.Actor) dep = self.create(items.DependencyItem) self.connect(dep, dep.head, actor1) self.connect(dep, dep.tail, actor2) dep_subj = dep.subject self.disconnect(dep, dep.tail) # reconnect self.connect(dep, dep.tail, actor2) self.assertTrue(dep.subject is not None) self.assertTrue(dep.subject is not dep_subj) # the old subject has been deleted self.assertTrue(dep.subject in actor1.subject.supplierDependency) self.assertTrue(dep.subject in actor2.subject.clientDependency) # TODO: test with interface (usage) and component (realization) # TODO: test with multiple diagrams (should reuse existing relationships first) def test_multi_dependency(self): """Test multiple dependencies Dependency should appear in a new diagram, bound on a new dependency item. """ actoritem1 = self.create(items.ActorItem, UML.Actor) actoritem2 = self.create(items.ActorItem, UML.Actor) actor1 = actoritem1.subject actor2 = actoritem2.subject dep = self.create(items.DependencyItem) self.connect(dep, dep.head, actoritem1) self.connect(dep, dep.tail, actoritem2) self.assertTrue(dep.subject) self.assertEquals(1, len(actor1.supplierDependency)) self.assertTrue(actor1.supplierDependency[0] is dep.subject) self.assertEquals(1, len(actor2.clientDependency)) self.assertTrue(actor2.clientDependency[0] is dep.subject) # Do the same thing, but now on a new diagram: diagram2 = self.element_factory.create(UML.Diagram) actoritem3 = diagram2.create(items.ActorItem, subject=actor1) actoritem4 = diagram2.create(items.ActorItem, subject=actor2) dep2 = diagram2.create(items.DependencyItem) self.connect(dep2, dep2.head, actoritem3) cinfo = diagram2.canvas.get_connection(dep2.head) self.assertNotSame(None, cinfo) self.assertSame(cinfo.connected, actoritem3) self.connect(dep2, dep2.tail, actoritem4) self.assertNotSame(dep2.subject, None) self.assertEquals(1, len(actor1.supplierDependency)) self.assertTrue(actor1.supplierDependency[0] is dep.subject) self.assertEquals(1, len(actor2.clientDependency)) self.assertTrue(actor2.clientDependency[0] is dep.subject) self.assertSame(dep.subject, dep2.subject) def test_dependency_type_auto(self): """Test dependency type automatic determination """ cls = self.create(items.ClassItem, UML.Class) iface = self.create(items.InterfaceItem, UML.Interface) dep = self.create(items.DependencyItem) assert dep.auto_dependency self.connect(dep, dep.tail, cls) # connect client self.connect(dep, dep.head, iface) # connect supplier self.assertTrue(dep.subject is not None) self.assertTrue(isinstance(dep.subject, UML.Usage), dep.subject) self.assertTrue(dep.subject in self.element_factory.select()) class GeneralizationTestCase(TestCase): """ Generalization item connection adapter tests. """ def test_glue(self): """Test generalization item glueing using two classes """ gen = self.create(items.GeneralizationItem) c1 = self.create(items.ClassItem, UML.Class) c2 = self.create(items.ClassItem, UML.Class) glued = self.allow(gen, gen.tail, c1) self.assertTrue(glued) self.connect(gen, gen.tail, c1) self.assertTrue(self.get_connected(gen.tail) is c1) self.assertTrue(gen.subject is None) glued = self.allow(gen, gen.head, c2) self.assertTrue(glued) def test_connection(self): """Test generalization item connection using two classes """ gen = self.create(items.GeneralizationItem) c1 = self.create(items.ClassItem, UML.Class) c2 = self.create(items.ClassItem, UML.Class) self.connect(gen, gen.tail, c1) assert self.get_connected(gen.tail) is c1 self.connect(gen, gen.head, c2) self.assertTrue(gen.subject is not None) self.assertTrue(gen.subject.general is c2.subject) self.assertTrue(gen.subject.specific is c1.subject) def test_reconnection(self): """Test generalization item connection using two classes On reconnection a new Generalization is created. """ gen = self.create(items.GeneralizationItem) c1 = self.create(items.ClassItem, UML.Class) c2 = self.create(items.ClassItem, UML.Class) self.connect(gen, gen.tail, c1) assert self.get_connected(gen.tail) is c1 self.connect(gen, gen.head, c2) self.assertTrue(gen.subject is not None) self.assertTrue(gen.subject.general is c2.subject) self.assertTrue(gen.subject.specific is c1.subject) # Now do the same on a new diagram: diagram2 = self.element_factory.create(UML.Diagram) c3 = diagram2.create(items.ClassItem, subject=c1.subject) c4 = diagram2.create(items.ClassItem, subject=c2.subject) gen2 = diagram2.create(items.GeneralizationItem) self.connect(gen2, gen2.head, c3) cinfo = diagram2.canvas.get_connection(gen2.head) self.assertNotSame(None, cinfo) self.assertSame(cinfo.connected, c3) self.connect(gen2, gen2.tail, c4) self.assertNotSame(gen.subject, gen2.subject) self.assertEquals(1, len(c1.subject.generalization)) self.assertSame(c1.subject.generalization[0], gen.subject) #self.assertEquals(1, len(actor2.clientDependency)) #self.assertTrue(actor2.clientDependency[0] is dep.subject) def test_reconnection2(self): """Test reconnection of generalization """ c1 = self.create(items.ClassItem, UML.Class) c2 = self.create(items.ClassItem, UML.Class) c3 = self.create(items.ClassItem, UML.Class) gen = self.create(items.GeneralizationItem) # connect: c1 -> c2 self.connect(gen, gen.head, c1) self.connect(gen, gen.tail, c2) s = gen.subject # reconnect: c2 -> c3 self.connect(gen, gen.tail, c3) self.assertSame(s, gen.subject) self.assertSame(c1.subject, gen.subject.general) self.assertSame(c3.subject, gen.subject.specific) self.assertNotSame(c2.subject, gen.subject.specific) class AssociationConnectorTestCase(TestCase): """ Association item connection adapters tests. """ def test_glue(self): """Test association item glue """ asc = self.create(items.AssociationItem) c1 = self.create(items.ClassItem, UML.Class) c2 = self.create(items.ClassItem, UML.Class) glued = self.allow(asc, asc.head, c1) self.assertTrue(glued) self.connect(asc, asc.head, c1) glued = self.allow(asc, asc.tail, c2) self.assertTrue(glued) def test_connect(self): """Test association item connection """ asc = self.create(items.AssociationItem) c1 = self.create(items.ClassItem, UML.Class) c2 = self.create(items.ClassItem, UML.Class) self.connect(asc, asc.head, c1) self.assertTrue(asc.subject is None) # no UML metaclass yet self.connect(asc, asc.tail, c2) self.assertTrue(asc.subject is not None) # Diagram, Class *2, Property *2, Association self.assertEquals(6, len(list(self.element_factory.select()))) self.assertTrue(asc.head_end.subject is not None) self.assertTrue(asc.tail_end.subject is not None) def test_reconnect(self): """Test association item reconnection """ asc = self.create(items.AssociationItem) c1 = self.create(items.ClassItem, UML.Class) c2 = self.create(items.ClassItem, UML.Class) c3 = self.create(items.ClassItem, UML.Class) self.connect(asc, asc.head, c1) self.connect(asc, asc.tail, c2) UML.model.set_navigability(asc.subject, asc.tail_end.subject, True) a = asc.subject self.connect(asc, asc.tail, c3) self.assertSame(a, asc.subject) ends = [p.type for p in asc.subject.memberEnd] self.assertTrue(c1.subject in ends) self.assertTrue(c3.subject in ends) self.assertTrue(c2.subject not in ends) self.assertTrue(asc.tail_end.subject.navigability) def test_disconnect(self): """Test association item disconnection """ asc = self.create(items.AssociationItem) c1 = self.create(items.ClassItem, UML.Class) c2 = self.create(items.ClassItem, UML.Class) self.connect(asc, asc.head, c1) self.assertTrue(asc.subject is None) # no UML metaclass yet self.connect(asc, asc.tail, c2) assert asc.subject is not None self.disconnect(asc, asc.head) # after disconnection: one diagram and two classes self.assertEquals(3, len(list(self.element_factory.select()))) # vim:sw=4:et:ai gaphor-0.17.2/gaphor/adapters/classes/tests/test_implementation.py000066400000000000000000000055751220151210700254040ustar00rootroot00000000000000""" Test implementation (interface realization) item connectors. """ from gaphor import UML from gaphor.diagram import items from gaphor.tests import TestCase class ImplementationTestCase(TestCase): def test_non_interface_glue(self): """Test non-interface glueing with implementation """ impl = self.create(items.ImplementationItem) clazz = self.create(items.ClassItem, UML.Class) glued = self.allow(impl, impl.head, clazz) # connecting head to non-interface item is disallowed self.assertFalse(glued) def test_interface_glue(self): """Test interface glueing with implementation """ iface = self.create(items.InterfaceItem, UML.Interface) impl = self.create(items.ImplementationItem) glued = self.allow(impl, impl.head, iface) self.assertTrue(glued) def test_classifier_glue(self): """Test classifier glueing with implementation """ impl = self.create(items.ImplementationItem) clazz = self.create(items.ClassItem, UML.Class) glued = self.allow(impl, impl.tail, clazz) self.assertTrue(glued) def test_connection(self): """Test connection of class and interface with implementation """ iface = self.create(items.InterfaceItem, UML.Interface) impl = self.create(items.ImplementationItem) clazz = self.create(items.ClassItem, UML.Class) self.connect(impl, impl.head, iface) self.connect(impl, impl.tail, clazz) # check the datamodel self.assertTrue(isinstance(impl.subject, UML.Implementation)) ct = self.get_connected(impl.head) self.assertTrue(ct is iface) self.assertTrue(impl.subject is not None) self.assertTrue(impl.subject.contract[0] is iface.subject) self.assertTrue(impl.subject.implementatingClassifier[0] is clazz.subject) def test_reconnection(self): """Test reconnection of class and interface with implementation """ iface = self.create(items.InterfaceItem, UML.Interface) c1 = self.create(items.ClassItem, UML.Class) c2 = self.create(items.ClassItem, UML.Class) impl = self.create(items.ImplementationItem) # connect: iface -> c1 self.connect(impl, impl.head, iface) self.connect(impl, impl.tail, c1) s = impl.subject # reconnect: iface -> c2 self.connect(impl, impl.tail, c2) self.assertSame(s, impl.subject) self.assertEquals(1, len(impl.subject.contract)) self.assertEquals(1, len(impl.subject.implementatingClassifier)) self.assertTrue(iface.subject in impl.subject.contract) self.assertTrue(c2.subject in impl.subject.implementatingClassifier) self.assertTrue(c1.subject not in impl.subject.implementatingClassifier, impl.subject.implementatingClassifier) # vim:sw=4:et:ai gaphor-0.17.2/gaphor/adapters/classes/tests/test_interfaceconnect.py000066400000000000000000000121701220151210700256560ustar00rootroot00000000000000""" Test connections to folded interface. """ from gaphor import UML from gaphor.diagram import items from gaphor.tests import TestCase class ImplementationTestCase(TestCase): def test_folded_interface_connection(self): """Test connecting implementation to folded interface """ iface = self.create(items.InterfaceItem, UML.Interface) iface.folded = iface.FOLDED_PROVIDED impl = self.create(items.ImplementationItem) assert not impl._solid self.connect(impl, impl.head, iface, iface.ports()[0]) self.assertTrue(impl._solid) def test_folded_interface_disconnection(self): """Test disconnection implementation from folded interface """ iface = self.create(items.InterfaceItem, UML.Interface) iface.folded = iface.FOLDED_PROVIDED impl = self.create(items.ImplementationItem) assert not impl._solid self.connect(impl, impl.head, iface, iface.ports()[0]) assert impl._solid self.disconnect(impl, impl.head) self.assertTrue(not impl._solid) class DependencyTestCase(TestCase): def test_folded_interface_connection(self): """Test connecting dependency to folded interface """ iface = self.create(items.InterfaceItem, UML.Interface) iface.folded = iface.FOLDED_PROVIDED dep = self.create(items.DependencyItem) assert not dep._solid self.connect(dep, dep.head, iface, iface.ports()[0]) self.assertTrue(dep._solid) # at the end, interface folded notation shall be `required' one self.assertEquals(iface.folded, iface.FOLDED_REQUIRED) def test_folded_interface_disconnection(self): """Test disconnection dependency from folded interface """ iface = self.create(items.InterfaceItem, UML.Interface) iface.folded = iface.FOLDED_PROVIDED dep = self.create(items.DependencyItem) assert not dep._solid self.connect(dep, dep.head, iface, iface.ports()[0]) assert dep._solid self.disconnect(dep, dep.head) self.assertTrue(not dep._solid) # after disconnection, interface folded notation shall be `provided' one self.assertEquals(iface.folded, iface.FOLDED_PROVIDED) def test_unfolded_interface_disconnection(self): """Test disconnection dependency from unfolded interface """ iface = self.create(items.InterfaceItem, UML.Interface) dep = self.create(items.DependencyItem) self.connect(dep, dep.head, iface, iface.ports()[0]) assert not dep._solid self.disconnect(dep, dep.head) self.assertTrue(not dep._solid) # after disconnection, interface folded property shall be # `FOLDED_NONE' self.assertEquals(iface.folded, iface.FOLDED_NONE) LINES = (items.ImplementationItem, items.DependencyItem, items.GeneralizationItem, items.AssociationItem, items.CommentLineItem) class FoldedInterfaceMultipleLinesTestCase(TestCase): """ Test connection of additional diagram lines to folded interface, which has already usage depenendency or implementation connected. """ def setUp(self): super(FoldedInterfaceMultipleLinesTestCase, self).setUp() self.iface = self.create(items.InterfaceItem, UML.Interface) self.iface.folded = self.iface.FOLDED_PROVIDED def test_interface_with_implementation(self): """Test glueing different lines to folded interface with implementation """ impl = self.create(items.ImplementationItem) self.connect(impl, impl.head, self.iface, self.iface.ports()[0]) for cls in LINES: line = self.create(cls) glued = self.allow(line, line.head, self.iface) # no additional lines (specified above) can be glued self.assertFalse(glued, 'Glueing of %s should not be allowed' % cls) def test_interface_with_dependency(self): """Test glueing different lines to folded interface with dependency """ dep = self.create(items.DependencyItem) self.connect(dep, dep.head, self.iface, self.iface.ports()[0]) for cls in LINES: line = self.create(cls) glued = self.allow(line, line.head, self.iface) # no additional lines (specified above) can be glued self.assertFalse(glued, 'Glueing of %s should not be allowed' % cls) class FoldedInterfaceSingleLineTestCase(TestCase): """ Test connection of diagram lines to folded interface. Any lines beside implementation and dependency should be forbidden to connect. """ def test_interface_with_forbidden_lines(self): """Test glueing forbidden lines to folded interface """ iface = self.create(items.InterfaceItem, UML.Interface) iface.folded = iface.FOLDED_PROVIDED for cls in LINES[2:]: line = self.create(cls) glued = self.allow(line, line.head, iface) # no additional lines (specified above) can be glued self.assertFalse(glued, 'Glueing of %s should not be allowed' % cls) # vim:sw=4:et:ai gaphor-0.17.2/gaphor/adapters/components/000077500000000000000000000000001220151210700203205ustar00rootroot00000000000000gaphor-0.17.2/gaphor/adapters/components/__init__.py000066400000000000000000000000001220151210700224170ustar00rootroot00000000000000gaphor-0.17.2/gaphor/adapters/components/connectorconnect.py000066400000000000000000000224401220151210700242400ustar00rootroot00000000000000""" Connector connections. Implemented using interface item in assembly connector mode, see `gaphor.diagram.connector` module for details. """ from zope import component from gaphor import UML from gaphor.diagram import items from gaphor.adapters.connectors import AbstractConnect class ConnectorConnectBase(AbstractConnect): def _get_interfaces(self, c1, c2): """ Return list of common interfaces provided by first component and required by second component. :Parameters: c1 Component providing interfaces. c2 Component requiring interfaces. """ provided = set(c1.subject.provided) required = set(c2.subject.required) interfaces = list(provided.intersection(required)) interfaces.sort(key=operator.attrgetter('name')) return interfaces def get_connecting(self, iface, both=False): """ Get items connecting to interface. :Parameters: iface Interface in question. both If true, then filter out one-side connections. """ canvas = iface.canvas connected = canvas.get_connections(connected=iface) if both: connected = [c for c in connected if canvas.get_connection(c.item.opposite(c.handle))] return connected @staticmethod def get_component(connector): """ Get component connected by connector. """ canvas = connector.canvas c1 = canvas.get_connection(connector.head) c2 = canvas.get_connection(connector.tail) component = None if c1 and isinstance(c1.connected, items.ComponentItem): component = c1.connected elif c2 and isinstance(c2.connected, items.ComponentItem): component = c2.connected return component def create_uml(self, connector, component, assembly, iface): """ Create assembly connector UML metamodel for given connector item and component. :Parameters: connector Connector item. component Component item. assembly Instance of Connector UML metaclass. iface Instance of Interface UML metaclass. """ connector.subject = assembly end = self.element_factory.create(UML.ConnectorEnd) end.role = iface end.partWithPort = self.element_factory.create(UML.Port) assembly.end = end component.subject.ownedPort = end.partWithPort def drop_uml(self, connector, component): """ Destroy assembly connector UML metamodel existing between connector item and component item. :Parameters: connector Connector item. component Component item. """ p = component.subject.ownedPort[0] p.unlink() connector.subject = None def allow(self, handle, port): glue_ok = super(ConnectorConnectBase, self).allow(handle, port) iface = self.element component = self.get_connected(self.line.opposite(handle)) if isinstance(component, items.InterfaceItem): component, iface = iface, component port = self.get_connected_port(self.line.opposite(handle)) # connect only components and interfaces but not two interfaces nor # two components glue_ok = not (isinstance(component, items.ComponentItem) \ and isinstance(iface, items.ComponentItem) \ or isinstance(component, items.InterfaceItem) \ and isinstance(iface, items.InterfaceItem)) # if port type is known, then allow connection to proper port only if glue_ok and component is not None and iface is not None \ and (port.required or port.provided): assert isinstance(component, items.ComponentItem) assert isinstance(iface, items.InterfaceItem) glue_ok = port.provided and iface.subject in component.subject.provided \ or port.required and iface.subject in component.subject.required return glue_ok return glue_ok def connect(self, handle, port): super(ConnectorConnectBase, self).connect(handle, port) line = self.line canvas = line.canvas c1 = self.get_connected(line.head) c2 = self.get_connected(line.tail) if c1 and c2: # reference interface and component correctly iface = c1 component = c2 if isinstance(component, items.InterfaceItem): assert isinstance(iface, items.ComponentItem) component, iface = iface, component connections = self.get_connecting(iface, both=True) ports = set(c.port for c in connections) # to make an assembly at least two connector ends need to exist # also, two different ports of interface need to be connected if len(connections) > 1 and len(ports) == 2: # find assembly connector assembly = None for c in connections: if c.item.subject: assembly = c.item.subject assert assembly.kind == 'assembly' break if assembly is None: assembly = self.element_factory.create(UML.Connector) assembly.kind = 'assembly' for c in connections: connector = c.item self.create_uml(connector, self.get_component(connector), assembly, iface.subject) else: self.create_uml(line, component, assembly, iface.subject) def disconnect(self, handle): super(ConnectorConnectBase, self).disconnect(handle) line = self.line if line.subject is None: return iface = self.get_connected(line.head) if not isinstance(iface, items.InterfaceItem): iface = self.get_connected(line.tail) connections = list(self.get_connecting(iface, both=True)) # find ports, which will stay connected after disconnection ports = set(c.port for c in connections if c.item is not self.line) # destroy whole assembly if one connected item stays # or only one port will stay connected if len(connections) == 2 or len(ports) == 1: connector = line.subject for ci in connections: c = self.get_component(ci.item) self.drop_uml(ci.item, c) line.request_update(matrix=False) connector.unlink() else: c = self.get_component(line) self.drop_uml(line, c) class ComponentConnectorConnect(ConnectorConnectBase): """ Connection of connector item to a component. """ component.adapts(items.ComponentItem, items.ConnectorItem) component.provideAdapter(ComponentConnectorConnect) class InterfaceConnectorConnect(ConnectorConnectBase): """ Connect connector to an interface to maintain assembly connection. See also `AbstractConnect` class for exception of interface item connections. """ component.adapts(items.InterfaceItem, items.ConnectorItem) def allow(self, handle, port): """ Allow glueing to folded interface only and when only connectors are connected. """ glue_ok = super(InterfaceConnectorConnect, self).allow(handle, port) iface = self.element glue_ok = glue_ok and iface.folded != iface.FOLDED_NONE if glue_ok: # find connected items, which are not connectors canvas = self.element.canvas connections = self.get_connecting(self.element) lines = [c.item for c in connections if not isinstance(c.item, items.ConnectorItem)] glue_ok = len(lines) == 0 return glue_ok def connect(self, handle, port): super(InterfaceConnectorConnect, self).connect(handle, port) iface = self.element iface.folded = iface.FOLDED_ASSEMBLY # determine required and provided ports pport = port ports = iface.ports() index = ports.index(port) rport = ports[(index + 2) % 4] if not port.provided and not port.required: component = self.get_connected(self.line.opposite(handle)) if component is not None and iface.subject in component.subject.required: pport, rport = rport, pport pport.provided = True rport.required = True iface._angle = rport.angle ports[(index + 1) % 4].connectable = False ports[(index + 3) % 4].connectable = False def disconnect(self, handle): super(InterfaceConnectorConnect, self).disconnect(handle) iface = self.element # about to disconnect last connector if len(list(self.get_connecting(iface))) == 1: ports = iface.ports() iface.folded = iface.FOLDED_PROVIDED iface._angle = ports[0].angle for p in ports: p.connectable = True p.provided = False p.required = False component.provideAdapter(InterfaceConnectorConnect) gaphor-0.17.2/gaphor/adapters/components/tests/000077500000000000000000000000001220151210700214625ustar00rootroot00000000000000gaphor-0.17.2/gaphor/adapters/components/tests/__init__.py000066400000000000000000000000001220151210700235610ustar00rootroot00000000000000gaphor-0.17.2/gaphor/adapters/components/tests/test_connector.py000066400000000000000000000514421220151210700250730ustar00rootroot00000000000000""" Test connector item connectors. """ from gaphor.tests import TestCase from gaphor import UML from gaphor.diagram import items from gaphor.adapters.components.connectorconnect import ConnectorConnectBase class ComponentConnectTestCase(TestCase): """ Test connection of connector item to a component. """ def test_glue(self): """Test glueing connector to component """ component = self.create(items.ComponentItem, UML.Component) line = self.create(items.ConnectorItem) glued = self.allow(line, line.head, component) self.assertTrue(glued) def test_connection(self): """Test connecting connector to a component """ component = self.create(items.ComponentItem, UML.Component) line = self.create(items.ConnectorItem) self.connect(line, line.head, component) self.assertTrue(line.subject is None) #self.assertTrue(line.end is None) def test_glue_both(self): """Test glueing connector to component when one is connected """ c1 = self.create(items.ComponentItem, UML.Component) c2 = self.create(items.ComponentItem, UML.Component) line = self.create(items.ConnectorItem) self.connect(line, line.head, c1) glued = self.allow(line, line.tail, c2) self.assertFalse(glued) class InterfaceConnectTestCase(TestCase): """ Test connection with interface acting as assembly connector. """ def test_non_folded_glue(self): """Test non-folded interface glueing """ iface = self.create(items.InterfaceItem, UML.Component) line = self.create(items.ConnectorItem) glued = self.allow(line, line.head, iface) self.assertFalse(glued) def test_folded_glue(self): """Test folded interface glueing """ iface = self.create(items.InterfaceItem, UML.Component) line = self.create(items.ConnectorItem) iface.folded = iface.FOLDED_REQUIRED glued = self.allow(line, line.head, iface) self.assertTrue(glued) def test_glue_when_dependency_connected(self): """Test interface glueing, when dependency connected """ iface = self.create(items.InterfaceItem, UML.Component) dep = self.create(items.DependencyItem) line = self.create(items.ConnectorItem) self.connect(dep, dep.head, iface) iface.folded = iface.FOLDED_REQUIRED glued = self.allow(line, line.head, iface) self.assertFalse(glued) def test_glue_when_implementation_connected(self): """Test interface glueing, when implementation connected """ iface = self.create(items.InterfaceItem, UML.Component) impl = self.create(items.ImplementationItem) line = self.create(items.ConnectorItem) self.connect(impl, impl.head, iface) iface.folded = iface.FOLDED_REQUIRED glued = self.allow(line, line.head, iface) self.assertFalse(glued) def test_glue_when_connector_connected(self): """Test interface glueing, when connector connected """ iface = self.create(items.InterfaceItem, UML.Component) iface.folded = iface.FOLDED_REQUIRED line1 = self.create(items.ConnectorItem) line2 = self.create(items.ConnectorItem) self.connect(line1, line1.head, iface) self.assertEquals(iface.FOLDED_ASSEMBLY, iface.folded) glued = self.allow(line2, line2.head, iface) self.assertTrue(glued) def test_simple_connection(self): """Test simple connection to an interface """ iface = self.create(items.InterfaceItem, UML.Component) line = self.create(items.ConnectorItem) iface.folded = iface.FOLDED_PROVIDED pport = iface.ports()[0] rport = iface.ports()[2] # test preconditions assert not pport.provided and not pport.required assert not rport.provided and not rport.required self.connect(line, line.head, iface, pport) # interface goes into assembly mode self.assertEquals(iface.FOLDED_ASSEMBLY, iface.folded) self.assertFalse(iface._name.is_visible()) # no UML metamodel yet self.assertTrue(line.subject is None) #self.assertTrue(line.end is None) # check port status self.assertTrue(pport.provided and not pport.required and pport.connectable) self.assertTrue(rport.required and not rport.provided and rport.connectable) p1 = iface.ports()[1] p2 = iface.ports()[3] self.assertTrue(not p1.required and not p1.provided and not p1.connectable) self.assertTrue(not p2.required and not p2.provided and not p2.connectable) def test_connection_angle_change(self): """Test angle after connection to an interface """ iface = self.create(items.InterfaceItem, UML.Component) line = self.create(items.ConnectorItem) iface.folded = iface.FOLDED_PROVIDED pport = iface.ports()[1] rport = iface.ports()[3] # test preconditions assert not pport.provided and not pport.required assert not rport.provided and not rport.required assert iface._angle == 0.0 self.connect(line, line.head, iface, pport) self.assertEquals(rport.angle, iface._angle) def test_connection_of_two_connectors_one_side(self): """Test connection of two connectors to required port of an interface """ iface = self.create(items.InterfaceItem, UML.Component) c1 = self.create(items.ConnectorItem) c2 = self.create(items.ConnectorItem) iface.folded = iface.FOLDED_PROVIDED pport = iface.ports()[0] rport = iface.ports()[2] # connect to the same interface self.connect(c1, c1.head, iface, pport) self.connect(c2, c2.head, iface, pport) # no UML metamodel yet self.assertTrue(c1.subject is None) #self.assertTrue(c1.end is None) self.assertTrue(c2.subject is None) #self.assertTrue(c2.end is None) # check port status self.assertTrue(pport.provided and not pport.required) self.assertTrue(rport.required and not rport.provided) p1 = iface.ports()[1] p2 = iface.ports()[3] self.assertTrue(not p1.required and not p1.provided) self.assertTrue(not p2.required and not p2.provided) def test_connection_of_two_connectors_two_sides(self): """Test connection of two connectors to required and provided ports of an interface """ iface = self.create(items.InterfaceItem, UML.Component) c1 = self.create(items.ConnectorItem) c2 = self.create(items.ConnectorItem) iface.folded = iface.FOLDED_PROVIDED pport = iface.ports()[0] rport = iface.ports()[2] self.connect(c1, c1.head, iface, pport) self.connect(c2, c2.head, iface, rport) # no UML metamodel yet self.assertTrue(c1.subject is None) #self.assertTrue(c1.end is None) self.assertTrue(c2.subject is None) #self.assertTrue(c2.end is None) # check port status self.assertTrue(pport.provided and not pport.required) self.assertTrue(rport.required and not rport.provided) p1 = iface.ports()[1] p2 = iface.ports()[3] self.assertTrue(not p1.required and not p1.provided) self.assertTrue(not p2.required and not p2.provided) def test_simple_disconnection(self): """Test disconnection of simple connection to an interface """ iface = self.create(items.InterfaceItem, UML.Component) line = self.create(items.ConnectorItem) iface.folded = iface.FOLDED_PROVIDED pport = iface.ports()[1] self.connect(line, line.head, iface, pport) # test preconditions assert pport.provided and not pport.required and pport.connectable self.disconnect(line, line.head) self.assertEquals(iface.FOLDED_PROVIDED, iface.folded) self.assertEquals(iface._angle, 0) self.assertTrue(iface._name.is_visible()) self.assertFalse(any(p.provided for p in iface.ports())) self.assertFalse(any(p.required for p in iface.ports())) self.assertTrue(all(p.connectable for p in iface.ports())) class AssemblyConnectorTestCase(TestCase): """ Test assembly connector. It is assumed that interface and component connection tests defined above are working correctly. """ def create_interfaces(self, *args): """ Generate interfaces with names sepecified by arguments. :Paramters: args List of interface names. """ for name in args: interface = self.element_factory.create(UML.Interface) interface.name = name yield interface def provide(self, component, interface): """ Change component's data so it implements interfaces. """ impl = self.element_factory.create(UML.Implementation) component.implementation = impl impl.contract = interface def require(self, component, interface): """ Change component's data so it requires interface. """ usage = self.element_factory.create(UML.Usage) component.clientDependency = usage usage.supplier = interface def test_getting_component(self): """Test getting component """ conn1 = self.create(items.ConnectorItem) conn2 = self.create(items.ConnectorItem) c1 = self.create(items.ComponentItem, UML.Component) c2 = self.create(items.ComponentItem, UML.Component) # connect component self.connect(conn1, conn1.tail, c1) self.connect(conn2, conn2.head, c2) self.assertTrue(c1 is ConnectorConnectBase.get_component(conn1)) self.assertTrue(c2 is ConnectorConnectBase.get_component(conn2)) def test_connection(self): """Test basic assembly connection """ conn1 = self.create(items.ConnectorItem) conn2 = self.create(items.ConnectorItem) c1 = self.create(items.ComponentItem, UML.Component) c2 = self.create(items.ComponentItem, UML.Component) iface = self.create(items.InterfaceItem, UML.Interface) iface.folded = iface.FOLDED_ASSEMBLY pport = iface.ports()[0] rport = iface.ports()[2] # first component provides interface # and the second one requires it self.provide(c1.subject, iface.subject) self.require(c2.subject, iface.subject) # connect component self.connect(conn1, conn1.head, c1) self.connect(conn2, conn2.head, c2) # make an assembly self.connect(conn1, conn1.tail, iface, pport) self.connect(conn2, conn2.tail, iface, rport) # test UML data model self.assertTrue(conn1.subject is conn2.subject, '%s is not %s' % (conn1.subject, conn2.subject)) assembly = conn1.subject self.assertTrue(isinstance(assembly, UML.Connector)) self.assertEquals('assembly', assembly.kind) # there should be two connector ends self.assertEquals(2, len(assembly.end)) # interface is on both ends #end1 = conn1.end #end2 = conn2.end #self.assertTrue(end1 in assembly.end, # '%s not in %s' % (end1, assembly.end)) #self.assertTrue(end2 in assembly.end, # '%s not in %s' % (end2, assembly.end)) #self.assertEquals(end1.role, iface.subject) #self.assertEquals(end2.role, iface.subject) # ends of connector point to components #p1 = end1.partWithPort #p2 = end2.partWithPort #self.assertEquals(p1, c1.subject.ownedPort[0], # '%s != %s' % (p1, c1.subject.ownedPort)) #self.assertEquals(p2, c2.subject.ownedPort[0], # '%s != %s' % (p2, c2.subject.ownedPort)) def test_required_port_glue(self): """Test if required port glueing works """ conn1 = self.create(items.ConnectorItem) conn2 = self.create(items.ConnectorItem) c1 = self.create(items.ComponentItem, UML.Component) c2 = self.create(items.ComponentItem, UML.Component) iface = self.create(items.InterfaceItem, UML.Interface) iface.folded = iface.FOLDED_ASSEMBLY pport = iface.ports()[0] rport = iface.ports()[2] self.provide(c1.subject, iface.subject) self.require(c2.subject, iface.subject) # connect components self.connect(conn1, conn1.head, c1) self.connect(conn2, conn2.head, c2) self.connect(conn1, conn1.tail, iface, pport) glued = self.allow(conn2, conn2.tail, iface, rport) self.assertTrue(glued) def test_wrong_port_glue(self): """Test if incorrect port glueing is forbidden """ conn1 = self.create(items.ConnectorItem) conn2 = self.create(items.ConnectorItem) conn3 = self.create(items.ConnectorItem) c1 = self.create(items.ComponentItem, UML.Component) c2 = self.create(items.ComponentItem, UML.Component) c3 = self.create(items.ComponentItem, UML.Component) iface = self.create(items.InterfaceItem, UML.Interface) iface.folded = iface.FOLDED_ASSEMBLY pport = iface.ports()[0] rport = iface.ports()[2] self.provide(c1.subject, iface.subject) self.require(c2.subject, iface.subject) self.require(c3.subject, iface.subject) # connect first two components self.connect(conn1, conn1.head, c1) self.connect(conn2, conn2.head, c2) self.connect(conn1, conn1.tail, iface, pport) self.connect(conn3, conn3.tail, iface, pport) # cannot allow to provided port of interface, which is required glued = self.allow(conn2, conn2.tail, iface, pport) self.assertFalse(glued) # cannot allow component, which requires an interface, when # connector is connected to to provided port glued = self.allow(conn3, conn3.head, c3) self.assertFalse(glued) def test_port_status(self): """Test if port type is set properly """ conn1 = self.create(items.ConnectorItem) c1 = self.create(items.ComponentItem, UML.Component) iface = self.create(items.InterfaceItem, UML.Interface) iface.folded = iface.FOLDED_ASSEMBLY pport = iface.ports()[0] rport = iface.ports()[2] # component requires interface self.require(c1.subject, iface.subject) # connect component self.connect(conn1, conn1.head, c1) # first step to make an assembly self.connect(conn1, conn1.tail, iface, rport) # check port type self.assertTrue(pport.provided) self.assertTrue(rport.required) def test_connection_order(self): """Test connection order of assembly connection """ conn1 = self.create(items.ConnectorItem) conn2 = self.create(items.ConnectorItem) c1 = self.create(items.ComponentItem, UML.Component) c2 = self.create(items.ComponentItem, UML.Component) iface = self.create(items.InterfaceItem, UML.Interface) iface.folded = iface.FOLDED_ASSEMBLY pport = iface.ports()[0] rport = iface.ports()[2] # both components provide interface only self.provide(c1.subject, iface.subject) self.provide(c2.subject, iface.subject) # connect components self.connect(conn1, conn1.head, c1) self.connect(conn2, conn2.head, c2) # connect to provided port self.connect(conn1, conn1.tail, iface, pport) self.connect(conn2, conn2.tail, iface, pport) # no UML data model yet (no connection on required port) self.assertTrue(conn1.subject is None) self.assertTrue(conn2.subject is None) #self.assertTrue(conn1.end is None) #self.assertTrue(conn2.end is None) def test_addtional_connections(self): """Test additional connections to assembly connection """ conn1 = self.create(items.ConnectorItem) conn2 = self.create(items.ConnectorItem) conn3 = self.create(items.ConnectorItem) c1 = self.create(items.ComponentItem, UML.Component) c2 = self.create(items.ComponentItem, UML.Component) c3 = self.create(items.ComponentItem, UML.Component) iface = self.create(items.InterfaceItem, UML.Interface) iface.folded = iface.FOLDED_ASSEMBLY pport = iface.ports()[0] rport = iface.ports()[2] # provide and require interface by components self.provide(c1.subject, iface.subject) self.require(c2.subject, iface.subject) self.require(c3.subject, iface.subject) # connect components self.connect(conn1, conn1.head, c1) self.connect(conn2, conn2.head, c2) self.connect(conn3, conn3.head, c3) # create assembly self.connect(conn1, conn1.tail, iface, pport) self.connect(conn2, conn2.tail, iface, rport) # test precondition assert conn1.subject and conn2.subject # additional connection self.connect(conn3, conn3.tail, iface, rport) # test UML data model self.assertTrue(conn3.subject is conn1.subject) #self.assertTrue(conn3.end is not None) assembly = conn1.subject self.assertEquals(3, len(assembly.end)) #end3 = conn3.end #self.assertTrue(end3 in assembly.end, # '%s not in %s' % (end3, assembly.end)) #self.assertEquals(end3.role, iface.subject) # ends of connector point to components #p3 = end3.partWithPort #self.assertEquals(p3, c3.subject.ownedPort[0], # '%s != %s' % (p3, c3.subject.ownedPort)) def test_disconnection(self): """Test assembly connector disconnection """ conn1 = self.create(items.ConnectorItem) conn2 = self.create(items.ConnectorItem) c1 = self.create(items.ComponentItem, UML.Component) c2 = self.create(items.ComponentItem, UML.Component) iface = self.create(items.InterfaceItem, UML.Interface) iface.folded = iface.FOLDED_ASSEMBLY pport = iface.ports()[0] rport = iface.ports()[2] # first component provides interface # and the second one requires it self.provide(c1.subject, iface.subject) self.require(c2.subject, iface.subject) # connect component self.connect(conn1, conn1.head, c1) self.connect(conn2, conn2.head, c2) # make an assembly self.connect(conn1, conn1.tail, iface, pport) self.connect(conn2, conn2.tail, iface, rport) # test precondition assert conn1.subject is conn2.subject self.disconnect(conn1, conn1.head) self.assertTrue(conn1.subject is None) self.assertTrue(conn2.subject is None) self.assertEquals(0, len(self.kindof(UML.Connector))) self.assertEquals(0, len(self.kindof(UML.ConnectorEnd))) self.assertEquals(0, len(self.kindof(UML.Port))) def test_disconnection_order(self): """Test assembly connector disconnection order """ conn1 = self.create(items.ConnectorItem) conn2 = self.create(items.ConnectorItem) conn3 = self.create(items.ConnectorItem) c1 = self.create(items.ComponentItem, UML.Component) c2 = self.create(items.ComponentItem, UML.Component) c3 = self.create(items.ComponentItem, UML.Component) iface = self.create(items.InterfaceItem, UML.Interface) iface.folded = iface.FOLDED_ASSEMBLY pport = iface.ports()[0] rport = iface.ports()[2] # provide and require interface self.provide(c1.subject, iface.subject) self.require(c2.subject, iface.subject) self.require(c3.subject, iface.subject) # connect components self.connect(conn1, conn1.head, c1) self.connect(conn2, conn2.head, c2) self.connect(conn3, conn3.head, c3) # make assembly self.connect(conn1, conn1.tail, iface, pport) self.connect(conn2, conn2.tail, iface, rport) self.connect(conn3, conn3.tail, iface, rport) # test precondition assert conn1.subject is conn2.subject is conn3.subject # disconnect from provided port # assembly should be destroyed log.debug('Perform disconnect from here') self.disconnect(conn1, conn1.head) log.debug('Disconnect done') self.assertTrue(conn1.subject is None) self.assertTrue(conn2.subject is None) self.assertTrue(conn3.subject is None) self.assertEquals(0, len(self.kindof(UML.Connector))) self.assertEquals(0, len(self.kindof(UML.ConnectorEnd))) self.assertEquals(0, len(self.kindof(UML.Port))) # vim:sw=4:et:ai gaphor-0.17.2/gaphor/adapters/connectors.py000066400000000000000000000373201220151210700206670ustar00rootroot00000000000000""" Connector adapters. To register connectors implemented in this module, it is imported in gaphor.adapter package. """ from zope import interface, component from logging import getLogger from gaphas import geometry from gaphor import UML from gaphor.core import inject from gaphor.diagram.interfaces import IConnect from gaphor.diagram import items logger = getLogger('Connector') class AbstractConnect(object): """ Connection adapter for Gaphor diagram items. Line item ``line`` connects with a handle to a connectable item ``element``. Attributes: - line: connecting item - element: connectable item The following methods are required to make this work: - `allow()`: is the connection allowed at all (during mouse movement for example). - `connect()`: Establish a connection between element and line. Also takes care of disconnects, if required (e.g. 1:1 relationships) - `disconnect()`: Break connection, called when dropping a handle on a point where it can not connect. - `reconnect()`: Connect to another item (only used if present) By convention the adapters are registered by (element, line) -- in that order. """ interface.implements(IConnect) element_factory = inject('element_factory') def __init__(self, element, line): self.element = element self.line = line self.canvas = self.element.canvas assert self.canvas == self.element.canvas == self.line.canvas def get_connection(self, handle): """ Get connection information """ return self.canvas.get_connection(handle) def get_connected(self, handle): """ Get item connected to a handle. """ cinfo = self.canvas.get_connection(handle) if cinfo is not None: return cinfo.connected def get_connected_port(self, handle): """ Get port of item connected to connecting item via specified handle. """ cinfo = self.canvas.get_connection(handle) if cinfo is not None: return cinfo.port def allow(self, handle, port): """ Determine if items can be connected. The method contains a hack for folded interfaces, see `gaphor.diagram.classes.interface` module documentation for connection to folded interface rules. Returns `True` by default. """ iface = self.element if isinstance(iface, items.InterfaceItem) and iface.folded: canvas = self.canvas count = any(canvas.get_connections(connected=iface)) return not count and isinstance(self.line, (items.DependencyItem, items.ImplementationItem)) return True def connect(self, handle, port): """ Connect to an element. Note that at this point the line may be connected to some other, or the same element. Also the connection at UML level still exists. Returns `True` if a connection is established. """ return True # def reconnect(self, handle, port): # """ # UML model reconnection method. # """ # raise NotImplementedError('Reconnection not implemented') def disconnect(self, handle): """ Disconnect UML model level connections. """ pass class CommentLineElementConnect(AbstractConnect): """ Connect a comment line to any element item. """ component.adapts(items.ElementItem, items.CommentLineItem) def allow(self, handle, port): """ In addition to the normal check, both line ends may not be connected to the same element. Same goes for subjects. One of the ends should be connected to a UML.Comment element. """ opposite = self.line.opposite(handle) connected_to = self.get_connected(opposite) element = self.element if connected_to is element: return None # Same goes for subjects: if connected_to and \ (not (connected_to.subject or element.subject)) \ and connected_to.subject is element.subject: return None # One end should be connected to a CommentItem: cls = items.CommentItem glue_ok = isinstance(connected_to, cls) ^ isinstance(self.element, cls) if connected_to and not glue_ok: return None # Do not allow to links between the comment and the element if connected_to and element and \ ((isinstance(connected_to.subject, UML.Comment) and \ self.element.subject in connected_to.subject.annotatedElement) or \ (isinstance(self.element.subject, UML.Comment) and \ connected_to.subject in self.element.subject.annotatedElement)): return None return super(CommentLineElementConnect, self).allow(handle, port) def connect(self, handle, port): if super(CommentLineElementConnect, self).connect(handle, port): opposite = self.line.opposite(handle) connected_to = self.get_connected(opposite) if connected_to: if isinstance(connected_to.subject, UML.Comment): connected_to.subject.annotatedElement = self.element.subject else: self.element.subject.annotatedElement = connected_to.subject def disconnect(self, handle): opposite = self.line.opposite(handle) oct = self.get_connected(opposite) hct = self.get_connected(handle) if hct and oct: logger.debug('Disconnecting %s and %s' % (hct, oct)) try: if hct.subject and isinstance(oct.subject, UML.Comment): del oct.subject.annotatedElement[hct.subject] elif hct.subject and oct.subject: del hct.subject.annotatedElement[oct.subject] except ValueError: logger.debug('Invoked CommentLineElementConnect.disconnect() for nonexistant relationship') super(CommentLineElementConnect, self).disconnect(handle) component.provideAdapter(CommentLineElementConnect) class CommentLineLineConnect(AbstractConnect): """ Connect a comment line to any diagram line. """ component.adapts(items.DiagramLine, items.CommentLineItem) def allow(self, handle, port): """ In addition to the normal check, both line ends may not be connected to the same element. Same goes for subjects. One of the ends should be connected to a UML.Comment element. """ opposite = self.line.opposite(handle) element = self.element connected_to = self.get_connected(opposite) # do not connect to the same item nor connect to other comment line if connected_to is element or not element.subject or \ isinstance(element, items.CommentLineItem): return None # Same goes for subjects: if connected_to and \ (not (connected_to.subject or element.subject)) \ and connected_to.subject is element.subject: return None print 'Connecting', element, 'with', element.subject # One end should be connected to a CommentItem: cls = items.CommentItem glue_ok = isinstance(connected_to, cls) ^ isinstance(self.element, cls) if connected_to and not glue_ok: return None return super(CommentLineLineConnect, self).allow(handle, port) def connect(self, handle, port): if super(CommentLineLineConnect, self).connect(handle, port): opposite = self.line.opposite(handle) c = self.get_connected(opposite) if c and self.element.subject: if isinstance(c.subject, UML.Comment): c.subject.annotatedElement = self.element.subject else: self.element.subject.annotatedElement = c.subject def disconnect(self, handle): c1 = self.get_connected(handle) opposite = self.line.opposite(handle) c2 = self.get_connected(opposite) if c1 and c2: if isinstance(c1.subject, UML.Comment): del c1.subject.annotatedElement[c2.subject] elif c2.subject: del c2.subject.annotatedElement[c1.subject] super(CommentLineLineConnect, self).disconnect(handle) component.provideAdapter(CommentLineLineConnect) class UnaryRelationshipConnect(AbstractConnect): """ Base class for relationship connections, such as associations, dependencies and implementations. Unary relationships are allowed to connect both ends to the same element This class introduces a new method: relationship() which is used to find an existing relationship in the model that does not yet exist on the canvas. """ element_factory = inject('element_factory') def relationship(self, required_type, head, tail): """ Find an existing relationship in the model that meets the required type and is connected to the same model element the head and tail of the line are conncted to. type - the type of relationship we're looking for head - tuple (association name on line, association name on element) tail - tuple (association name on line, association name on element) """ line = self.line head_subject = self.get_connected(line.head).subject tail_subject = self.get_connected(line.tail).subject # First check if the right subject is already connected: if line.subject \ and getattr(line.subject, head.name) is head_subject \ and getattr(line.subject, tail.name) is tail_subject: return line.subject # Try to find a relationship, that is already created, but not # yet displayed in the diagram. for gen in getattr(tail_subject, tail.opposite): if not isinstance(gen, required_type): continue gen_head = getattr(gen, head.name) try: if not head_subject in gen_head: continue except TypeError: if not gen_head is head_subject: continue # Check for this entry on line.canvas for item in gen.presentation: # Allow line to be returned. Avoids strange # behaviour during loading if item.canvas is line.canvas and item is not line: break else: return gen return None def relationship_or_new(self, type, head, tail): """ Like relation(), but create a new instance of none was found. """ relation = self.relationship(type, head, tail) if not relation: line = self.line relation = self.element_factory.create(type) setattr(relation, head.name, self.get_connected(line.head).subject) setattr(relation, tail.name, self.get_connected(line.tail).subject) return relation def reconnect_relationship(self, handle, head, tail): """ Reconnect relationship for given handle. :Parameters: handle Handle at which reconnection happens. head Relationship head attribute name. tail Relationship tail attribute name. """ line = self.line c1 = self.get_connected(line.head) c2 = self.get_connected(line.tail) if line.head is handle: setattr(line.subject, head.name, c1.subject) elif line.tail is handle: setattr(line.subject, tail.name, c2.subject) else: raise ValueError('Incorrect handle passed to adapter') def connect_connected_items(self, connections=None): """ Cause items connected to ``line`` to reconnect, allowing them to establish or destroy relationships at model level. """ line = self.line canvas = self.canvas solver = canvas.solver # First make sure coordinates match solver.solve() for cinfo in connections or canvas.get_connections(connected=line): adapter = component.queryMultiAdapter((line, cinfo.connected), IConnect) assert adapter adapter.connect(cinfo.handle, cinfo.port) def disconnect_connected_items(self): """ Cause items connected to @line to be disconnected. This is nessesary if the subject of the @line is to be removed. Returns a list of (item, handle) pairs that were connected (this list can be used to connect items again with connect_connected_items()). """ line = self.line canvas = self.canvas solver = canvas.solver # First make sure coordinates match solver.solve() connections = list(canvas.get_connections(connected=line)) for cinfo in connections: adapter = component.queryMultiAdapter((cinfo.item, cinfo.connected), IConnect) assert adapter adapter.disconnect(cinfo.handle) return connections def connect_subject(self, handle): """ Establish the relationship at model level. """ raise NotImplementedError, 'Implement connect_subject() in a subclass' def disconnect_subject(self, handle): """ Disconnect the diagram item from its model element. If there are no more presentations(diagram items) connected to the model element, unlink() it too. """ line = self.line old = line.subject del line.subject if old and len(old.presentation) == 0: old.unlink() def connect(self, handle, port): """ Connect the items to each other. The model level relationship is created by create_subject() """ if super(UnaryRelationshipConnect, self).connect(handle, port): opposite = self.line.opposite(handle) oct = self.get_connected(opposite) if oct: self.connect_subject(handle) line = self.line if line.subject: self.connect_connected_items() return True def disconnect(self, handle): """ Disconnect model element. """ line = self.line opposite = line.opposite(handle) oct = self.get_connected(opposite) hct = self.get_connected(handle) if hct and oct: # Both sides of line are connected => disconnect old = line.subject connections = self.disconnect_connected_items() self.disconnect_subject(handle) if old: self.connect_connected_items(connections) super(UnaryRelationshipConnect, self).disconnect(handle) class RelationshipConnect(UnaryRelationshipConnect): """ """ def allow(self, handle, port): """ In addition to the normal check, both relationship ends may not be connected to the same element. Same goes for subjects. """ opposite = self.line.opposite(handle) line = self.line element = self.element connected_to = self.get_connected(opposite) # Element can not be a parent for itself. if connected_to is element: return None # Same goes for subjects: if connected_to and \ (not (connected_to.subject or element.subject)) \ and connected_to.subject is element.subject: return None return super(RelationshipConnect, self).allow(handle, port) # vim:sw=4:et:ai gaphor-0.17.2/gaphor/adapters/editors.py000066400000000000000000000133351220151210700201630ustar00rootroot00000000000000""" Adapters """ from zope import interface, component from gaphas.item import NW, SE from gaphas import geometry from gaphas import constraint from gaphor import UML from gaphor.core import inject from gaphor.diagram.interfaces import IEditor from gaphor.diagram import items from gaphor.misc.rattr import rgetattr, rsetattr from simplegeneric import generic @generic def editable(el): """ Return editable part of UML element. It returns element itself by default. """ return el @editable.when_type(UML.Slot) def editable_slot(el): """ Return editable part of a slot. """ return el.value class CommentItemEditor(object): """ Text edit support for Comment item. """ interface.implements(IEditor) component.adapts(items.CommentItem) def __init__(self, item): self._item = item def is_editable(self, x, y): return True def get_text(self): return self._item.subject.body def get_bounds(self): return None def update_text(self, text): self._item.subject.body = text def key_pressed(self, pos, key): pass component.provideAdapter(CommentItemEditor) class NamedItemEditor(object): """ Text edit support for Named items. """ interface.implements(IEditor) component.adapts(items.NamedItem) def __init__(self, item): self._item = item def is_editable(self, x, y): return True def get_text(self): s = self._item.subject return s.name if s else '' def get_bounds(self): return None def update_text(self, text): if self._item.subject: self._item.subject.name = text self._item.request_update() def key_pressed(self, pos, key): pass component.provideAdapter(NamedItemEditor) class DiagramItemTextEditor(object): """ Text edit support for diagram items containing text elements. """ interface.implements(IEditor) component.adapts(items.DiagramItem) def __init__(self, item): self._item = item self._text_element = None def is_editable(self, x, y): if not self._item.subject: return False for txt in self._item.texts(): if (x, y) in txt.bounds: self._text_element = txt break return self._text_element is not None def get_text(self): if self._text_element: return rgetattr(self._item.subject, self._text_element.attr) def get_bounds(self): return None def update_text(self, text): log.debug('Updating text to %s' % text) if self._text_element: self._text_element.text = text rsetattr(self._item.subject, self._text_element.attr, text) def key_pressed(self, pos, key): pass component.provideAdapter(DiagramItemTextEditor) class CompartmentItemEditor(object): """ Text editor support for compartment items. """ interface.implements(IEditor) component.adapts(items.CompartmentItem) def __init__(self, item): self._item = item self._edit = None def is_editable(self, x, y): """ Find out what's located at point (x, y), is it in the name part or is it text in some compartment """ self._edit = self._item.item_at(x, y) return bool(self._edit and self._edit.subject) def get_text(self): return UML.format(editable(self._edit.subject)) def get_bounds(self): return None def update_text(self, text): UML.parse(editable(self._edit.subject), text) def key_pressed(self, pos, key): pass component.provideAdapter(CompartmentItemEditor) class AssociationItemEditor(object): interface.implements(IEditor) component.adapts(items.AssociationItem) def __init__(self, item): self._item = item self._edit = None def is_editable(self, x, y): """Find out what's located at point (x, y), is it in the name part or is it text in some compartment """ item = self._item if not item.subject: return False if item.head_end.point((x, y)) <= 0: self._edit = item.head_end elif item.tail_end.point((x, y)) <= 0: self._edit = item.tail_end else: self._edit = item return True def get_text(self): if self._edit is self._item: return self._edit.subject.name return UML.format(self._edit.subject, visibility=True, is_derived=True, type=True, multiplicity=True, default=True) def get_bounds(self): return None def update_text(self, text): UML.parse(self._edit.subject, text) def key_pressed(self, pos, key): pass component.provideAdapter(AssociationItemEditor) class ForkNodeItemEditor(object): """Text edit support for fork node join specification. """ interface.implements(IEditor) component.adapts(items.ForkNodeItem) element_factory = inject('element_factory') def __init__(self, item): self._item = item def is_editable(self, x, y): return True def get_text(self): """ Get join specification text. """ if self._item.subject.joinSpec: return self._item.subject.joinSpec else: return '' def get_bounds(self): return None def update_text(self, text): """ Set join specification text. """ spec = self._item.subject.joinSpec if not spec: spec = text def key_pressed(self, pos, key): pass component.provideAdapter(ForkNodeItemEditor) # vim:sw=4:et:ai gaphor-0.17.2/gaphor/adapters/grouping.py000066400000000000000000000164071220151210700203470ustar00rootroot00000000000000""" Grouping functionality allows to nest one item within another item (parent item). This is useful in several use cases - artifact deployed within a node - a class within a package or a component - composite structures (i.e. component within a node) The grouping adapters has to implement three methods, see `AbstractGroup` class. It is important to note, that grouping adapters can be queried before instance of an item to be grouped is created. This happens when item is about to be created. Therefore `AbstractGroup.can_contain` has to be aware that `AbstractGroup.item` can be null. """ from zope import interface, component from gaphor import UML from gaphor.core import inject from gaphor.diagram import items from gaphor.diagram.interfaces import IGroup class AbstractGroup(object): """ Base class for grouping UML objects. :Attributes: parent Parent item, which groups other items. item Item to be grouped. """ interface.implements(IGroup) element_factory = inject('element_factory') def __init__(self, parent, item): self.parent = parent self.item = item def can_contain(self): """ Check if parent can contain an item. True by default. """ return True def group(self): """ Group an item within parent. """ raise NotImplemented, 'This is abstract method' def ungroup(self): """ Remove item from parent. """ raise NotImplemented, 'This is abstract method' class InteractionLifelineGroup(AbstractGroup): """ Add lifeline to interaction. """ def group(self): self.parent.subject.lifeline = self.item.subject self.parent.canvas.reparent(self.item, self.parent) def ungroup(self): del self.parent.subject.lifeline[self.item.subject] component.provideAdapter(factory=InteractionLifelineGroup, adapts=(items.InteractionItem, items.LifelineItem)) class NodeGroup(AbstractGroup): """ Add node to another node. """ def group(self): self.parent.subject.nestedNode = self.item.subject def ungroup(self): del self.parent.subject.nestedNode[self.item.subject] component.provideAdapter(factory=NodeGroup, adapts=(items.NodeItem, items.NodeItem)) class NodeComponentGroup(AbstractGroup): """ Add components to node using internal structures. """ def group(self): node = self.parent.subject component = self.item.subject # node attribute a1 = self.element_factory.create(UML.Property) a1.aggregation = 'composite' # component attribute a2 = self.element_factory.create(UML.Property) e1 = self.element_factory.create(UML.ConnectorEnd) e2 = self.element_factory.create(UML.ConnectorEnd) # create connection between node and component e1.role = a1 e2.role = a2 connector = self.element_factory.create(UML.Connector) connector.end = e1 connector.end = e2 # compose component within node node.ownedAttribute = a1 node.ownedConnector = connector component.ownedAttribute = a2 def ungroup(self): node = self.parent.subject component = self.item.subject for connector in node.ownedConnector: e1 = connector.end[0] e2 = connector.end[1] if e1.role in node.ownedAttribute and e2.role in component.ownedAttribute: e1.role.unlink() e2.role.unlink() e1.unlink() e2.unlink() connector.unlink() log.debug('Removed %s from node %s' % (component, node)) component.provideAdapter(factory=NodeComponentGroup, adapts=(items.NodeItem, items.ComponentItem)) class NodeArtifactGroup(AbstractGroup): """ Deploy artifact on node. """ def group(self): node = self.parent.subject artifact = self.item.subject # deploy artifact on node deployment = self.element_factory.create(UML.Deployment) node.deployment = deployment deployment.deployedArtifact = artifact def ungroup(self): node = self.parent.subject artifact = self.item.subject for deployment in node.deployment: if deployment.deployedArtifact[0] is artifact: deployment.unlink() log.debug('Removed %s from node %s' % (artifact, node)) component.provideAdapter(factory=NodeArtifactGroup, adapts=(items.NodeItem, items.ArtifactItem)) class SubsystemUseCaseGroup(AbstractGroup): """ Make subsystem a subject of an use case. """ def group(self): component = self.parent.subject usecase = self.item.subject usecase.subject = component def ungroup(self): component = self.parent.subject usecase = self.item.subject usecase.subject.remove(component) component.provideAdapter(factory=SubsystemUseCaseGroup, adapts=(items.SubsystemItem, items.UseCaseItem)) class ActivityPartitionsGroup(AbstractGroup): """ Group activity partitions. """ def can_contain(self): return not self.parent.subject \ or (self.parent.subject and len(self.parent.subject.node) == 0) def group(self): p = self.parent.subject sp = self.element_factory.create(UML.ActivityPartition) self.item.subject = sp sp.name = 'Swimlane' if p: p.subpartition = sp for k in self.item.canvas.get_children(self.item): sp.subpartition = k.subject def ungroup(self): p = self.parent.subject sp = self.item.subject if p: p.subpartition.remove(sp) self.item.subject = None for s in sp.subpartition: sp.subpartition.remove(s) assert len(sp.node) == 0 # ungroup activity nodes canvas = self.item.canvas nodes = [n for n in canvas.get_children(self.item) if isinstance(n, (items.ActivityNodeItem, items.ActionItem, items.ObjectNodeItem, items.ForkNodeItem))] for n in nodes: canvas.reparent(n, None) sp.unlink() component.provideAdapter(factory=ActivityPartitionsGroup, adapts=(items.PartitionItem, items.PartitionItem)) class ActivityNodePartitionGroup(AbstractGroup): """ Group activity nodes within activity partition. """ def can_contain(self): return self.parent.subject \ and len(self.parent.subject.subpartition) == 0 def group(self): partition = self.parent.subject node = self.item.subject partition.node = node def ungroup(self): partition = self.parent.subject node = self.item.subject partition.node.remove(node) component.provideAdapter(factory=ActivityNodePartitionGroup, adapts=(items.PartitionItem, items.ActivityNodeItem)) component.provideAdapter(factory=ActivityNodePartitionGroup, adapts=(items.PartitionItem, items.ActionItem)) component.provideAdapter(factory=ActivityNodePartitionGroup, adapts=(items.PartitionItem, items.ObjectNodeItem)) component.provideAdapter(factory=ActivityNodePartitionGroup, adapts=(items.PartitionItem, items.ForkNodeItem)) gaphor-0.17.2/gaphor/adapters/interactions/000077500000000000000000000000001220151210700206355ustar00rootroot00000000000000gaphor-0.17.2/gaphor/adapters/interactions/__init__.py000066400000000000000000000000001220151210700227340ustar00rootroot00000000000000gaphor-0.17.2/gaphor/adapters/interactions/messageconnect.py000066400000000000000000000116241220151210700242110ustar00rootroot00000000000000""" Message item connection adapters. """ from gaphor.adapters.connectors import AbstractConnect from zope import interface, component from gaphor import UML from gaphor.diagram import items class MessageLifelineConnect(AbstractConnect): """ Connect lifeline with a message. A message can connect to both the lifeline's head (the rectangle) or the lifetime line. In case it's added to the head, the message is considered to be part of a communication diagram. If the message is added to a lifetime line, it's considered a sequence diagram. """ component.adapts(items.LifelineItem, items.MessageItem) def connect_lifelines(self, line, send, received): """ Always create a new Message with two EventOccurence instances. """ def get_subject(): if not line.subject: message = self.element_factory.create(UML.Message) message.name = 'call()' line.subject = message return line.subject if send: message = get_subject() if not message.sendEvent: event = self.element_factory.create(UML.MessageOccurrenceSpecification) event.sendMessage = message event.covered = send.subject if received: message = get_subject() if not message.receiveEvent: event = self.element_factory.create(UML.MessageOccurrenceSpecification) event.receiveMessage = message event.covered = received.subject def disconnect_lifelines(self, line): """ Disconnect lifeline and set appropriate kind of message item. If there are no lifelines connected on both ends, then remove the message from the data model. """ send = self.get_connected(line.head) received = self.get_connected(line.tail) if send: event = line.subject.receiveEvent if event: event.unlink() if received: event = line.subject.sendEvent if event: event.unlink() # one is disconnected and one is about to be disconnected, # so destroy the message if not send or not received: # Both ends are disconnected: message = line.subject del line.subject if not message.presentation: message.unlink() for message in list(line._messages): line.remove_message(message, False) message.unlink() for message in list(line._inverted_messages): line.remove_message(message, True) message.unlink() def allow(self, handle, port): """ Glue to lifeline's head or lifetime. If lifeline's lifetime is visible then disallow connection to lifeline's head. """ element = self.element lifetime = element.lifetime line = self.line opposite = line.opposite(handle) ol = self.get_connected(opposite) if ol: opposite_is_visible = ol.lifetime.visible # connect lifetimes if both are visible or both invisible return not (lifetime.visible ^ opposite_is_visible) return not (lifetime.visible ^ (port is element.lifetime.port)) def connect(self, handle, port): super(MessageLifelineConnect, self).connect(handle, port) line = self.line send = self.get_connected(line.head) received = self.get_connected(line.tail) self.connect_lifelines(line, send, received) lifetime = self.element.lifetime # if connected to head, then make lifetime invisible if port is lifetime.port: lifetime.min_length = lifetime.MIN_LENGTH_VISIBLE else: lifetime.visible = False lifetime.connectable = False def disconnect(self, handle): super(MessageLifelineConnect, self).disconnect(handle) line = self.line send = self.get_connected(line.head) received = self.get_connected(line.tail) lifeline = self.element lifetime = lifeline.lifetime # if a message is delete message, then disconnection causes # lifeline to be no longer destroyed (note that there can be # only one delete message connected to lifeline) if received and line.subject.messageSort == 'deleteMessage': received.is_destroyed = False received.request_update() self.disconnect_lifelines(line) if len(list(self.canvas.get_connections(connected=lifeline))) == 1: # after disconnection count of connected items will be # zero, so allow connections to lifeline's lifetime lifetime.connectable = True lifetime.min_length = lifetime.MIN_LENGTH component.provideAdapter(MessageLifelineConnect) gaphor-0.17.2/gaphor/adapters/interactions/tests/000077500000000000000000000000001220151210700217775ustar00rootroot00000000000000gaphor-0.17.2/gaphor/adapters/interactions/tests/__init__.py000066400000000000000000000000001220151210700240760ustar00rootroot00000000000000gaphor-0.17.2/gaphor/adapters/interactions/tests/test_message.py000066400000000000000000000227561220151210700250500ustar00rootroot00000000000000""" Message connection adapter tests. """ from gaphor.tests import TestCase from gaphor import UML from gaphor.diagram import items class BasicMessageConnectionsTestCase(TestCase): def test_head_glue(self): """Test message head glue """ ll = self.create(items.LifelineItem) msg = self.create(items.MessageItem) # get head port port = ll.ports()[0] glued = self.allow(msg, msg.head, ll, port) self.assertTrue(glued) def test_invisible_lifetime_glue(self): """Test message to invisible lifetime glue """ ll = self.create(items.LifelineItem) msg = self.create(items.MessageItem) glued = self.allow(msg, msg.head, ll, ll.lifetime.port) assert not ll.lifetime.visible self.assertFalse(glued) def test_visible_lifetime_glue(self): """Test message to visible lifetime glue """ ll = self.create(items.LifelineItem) msg = self.create(items.MessageItem) ll.lifetime.visible = True glued = self.allow(msg, msg.head, ll, ll.lifetime.port) self.assertTrue(glued) def test_lost_message_connection(self): """Test lost message connection """ ll = self.create(items.LifelineItem) msg = self.create(items.MessageItem) self.connect(msg, msg.head, ll) # If one side is connected a "lost" message is created self.assertTrue(msg.subject is not None) self.assertEquals(msg.subject.messageKind, 'lost') messages = self.kindof(UML.Message) occurences = self.kindof(UML.MessageOccurrenceSpecification) self.assertEquals(1, len(messages)) self.assertEquals(1, len(occurences)) self.assertTrue(messages[0] is msg.subject) self.assertTrue(occurences[0] is msg.subject.sendEvent) def test_found_message_connection(self): """Test found message connection """ ll = self.create(items.LifelineItem) msg = self.create(items.MessageItem) self.connect(msg, msg.tail, ll) # If one side is connected a "found" message is created self.assertTrue(msg.subject is not None) self.assertEquals(msg.subject.messageKind, 'found') messages = self.kindof(UML.Message) occurences = self.kindof(UML.MessageOccurrenceSpecification) self.assertEquals(1, len(messages)) self.assertEquals(1, len(occurences)) self.assertTrue(messages[0] is msg.subject) self.assertTrue(occurences[0] is msg.subject.receiveEvent) def test_complete_message_connection(self): """Test complete message connection """ ll1 = self.create(items.LifelineItem) ll2 = self.create(items.LifelineItem) msg = self.create(items.MessageItem) self.connect(msg, msg.head, ll1) self.connect(msg, msg.tail, ll2) # two sides are connected - "complete" message is created self.assertTrue(msg.subject is not None) self.assertEquals(msg.subject.messageKind, 'complete') messages = self.kindof(UML.Message) occurences = self.kindof(UML.MessageOccurrenceSpecification) self.assertEquals(1, len(messages)) self.assertEquals(2, len(occurences)) self.assertTrue(messages[0] is msg.subject) self.assertTrue(msg.subject.sendEvent in occurences, '%s' % occurences) self.assertTrue(msg.subject.receiveEvent in occurences, '%s' % occurences) def test_lifetime_connection(self): """Test messages' lifetimes connection """ msg = self.create(items.MessageItem) ll1 = self.create(items.LifelineItem) ll2 = self.create(items.LifelineItem) # make lifelines to be in sequence diagram mode ll1.lifetime.visible = True ll2.lifetime.visible = True assert ll1.lifetime.visible and ll2.lifetime.visible # connect lifetimes with messages message to lifeline's head self.connect(msg, msg.head, ll1, ll1.lifetime.port) self.connect(msg, msg.tail, ll2, ll2.lifetime.port) self.assertTrue(msg.subject is not None) self.assertEquals(msg.subject.messageKind, 'complete') def test_disconnection(self): """Test message disconnection """ ll1 = self.create(items.LifelineItem) ll2 = self.create(items.LifelineItem) msg = self.create(items.MessageItem) self.connect(msg, msg.head, ll1) self.connect(msg, msg.tail, ll2) # one side disconnection self.disconnect(msg, msg.head) self.assertTrue(msg.subject is not None, '%s' % msg.subject) # 2nd side disconnection self.disconnect(msg, msg.tail) self.assertTrue(msg.subject is None, '%s' % msg.subject) def test_lifetime_connectivity_on_head(self): """Test lifeline's lifetime connectivity change on head connection """ ll = self.create(items.LifelineItem) msg = self.create(items.MessageItem) # connect message to lifeline's head, lifeline's lifetime # visibility and connectivity should change self.connect(msg, msg.head, ll) self.assertFalse(ll.lifetime.visible) self.assertFalse(ll.lifetime.connectable) self.assertEquals(ll.lifetime.MIN_LENGTH, ll.lifetime.min_length) # ... and disconnection self.disconnect(msg, msg.head) self.assertTrue(ll.lifetime.connectable) self.assertEquals(ll.lifetime.MIN_LENGTH, ll.lifetime.min_length) def test_lifetime_connectivity_on_lifetime(self): """Test lifeline's lifetime connectivity change on lifetime connection """ ll = self.create(items.LifelineItem) msg = self.create(items.MessageItem) ll.lifetime.visible = True # connect message to lifeline's lifetime, lifeline's lifetime # visibility and connectivity should unchange self.connect(msg, msg.head, ll, ll.lifetime.port) self.assertTrue(ll.lifetime.connectable) self.assertEquals(ll.lifetime.MIN_LENGTH_VISIBLE, ll.lifetime.min_length) # ... and disconnection self.disconnect(msg, msg.head) self.assertTrue(ll.lifetime.connectable) self.assertTrue(ll.lifetime.visible) self.assertEquals(ll.lifetime.MIN_LENGTH, ll.lifetime.min_length) class DiagramModeMessageConnectionTestCase(TestCase): def test_message_glue_cd(self): """Test glueing message on communication diagram """ lifeline1 = self.create(items.LifelineItem) lifeline2 = self.create(items.LifelineItem) message = self.create(items.MessageItem) # make second lifeline to be in sequence diagram mode lifeline2.lifetime.visible = True # connect head of message to lifeline's head self.connect(message, message.head, lifeline1) glued = self.allow(message, message.tail, lifeline2, lifeline2.lifetime.port) # no connection possible as 2nd lifeline is in sequence diagram # mode self.assertFalse(glued) def test_message_glue_sd(self): """Test glueing message on sequence diagram """ msg = self.create(items.MessageItem) ll1 = self.create(items.LifelineItem) ll2 = self.create(items.LifelineItem) # 1st lifeline - communication diagram # 2nd lifeline - sequence diagram ll2.lifetime.visible = True # connect lifetime of message to lifeline's lifetime self.connect(msg, msg.head, ll1, ll1.lifetime.port) glued = self.allow(msg, msg.tail, ll2) # no connection possible as 2nd lifeline is in communication # diagram mode self.assertFalse(glued) def test_messages_disconnect_cd(self): """Test disconnecting messages on communication diagram """ ll1 = self.create(items.LifelineItem) ll2 = self.create(items.LifelineItem) msg = self.create(items.MessageItem) self.connect(msg, msg.head, ll1) self.connect(msg, msg.tail, ll2) factory = self.element_factory subject = msg.subject assert subject.sendEvent and subject.receiveEvent # add some more messages m1 = UML.model.create_message(factory, subject) m2 = UML.model.create_message(factory, subject) msg.add_message(m1, False) msg.add_message(m2, False) # add some inverted messages m3 = UML.model.create_message(factory, subject, True) m4 = UML.model.create_message(factory, subject, True) msg.add_message(m3, True) msg.add_message(m4, True) messages = list(self.kindof(UML.Message)) occurences = set(self.kindof(UML.MessageOccurrenceSpecification)) # verify integrity of messages self.assertEquals(5, len(messages)) self.assertEquals(10, len(occurences)) for m in messages: self.assertTrue(m.sendEvent in occurences) self.assertTrue(m.receiveEvent in occurences) # lost/received messages self.disconnect(msg, msg.head) self.assertEquals(5, len(messages)) # verify integrity of messages self.assertEquals(10, len(occurences)) for m in messages: self.assertTrue(m.sendEvent is None or m.sendEvent in occurences) self.assertTrue(m.receiveEvent is None or m.receiveEvent in occurences) # no message after full disconnection self.disconnect(msg, msg.tail) self.assertEquals(0, len(self.kindof(UML.Message))) # vim:sw=4:et:ai gaphor-0.17.2/gaphor/adapters/profiles/000077500000000000000000000000001220151210700177565ustar00rootroot00000000000000gaphor-0.17.2/gaphor/adapters/profiles/__init__.py000066400000000000000000000000001220151210700220550ustar00rootroot00000000000000gaphor-0.17.2/gaphor/adapters/profiles/extensionconnect.py000066400000000000000000000065051220151210700237240ustar00rootroot00000000000000from gaphor.adapters.connectors import RelationshipConnect from zope import interface, component from gaphor import UML from gaphor.diagram import items class ExtensionConnect(RelationshipConnect): """ Connect class and stereotype items using an extension item. """ component.adapts(items.ClassifierItem, items.ExtensionItem) def allow(self, handle, port): line = self.line subject = self.element.subject if handle is line.head: # Element at the head should be a class # (implies stereotype as well) allow = isinstance(subject, UML.Class) elif handle is line.tail: # Element at the tail should be a stereotype allow = isinstance(subject, UML.Stereotype) return allow and super(ExtensionConnect, self).allow(handle, port) def connect_subject(self, handle): element = self.element line = self.line c1 = self.get_connected(line.head) c2 = self.get_connected(line.tail) if c1 and c2: head_type = c1.subject tail_type = c2.subject # First check if we do not already contain the right subject: if line.subject: end1 = line.subject.memberEnd[0] end2 = line.subject.memberEnd[1] if (end1.type is head_type and end2.type is tail_type) \ or (end2.type is head_type and end1.type is tail_type): return # TODO: make element at head end update! c1.request_update() # Find all associations and determine if the properties on # the association ends have a type that points to the class. for assoc in self.element_factory.select(): if isinstance(assoc, UML.Extension): end1 = assoc.memberEnd[0] end2 = assoc.memberEnd[1] if (end1.type is head_type and end2.type is tail_type) \ or (end2.type is head_type and end1.type is tail_type): # check if this entry is not yet in the diagram # Return if the association is not (yet) on the canvas for item in assoc.presentation: if item.canvas is element.canvas: break else: line.subject = assoc return else: # Create a new Extension relationship relation = UML.model.extend_with_stereotype(self.element_factory, head_type, tail_type) line.subject = relation def disconnect_subject(self, handle): """ Disconnect model element. Disconnect property (memberEnd) too, in case of end of life for Extension. """ opposite = self.line.opposite(handle) hct = self.get_connected(handle) oct = self.get_connected(opposite) if hct and oct: old = self.line.subject del self.line.subject if old and len(old.presentation) == 0: for e in old.memberEnd: e.unlink() old.unlink() component.provideAdapter(ExtensionConnect) gaphor-0.17.2/gaphor/adapters/profiles/metaclasseditor.py000066400000000000000000000046201220151210700235150ustar00rootroot00000000000000""" Metaclass item editors. """ import gtk from gaphor.core import _, inject, transactional from gaphor.ui.interfaces import IPropertyPage from zope import interface, component from gaphor.diagram import items from gaphor.adapters.propertypages import create_hbox_label, EventWatcher from gaphor import UML def _issubclass(c, b): try: return issubclass(c, b) except TypeError: return False class MetaclassNameEditor(object): """ Metaclass name editor. Provides editable combo box entry with predefined list of names of UML classes. """ interface.implements(IPropertyPage) order = 10 NAME_LABEL = _('Name') CLASSES = list(sorted(n for n in dir(UML) if _issubclass(getattr(UML, n), UML.Element) and n != 'Stereotype')) def __init__(self, item): self.item = item self.size_group = gtk.SizeGroup(gtk.SIZE_GROUP_HORIZONTAL) self.watcher = EventWatcher(item.subject) def construct(self): page = gtk.VBox() subject = self.item.subject if not subject: return page hbox = create_hbox_label(self, page, self.NAME_LABEL) model = gtk.ListStore(str) for c in self.CLASSES: model.append([c]) cb = gtk.ComboBoxEntry(model, 0) completion = gtk.EntryCompletion() completion.set_model(model) completion.set_minimum_key_length(1) completion.set_text_column(0) cb.child.set_completion(completion) entry = cb.child entry.set_text(subject and subject.name or '') hbox.pack_start(cb) page.set_data('default', entry) # monitor subject.name attribute changed_id = entry.connect('changed', self._on_name_change) def handler(event): if event.element is subject and event.new_value is not None: entry.handler_block(changed_id) entry.set_text(event.new_value) entry.handler_unblock(changed_id) self.watcher.watch('name', handler) \ .register_handlers() entry.connect('destroy', self.watcher.unregister_handlers) page.show_all() return page @transactional def _on_name_change(self, entry): self.item.subject.name = entry.get_text() component.provideAdapter(MetaclassNameEditor, adapts=[items.MetaclassItem], name='Properties') # vim:sw=4:et:ai gaphor-0.17.2/gaphor/adapters/profiles/stereotypespage.py000066400000000000000000000201011220151210700235450ustar00rootroot00000000000000""" Stereotype property page. """ import gtk from gaphor.core import _, inject, transactional from gaphor.ui.interfaces import IPropertyPage from gaphor.diagram import items from gaphor.diagram.diagramitem import StereotypeSupport from zope import interface, component from gaphor import UML from gaphor.adapters.propertypages import on_text_cell_edited, on_bool_cell_edited class StereotypeAttributes(gtk.TreeStore): """ GTK tree model to edit instance specifications of stereotypes. """ element_factory = inject('element_factory') def __init__(self, subject): gtk.TreeStore.__init__(self, str, str, bool, object, object, object) self.subject = subject self.refresh() def refresh(self): self.clear() subject = self.subject stereotypes = UML.model.get_stereotypes(self.element_factory, subject) instances = self.subject.appliedStereotype # shortcut map stereotype -> slot (InstanceSpecification) slots = {} for obj in instances: for slot in obj.slot: slots[slot.definingFeature] = slot for st in stereotypes: for obj in instances: if st in obj.classifier: break else: obj = None parent = self.append(None, (st.name, '', bool(obj), st, None, None)) if obj: for attr in st.ownedAttribute: if not attr.association: slot = slots.get(attr) value = slot.value if slot else '' data = (attr.name, value, True, attr, obj, slot) #print 'data', data self.append(parent, data) else: for attr in st.ownedAttribute: if not attr.association: data = (attr.name, '', False, attr, None, None) #print 'no data', data self.append(parent, data) @transactional def set_value(self, iter, col, value): if col == 2: self.select_stereotype(iter) elif col == 1: self.set_slot_value(iter, value) else: print 'col', col def select_stereotype(self, iter): """ Select the stereotype. """ path = self.get_path(iter) row = self[path] name, old_value, is_applied, stereotype, _, _ = row value = not is_applied log.debug('selecting %s' % list(row)) subject = self.subject if value: UML.model.apply_stereotype(self.element_factory, subject, stereotype) else: UML.model.remove_stereotype(subject, stereotype) row[2] = value # TODO: change refresh in a refresh of the data model, rather than a clear-refresh self.refresh() def set_slot_value(self, iter, value): """ Set value of stereotype property applied to an UML element. Slot is created if instance Create valuChange value of instance spe """ path = self.get_path(iter) row = self[path] name, old_value, is_applied, attr, obj, slot = row if isinstance(attr, UML.Stereotype): return # don't edit stereotype rows log.debug('editing %s' % list(row)) if slot is None and not value: return # nothing to do and don't create slot without value if slot is None: slot = UML.model.add_slot(self.element_factory, obj, attr) assert slot if value: slot.value = value else: # no value, then remove slot del obj.slot[slot] slot = None value = '' row[1] = value row[5] = slot log.debug('slots %s' % obj.slot) def create_stereotype_tree_view(model): """ Create a tree view for a editable tree model. :Parameters: model Model, for which tree view is created. """ tree_view = gtk.TreeView(model) tree_view.set_rules_hint(True) # Stereotype/Attributes col = gtk.TreeViewColumn('%s / %s' % (_('Stereotype'), _('Attribute'))) col.set_expand(True) renderer = gtk.CellRendererToggle() renderer.set_property('active', True) renderer.set_property('activatable', True) renderer.connect('toggled', on_bool_cell_edited, model, 2) col.pack_start(renderer, expand=False) col.add_attribute(renderer, 'active', 2) def show_checkbox(column, cell, model, iter): #value = model.get_value(iter, 4) #cell.set_property('active', value is not None) value = model.get_value(iter, 3) cell.set_property('visible', isinstance(value, UML.Stereotype)) col.set_cell_data_func(renderer, show_checkbox) renderer = gtk.CellRendererText() renderer.set_property('editable', False) renderer.set_property('is-expanded', True) col.pack_start(renderer, expand=False) col.add_attribute(renderer, 'text', 0) tree_view.append_column(col) # TODO: use col.set_cell_data_func(renderer, func, None) to toggle visibility # Value renderer = gtk.CellRendererText() renderer.set_property('is-expanded', True) renderer.connect('edited', on_text_cell_edited, model, 1) col = gtk.TreeViewColumn(_('Value'), renderer, text=1) col.set_expand(True) def set_editable(column, cell, model, iter): value = model.get_value(iter, 4) cell.set_property('editable', bool(value)) col.set_cell_data_func(renderer, set_editable) tree_view.append_column(col) #tree_view.connect('key_press_event', remove_on_keypress) #tree_view.connect('key_press_event', swap_on_keypress) return tree_view class StereotypePage(object): interface.implements(IPropertyPage) order = 40 element_factory = inject('element_factory') def __init__(self, item): self.item = item self.size_group = gtk.SizeGroup(gtk.SIZE_GROUP_HORIZONTAL) def construct(self): page = gtk.VBox() subject = self.item.subject if subject is None: return None stereotypes = UML.model.get_stereotypes(self.element_factory, subject) if not stereotypes: return None #applied = set(UML.model.get_applied_stereotypes(subject)) #for i, stereotype in enumerate(stereotypes): # if (i % 3) == 0: # hbox = gtk.HBox(spacing=20) # page.pack_start(hbox, expand=False) # button = gtk.CheckButton(label=stereotype.name) # button.set_active(stereotype in applied) # button.connect('toggled', self._on_stereotype_selected, stereotype) # hbox.pack_start(button, expand=False) # show stereotypes attributes toggle if isinstance(self.item, StereotypeSupport): hbox = gtk.HBox() label = gtk.Label('') hbox.pack_start(label, expand=False) button = gtk.CheckButton(_('Show stereotypes attributes')) button.set_active(self.item.show_stereotypes_attrs) button.connect('toggled', self._on_show_stereotypes_attrs_change) hbox.pack_start(button) page.pack_start(hbox, expand=False) # stereotype attributes self.model = StereotypeAttributes(self.item.subject) tree_view = create_stereotype_tree_view(self.model) page.pack_start(tree_view) page.show_all() return page #@transactional #def _on_stereotype_selected(self, button, stereotype): # subject = self.item.subject # if button.get_active(): # UML.model.apply_stereotype(self.element_factory, subject, stereotype) # else: # UML.model.remove_stereotype(subject, stereotype) # self.model.refresh() @transactional def _on_show_stereotypes_attrs_change(self, button): self.item.show_stereotypes_attrs = button.get_active() self.item.request_update() component.provideAdapter(StereotypePage, adapts=[UML.Element], name='Stereotypes') # vim:sw=4:et:ai gaphor-0.17.2/gaphor/adapters/profiles/tests/000077500000000000000000000000001220151210700211205ustar00rootroot00000000000000gaphor-0.17.2/gaphor/adapters/profiles/tests/__init__.py000066400000000000000000000000001220151210700232170ustar00rootroot00000000000000gaphor-0.17.2/gaphor/adapters/profiles/tests/test_extension.py000066400000000000000000000034461220151210700245540ustar00rootroot00000000000000""" Extension item connection adapter tests. """ from gaphor.tests import TestCase from gaphor import UML from gaphor.diagram import items class ExtensionConnectorTestCase(TestCase): """ Extension item connection adapter tests. """ def test_class_glue(self): """Test extension item glueing to a class """ ext = self.create(items.ExtensionItem) cls = self.create(items.ClassItem, UML.Class) # cannot connect extension item tail to a class glued = self.allow(ext, ext.tail, cls) self.assertFalse(glued) def test_stereotype_glue(self): """Test extension item glueing to a stereotype """ ext = self.create(items.ExtensionItem) st = self.create(items.ClassItem, UML.Stereotype) # test precondition assert type(st.subject) is UML.Stereotype # can connect extension item head to a Stereotype UML metaclass, # because it derives from Class UML metaclass glued = self.allow(ext, ext.head, st) self.assertTrue(glued) def test_glue(self): """Test extension item glue """ ext = self.create(items.ExtensionItem) st = self.create(items.ClassItem, UML.Stereotype) cls = self.create(items.ClassItem, UML.Class) glued = self.allow(ext, ext.tail, st) self.assertTrue(glued) self.connect(ext, ext.tail, st) glued = self.allow(ext, ext.head, cls) self.assertTrue(glued) def test_connection(self): """Test extension item connection """ ext = self.create(items.ExtensionItem) st = self.create(items.ClassItem, UML.Stereotype) cls = self.create(items.ClassItem, UML.Class) self.connect(ext, ext.tail, st) self.connect(ext, ext.head, cls) gaphor-0.17.2/gaphor/adapters/profiles/tests/test_metaclasseditor.py000066400000000000000000000013311220151210700257120ustar00rootroot00000000000000 from gaphor.tests import TestCase from gaphor.adapters.profiles.metaclasseditor import MetaclassNameEditor from gaphor.diagram import items from gaphor import UML import gtk class MetaclassEditorTest(TestCase): def test_name_selection(self): ci = self.create(items.MetaclassItem, UML.Class) ci.subject.name = 'Class' editor = MetaclassNameEditor(ci) page = editor.construct() self.assertTrue(page) combo = page.get_children()[0].get_children()[1] self.assertSame(gtk.ComboBoxEntry, type(combo)) self.assertEquals("Class", combo.child.get_text()) ci.subject.name = 'Blah' self.assertEquals('Blah', combo.child.get_text()) # vim:sw=4:et:ai gaphor-0.17.2/gaphor/adapters/propertypages.py000066400000000000000000001273701220151210700214230ustar00rootroot00000000000000""" Adapters for the Property Editor. To register property pages implemented in this module, it is imported in gaphor.adapter package. # TODO: make all labels align top-left # Add hidden columns for list stores where i can put the actual object # being edited. TODO: - stereotypes - association / association ends. - Follow HIG guidelines: * Leave a 12-pixel border between the edge of the window and the nearest controls. * Leave a 12-pixel horizontal gap between a control and its label. (The gap may be bigger for other controls in the same group, due to differences in the lengths of the labels.) * Labels must be concise and make sense when taken out of context. Otherwise, users relying on screenreaders or similar assistive technologies will not always be able to immediately understand the relationship between a control and those surrounding it. * Assign access keys to all editable controls. Ensure that using the access key focuses its associated control. """ import gobject import gtk import math from gaphor.core import _, inject, transactional from gaphor.services.elementdispatcher import EventWatcher from gaphor.ui.interfaces import IPropertyPage from gaphor.diagram import items from zope import interface, component from gaphor import UML from gaphor.UML.interfaces import IAttributeChangeEvent import gaphas.item from gaphas.decorators import async class EditableTreeModel(gtk.ListStore): """ Editable GTK tree model based on ListStore model. Every row is represented by a list of editable values. Last column contains an object, which is being edited (this column is not displayed). When editable value in first column is set to empty string then object is deleted. Last row is empty and contains no object to edit. It allows to enter new values. When model is edited, then item is requested to be updated on canvas. Attributes: - _item: diagram item owning tree model """ element_factory = inject('element_factory') def __init__(self, item, cols=None): """ Create new model. Parameters: - _item: diagram item owning tree model - cols: model columns, defaults to [str, object] """ if cols is None: cols = (str, object) super(EditableTreeModel, self).__init__(*cols) self._item = item for data in self._get_rows(): self.append(data) self._add_empty() def refresh(self, obj): for row in self: #print 'refresh for', obj if row[-1] is obj: seIlf._set_object_value(row, len(row) - 1, obj) self.row_changed(row.path, row.iter) #print 'found!' return def _get_rows(self): """ Return rows to be edited. Last column has to contain object being edited. """ raise NotImplemented def _create_object(self): """ Create new object. """ raise NotImplemented def _set_object_value(self, row, col, value): """ Update row's column with a value. """ raise NotImplemented def _swap_objects(self, o1, o2): """ Swap two objects. If objects are swapped, then return ``True``. """ raise NotImplemented def _get_object(self, iter): """ Get object from ``iter``. """ path = self.get_path(iter) return self[path][-1] def swap(self, a, b): """ Swap two list rows. Parameters: - a: path to first row - b: path to second row """ if not a or not b: return o1 = self[a][-1] o2 = self[b][-1] if o1 and o2 and self._swap_objects(o1, o2): #self._item.request_update(matrix=False) super(EditableTreeModel, self).swap(a, b) def _add_empty(self): """ Add empty row to the end of the model. """ self.append([None] * self.get_n_columns()) def iter_prev(self, iter): """ Get previous GTK tree iterator to ``iter``. """ i = self.get_path(iter)[0] if i == 0: return None return self.get_iter((i - 1,)) @transactional def set_value(self, iter, col, value): path = self.get_path(iter) row = self[path] if col == 0 and not value and row[-1]: # kill row and delete object if text of first column is empty self.remove(iter) elif value and col == 0 and not row[-1]: # create new object obj = self._create_object() row[-1] = obj self._set_object_value(row, col, value) self._add_empty() elif row[-1]: self._set_object_value(row, col, value) #self._item.request_update(matrix=False) def remove(self, iter): """ Remove object from GTK model and destroy it. """ obj = self._get_object(iter) if obj: obj.unlink() #self._item.request_update(matrix=False) return super(EditableTreeModel, self).remove(iter) else: return iter class ClassAttributes(EditableTreeModel): """ GTK tree model to edit class attributes. """ def _get_rows(self): for attr in self._item.subject.ownedAttribute: if not attr.association: yield [UML.format(attr), attr.isStatic, attr] def _create_object(self): attr = self.element_factory.create(UML.Property) self._item.subject.ownedAttribute = attr return attr @transactional def _set_object_value(self, row, col, value): attr = row[-1] if col == 0: UML.parse(attr, value) row[0] = UML.format(attr) elif col == 1: attr.isStatic = not attr.isStatic row[1] = attr.isStatic elif col == 2: # Value in attribute object changed: row[0] = UML.format(attr) row[1] = attr.isStatic def _swap_objects(self, o1, o2): return self._item.subject.ownedAttribute.swap(o1, o2) class ClassOperations(EditableTreeModel): """ GTK tree model to edit class operations. """ def _get_rows(self): for operation in self._item.subject.ownedOperation: yield [UML.format(operation), operation.isAbstract, operation.isStatic, operation] def _create_object(self): operation = self.element_factory.create(UML.Operation) self._item.subject.ownedOperation = operation return operation @transactional def _set_object_value(self, row, col, value): operation = row[-1] if col == 0: UML.parse(operation, value) row[0] = UML.format(operation) elif col == 1: operation.isAbstract = not operation.isAbstract row[1] = operation.isAbstract elif col == 2: operation.isStatic = not operation.isStatic row[2] = operation.isStatic elif col == 3: row[0] = UML.format(operation) row[1] = operation.isAbstract row[2] = operation.isStatic def _swap_objects(self, o1, o2): return self._item.subject.ownedOperation.swap(o1, o2) class CommunicationMessageModel(EditableTreeModel): """ GTK tree model for list of messages on communication diagram. """ def __init__(self, item, cols=None, inverted=False): self.inverted = inverted super(CommunicationMessageModel, self).__init__(item, cols) def _get_rows(self): if self.inverted: for message in self._item._inverted_messages: yield [message.name, message] else: for message in self._item._messages: yield [message.name, message] def remove(self, iter): """ Remove message from message item and destroy it. """ message = self._get_object(iter) item = self._item super(CommunicationMessageModel, self).remove(iter) item.remove_message(message, self.inverted) def _create_object(self): item = self._item subject = item.subject message = UML.model.create_message(self.element_factory, subject, self.inverted) item.add_message(message, self.inverted) return message def _set_object_value(self, row, col, value): message = row[-1] message.name = value row[0] = value self._item.set_message_text(message, value, self.inverted) def _swap_objects(self, o1, o2): return self._item.swap_messages(o1, o2, self.inverted) @transactional def remove_on_keypress(tree, event): """ Remove selected items from GTK model on ``backspace`` keypress. """ k = gtk.gdk.keyval_name(event.keyval).lower() if k == 'backspace' or k == 'kp_delete': model, iter = tree.get_selection().get_selected() if iter: model.remove(iter) @transactional def swap_on_keypress(tree, event): """ Swap selected and previous (or next) items. """ k = gtk.gdk.keyval_name(event.keyval).lower() if k == 'equal' or k == 'kp_add': model, iter = tree.get_selection().get_selected() model.swap(iter, model.iter_next(iter)) return True elif k == 'minus': model, iter = tree.get_selection().get_selected() model.swap(iter, model.iter_prev(iter)) return True @transactional def on_text_cell_edited(renderer, path, value, model, col=0): """ Update editable tree model based on fresh user input. """ iter = model.get_iter(path) model.set_value(iter, col, value) @transactional def on_bool_cell_edited(renderer, path, model, col): """ Update editable tree model based on fresh user input. """ iter = model.get_iter(path) model.set_value(iter, col, renderer.get_active()) class UMLComboModel(gtk.ListStore): """ UML combo box model. Model allows to easily create a combo box with values and their labels, for example label1 -> value1 label2 -> value2 label3 -> value3 Labels are displayed by combo box and programmer has easy access to values associated with given label. Attributes: - _data: model data - _indices: dictionary of values' indices """ def __init__(self, data): super(UMLComboModel, self).__init__(str) self._indices = {} self._data = data # add labels to underlying model and store index information for i, (label, value) in enumerate(data): self.append([label]) self._indices[value] = i def get_index(self, value): """ Return index of a ``value``. """ return self._indices[value] def get_value(self, index): """ Get value for given ``index``. """ return self._data[index][1] def create_uml_combo(data, callback): """ Create a combo box using ``UMLComboModel`` model. Combo box is returned. """ model = UMLComboModel(data) combo = gtk.ComboBox(model) cell = gtk.CellRendererText() combo.pack_start(cell, True) combo.add_attribute(cell, 'text', 0) combo.connect('changed', callback) return combo def create_hbox_label(adapter, page, label): """ Create a HBox with a label for given property page adapter and page itself. """ hbox = gtk.HBox(spacing=12) label = gtk.Label(label) label.set_alignment(0.0, 0.5) adapter.size_group.add_widget(label) hbox.pack_start(label, expand=False) page.pack_start(hbox, expand=False) return hbox def create_tree_view(model, names, tip='', ro_cols=None): """ Create a tree view for a editable tree model. :Parameters: model Model, for which tree view is created. names Names of columns. tip User interface tool tip for tree view. ro_cols Collection of indices pointing read only columns. """ if ro_cols is None: ro_cols = set() tree_view = gtk.TreeView(model) tree_view.set_rules_hint(True) n = model.get_n_columns() - 1 for name, i in zip(names, range(n)): col_type = model.get_column_type(i) if col_type == gobject.TYPE_STRING: renderer = gtk.CellRendererText() renderer.set_property('editable', i not in ro_cols) renderer.set_property('is-expanded', True) renderer.connect('edited', on_text_cell_edited, model, i) col = gtk.TreeViewColumn(name, renderer, text=i) col.set_expand(True) tree_view.append_column(col) elif col_type == gobject.TYPE_BOOLEAN: renderer = gtk.CellRendererToggle() renderer.set_property('activatable', i not in ro_cols) renderer.connect('toggled', on_bool_cell_edited, model, i) col = gtk.TreeViewColumn(name, renderer, active=i) col.set_expand(False) tree_view.append_column(col) tree_view.connect('key_press_event', remove_on_keypress) tree_view.connect('key_press_event', swap_on_keypress) tip = tip + """ Press ENTER to edit item, BS/DEL to remove item. Use -/= to move items up or down.\ """ tree_view.set_tooltip_text(tip) return tree_view class CommentItemPropertyPage(object): """ Property page for Comments """ interface.implements(IPropertyPage) component.adapts(UML.Comment) order = 0 def __init__(self, subject): self.subject = subject self.watcher = EventWatcher(subject) def construct(self): subject = self.subject page = gtk.VBox() if not subject: return page label = gtk.Label(_('Comment')) label.set_justify(gtk.JUSTIFY_LEFT) page.pack_start(label, expand=False) buffer = gtk.TextBuffer() if subject.body: buffer.set_text(subject.body) text_view = gtk.TextView() text_view.set_buffer(buffer) text_view.show() text_view.set_size_request(-1, 100) page.pack_start(text_view) page.set_data('default', text_view) changed_id = buffer.connect('changed', self._on_body_change) def handler(event): if not text_view.props.has_focus: buffer.handler_block(changed_id) buffer.set_text(event.new_value) buffer.handler_unblock(changed_id) self.watcher.watch('body', handler) \ .register_handlers() text_view.connect("destroy", self.watcher.unregister_handlers) return page @transactional def _on_body_change(self, buffer): self.subject.body = buffer.get_text(buffer.get_start_iter(), buffer.get_end_iter()) component.provideAdapter(CommentItemPropertyPage, name='Properties') class NamedElementPropertyPage(object): """ An adapter which works for any named item view. It also sets up a table view which can be extended. """ interface.implements(IPropertyPage) component.adapts(UML.NamedElement) order = 10 NAME_LABEL = _('Name') def __init__(self, subject): assert subject is None or isinstance(subject, UML.NamedElement), '%s' % type(subject) self.subject = subject self.watcher = EventWatcher(subject) self.size_group = gtk.SizeGroup(gtk.SIZE_GROUP_HORIZONTAL) def construct(self): page = gtk.VBox() subject = self.subject if not subject: return page hbox = create_hbox_label(self, page, self.NAME_LABEL) entry = gtk.Entry() entry.set_text(subject and subject.name or '') hbox.pack_start(entry) page.set_data('default', entry) # monitor subject.name attribute changed_id = entry.connect('changed', self._on_name_change) def handler(event): if event.element is subject and event.new_value is not None: entry.handler_block(changed_id) entry.set_text(event.new_value) entry.handler_unblock(changed_id) self.watcher.watch('name', handler) \ .register_handlers() entry.connect("destroy", self.watcher.unregister_handlers) return page @transactional def _on_name_change(self, entry): self.subject.name = entry.get_text() component.provideAdapter(NamedElementPropertyPage, name='Properties') class NamedItemPropertyPage(NamedElementPropertyPage): """ Base class for named item based adapters. """ def __init__(self, item): self.item = item super(NamedItemPropertyPage, self).__init__(item.subject) class ClassPropertyPage(NamedElementPropertyPage): """ Adapter which shows a property page for a class view. """ component.adapts(UML.Class) def __init__(self, subject): super(ClassPropertyPage, self).__init__(subject) def construct(self): page = super(ClassPropertyPage, self).construct() if not self.subject: return page # Abstract toggle hbox = gtk.HBox() label = gtk.Label('') label.set_justify(gtk.JUSTIFY_LEFT) self.size_group.add_widget(label) hbox.pack_start(label, expand=False) button = gtk.CheckButton(_("Abstract")) button.set_active(self.subject.isAbstract) button.connect('toggled', self._on_abstract_change) hbox.pack_start(button) page.pack_start(hbox, expand=False) return page @transactional def _on_abstract_change(self, button): self.subject.isAbstract = button.get_active() component.provideAdapter(ClassPropertyPage, name='Properties') class InterfacePropertyPage(NamedItemPropertyPage): """ Adapter which shows a property page for an interface view. """ component.adapts(items.InterfaceItem) def construct(self): page = super(InterfacePropertyPage, self).construct() item = self.item # Fold toggle hbox = gtk.HBox() label = gtk.Label('') label.set_justify(gtk.JUSTIFY_LEFT) self.size_group.add_widget(label) hbox.pack_start(label, expand=False) button = gtk.CheckButton(_("Folded")) button.set_active(item.folded) button.connect('toggled', self._on_fold_change) connected_items = [c.item for c in item.canvas.get_connections(connected=item)] allowed = (items.DependencyItem, items.ImplementationItem) can_fold = len(connected_items) == 0 \ or len(connected_items) == 1 and isinstance(connected_items[0], allowed) button.set_sensitive(can_fold) hbox.pack_start(button) page.pack_start(hbox, expand=False) return page @transactional def _on_fold_change(self, button): item = self.item connected_items = [c.item for c in item.canvas.get_connections(connected=item)] assert len(connected_items) <= 1 line = None if len(connected_items) == 1: line = connected_items[0] fold = button.get_active() if fold: item.folded = item.FOLDED_PROVIDED else: item.folded = item.FOLDED_NONE if line: if fold and isinstance(line, items.DependencyItem): item.folded = item.FOLDED_REQUIRED line._solid = fold constraint = line.canvas.get_connection(line.head).constraint constraint.ratio_x = 0.5 constraint.ratio_y = 0.5 line.request_update() component.provideAdapter(InterfacePropertyPage, name='Properties') class AttributesPage(object): """ An editor for attributes associated with classes and interfaces. """ interface.implements(IPropertyPage) component.adapts(items.ClassItem) order = 20 def __init__(self, item): super(AttributesPage, self).__init__() self.item = item self.watcher = EventWatcher(item.subject) def construct(self): page = gtk.VBox() if not self.item.subject: return page # Show attributes toggle hbox = gtk.HBox() label = gtk.Label('') label.set_justify(gtk.JUSTIFY_LEFT) hbox.pack_start(label, expand=False) button = gtk.CheckButton(_('Show attributes')) button.set_active(self.item.show_attributes) button.connect('toggled', self._on_show_attributes_change) hbox.pack_start(button) page.pack_start(hbox, expand=False) def create_model(): return ClassAttributes(self.item, (str, bool, object)) self.model = create_model() tip = """\ Add and edit class attributes according to UML syntax. Attribute syntax examples - attr - + attr: int - # /attr: int """ tree_view = create_tree_view(self.model, (_('Attributes'), _('S')), tip) page.pack_start(tree_view) @async(single=True) def handler(event): # Single it's asynchronous, make sure all properties are still there if not tree_view.props.has_focus and self.item and self.item.subject: self.model = create_model() tree_view.set_model(self.model) self.watcher.watch('ownedAttribute.name', handler) \ .watch('ownedAttribute.isDerived', handler) \ .watch('ownedAttribute.visibility', handler) \ .watch('ownedAttribute.isStatic', handler) \ .watch('ownedAttribute.lowerValue', handler) \ .watch('ownedAttribute.upperValue', handler) \ .watch('ownedAttribute.defaultValue', handler) \ .watch('ownedAttribute.typeValue', handler) \ .register_handlers() tree_view.connect('destroy', self.watcher.unregister_handlers) return page @transactional def _on_show_attributes_change(self, button): self.item.show_attributes = button.get_active() self.item.request_update() component.provideAdapter(AttributesPage, name='Attributes') class OperationsPage(object): """ An editor for operations associated with classes and interfaces. """ interface.implements(IPropertyPage) component.adapts(items.ClassItem) order = 30 def __init__(self, item): super(OperationsPage, self).__init__() self.item = item self.watcher = EventWatcher(item.subject) def construct(self): page = gtk.VBox() if not self.item.subject: return page # Show operations toggle hbox = gtk.HBox() label = gtk.Label("") label.set_justify(gtk.JUSTIFY_LEFT) hbox.pack_start(label, expand=False) button = gtk.CheckButton(_("Show operations")) button.set_active(self.item.show_operations) button.connect('toggled', self._on_show_operations_change) hbox.pack_start(button) page.pack_start(hbox, expand=False) def create_model(): return ClassOperations(self.item, (str, bool, bool, object)) self.model = create_model() tip = """\ Add and edit class operations according to UML syntax. Operation syntax examples - call() - + call(a: int, b: str) - # call(a: int: b: str): bool """ tree_view = create_tree_view(self.model, (_('Operation'), _('A'), _('S')), tip) page.pack_start(tree_view) @async(single=True) def handler(event): if not tree_view.props.has_focus and self.item and self.item.subject: self.model = create_model() tree_view.set_model(self.model) self.watcher.watch('ownedOperation.name', handler) \ .watch('ownedOperation.isAbstract', handler) \ .watch('ownedOperation.visibility', handler) \ .watch('ownedOperation.returnResult.lowerValue', handler) \ .watch('ownedOperation.returnResult.upperValue', handler) \ .watch('ownedOperation.returnResult.typeValue', handler) \ .watch('ownedOperation.formalParameter.lowerValue', handler) \ .watch('ownedOperation.formalParameter.upperValue', handler) \ .watch('ownedOperation.formalParameter.typeValue', handler) \ .watch('ownedOperation.formalParameter.defaultValue', handler) \ .register_handlers() tree_view.connect('destroy', self.watcher.unregister_handlers) return page @transactional def _on_show_operations_change(self, button): self.item.show_operations = button.get_active() self.item.request_update() component.provideAdapter(OperationsPage, name='Operations') class DependencyPropertyPage(object): """ Dependency item editor. """ interface.implements(IPropertyPage) component.adapts(items.DependencyItem) order = 0 element_factory = inject('element_factory') DEPENDENCY_TYPES = ( (_('Dependency'), UML.Dependency), (_('Usage'), UML.Usage), (_('Realization'), UML.Realization), (_('Implementation'), UML.Implementation)) def __init__(self, item): super(DependencyPropertyPage, self).__init__() self.item = item self.size_group = gtk.SizeGroup(gtk.SIZE_GROUP_HORIZONTAL) self.watcher = EventWatcher(self.item) def construct(self): page = gtk.VBox() hbox = create_hbox_label(self, page, _('Dependency type')) self.combo = create_uml_combo(self.DEPENDENCY_TYPES, self._on_dependency_type_change) hbox.pack_start(self.combo, expand=False) hbox = create_hbox_label(self, page, '') button = gtk.CheckButton(_('Automatic')) button.set_active(self.item.auto_dependency) button.connect('toggled', self._on_auto_dependency_change) hbox.pack_start(button) self.watcher.watch('subject', self._on_subject_change).register_handlers() button.connect('destroy', self.watcher.unregister_handlers) self.update() return page def _on_subject_change(self, event): self.update() def update(self): """ Update dependency type combo box. Disallow dependency type when dependency is established. """ combo = self.combo item = self.item index = combo.get_model().get_index(item.dependency_type) combo.props.sensitive = not item.auto_dependency combo.set_active(index) @transactional def _on_dependency_type_change(self, combo): combo = self.combo cls = combo.get_model().get_value(combo.get_active()) self.item.dependency_type = cls if self.item.subject: self.element_factory.swap_element(self.item.subject, cls) self.item.request_update() @transactional def _on_auto_dependency_change(self, button): self.item.auto_dependency = button.get_active() self.update() component.provideAdapter(DependencyPropertyPage, name='Properties') class AssociationPropertyPage(NamedItemPropertyPage): """ """ component.adapts(items.AssociationItem) def construct_end(self, title, end): if not end.subject: return None # TODO: use gtk.Frame here frame = gtk.Frame('%s (: %s)' % (title, end.subject.type.name)) vbox = gtk.VBox() vbox.set_border_width(6) vbox.set_spacing(6) frame.add(vbox) self.create_pages(end, vbox) return frame def construct(self): page = super(AssociationPropertyPage, self).construct() if not self.subject: return page hbox = gtk.HBox() label = gtk.Label('') label.set_justify(gtk.JUSTIFY_LEFT) self.size_group.add_widget(label) hbox.pack_start(label, expand=False) button = gtk.CheckButton(_('Show direction')) button.set_active(self.item.show_direction) button.connect('toggled', self._on_show_direction_change) hbox.pack_start(button) button = gtk.Button(_('Invert Direction')) button.connect('clicked', self._on_invert_direction_change) hbox.pack_start(button) page.pack_start(hbox, expand=False) box = self.construct_end(_('Head'), self.item.head_end) if box: page.pack_start(box, expand=False) box = self.construct_end(_('Tail'), self.item.tail_end) if box: page.pack_start(box, expand=False) self.update() return page def update(self): pass @transactional def _on_show_direction_change(self, button): self.item.show_direction = button.get_active() @transactional def _on_invert_direction_change(self, button): self.item.invert_direction() def get_adapters(self, item): """ Return an ordered list of (order, name, adapter). """ adaptermap = {} try: if item.subject: for name, adapter in component.getAdapters([item.subject,], IPropertyPage): adaptermap[name] = (adapter.order, name, adapter) except AttributeError: pass for name, adapter in component.getAdapters([item,], IPropertyPage): adaptermap[name] = (adapter.order, name, adapter) adapters = adaptermap.values() adapters.sort() return adapters def create_pages(self, item, vbox): """ Load all tabs that can operate on the given item. The first item will not contain a title. """ adapters = self.get_adapters(item) first = True for _, name, adapter in adapters: try: page = adapter.construct() if page is None: continue if first: vbox.pack_start(page, expand=False) first = False else: expander = gtk.Expander() expander.set_use_markup(True) expander.set_label('%s' % name) expander.add(page) expander.show_all() vbox.pack_start(expander, expand=False) except Exception, e: log.error('Could not construct property page for ' + name, exc_info=True) component.provideAdapter(AssociationPropertyPage, name='Properties') class AssociationEndPropertyPage(object): """ Property page for association end properties. """ interface.implements(IPropertyPage) component.adapts(UML.Property) order = 0 NAVIGABILITY = [None, False, True] def __init__(self, subject): self.subject = subject self.watcher = EventWatcher(subject) def construct(self): vbox = gtk.VBox() entry = gtk.Entry() #entry.set_text(UML.format(self.subject, visibility=True, is_derived=Truemultiplicity=True) or '') # monitor subject attribute (all, cause it contains many children) changed_id = entry.connect('changed', self._on_end_name_change) def handler(event): if not entry.props.has_focus: entry.handler_block(changed_id) entry.set_text(UML.format(self.subject, visibility=True, is_derived=True, multiplicity=True) or '') #entry.set_text(UML.format(self.subject, multiplicity=True) or '') entry.handler_unblock(changed_id) handler(None) self.watcher.watch('name', handler) \ .watch('aggregation', handler)\ .watch('visibility', handler)\ .watch('lowerValue', handler)\ .watch('upperValue', handler)\ .register_handlers() entry.connect("destroy", self.watcher.unregister_handlers) vbox.pack_start(entry) entry.set_tooltip_text("""\ Enter attribute name and multiplicity, for example - name + name [1] - name [1..2] ~ 1..2 - [1..2]\ """) combo = gtk.combo_box_new_text() for t in ('Unknown navigation', 'Not navigable', 'Navigable'): combo.append_text(t) nav = self.subject.navigability combo.set_active(self.NAVIGABILITY.index(nav)) combo.connect('changed', self._on_navigability_change) vbox.pack_start(combo, expand=False) combo = gtk.combo_box_new_text() for t in ('No aggregation', 'Shared', 'Composite'): combo.append_text(t) combo.set_active(['none', 'shared', 'composite'].index(self.subject.aggregation)) combo.connect('changed', self._on_aggregation_change) vbox.pack_start(combo, expand=False) return vbox @transactional def _on_end_name_change(self, entry): UML.parse(self.subject, entry.get_text()) @transactional def _on_navigability_change(self, combo): nav = self.NAVIGABILITY[combo.get_active()] UML.model.set_navigability(self.subject.association, self.subject, nav) @transactional def _on_aggregation_change(self, combo): self.subject.aggregation = ('none', 'shared', 'composite')[combo.get_active()] component.provideAdapter(AssociationEndPropertyPage, name='Properties') class LineStylePage(object): """ Basic line style properties: color, orthogonal, etc. """ interface.implements(IPropertyPage) component.adapts(gaphas.item.Line) order = 400 def __init__(self, item): super(LineStylePage, self).__init__() self.item = item self.size_group = gtk.SizeGroup(gtk.SIZE_GROUP_HORIZONTAL) def construct(self): page = gtk.VBox() hbox = gtk.HBox() label = gtk.Label('') label.set_justify(gtk.JUSTIFY_LEFT) self.size_group.add_widget(label) hbox.pack_start(label, expand=False) button = gtk.CheckButton(_('Orthogonal')) button.set_active(self.item.orthogonal) button.connect('toggled', self._on_orthogonal_change) hbox.pack_start(button) page.pack_start(hbox, expand=False) if len(self.item.handles()) < 3: # Only one segment button.props.sensitive = False hbox = gtk.HBox() label = gtk.Label('') label.set_justify(gtk.JUSTIFY_LEFT) self.size_group.add_widget(label) hbox.pack_start(label, expand=False) button = gtk.CheckButton(_('Horizontal')) button.set_active(self.item.horizontal) button.connect('toggled', self._on_horizontal_change) hbox.pack_start(button) page.pack_start(hbox, expand=False) return page @transactional def _on_orthogonal_change(self, button): self.item.orthogonal = button.get_active() @transactional def _on_horizontal_change(self, button): self.item.horizontal = button.get_active() component.provideAdapter(LineStylePage, name='Style') class ObjectNodePropertyPage(NamedItemPropertyPage): """ """ component.adapts(items.ObjectNodeItem) ORDERING_VALUES = ['unordered', 'ordered', 'LIFO', 'FIFO'] def construct(self): page = super(ObjectNodePropertyPage, self).construct() subject = self.subject if not subject: return page hbox = create_hbox_label(self, page, _('Upper bound')) entry = gtk.Entry() entry.set_text(subject.upperBound or '') entry.connect('changed', self._on_upper_bound_change) hbox.pack_start(entry) hbox = create_hbox_label(self, page, '') combo = gtk.combo_box_new_text() for v in self.ORDERING_VALUES: combo.append_text(v) combo.set_active(self.ORDERING_VALUES.index(subject.ordering)) combo.connect('changed', self._on_ordering_change) hbox.pack_start(combo, expand=False) hbox = create_hbox_label(self, page, '') button = gtk.CheckButton(_('Ordering')) button.set_active(self.item.show_ordering) button.connect('toggled', self._on_ordering_show_change) hbox.pack_start(button, expand=False) return page def update(self): pass @transactional def _on_upper_bound_change(self, entry): value = entry.get_text().strip() self.item.set_upper_bound(value) @transactional def _on_ordering_change(self, combo): value = self.ORDERING_VALUES[combo.get_active()] self.subject.ordering = value @transactional def _on_ordering_show_change(self, button): self.item.show_ordering = button.get_active() self.item.set_ordering(self.subject.ordering) component.provideAdapter(ObjectNodePropertyPage, name='Properties') class JoinNodePropertyPage(NamedItemPropertyPage): """ """ component.adapts(items.ForkNodeItem) def construct(self): page = super(JoinNodePropertyPage, self).construct() subject = self.subject if not subject: return page hbox = gtk.HBox() page.pack_start(hbox, expand=False) if isinstance(subject, UML.JoinNode): hbox = create_hbox_label(self, page, _('Join specification')) entry = gtk.Entry() entry.set_text(subject.joinSpec or '') entry.connect('changed', self._on_join_spec_change) hbox.pack_start(entry) button = gtk.CheckButton(_('Horizontal')) button.set_active(self.item.matrix[2] != 0) button.connect('toggled', self._on_horizontal_change) page.pack_start(button, expand=False) return page def update(self): pass @transactional def _on_join_spec_change(self, entry): value = entry.get_text().strip() print 'new joinspec', value self.subject.joinSpec = value def _on_horizontal_change(self, button): if button.get_active(): self.item.matrix.rotate(math.pi/2) else: self.item.matrix.rotate(-math.pi/2) self.item.request_update() component.provideAdapter(JoinNodePropertyPage, name='Properties') class FlowPropertyPageAbstract(NamedElementPropertyPage): """ Flow item element editor. """ def construct(self): page = super(FlowPropertyPageAbstract, self).construct() subject = self.subject if not subject: return page hbox = create_hbox_label(self, page, _('Guard')) entry = gtk.Entry() entry.set_text(subject.guard or '') changed_id = entry.connect('changed', self._on_guard_change) hbox.pack_start(entry) def handler(event): entry.handler_block(changed_id) v = event.new_value entry.set_text(v if v else '') entry.handler_unblock(changed_id) self.watcher.watch('guard', handler).register_handlers() entry.connect('destroy', self.watcher.unregister_handlers) return page @transactional def _on_guard_change(self, entry): value = entry.get_text().strip() self.subject.guard = value # fixme: unify ObjectFlowPropertyPage and ControlFlowPropertyPage # after introducing common class for element editors class ControlFlowPropertyPage(FlowPropertyPageAbstract): component.adapts(UML.ControlFlow) class ObjectFlowPropertyPage(FlowPropertyPageAbstract): component.adapts(UML.ObjectFlow) component.provideAdapter(ControlFlowPropertyPage, name='Properties') component.provideAdapter(ObjectFlowPropertyPage, name='Properties') class ComponentPropertyPage(NamedItemPropertyPage): """ """ component.adapts(items.ComponentItem) def construct(self): page = super(ComponentPropertyPage, self).construct() subject = self.subject if not subject: return page hbox = gtk.HBox() page.pack_start(hbox, expand=False) button = gtk.CheckButton(_('Indirectly instantiated')) button.set_active(subject.isIndirectlyInstantiated) button.connect('toggled', self._on_ii_change) hbox.pack_start(button, expand=False) return page def update(self): pass @transactional def _on_ii_change(self, button): """ Called when user clicks "Indirectly instantiated" check button. """ subject = self.subject if subject: subject.isIndirectlyInstantiated = button.get_active() component.provideAdapter(ComponentPropertyPage, name='Properties') class MessagePropertyPage(NamedItemPropertyPage): """ Property page for editing message items. When message is on communication diagram, then additional messages can be added. On sequence diagram sort of message can be changed. """ component.adapts(items.MessageItem) element_factory = inject('element_factory') NAME_LABEL = _('Message') MESSAGE_SORT = ( ('Call', 'synchCall'), ('Asynchronous', 'asynchCall'), ('Signal', 'asynchSignal'), ('Create', 'createMessage'), ('Delete', 'deleteMessage'), ('Reply', 'reply')) def construct(self): page = super(MessagePropertyPage, self).construct() item = self.item subject = item.subject if not subject: return page if item.is_communication(): self._messages = CommunicationMessageModel(item) tree_view = create_tree_view(self._messages, (_('Message'),)) tree_view.set_headers_visible(False) frame = gtk.Frame(label=_('Additional Messages')) frame.add(tree_view) page.pack_start(frame) self._inverted_messages = CommunicationMessageModel(item, inverted=True) tree_view = create_tree_view(self._inverted_messages, (_('Message'),)) tree_view.set_headers_visible(False) frame = gtk.Frame(label=_('Inverted Messages')) frame.add(tree_view) page.pack_end(frame) else: hbox = create_hbox_label(self, page, _('Message sort')) sort_data = self.MESSAGE_SORT lifeline = None cinfo = item.canvas.get_connection(item.tail) if cinfo: lifeline = cinfo.connected # disallow connecting two delete messages to a lifeline if lifeline and lifeline.is_destroyed \ and subject.messageSort != 'deleteMessage': sort_data = list(sort_data) assert sort_data[4][1] == 'deleteMessage' del sort_data[4] combo = self.combo = create_uml_combo(sort_data, self._on_message_sort_change) hbox.pack_start(combo, expand=False) index = combo.get_model().get_index(subject.messageSort) combo.set_active(index) return page @transactional def _on_message_sort_change(self, combo): """ Update message item's message sort information. """ combo = self.combo ms = combo.get_model().get_value(combo.get_active()) item = self.item subject = item.subject lifeline = None cinfo = item.canvas.get_connection(item.tail) if cinfo: lifeline = cinfo.connected # # allow only one delete message to connect to lifeline's lifetime # destroyed status can be changed only by delete message itself # if lifeline: if subject.messageSort == 'deleteMessage' \ or not lifeline.is_destroyed: is_destroyed = ms == 'deleteMessage' lifeline.is_destroyed = is_destroyed # TODO: is required here? lifeline.request_update() subject.messageSort = ms # TODO: is required here? item.request_update() component.provideAdapter(MessagePropertyPage, name='Properties') # vim:sw=4:et:ai gaphor-0.17.2/gaphor/adapters/relationships.py000066400000000000000000000034661220151210700214020ustar00rootroot00000000000000""" This module is not yet ready to be used for adapters. Here the logic should be placed that allows the application to draw already existing relationships on the diagram when a model element is added (e.g. by drag and drop). """ from zope import component ###class AssociationRelationship(Relationship): ### """Relationship for associations. ### """ ### def relationship(self, line, head_subject = None, tail_subject = None): ### # First check if we do not already contain the right subject: ### if line.subject: ### end1 = line.subject.memberEnd[0] ### end2 = line.subject.memberEnd[1] ### if (end1.type is head_type and end2.type is tail_type) \ ### or (end2.type is head_type and end1.type is tail_type): ### return ### ### # Find all associations and determine if the properties on the ### # association ends have a type that points to the class. ### Association = UML.Association ### for assoc in resource(UML.ElementFactory).itervalues(): ### if isinstance(assoc, Association): ### #print 'assoc.memberEnd', assoc.memberEnd ### end1 = assoc.memberEnd[0] ### end2 = assoc.memberEnd[1] ### if (end1.type is head_type and end2.type is tail_type) \ ### or (end2.type is head_type and end1.type is tail_type): ### # check if this entry is not yet in the diagram ### # Return if the association is not (yet) on the canvas ### for item in assoc.presentation: ### if item.canvas is line.canvas: ### break ### else: ### return assoc ### return None # vim:sw=4:et:ai gaphor-0.17.2/gaphor/adapters/states/000077500000000000000000000000001220151210700174365ustar00rootroot00000000000000gaphor-0.17.2/gaphor/adapters/states/__init__.py000066400000000000000000000000521220151210700215440ustar00rootroot00000000000000import propertypages import vertexconnect gaphor-0.17.2/gaphor/adapters/states/propertypages.py000066400000000000000000000062321220151210700227170ustar00rootroot00000000000000""" State items property pages. To register property pages implemented in this module, it is imported in gaphor.adapter package. """ import gtk from gaphor.core import _, inject, transactional from gaphor import UML from gaphor.diagram import items from zope import interface, component from gaphor.adapters.propertypages import NamedItemPropertyPage, create_hbox_label class TransitionPropertyPage(NamedItemPropertyPage): """ Transition property page allows to edit guard specification. """ element_factory = inject('element_factory') component.adapts(items.TransitionItem) def construct(self): page = super(TransitionPropertyPage, self).construct() subject = self.subject if not subject: return page hbox = create_hbox_label(self, page, _('Guard')) entry = gtk.Entry() v = subject.guard.specification entry.set_text(v if v else '') entry.connect('changed', self._on_guard_change) changed_id = entry.connect('changed', self._on_guard_change) hbox.pack_start(entry) def handler(event): entry.handler_block(changed_id) v = event.new_value entry.set_text(v if v else '') entry.handler_unblock(changed_id) self.watcher.watch('guard.specification', handler).register_handlers() entry.connect('destroy', self.watcher.unregister_handlers) return page def update(self): pass @transactional def _on_guard_change(self, entry): value = entry.get_text().strip() self.subject.guard.specification = value component.provideAdapter(TransitionPropertyPage, name='Properties') class StatePropertyPage(NamedItemPropertyPage): """ State property page. """ element_factory = inject('element_factory') component.adapts(items.StateItem) def construct(self): page = super(StatePropertyPage, self).construct() subject = self.subject if not subject: return page hbox = create_hbox_label(self, page, _('Entry')) entry = gtk.Entry() if self.item._entry.subject: entry.set_text(self.item._entry.subject.name) entry.connect('changed', self._on_text_change, self.item.set_entry) hbox.pack_start(entry) hbox = create_hbox_label(self, page, _('Exit')) entry = gtk.Entry() if self.item._exit.subject: entry.set_text(self.item._exit.subject.name) entry.connect('changed', self._on_text_change, self.item.set_exit) hbox.pack_start(entry) hbox = create_hbox_label(self, page, _('Do Activity')) entry = gtk.Entry() if self.item._do_activity.subject: entry.set_text(self.item._do_activity.subject.name) entry.connect('changed', self._on_text_change, self.item.set_do_activity) hbox.pack_start(entry) page.show_all() return page def update(self): pass @transactional def _on_text_change(self, entry, method): value = entry.get_text().strip() method(value) component.provideAdapter(StatePropertyPage, name='Properties') gaphor-0.17.2/gaphor/adapters/states/tests/000077500000000000000000000000001220151210700206005ustar00rootroot00000000000000gaphor-0.17.2/gaphor/adapters/states/tests/__init__.py000066400000000000000000000000001220151210700226770ustar00rootroot00000000000000gaphor-0.17.2/gaphor/adapters/states/tests/test_transition_connect.py000066400000000000000000000142441220151210700261210ustar00rootroot00000000000000""" Test transition item and state vertices connections. """ from gaphor.tests import TestCase from zope import component from gaphor import UML from gaphor.diagram import items from gaphor.diagram.interfaces import IConnect class TransitionConnectorTestCase(TestCase): services = TestCase.services def test_vertex_connect(self): """Test transition to state vertex connection """ v1 = self.create(items.StateItem, UML.State) v2 = self.create(items.StateItem, UML.State) t = self.create(items.TransitionItem) assert t.subject is None # connect vertices with transition self.connect(t, t.head, v1) self.connect(t, t.tail, v2) self.assertTrue(t.subject is not None) self.assertEquals(1, len(self.kindof(UML.Transition))) self.assertEquals(t.subject, v1.subject.outgoing[0]) self.assertEquals(t.subject, v2.subject.incoming[0]) self.assertEquals(t.subject.source, v1.subject) self.assertEquals(t.subject.target, v2.subject) def test_vertex_reconnect(self): """Test transition to state vertex reconnection """ v1 = self.create(items.StateItem, UML.State) v2 = self.create(items.StateItem, UML.State) v3 = self.create(items.StateItem, UML.State) t = self.create(items.TransitionItem) assert t.subject is None # connect: v1 -> v2 self.connect(t, t.head, v1) self.connect(t, t.tail, v2) s = t.subject s.name = 'tname' s.guard.specification = 'tguard' # reconnect: v1 -> v3 self.connect(t, t.tail, v3) self.assertSame(s, t.subject) self.assertEquals(1, len(self.kindof(UML.Transition))) self.assertEquals(t.subject, v1.subject.outgoing[0]) self.assertEquals(t.subject, v3.subject.incoming[0]) self.assertEquals(t.subject.source, v1.subject) self.assertEquals(t.subject.target, v3.subject) self.assertEquals(0, len(v2.subject.incoming)) self.assertEquals(0, len(v2.subject.outgoing)) def test_vertex_disconnect(self): """Test transition and state vertices disconnection """ t = self.create(items.TransitionItem) v1 = self.create(items.StateItem, UML.State) v2 = self.create(items.StateItem, UML.State) self.connect(t, t.head, v1) self.connect(t, t.tail, v2) assert t.subject is not None self.assertEquals(1, len(self.kindof(UML.Transition))) # test preconditions assert t.subject == v1.subject.outgoing[0] assert t.subject == v2.subject.incoming[0] self.disconnect(t, t.tail) self.assertTrue(t.subject is None) self.disconnect(t, t.head) self.assertTrue(t.subject is None) def test_initial_pseudostate_connect(self): """Test transition and initial pseudostate connection """ v1 = self.create(items.InitialPseudostateItem, UML.Pseudostate) v2 = self.create(items.StateItem, UML.State) t = self.create(items.TransitionItem) assert t.subject is None # connect head of transition to an initial pseudostate self.connect(t, t.head, v1) self.assertTrue(t.subject is None) # connect tail of transition to a state self.connect(t, t.tail, v2) self.assertTrue(t.subject is not None) self.assertEquals(1, len(self.kindof(UML.Transition))) # test preconditions assert t.subject == v1.subject.outgoing[0] assert t.subject == v2.subject.incoming[0] # we should not be able to connect two transitions to initial # pseudostate t2 = self.create(items.TransitionItem) # connection to `t2` should not be possible as v1 is already connected # to `t` glued = self.allow(t2, t2.head, v1) self.assertFalse(glued) self.assertTrue(self.get_connected(t2.head) is None) def test_initial_pseudostate_disconnect(self): """Test transition and initial pseudostate disconnection """ v1 = self.create(items.InitialPseudostateItem, UML.Pseudostate) v2 = self.create(items.StateItem, UML.State) t = self.create(items.TransitionItem) assert t.subject is None # connect head of transition to an initial pseudostate self.connect(t, t.head, v1) self.assertTrue(self.get_connected(t.head)) # perform the test self.disconnect(t, t.head) self.assertFalse(self.get_connected(t.head)) def test_initial_pseudostate_tail_glue(self): """Test transition tail and initial pseudostate glueing """ v1 = self.create(items.InitialPseudostateItem, UML.Pseudostate) t = self.create(items.TransitionItem) assert t.subject is None # no tail connection should be possible glued = self.allow(t, t.tail, v1) self.assertFalse(glued) def test_final_state_connect(self): """Test transition to final state connection """ v1 = self.create(items.StateItem, UML.State) v2 = self.create(items.FinalStateItem, UML.FinalState) t = self.create(items.TransitionItem) # connect head of transition to a state self.connect(t, t.head, v1) # check if transition can connect to final state glued = self.allow(t, t.tail, v2) self.assertTrue(glued) # and connect tail of transition to final state self.connect(t, t.tail, v2) self.assertTrue(t.subject is not None) self.assertEquals(1, len(self.kindof(UML.Transition))) self.assertEquals(t.subject, v1.subject.outgoing[0]) self.assertEquals(t.subject, v2.subject.incoming[0]) self.assertEquals(t.subject.source, v1.subject) self.assertEquals(t.subject.target, v2.subject) def test_final_state_head_glue(self): """Test transition head to final state connection """ v = self.create(items.FinalStateItem, UML.FinalState) t = self.create(items.TransitionItem) glued = self.allow(t, t.head, v) self.assertFalse(glued) # vim:sw=4:et:ai gaphor-0.17.2/gaphor/adapters/states/vertexconnect.py000066400000000000000000000063061220151210700227040ustar00rootroot00000000000000""" Connection between two state machine vertices (state, pseudostate) using transition. To register connectors implemented in this module, it is imported in gaphor.adapter package. """ from zope import interface, component from gaphor import UML from gaphor.diagram import items from gaphor.adapters.connectors import RelationshipConnect class VertexConnect(RelationshipConnect): """ Abstract relationship between two state vertices. """ def reconnect(self, handle, port): self.reconnect_relationship(handle, UML.Transition.source, UML.Transition.target) def connect_subject(self, handle): relation = self.relationship_or_new(UML.Transition, UML.Transition.source, UML.Transition.target) self.line.subject = relation if relation.guard is None: relation.guard = self.element_factory.create(UML.Constraint) class TransitionConnect(VertexConnect): """ Connect two state vertices using transition item. """ component.adapts(items.VertexItem, items.TransitionItem) def allow(self, handle, port): """ Glue transition handle and vertex item. Guard from connecting transition's head with final state. """ line = self.line subject = self.element.subject is_final = isinstance(subject, UML.FinalState) if isinstance(subject, UML.State) and not is_final \ or handle is line.tail and is_final: return super(TransitionConnect, self).allow(handle, port) else: return None component.provideAdapter(TransitionConnect) class InitialPseudostateTransitionConnect(VertexConnect): """ Connect initial pseudostate using transition item. It modifies InitialPseudostateItem._connected attribute to disallow connection of more than one transition. """ component.adapts(items.InitialPseudostateItem, items.TransitionItem) def allow(self, handle, port): """ Glue to initial pseudostate with transition's head and when there are no transitions connected. """ line = self.line element = self.element subject = element.subject # Check if no other items are connected connections = self.canvas.get_connections(connected=element) connected_items = filter(lambda c: isinstance(c.item, items.TransitionItem) and c.item is not line, connections) if handle is line.head and not any(connected_items): return super(InitialPseudostateTransitionConnect, self).allow(handle, port) else: return None component.provideAdapter(InitialPseudostateTransitionConnect) class HistoryPseudostateTransitionConnect(VertexConnect): """ Connect history pseudostate using transition item. It modifies InitialPseudostateItem._connected attribute to disallow connection of more than one transition. """ component.adapts(items.HistoryPseudostateItem, items.TransitionItem) def allow(self, handle, port): """ """ return super(HistoryPseudostateTransitionConnect, self).allow(handle, port) component.provideAdapter(HistoryPseudostateTransitionConnect) # vim:sw=4:et:ai gaphor-0.17.2/gaphor/adapters/tests/000077500000000000000000000000001220151210700172755ustar00rootroot00000000000000gaphor-0.17.2/gaphor/adapters/tests/__init__.py000066400000000000000000000000151220151210700214020ustar00rootroot00000000000000# unit tests gaphor-0.17.2/gaphor/adapters/tests/test_comment.py000066400000000000000000000173531220151210700223610ustar00rootroot00000000000000""" Comment and comment line items connection adapters tests. """ from gaphor import UML from gaphor.diagram import items from gaphor.tests import TestCase class CommentLineTestCase(TestCase): services = TestCase.services + ['sanitizer'] # NOTE: Still have to test what happens if one Item at the CommentLineItem # end is removed, while the item still has references and is not # removed itself. def test_commentline_annotated_element(self): """Test comment line item annotated element creation """ comment = self.create(items.CommentItem, UML.Comment) line = self.create(items.CommentLineItem) self.connect(line, line.head, comment) # connected, but no annotated element yet self.assertTrue(self.get_connected(line.head) is not None) self.assertFalse(comment.subject.annotatedElement) def test_commentline_same_comment_glue(self): """Test comment line item glueing to already connected comment item """ comment = self.create(items.CommentItem, UML.Comment) line = self.create(items.CommentLineItem) self.connect(line, line.head, comment) glued = self.allow(line, line.tail, comment) self.assertFalse(glued) def test_commentline_element_connect(self): """Test comment line connecting to comment and actor items. """ comment = self.create(items.CommentItem, UML.Comment) line = self.create(items.CommentLineItem) ac = self.create(items.ActorItem, UML.Actor) self.connect(line, line.head, comment) self.connect(line, line.tail, ac) self.assertTrue(self.get_connected(line.tail) is ac) self.assertEquals(1, len(comment.subject.annotatedElement)) self.assertTrue(ac.subject in comment.subject.annotatedElement) def test_commentline_element_connect(self): """Test comment line connecting to comment and actor items. """ comment = self.create(items.CommentItem, UML.Comment) line = self.create(items.CommentLineItem) ac = self.create(items.ActorItem, UML.Actor) self.connect(line, line.head, comment) self.connect(line, line.tail, ac) self.assertTrue(self.get_connected(line.tail) is ac) self.assertEquals(1, len(comment.subject.annotatedElement)) self.assertTrue(ac.subject in comment.subject.annotatedElement) def test_commentline_element_reconnect(self): """Test comment line connecting to comment and actor items. """ comment = self.create(items.CommentItem, UML.Comment) line = self.create(items.CommentLineItem) ac = self.create(items.ActorItem, UML.Actor) self.connect(line, line.head, comment) self.connect(line, line.tail, ac) self.assertTrue(self.get_connected(line.tail) is ac) self.assertEquals(1, len(comment.subject.annotatedElement)) self.assertTrue(ac.subject in comment.subject.annotatedElement) ac2 = self.create(items.ActorItem, UML.Actor) #ac.canvas.disconnect_item(line, line.tail) self.disconnect(line, line.tail) self.connect(line, line.tail, ac2) self.assertTrue(self.get_connected(line.tail) is ac2) self.assertEquals(1, len(comment.subject.annotatedElement)) self.assertTrue(ac2.subject in comment.subject.annotatedElement) def test_commentline_element_disconnect(self): """Test comment line connecting to comment and disconnecting actor item. """ comment = self.create(items.CommentItem, UML.Comment) line = self.create(items.CommentLineItem) ac = self.create(items.ActorItem, UML.Actor) self.connect(line, line.head, comment) self.connect(line, line.tail, ac) self.assertTrue(self.get_connected(line.tail) is ac) self.disconnect(line, line.tail) self.assertFalse(self.get_connected(line.tail) is ac) def test_commentline_unlink(self): """Test comment line unlinking. """ clazz = self.create(items.ClassItem, UML.Class) comment = self.create(items.CommentItem, UML.Comment) line = self.create(items.CommentLineItem) self.connect(line, line.head, comment) self.connect(line, line.tail, clazz) self.assertTrue(clazz.subject in comment.subject.annotatedElement) self.assertTrue(comment.subject in clazz.subject.ownedComment) self.assertTrue(line.canvas) # FixMe: This should invoke the disconnnect handler of the line's # handles. line.unlink() self.assertFalse(line.canvas) self.assertFalse(clazz.subject in comment.subject.annotatedElement) self.assertFalse(comment.subject in clazz.subject.ownedComment) self.assertTrue(len(comment.subject.annotatedElement) == 0, comment.subject.annotatedElement) self.assertTrue(len(clazz.subject.ownedComment) == 0, clazz.subject.ownedComment) def test_commentline_element_unlink(self): """Test comment line unlinking using a class item. """ clazz = self.create(items.ClassItem, UML.Class) comment = self.create(items.CommentItem, UML.Comment) line = self.create(items.CommentLineItem) self.connect(line, line.head, comment) self.connect(line, line.tail, clazz) self.assertTrue(clazz.subject in comment.subject.annotatedElement) self.assertTrue(comment.subject in clazz.subject.ownedComment) self.assertTrue(line.canvas) clazz_subject = clazz.subject # FixMe: This should invoke the disconnnect handler of the line's # handles. clazz.unlink() self.assertFalse(clazz.canvas) self.assertTrue(line.canvas) self.assertFalse(comment.subject.annotatedElement) self.assertTrue(len(clazz_subject.ownedComment) == 0) def test_commentline_relationship_unlink(self): """Test comment line to a relationship item connection and unlink. Demonstrates defect #103. """ clazz1 = self.create(items.ClassItem, UML.Class) clazz2 = self.create(items.ClassItem, UML.Class) gen = self.create(items.GeneralizationItem) self.connect(gen, gen.head, clazz1) self.connect(gen, gen.tail, clazz2) assert gen.subject # now, connect comment to a generalization (relationship) comment = self.create(items.CommentItem, UML.Comment) line = self.create(items.CommentLineItem) self.connect(line, line.head, comment) self.connect(line, line.tail, gen) self.assertTrue(gen.subject in comment.subject.annotatedElement) self.assertTrue(comment.subject in gen.subject.ownedComment) # FixMe: This should invoke the disconnnect handler of the line's # handles. gen.unlink() self.assertFalse(comment.subject.annotatedElement) self.assertTrue(gen.subject is None) def test_commentline_linked_to_same_element_twice(self): """ It is not allowed to create two commentlines between the same elements. """ clazz = self.create(items.ClassItem, UML.Class) # now, connect comment to a generalization (relationship) comment = self.create(items.CommentItem, UML.Comment) line1 = self.create(items.CommentLineItem) self.connect(line1, line1.head, comment) self.connect(line1, line1.tail, clazz) self.assertTrue(clazz.subject in comment.subject.annotatedElement) self.assertTrue(comment.subject in clazz.subject.ownedComment) # Now add another line line2 = self.create(items.CommentLineItem) self.connect(line2, line2.head, comment) self.assertFalse(self.allow(line2, line2.tail, clazz)) # vim: sw=4:et:ai gaphor-0.17.2/gaphor/adapters/tests/test_connector.py000066400000000000000000000004771220151210700227100ustar00rootroot00000000000000""" Test Item connections. """ from gaphor.tests import TestCase from zope import component from gaphor import UML from gaphor.diagram import items from gaphor.diagram.interfaces import IConnect class ConnectorTestCase(TestCase): pass # fixme: relationship test moved from multi dependency test # vim:sw=4:et:ai gaphor-0.17.2/gaphor/adapters/tests/test_editor.py000066400000000000000000000113521220151210700221760ustar00rootroot00000000000000 from gaphor.tests import TestCase from gaphor import UML from gaphor.diagram import items from gaphor.diagram.interfaces import IEditor from gaphor.adapters.propertypages import AttributesPage, OperationsPage import gtk class EditorTestCase(TestCase): def setUp(self): super(EditorTestCase, self).setUp() def test_association_editor(self): assoc = self.create(items.AssociationItem) adapter = IEditor(assoc) assert not adapter.is_editable(10, 10) assert adapter._edit is None # Intermezzo: connect the association between two classes class1 = self.create(items.ClassItem, UML.Class) class2 = self.create(items.ClassItem, UML.Class) assoc.handles()[0].pos = 10, 10 assoc.handles()[-1].pos = 100, 100 self.connect(assoc, assoc.head, class1) self.connect(assoc, assoc.tail, class2) assert assoc.subject # Now the association has a subject member, so editing should really # work. pos = 55, 55 self.assertTrue(adapter.is_editable(*pos)) self.assertTrue(adapter._edit is assoc) pos = assoc.head_end._name_bounds[:2] self.assertTrue(adapter.is_editable(*pos)) self.assertTrue(adapter._edit is assoc.head_end) pos = assoc.tail_end._name_bounds[:2] self.assertTrue(adapter.is_editable(*pos)) self.assertTrue(adapter._edit is assoc.tail_end) def test_objectnode_editor(self): node = self.create(items.ObjectNodeItem, UML.ObjectNode) self.diagram.canvas.update_now() adapter = IEditor(node) self.assertTrue(adapter.is_editable(10, 10)) #assert not adapter.edit_tag #assert adapter.is_editable(*node.tag_bounds[:2]) #assert adapter.edit_tag def test_classifier_editor(self): """ Test classifier editor """ klass = self.create(items.ClassItem, UML.Class) klass.subject.name = 'Class1' self.diagram.canvas.update() attr = self.element_factory.create(UML.Property) attr.name = "blah" klass.subject.ownedAttribute = attr oper = self.element_factory.create(UML.Operation) oper.name = 'method' klass.subject.ownedOperation = oper self.diagram.canvas.update() edit = IEditor(klass) self.assertEqual('CompartmentItemEditor', edit.__class__.__name__) self.assertEqual(True, edit.is_editable(10, 10)) # Test the inner working of the editor self.assertEqual(klass, edit._edit) self.assertEqual('Class1', edit.get_text()) # The attribute: y = klass._header_size[1] + klass.style.compartment_padding[0] + 3 self.assertEqual(True, edit.is_editable(4, y)) self.assertEqual(attr, edit._edit.subject) self.assertEqual('+ blah', edit.get_text()) y += klass.compartments[0].height # The operation self.assertEqual(True, edit.is_editable(3, y)) self.assertEqual(oper, edit._edit.subject) self.assertEqual('+ method()', edit.get_text()) def test_class_attribute_editor(self): klass = self.create(items.ClassItem, UML.Class) klass.subject.name = 'Class1' editor = AttributesPage(klass) page = editor.construct() tree_view = page.get_children()[1] self.assertSame(gtk.TreeView, type(tree_view)) attr = self.element_factory.create(UML.Property) attr.name = "blah" klass.subject.ownedAttribute = attr self.assertSame(attr, tree_view.get_model()[0][-1]) self.assertEquals("+ blah", tree_view.get_model()[0][0]) attr.name = "foo" self.assertEquals("+ foo", tree_view.get_model()[0][0]) attr.typeValue = 'int' self.assertEquals("+ foo: int", tree_view.get_model()[0][0]) attr.isDerived = True self.assertEquals("+ /foo: int", tree_view.get_model()[0][0]) page.destroy() def test_class_operation_editor(self): klass = self.create(items.ClassItem, UML.Class) klass.subject.name = 'Class1' editor = OperationsPage(klass) page = editor.construct() tree_view = page.get_children()[1] self.assertSame(gtk.TreeView, type(tree_view)) oper = self.element_factory.create(UML.Operation) oper.name = 'o' klass.subject.ownedOperation = oper self.assertSame(oper, tree_view.get_model()[0][-1]) self.assertEquals("+ o()", tree_view.get_model()[0][0]) p = self.element_factory.create(UML.Parameter) p.name = 'blah' oper.formalParameter = p self.assertEquals("+ o(in blah)", tree_view.get_model()[0][0]) page.destroy() # vim:sw=4:et:ai gaphor-0.17.2/gaphor/adapters/tests/test_grouping.py000066400000000000000000000252161220151210700225460ustar00rootroot00000000000000""" Tests for grouping functionality in Gaphor. """ from gaphor import UML from gaphor.ui.namespace import NamespaceModel from gaphor.diagram import items from gaphor.tests import TestCase class NodesGroupTestCase(TestCase): """ Nodes grouping tests. """ def test_grouping(self): """Test node within another node composition """ n1 = self.create(items.NodeItem, UML.Node) n2 = self.create(items.NodeItem, UML.Node) self.group(n1, n2) self.assertTrue(n2.subject in n1.subject.nestedNode) self.assertFalse(n1.subject in n2.subject.nestedNode) def test_ungrouping(self): """Test decomposition of component from node """ n1 = self.create(items.NodeItem, UML.Node) n2 = self.create(items.NodeItem, UML.Node) self.group(n1, n2) self.ungroup(n1, n2) self.assertFalse(n2.subject in n1.subject.nestedNode) self.assertFalse(n1.subject in n2.subject.nestedNode) class NodeComponentGroupTestCase(TestCase): def test_grouping(self): """Test component within node composition """ n = self.create(items.NodeItem, UML.Node) c = self.create(items.ComponentItem, UML.Component) self.group(n, c) self.assertEquals(1, len(n.subject.ownedAttribute)) self.assertEquals(1, len(n.subject.ownedConnector)) self.assertEquals(1, len(c.subject.ownedAttribute)) self.assertEquals(2, len(self.kindof(UML.ConnectorEnd))) a1 = n.subject.ownedAttribute[0] a2 = c.subject.ownedAttribute[0] self.assertTrue(a1.isComposite) self.assertTrue(a1 in n.subject.part) connector = n.subject.ownedConnector[0] self.assertTrue(connector.end[0].role is a1) self.assertTrue(connector.end[1].role is a2) def test_ungrouping(self): """Test decomposition of component from node """ n = self.create(items.NodeItem, UML.Node) c = self.create(items.ComponentItem, UML.Component) query = self.group(n, c) query = self.ungroup(n, c) self.assertEquals(0, len(n.subject.ownedAttribute)) self.assertEquals(0, len(c.subject.ownedAttribute)) self.assertEquals(0, len(self.kindof(UML.Property))) self.assertEquals(0, len(self.kindof(UML.Connector))) self.assertEquals(0, len(self.kindof(UML.ConnectorEnd))) class NodeArtifactGroupTestCase(TestCase): def test_grouping(self): """Test artifact within node deployment """ n = self.create(items.NodeItem, UML.Node) a = self.create(items.ArtifactItem, UML.Artifact) self.group(n, a) self.assertEquals(1, len(n.subject.deployment)) self.assertTrue(n.subject.deployment[0].deployedArtifact[0] is a.subject) def test_ungrouping(self): """Test removal of artifact from node """ n = self.create(items.NodeItem, UML.Node) a = self.create(items.ArtifactItem, UML.Artifact) query = self.group(n, a) query = self.ungroup(n, a) self.assertEquals(0, len(n.subject.deployment)) self.assertEquals(0, len(self.kindof(UML.Deployment))) class SubsystemUseCaseGroupTestCase(TestCase): def test_grouping(self): """Test adding an use case to a subsystem """ s = self.create(items.SubsystemItem, UML.Component) uc1 = self.create(items.UseCaseItem, UML.UseCase) uc2 = self.create(items.UseCaseItem, UML.UseCase) self.group(s, uc1) self.assertEquals(1, len(uc1.subject.subject)) self.group(s, uc2) self.assertEquals(1, len(uc2.subject.subject)) # Classifier.useCase is not navigable to UseCase #self.assertEquals(2, len(s.subject.useCase)) def test_grouping_with_namespace(self): """Test adding an use case to a subsystem (with namespace) """ namespace = NamespaceModel(self.element_factory) s = self.create(items.SubsystemItem, UML.Component) uc = self.create(items.UseCaseItem, UML.UseCase) # manipulate namespace c = self.element_factory.create(UML.Class) attribute = self.element_factory.create(UML.Property) c.ownedAttribute = attribute self.group(s, uc) self.assertEquals(1, len(uc.subject.subject)) self.assertTrue(s.subject.namespace is not uc.subject) def test_ungrouping(self): """Test removal of use case from subsystem """ s = self.create(items.SubsystemItem, UML.Component) uc1 = self.create(items.UseCaseItem, UML.UseCase) uc2 = self.create(items.UseCaseItem, UML.UseCase) self.group(s, uc1) self.group(s, uc2) self.ungroup(s, uc1) self.assertEquals(0, len(uc1.subject.subject)) # Classifier.useCase is not navigable to UseCase #self.assertEquals(1, len(s.subject.useCase)) self.ungroup(s, uc2) self.assertEquals(0, len(uc2.subject.subject)) # Classifier.useCase is not navigable to UseCase #self.assertEquals(0, len(s.subject.useCase)) class PartitionGroupTestCase(TestCase): def test_no_subpartition_when_nodes_in(self): """Test adding subpartition when nodes added """ p = self.create(items.PartitionItem) a1 = self.create(items.ActionItem, UML.Action) p1 = self.create(items.PartitionItem) p2 = self.create(items.PartitionItem) self.group(p, p1) self.group(p1, a1) self.assertFalse(self.can_group(p1, p2)) def test_no_nodes_when_subpartition_in(self): """Test adding nodes when subpartition added """ p = self.create(items.PartitionItem) a1 = self.create(items.ActionItem, UML.Action) p1 = self.create(items.PartitionItem) self.group(p, p1) self.assertFalse(self.can_group(p, a1)) def test_action_grouping(self): """Test adding action to partition """ p1 = self.create(items.PartitionItem) p2 = self.create(items.PartitionItem) a1 = self.create(items.ActionItem, UML.Action) a2 = self.create(items.ActionItem, UML.Action) self.assertFalse(self.can_group(p1, a1)) # cannot add to dimension self.group(p1, p2) self.group(p2, a1) self.assertTrue(self.can_group(p2, a1)) self.assertEquals(1, len(p2.subject.node)) self.group(p2, a2) self.assertEquals(2, len(p2.subject.node)) def test_subpartition_grouping(self): """Test adding subpartition to partition """ p = self.create(items.PartitionItem) p1 = self.create(items.PartitionItem) p2 = self.create(items.PartitionItem) self.group(p, p1) self.assertTrue(p.subject is None) self.assertTrue(p1.subject is not None) self.group(p, p2) self.assertTrue(p.subject is None) self.assertTrue(p2.subject is not None) def test_ungrouping(self): """Test action and subpartition removal """ p1 = self.create(items.PartitionItem) p2 = self.create(items.PartitionItem) a1 = self.create(items.ActionItem, UML.Action) a2 = self.create(items.ActionItem, UML.Action) self.group(p1, p2) # group to p2, it is disallowed to p1 self.group(p2, a1) self.group(p2, a2) self.ungroup(p2, a1) self.assertEquals(1, len(p2.subject.node)) self.ungroup(p2, a2) self.assertEquals(0, len(p2.subject.node)) self.ungroup(p1, p2) self.assertTrue(p1.subject is None, p1.subject) self.assertTrue(p2.subject is None, p2.subject) self.assertEquals(0, len(self.kindof(UML.ActivityPartition))) def test_ungrouping_with_actions(self): """Test subpartition with actions removal """ p1 = self.create(items.PartitionItem) p2 = self.create(items.PartitionItem) a1 = self.create(items.ActionItem, UML.Action) a2 = self.create(items.ActionItem, UML.Action) self.group(p1, p2) # group to p2, it is disallowed to p1 self.group(p2, a1) self.group(p2, a2) partition = p2.subject assert len(partition.node) == 2, partition.node assert 2 == len(p2.canvas.get_children(p2)), p2.canvas.get_children(p2) self.ungroup(p1, p2) self.assertEquals(0, len(partition.node)) self.assertEquals(0, len(p2.canvas.get_children(p2))) self.assertEquals(0, len(partition.node)) def test_nested_subpartition_ungrouping(self): """Test removal of subpartition with swimlanes """ p1 = self.create(items.PartitionItem) p2 = self.create(items.PartitionItem) p3 = self.create(items.PartitionItem) p4 = self.create(items.PartitionItem) self.group(p1, p2) self.group(p2, p3) self.group(p2, p4) self.assertTrue(p2.subject is not None, p2.subject) self.assertTrue(p3.subject is not None, p3.subject) self.assertTrue(p4.subject is not None, p4.subject) self.ungroup(p1, p2) self.assertTrue(p1.subject is None, p1.subject) self.assertTrue(p2.subject is None, p2.subject) self.assertTrue(p3.subject is not None, p3.subject) self.assertTrue(p4.subject is not None, p4.subject) self.assertEquals(2, len(self.kindof(UML.ActivityPartition))) def test_nested_subpartition_regrouping(self): """Test regrouping of subpartition with swimlanes """ p1 = self.create(items.PartitionItem) p2 = self.create(items.PartitionItem) p3 = self.create(items.PartitionItem) p4 = self.create(items.PartitionItem) self.group(p1, p2) self.group(p2, p3) self.group(p2, p4) self.assertTrue(p2.subject is not None, p2.subject) self.assertTrue(p3.subject is not None, p3.subject) self.assertTrue(p4.subject is not None, p4.subject) self.ungroup(p1, p2) self.assertTrue(p1.subject is None, p1.subject) self.assertTrue(p2.subject is None, p2.subject) self.assertTrue(p3.subject is not None, p3.subject) self.assertTrue(p4.subject is not None, p4.subject) self.assertEquals(2, len(self.kindof(UML.ActivityPartition))) self.group(p1, p2) self.assertEquals(3, len(self.kindof(UML.ActivityPartition))) self.assertTrue(p2.subject is not None, p2.subject) self.assertTrue(p3.subject is not None, p3.subject) self.assertTrue(p4.subject is not None, p4.subject) self.assertTrue(p3.subject in p2.subject.subpartition, p2.subject.subpartition) self.assertTrue(p4.subject in p2.subject.subpartition, p2.subject.subpartition) gaphor-0.17.2/gaphor/adapters/usecases/000077500000000000000000000000001220151210700177465ustar00rootroot00000000000000gaphor-0.17.2/gaphor/adapters/usecases/__init__.py000066400000000000000000000000001220151210700220450ustar00rootroot00000000000000gaphor-0.17.2/gaphor/adapters/usecases/tests/000077500000000000000000000000001220151210700211105ustar00rootroot00000000000000gaphor-0.17.2/gaphor/adapters/usecases/tests/__init__.py000066400000000000000000000000001220151210700232070ustar00rootroot00000000000000gaphor-0.17.2/gaphor/adapters/usecases/tests/test_extend.py000066400000000000000000000044151220151210700240140ustar00rootroot00000000000000""" Test extend item connections. """ from gaphor.tests import TestCase from gaphor import UML from gaphor.diagram import items class ExtendItemTestCase(TestCase): def test_use_case_glue(self): """Test "extend" glueing to use cases """ uc1 = self.create(items.UseCaseItem, UML.UseCase) extend = self.create(items.ExtendItem) glued = self.allow(extend, extend.head, uc1) self.assertTrue(glued) def test_use_case_connect(self): """Test connecting "extend" to use cases """ uc1 = self.create(items.UseCaseItem, UML.UseCase) uc2 = self.create(items.UseCaseItem, UML.UseCase) extend = self.create(items.ExtendItem) self.connect(extend, extend.head, uc1) self.assertTrue(self.get_connected(extend.head), uc1) self.connect(extend, extend.tail, uc2) self.assertTrue(self.get_connected(extend.tail), uc2) def test_use_case_connect(self): """Test reconnecting use cases with "extend" """ uc1 = self.create(items.UseCaseItem, UML.UseCase) uc2 = self.create(items.UseCaseItem, UML.UseCase) uc3 = self.create(items.UseCaseItem, UML.UseCase) extend = self.create(items.ExtendItem) # connect: uc1 -> uc2 self.connect(extend, extend.head, uc1) self.connect(extend, extend.tail, uc2) e = extend.subject # reconnect: uc1 -> uc2 self.connect(extend, extend.tail, uc3) self.assertSame(e, extend.subject) self.assertSame(extend.subject.extendedCase, uc1.subject) self.assertSame(extend.subject.extension, uc3.subject) def test_use_case_disconnect(self): """Test disconnecting "extend" from use cases """ uc1 = self.create(items.UseCaseItem, UML.UseCase) uc2 = self.create(items.UseCaseItem, UML.UseCase) extend = self.create(items.ExtendItem) self.connect(extend, extend.head, uc1) self.connect(extend, extend.tail, uc2) self.disconnect(extend, extend.head) self.assertTrue(self.get_connected(extend.head) is None) self.assertTrue(extend.subject is None) self.disconnect(extend, extend.tail) self.assertTrue(self.get_connected(extend.tail) is None) # vim:sw=4:et:ai gaphor-0.17.2/gaphor/adapters/usecases/tests/test_include.py000066400000000000000000000044701220151210700241510ustar00rootroot00000000000000""" Test include item connections. """ from gaphor.tests import TestCase from gaphor import UML from gaphor.diagram import items class IncludeItemTestCase(TestCase): def test_use_case_glue(self): """Test "include" glueing to use cases """ uc1 = self.create(items.UseCaseItem, UML.UseCase) include = self.create(items.IncludeItem) glued = self.allow(include, include.head, uc1) self.assertTrue(glued) def test_use_case_connect(self): """Test connecting "include" to use cases """ uc1 = self.create(items.UseCaseItem, UML.UseCase) uc2 = self.create(items.UseCaseItem, UML.UseCase) include = self.create(items.IncludeItem) self.connect(include, include.head, uc1) self.assertTrue(self.get_connected(include.head), uc1) self.connect(include, include.tail, uc2) self.assertTrue(self.get_connected(include.tail), uc2) def test_use_case_connect(self): """Test reconnecting use cases with "include" """ uc1 = self.create(items.UseCaseItem, UML.UseCase) uc2 = self.create(items.UseCaseItem, UML.UseCase) uc3 = self.create(items.UseCaseItem, UML.UseCase) include = self.create(items.IncludeItem) # connect: uc1 -> uc2 self.connect(include, include.head, uc1) self.connect(include, include.tail, uc2) e = include.subject # reconnect: uc1 -> uc2 self.connect(include, include.tail, uc3) self.assertSame(e, include.subject) self.assertSame(include.subject.addition, uc1.subject) self.assertSame(include.subject.includingCase, uc3.subject) def test_use_case_disconnect(self): """Test disconnecting "include" from use cases """ uc1 = self.create(items.UseCaseItem, UML.UseCase) uc2 = self.create(items.UseCaseItem, UML.UseCase) include = self.create(items.IncludeItem) self.connect(include, include.head, uc1) self.connect(include, include.tail, uc2) self.disconnect(include, include.head) self.assertTrue(self.get_connected(include.head) is None) self.assertTrue(include.subject is None) self.disconnect(include, include.tail) self.assertTrue(self.get_connected(include.tail) is None) # vim:sw=4:et:ai gaphor-0.17.2/gaphor/adapters/usecases/usecaseconnect.py000066400000000000000000000035271220151210700233310ustar00rootroot00000000000000""" Use cases related connection adapters. """ from zope import component from gaphor import UML from gaphor.diagram import items from gaphor.adapters.connectors import RelationshipConnect class IncludeConnect(RelationshipConnect): """ Connect use cases with an include item relationship. """ component.adapts(items.UseCaseItem, items.IncludeItem) def allow(self, handle, port): line = self.line element = self.element if not (element.subject and isinstance(element.subject, UML.UseCase)): return None return super(IncludeConnect, self).allow(handle, port) def reconnect(self, handle, port): self.reconnect_relationship(handle, UML.Include.addition, UML.Include.includingCase) def connect_subject(self, handle): relation = self.relationship_or_new(UML.Include, UML.Include.addition, UML.Include.includingCase) self.line.subject = relation component.provideAdapter(IncludeConnect) class ExtendConnect(RelationshipConnect): """ Connect use cases with an extend item relationship. """ component.adapts(items.UseCaseItem, items.ExtendItem) def allow(self, handle, port): line = self.line element = self.element if not (element.subject and isinstance(element.subject, UML.UseCase)): return None return super(ExtendConnect, self).allow(handle, port) def reconnect(self, handle, port): self.reconnect_relationship(handle, UML.Extend.extendedCase, UML.Extend.extension) def connect_subject(self, handle): relation = self.relationship_or_new(UML.Extend, UML.Extend.extendedCase, UML.Extend.extension) self.line.subject = relation component.provideAdapter(ExtendConnect) # vim:sw=4:et:ai gaphor-0.17.2/gaphor/application.py000066400000000000000000000124701220151210700172110ustar00rootroot00000000000000""" The Application object. One application should be available. All important services are present in the application object: - plugin manager - undo manager - main window - UML element factory - action sets """ import pkg_resources from zope import component from logging import getLogger from gaphor.interfaces import IService, IEventFilter from gaphor.event import ServiceInitializedEvent, ServiceShutdownEvent logger = getLogger('Application') class NotInitializedError(Exception): pass class _Application(object): """ The Gaphor application is started from the Application instance. It behaves like a singleton in many ways. The Application is responsible for loading services and plugins. Services are registered as "utilities" in the application registry. Methods are provided that wrap zope.component's handle, adapter and subscription registrations. In addition to registration methods also unregister methods are provided. This way services can be properly unregistered on shutdown for example. """ # interface.implements(IApplication) _ESSENTIAL_SERVICES = ['component_registry'] def __init__(self): self._uninitialized_services = {} self._event_filter = None self.component_registry = None def init(self, services=None): """ Initialize the application. """ self.load_services(services) self.init_all_services() essential_services = property(lambda s: s._ESSENTIAL_SERVICES, doc= """ Provide an ordered list of services that need to be loaded first. """) def load_services(self, services=None): """ Load services from resources. Services are registered as utilities in zope.component. Service should provide an interface gaphor.interfaces.IService. """ # Ensure essential services are always loaded. if services: for name in self.essential_services: if name not in services: services.append(name) for ep in pkg_resources.iter_entry_points('gaphor.services'): cls = ep.load() if not IService.implementedBy(cls): raise NameError, 'Entry point %s doesn''t provide IService' % ep.name if not services or ep.name in services: logger.debug('found service entry point "%s"' % ep.name) srv = cls() self._uninitialized_services[ep.name] = srv def init_all_services(self): for name in self.essential_services: self.init_service(name) while self._uninitialized_services: self.init_service(self._uninitialized_services.iterkeys().next()) def init_service(self, name): """ Initialize a not yet initialized service. Raises ComponentLookupError if the service has not been found """ try: srv = self._uninitialized_services.pop(name) except KeyError: raise component.ComponentLookupError(IService, name) else: logger.info('initializing service service.%s' % name) srv.init(self) # Bootstrap symptoms if name in self.essential_services: setattr(self, name, srv) self.component_registry.register_utility(srv, IService, name) self.component_registry.handle(ServiceInitializedEvent(name, srv)) return srv distribution = property(lambda s: pkg_resources.get_distribution('gaphor'), doc='Get the PkgResources distribution for Gaphor') def get_service(self, name): if not self.component_registry: raise NotInitializedError('First call Application.init() to load services') try: return self.component_registry.get_service(name) except component.ComponentLookupError: return self.init_service(name) def run(self): import gtk gtk.main() def shutdown(self): for name, srv in self.component_registry.get_utilities(IService): if name not in self.essential_services: self.shutdown_service(name) for name in reversed(self.essential_services): self.shutdown_service(name) setattr(self, name, None) def shutdown_service(self, name): srv = self.component_registry.get_service(name) self.component_registry.handle(ServiceShutdownEvent(name, srv)) self.component_registry.unregister_utility(srv, IService, name) srv.shutdown() # Make sure there is only one! Application = _Application() class inject(object): """ Simple descriptor for dependency injection. This is technically a wrapper around Application.get_service(). Usage:: >>> class A(object): ... element_factory = inject('element_factory') """ def __init__(self, name): self._name = name #self._s = None def __get__(self, obj, class_=None): """ Resolve a dependency, but only if we're called from an object instance. """ if not obj: return self return Application.get_service(self._name) #if self._s is None: # self._s = _Application.get_service(self._name) #return self._s # vim:sw=4:et:ai gaphor-0.17.2/gaphor/core.py000066400000000000000000000006041220151210700156320ustar00rootroot00000000000000""" The Core module provides an entry point for Gaphor's core constructs. An average module should only need to import this module. """ from gaphor.application import inject, Application from gaphor.transaction import Transaction, transactional from gaphor.action import action, toggle_action, radio_action, open_action, build_action_group from gaphor.i18n import _ # vim:sw=4:et:ai gaphor-0.17.2/gaphor/diagram/000077500000000000000000000000001220151210700157345ustar00rootroot00000000000000gaphor-0.17.2/gaphor/diagram/README000066400000000000000000000040751220151210700166220ustar00rootroot00000000000000module: gaphor.diagram ====================== This module contains the items that can be placed in gaphor.UML.Diagram's. Each diagram item represents one or more gaphor.UML.Element's (subclasses thereof). Diagram items are subclassed from Presentation. This is the base class for "presentation elements": items used to visualize an UML element. Also gaphor.UML.Element is inherited. This class contains logic for event notification among other utility functions (e.g. the __unlink__ signal). Presentation elements ("items") have a "subject" attribute, that refers to the (main-) model element class that is represented (e.g. a class representation has as subject a gaphor.UML.Class instance, other elements, such as gaphor.UML.Property and gaphor.UML.Operation are implicitly referenced. Signal handling --------------- As of this version, signal notification is done through zope.component. As a result all modification events originated from gaphor.UML classes are received by all items. (In the old days every item should register for specific events on specific model elements, which resulted in quite a complex administration of event handlers, since all those handlers should be unregistered when an item was removed). Connecting items ---------------- Another change, due to the introduction of the Zope3 framework, is how items are connected to one another. This is done through adapters (multi-adapters to be more specific). An adapter is used to add additional behavior to an object. In this case the IConnect interface should be implemented for (element, line) tuples. For each possible connection an adapter should be written (CommentItem with any DiagramItem, AssociationItem with ClassifierItem, etc.). The big (huge) advantage is that complex connection stuff is removed from the diagram items (which resulted in cyclic dependencies in the past) and is put on a higher level: the adapter. See interfaces.py and adapter.py for implementations. Text editing ------------ Like item connecting, text editing is also implemented through adapters. The IEditor interface is used for this. gaphor-0.17.2/gaphor/diagram/__init__.py000066400000000000000000000052361220151210700200530ustar00rootroot00000000000000""" The diagram package contains items (to be drawn on the diagram), tools (used for interacting with the diagram) and interfaces (used for adapting the diagram). """ import inspect import gobject import uuid from gaphor.diagram.style import Style # Map UML elements to their (default) representation. _uml_to_item_map = { } def create(type): return create_as(type, str(uuid.uuid1())) def create_as(type, id): return type(id) def get_diagram_item(element): global _uml_to_item_map return _uml_to_item_map.get(element) def set_diagram_item(element, item): global _uml_to_item_map _uml_to_item_map[element] = item def uml(uml_class, stereotype=None): """ Assign UML metamodel class and a stereotype to diagram item. :Parameters: uml_class UML metamodel class. stereotype Stereotype name (i.e. 'subsystem'). """ def f(item_class): t = uml_class if stereotype is not None: t = (uml_class, stereotype) item_class.__stereotype__ = stereotype set_diagram_item(t, item_class) return item_class return f class DiagramItemMeta(type): """ Initialize a new diagram item. 1. Register UML.Elements by means of the __uml__ attribute (see map_uml_class method). 2. Set items style information. @ivar style: style information """ def __init__(self, name, bases, data): type.__init__(self, name, bases, data) self.map_uml_class(data) self.set_style(data) def map_uml_class(self, data): """ Map UML class to diagram item. @param cls: new instance of item class @param data: metaclass data with UML class information """ if '__uml__' in data: obj = data['__uml__'] if isinstance(obj, (tuple, set, list)): for c in obj: set_diagram_item(c, self) else: set_diagram_item(obj, self) def set_style(self, data): """ Set item style information by merging provided information with style information from base classes. @param cls: new instance of diagram item class @param bases: base classes of an item @param data: metaclass data with style information """ style = Style() for c in self.__bases__: if hasattr(c, 'style'): for (name, value) in c.style.items(): style.add(name, value) if '__style__' in data: for (name, value) in data['__style__'].iteritems(): style.add(name, value) self.style = style # vim:sw=4:et gaphor-0.17.2/gaphor/diagram/actions/000077500000000000000000000000001220151210700173745ustar00rootroot00000000000000gaphor-0.17.2/gaphor/diagram/actions/__init__.py000066400000000000000000000000001220151210700214730ustar00rootroot00000000000000gaphor-0.17.2/gaphor/diagram/actions/action.py000066400000000000000000000040511220151210700212230ustar00rootroot00000000000000""" Action diagram item. """ from math import pi from gaphor import UML from gaphor.diagram.nameditem import NamedItem from gaphor.diagram.style import ALIGN_CENTER, ALIGN_MIDDLE class ActionItem(NamedItem): __uml__ = UML.Action __style__ = { 'min-size': (50, 30), 'name-align': (ALIGN_CENTER, ALIGN_MIDDLE), } def draw(self, context): """ Draw action symbol. """ super(ActionItem, self).draw(context) c = context.cairo d = 15 c.move_to(0, d) c.arc(d, d, d, pi, 1.5 * pi) c.line_to(self.width - d, 0) c.arc(self.width - d, d, d, 1.5 * pi, 0) c.line_to(self.width, self.height - d) c.arc(self.width - d, self.height - d, d, 0, 0.5 * pi) c.line_to(d, self.height) c.arc(d, self.height - d, d, 0.5 * pi, pi) c.close_path() c.stroke() class SendSignalActionItem(NamedItem): __uml__ = UML.SendSignalAction __style__ = { 'min-size': (50, 30), 'name-align': (ALIGN_CENTER, ALIGN_MIDDLE), } def draw(self, context): """ Draw action symbol. """ super(SendSignalActionItem, self).draw(context) c = context.cairo d = 15 w = self.width h = self.height c.move_to(0, 0) c.line_to(w-d, 0) c.line_to(w, h/2) c.line_to(w-d, h) c.line_to(0, h) c.close_path() c.stroke() class AcceptEventActionItem(NamedItem): __uml__ = UML.SendSignalAction __style__ = { 'min-size': (50, 30), 'name-align': (ALIGN_CENTER, ALIGN_MIDDLE), } def draw(self, context): """ Draw action symbol. """ super(AcceptEventActionItem, self).draw(context) c = context.cairo d = 15 w = self.width h = self.height c.move_to(0, 0) c.line_to(w, 0) c.line_to(w, h) c.line_to(0, h) c.line_to(d, h/2) c.close_path() c.stroke() # vim:sw=4:et:ai gaphor-0.17.2/gaphor/diagram/actions/flow.py000066400000000000000000000412001220151210700207120ustar00rootroot00000000000000""" Control flow and object flow implementation. Contains also implementation to split flows using activity edge connectors. """ from math import atan, pi, sin, cos from gaphor import UML from gaphor.diagram.diagramline import NamedLine from gaphor.diagram.style import ALIGN_LEFT, ALIGN_RIGHT, ALIGN_TOP node_classes = { UML.ForkNode: UML.JoinNode, UML.DecisionNode: UML.MergeNode, UML.JoinNode: UML.ForkNode, UML.MergeNode: UML.DecisionNode, } class FlowItem(NamedLine): """ Representation of control flow and object flow. Flow item has name and guard. It can be splitted into two flows with activity edge connectors. """ __uml__ = UML.ControlFlow __style__ = { 'name-align': (ALIGN_RIGHT, ALIGN_TOP), 'name-padding': (5, 15, 5, 5), } def __init__(self, id = None): NamedLine.__init__(self, id) self._guard = self.add_text('guard.value', editable=True) self.watch('subject.guard', self.on_control_flow_guard) self.watch('subject.guard', self.on_control_flow_guard) def postload(self): try: self._guard.text = self.subject.guard.value except AttributeError, e: self._guard.text = '' super(FlowItem, self).postload() def on_control_flow_guard(self, event): subject = self.subject try: self._guard.text = subject.guard if subject else '' except AttributeError, e: self._guard.text = '' self.request_update() def draw_tail(self, context): cr = context.cairo cr.line_to(0, 0) cr.stroke() cr.move_to(15, -6) cr.line_to(0, 0) cr.line_to(15, 6) #class ACItem(TextElement): class ACItem(object): """ Activity edge connector. It is a circle with name inside. """ RADIUS = 10 def __init__(self, id): pass #TextElement.__init__(self, id) #self._circle = diacanvas.shape.Ellipse() #self._circle.set_line_width(2.0) #self._circle.set_fill_color(diacanvas.color(255, 255, 255)) #self._circle.set_fill(diacanvas.shape.FILL_SOLID) #self.show_border = False # set new value notification function to change activity edge # connector name globally #vnf = self.on_subject_notify__value #def f(subject, pspec): # vnf(subject, pspec) # if self.parent._opposite: # self.parent._opposite._connector.subject.value = subject.value #self.on_subject_notify__value = f def move_center(self, x, y): """ Move center of item to point (x, y). Other parts of item are aligned to this point. """ a = self.props.affine x -= self.RADIUS y -= self.RADIUS self.props.affine = (a[0], a[1], a[2], a[3], x, y) def on_update(self, affine): """ Center name of activity edge connector and put a circle around it. """ r = self.RADIUS * 2 x = self.RADIUS y = self.RADIUS self._circle.ellipse(center = (x, y), width = r, height = r) # get label size and move it so it is centered with circle w, h = self.get_size() x, y = x - w / 2, y - h / 2 self._name.set_pos((x, y)) self._name_bounds = (x, y, x + w, y + h) TextElement.on_update(self, affine) self.set_bounds((-1, -1, r + 1, r + 1)) #class CFlowItem(FlowItem): # """ # Abstract class for flows with activity edge connector. Flow with # activity edge connector references other one, which has activity edge # connector with same name (it is called opposite one). # # Such flows have active and inactive ends. Active end is connected to # any node and inactive end is connected only to activity edge connector. # """ # # def __init__(self, id = None): # FlowItem.__init__(self, id) # # self._connector = ACItem('value') # # factory = resource(UML.ElementFactory) # # self._opposite = None # # # when flow item with connector is deleted, then kill opposite, too # self.unlink_handler_id = self.connect('__unlink__', self.kill_opposite) # # # def kill_opposite(self, source, name): # # do not allow to be killed by opposite # self._opposite.disconnect(self._opposite.unlink_handler_id) # self._opposite.unlink() # # # def save(self, save_func): # """ # Save connector name and opposite flow with activity edge connector. # """ # FlowItem.save(self, save_func) # save_func('opposite', self._opposite, True) # save_func('connector-name', self._connector.subject.value) # # # def load(self, name, value): # """ # Load connector name and opposite flow with activity edge connector. # """ # if name == 'connector-name': # self._connector.subject.value = value # elif name == 'opposite': # self._opposite = value # else: # FlowItem.load(self, name, value) # # # def on_update(self, affine): # """ # Draw flow line and activity edge connector. # """ # # get parent line points to determine angle # # used to rotate position of activity edge connector # p1, p2 = self.get_line() # # # calculate position of connector center # r = self._connector.RADIUS # #x = p1[0] < p2[0] and r or -r # x = p1[0] < p2[0] and -r or r # y = 0 # x, y = rotate(p1, p2, x, y, p1[0], p1[1]) # # self._connector.move_center(x, y) # # FlowItem.on_update(self, affine) # # # def confirm_connect_handle(self, handle): # """See NamedLine.confirm_connect_handle(). # """ # c1 = self.get_active_handle().connected_to # source # c2 = self._opposite.get_active_handle().connected_to # target # # # set correct relationship between connected items; # # it should be (source, target) not (target, source); # # otherwise we are looking for non-existing or wrong relationship # if isinstance(self, CFlowItemB): # c1, c2 = c2, c1 # # self.connect_items(c1, c2) # self._opposite.set_subject(self.subject) # # # def allow_connect_handle(self, handle, connecting_to): # if handle == self.get_inactive_handle(): # return False # return FlowItem.allow_connect_handle(self, handle, connecting_to) # # # def confirm_disconnect_handle (self, handle, was_connected_to): # """See NamedLine.confirm_disconnect_handle(). # """ # c1 = self.get_active_handle().connected_to # source # c2 = self._opposite.get_active_handle().connected_to # target # self.disconnect_items(c1, c2, was_connected_to) # self._opposite.set_subject(None) # # # #class CFlowItemA(CFlowItem): # """ # * Is used for split flows, as is CFlowItemB * # # Flow with activity edge connector, which starts from node and points to # activity edge connector. # """ # def __init__(self, id): # CFlowItem.__init__(self, id) # self.create_guard() # # # def on_update(self, affine): # self.update_guard(affine) # CFlowItem.on_update(self, affine) # # # def get_line(self): # p1 = self.handles[-1].get_pos_i() # p2 = self.handles[-2].get_pos_i() # return p1, p2 # # # def get_active_handle(self): # """ # Return source handle as active one. # """ # return self.handles[0] # # # def get_inactive_handle(self): # """ # Return target handle as inactive one. # """ # return self.handles[-1] # # # #class CFlowItemB(CFlowItem): # """ # Flow with activity edge connector, which starts from activity edge # connector and points to a node. # """ # def __init__(self, id): # CFlowItem.__init__(self, id) # self.create_name() # # # def on_update(self, affine): # self.update_name(affine) # CFlowItem.on_update(self, affine) # # # def get_line(self): # p1 = self.handles[0].get_pos_i() # p2 = self.handles[1].get_pos_i() # return p1, p2 # # # def get_active_handle(self): # """ # Return target handle as active one. # """ # return self.handles[-1] # # # def get_inactive_handle(self): # """ # Return source handle as inactive one. # """ # return self.handles[0] # # #def move_collection(src, target, name): # """ # Copy collection from one object to another. # # src - source object # target - target object # name - name of attribute, which is collection to copy # """ # # first make of copy of collection, because assigning # # element to target collection moves this element # for flow in list(getattr(src, name)): # getattr(target, name).append(flow) # #def is_fd(node): # """ # Check if node is fork or decision node. # """ # return isinstance(node, (UML.ForkNode, UML.DecisionNode)) # # #def change_node_class(node): # """ # If UML constraints for fork, join, decision and merge nodes are not # met, then create new node depending on input node class, i.e. create # fork node from join node or merge node from decision node. # # If constraints are met, then return node itself. # """ # if is_fd(node) and len(node.incoming) > 1 \ # or not is_fd(node) and len(node.incoming) < 2: # # factory = resource(UML.ElementFactory) # cls = node_classes[node.__class__] # log.debug('creating %s' % cls) # nn = factory.create(cls) # move_collection(node, nn, 'incoming') # move_collection(node, nn, 'outgoing') # else: # nn = node # # assert nn is not None # # # we have to accept zero of outgoing edges in case of fork/descision # # nodes # assert is_fd(nn) and len(nn.incoming) <= 1 \ # or not is_fd(nn) and len(nn.incoming) >= 1, '%s' % nn # assert is_fd(nn) and len(nn.outgoing) >= 0 \ # or not is_fd(nn) and len(nn.outgoing) <= 1, '%s' % nn # return nn # # #def combine_nodes(node): # """ # Create fork/join (decision/merge) nodes combination as described in UML # specification. # """ # log.debug('combining nodes') # # cls = node_classes[node.__class__] # log.debug('creating %s' % cls) # factory = resource(UML.ElementFactory) # target = factory.create(cls) # # source = node # if is_fd(node): # source = target # move_collection(node, target, 'incoming') # # # create new fork node # cls = node_classes[target.__class__] # log.debug('creating %s' % cls) # target = factory.create(cls) # move_collection(node, target, 'outgoing') # else: # # fork node is created, referenced by target # move_collection(node, target, 'outgoing') # # assert not is_fd(source) # assert is_fd(target) # # # create flow # c1 = count_object_flows(source, 'incoming') # c2 = count_object_flows(target, 'outgoing') # # if c1 > 0 or c2 > 0: # flow = factory.create(UML.ControlFlow) # else: # flow = factory.create(UML.ObjectFlow) # flow.source = source # flow.target = target # # assert len(source.incoming) > 1 # assert len(source.outgoing) == 1 # # assert len(target.incoming) == 1 # assert len(target.outgoing) > 1 # # return source # # #def decombine_nodes(source): # """ # Create node depending on source argument which denotes combination of # fork/join (decision/merge) nodes as described in UML specification. # # Combination of nodes is destroyed. # """ # log.debug('decombining nodes') # flow = source.outgoing[0] # target = flow.target # # if len(source.incoming) < 2: # # create fork or decision # cls = target.__class__ # else: # # create join or merge # cls = source.__class__ # # factory = resource(UML.ElementFactory) # node = factory.create(cls) # # move_collection(source, node, 'incoming') # move_collection(target, node, 'outgoing') # # assert source != node # # # delete target and combining flow # # source should be deleted by caller # target.unlink() # flow.unlink() # # # return new node # return node #def determine_node_on_connect(el): # """ # Determine classes of nodes depending on amount of incoming # and outgoing edges. This method is called when flow is attached # to node. # # If there is more than one incoming edge and more than one # outgoing edge, then create two nodes and combine them with # flow as described in UML specification. # """ # subject = el.subject # if not isinstance(subject, tuple(node_classes.keys())): # return # # new_subject = subject # # if len(subject.incoming) > 1 and len(subject.outgoing) > 1: # new_subject = combine_nodes(subject) # el.props.combined = True # # else: # new_subject = change_node_class(subject) # # change_node_subject(el, new_subject) # # if el.props.combined: # check_combining_flow(el) #def determine_node_on_disconnect(el): # """ # Determine classes of nodes depending on amount of incoming # and outgoing edges. This method is called when flow is dettached # from node. # # If there are combined nodes and there is no need for them, then replace # combination with appropriate node (i.e. replace with fork node when # there are less than two incoming edges). This way data model is kept as # simple as possible. # """ # subject = el.subject # if not isinstance(subject, tuple(node_classes.keys())): # return # # new_subject = subject # # if el.props.combined: # cs = subject.outgoing[0].target # # decombine node when there is no more than one incoming # # and no more than one outgoing flow # if len(subject.incoming) < 2 or len(cs.outgoing) < 2: # new_subject = decombine_nodes(subject) # el.props.combined = False # else: # check_combining_flow(el) # # else: # new_subject = change_node_class(subject) # # change_node_subject(el, new_subject) #def change_node_subject(el, new_subject): # """ # Change element's subject if new subject is different than element's # subject. If subject is changed, then old subject is destroyed. # """ # subject = el.subject # if new_subject != subject: # log.debug('changing subject of ui node %s' % el) # el.set_subject(new_subject) # # log.debug('deleting node %s' % subject) # subject.unlink() #def create_flow(cls, flow): # """ # Create new flow of class cls. Flow data from flow argument are copied # to new created flow. Old flow is destroyed. # """ # factory = resource(UML.ElementFactory) # f = factory.create(cls) # f.source = flow.source # f.target = flow.target # flow.unlink() # return f #def count_object_flows(node, attr): # """ # Count incoming or outgoing object flows. # """ # return len(getattr(node, attr) # .select(lambda flow: isinstance(flow, UML.ObjectFlow))) # # #def check_combining_flow(el): # """ # Set object flow as combining flow when incoming or outgoing flow count # is greater than zero. Otherwise change combining flow to control flow. # """ # subject = el.subject # flow = subject.outgoing[0] # combining flow # combined = flow.target # combined node # # c1 = count_object_flows(subject, 'incoming') # c2 = count_object_flows(combined, 'outgoing') # # log.debug('combined incoming and outgoing object flow count: (%d, %d)' % (c1, c2)) # # if (c1 > 0 or c2 > 0) and isinstance(flow, UML.ControlFlow): # log.debug('changing combing flow to object flow') # create_flow(UML.ObjectFlow, flow) # elif c1 == 0 and c2 == 0 and isinstance(flow, UML.ObjectFlow): # log.debug('changing combing flow to control flow') # create_flow(UML.ControlFlow, flow) # #def create_connector_end(connector, role): # """ # Create Connector End, set role and attach created end to # connector. # """ # end = resource(UML.ElementFactory).create(UML.ConnectorEnd) # end.role = role # connector.end = end # assert end in role.end # return end # #def rotate(p1, p2, a, b, x, y): # """ # Rotate point (a, b) by angle, which is determined by line (p1, p2). # # Rotated point is moved by vector (x, y). # """ # try: # angle = atan((p1[1] - p2[1]) / (p1[0] - p2[0])) # except ZeroDivisionError: # da = p1[1] < p2[1] and 1.5 or -1.5 # angle = pi * da # # sin_angle = sin(angle) # cos_angle = cos(angle) # return (cos_angle * a - sin_angle * b + x, # sin_angle * a + cos_angle * b + y) # vim:sw=4:et:ai gaphor-0.17.2/gaphor/diagram/actions/partition.py000066400000000000000000000077121220151210700217660ustar00rootroot00000000000000""" Activity Partition item. TODO: partition can be resized only horizontally or vertically, therefore - define constraints for horizontal and vertical handles - reallocate handles in such way, so they clearly indicate horizontal or vertical size change """ from gaphor import UML from gaphor.diagram.nameditem import NamedItem class PartitionItem(NamedItem): __uml__ = UML.ActivityPartition __stereotype__ = { 'external': lambda self: self.subject and self.subject.isExternal, } __style__ = { 'min-size': (100, 300), 'line-width': 2.4, } DELTA = 30 def __init__(self, id=None): super(PartitionItem, self).__init__(id) self._toplevel = False self._bottom = False self._subpart = False self._hdmax = 0 # maximum subpartition header height def pre_update(self, context): super(PartitionItem, self).pre_update(context) # get subpartitions children = list(k for k in self.canvas.get_children(self) if isinstance(k, PartitionItem)) self._toplevel = self.canvas.get_parent(self) is None self._subpart = len(children) > 0 self._bottom = not self._toplevel and not self._subpart if self._toplevel: self._header_size = self._header_size[0], self.DELTA handles = self.handles() # toplevel partition controls the height # partitions at the very bottom control the width # middle partitions control nothing for h in handles: h.movable = False h.visible = False if self._bottom: h = handles[1] h.visible = h.movable = True if self._toplevel: h1, h2 = handles[2:4] h1.visible = h1.movable = True h2.visible = h2.movable = True if self._subpart: wsum = sum(sl.width for sl in children) self._hdmax = max(sl._header_size[1] for sl in children) # extend width of swimline due the children but keep the height # untouched self.width = wsum dp = 0 for sl in self.canvas.get_children(self): x, y = sl.matrix[4], sl.matrix[5] x = dp - x y = - y + self._header_size[1] + self._hdmax - sl._header_size[1] sl.matrix.translate(x, y) sl.height = sl.min_height = max(0, self.height - self._header_size[1]) dp += sl.width def draw(self, context): """ By default horizontal partition is drawn. It is open on right side (or bottom side when horizontal). """ cr = context.cairo cr.set_line_width(self.style.line_width) if self.subject and not self.subject.isDimension and self._toplevel: cr.move_to(0, 0) cr.line_to(self.width, 0) h = self._header_size[1] # draw outside lines if this item is toplevel partition if self._toplevel: cr.move_to(0, self.height) cr.line_to(0, h) cr.line_to(self.width, h) cr.line_to(self.width, self.height) super(PartitionItem, self).draw(context) if self._subpart: # header line for all subparitions hd = h + self._hdmax cr.move_to(0, hd) cr.line_to(self.width, hd) if self._subpart: # draw inside lines for all children but last one dp = 0 for sl in self.canvas.get_children(self)[:-1]: dp += sl.width cr.move_to(dp, h) cr.line_to(dp, self.height) cr.stroke() if context.hovered or context.dropzone: cr.save() cr.set_dash((1.0, 5.0), 0) cr.set_line_width(1.0) cr.rectangle(0, 0, self.width, self.height) self.highlight(context) cr.stroke() cr.restore() # vim:sw=4:et gaphor-0.17.2/gaphor/diagram/actions/tests/000077500000000000000000000000001220151210700205365ustar00rootroot00000000000000gaphor-0.17.2/gaphor/diagram/actions/tests/test_action.py000066400000000000000000000004511220151210700234240ustar00rootroot00000000000000""" Test actions. """ from gaphor import UML from gaphor.diagram.actions.action import ActionItem from gaphor.tests.testcase import TestCase class ActionTestCase(TestCase): def test_action(self): """Test creation of actions. """ self.create(ActionItem, UML.Action) gaphor-0.17.2/gaphor/diagram/actions/tests/test_flow.py000066400000000000000000000020221220151210700231120ustar00rootroot00000000000000import gaphor.UML as UML from gaphor.diagram import items from gaphor.tests.testcase import TestCase class FlowTestCase(TestCase): def test_flow(self): self.create(items.FlowItem, UML.ControlFlow) def test_name(self): """ Test updating of flow name text. """ flow = self.create(items.FlowItem, UML.ControlFlow) flow.subject.name = 'Blah' self.assertEquals('Blah', flow._name.text) flow.subject = None self.assertEquals('', flow._name.text) def test_guard(self): """ Test updating of flow guard text. """ flow = self.create(items.FlowItem, UML.ControlFlow) self.assertEquals('', flow._guard.text) flow.subject.guard = 'GuardMe' self.assertEquals('GuardMe', flow._guard.text) flow.subject = None self.assertEquals('', flow._guard.text) def test_persistence(self): """ TODO: Test connector item saving/loading """ pass # vim:sw=4:et:ai gaphor-0.17.2/gaphor/diagram/activitynodes.py000066400000000000000000000230551220151210700212000ustar00rootroot00000000000000""" Activity control nodes. """ import math from gaphas.util import path_ellipse from gaphas.state import observed, reversible_property from gaphas.item import Handle, Item, LinePort from gaphas.constraint import EqualsConstraint, LessThanConstraint from gaphas.geometry import distance_line_point from gaphor import UML from gaphor.core import inject from gaphor.diagram.diagramitem import DiagramItem from gaphor.diagram.nameditem import NamedItem from gaphor.diagram.style import ALIGN_LEFT, ALIGN_CENTER, ALIGN_TOP, \ ALIGN_RIGHT, ALIGN_BOTTOM from gaphor.diagram.style import get_text_point DEFAULT_JOIN_SPEC = 'and' class ActivityNodeItem(NamedItem): """Basic class for simple activity nodes. Simple activity node is not resizable. """ __style__ = { 'name-outside': True, 'name-padding': (2, 2, 2, 2), } def __init__(self, id=None): NamedItem.__init__(self, id) # Do not allow resizing of the node for h in self._handles: h.movable = False class InitialNodeItem(ActivityNodeItem): """ Representation of initial node. Initial node has name which is put near top-left side of node. """ __uml__ = UML.InitialNode __style__ = { 'min-size': (20, 20), 'name-align': (ALIGN_LEFT, ALIGN_TOP), } RADIUS = 10 def draw(self, context): cr = context.cairo r = self.RADIUS d = r * 2 path_ellipse(cr, r, r, d, d) cr.set_line_width(0.01) cr.fill() super(InitialNodeItem, self).draw(context) class ActivityFinalNodeItem(ActivityNodeItem): """Representation of activity final node. Activity final node has name which is put near right-bottom side of node. """ __uml__ = UML.ActivityFinalNode __style__ = { 'min-size': (30, 30), 'name-align': (ALIGN_RIGHT, ALIGN_BOTTOM), } RADIUS_1 = 10 RADIUS_2 = 15 def draw(self, context): cr = context.cairo r = self.RADIUS_2 + 1 d = self.RADIUS_1 * 2 path_ellipse(cr, r, r, d, d) cr.set_line_width(0.01) cr.fill() d = r * 2 path_ellipse(cr, r, r, d, d) cr.set_line_width(0.01) cr.set_line_width(2) cr.stroke() super(ActivityFinalNodeItem, self).draw(context) class FlowFinalNodeItem(ActivityNodeItem): """ Representation of flow final node. Flow final node has name which is put near right-bottom side of node. """ __uml__ = UML.FlowFinalNode __style__ = { 'min-size': (20, 20), 'name-align': (ALIGN_RIGHT, ALIGN_BOTTOM), } RADIUS = 10 def draw(self, context): cr = context.cairo r = self.RADIUS d = r * 2 path_ellipse(cr, r, r, d, d) cr.stroke() dr = (1 - math.sin(math.pi / 4)) * r cr.move_to(dr, dr) cr.line_to(d - dr, d - dr) cr.move_to(dr, d - dr) cr.line_to(d - dr, dr) cr.stroke() super(FlowFinalNodeItem, self).draw(context) class DecisionNodeItem(ActivityNodeItem): """ Representation of decision or merge node. """ __uml__ = UML.DecisionNode __style__ = { 'min-size': (20, 30), 'name-align': (ALIGN_LEFT, ALIGN_TOP), } RADIUS = 15 def __init__(self, id=None): ActivityNodeItem.__init__(self, id) self._combined = None #self.set_prop_persistent('combined') def save(self, save_func): if self._combined: save_func('combined', self._combined, reference=True) super(DecisionNodeItem, self).save(save_func) def load(self, name, value): if name == 'combined': self._combined = value else: super(DecisionNodeItem, self).load(name, value) @observed def _set_combined(self, value): #self.preserve_property('combined') self._combined = value combined = reversible_property(lambda s: s._combined, _set_combined) def draw(self, context): """ Draw diamond shape, which represents decision and merge nodes. """ cr = context.cairo r = self.RADIUS r2 = r * 2/3 cr.move_to(r2, 0) cr.line_to(r2 * 2, r) cr.line_to(r2, r * 2) cr.line_to(0, r) cr.close_path() cr.stroke() super(DecisionNodeItem, self).draw(context) class ForkNodeItem(Item, DiagramItem): """ Representation of fork and join node. """ element_factory = inject('element_factory') __uml__ = UML.ForkNode __style__ = { 'min-size': (6, 45), 'name-align': (ALIGN_CENTER, ALIGN_BOTTOM), 'name-padding': (2, 2, 2, 2), 'name-outside': True, 'name-align-str': None, } STYLE_TOP = { 'text-align': (ALIGN_CENTER, ALIGN_TOP), 'text-outside': True, } def __init__(self, id=None): Item.__init__(self) DiagramItem.__init__(self, id) h1, h2 = Handle(), Handle() self._handles.append(h1) self._handles.append(h2) self._ports.append(LinePort(h1.pos, h2.pos)) self._combined = None self._join_spec = self.add_text('joinSpec', pattern='{ joinSpec = %s }', style=self.STYLE_TOP, visible=self.is_join_spec_visible) self._name = self.add_text('name', style={ 'text-align': self.style.name_align, 'text-padding': self.style.name_padding, 'text-outside': self.style.name_outside, 'text-align-str': self.style.name_align_str, 'text-align-group': 'stereotype', }, editable=True) self.watch('subject.name', self.on_named_element_name)\ .watch('subject.joinSpec', self.on_join_node_join_spec) def save(self, save_func): save_func('matrix', tuple(self.matrix)) save_func('height', float(self._handles[1].pos.y)) if self._combined: save_func('combined', self._combined, reference=True) DiagramItem.save(self, save_func) def load(self, name, value): if name == 'matrix': self.matrix = eval(value) elif name == 'height': self._handles[1].pos.y = eval(value) elif name == 'combined': self._combined = value else: #DiagramItem.load(self, name, value) super(ForkNodeItem, self).load(name, value) def postload(self): subject = self.subject if subject and isinstance(subject, UML.JoinNode) and subject.joinSpec: self._join_spec.text = self.subject.joinSpec self.on_named_element_name(None) super(ForkNodeItem, self).postload() @observed def _set_combined(self, value): #self.preserve_property('combined') self._combined = value combined = reversible_property(lambda s: s._combined, _set_combined) def setup_canvas(self): super(ForkNodeItem, self).setup_canvas() self.register_handlers() h1, h2 = self._handles cadd = self.canvas.solver.add_constraint c1 = EqualsConstraint(a=h1.pos.x, b=h2.pos.x) c2 = LessThanConstraint(smaller=h1.pos.y, bigger=h2.pos.y, delta=30) self.__constraints = (cadd(c1), cadd(c2)) map(self.canvas.solver.add_constraint, self.__constraints) def teardown_canvas(self): super(ForkNodeItem, self).teardown_canvas() map(self.canvas.solver.remove_constraint, self.__constraints) self.unregister_handlers() def is_join_spec_visible(self): """ Check if join specification should be displayed. """ return isinstance(self.subject, UML.JoinNode) \ and self.subject.joinSpec is not None \ and self.subject.joinSpec != DEFAULT_JOIN_SPEC def text_align(self, extents, align, padding, outside): h1, h2 = self._handles w, _ = self.style.min_size h = h2.pos.y - h1.pos.y x, y = get_text_point(extents, w, h, align, padding, outside) return x, y def pre_update(self, context): self.update_stereotype() Item.pre_update(self, context) DiagramItem.pre_update(self, context) def post_update(self, context): Item.post_update(self, context) DiagramItem.post_update(self, context) def draw(self, context): """ Draw vertical line - symbol of fork and join nodes. Join specification is also drawn above the item. """ Item.draw(self, context) DiagramItem.draw(self, context) cr = context.cairo cr.set_line_width(6) h1, h2 = self._handles cr.move_to(h1.pos.x, h1.pos.y) cr.line_to(h2.pos.x, h2.pos.y) cr.stroke() def point(self, pos): h1, h2 = self._handles d, p = distance_line_point(h1.pos, h2.pos, pos) # Substract line_width / 2 return d - 3 def on_named_element_name(self, event): print 'on_named_element_name', self.subject subject = self.subject if subject: self._name.text = subject.name self.request_update() def on_join_node_join_spec(self, event): subject = self.subject if subject: self._join_spec.text = subject.joinSpec or DEFAULT_JOIN_SPEC self.request_update() def is_join_node(subject): """ Check if ``subject`` is join node. """ return subject and isinstance(subject, UML.JoinNode) # vim:sw=4:et gaphor-0.17.2/gaphor/diagram/actor.py000066400000000000000000000032031220151210700174140ustar00rootroot00000000000000""" Actor item classes. """ from math import pi from gaphor import UML from gaphor.diagram.style import ALIGN_CENTER, ALIGN_BOTTOM from gaphor.diagram.classifier import ClassifierItem class ActorItem(ClassifierItem): """ Actor item is a classifier in icon mode. Maybe it should be possible to switch to comparment mode in the future. """ __uml__ = UML.Actor HEAD = 11 ARM = 19 NECK = 10 BODY = 20 __style__ = { 'min-size': (ARM * 2, HEAD + NECK + BODY + ARM), 'name-align': (ALIGN_CENTER, ALIGN_BOTTOM), 'name-padding': (5, 0, 5, 0), 'name-outside': True, } def __init__(self, id = None): ClassifierItem.__init__(self, id) self.drawing_style = self.DRAW_ICON def draw_icon(self, context): """ Draw actor's icon creature. """ super(ActorItem, self).draw(context) cr = context.cairo head, neck, arm, body = self.HEAD, self.NECK, self.ARM, self.BODY fx = self.width / (arm * 2); fy = self.height / (head + neck + body + arm) x = arm * fx y = (head / 2) * fy cy = head * fy cr.move_to(x + head * fy / 2.0, y) cr.arc(x, y, head * fy / 2.0, 0, 2 * pi) cr.move_to(x, y + cy / 2) cr.line_to(arm * fx, (head + neck + body) * fy) cr.move_to(0, (head + neck) * fy) cr.line_to(arm * 2 * fx, (head + neck) * fy) cr.move_to(0, (head + neck + body + arm) * fy) cr.line_to(arm * fx, (head + neck + body) * fy) cr.line_to(arm * 2 * fx, (head + neck + body + arm) * fy) cr.stroke() # vim:sw=4:et gaphor-0.17.2/gaphor/diagram/artifact.py000066400000000000000000000027311220151210700201060ustar00rootroot00000000000000""" Artifact item. """ from gaphor import UML from gaphor.diagram.classifier import ClassifierItem class ArtifactItem(ClassifierItem): __uml__ = UML.Artifact __icon__ = True __style__ = { 'name-padding': (10, 25, 10, 10), } ICON_HEIGHT = 20 def __init__(self, id=None): ClassifierItem.__init__(self, id) self.height = 50 self.width = 120 # Set drawing style to compartment w/ small icon self.drawing_style = self.DRAW_COMPARTMENT_ICON self._line = [] def pre_update_compartment_icon(self, context): super(ArtifactItem, self).pre_update_compartment_icon(context) w = self.ICON_WIDTH h = self.ICON_HEIGHT ix, iy = self.get_icon_pos() ear = 5 self._line = ( (ix + w - ear, iy + ear), (ix + w, iy + ear), (ix + w - ear, iy), (ix, iy), (ix, iy + h), (ix + w, iy + h), (ix + w, iy + ear)) def draw_compartment_icon(self, context): cr = context.cairo cr.save() self.draw_compartment(context) cr.restore() # draw icon w = self.ICON_WIDTH h = self.ICON_HEIGHT ix, iy = self.get_icon_pos() ear = 5 cr.set_line_width(1.0) cr.move_to(ix + w - ear, iy) for x, y in self._line: cr.line_to(x, y) cr.stroke() # vim:sw=4:et gaphor-0.17.2/gaphor/diagram/classes/000077500000000000000000000000001220151210700173715ustar00rootroot00000000000000gaphor-0.17.2/gaphor/diagram/classes/__init__.py000066400000000000000000000000001220151210700214700ustar00rootroot00000000000000gaphor-0.17.2/gaphor/diagram/classes/association.py000066400000000000000000000440531220151210700222650ustar00rootroot00000000000000""" Association item - graphical representation of an association. Plan: - transform AssociationEnd in a (dumb) data class - for assocation name and direction tag, use the same trick as is used for line ends. """ # TODO: for Association.postload(): in some cases where the association ends # are connected to the same Class, the head_end property is connected to the # tail end and visa versa. from gaphor.diagram.textelement import text_extents, text_multiline from gaphas.state import reversible_property from gaphas import Item from gaphas.geometry import Rectangle, distance_point_point_fast from gaphas.geometry import distance_rectangle_point, distance_line_point from gaphor import UML from gaphor.diagram.diagramline import NamedLine class AssociationItem(NamedLine): """ AssociationItem represents associations. An AssociationItem has two AssociationEnd items. Each AssociationEnd item represents a Property (with Property.association == my association). """ __uml__ = UML.Association def __init__(self, id=None): NamedLine.__init__(self, id) # AssociationEnds are really inseperable from the AssociationItem. # We give them the same id as the association item. self._head_end = AssociationEnd(owner=self, end="head") self._tail_end = AssociationEnd(owner=self, end="tail") # Direction depends on the ends that hold the ownedEnd attributes. self._show_direction = False self._dir_angle = 0 self._dir_pos = 0, 0 #self.watch('subject.ownedEnd')\ #.watch('subject.memberEnd') # For the association ends: base = 'subject.memberEnd.' self.watch(base + 'name', self.on_association_end_value)\ .watch(base + 'aggregation', self.on_association_end_value)\ .watch(base + 'classifier', self.on_association_end_value)\ .watch(base + 'visibility', self.on_association_end_value)\ .watch(base + 'lowerValue', self.on_association_end_value)\ .watch(base + 'upperValue', self.on_association_end_value)\ .watch(base + 'owningAssociation', self.on_association_end_value) \ .watch(base + 'type.ownedAttribute', self.on_association_end_value) \ .watch(base + 'type.ownedAttribute', self.on_association_end_value) \ .watch('subject.ownedEnd') \ .watch('subject.navigableOwnedEnd') def set_show_direction(self, dir): self._show_direction = dir self.request_update() show_direction = reversible_property(lambda s: s._show_direction, set_show_direction) def setup_canvas(self): super(AssociationItem, self).setup_canvas() def teardown_canvas(self): super(AssociationItem, self).teardown_canvas() def save(self, save_func): NamedLine.save(self, save_func) save_func('show-direction', self._show_direction) if self._head_end.subject: save_func('head-subject', self._head_end.subject) if self._tail_end.subject: save_func('tail-subject', self._tail_end.subject) def load(self, name, value): # end_head and end_tail were used in an older Gaphor version if name in ( 'head_end', 'head_subject', 'head-subject' ): #type(self._head_end).subject.load(self._head_end, value) #self._head_end.load('subject', value) self._head_end.subject = value elif name in ( 'tail_end', 'tail_subject', 'tail-subject' ): #type(self._tail_end).subject.load(self._tail_end, value) #self._tail_end.load('subject', value) self._tail_end.subject = value else: NamedLine.load(self, name, value) def postload(self): NamedLine.postload(self) self._head_end.set_text() self._tail_end.set_text() head_end = property(lambda self: self._head_end) tail_end = property(lambda self: self._tail_end) def unlink(self): self._head_end.unlink() self._tail_end.unlink() super(AssociationItem, self).unlink() def invert_direction(self): """ Invert the direction of the association, this is done by swapping the head and tail-ends subjects. """ if not self.subject: return self.subject.memberEnd.swap(self.subject.memberEnd[0], self.subject.memberEnd[1]) self.request_update() def on_named_element_name(self, event): """ Update names of the association as well as its ends. Override NamedLine.on_named_element_name. """ if event is None: super(AssociationItem, self).on_named_element_name(event) self.on_association_end_value(event) elif event.element is self.subject: super(AssociationItem, self).on_named_element_name(event) else: self.on_association_end_value(event) def on_association_end_value(self, event): """ Handle events and update text on association end. """ #if event: # element = event.element # for end in (self._head_end, self._tail_end): # subject = end.subject # if subject and element in (subject, subject.lowerValue, \ # subject.upperValue, subject.taggedValue): # end.set_text() # self.request_update() ## break; #else: for end in (self._head_end, self._tail_end): end.set_text() self.request_update() def post_update(self, context): """ Update the shapes and sub-items of the association. """ handles = self.handles() # Update line endings: head_subject = self._head_end.subject tail_subject = self._tail_end.subject # Update line ends using the aggregation and isNavigable values: if head_subject and tail_subject: if tail_subject.aggregation == intern('composite'): self.draw_head = self.draw_head_composite elif tail_subject.aggregation == intern('shared'): self.draw_head = self.draw_head_shared elif self._head_end.subject.navigability is True: self.draw_head = self.draw_head_navigable elif self._head_end.subject.navigability is False: self.draw_head = self.draw_head_none else: self.draw_head = self.draw_head_undefined if head_subject.aggregation == intern('composite'): self.draw_tail = self.draw_tail_composite elif head_subject.aggregation == intern('shared'): self.draw_tail = self.draw_tail_shared elif self._tail_end.subject.navigability is True: self.draw_tail = self.draw_tail_navigable elif self._tail_end.subject.navigability is False: self.draw_tail = self.draw_tail_none else: self.draw_tail = self.draw_tail_undefined if self._show_direction: inverted = self.tail_end.subject is self.subject.memberEnd[0] pos, angle = self._get_center_pos(inverted) self._dir_pos = pos self._dir_angle = angle else: self.draw_head = self.draw_head_undefined self.draw_tail = self.draw_tail_undefined # update relationship after self.set calls to avoid circural updates super(AssociationItem, self).post_update(context) # Calculate alignment of the head name and multiplicity self._head_end.post_update(context, handles[0].pos, handles[1].pos) # Calculate alignment of the tail name and multiplicity self._tail_end.post_update(context, handles[-1].pos, handles[-2].pos) def point(self, pos): """ Returns the distance from the Association to the (mouse) cursor. """ return min(super(AssociationItem, self).point(pos), self._head_end.point(pos), self._tail_end.point(pos)) def draw_head_none(self, context): """ Draw an 'x' on the line end to indicate no navigability at association head. """ cr = context.cairo cr.move_to(6, -4) cr.rel_line_to(8, 8) cr.rel_move_to(0, -8) cr.rel_line_to(-8, 8) cr.stroke() cr.move_to(0, 0) def draw_tail_none(self, context): """ Draw an 'x' on the line end to indicate no navigability at association tail. """ cr = context.cairo cr.line_to(0, 0) cr.move_to(6, -4) cr.rel_line_to(8, 8) cr.rel_move_to(0, -8) cr.rel_line_to(-8, 8) cr.stroke() def _draw_diamond(self, cr): """ Helper function to draw diamond shape for shared and composite aggregations. """ cr.move_to(20, 0) cr.line_to(10, -6) cr.line_to(0, 0) cr.line_to(10, 6) #cr.line_to(20, 0) cr.close_path() def draw_head_composite(self, context): """ Draw a closed diamond on the line end to indicate composite aggregation at association head. """ cr = context.cairo self._draw_diamond(cr) context.cairo.fill_preserve() cr.stroke() cr.move_to(20, 0) def draw_tail_composite(self, context): """ Draw a closed diamond on the line end to indicate composite aggregation at association tail. """ cr = context.cairo cr.line_to(20, 0) cr.stroke() self._draw_diamond(cr) cr.fill_preserve() cr.stroke() def draw_head_shared(self, context): """ Draw an open diamond on the line end to indicate shared aggregation at association head. """ cr = context.cairo self._draw_diamond(cr) cr.move_to(20, 0) def draw_tail_shared(self, context): """ Draw an open diamond on the line end to indicate shared aggregation at association tail. """ cr = context.cairo cr.line_to(20, 0) cr.stroke() self._draw_diamond(cr) cr.stroke() def draw_head_navigable(self, context): """ Draw a normal arrow to indicate association end navigability at association head. """ cr = context.cairo cr.move_to(15, -6) cr.line_to(0, 0) cr.line_to(15, 6) cr.stroke() cr.move_to(0, 0) def draw_tail_navigable(self, context): """ Draw a normal arrow to indicate association end navigability at association tail. """ cr = context.cairo cr.line_to(0, 0) cr.stroke() cr.move_to(15, -6) cr.line_to(0, 0) cr.line_to(15, 6) def draw_head_undefined(self, context): """ Draw nothing to indicate undefined association end at association head. """ context.cairo.move_to(0, 0) def draw_tail_undefined(self, context): """ Draw nothing to indicate undefined association end at association tail. """ context.cairo.line_to(0, 0) def draw(self, context): super(AssociationItem, self).draw(context) cr = context.cairo self._head_end.draw(context) self._tail_end.draw(context) if self._show_direction: cr.save() try: cr.translate(*self._dir_pos) cr.rotate(self._dir_angle) cr.move_to(0, 0) cr.line_to(6, 5) cr.line_to(0, 10) cr.fill() finally: cr.restore() def item_at(self, x, y): if distance_point_point_fast(self._handles[0].pos, (x, y)) < 10: return self._head_end elif distance_point_point_fast(self._handles[-1].pos, (x, y)) < 10: return self._tail_end return self class AssociationEnd(UML.Presentation): """ An association end represents one end of an association. An association has two ends. An association end has two labels: one for the name and one for the multiplicity (and maybe one for tagged values in the future). An AsociationEnd has no ID, hence it will not be stored, but it will be recreated by the owning Association. TODO: - add on_point() and let it return min(distance(_name), distance(_mult)) or the first 20-30 units of the line, for association end popup menu. """ def __init__(self, owner, id=None, end=None): UML.Presentation.__init__(self, id=False) # Transient object self._owner = owner self._end = end # Rendered text for name and multiplicity self._name = None self._mult = None self._name_bounds = Rectangle() self._mult_bounds = Rectangle() self.font = 'sans 10' def request_update(self): self._owner.request_update() def set_text(self): """ Set the text on the association end. """ if self.subject: try: n, m = UML.format(self.subject) except ValueError: # need more than 0 values to unpack: property was rendered as # attribute while in a UNDO action for example. pass else: self._name = n self._mult = m self.request_update() def point_name(self, pos): drp = distance_rectangle_point return drp(self._name_bounds, pos) def point_mult(self, pos): drp = distance_rectangle_point return drp(self._mult_bounds, pos) def point(self, pos): return min(self.point_name(pos), self.point_mult(pos)) def get_name(self): return self._name def get_mult(self): return self._mult def post_update(self, context, p1, p2): """ Update label placement for association's name and multiplicity label. p1 is the line end and p2 is the last but one point of the line. """ cr = context.cairo ofs = 5 name_dx = 0.0 name_dy = 0.0 mult_dx = 0.0 mult_dy = 0.0 dx = float(p2[0]) - float(p1[0]) dy = float(p2[1]) - float(p1[1]) name_w, name_h = map(max, text_extents(cr, self._name, self.font), (10, 10)) mult_w, mult_h = map(max, text_extents(cr, self._mult, self.font), (10, 10)) if dy == 0: rc = 1000.0 # quite a lot... else: rc = dx / dy abs_rc = abs(rc) h = dx > 0 # right side of the box v = dy > 0 # bottom side if abs_rc > 6: # horizontal line if h: name_dx = ofs name_dy = -ofs - name_h mult_dx = ofs mult_dy = ofs else: name_dx = -ofs - name_w name_dy = -ofs - name_h mult_dx = -ofs - mult_w mult_dy = ofs elif 0 <= abs_rc <= 0.2: # vertical line if v: name_dx = -ofs - name_w name_dy = ofs mult_dx = ofs mult_dy = ofs else: name_dx = -ofs - name_w name_dy = -ofs - name_h mult_dx = ofs mult_dy = -ofs - mult_h else: # Should both items be placed on the same side of the line? r = abs_rc < 1.0 # Find out alignment of text (depends on the direction of the line) align_left = (h and not r) or (r and not h) align_bottom = (v and not r) or (r and not v) if align_left: name_dx = ofs mult_dx = ofs else: name_dx = -ofs - name_w mult_dx = -ofs - mult_w if align_bottom: name_dy = -ofs - name_h mult_dy = -ofs - name_h - mult_h else: name_dy = ofs mult_dy = ofs + mult_h self._name_bounds = Rectangle(p1[0] + name_dx, p1[1] + name_dy, width=name_w, height=name_h) self._mult_bounds = Rectangle(p1[0] + mult_dx, p1[1] + mult_dy, width=mult_w, height=mult_h) def point(self, pos): """Given a point (x, y) return the distance to the canvas item. """ drp = distance_rectangle_point d1 = drp(self._name_bounds, pos) d2 = drp(self._mult_bounds, pos) # try: # d3 = geometry.distance_point_point(self._point1, pos) # d4, dummy = distance_line_point(self._point1, self._point2, pos, 1.0, 0) #diacanvas.shape.CAP_ROUND) # if d3 < 15 and d4 < 5: # d3 = 0.0 # except Exception, e: # log.error("Could not determine distance", exc_info=True) d3 = 1000.0 return min(d1, d2, d3) def draw(self, context): """Draw name and multiplicity of the line end. """ if not self.subject: return cr = context.cairo text_multiline(cr, self._name_bounds[0], self._name_bounds[1], self._name, self.font) text_multiline(cr, self._mult_bounds[0], self._mult_bounds[1], self._mult, self.font) cr.stroke() if context.hovered or context.focused or context.draw_all: cr.set_line_width(0.5) b = self._name_bounds cr.rectangle(b.x, b.y, b.width, b.height) cr.stroke() b = self._mult_bounds cr.rectangle(b.x, b.y, b.width, b.height) cr.stroke() # vim:sw=4:et gaphor-0.17.2/gaphor/diagram/classes/dependency.py000066400000000000000000000056271220151210700220730ustar00rootroot00000000000000""" Common dependencies like dependency, usage, implementation and realization. Dependency Type =============== Dependency type should be determined automatically by default. User should be able to override the dependency type. When dependency item is connected between two items, then type of the dependency cannot be changed. For example, if two class items are connected, then dependency type cannot be changed to realization as this dependency type can only exist between a component and a classifier. Function dependency_type in model factory should be used to determine type of a dependency in automatic way. """ from gaphor import UML from gaphor.diagram.diagramline import DiagramLine class DependencyItem(DiagramLine): """ Dependency item represents several types of dependencies, i.e. normal dependency or usage. Usually a dependency looks like a dashed line with an arrow head. The dependency can have a stereotype attached to it, stating the kind of dependency we're dealing with. In case of usage dependency connected to folded interface, the line is drawn as solid line without arrow head. """ __uml__ = UML.Dependency # do not use issubclass, because issubclass(UML.Implementation, UML.Realization) # we need to be very strict here __stereotype__ = { 'use': lambda self: self._dependency_type == UML.Usage, 'realize': lambda self: self._dependency_type == UML.Realization, 'implements': lambda self: self._dependency_type == UML.Implementation, } def __init__(self, id=None): DiagramLine.__init__(self, id) self._dependency_type = UML.Dependency self.auto_dependency = True self._solid = False def save(self, save_func): DiagramLine.save(self, save_func) save_func('auto_dependency', self.auto_dependency) def load(self, name, value): if name == 'auto_dependency': self.auto_dependency = eval(value) else: DiagramLine.load(self, name, value) def postload(self): if self.subject: dependency_type = self.subject.__class__ DiagramLine.postload(self) self._dependency_type = dependency_type else: DiagramLine.postload(self) def set_dependency_type(self, dependency_type): self._dependency_type = dependency_type dependency_type = property(lambda s: s._dependency_type, set_dependency_type) def draw_head(self, context): cr = context.cairo if not self._solid: cr.set_dash((), 0) cr.move_to(15, -6) cr.line_to(0, 0) cr.line_to(15, 6) cr.stroke() cr.move_to(0, 0) def draw(self, context): if not self._solid: context.cairo.set_dash((7.0, 5.0), 0) super(DependencyItem, self).draw(context) # vim:sw=4:et gaphor-0.17.2/gaphor/diagram/classes/generalization.py000066400000000000000000000010741220151210700227600ustar00rootroot00000000000000""" Generalization -- """ import gobject from gaphor import UML from gaphor.diagram.diagramline import DiagramLine class GeneralizationItem(DiagramLine): __uml__ = UML.Generalization __relationship__ = 'general', None, 'specific', 'generalization' def __init__(self, id=None): DiagramLine.__init__(self, id) def draw_head(self, context): cr = context.cairo cr.move_to(0, 0) cr.line_to(15, -10) cr.line_to(15, 10) cr.close_path() cr.stroke() cr.move_to(15, 0) # vim:sw=4:et:ai gaphor-0.17.2/gaphor/diagram/classes/implementation.py000066400000000000000000000014001220151210700227630ustar00rootroot00000000000000""" Implementation of interface. """ from gaphor import UML from gaphor.diagram.diagramline import DiagramLine class ImplementationItem(DiagramLine): __uml__ = UML.Implementation def __init__(self, id = None): DiagramLine.__init__(self, id) self._solid = False def draw_head(self, context): cr = context.cairo cr.move_to(0, 0) if not self._solid: cr.set_dash((), 0) cr.line_to(15, -10) cr.line_to(15, 10) cr.close_path() cr.stroke() cr.move_to(15, 0) def draw(self, context): if not self._solid: context.cairo.set_dash((7.0, 5.0), 0) super(ImplementationItem, self).draw(context) # vim:sw=4 gaphor-0.17.2/gaphor/diagram/classes/interface.py000066400000000000000000000222161220151210700217060ustar00rootroot00000000000000""" Interface item implementation. There are several notations supported - class box with interface stereotype - folded interface - ball is drawn to indicate provided interface - socket is drawn to indicate required interface Interface item can act as icon of assembly connector, see `gaphor.diagram.connector` module documentation for details. *Documentation of this module does not take into accout assembly connector icon mode.* Folded Interface Item ===================== Folded interface notation is reserved for very simple situations. When interface is folded - only an implementation can be connected (ball - provided interface) - or only usage dependency can be connected (socket - required interface) Above means that interface cannot be folded when - both, usage dependency and implementation are connected - any other lines are connected Dependencies ------------ Dependencies between folded interfaces are *not supported* +---------------------+---------------------+ | *Supported* | *Unsupported* | +=====================+=====================+ | :: | :: | | | | | |A|--( O--|B| | |A|--(--->O--|B| | | Z Z | Z Z | +---------------------+---------------------+ On above diagram, A requires interface Z and B provides interface Z. Additionally, on the right diagram, Z is connected to itself with dependency. There is no need for additional dependency - UML data model provides information, that Z is common for A and B (A requires Z, B provides Z) - on a diagram, both folded interface items (required and provided) represent the same interface, which is easily identifiable with its name Even more, adding a dependency between folded interfaces provides information, on UML data model level, that an interface depenends on itself but it is not the intention of this (*unsupported*) notation. For more examples of non-supported by Gaphor notation, see http://martinfowler.com/bliki/BallAndSocket.html. Folding and Connecting ---------------------- Current approach to folding and connecting lines to an interface is as follows - allow folding/unfolding of an interface only when there is only one implementation or depenedency usage connected - when interface is folded, allow only one implementation or depenedency usage to be connected Folding and unfolding is performed by `InterfacePropertyPage` class. """ from math import pi from gaphas.state import observed, reversible_property from gaphas.item import NW, NE, SE, SW from gaphas.connector import LinePort from gaphas.geometry import distance_line_point, distance_point_point from gaphor import UML from klass import ClassItem from gaphor.diagram.nameditem import NamedItem from gaphor.diagram.style import ALIGN_TOP, ALIGN_BOTTOM, ALIGN_CENTER class InterfacePort(LinePort): """ Interface connection port. It is simple line port, which changes glue behaviour depending on interface folded state. If interface is folded, then `InterfacePort.glue` method suggests connection in the middle of the port. The port provides rotation angle information as well. Rotation angle is direction the port is facing (i.e. 0 is north, PI/2 is west, etc.). The rotation angle shall be used to determine rotation of required interface notation (socket's arc is in the same direction as the angle). :IVariables: angle Rotation angle. iface Interface owning port. """ def __init__(self, start, end, iface, angle): super(InterfacePort, self).__init__(start, end) self.angle = angle self.iface = iface self.required = False self.provided = False def glue(self, pos): """ Behaves like simple line port, but for folded interface suggests connection to the middle point of a port. """ if self.iface.folded: px = (self.start.x + self.end.x) / 2 py = (self.start.y + self.end.y) / 2 d = distance_point_point((px, py), pos) return (px, py), d else: p1 = self.start p2 = self.end d, pl = distance_line_point(p1, p2, pos) return pl, d class InterfaceItem(ClassItem): """ Interface item supporting class box, folded notations and assembly connector icon mode. When in folded mode, provided (ball) notation is used by default. """ __uml__ = UML.Interface __stereotype__ = {'interface': lambda self: self.drawing_style != self.DRAW_ICON} __style__ = { 'icon-size': (20, 20), 'icon-size-provided': (20, 20), 'icon-size-required': (28, 28), 'name-outside': False, } UNFOLDED_STYLE = { 'text-align': (ALIGN_CENTER, ALIGN_TOP), 'text-outside': False, } FOLDED_STYLE = { 'text-align': (ALIGN_CENTER, ALIGN_BOTTOM), 'text-outside': True, } RADIUS_PROVIDED = 10 RADIUS_REQUIRED = 14 # Non-folded mode. FOLDED_NONE = 0 # Folded mode, provided (ball) notation. FOLDED_PROVIDED = 1 # Folded mode, required (socket) notation. FOLDED_REQUIRED = 2 # Folded mode, notation of assembly connector icon mode (ball&socket). FOLDED_ASSEMBLY = 3 def __init__(self, id=None): ClassItem.__init__(self, id) self._folded = self.FOLDED_NONE self._angle = 0 old_f = self._name.is_visible self._name.is_visible = lambda: old_f() and self._folded != self.FOLDED_ASSEMBLY handles = self._handles h_nw = handles[NW] h_ne = handles[NE] h_sw = handles[SW] h_se = handles[SE] # edge of element define default element ports self._ports = [ InterfacePort(h_nw.pos, h_ne.pos, self, 0), InterfacePort(h_ne.pos, h_se.pos, self, pi / 2), InterfacePort(h_se.pos, h_sw.pos, self, pi), InterfacePort(h_sw.pos, h_nw.pos, self, pi * 1.5) ] self.watch('subject.ownedAttribute', self.on_class_owned_attribute) \ .watch('subject.ownedOperation', self.on_class_owned_operation) \ .watch('subject.supplierDependency') @observed def set_drawing_style(self, style): """ In addition to setting the drawing style, the handles are make non-movable if the icon (folded) style is used. """ super(InterfaceItem, self).set_drawing_style(style) if self._drawing_style == self.DRAW_ICON: self.folded = self.FOLDED_PROVIDED # set default folded mode else: self.folded = self.FOLDED_NONE # unset default folded mode drawing_style = reversible_property(lambda self: self._drawing_style, set_drawing_style) def _is_folded(self): """ Check if interface item is folded interface item. """ return self._folded def _set_folded(self, folded): """ Set folded notation. :param folded: Folded state, see FOLDED_* constants. """ self._folded = folded if folded == self.FOLDED_NONE: movable = True draw_mode = self.DRAW_COMPARTMENT name_style = self.UNFOLDED_STYLE else: if self._folded == self.FOLDED_PROVIDED: icon_size = self.style.icon_size_provided else: # required interface or assembly icon mode icon_size = self.style.icon_size_required self.style.icon_size = icon_size self.min_width, self.min_height = icon_size self.width, self.height = icon_size # update only h_se handle - rest of handles should be updated by # constraints h_nw = self._handles[NW] h_se = self._handles[SE] h_se.pos.x = h_nw.pos.x + self.min_width h_se.pos.y = h_nw.pos.y + self.min_height movable = False draw_mode = self.DRAW_ICON name_style = self.FOLDED_STYLE # call super method to avoid recursion (set_drawing_style calls # _set_folded method) super(InterfaceItem, self).set_drawing_style(draw_mode) self._name.style.update(name_style) for h in self._handles: h.movable = movable self.request_update() folded = property(_is_folded, _set_folded, doc="Check or set folded notation, see FOLDED_* constants.") def draw_icon(self, context): cr = context.cairo h_nw = self._handles[NW] cx, cy = h_nw.pos.x + self.width / 2, h_nw.pos.y + self.height / 2 required = self._folded == self.FOLDED_REQUIRED or self._folded == self.FOLDED_ASSEMBLY provided = self._folded == self.FOLDED_PROVIDED or self._folded == self.FOLDED_ASSEMBLY if required: cr.save() cr.arc_negative(cx, cy, self.RADIUS_REQUIRED, self._angle, pi + self._angle) cr.restore() if provided: cr.move_to(cx + self.RADIUS_PROVIDED, cy) cr.arc(cx, cy, self.RADIUS_PROVIDED, 0, pi*2) cr.stroke() super(InterfaceItem, self).draw(context) # vim:sw=4:et:ai gaphor-0.17.2/gaphor/diagram/classes/klass.py000066400000000000000000000167671220151210700211010ustar00rootroot00000000000000"""This module defines two visualization items - OperationItem and ClassItem.""" from gaphas.state import observed, reversible_property from gaphor import UML from gaphor.i18n import _ from gaphor.diagram.classifier import ClassifierItem from gaphor.diagram.compartment import FeatureItem class OperationItem(FeatureItem): """This is visualization of a class operation and is a type of FeatureItem.""" def render(self): """Render the OperationItem.""" return UML.format(self.subject,\ visibility=True,\ type=True,\ multiplicity=True,\ default=True) or '' class ClassItem(ClassifierItem): """This item visualizes a Class instance. A ClassItem contains two compartments (Compartment): one for attributes and one for operations. To add and remove such features the ClassItem implements the CanvasGroupable interface. Items can be added by callling class.add() and class.remove(). This is used to handle CanvasItems, not UML objects!""" __uml__ = UML.Class, UML.Stereotype __stereotype__ = { 'stereotype': UML.Stereotype, 'metaclass': lambda self: (not isinstance(self.subject, UML.Stereotype)) and hasattr(self.subject, 'extension') and self.subject.extension, } __style__ = { 'extra-space': 'compartment', 'abstract-feature-font': 'sans italic 10', } def __init__(self, id=None): """Constructor. Initialize the ClassItem. This will also call the ClassifierItem constructor. The drawing style is set here as well. The class item will create two compartments - one for attributes and another for operations.""" ClassifierItem.__init__(self, id) self.drawing_style = self.DRAW_COMPARTMENT self._attributes = self.create_compartment('attributes') self._attributes.font = self.style.feature_font self._operations = self.create_compartment('operations') self._operations.font = self.style.feature_font self._operations.use_extra_space = True self.watch('subject.ownedOperation', self.on_class_owned_operation)\ .watch('subject.ownedAttribute.association', self.on_class_owned_attribute) \ .watch('subject.ownedAttribute.name') \ .watch('subject.ownedAttribute.isStatic') \ .watch('subject.ownedAttribute.isDerived') \ .watch('subject.ownedAttribute.visibility') \ .watch('subject.ownedAttribute.lowerValue') \ .watch('subject.ownedAttribute.upperValue') \ .watch('subject.ownedAttribute.defaultValue') \ .watch('subject.ownedAttribute.typeValue') \ .watch('subject.ownedOperation.name') \ .watch('subject.ownedOperation.isAbstract', self.on_operation_is_abstract) \ .watch('subject.ownedOperation.isStatic') \ .watch('subject.ownedOperation.visibility') \ .watch('subject.ownedOperation.returnResult.lowerValue') \ .watch('subject.ownedOperation.returnResult.upperValue') \ .watch('subject.ownedOperation.returnResult.typeValue') \ .watch('subject.ownedOperation.formalParameter.lowerValue') \ .watch('subject.ownedOperation.formalParameter.upperValue') \ .watch('subject.ownedOperation.formalParameter.typeValue') \ .watch('subject.ownedOperation.formalParameter.defaultValue') def save(self, save_func): """Store the show- properties *before* the width/height properties, otherwise the classes will unintentionally grow due to "visible" attributes or operations.""" self.save_property(save_func, 'show-attributes') self.save_property(save_func, 'show-operations') ClassifierItem.save(self, save_func) def postload(self): """Called once the ClassItem has been loaded. First the ClassifierItem is "post-loaded", then the attributes and operations are synchronized.""" super(ClassItem, self).postload() self.sync_attributes() self.sync_operations() @observed def _set_show_operations(self, value): """Sets the show operations property. This will either show or hide the operations compartment of the ClassItem. This is part of the show_operations property.""" self._operations.visible = value self._operations.use_extra_space = value self._attributes.use_extra_space = not self._operations.visible show_operations = reversible_property(fget=lambda s: s._operations.visible, fset=_set_show_operations) @observed def _set_show_attributes(self, value): """Sets the show attributes property. This will either show or hide the attributes compartment of the ClassItem. This is part of the show_attributes property.""" self._attributes.visible = value show_attributes = reversible_property(fget=lambda s: s._attributes.visible, fset=_set_show_attributes) def _create_attribute(self, attribute): """Create a new attribute item. This will create a new FeatureItem and assigns the specified attribute as the subject.""" new = FeatureItem() new.subject = attribute new.font = self.style.feature_font self._attributes.append(new) def _create_operation(self, operation): """Create a new operation item. This will create a new OperationItem and assigns the specified operation as the subject.""" new = OperationItem() new.subject = operation new.font = self.style.feature_font self._operations.append(new) def sync_attributes(self): """Sync the contents of the attributes compartment with the data in self.subject.""" owned_attributes = [a for a in self.subject.ownedAttribute if not a.association] self.sync_uml_elements(owned_attributes,\ self._attributes,\ self._create_attribute) def sync_operations(self): """Sync the contents of the operations compartment with the data in self.subject.""" self.sync_uml_elements(self.subject.ownedOperation,\ self._operations,\ self._create_operation) def on_class_owned_attribute(self, event): """Event handler for owned attributes. This will synchronize the attributes of this ClassItem.""" if self.subject: self.sync_attributes() def on_class_owned_operation(self, event): """Event handler for owned operations. This will synchronize the operations of this ClassItem.""" if self.subject: self.sync_operations() def on_operation_is_abstract(self, event): """Event handler for abstract operations. This will change the font of the operation.""" o = [o for o in self._operations if o.subject is event.element] if o: o = o[0] o.font = (o.subject and o.subject.isAbstract) \ and self.style.abstract_feature_font or self.style.feature_font self.request_update() gaphor-0.17.2/gaphor/diagram/classes/package.py000066400000000000000000000016671220151210700213500ustar00rootroot00000000000000""" Package diagram item. """ from gaphor import UML from gaphor.diagram.nameditem import NamedItem class PackageItem(NamedItem): __uml__ = UML.Package, UML.Profile __stereotype__ = { 'profile': UML.Profile, } __style__ = { 'min-size': (NamedItem.style.min_size[0], 70), 'name-font': 'sans bold 10', 'name-padding': (25, 10, 5, 10), 'tab-x': 50, 'tab-y': 20, } def __init__(self, id=None): super(PackageItem, self).__init__(id) def draw(self, context): super(PackageItem, self).draw(context) cr = context.cairo o = 0.0 h = self.height w = self.width x = self.style.tab_x y = self.style.tab_y cr.move_to(x, y) cr.line_to(x, o) cr.line_to(o, o) cr.line_to(o, h) cr.line_to(w, h) cr.line_to(w, y) cr.line_to(o, y) cr.stroke() # vim:sw=4:et gaphor-0.17.2/gaphor/diagram/classes/tests/000077500000000000000000000000001220151210700205335ustar00rootroot00000000000000gaphor-0.17.2/gaphor/diagram/classes/tests/__init__.py000066400000000000000000000000001220151210700226320ustar00rootroot00000000000000gaphor-0.17.2/gaphor/diagram/classes/tests/test_association.py000066400000000000000000000066741220151210700244750ustar00rootroot00000000000000""" Unnit tests for AssociationItem. """ from zope import component from gaphor.diagram.interfaces import IConnect from gaphor.tests import TestCase from gaphor import UML from gaphor.diagram.items import AssociationItem, ClassItem, InterfaceItem, \ UseCaseItem, ActorItem class AssociationItemTestCase(TestCase): services = TestCase.services + ['element_dispatcher'] def setUp(self): super(AssociationItemTestCase, self).setUp() self.assoc = self.create(AssociationItem) self.class1 = self.create(ClassItem, UML.Class) self.class2 = self.create(ClassItem, UML.Class) def test_create(self): """Test association creation and its basic properties """ self.connect(self.assoc, self.assoc.head, self.class1) self.connect(self.assoc, self.assoc.tail, self.class2) self.assertTrue(isinstance(self.assoc.subject, UML.Association)) self.assertTrue(self.assoc.head_end.subject is not None) self.assertTrue(self.assoc.tail_end.subject is not None) self.assertFalse(self.assoc.show_direction) self.assoc.show_direction = True self.assertTrue(self.assoc.show_direction) def test_invert_direction(self): """Test association direction inverting """ self.connect(self.assoc, self.assoc.head, self.class1) self.connect(self.assoc, self.assoc.tail, self.class2) head_subject = self.assoc.subject.memberEnd[0] tail_subject = self.assoc.subject.memberEnd[1] self.assoc.invert_direction() self.assertTrue(head_subject is self.assoc.subject.memberEnd[1]) self.assertTrue(tail_subject is self.assoc.subject.memberEnd[0]) def test_association_end_updates(self): """Test association end navigability connected to a class""" from gaphas.canvas import Canvas canvas = Canvas() c1 = self.create(ClassItem, UML.Class) c2 = self.create(ClassItem, UML.Class) a = self.create(AssociationItem) self.connect(a, a.head, c1) c = self.get_connected(a.head) self.assertTrue(c is c1) self.connect(a, a.tail, c2) c = self.get_connected(a.tail) self.assertTrue(c is c2) assert a.subject.memberEnd, a.subject.memberEnd assert a.subject.memberEnd[0] is a.head_end.subject assert a.subject.memberEnd[1] is a.tail_end.subject assert a.subject.memberEnd[0].name is None dispatcher = self.get_service('element_dispatcher') print dispatcher._handlers.has_key((a.subject.memberEnd[0], UML.Property.name)) print '*' * 60 a.subject.memberEnd[0].name = 'blah' print '*' * 60 self.diagram.canvas.update() assert a.head_end._name == '+ blah', a.head_end.get_name() def test_association_orthogonal(self): c1 = self.create(ClassItem, UML.Class) c2 = self.create(ClassItem, UML.Class) a = self.create(AssociationItem) self.connect(a, a.head, c1) c = self.get_connected(a.head) self.assertTrue(c is c1) a.matrix.translate(100, 100) self.connect(a, a.tail, c2) c = self.get_connected(a.tail) self.assertTrue(c is c2) try: a.orthogonal = True except ValueError: pass # Expected, hanve only 2 handles, need 3 or more else: assert False, 'Can not set line to orthogonal with less than 3 handles' # vim:sw=4:et:ai gaphor-0.17.2/gaphor/diagram/classes/tests/test_class.py000066400000000000000000000113601220151210700232520ustar00rootroot00000000000000""" Test classes. """ from gaphor.tests.testcase import TestCase from gaphor import UML from gaphor.diagram.classes.klass import ClassItem from gaphor.diagram.interfaces import IEditor import gaphor.adapters class ClassTestCase(TestCase): def test_compartments(self): """ Test creation of classes and working of compartments """ element_factory = self.element_factory diagram = element_factory.create(UML.Diagram) klass = diagram.create(ClassItem, subject=element_factory.create(UML.Class)) self.assertEqual(2, len(klass._compartments)) self.assertEqual(0, len(klass._compartments[0])) self.assertEqual(0, len(klass._compartments[1])) self.assertEqual((10, 10), klass._compartments[0].get_size()) diagram.canvas.update() self.assertEqual((10, 10), klass._compartments[0].get_size()) self.assertEqual(50, float(klass.min_height)) # min_height self.assertEqual(100, float(klass.min_width)) attr = element_factory.create(UML.Property) attr.name = 4 * 'x' # about 44 pixels klass.subject.ownedAttribute = attr diagram.canvas.update() self.assertEqual(1, len(klass._compartments[0])) self.assertEqual((44.0, 20.0), klass._compartments[0].get_size()) oper = element_factory.create(UML.Operation) oper.name = 4 * 'x' # about 44 pixels klass.subject.ownedOperation = oper oper = element_factory.create(UML.Operation) oper.name = 6 * 'x' # about 66 pixels klass.subject.ownedOperation = oper diagram.canvas.update() self.assertEqual(2, len(klass._compartments[1])) self.assertEqual((63.0, 34.0), klass._compartments[1].get_size()) def test_attribute_removal(self): element_factory = self.element_factory diagram = element_factory.create(UML.Diagram) klass = diagram.create(ClassItem, subject=element_factory.create(UML.Class)) diagram.canvas.update() attr = element_factory.create(UML.Property) attr.name = "blah1" klass.subject.ownedAttribute = attr attr2 = element_factory.create(UML.Property) attr2.name = "blah2" klass.subject.ownedAttribute = attr2 attr = element_factory.create(UML.Property) attr.name = "blah3" klass.subject.ownedAttribute = attr diagram.canvas.update() self.assertEqual(3, len(klass._compartments[0])) attr2.unlink() diagram.canvas.update() self.assertEqual(2, len(klass._compartments[0])) def test_item_at(self): """ Test working of item_at method """ element_factory = self.element_factory diagram = element_factory.create(UML.Diagram) klass = diagram.create(ClassItem, subject=element_factory.create(UML.Class)) klass.subject.name = 'Class1' diagram.canvas.update() attr = element_factory.create(UML.Property) attr.name = "blah" klass.subject.ownedAttribute = attr oper = element_factory.create(UML.Operation) oper.name = 'method' klass.subject.ownedOperation = oper diagram.canvas.update() assert len(klass.compartments[0]) == 1 assert len(klass.compartments[1]) == 1 name_size = klass._header_size assert klass.item_at(10, 10) is klass assert klass.item_at(name_size[0] - 1, name_size[1] - 1) is klass padding = klass.style.compartment_padding vspacing = klass.style.compartment_vspacing x = padding[-1] + 1 y = name_size[1] + padding[0] + 2 assert klass.item_at(x, y) is not None, klass.item_at(x, y) assert klass.item_at(x, y).subject is attr, klass.item_at(x, y).subject y = name_size[1] + klass.compartments[0].height + padding[0] + 2 assert klass.item_at(x, y) is not None, klass.item_at(x, y) assert klass.item_at(x, y).subject is oper, klass.item_at(x, y).subject def test_compartment_resizing(self): element_factory = self.element_factory diagram = element_factory.create(UML.Diagram) klass = diagram.create(ClassItem, subject=element_factory.create(UML.Class)) klass.subject.name = 'Class1' diagram.canvas.update() attr = element_factory.create(UML.Property) attr.name = 'blah' klass.subject.ownedAttribute = attr oper = element_factory.create(UML.Operation) oper.name = 'method' klass.subject.ownedOperation = oper self.assertEquals(100, klass.width) attr.name = 'x' * 25 log.debug('name: %s' % attr.name) diagram.canvas.update() width = klass.width self.assertEquals(170.0, width) # vim:sw=4:et:ai gaphor-0.17.2/gaphor/diagram/classes/tests/test_feature.py000066400000000000000000000020521220151210700235760ustar00rootroot00000000000000 from gaphor.tests.testcase import TestCase from gaphor import UML from gaphor.diagram.classes.klass import ClassItem from gaphor.diagram.compartment import FeatureItem from gaphor.UML.diagram import DiagramCanvas class FeatureTestCase(TestCase): def setUp(self): super(FeatureTestCase, self).setUp() def tearDown(self): super(FeatureTestCase, self).tearDown() def testAttribute(self): """ Test how attribute is updated """ attr = self.element_factory.create(UML.Property) UML.parse(attr, '-name:myType') clazzitem = self.create(ClassItem, UML.Class) clazzitem.subject.ownedAttribute = attr self.assertEquals(1, len(clazzitem._compartments[0])) item = clazzitem._compartments[0][0] self.assertTrue(isinstance(item, FeatureItem)) size = item.get_size() self.assertNotEquals((0, 0), size) attr.defaultValue = 'myDefault' self.diagram.canvas.update() self.assertTrue(size < item.get_size()) # vim:sw=4:et:ai gaphor-0.17.2/gaphor/diagram/classes/tests/test_interface.py000066400000000000000000000055021220151210700241060ustar00rootroot00000000000000""" Test classes. """ from zope import component from gaphor.tests import TestCase from gaphor import UML from gaphor.diagram.classes.interface import InterfaceItem class InterfaceTestCase(TestCase): def test_interface_creation(self): """Test interface creation """ iface = self.create(InterfaceItem, UML.Interface) self.assertTrue(isinstance(iface.subject, UML.Interface)) self.assertTrue(iface._name.is_visible()) # check style information self.assertFalse(iface.style.name_outside) def test_changing_to_icon_mode(self): """Test interface changing to icon mode """ iface = self.create(InterfaceItem, UML.Interface) iface.drawing_style = iface.DRAW_ICON self.assertEquals(iface.DRAW_ICON, iface.drawing_style) # default folded mode is provided self.assertTrue(iface.FOLDED_PROVIDED, iface.folded) # check if style information changed self.assertTrue(iface._name.style.text_outside) # handles are not movable anymore for h in iface.handles(): self.assertFalse(h.movable) # name is visible self.assertTrue(iface._name.is_visible()) def test_changing_to_classifier_mode(self): """Test interface changing to classifier mode """ iface = self.create(InterfaceItem, UML.Interface) iface.drawing_style = iface.DRAW_ICON iface.drawing_style = iface.DRAW_COMPARTMENT self.assertEquals(iface.DRAW_COMPARTMENT, iface.drawing_style) # check if style information changed self.assertFalse(iface._name.style.text_outside) # handles are movable again for h in iface.handles(): self.assertTrue(h.movable) def test_assembly_connector_icon_mode(self): """Test interface in assembly connector icon mode """ iface = self.create(InterfaceItem, UML.Interface) assert iface._name.is_visible() iface.folded = iface.FOLDED_ASSEMBLY self.assertFalse(iface._name.is_visible()) def test_folded_interface_persistence(self): """Test folded interface saving/loading """ iface = self.create(InterfaceItem, UML.Interface) # note: assembly folded mode.. iface.folded = iface.FOLDED_REQUIRED data = self.save() self.load(data) interfaces = self.diagram.canvas.select(lambda e: isinstance(e, InterfaceItem)) self.assertEquals(1, len(interfaces)) # ... gives provided folded mode on load; # correct folded mode is determined by connections, which will be # recreated later, i.e. required folded mode will be set when # implementation connects to the interface self.assertEquals(iface.FOLDED_PROVIDED, interfaces[0].folded) # vim:sw=4:et:ai gaphor-0.17.2/gaphor/diagram/classifier.py000066400000000000000000000016341220151210700204360ustar00rootroot00000000000000""" Classifier diagram item. """ from gaphor.diagram.compartment import CompartmentItem class ClassifierItem(CompartmentItem): """ Base class for UML classifiers. Classifiers can be abstract and this feature is supported by this class. """ __style__ = { 'name-font': 'sans bold 10', 'abstract-name-font': 'sans bold italic 10', } def __init__(self, id=None): super(ClassifierItem, self).__init__(id) self.watch('subject.isAbstract', self.on_classifier_is_abstract) def on_classifier_is_abstract(self, event): self._name.font = self.style.abstract_name_font \ if self.subject and self.subject.isAbstract \ else self.style.name_font self.request_update() def postload(self): super(ClassifierItem, self).postload() self.on_classifier_is_abstract(None) # vim:sw=4:et gaphor-0.17.2/gaphor/diagram/comment.py000066400000000000000000000034551220151210700177570ustar00rootroot00000000000000""" CommentItem diagram item """ from gaphor import UML from elementitem import ElementItem from gaphas.item import NW from textelement import text_multiline, text_extents class CommentItem(ElementItem): __uml__ = UML.Comment __style__ = { 'font': 'sans 10' } EAR=15 OFFSET=5 def __init__(self, id=None): ElementItem.__init__(self, id) self.min_width = CommentItem.EAR + 2 * CommentItem.OFFSET self.height = 50 self.width = 100 self.watch('subject.body') def edit(self): #self.start_editing(self._body) pass def pre_update(self, context): if not self.subject: return cr = context.cairo e = self.EAR o = self.OFFSET w, h = text_extents(cr, self.subject.body, self.style.font, width=self.width - e) self.min_width = w + e + o * 2 self.min_height = h + o * 2 ElementItem.pre_update(self, context) def draw(self, context): if not self.subject: return c = context.cairo # Width and height, adjusted for line width... ox = float(self._handles[NW].pos.x) oy = float(self._handles[NW].pos.y) w = self.width + ox h = self.height + oy ear = CommentItem.EAR c.move_to(w - ear, oy) line_to = c.line_to line_to(w - ear, oy + ear) line_to(w, oy + ear) line_to(w - ear, oy) line_to(ox, oy) line_to(ox, h) line_to(w, h) line_to(w, oy + ear) c.stroke() if self.subject.body: off = self.OFFSET # Do not print empty string, since cairo-win32 can't handle it. text_multiline(c, off, off, self.subject.body, self.style.font, self.width - ear, self.height) # vim:sw=4:et:ai gaphor-0.17.2/gaphor/diagram/commentline.py000066400000000000000000000020301220151210700206130ustar00rootroot00000000000000""" CommentLine -- A line that connects a comment to another model element. """ import gobject from zope import component from gaphor import UML from diagramline import DiagramLine from interfaces import IConnect class CommentLineItem(DiagramLine): def __init__(self, id=None): DiagramLine.__init__(self, id) def save (self, save_func): DiagramLine.save(self, save_func) def load (self, name, value): DiagramLine.load(self, name, value) def postload(self): DiagramLine.postload(self) def unlink(self): canvas = self.canvas c1 = canvas.get_connection(self.head) c2 = canvas.get_connection(self.tail) if c1 and c2: query = (c1.connected, self) adapter = component.queryMultiAdapter(query, IConnect) adapter.disconnect(self.head) super(CommentLineItem, self).unlink() def draw(self, context): context.cairo.set_dash((7.0, 5.0), 0) DiagramLine.draw(self, context) # vim: sw=4:et:ai gaphor-0.17.2/gaphor/diagram/compartment.py000066400000000000000000000442611220151210700206460ustar00rootroot00000000000000""" Diagram item with compartments. """ import cairo, pango, pangocairo from gaphas.state import observed, reversible_property from gaphor import UML from gaphor.diagram.diagramitem import DiagramItem from gaphor.diagram.nameditem import NamedItem from textelement import text_extents, text_align class FeatureItem(object): """ FeatureItems are model elements who recide inside a ClassifierItem, such as methods and attributes. Those items can have comments attached, but only on the left and right side. Note that features can also be used inside objects. """ def __init__(self, pattern='%s', order=0): super(FeatureItem, self).__init__() self.width = 0 self.height = 0 self.text = '' self.font = None self.subject = None self.order = order self.pattern = pattern def save(self, save_func): DiagramItem.save(self, save_func) def postload(self): if self.subject: self.text = self.render() self.on_feature_is_static(None) def get_size(self, update=False): """ Return the size of the feature. If update == True the item is directly updated. """ return self.width, self.height def get_text(self): return '' def update_size(self, text, context): if text: cr = context.cairo self.width, self.height = text_extents(cr, text) else: self.width, self.height = 0, 0 def pre_update(self, context): self.update_size(self.render(), context) def point(self, pos): """ """ return distance_rectangle_point((0, 0, self.width, self.height), pos) def render(self): """ Return a rendered feature, as a string. """ return UML.format(self.subject, pattern=self.pattern) or '' def draw(self, context): cr = context.cairo if isinstance(cr, cairo.Context): cr = pangocairo.CairoContext(cr) layout = cr.create_layout() layout.set_font_description(pango.FontDescription(self.font)) layout.set_text(self.render() or '') if hasattr(self.subject, 'isStatic') and self.subject.isStatic: attrlist = pango.AttrList() attrlist.insert(pango.AttrUnderline(pango.UNDERLINE_SINGLE, 2, -1)) layout.set_attributes(attrlist) cr.show_layout(layout) class Compartment(list): """ Compartment in a classifier or named item (i.e. class, component, state). A compartment is a list of feature items. """ def __init__(self, name, owner, id=None): self.name = name self.owner = owner self.id = id self.visible = True self.width = 0 self.height = 0 self.title = None self.font = None self.title_height = 0 self.use_extra_space = False def save(self, save_func): #log.debug('Compartment.save: %s' % self) for item in self: save_func(None, item) def has_item(self, item): """ Check if the compartment already contains an item with the same subject as item. """ s = item.subject local_elements = [f.subject for f in self] return s and s in local_elements def get_size(self): """ Get width, height of the compartment. pre_update should have been called so widthand height have been calculated. """ if self.visible: return self.width, self.height else: return 0, 0 def pre_update(self, context): """ Pre update, determine width and height of the compartment. """ self.width = self.height = 0 cr = context.cairo for item in self: item.pre_update(context) if self: # self (=list) contains items sizes = [ (0, 0) ] # to not throw exceptions by max and sum if self.title: w, h = text_extents(cr, self.title) self.title_height = h sizes.append((w, h)) sizes.extend(f.get_size(True) for f in self) self.width = max(size[0] for size in sizes) self.height = sum(size[1] for size in sizes) vspacing = self.owner.style.compartment_vspacing self.height += vspacing * (len(sizes) - 1) padding = self.owner.style.compartment_padding self.width += padding[1] + padding[3] self.height += padding[0] + padding[2] def post_update(self, context): for item in self: item.post_update(context) def draw(self, context): cr = context.cairo padding = self.owner.style.compartment_padding vspacing = self.owner.style.compartment_vspacing cr.translate(padding[1], padding[0]) offset = 0 if self.title: text_align(cr, self.owner.width / 2.0, padding[0], self.title, font=self.font, align_y=1) offset += self.title_height + vspacing for item in self: cr.save() try: cr.translate(0, offset) #cr.move_to(0, offset) item.draw(context) offset += vspacing + item.height finally: cr.restore() def item_at(self, x, y): if 0 > x > self.width: return None padding = self.owner.style.compartment_padding vspacing = self.owner.style.compartment_vspacing height = padding[0] if self.title: height += self.title_height + vspacing if y < height: return None vspacing = self.owner.style.compartment_vspacing for f in self: w, h = f.get_size(True) height += h + vspacing if y < height: return f return None class CompartmentItem(NamedItem): """ Abstract class for visualization of named items and classifiers, which have compartments, i.e. classes, interfaces, components, states. Compartment item has ability to display stereotypes attributes. They are displayed in separate compartments (one per stereotype). Compartment item has three drawing styles (changed with `ClassifierItem.drawing_style` property) - the comparttment view - often used by classes - a compartment view, but with a little stereotype icon in the right corner - an icon - used by actor and interface items Methods pre_update/post_update/draw are defined to support drawing styles. Appropriate methods are called depending on drawing style. """ # Do not use preset drawing style DRAW_NONE = 0 # Draw the famous box style DRAW_COMPARTMENT = 1 # Draw compartment with little icon in upper right corner DRAW_COMPARTMENT_ICON = 2 # Draw as icon DRAW_ICON = 3 __style__ = { 'min-size': (100, 50), 'icon-size': (20, 20), 'feature-font': 'sans 10', 'from-padding': (7, 2, 7, 2), 'compartment-padding': (5, 5, 5, 5), # (top, right, bottom, left) 'compartment-vspacing': 0, 'name-padding': (10, 10, 10, 10), 'stereotype-padding': (10, 10, 2, 10), # extra space can be used by header or a compartment; # we don't want to consume the extra space by compartments, which # contain stereotype information 'extra-space': 'header', # 'header' or 'compartment' } # Default size for small icons ICON_WIDTH = 15 ICON_HEIGHT = 25 ICON_MARGIN_X = 10 ICON_MARGIN_Y = 10 def __init__(self, id=None): NamedItem.__init__(self, id) self._compartments = [] self._drawing_style = CompartmentItem.DRAW_NONE self.watch('subject.appliedStereotype', self.on_stereotype_change) \ .watch('subject.appliedStereotype.slot', self.on_stereotype_attr_change) \ .watch('subject.appliedStereotype.slot.definingFeature.name') \ .watch('subject.appliedStereotype.slot.value') self._extra_space = 0 def on_stereotype_change(self, event): if self._show_stereotypes_attrs: if isinstance(event, UML.event.AssociationAddEvent): self._create_stereotype_compartment(event.new_value) elif isinstance(event, UML.event.AssociationDeleteEvent): self._remove_stereotype_compartment(event.old_value) def _find_stereotype_compartment(self, obj): for comp in self._compartments: if comp.id is obj: return comp def on_stereotype_attr_change(self, event): if event and self.subject \ and event.element in self.subject.appliedStereotype \ and self._show_stereotypes_attrs: comp = self._find_stereotype_compartment(event.element) if comp is None: log.debug('No compartment found for %s' % event.element) return if isinstance(event, (UML.event.AssociationAddEvent, UML.event.AssociationDeleteEvent)): self._update_stereotype_compartment(comp, event.element) self.request_update() def _create_stereotype_compartment(self, obj): st = obj.classifier[0].name c = Compartment(st, self, obj) c.title = UML.model.STEREOTYPE_FMT % st self._update_stereotype_compartment(c, obj) self._compartments.append(c) self.request_update() def _remove_stereotype_compartment(self, obj): comp = self._find_stereotype_compartment(obj) if comp is not None: self._compartments.remove(comp) self.request_update() def _update_stereotype_compartment(self, comp, obj): del comp[:] for slot in obj.slot: item = FeatureItem() item.subject = slot comp.append(item) comp.visible = len(obj.slot) > 0 def update_stereotypes_attrs(self): """ Display or hide stereotypes attributes. New compartment is created for every stereotype having attributes redefined. """ # remove all stereotype compartments first for comp in self._compartments: if isinstance(comp.id, UML.InstanceSpecification): self._compartments.remove(comp) if self._show_stereotypes_attrs: for obj in self.subject.appliedStereotype: self._create_stereotype_compartment(obj) log.debug('Showing stereotypes attributes enabled') else: log.debug('Showing stereotypes attributes disabled') def save(self, save_func): # Store the show- properties *before* the width/height properties, # otherwise the classes will unintentionally grow due to "visible" # attributes or operations. self.save_property(save_func, 'drawing-style') NamedItem.save(self, save_func) @observed def set_drawing_style(self, style): """ Set the drawing style for this classifier: DRAW_COMPARTMENT, DRAW_COMPARTMENT_ICON or DRAW_ICON. """ if style != self._drawing_style: self._drawing_style = style self.request_update() # if self.canvas: # request_resolve = self.canvas.solver.request_resolve # for h in self._handles: # request_resolve(h.x) # request_resolve(h.y) if self._drawing_style == self.DRAW_COMPARTMENT: self.draw = self.draw_compartment self.pre_update = self.pre_update_compartment self.post_update = self.post_update_compartment elif self._drawing_style == self.DRAW_COMPARTMENT_ICON: self.draw = self.draw_compartment_icon self.pre_update = self.pre_update_compartment_icon self.post_update = self.post_update_compartment_icon elif self._drawing_style == self.DRAW_ICON: self.draw = self.draw_icon self.pre_update = self.pre_update_icon self.post_update = self.post_update_icon drawing_style = reversible_property(lambda self: self._drawing_style, set_drawing_style) def create_compartment(self, name): """ Create a new compartment. Compartments contain data such as attributes and operations. It is common to create compartments during the construction of the diagram item. Their visibility can be toggled by Compartment.visible. """ c = Compartment(name, self) self._compartments.append(c) return c compartments = property(lambda s: s._compartments) def sync_uml_elements(self, elements, compartment, creator=None): """ This method synchronized a list of elements with the items in a compartment. A creator-function should be passed which is used for creating new compartment items. @elements: the list of attributes or operations in the model @compartment: our local representation @creator: factory method for creating new attr. or oper.'s """ # extract the UML elements from the compartment local_elements = [f.subject for f in compartment] # map local element with compartment element mapping = dict(zip(local_elements, compartment)) to_add = [el for el in elements if el not in local_elements] # sync local elements with elements del compartment[:] for el in elements: if el in to_add: creator(el) else: compartment.append(mapping[el]) #log.debug('elements order in model: %s' % [f.name for f in elements]) #log.debug('elements order in diagram: %s' % [f.subject.name for f in compartment]) assert tuple([f.subject for f in compartment]) == tuple(elements) self.request_update() def pre_update_compartment_icon(self, context): self.pre_update_compartment(context) # icon width plus right margin self.min_width = max(self.min_width, self._header_size[0] + self.ICON_WIDTH + 10) def pre_update_icon(self, context): super(CompartmentItem, self).pre_update(context) def pre_update_compartment(self, context): """ Update state for box-style presentation. Calculate minimal size, which is based on header and comparments sizes. """ super(CompartmentItem, self).pre_update(context) for comp in self._compartments: comp.pre_update(context) sizes = [comp.get_size() for comp in self._compartments if comp.visible] sizes.append((self.min_width, self._header_size[1])) self.min_width = max(size[0] for size in sizes) h = sum(size[1] for size in sizes) self.min_height = max(self.style.min_size[1], h) def post_update_compartment_icon(self, context): """ Update state for box-style w/ small icon. """ super(CompartmentItem, self).post_update(context) def post_update_icon(self, context): """ Update state for icon-only presentation. """ super(CompartmentItem, self).post_update(context) def post_update_compartment(self, context): super(CompartmentItem, self).post_update(context) assert abs(self.width - self.min_width) >= 0, 'failed %s >= %s' % (self.width, self.min_width) assert abs(self.height - self.min_height) >= 0, 'failed %s >= %s' % (self.height, self.min_height) def get_icon_pos(self): """ Get icon position. """ return self.width - self.ICON_MARGIN_X - self.ICON_WIDTH, \ self.ICON_MARGIN_Y def draw_compartment_border(self, context): """ Standard classifier border is a rectangle. """ cr = context.cairo cr.rectangle(0, 0, self.width, self.height) self.fill_background(context) cr.stroke() def draw_compartment(self, context): self.draw_compartment_border(context) super(CompartmentItem, self).draw(context) cr = context.cairo # make room for name, stereotype, etc. y = self._header_size[1] cr.translate(0, y) if self._drawing_style == self.DRAW_COMPARTMENT_ICON: width = self.width - self.ICON_WIDTH else: width = self.width extra_space = self.height - self.min_height # extra space is used by header if self.style.extra_space == 'header': cr.translate(0, extra_space) # draw compartments and stereotype compartments extra_used = False for comp in self._compartments: if not comp.visible: continue cr.save() cr.move_to(0, 0) cr.line_to(self.width, 0) cr.stroke() try: comp.draw(context) finally: cr.restore() d = comp.height if not extra_used and comp.use_extra_space \ and self.style.extra_space == 'compartment': d += extra_space extra_used = True cr.translate(0, d) # if extra space is used by last compartment, then do nothing def item_at(self, x, y): """ Find the composite item (attribute or operation) for the classifier. """ if self.drawing_style not in (self.DRAW_COMPARTMENT, self.DRAW_COMPARTMENT_ICON): return self header_height = self._header_size[1] compartments = [ comp for comp in self.compartments if comp.visible] # Edit is in name compartment -> edit name if y < header_height or not len(compartments): return self padding = self.style.compartment_padding vspacing = self.style.compartment_vspacing # place offset at top of first comparement y -= header_height y += vspacing / 2.0 for comp in compartments: item = comp.item_at(x, y) if item: return item y -= comp.height return None # vi:ai:sw=4:et gaphor-0.17.2/gaphor/diagram/component.py000066400000000000000000000025451220151210700203160ustar00rootroot00000000000000""" Component item. """ from gaphor import UML from gaphor.diagram.classifier import ClassifierItem class ComponentItem(ClassifierItem): __uml__ = UML.Component __icon__ = True __style__ = { 'name-padding': (10, 25, 10, 10), } BAR_WIDTH = 10 BAR_HEIGHT = 5 BAR_PADDING = 5 def __init__(self, id=None): ClassifierItem.__init__(self, id) # Set drawing style to compartment w// small icon self.drawing_style = self.DRAW_COMPARTMENT_ICON def draw_compartment_icon(self, context): cr = context.cairo cr.save() self.draw_compartment(context) cr.restore() ix, iy = self.get_icon_pos() cr.set_line_width(1.0) cr.rectangle(ix, iy, self.ICON_WIDTH, self.ICON_HEIGHT) cr.stroke() bx = ix - self.BAR_PADDING bar_upper_y = iy + self.BAR_PADDING bar_lower_y = iy + self.BAR_PADDING * 3 color = cr.get_source() cr.rectangle(bx, bar_lower_y, self.BAR_WIDTH, self.BAR_HEIGHT) cr.set_source_rgb(1,1,1) # white cr.fill_preserve() cr.set_source(color) cr.stroke() cr.rectangle(bx, bar_upper_y, self.BAR_WIDTH, self.BAR_HEIGHT) cr.set_source_rgb(1,1,1) # white cr.fill_preserve() cr.set_source(color) cr.stroke() # vim:sw=4:et gaphor-0.17.2/gaphor/diagram/components/000077500000000000000000000000001220151210700201215ustar00rootroot00000000000000gaphor-0.17.2/gaphor/diagram/components/__init__.py000066400000000000000000000000001220151210700222200ustar00rootroot00000000000000gaphor-0.17.2/gaphor/diagram/components/subsystem.py000066400000000000000000000016721220151210700225370ustar00rootroot00000000000000""" Subsystem item represents a component with stereotype subsystem (see table B.1 UML Keywords in UML 2.2 specification). Subsystem item is part of components Gaphor package because it will show components, nodes and other items within cotext of a subsystem. At the moment (in the future additionally) it makes only sense to use it on use cases diagram. """ from gaphor import UML from gaphor.diagram.component import ComponentItem from gaphor.diagram.style import ALIGN_LEFT, ALIGN_TOP from gaphor.diagram import uml @uml(UML.Component, stereotype='subsystem') class SubsystemItem(ComponentItem): __style__ = { 'name-align': (ALIGN_LEFT, ALIGN_TOP), } def __init__(self, id=None): super(SubsystemItem, self).__init__(id) def draw(self, context): super(SubsystemItem, self).draw(context) cr = context.cairo cr.rectangle(0, 0, self.width, self.height) cr.stroke() # vim:sw=4:et gaphor-0.17.2/gaphor/diagram/components/tests/000077500000000000000000000000001220151210700212635ustar00rootroot00000000000000gaphor-0.17.2/gaphor/diagram/components/tests/__init__.py000066400000000000000000000000001220151210700233620ustar00rootroot00000000000000gaphor-0.17.2/gaphor/diagram/components/tests/test_node.py000066400000000000000000000002021220151210700236130ustar00rootroot00000000000000from gaphor.diagram import items from gaphor import UML from gaphor.tests import TestCase class NodeTestCase(TestCase): pass gaphor-0.17.2/gaphor/diagram/connector.py000066400000000000000000000145751220151210700203140ustar00rootroot00000000000000""" Implementation of connector from Composite Structures and Components. Only assembly connector (see chapter Components in UML specification) is supported at the moment. The implementation is based on `ConnectorItem` class and `InterfaceItem` class in assembly connector mode. Assembly Connector ================== To connect two components with assembly connector connect folded interface and component items using connector item. If component is provides or requires connected interface, then assembly connection in UML data model will be created and connector item will display name of the interface. Otherwise, UML data model is not updated and connector item does not display interface name. Interface item in assembly connector mode does not display interface name as it is displayed by connectors. Connector item visualizes two UML metaclasses - ConnectorEnd metaclass when connecting to interface item in assembly mode - Connector metaclass in other cases Using property pages of connector item, user can change superinterface of connected interface. Assembly Connector Mode of Interface Item ----------------------------------------- Assembly connector notation is supported using interface item because of its simplicity - no need for additional assembly connector item - because connection is made to specific interface, there is no need for performing a search for common interface of all connected components - separate assembly connector item would require some rotation support, instead interface item's rotation capabilities are reused Implementation Alternatives --------------------------- There were several alternatives of assembly connector notation explored. In Gaphor 0.8.x there was assembly connector item, with additional handles and lines. User was dragging a handle of an additional line to connect to a component, disadvantages - item's connection behaviour is not consistent with other items - rotation needs to be implemented For Gaphor 0.14 and later, two other ideas were considered. First one required assembly connector item as well. Connector item could visualize ConnectorEnd and Connector UML metaclasses and it would be used to connect assembly connector item and items of components. It is very consistent with the rest of Gaphor application but - it proved to be very complicated in implementation - requires additional item Second alternative was to have connector item only. It is very simple concept in first place. When connector item connects two components, then draw assembly connector icon in the middle of a line. The solution is very simple in implementation and consistent with the rest of the application until multiple components have to be connected with one assembly connector. UML Specificatiom Issues ======================== UML specification is not clear about interfaces as connectable elements and connector's `kind` attribute. Current implementation is subject to change in the future, when UML specification clarifies issues described below. See also http://www.omg.org/issues/uml2-rtf.open.html#Issue7251 Connector Kind -------------- Chapter Components of UML specification adds `kind` attribute to connector metaclass. This is enumeration with two possible values `assembly' and `delegation'. It is not clear what value should be assigned to `kind` attribute of connector, which is defined between connectable elements like ports (not characterized by interfaces), properties and parameters. Interfaces as Connectable Elements ---------------------------------- Chapter Composite Structures in UML Superstructure 2.1.2 document does not specify interfaces as connectable elements. But definition of assembly connector says: An assembly connector is a connector between two components that defines that one component provides the services that another component requires. An assembly connector is a connector that is defined from a required _interface_ or port to a provided _interface_ or port. Therefore, code of connector items is written with assumption, that interfaces are connectable elements. """ from logging import getLogger from gaphor import UML from gaphor.diagram.diagramline import NamedLine from gaphor.diagram.style import ALIGN_CENTER, ALIGN_BOTTOM from operator import attrgetter logger = getLogger('Connector') class ConnectorItem(NamedLine): """ Connector item line. Represents Connector UML metaclass. If connected to interface item in assembly connector mode, then `Connector.end` attribute represents appropriate `ConnectorEnd` UML metaclass instance. :Attributes: subject Connector UML metaclass instance. end ConnectorEnd UML metaclass instance. _interface Interface name, when connector is assembly connector. """ __uml__ = UML.Connector __style__ = { 'name-align': (ALIGN_CENTER, ALIGN_BOTTOM), 'name-outside': True, } def __init__(self, id): super(ConnectorItem, self).__init__(id) self._interface = self.add_text('end.role.name', style={ 'text-align-group': 'stereotype', }) self.watch('subject.end.role.name', self.on_interface_name) def postload(self): super(ConnectorItem, self).postload() self.on_interface_name(None) def on_interface_name(self, event): """ Callback used, when interface name changes (interface is referenced by `ConnectorItem.subject.end.role`). """ try: self._interface.text = self.subject.end['it.role', 0].role.name except (IndexError, AttributeError), e: logger.error(e) self._interface.text = '' else: self.request_update(matrix=False) def draw_tail(self, context): cr = context.cairo cr.line_to(0, 0) if self.subject and self.subject.kind == 'delegation': cr.move_to(15, -6) cr.line_to(0, 0) cr.line_to(15, 6) def save(self, save_func): super(ConnectorItem, self).save(save_func) #save_func('end', self.end) def load(self, name, value): if name == 'end': pass #self.end = value else: super(ConnectorItem, self).load(name, value) #def on_named_element_name(self, event): # if isinstance(self.subject, UML.Connector): # super(ConnectorItem, self).on_named_element_name(event) # vim:sw=4:et:ai gaphor-0.17.2/gaphor/diagram/diagramitem.py000066400000000000000000000201271220151210700205730ustar00rootroot00000000000000""" DiagramItem provides basic functionality for presentations. Such as a modifier 'subject' property and a unique id. """ from zope import component from gaphas.state import observed, reversible_property from logging import getLogger from gaphor import UML from gaphor.services.elementdispatcher import EventWatcher from gaphor.core import inject from gaphor.diagram import DiagramItemMeta from gaphor.diagram.textelement import EditableTextSupport from gaphor.diagram.style import ALIGN_CENTER, ALIGN_TOP logger = getLogger('Diagram') class StereotypeSupport(object): """ Support for stereotypes for every diagram item. """ STEREOTYPE_ALIGN = { 'text-align' : (ALIGN_CENTER, ALIGN_TOP), 'text-padding': (5, 10, 2, 10), 'text-outside': False, 'text-align-group': 'stereotype', 'line-width': 2, } def __init__(self): self._stereotype = self.add_text('stereotype', style=self.STEREOTYPE_ALIGN, visible=lambda: self._stereotype.text) self._show_stereotypes_attrs = False @observed def _set_show_stereotypes_attrs(self, value): self._show_stereotypes_attrs = value self.update_stereotypes_attrs() show_stereotypes_attrs = reversible_property( fget=lambda s: s._show_stereotypes_attrs, fset=_set_show_stereotypes_attrs, doc=""" Diagram item should show stereotypes attributes when property is set to True. When changed, method `update_stereotypes_attrs` is called. """) def update_stereotypes_attrs(self): """ Update display of stereotypes attributes. The method does nothing at the moment. In the future it should probably display stereotypes attributes under stereotypes header. Abstract class for classifiers overrides this method to display stereotypes attributes in compartments. """ pass def set_stereotype(self, text=None): """ Set the stereotype text for the diagram item. Note, that text is not Stereotype object. @arg text: stereotype text """ self._stereotype.text = text self.request_update() stereotype = property(lambda s: s._stereotype, set_stereotype) def update_stereotype(self): """ Update the stereotype definitions (text) of this item. Note, that this method is also called from ExtensionItem.confirm_connect_handle method. """ # by default no stereotype, however check for __stereotype__ # attribute to assign some static stereotype see interfaces, # use case relationships, package or class for examples stereotype = getattr(self, '__stereotype__', ()) if stereotype: stereotype = self.parse_stereotype(stereotype) # Phew! :] :P stereotype = UML.model.stereotypes_str(self.subject, stereotype) self.set_stereotype(stereotype) def parse_stereotype(self, data): if isinstance(data, str): # return data as stereotype if it is a string return (data,) subject = self.subject for stereotype, condition in data.items(): if isinstance(condition, tuple): cls, predicate = condition elif isinstance(condition, type): cls = condition predicate = None elif callable(condition): cls = None predicate = condition else: assert False, 'wrong conditional %s' % condition ok = True if cls: ok = type(subject) is cls #isinstance(subject, cls) if predicate: ok = predicate(self) if ok: return (stereotype,) return () class DiagramItem(UML.Presentation, StereotypeSupport, EditableTextSupport): """ Basic functionality for all model elements (lines and elements!). This class contains common functionallity for model elements and relationships. It provides an interface similar to UML.Element for connecting and disconnecting signals. This class is not very useful on its own. It contains some glue-code for diacanvas.DiaCanvasItem and gaphor.UML.Element. Example: class ElementItem(diacanvas.CanvasElement, DiagramItem): connect = DiagramItem.connect disconnect = DiagramItem.disconnect ... @cvar style: styles information (derived from DiagramItemMeta) """ __metaclass__ = DiagramItemMeta dispatcher = inject('element_dispatcher') def __init__(self, id=None): UML.Presentation.__init__(self) EditableTextSupport.__init__(self) StereotypeSupport.__init__(self) self._id = id # properties, which should be saved in file self._persistent_props = set() def update(event): self.request_update() self.watcher = EventWatcher(self, default_handler=update) self.watch('subject') \ .watch('subject.appliedStereotype.classifier.name', self.on_element_applied_stereotype) id = property(lambda self: self._id, doc='Id') def set_prop_persistent(self, name): """ Specify property of diagram item, which should be saved in file. """ self._persistent_props.add(name) # TODO: Use adapters for load/save functionality def save(self, save_func): if self.subject: save_func('subject', self.subject) save_func('show_stereotypes_attrs', self.show_stereotypes_attrs) # save persistent properties for p in self._persistent_props: save_func(p, getattr(self, p.replace('-', '_'))) def load(self, name, value): if name == 'subject': type(self).subject.load(self, value) elif name == 'show_stereotypes_attrs': self._show_stereotypes_attrs = eval(value) else: try: setattr(self, name.replace('-', '_'), eval(value)) except: logger.warning('%s has no property named %s (value %s)'%\ (self, name, value)) def postload(self): if self.subject: self.update_stereotype() self.update_stereotypes_attrs() def save_property(self, save_func, name): """ Save a property, this is a shorthand method. """ save_func(name, getattr(self, name.replace('-', '_'))) def save_properties(self, save_func, *names): """ Save a property, this is a shorthand method. """ for name in names: self.save_property(save_func, name) def unlink(self): """ Remove the item from the canvas and set subject to None. """ if self.canvas: self.canvas.remove(self) super(DiagramItem, self).unlink() def request_update(self): """ Placeholder for gaphor.Item's request_update() method. """ pass def pre_update(self, context): EditableTextSupport.pre_update(self, context) def post_update(self, context): EditableTextSupport.post_update(self, context) def draw(self, context): EditableTextSupport.draw(self, context) def item_at(self, x, y): return self def on_element_applied_stereotype(self, event): if self.subject: self.update_stereotype() self.request_update() def watch(self, path, handler=None): """ Watch a certain path of elements starting with the DiagramItem. The handler is optional and will default to a simple self.request_update(). Watches should be set in the constructor, so they can be registered and unregistered in one shot. This interface is fluent(returns self). """ self.watcher.watch(path, handler) return self def register_handlers(self): self.watcher.register_handlers() def unregister_handlers(self): self.watcher.unregister_handlers() # vim:sw=4:et:ai gaphor-0.17.2/gaphor/diagram/diagramline.py000066400000000000000000000164371220151210700205750ustar00rootroot00000000000000""" Basic functionality for canvas line based items on a diagram. """ from math import atan2, pi import gaphas from gaphor import UML from diagramitem import DiagramItem from interfaces import IConnect from gaphor.diagram.style import get_text_point_at_line, \ get_text_point_at_line2, \ ALIGN_CENTER, ALIGN_LEFT, ALIGN_RIGHT, ALIGN_TOP class DiagramLine(gaphas.Line, DiagramItem): """ Base class for diagram lines. """ def __init__(self, id = None): gaphas.Line.__init__(self) DiagramItem.__init__(self, id) self.fuzziness = 2 head = property(lambda self: self._handles[0]) tail = property(lambda self: self._handles[-1]) def setup_canvas(self): gaphas.Line.setup_canvas(self) self.register_handlers() def teardown_canvas(self): gaphas.Line.teardown_canvas(self) self.unregister_handlers() def pre_update(self, context): # first, update stereotype to know its text self.update_stereotype() gaphas.Line.pre_update(self, context) DiagramItem.pre_update(self, context) def post_update(self, context): gaphas.Line.post_update(self, context) DiagramItem.post_update(self, context) def draw(self, context): gaphas.Line.draw(self, context) DiagramItem.draw(self, context) def point(self, pos): d1 = gaphas.Line.point(self, pos) d2 = DiagramItem.point(self, pos) return min(d1, d2) def save(self, save_func): DiagramItem.save(self, save_func) save_func('matrix', tuple(self.matrix)) for prop in ('orthogonal', 'horizontal'): save_func(prop, getattr(self, prop)) points = [] for h in self.handles(): points.append(tuple(map(float, h.pos))) save_func('points', points) canvas = self.canvas c = canvas.get_connection(self.head) if c: save_func('head-connection', c.connected, reference=True) c = canvas.get_connection(self.tail) if c: save_func('tail-connection', c.connected, reference=True) def load(self, name, value): if name == 'matrix': self.matrix = eval(value) elif name == 'points': points = eval(value) for x in xrange(len(points) - 2): h = self._create_handle((0, 0)) self._handles.insert(1, h) for i, p in enumerate(points): self.handles()[i].pos = p # Update connection ports of the line. Only handles are saved # in Gaphor file therefore ports need to be recreated after # handles information is loaded. self._update_ports() elif name == 'orthogonal': self._load_orthogonal = eval(value) elif name in ('head_connection', 'head-connection'): self._load_head_connection = value elif name in ('tail_connection', 'tail-connection'): self._load_tail_connection = value else: DiagramItem.load(self, name, value) def _get_sink(self, handle, item): """ Instant port finder. This is not the nicest place for such method. TODO: figure out if part of this functionality can be provided by the storage code. """ from gaphas.aspect import ConnectionSink hpos = self.canvas.get_matrix_i2i(self, item).transform_point(*handle.pos) port = None dist = 10e6 for p in item.ports(): pos, d = p.glue(hpos) if not port or d < dist: port = p dist = d return ConnectionSink(item, port) def _postload_connect(self, handle, item): """ Postload connect method. """ from gaphas.aspect import Connector connector = Connector(self, handle) sink = self._get_sink(handle, item) connector.connect(sink) def postload(self): if hasattr(self, '_load_orthogonal'): # Ensure there are enough handles if self._load_orthogonal and len(self._handles) < 3: p0 = self._handles[-1].pos self._handles.insert(1, self._create_handle(p0)) self.orthogonal = self._load_orthogonal del self._load_orthogonal # First update matrix and solve constraints (NE and SW handle are # lazy and are resolved by the constraint solver rather than set # directly. self.canvas.update_matrix(self) self.canvas.solver.solve() if hasattr(self, '_load_head_connection'): self._postload_connect(self.head, self._load_head_connection) del self._load_head_connection if hasattr(self, '_load_tail_connection'): self._postload_connect(self.tail, self._load_tail_connection) del self._load_tail_connection DiagramItem.postload(self) def _get_middle_segment(self): """ Get middle line segment. """ handles = self._handles m = len(handles) / 2 assert m - 1 >= 0 and m < len(handles) return handles[m - 1], handles[m] def _get_center_pos(self, inverted=False): """ Return position in the centre of middle segment of a line. Angle of the middle segment is also returned. """ h0, h1 = self._get_middle_segment() pos = (h0.pos.x + h1.pos.x) / 2, (h0.pos.y + h1.pos.y) / 2 angle = atan2(h1.pos.y - h0.pos.y, h1.pos.x - h0.pos.x) if inverted: angle += pi return pos, angle def text_align(self, extents, align, padding, outside): handles = self._handles halign, valign = align if halign == ALIGN_LEFT: p1 = handles[0].pos p2 = handles[-1].pos x, y = get_text_point_at_line(extents, p1, p2, align, padding) elif halign == ALIGN_CENTER: h0, h1 = self._get_middle_segment() p1 = h0.pos p2 = h1.pos x, y = get_text_point_at_line2(extents, p1, p2, align, padding) elif halign == ALIGN_RIGHT: p1 = handles[-1].pos p2 = handles[-2].pos x, y = get_text_point_at_line(extents, p1, p2, align, padding) return x, y class NamedLine(DiagramLine): __style__ = { 'name-align': (ALIGN_CENTER, ALIGN_TOP), 'name-padding': (5, 5, 5, 5), 'name-outside': True, 'name-align-str': None, } def __init__(self, id=None): DiagramLine.__init__(self, id) self._name = self.add_text('name', style={ 'text-align': self.style.name_align, 'text-padding': self.style.name_padding, 'text-outside': self.style.name_outside, 'text-align-str': self.style.name_align_str, 'text-align-group': 'stereotype', }, editable=True) self.watch('subject.name', self.on_named_element_name) def postload(self): super(NamedLine, self).postload() self.on_named_element_name(None) def on_named_element_name(self, event): self._name.text = self.subject and self.subject.name or '' self.request_update() # vim:sw=4:et:ai gaphor-0.17.2/gaphor/diagram/elementitem.py000066400000000000000000000056511220151210700206250ustar00rootroot00000000000000""" Abstract classes for element-like Diagram items. """ import gobject import cairo import gaphas from zope import component from diagramitem import DiagramItem from gaphor.diagram.style import get_text_point class ElementItem(gaphas.Element, DiagramItem): __style__ = { 'min-size': (0, 0), 'stereotype-padding': (5, 10, 5, 10), 'background': 'solid', 'background-color': (1, 1, 1, 0.8), 'highlight-color': (0, 0, 1, 0.4), 'background-gradient': ((0.8, 0.8, 0.8, 0.5), (1.0, 1.0, 1.0, 0.5)) } def __init__(self, id=None): gaphas.Element.__init__(self) DiagramItem.__init__(self, id) self.min_width = self.style.min_size[0] self.min_height = self.style.min_size[1] self.auto_resize = 0 def save(self, save_func): save_func('matrix', tuple(self.matrix)) for prop in ('width', 'height'): self.save_property(save_func, prop) DiagramItem.save(self, save_func) def load(self, name, value): if name == 'matrix': self.matrix = eval(value) else: DiagramItem.load(self, name, value) def setup_canvas(self): gaphas.Element.setup_canvas(self) self.register_handlers() def teardown_canvas(self): gaphas.Element.teardown_canvas(self) self.unregister_handlers() def pre_update(self, context): #super(ElementItem, self).pre_update(context) self.update_stereotype() DiagramItem.pre_update(self, context) gaphas.Element.pre_update(self, context) def point(self, pos): d1 = gaphas.Element.point(self, pos) d2 = DiagramItem.point(self, pos) return min(d1, d2) def post_update(self, context): gaphas.Element.post_update(self, context) DiagramItem.post_update(self, context) def fill_background(self, context): cr = context.cairo cr.save() try: if self.style.background == 'solid': cr.set_source_rgba(*self.style.background_color) cr.fill_preserve() elif self.style.background == 'gradient': # TODO: check if style is gradient g = cairo.LinearGradient(0, 0, self.width, self.height) for i, c in enumerate(self.style.background_gradient): g.add_color_stop_rgba(i, *c) cr.set_source(g) cr.fill_preserve() finally: cr.restore() def highlight(self, context): cr = context.cairo cr.save() try: if context.dropzone: cr.set_source_rgba(*self.style.highlight_color) cr.set_line_width(cr.get_line_width() * 3.141) cr.stroke_preserve() finally: cr.restore() def draw(self, context): self.fill_background(context) self.highlight(context) gaphas.Element.draw(self, context) DiagramItem.draw(self, context) def text_align(self, extents, align, padding, outside): x, y = get_text_point(extents, self.width, self.height, align, padding, outside) return x, y # vim:sw=4:et:ai gaphor-0.17.2/gaphor/diagram/extend.py000066400000000000000000000004161220151210700175760ustar00rootroot00000000000000""" Use case extension relationship. """ from gaphor import UML from gaphor.diagram.include import IncludeItem class ExtendItem(IncludeItem): """ Use case extension relationship. """ __uml__ = UML.Extend __stereotype__ = 'extend' # vim:sw=4:et gaphor-0.17.2/gaphor/diagram/extension.py000066400000000000000000000017201220151210700203220ustar00rootroot00000000000000""" ExtensionItem -- Graphical representation of an association. """ # TODO: for Extension.postload(): in some cases where the association ends # are connected to the same Class, the head_end property is connected to the # tail end and visa versa. from gaphor import UML from gaphor.diagram.diagramline import NamedLine class ExtensionItem(NamedLine): """ ExtensionItem represents associations. An ExtensionItem has two ExtensionEnd items. Each ExtensionEnd item represents a Property (with Property.association == my association). """ __uml__ = UML.Extension def __init__(self, id=None): NamedLine.__init__(self, id) self.watch('subject.ownedEnd') def draw_head(self, context): cr = context.cairo cr.move_to(0, 0) cr.line_to(15, -10) cr.line_to(15, 10) cr.line_to(0, 0) cr.set_source_rgb(0, 0, 0) cr.fill() cr.move_to(15, 0) # vim:sw=4:et:ai gaphor-0.17.2/gaphor/diagram/include.py000066400000000000000000000012541220151210700177330ustar00rootroot00000000000000""" Use case inclusion relationship. """ from gaphor import UML from gaphor.diagram.diagramline import DiagramLine class IncludeItem(DiagramLine): """ Use case inclusion relationship. """ __uml__ = UML.Include __stereotype__ = 'include' def __init__(self, id=None): DiagramLine.__init__(self, id) def draw_head(self, context): cr = context.cairo cr.set_dash((), 0) cr.move_to(15, -6) cr.line_to(0, 0) cr.line_to(15, 6) cr.stroke() cr.move_to(0, 0) def draw(self, context): context.cairo.set_dash((7.0, 5.0), 0) super(IncludeItem, self).draw(context) # vim:sw=4:et gaphor-0.17.2/gaphor/diagram/interaction.py000066400000000000000000000013051220151210700206240ustar00rootroot00000000000000""" Interaction diagram item. """ from gaphor import UML from gaphor.diagram.nameditem import NamedItem from gaphor.diagram.style import ALIGN_LEFT, ALIGN_TOP class InteractionItem(NamedItem): __uml__ = UML.Interaction __style__ = { 'min-size': (150, 100), 'name-align': (ALIGN_TOP, ALIGN_LEFT), } def draw(self, context): cr = context.cairo cr.rectangle(0, 0, self.width, self.height) super(InteractionItem, self).draw(context) # draw pentagon w, h = self._header_size h2 = h / 2.0 cr.move_to(0, h) cr.line_to(w - 4, h) cr.line_to(w, h2) cr.line_to(w, 0) cr.stroke() # vim:sw=4:et gaphor-0.17.2/gaphor/diagram/interfaces.py000066400000000000000000000060471220151210700204400ustar00rootroot00000000000000""" This module describes the interfaces specific to the gaphor.diagram module. These interfaces are: - IConnect Use to define adapters for connecting - IEditor Text editor interface """ from zope import interface class IEditor(interface.Interface): """ Provide an interface for editing text with the TextEditTool. """ def is_editable(self, x, y): """ Is this item editable in it's current state. x, y represent the cursors (x, y) position. (this method should be called before get_text() is called. """ def get_text(self): """ Get the text to be updated """ def get_bounds(self): """ Get the bounding box of the (current) text. The edit tool is not required to do anything with this information but it might help for some nicer displaying of the text widget. Returns: a gaphas.geometry.Rectangle """ def update_text(self, text): """ Update with the new text. """ def key_pressed(self, pos, key): """ Called every time a key is pressed. Allows for 'Enter' as escape character in single line editing. """ class IConnect(interface.Interface): """ This interface is used by the HandleTool to allow connecting lines to element items. For each specific case (Element, Line) an adapter could be written. """ def connect(self, handle, port): """ Connect a line's handle to element. Note that at the moment of the connect, handle.connected_to may point to some other item. The implementor should do the disconnect of the other element themselves. """ def disconnect(self, handle): """ The true disconnect. Disconnect a handle.connected_to from an element. This requires that the relationship is also removed at model level. """ def connect_constraints(self, handle): """ Connect a handle to the element. """ def disconnect_constraints(self, handle): """ Disconnect a line's handle from an element. This is called whenever a handle is dragged. """ def glue(self, handle): """ Determine if a handle can glue to a specific element. Returns a tuple (x, y) if the line and element may connect, None otherwise. """ class IGroup(interface.Interface): """ Provide interface for adding one UML object to another, i.e. interactions contain lifelines and components contain classes objects. """ def pre_can_contain(self): """ Determine if parent can contain item, which is instance of given class. Method called before item creation. """ def can_contain(self): """ Determine if parent can contain item. """ def group(self): """ Perform grouping of items. """ def ungroup(self): """ Perform ungrouping of items. """ # vim: sw=4:et:ai gaphor-0.17.2/gaphor/diagram/items.py000066400000000000000000000052771220151210700174420ustar00rootroot00000000000000""" All Item's defined in the diagram package. This module is a shorthand for importing each module individually. """ # Base classes: from gaphor.diagram.diagramitem import DiagramItem from gaphor.diagram.diagramline import DiagramLine, NamedLine from gaphor.diagram.elementitem import ElementItem from gaphor.diagram.nameditem import NamedItem from gaphor.diagram.compartment import CompartmentItem, FeatureItem from gaphor.diagram.classifier import ClassifierItem # General: from gaphor.diagram.comment import CommentItem from gaphor.diagram.commentline import CommentLineItem from gaphor.diagram.simpleitem import Line, Box, Ellipse # Classes: from gaphor.diagram.classes.klass import ClassItem, OperationItem from gaphor.diagram.classes.interface import InterfaceItem from gaphor.diagram.classes.package import PackageItem from gaphor.diagram.classes.association import AssociationItem from gaphor.diagram.classes.dependency import DependencyItem from gaphor.diagram.classes.generalization import GeneralizationItem from gaphor.diagram.classes.implementation import ImplementationItem # Components: from gaphor.diagram.artifact import ArtifactItem from gaphor.diagram.connector import ConnectorItem from gaphor.diagram.component import ComponentItem from gaphor.diagram.node import NodeItem from gaphor.diagram.components.subsystem import SubsystemItem # Actions: from gaphor.diagram.activitynodes import ActivityNodeItem from gaphor.diagram.activitynodes import InitialNodeItem, ActivityFinalNodeItem from gaphor.diagram.activitynodes import FlowFinalNodeItem from gaphor.diagram.activitynodes import DecisionNodeItem from gaphor.diagram.activitynodes import ForkNodeItem from gaphor.diagram.objectnode import ObjectNodeItem from gaphor.diagram.actions.action import ActionItem, SendSignalActionItem, AcceptEventActionItem from gaphor.diagram.actions.flow import FlowItem from gaphor.diagram.actions.partition import PartitionItem # Interactions from gaphor.diagram.interaction import InteractionItem from gaphor.diagram.lifeline import LifelineItem from gaphor.diagram.message import MessageItem # States from gaphor.diagram.states import VertexItem from gaphor.diagram.states.state import StateItem from gaphor.diagram.states.transition import TransitionItem from gaphor.diagram.states.finalstate import FinalStateItem from gaphor.diagram.states.pseudostates import InitialPseudostateItem, HistoryPseudostateItem # Use Cases: from gaphor.diagram.actor import ActorItem from gaphor.diagram.usecase import UseCaseItem from gaphor.diagram.include import IncludeItem from gaphor.diagram.extend import ExtendItem # Stereotypes: from gaphor.diagram.extension import ExtensionItem from gaphor.diagram.profiles.metaclass import MetaclassItem gaphor-0.17.2/gaphor/diagram/lifeline.py000066400000000000000000000163761220151210700201120ustar00rootroot00000000000000""" Lifeline diagram item. Implementation Details ====================== Represented Classifier ---------------------- It is not clear how to attach a connectable element to a lifeline. For now, ``Lifeline.represents`` is ``None``. Ideas: - drag and drop classifier from tree onto a lifeline - match lifeline's name with classifier's name (what about namespace?) - connect message to classifier, then classifier becomes a lifeline Destruction Event ----------------- Occurence specification is not implemented, therefore destruction event cannot be supported. Still, destruction event notation is shown at the bottom of the lifeline's lifetime when delete message is connected to a lifeline. """ from gaphas.item import SW, SE from gaphas.connector import Handle, LinePort from gaphas.solver import STRONG from gaphas.geometry import distance_line_point, Rectangle from gaphas.constraint import LessThanConstraint, EqualsConstraint, CenterConstraint, LineAlignConstraint from gaphor import UML from gaphor.diagram.nameditem import NamedItem from gaphor.diagram.style import ALIGN_CENTER, ALIGN_MIDDLE class LifetimePort(LinePort): def constraint(self, canvas, item, handle, glue_item): """ Create connection line constraint between item's handle and the port. """ line = canvas.project(glue_item, self.start, self.end) point = canvas.project(item, handle.pos) x, y = canvas.get_matrix_i2c(item).transform_point(*handle.pos) x, y = canvas.get_matrix_c2i(glue_item).transform_point(x, y) # keep message at the same distance from head or bottom of lifetime # line depending on situation height = self.end.y - self.start.y if y / height < 0.5: delta = y - self.start.y align = 0 else: delta = y - self.end.y align = 1 return LineAlignConstraint(line, point, align, delta) class LifetimeItem(object): """ Lifeline's lifetime object. Provides basic properties of lifeline's lifetime. :Attributes: top Top handle. bottom Bottom handle. port Lifetime connection port. visible Determines port visibility. min_length Minimum length of lifetime. length Length of lifetime. """ MIN_LENGTH = 10 MIN_LENGTH_VISIBLE = 3 * MIN_LENGTH def __init__(self): super(LifetimeItem, self).__init__() self.top = Handle(strength=STRONG - 1) self.bottom = Handle(strength=STRONG) self.top.movable = False self.top.visible = False self.port = LifetimePort(self.top.pos, self.bottom.pos) self.visible = False self._c_min_length = None # to be set by lifeline item def _set_length(self, length): """ Set lifeline's lifetime length. """ self.bottom.pos.y = self.top.pos.y + length length = property(lambda s: s.bottom.pos.y - s.top.pos.y, _set_length) def _set_min_length(self, length): assert self._c_min_length is not None self._c_min_length.delta = length min_length = property(lambda s: s._c_min_length.delta, _set_min_length) def _set_connectable(self, connectable): self.port.connectable = connectable self.bottom.movable = connectable connectable = property(lambda s: s.port.connectable, _set_connectable) def _is_visible(self): return self.length > self.MIN_LENGTH def _set_visible(self, visible): """ Set lifetime visibility. """ if visible: self.bottom.pos.y = self.top.pos.y + 3 * self.MIN_LENGTH else: self.bottom.pos.y = self.top.pos.y + self.MIN_LENGTH visible = property(_is_visible, _set_visible) class LifelineItem(NamedItem): """ Lifeline item. The item represents head of lifeline. Lifeline's lifetime is represented by `lifetime` instance. :Attributes: lifetime Lifeline's lifetime part. is_destroyed Check if delete message is connected. """ __uml__ = UML.Lifeline __style__ = { 'name-align': (ALIGN_CENTER, ALIGN_MIDDLE), } def __init__(self, id = None): NamedItem.__init__(self, id) self.is_destroyed = False self.lifetime = LifetimeItem() top = self.lifetime.top bottom = self.lifetime.bottom self._handles.append(top) self._handles.append(bottom) self._ports.append(self.lifetime.port) def setup_canvas(self): super(LifelineItem, self).setup_canvas() top = self.lifetime.top bottom = self.lifetime.bottom # create constraints to: # - keep bottom handle below top handle # - keep top and bottom handle in the middle of the head c1 = CenterConstraint(self._handles[SW].pos.x, self._handles[SE].pos.x, bottom.pos.x) c2 = EqualsConstraint(top.pos.x, bottom.pos.x, delta=0.0) c3 = EqualsConstraint(self._handles[SW].pos.y, top.pos.y, delta=0.0) self.lifetime._c_min_length = LessThanConstraint(top.pos.y, bottom.pos.y, delta=LifetimeItem.MIN_LENGTH) self.__constraints = (c1, c2, c3, self.lifetime._c_min_length) map(self.canvas.solver.add_constraint, self.__constraints) def teardown_canvas(self): super(LifelineItem, self).teardown_canvas() map(self.canvas.solver.remove_constraint, self.__constraints) def save(self, save_func): super(LifelineItem, self).save(save_func) save_func('lifetime-length', self.lifetime.length) def load(self, name, value): if name == 'lifetime-length': self.lifetime.bottom.pos.y = self.height + float(value) else: super(LifelineItem, self).load(name, value) def draw(self, context): """ Draw lifeline. Lifeline's head is always drawn. Lifeline's lifetime is drawn when lifetime is visible. """ super(LifelineItem, self).draw(context) cr = context.cairo cr.rectangle(0, 0, self.width, self.height) cr.stroke() if context.hovered or context.focused or self.lifetime.visible: top = self.lifetime.top bottom = self.lifetime.bottom cr = context.cairo cr.save() cr.set_dash((7.0, 5.0), 0) cr.move_to(top.pos.x, top.pos.y) cr.line_to(bottom.pos.x, bottom.pos.y) cr.stroke() cr.restore() # draw destruction event if self.is_destroyed: d1 = 8 d2 = d1 * 2 cr.move_to(bottom.pos.x - d1, bottom.pos.y - d2) cr.line_to(bottom.pos.x + d1, bottom.pos.y) cr.move_to(bottom.pos.x - d1, bottom.pos.y) cr.line_to(bottom.pos.x + d1, bottom.pos.y - d2) cr.stroke() def point(self, pos): """ Find distance to lifeline item. Distance to lifeline's head and lifeline's lifetime is calculated and minimum is returned. """ d1 = super(LifelineItem, self).point(pos) top = self.lifetime.top bottom = self.lifetime.bottom d2 = distance_line_point(top.pos, bottom.pos, pos)[0] return min(d1, d2) # vim:sw=4:et gaphor-0.17.2/gaphor/diagram/message.py000066400000000000000000000230521220151210700177340ustar00rootroot00000000000000""" Sequence and communication diagram messages. Messages are implemented according to UML 2.1.1 specification. Implementation Details ====================== Message sort is supported but occurence specification is not implemented. This means that model drawn on a diagram is not complete on UML datamodel level, still it is valid UML diagram (see Lifelines Diagram in UML specification, page 461). Reply Messages -------------- Different sources show that reply message has filled arrow, including UML 2.0. UML 2.1.1 specification says that reply message should be drawn with an open arrow. This is visible on examples in UML 2.0 and UML 2.1.1 specifications. Asynchronous Signal -------------------- It is not clear how to draw signals. It is usually drawn with a half-open arrow. This approach is used in Gaphor, too. Delete Message -------------- Different sources show that delete message has a "X" at the tail. It does not seem to be correct solution. A "X" should be shown at the end of lifeline's lifetime instead (see ``lifeline`` module documentation for more information). Events ------ Occurence specification is not implemented, therefore - no events implemented (i.e. destroy event) - no message sequence number on communication diagram Operations ---------- ``Lifeline.represents`` attribute is ``None``, so it is not possible to specify operation (or signal) for a message. Instead, one has to put operation information in message's name. See also ``lifeline`` module documentation. """ from math import pi from gaphas.util import path_ellipse from gaphor import UML from gaphor.diagram.diagramline import NamedLine from gaphor.misc.odict import odict from gaphor.diagram.style import ALIGN_CENTER, ALIGN_BOTTOM PI_2 = pi / 2 class MessageItem(NamedLine): """ Message item is drawn on sequence and communication diagrams. On communication diagram, message item is decorated with an arrow in the middle of a line. Attributes: - _is_communication: check if message is on communication diagram - _arrow_pos: decorating arrow position - _arrow_angle: decorating arrow angle """ __style__ = { 'name-align-str': ':', } # name padding on sequence diagram SD_PADDING = NamedLine.style.name_padding # name padding on communication diagram CD_PADDING = (10, 10, 10, 10) def __init__(self, id=None): super(MessageItem, self).__init__(id) self._is_communication = False self._arrow_pos = 0, 0 self._arrow_angle = 0 self._messages = odict() self._inverted_messages = odict() def pre_update(self, context): """ Update communication diagram information. """ self._is_communication = self.is_communication() if self._is_communication: self._name.style.text_padding = self.CD_PADDING else: self._name.style.text_padding = self.SD_PADDING super(MessageItem, self).pre_update(context) def post_update(self, context): """ Update communication diagram information. """ super(MessageItem, self).post_update(context) if self._is_communication: pos, angle = self._get_center_pos() self._arrow_pos = pos self._arrow_angle = angle def save(self, save_func): save_func('message', list(self._messages), reference=True) save_func('inverted', list(self._inverted_messages), reference=True) super(MessageItem, self).save(save_func) def load(self, name, value): if name == 'message': #print 'message! value =', value self.add_message(value, False) elif name == 'inverted': #print 'inverted! value =', value self.add_message(value, True) else: super(MessageItem, self).load(name, value) def postload(self): for message in self._messages: self.set_message_text(message, message.name, False) for message in self._inverted_messages: self.set_message_text(message, message.name, True) super(MessageItem, self).postload() def _draw_circle(self, cr): """ Draw circle for lost/found messages. """ # method is called by draw_head or by draw_tail methods, # so draw in (0, 0)) cr.set_line_width(0.01) cr.arc(0.0, 0.0, 4, 0.0, 2 * pi) cr.fill() def _draw_arrow(self, cr, half=False, filled=True): """ Draw an arrow. Parameters: - half: draw half-open arrow - filled: draw filled arrow """ cr.move_to(15, 6) cr.line_to(0, 0) if not half: cr.line_to(15, -6) if filled: cr.close_path() cr.fill_preserve() def draw_head(self, context): cr = context.cairo # no head drawing in case of communication diagram if self._is_communication: cr.move_to(0, 0) return cr.move_to(0, 0) subject = self.subject if subject and subject.messageKind == 'found': self._draw_circle(cr) cr.stroke() cr.move_to(0, 0) def draw_tail(self, context): cr = context.cairo # no tail drawing in case of communication diagram if self._is_communication: cr.line_to(0, 0) return subject = self.subject if subject and subject.messageSort in ('createMessage', 'reply'): cr.set_dash((7.0, 5.0), 0) cr.line_to(0, 0) cr.stroke() cr.set_dash((), 0) if subject: w = cr.get_line_width() if subject.messageKind == 'lost': self._draw_circle(cr) cr.stroke() cr.set_line_width(w) half = subject.messageSort == 'asynchSignal' filled = subject.messageSort in ('synchCall', 'deleteMessage') self._draw_arrow(cr, half, filled) else: self._draw_arrow(cr) cr.stroke() def _draw_decorating_arrow(self, cr, inverted=False): cr.save() try: angle = self._arrow_angle hint = -1 # rotation hint, keep arrow on the same side as message text # elements if abs(angle) >= PI_2 and angle != -PI_2: hint = 1 if inverted: angle += hint * pi x, y = self._arrow_pos # move to arrow pos and rotate, below we operate in horizontal # mode cr.translate(x, y) cr.rotate(angle) # add some padding cr.translate(0, 6 * hint) # draw decorating arrow d = 15 dr = d - 4 r = 3 cr.set_line_width(1.5) cr.move_to(-d, 0) cr.line_to(d, 0) cr.line_to(dr, r) cr.move_to(dr, -r) cr.line_to(d, 0) cr.stroke() finally: cr.restore() def draw(self, context): super(MessageItem, self).draw(context) # on communication diagram draw decorating arrows for messages and # inverted messages if self._is_communication: cr = context.cairo self._draw_decorating_arrow(cr) if len(self._inverted_messages) > 0: self._draw_decorating_arrow(cr, True) def is_communication(self): """ Check if message is connecting to lifelines on communication diagram. """ canvas = self.canvas c1 = canvas.get_connection(self.head) c2 = canvas.get_connection(self.tail) return c1 and not c1.connected.lifetime.visible \ or c2 and not c2.connected.lifetime.visible def add_message(self, message, inverted): """ Add message onto communication diagram. """ if inverted: messages = self._inverted_messages style = { 'text-align-group': 'inverted', 'text-align': (ALIGN_CENTER, ALIGN_BOTTOM), } else: messages = self._messages group = 'stereotype' style = { 'text-align-group': 'stereotype', } style['text-align-str'] = ':' style['text-padding'] = self.CD_PADDING txt = self.add_text('name', style=style) txt.text = message.name messages[message] = txt self.request_update() def remove_message(self, message, inverted): """ Remove message from communication diagram. """ if inverted: messages = self._inverted_messages else: messages = self._messages txt = messages[message] self.remove_text(txt) del messages[message] self.request_update() def set_message_text(self, message, text, inverted): """ Set text of message on communication diagram. """ if inverted: messages = self._inverted_messages else: messages = self._messages messages[message].text = text self.request_update() def swap_messages(self, m1, m2, inverted): """ Swap order of two messages on communication diagram. """ if inverted: messages = self._inverted_messages else: messages = self._messages t1 = messages[m1] t2 = messages[m2] self.swap_texts(t1, t2) messages.swap(m1, m2) self.request_update() return True # vim:sw=4:et gaphor-0.17.2/gaphor/diagram/nameditem.py000066400000000000000000000100631220151210700202510ustar00rootroot00000000000000""" Base classes related to items, which represent UML classes deriving from NamedElement. """ from gaphor import UML from gaphor.UML.interfaces import IAttributeChangeEvent from gaphor.diagram.elementitem import ElementItem from gaphor.diagram.style import get_min_size, ALIGN_CENTER, ALIGN_TOP class NamedItem(ElementItem): __style__ = { 'min-size' : (100, 50), 'from-font' : 'sans 8', 'name-font' : 'sans 10', 'name-align' : (ALIGN_CENTER, ALIGN_TOP), 'name-padding': (5, 10, 5, 10), 'name-outside': False, 'name-align-str': None, 'name-rotated': False, } def __init__(self, id=None): """ Create named item. """ ElementItem.__init__(self, id) # create (from ...) text to distinguish diagram items from # different namespace self._from = self.add_text('from', pattern='(from %s)', style={'text-align-group': 'stereotype', 'font': self.style.from_font }, visible=self.is_namespace_info_visible) self._name = self.add_text('name', style={ 'font': self.style.name_font, 'text-align': self.style.name_align, 'text-padding': self.style.name_padding, 'text-outside': self.style.name_outside, 'text-rotated': self.style.name_rotated, 'text-align-str': self.style.name_align_str, 'text-align-group': 'stereotype', }, editable=True) # size of stereotype, namespace and name text self._header_size = 0, 0 self.watch('subject.name', self.on_named_element_name)\ .watch('subject.namespace', self.on_named_element_namespace) def postload(self): self.on_named_element_name(None) self.on_named_element_namespace(None) super(NamedItem, self).postload() def is_namespace_info_visible(self): """ Display name space info when it is different, then diagram's or parent's namespace. """ subject = self.subject canvas = self.canvas if not subject or not canvas: return False if not self._name.is_visible(): return False namespace = subject.namespace parent = canvas.get_parent(self) # if there is a parent (i.e. interaction) if parent and parent.subject \ and parent.subject.namespace is not namespace: return False return self._from.text and namespace is not canvas.diagram.namespace def on_named_element_name(self, event): """ Callback to be invoked, when named element name is changed. """ if self.subject: self._name.text = self.subject.name self.request_update() def on_named_element_namespace(self, event): """ Add a line '(from ...)' to the class item if subject's namespace is not the same as the namespace of this diagram. """ subject = self.subject if subject and subject.namespace: self._from.text = subject.namespace.name else: self._from.text = '' self.request_update() def pre_update(self, context): """ Calculate minimal size and header size. """ super(NamedItem, self).pre_update(context) style = self._name.style # we can determine minimal size and header size only # when name is aligned inside an item if not style.text_outside: # at this stage stereotype text group should be already updated assert 'stereotype' in self._text_groups_sizes nw, nh = self._text_groups_sizes['stereotype'] self._header_size = get_min_size(nw, nh, self.style.name_padding) self.min_width = max(self.style.min_size[0], self._header_size[0]) self.min_height = max(self.style.min_size[1], self._header_size[1]) # vim:sw=4:et:ai gaphor-0.17.2/gaphor/diagram/node.py000066400000000000000000000025541220151210700172410ustar00rootroot00000000000000""" Node item may represent a node or a device UML metamodel classes. Grouping ======== Node item can group following items - other nodes, which are represented with Node.nestedNode on UML metamodel level - deployed artifacts using deployment - components, which are parts of a node acting as structured classifier (nodes may have internal structures) Node item grouping logic is implemented in `gaphor.adapters.grouping` module. """ from gaphor import UML from gaphor.diagram.classifier import ClassifierItem class NodeItem(ClassifierItem): """ Representation of node or device from UML Deployment package. """ __uml__ = UML.Node, UML.Device __stereotype__ = { 'device': UML.Device, } DEPTH = 10 def __init__(self, id=None): ClassifierItem.__init__(self, id) self.drawing_style = self.DRAW_COMPARTMENT self.height = 50 self.width = 120 def draw_compartment(self, context): cr = context.cairo cr.save() super(NodeItem, self).draw_compartment(context) cr.restore() d = self.DEPTH w = self.width h = self.height cr.move_to(0, 0) cr.line_to(d, -d) cr.line_to(w + d, -d) cr.line_to(w + d, h - d) cr.line_to(w, h) cr.move_to(w, 0) cr.line_to(w + d, -d) cr.stroke() # vim:sw=4:et gaphor-0.17.2/gaphor/diagram/objectnode.py000066400000000000000000000067661220151210700204410ustar00rootroot00000000000000""" Object node item. """ import itertools from gaphas.state import observed, reversible_property from gaphor import UML from gaphor.core import inject from gaphor.diagram.nameditem import NamedItem from gaphor.diagram.style import ALIGN_CENTER, ALIGN_BOTTOM DEFAULT_UPPER_BOUND = '*' class ObjectNodeItem(NamedItem): """ Representation of object node. Object node is ordered and has upper bound specification. Ordering information can be hidden by user. """ element_factory = inject('element_factory') __uml__ = UML.ObjectNode STYLE_BOTTOM = { 'text-align': (ALIGN_CENTER, ALIGN_BOTTOM), 'text-outside': True, 'text-align-group': 'bottom', } def __init__(self, id = None): NamedItem.__init__(self, id) self._show_ordering = False self._upper_bound = self.add_text('upperBound', pattern='{ upperBound = %s }', style=self.STYLE_BOTTOM, visible=self.is_upper_bound_visible) self._ordering = self.add_text('ordering', pattern = '{ ordering = %s }', style = self.STYLE_BOTTOM, visible=self._get_show_ordering) self.watch('subject.upperBound', self.on_object_node_upper_bound)\ .watch('subject.ordering', self.on_object_node_ordering) def on_object_node_ordering(self, event): if self.subject: self._ordering.text = self.subject.ordering self.request_update() def on_object_node_upper_bound(self, event): subject = self.subject if subject and subject.upperBound: self._upper_bound.text = subject.upperBound self.request_update() def is_upper_bound_visible(self): """ Do not show upper bound, when it's set to default value. """ subject = self.subject return subject and subject.upperBound != DEFAULT_UPPER_BOUND @observed def _set_show_ordering(self, value): self._show_ordering = value self.request_update() def _get_show_ordering(self): return self._show_ordering show_ordering = reversible_property(_get_show_ordering, _set_show_ordering) def save(self, save_func): save_func('show-ordering', self._show_ordering) super(ObjectNodeItem, self).save(save_func) def load(self, name, value): if name == 'show-ordering': self._show_ordering = eval(value) else: super(ObjectNodeItem, self).load(name, value) def postload(self): if self.subject and self.subject.upperBound: self._upper_bound.text = self.subject.upperBound if self.subject and self._show_ordering: self.set_ordering(self.subject.ordering) super(ObjectNodeItem, self).postload() def draw(self, context): cr = context.cairo cr.rectangle(0, 0, self.width, self.height) cr.stroke() super(ObjectNodeItem, self).draw(context) def set_upper_bound(self, value): """ Set upper bound value of object node. """ subject = self.subject if subject: if not value: value = DEFAULT_UPPER_BOUND subject.upperBound = value #self._upper_bound.text = value def set_ordering(self, value): """ Set object node ordering value. """ subject = self.subject subject.ordering = value self._ordering.text = value # vim:sw=4:et:ai gaphor-0.17.2/gaphor/diagram/profiles/000077500000000000000000000000001220151210700175575ustar00rootroot00000000000000gaphor-0.17.2/gaphor/diagram/profiles/__init__.py000066400000000000000000000000001220151210700216560ustar00rootroot00000000000000gaphor-0.17.2/gaphor/diagram/profiles/metaclass.py000066400000000000000000000004061220151210700221050ustar00rootroot00000000000000""" Metaclass item for Metaclass UML metaclass :) from profiles. """ from gaphor.diagram.classes.klass import ClassItem from gaphor.diagram import uml from gaphor import UML @uml(UML.Component, stereotype='metaclass') class MetaclassItem(ClassItem): pass gaphor-0.17.2/gaphor/diagram/simpleitem.py000066400000000000000000000104431220151210700204600ustar00rootroot00000000000000""" Trivial drawing aids (box, line, ellipse). """ from gaphas.item import Line as _Line from gaphas.item import Element, NW from gaphas.util import path_ellipse from style import Style class Line(_Line): __style__ = { 'line-width': 2, 'line-color': (0, 0, 0, 1), } def __init__(self, id=None): super(Line, self).__init__() self.style = Style(Line.__style__) self._id = id self.fuzziness = 2 self._handles[0].connectable = False self._handles[-1].connectable = False id = property(lambda self: self._id, doc='Id') def save (self, save_func): save_func('matrix', tuple(self.matrix)) for prop in ('orthogonal', 'horizontal'): save_func(prop, getattr(self, prop)) points = [ ] for h in self.handles(): points.append(tuple(map(float, h.pos))) save_func('points', points) def load (self, name, value): if name == 'matrix': self.matrix = eval(value) elif name == 'points': points = eval(value) for x in xrange(len(points) - 2): h = self._create_handle((0, 0)) self._handles.insert(1, h) for i, p in enumerate(points): self.handles()[i].pos = p self._update_ports() elif name == 'horizontal': self.horizontal = eval(value) elif name == 'orthogonal': self._load_orthogonal = eval(value) def postload(self): if hasattr(self, '_load_orthogonal'): self.orthogonal = self._load_orthogonal del self._load_orthogonal def draw(self, context): cr = context.cairo style = self.style cr.set_line_width(style.line_width) cr.set_source_rgba(*style.line_color) super(Line, self).draw(context) class Box(Element): """ A Box has 4 handles (for a start):: NW +---+ NE SW +---+ SE """ __style__ = { 'border-width': 2, 'border-color': (0, 0, 0, 1), 'fill-color': (1, 1, 1, 0), } def __init__(self, id=None): super(Box, self).__init__(10, 10) self.style = Style(Box.__style__) self._id = id id = property(lambda self: self._id, doc='Id') def save(self, save_func): save_func('matrix', tuple(self.matrix)) save_func('width', self.width) save_func('height', self.height) def load(self, name, value): if name == 'matrix': self.matrix = eval(value) elif name == 'width': self.width = eval(value) elif name == 'height': self.height = eval(value) def postload(self): pass def draw(self, context): cr = context.cairo nw = self._handles[NW] style = self.style cr.rectangle(nw.pos.x, nw.pos.y, self.width, self.height) cr.set_source_rgba(*style.fill_color) cr.fill_preserve() cr.set_source_rgba(*style.border_color) cr.set_line_width(style.border_width) cr.stroke() class Ellipse(Element): """ """ __style__ = { 'border-width': 2, 'border-color': (0, 0, 0, 1), 'fill-color': (1, 1, 1, 0), } def __init__(self, id=None): super(Ellipse, self).__init__() self.style = Style(Ellipse.__style__) self._id = id id = property(lambda self: self._id, doc='Id') def save(self, save_func): save_func('matrix', tuple(self.matrix)) save_func('width', self.width) save_func('height', self.height) def load(self, name, value): if name == 'matrix': self.matrix = eval(value) elif name == 'width': self.width = eval(value) elif name == 'height': self.height = eval(value) def postload(self): pass def draw(self, context): cr = context.cairo nw = self._handles[NW] style = self.style rx = self.width / 2. ry = self.height / 2. cr.move_to(self.width, ry) path_ellipse(cr, rx, ry, self.width, self.height) cr.set_source_rgba(*style.fill_color) cr.fill_preserve() cr.set_source_rgba(*style.border_color) cr.set_line_width(style.border_width) cr.stroke() # vim:sw=4:et:ai gaphor-0.17.2/gaphor/diagram/states/000077500000000000000000000000001220151210700172375ustar00rootroot00000000000000gaphor-0.17.2/gaphor/diagram/states/__init__.py000066400000000000000000000021161220151210700213500ustar00rootroot00000000000000""" Package gaphor.diagram.states implements diagram items for UML state machines. Pseudostates ============ There are some similarities between activities and state machines, for example - initial node and initial psuedostate - final node and final state Of course, they differ in many aspects, but the similarites drive user interface of state machines. This is with respect of minimalization of the set of diagram items (i.e. there is only one diagram item for both join and fork nodes in activities implemented in Gaphor). There are separate diagram items for pseudostates - initial pseudostate item as there exists initial node item @todo: Probably, history pseudostates will be implemented as one diagram item with an option deep/shallow. [This section is going to be extended as we start to implement more pseudostates]. """ from gaphor.diagram.nameditem import NamedItem class VertexItem(NamedItem): """ Abstract class for all vertices. All state, pseudostate items derive from VertexItem, which simplifies transition connection adapters. """ pass gaphor-0.17.2/gaphor/diagram/states/finalstate.py000066400000000000000000000021001220151210700217340ustar00rootroot00000000000000""" Final state diagram item. """ from gaphor import UML from gaphor.diagram.style import ALIGN_RIGHT, ALIGN_BOTTOM from gaphas.util import path_ellipse from gaphor.diagram.states import VertexItem class FinalStateItem(VertexItem): __uml__ = UML.FinalState __style__ = { 'min-size': (30, 30), 'name-align': (ALIGN_RIGHT, ALIGN_BOTTOM), 'name-padding': (2, 2, 2, 2), 'name-outside': True, } RADIUS_1 = 10 RADIUS_2 = 15 def __init__(self, id=None): super(FinalStateItem, self).__init__(id) for h in self.handles(): h.movable = False def draw(self, context): """ Draw final state symbol. """ cr = context.cairo r = self.RADIUS_2 + 1 d = self.RADIUS_1 * 2 path_ellipse(cr, r, r, d, d) cr.set_line_width(0.01) cr.fill() d = r * 2 path_ellipse(cr, r, r, d, d) cr.set_line_width(0.01) cr.set_line_width(2) cr.stroke() super(FinalStateItem, self).draw(context) # vim:sw=4:et gaphor-0.17.2/gaphor/diagram/states/pseudostates.py000066400000000000000000000036141220151210700223400ustar00rootroot00000000000000""" Pseudostate diagram items. See also gaphor.diagram.states package description. """ from gaphor import UML from gaphor.diagram.style import ALIGN_LEFT, ALIGN_TOP from gaphas.util import path_ellipse from gaphor.diagram.textelement import text_center from gaphor.diagram.states import VertexItem class InitialPseudostateItem(VertexItem): """ Initial pseudostate diagram item. """ __uml__ = UML.Pseudostate __style__ = { 'min-size': (20, 20), 'name-align': (ALIGN_LEFT, ALIGN_TOP), 'name-padding': (2, 2, 2, 2), 'name-outside': True, } RADIUS = 10 def __init__(self, id=None): super(InitialPseudostateItem, self).__init__(id) for h in self.handles(): h.movable = False def draw(self, context): """ Draw intial pseudostate symbol. """ super(InitialPseudostateItem, self).draw(context) cr = context.cairo r = self.RADIUS d = r * 2 path_ellipse(cr, r, r, d, d) cr.set_line_width(0.01) cr.fill() class HistoryPseudostateItem(VertexItem): """ History pseudostate diagram item. """ __uml__ = UML.Pseudostate __style__ = { 'min-size': (30, 30), 'name-align': (ALIGN_LEFT, ALIGN_TOP), 'name-padding': (2, 2, 2, 2), 'name-outside': True, } RADIUS = 15 def __init__(self, id=None): super(HistoryPseudostateItem, self).__init__(id) for h in self.handles(): h.movable = False def draw(self, context): """ Draw intial pseudostate symbol. """ super(HistoryPseudostateItem, self).draw(context) cr = context.cairo r = self.RADIUS d = r * 2 path_ellipse(cr, r, r, d, d) #cr.set_line_width(1) cr.stroke() text_center(cr, r, r, "H", self.style.name_font) # vim:sw=4:et gaphor-0.17.2/gaphor/diagram/states/state.py000066400000000000000000000061541220151210700207370ustar00rootroot00000000000000""" State diagram item. """ import operator from gaphor import UML from gaphor.diagram.style import ALIGN_LEFT, ALIGN_CENTER, ALIGN_TOP from gaphor.diagram.states import VertexItem from gaphor.diagram.classifier import CompartmentItem from gaphor.diagram.compartment import FeatureItem from gaphor.core import inject DX = 15 DY = 8 DDX = 0.4 * DX DDY = 0.4 * DY class StateItem(CompartmentItem, VertexItem): element_factory = inject('element_factory') __uml__ = UML.State __style__ = { 'min-size': (50, 30), 'name-align': (ALIGN_CENTER, ALIGN_TOP), 'extra-space': 'compartment', } def __init__(self, id): super(StateItem, self).__init__(id) self.drawing_style = self.DRAW_COMPARTMENT self._activities = self.create_compartment('activities') self._activities.use_extra_space = True # non-visible by default, show when at least one item is visible self._activities.visible = False self._entry = FeatureItem(pattern='entry / %s', order=1) self._exit = FeatureItem(pattern='exit / %s', order=2) self._do_activity = FeatureItem(pattern='do / %s', order=3) def _set_activity(self, act, attr, text): if text and act not in self._activities: self._activities.append(act) act.subject = self.element_factory.create(UML.Activity) act.subject.name = text setattr(self.subject, attr, act.subject) # sort the activities according to defined order self._activities.sort(key=operator.attrgetter('order')) elif text and act in self._activities: act.subject.name = text elif not text and act in self._activities: self._activities.remove(act) act.subject.unlink() self._activities.visible = len(self._activities) > 0 self.request_update() def set_entry(self, text): self._set_activity(self._entry, 'entry', text) def set_exit(self, text): self._set_activity(self._exit, 'exit', text) def set_do_activity(self, text): self._set_activity(self._do_activity, 'doActivity', text) def postload(self): super(StateItem, self).postload() if self.subject.entry: self.set_entry(self.subject.entry.name) if self.subject.exit: self.set_exit(self.subject.exit.name) if self.subject.doActivity: self.set_do_activity(self.subject.doActivity.name) def draw_compartment_border(self, context): """ Draw state item. """ c = context.cairo c.move_to(0, DY) c.curve_to(0, DDY, DDX, 0, DX, 0) c.line_to(self.width - DX, 0) c.curve_to(self.width - DDX, 0, self.width, DDY, self.width, DY) c.line_to(self.width, self.height - DY) c.curve_to(self.width, self.height - DDY, self.width - DDX, self.height, self.width - DX, self.height) c.line_to(DX, self.height) c.curve_to(DDX, self.height, 0, self.height - DDY, 0, self.height - DY) c.close_path() c.stroke() # vim:sw=4:et gaphor-0.17.2/gaphor/diagram/states/tests/000077500000000000000000000000001220151210700204015ustar00rootroot00000000000000gaphor-0.17.2/gaphor/diagram/states/tests/__init__.py000066400000000000000000000000001220151210700225000ustar00rootroot00000000000000gaphor-0.17.2/gaphor/diagram/states/tests/test_pseudostates.py000066400000000000000000000015461220151210700245430ustar00rootroot00000000000000""" Test pseudostates. """ from gaphor import UML from gaphor.diagram.states.pseudostates import InitialPseudostateItem, HistoryPseudostateItem from gaphor.tests.testcase import TestCase class InitialPseudostate(TestCase): """ Initial pseudostate item test cases. """ def test_initial_pseudostate(self): """Test creation of initial pseudostate """ item = self.create(InitialPseudostateItem, UML.Pseudostate) self.assertEquals('initial', item.subject.kind) def test_history_pseudostate(self): """Test creation of initial pseudostate """ item = self.create(HistoryPseudostateItem, UML.Pseudostate) # history setting is done in the DiagramToolbox factory: item.subject.kind = 'shallowHistory' self.assertEquals('shallowHistory', item.subject.kind) # vim:sw=4:et:ai gaphor-0.17.2/gaphor/diagram/states/tests/test_states.py000066400000000000000000000035131220151210700233170ustar00rootroot00000000000000""" Test state items. """ from gaphor import UML from gaphor.diagram.states.state import StateItem from gaphor.tests.testcase import TestCase class StateTestCase(TestCase): def test_state(self): """Test creation of states """ self.create(StateItem, UML.State) def test_activities_persistence(self): """Test state activities saving/loading """ # all activities s1 = self.create(StateItem, UML.State) s1.subject.name = 's1' s1.set_entry('test 1 entry') s1.set_exit('test 1 exit') s1.set_do_activity('test 1 do') # not all activities s2 = self.create(StateItem, UML.State) s2.subject.name = 's2' s2.set_entry('test 2 entry') s2.set_do_activity('test 2 do') data = self.save() self.load(data) states = self.diagram.canvas.select(lambda e: isinstance(e, StateItem)) self.assertEquals(2, len(states)) s1, s2 = states if s1.subject.name == 's2': s1, s2 = s2, s1 self.assertEquals('test 1 entry', s1.subject.entry.name) self.assertEquals('test 1 exit', s1.subject.exit.name) self.assertEquals('test 1 do', s1.subject.doActivity.name) self.assertEquals(3, len(s1._activities)) self.assertTrue(s1._entry in s1._activities) self.assertTrue(s1._exit in s1._activities) self.assertTrue(s1._do_activity in s1._activities) self.assertEquals('test 2 entry', s2.subject.entry.name) self.assertTrue(s2.subject.exit is None) self.assertEquals('test 2 do', s2.subject.doActivity.name) self.assertEquals(2, len(s2._activities)) self.assertTrue(s2._entry in s2._activities) self.assertFalse(s2._exit in s2._activities) self.assertTrue(s2._do_activity in s2._activities) gaphor-0.17.2/gaphor/diagram/states/tests/test_transition.py000066400000000000000000000015671220151210700242150ustar00rootroot00000000000000""" Test transitions. """ from gaphor import UML from gaphor.diagram.states.transition import TransitionItem from gaphor.tests.testcase import TestCase class TransitionTestCase(TestCase): """ Test the working of transitions """ def test_transition_guard(self): """Test events of transition.guard. """ item = self.create(TransitionItem, UML.Transition) assert item._guard.text == '' c = self.element_factory.create(UML.Constraint) c.specification = 'blah' assert item._guard.text == '' item.subject.guard = c assert item.subject.guard is c assert item._guard.text == 'blah', item._guard.text del c.specification assert item._guard.text == '', item._guard.text c.specification = 'foo' assert item._guard.text == 'foo', item._guard.text # vim:sw=4:et:ai gaphor-0.17.2/gaphor/diagram/states/transition.py000066400000000000000000000026451220151210700220120ustar00rootroot00000000000000""" State transition implementation. """ from gaphor import UML from gaphor.core import inject from gaphor.diagram.diagramline import NamedLine from gaphor.diagram.style import ALIGN_LEFT, ALIGN_RIGHT, ALIGN_TOP class TransitionItem(NamedLine): """ Representation of state transition. """ __uml__ = UML.Transition __style__ = { 'name-align': (ALIGN_RIGHT, ALIGN_TOP), 'name-padding': (5, 15, 5, 5), } element_factory = inject('element_factory') def __init__(self, id = None): NamedLine.__init__(self, id) self._guard = self.add_text('guard.specification', editable=True) self.watch('subject.guard.specification', self.on_guard) def postload(self): """ Load guard specification information. """ try: self._guard.text = self.subject.guard.specification or '' except AttributeError: self._guard.text = '' super(TransitionItem, self).postload() def on_guard(self, event): try: self._guard.text = self.subject.guard.specification or '' except AttributeError: self._guard.text = '' self.request_update() def draw_tail(self, context): cr = context.cairo cr.line_to(0, 0) cr.stroke() cr.move_to(15, -6) cr.line_to(0, 0) cr.line_to(15, 6) # vim:sw=4:et:ai gaphor-0.17.2/gaphor/diagram/style.py000066400000000000000000000166701220151210700174600ustar00rootroot00000000000000""" Style classes and constants. """ from math import pi # padding PADDING_TOP, PADDING_RIGHT, PADDING_BOTTOM, PADDING_LEFT = range(4) # horizontal align ALIGN_LEFT, ALIGN_CENTER, ALIGN_RIGHT = -1, 0, 1 # vertical align ALIGN_TOP, ALIGN_MIDDLE, ALIGN_BOTTOM = -1, 0, 1 # hint tuples to move text depending on quadrant WIDTH_HINT = (0, 0, -1) # width hint tuple R_WIDTH_HINT = (-1, -1, 0) # width hint tuple PADDING_HINT = (1, 1, -1) # padding hint tuple EPSILON = 1e-6 class Style(object): """ Item style information. Style information is provided through object's attributes, i.e.:: >>> from gaphor.diagram import DiagramItemMeta >>> class InitialNodeItem(object): ... __metaclass__ = DiagramItemMeta ... __style__ = { ... 'name-align': ('center', 'top'), ... } is translated to:: >>> print InitialNodeItem().style.name_align ('center', 'top') """ def __init__(self, *args, **kwargs): super(Style, self).__init__() for d in args: self.update(d) if kwargs: self.update(kwargs) def add(self, name, value): """ Add style variable. Variable name can contain hyphens, which is converted to underscode, i.e. 'name-align' -> 'name_align'. @param name: style variable name @param value: style variable value """ name = name.replace('-', '_') setattr(self, name, value) def update(self, style): for name, value in style.items(): self.add(name, value) def items(self): """ Return iterator of (name, value) style information items. """ return self.__dict__.iteritems() def get_min_size(width, height, padding): """ Get minimal size of an object using padding information. @param width: object width @param height: object height @param padding: padding information as a tuple (top, right, bottom, left) """ width += padding[PADDING_LEFT] + padding[PADDING_RIGHT] height += padding[PADDING_TOP] + padding[PADDING_BOTTOM] return width, height def get_text_point(extents, width, height, align, padding, outside): """ Calculate position of the text relative to containing box defined by tuple (0, 0, width, height). Text is aligned using align and padding information. It can be also placed outside the box if ``outside'' parameter is set to ``True''. Parameters: - extents: text extents like width, height, etc. - width: width of the containing box - height: height of the containing box - align: text align information (center, top, etc.) - padding: text padding - outside: should text be put outside containing box """ #x_bear, y_bear, w, h, x_adv, y_adv = extents w, h = extents halign, valign = align if outside: if halign == ALIGN_LEFT: x = -w - padding[PADDING_LEFT] elif halign == ALIGN_CENTER: x = (width - w) / 2 elif halign == ALIGN_RIGHT: x = width + padding[PADDING_RIGHT] else: assert False if valign == ALIGN_TOP: y = -h -padding[PADDING_TOP] elif valign == ALIGN_MIDDLE: y = (height - h) / 2 elif valign == ALIGN_BOTTOM: y = height + padding[PADDING_BOTTOM] else: assert False else: if halign == ALIGN_LEFT: x = padding[PADDING_LEFT] elif halign == ALIGN_CENTER: x = (width - w) / 2 + padding[PADDING_LEFT] - padding[PADDING_RIGHT] elif halign == ALIGN_RIGHT: x = width - w - padding[PADDING_RIGHT] else: assert False if valign == ALIGN_TOP: y = padding[PADDING_TOP] elif valign == ALIGN_MIDDLE: y = (height - h) / 2 elif valign == ALIGN_BOTTOM: y = height - h - padding[PADDING_BOTTOM] else: assert False return x, y def get_text_point_at_line(extents, p1, p2, align, padding): """ Calculate position of the text relative to a line defined by points (p1, p2). Text is aligned using align and padding information. Parameters: - extents: text extents like width, height, etc. - p1: beginning of line - p2: end of line - align: text align information (center, top, etc.) - padding: text padding """ name_dx = 0.0 name_dy = 0.0 ofs = 5 dx = float(p2[0]) - float(p1[0]) dy = float(p2[1]) - float(p1[1]) name_w, name_h = extents if dy == 0: rc = 1000.0 # quite a lot... else: rc = dx / dy abs_rc = abs(rc) h = dx > 0 # right side of the box v = dy > 0 # bottom side if abs_rc > 6: # horizontal line if h: name_dx = ofs name_dy = -ofs - name_h else: name_dx = -ofs - name_w name_dy = -ofs - name_h elif 0 <= abs_rc <= 0.2: # vertical line if v: name_dx = -ofs - name_w name_dy = ofs else: name_dx = -ofs - name_w name_dy = -ofs - name_h else: # Should both items be placed on the same side of the line? r = abs_rc < 1.0 # Find out alignment of text (depends on the direction of the line) align_left = (h and not r) or (r and not h) align_bottom = (v and not r) or (r and not v) if align_left: name_dx = ofs else: name_dx = -ofs - name_w if align_bottom: name_dy = -ofs - name_h else: name_dy = ofs return p1[0] + name_dx, p1[1] + name_dy def get_text_point_at_line2(extents, p1, p2, align, padding): """ Calculate position of the text relative to a line defined by points (p1, p2). Text is aligned using align and padding information. TODO: merge with get_text_point_at_line function Parameters: - extents: text extents like width, height, etc. - p1: beginning of line - p2: end of line - align: text align information (center, top, etc.) - padding: text padding """ x0 = (p1[0] + p2[0]) / 2.0 y0 = (p1[1] + p2[1]) / 2.0 dx = p2[0] - p1[0] dy = p2[1] - p1[1] if abs(dx) < EPSILON: d1 = -1.0 d2 = 1.0 elif abs(dy) < EPSILON: d1 = 0.0 d2 = 0.0 else: d1 = dy / dx d2 = abs(d1) width, height = extents halign, valign = align # move to center and move by delta depending on line angle if d2 < 0.5774: # <0, 30>, <150, 180>, <-180, -150>, <-30, 0> # horizontal mode w2 = width / 2.0 hint = w2 * d2 x = x0 - w2 if valign == ALIGN_TOP: y = y0 - height - padding[PADDING_BOTTOM] - hint else: y = y0 + padding[PADDING_TOP] + hint else: # much better in case of vertical lines # determine quadrant, we are interested in 1 or 3 and 2 or 4 # see hint tuples below h2 = height / 2.0 q = cmp(d1, 0) if abs(dx) < EPSILON: hint = 0 else: hint = h2 / d2 if valign == ALIGN_TOP: x = x0 + PADDING_HINT[q] * (padding[PADDING_LEFT] + hint) + width * WIDTH_HINT[q] else: x = x0 - PADDING_HINT[q] * (padding[PADDING_RIGHT] + hint) + width * R_WIDTH_HINT[q] y = y0 - h2 return x, y # vim:sw=4:et gaphor-0.17.2/gaphor/diagram/tests/000077500000000000000000000000001220151210700170765ustar00rootroot00000000000000gaphor-0.17.2/gaphor/diagram/tests/__init__.py000066400000000000000000000000151220151210700212030ustar00rootroot00000000000000# unit tests gaphor-0.17.2/gaphor/diagram/tests/test_activitynodes.py000066400000000000000000000037211220151210700233770ustar00rootroot00000000000000import gaphor.UML as UML from gaphor.diagram import items from gaphor.tests.testcase import TestCase class ActivityNodesTestCase(TestCase): def test_decision_node(self): """Test creation of decision node """ self.create(items.DecisionNodeItem, UML.DecisionNode) def test_fork_node(self): """Test creation of fork node """ self.create(items.ForkNodeItem, UML.ForkNode) def test_decision_node_persistence(self): """Test saving/loading of decision node """ factory = self.element_factory item = self.create(items.DecisionNodeItem, UML.DecisionNode) data = self.save() self.load(data) item = self.diagram.canvas.select(lambda e: isinstance(e, items.DecisionNodeItem))[0] self.assertTrue(item.combined is None, item.combined) merge_node = factory.create(UML.MergeNode) item.combined = merge_node data = self.save() self.load(data) item = self.diagram.canvas.select(lambda e: isinstance(e, items.DecisionNodeItem))[0] self.assertTrue(item.combined is not None, item.combined) self.assertTrue(isinstance(item.combined, UML.MergeNode)) def test_fork_node_persistence(self): """Test saving/loading of fork node """ factory = self.element_factory item = self.create(items.ForkNodeItem, UML.ForkNode) data = self.save() self.load(data) item = self.diagram.canvas.select(lambda e: isinstance(e, items.ForkNodeItem))[0] self.assertTrue(item.combined is None, item.combined) merge_node = factory.create(UML.JoinNode) item.combined = merge_node data = self.save() self.load(data) item = self.diagram.canvas.select(lambda e: isinstance(e, items.ForkNodeItem))[0] self.assertTrue(item.combined is not None, item.combined) self.assertTrue(isinstance(item.combined, UML.JoinNode)) # vim:sw=4:et:ai gaphor-0.17.2/gaphor/diagram/tests/test_classifier_stereotypes.py000066400000000000000000000206101220151210700253000ustar00rootroot00000000000000""" Test classifier stereotypes attributes using component items. """ from gaphor import UML from gaphor.diagram.component import ComponentItem from gaphor.tests import TestCase class StereotypesAttributesTestCase(TestCase): def setUp(self): """ Create two stereotypes and extend component UML metaclass using them. """ super(StereotypesAttributesTestCase, self).setUp() factory = self.element_factory cls = factory.create(UML.Class) cls.name = 'Component' st1 = self.st1 = factory.create(UML.Stereotype) st1.name = 'st1' st2 = self.st2 = factory.create(UML.Stereotype) st2.name = 'st2' attr = factory.create(UML.Property) attr.name = 'st1_attr_1' st1.ownedAttribute = attr attr = factory.create(UML.Property) attr.name = 'st1_attr_2' st1.ownedAttribute = attr attr = factory.create(UML.Property) attr.name = 'st2_attr_1' st2.ownedAttribute = attr self.ext1 = UML.model.extend_with_stereotype(factory, cls, st1) self.ext2 = UML.model.extend_with_stereotype(factory, cls, st2) def tearDown(self): del self.st1 del self.st2 def test_applying_stereotype(self): """Test if stereotype compartment is created when stereotype is applied """ factory = self.element_factory c = self.create(ComponentItem, UML.Component) # test precondition assert len(c._compartments) == 0 c.show_stereotypes_attrs = True UML.model.apply_stereotype(factory, c.subject, self.st1) self.assertEquals(1, len(c._compartments)) self.assertFalse(c._compartments[0].visible) def test_adding_slot(self): """Test if stereotype attribute information is added when slot is added """ factory = self.element_factory c = self.create(ComponentItem, UML.Component) c.show_stereotypes_attrs = True obj = UML.model.apply_stereotype(factory, c.subject, self.st1) # test precondition assert not c._compartments[0].visible slot = UML.model.add_slot(factory, obj, self.st1.ownedAttribute[0]) compartment = c._compartments[0] self.assertTrue(compartment.visible) self.assertEquals(1, len(compartment)) def test_removing_last_slot(self): """Test removing last slot """ factory = self.element_factory c = self.create(ComponentItem, UML.Component) c.show_stereotypes_attrs = True obj = UML.model.apply_stereotype(factory, c.subject, self.st1) slot = UML.model.add_slot(factory, obj, self.st1.ownedAttribute[0]) compartment = c._compartments[0] # test precondition assert compartment.visible del obj.slot[slot] self.assertFalse(compartment.visible) def test_removing_stereotype(self): """Test if stereotype compartment is destroyed when stereotype is removed """ factory = self.element_factory c = self.create(ComponentItem, UML.Component) c.show_stereotypes_attrs = True UML.model.apply_stereotype(factory, c.subject, self.st1) # test precondition assert len(c._compartments) == 1 UML.model.remove_stereotype(c.subject, self.st1) self.assertEquals(0, len(c._compartments)) def test_deleting_extension(self): """Test if stereotype is removed when extension is deleteded """ factory = self.element_factory c = self.create(ComponentItem, UML.Component) c.show_stereotypes_attrs = True st1 = self.st1 ext1 = self.ext1 UML.model.apply_stereotype(factory, c.subject, st1) # test precondition assert len(c._compartments) == 1 assert len(c.subject.appliedStereotype) == 1 ext1.unlink() self.assertEquals(0, len(c.subject.appliedStereotype)) self.assertEquals(0, len(c._compartments)) def test_deleting_stereotype(self): """Test if stereotype is removed when stereotype is deleteded """ factory = self.element_factory c = self.create(ComponentItem, UML.Component) c.show_stereotypes_attrs = True st1 = self.st1 UML.model.apply_stereotype(factory, c.subject, st1) # test precondition assert len(c._compartments) == 1 assert len(c.subject.appliedStereotype) == 1 st1.unlink() self.assertEquals(0, len(c.subject.appliedStereotype)) self.assertEquals(0, len(c._compartments)) def test_removing_stereotype_attribute(self): """Test if stereotype instance specification is destroyed when stereotype attribute is removed """ factory = self.element_factory c = self.create(ComponentItem, UML.Component) c.show_stereotypes_attrs = True # test precondition assert len(c._compartments) == 0 obj = UML.model.apply_stereotype(factory, c.subject, self.st1) # test precondition assert len(c._compartments) == 1 assert len(self.kindof(UML.Slot)) == 0 attr = self.st1.ownedAttribute[0] slot = UML.model.add_slot(factory, obj, attr) assert len(obj.slot) == 1 assert len(self.kindof(UML.Slot)) == 1 self.assertTrue(slot.definingFeature) compartment = c._compartments[0] assert compartment.visible attr.unlink() self.assertEquals(0, len(obj.slot)) self.assertEquals(0, len(self.kindof(UML.Slot))) self.assertFalse(compartment.visible) def test_stereotype_attributes_status_saving(self): """Test stereotype attributes status saving """ factory = self.element_factory c = self.create(ComponentItem, UML.Component) c.show_stereotypes_attrs = True UML.model.apply_stereotype(factory, c.subject, self.st1) obj = UML.model.apply_stereotype(factory, c.subject, self.st2) # change attribute of 2nd stereotype attr = self.st2.ownedAttribute[0] slot = UML.model.add_slot(self.element_factory, obj, attr) slot.value = 'st2 test21' data = self.save() self.load(data) item = self.diagram.canvas.select(lambda e: isinstance(e, ComponentItem))[0] self.assertTrue(item.show_stereotypes_attrs) self.assertEquals(2, len(item._compartments)) # first stereotype has no attributes changed, so compartment # invisible self.assertFalse(item._compartments[0].visible) self.assertTrue(item._compartments[1].visible) def test_saving_stereotype_attributes(self): """Test stereotype attributes saving """ factory = self.element_factory c = self.create(ComponentItem, UML.Component) c.show_stereotypes_attrs = True UML.model.apply_stereotype(factory, c.subject, self.st1) UML.model.apply_stereotype(factory, c.subject, self.st2) self.assertEquals(3, len(self.st1.ownedAttribute)) attr1, attr2, attr3 = self.st1.ownedAttribute assert attr1.name == 'st1_attr_1', attr1.name assert attr2.name == 'st1_attr_2', attr2.name assert attr3.name == 'baseClass', attr3.name obj = c.subject.appliedStereotype[0] slot = UML.model.add_slot(self.element_factory, obj, attr1) slot.value = 'st1 test1' slot = UML.model.add_slot(self.element_factory, obj, attr2) slot.value = 'st1 test2' data = self.save() self.load(data) item = self.diagram.canvas.select(lambda e: isinstance(e, ComponentItem))[0] el = item.subject self.assertEquals(2, len(el.appliedStereotype)) # check if stereotypes are properly applied names = sorted(obj.classifier[0].name for obj in el.appliedStereotype) self.assertEquals(['st1', 'st2'], names) # two attributes were changed for stereotype st1, so 2 slots obj = el.appliedStereotype[0] self.assertEquals(2, len(obj.slot)) self.assertEquals('st1_attr_1', obj.slot[0].definingFeature.name) self.assertEquals('st1 test1', obj.slot[0].value) self.assertEquals('st1_attr_2', obj.slot[1].definingFeature.name) self.assertEquals('st1 test2', obj.slot[1].value) # no stereotype st2 attribute changes, no slots obj = el.appliedStereotype[1] self.assertEquals(0, len(obj.slot)) # vim:sw=4:et gaphor-0.17.2/gaphor/diagram/tests/test_connector.py000066400000000000000000000041651220151210700225070ustar00rootroot00000000000000""" Test connector item. """ from gaphor import UML from gaphor.diagram.connector import ConnectorItem from gaphor.tests.testcase import TestCase class ConnectorItemTestCase(TestCase): """ Connector item basic tests. """ def test_create(self): """Test creation of connector item """ conn = self.create(ConnectorItem, UML.Connector) self.assertFalse(conn.subject is None) #self.assertTrue(conn.end is None) def test_name(self): """Test connected interface name """ conn = self.create(ConnectorItem, UML.Connector) end = self.element_factory.create(UML.ConnectorEnd) iface = self.element_factory.create(UML.Interface) end.role = iface conn.subject.end = end #conn.end = end #self.assertTrue(conn._end is end) self.assertEquals('', conn._interface.text) iface.name = 'RedSea' self.assertEquals('RedSea', conn._interface.text) def test_setting_end(self): """Test creation of connector item """ conn = self.create(ConnectorItem, UML.Connector) end = self.element_factory.create(UML.ConnectorEnd) iface = self.element_factory.create(UML.Interface) end.role = iface iface.name = 'RedSea' conn.subject.end = end #conn.end = end #self.assertTrue(conn._end is end) self.assertEquals('RedSea', conn._interface.text) del conn.subject.end[end] conn.end = None self.assertEquals('', conn._interface.text) def test_persistence(self): """Test connector item saving/loading """ conn = self.create(ConnectorItem, UML.Connector) end = self.element_factory.create(UML.ConnectorEnd) #conn.end = end data = self.save() self.assertTrue(end.id in data) self.load(data) connectors = self.diagram.canvas.select(lambda e: isinstance(e, ConnectorItem)) ends = self.kindof(UML.ConnectorEnd) #self.assertTrue(connectors[0].end is not None) #self.assertTrue(connectors[0].end is ends[0]) # vim:sw=4:et:ai gaphor-0.17.2/gaphor/diagram/tests/test_diagramitem.py000066400000000000000000000027251220151210700230000ustar00rootroot00000000000000""" Test basic diagram item functionality like styles, etc. """ import unittest from gaphor.diagram.diagramitem import DiagramItem class ItemTestCase(unittest.TestCase): def setUp(self): class ItemA(DiagramItem): __style__ = { 'a-01': 1, 'a-02': 2, } self.ItemA = ItemA def test_style_assign(self): """ Test style assign """ item_a = self.ItemA() self.assertEqual(self.ItemA.style.a_01, 1) self.assertEqual(self.ItemA.style.a_02, 2) self.assertEqual(item_a.style.a_01, 1) self.assertEqual(item_a.style.a_02, 2) def test_style_override(self): """ Test style override """ class ItemB(self.ItemA): __style__ = { 'b-01': 3, 'b-02': 4, 'a-01': 5, } item_b = ItemB() self.assertEqual(ItemB.style.b_01, 3) self.assertEqual(ItemB.style.b_02, 4) self.assertEqual(ItemB.style.a_01, 5) self.assertEqual(ItemB.style.a_02, 2) self.assertEqual(item_b.style.b_01, 3) self.assertEqual(item_b.style.b_02, 4) self.assertEqual(item_b.style.a_01, 5) self.assertEqual(item_b.style.a_02, 2) # check ItemA style, it should remaing unaffected by ItemB style # changes self.assertEqual(self.ItemA.style.a_01, 1) self.assertEqual(self.ItemA.style.a_02, 2) gaphor-0.17.2/gaphor/diagram/tests/test_interfaces.py000066400000000000000000000010411220151210700226260ustar00rootroot00000000000000""" Test Interfaces. """ import unittest from zope import interface from gaphor import diagram from gaphor import diagram from gaphor.tests import TestCase class InterfacesTestCase(TestCase): def test_comment(self): #self.assertTrue(diagram.interfaces.ICommentItem.implementedBy(diagram.comment.CommentItem)) item = diagram.comment.CommentItem() editor = diagram.interfaces.IEditor(item) self.assertTrue(editor) self.assertTrue(editor._item is item) # vim: sw=4:et gaphor-0.17.2/gaphor/diagram/tests/test_message.py000066400000000000000000000103501220151210700221320ustar00rootroot00000000000000""" Test messages. """ from gaphor import UML from gaphor.diagram.message import MessageItem from gaphor.tests.testcase import TestCase class MessageTestCase(TestCase): def test_message(self): """Test creation of messages """ self.create(MessageItem, UML.Message) def test_adding_message(self): """Test adding message on communication diagram """ factory = self.element_factory item = self.create(MessageItem, UML.Message) message = factory.create(UML.Message) message.name = 'test-message' item.add_message(message, False) self.assertTrue(message in item._messages) self.assertTrue(message not in item._inverted_messages) self.assertEquals(item._messages[message].text, 'test-message') message = factory.create(UML.Message) message.name = 'test-inverted-message' item.add_message(message, True) self.assertTrue(message in item._inverted_messages) self.assertTrue(message not in item._messages) self.assertEquals(item._inverted_messages[message].text, 'test-inverted-message') def test_changing_message_text(self): """Test changing message text """ factory = self.element_factory item = self.create(MessageItem, UML.Message) message = factory.create(UML.Message) message.name = 'test-message' item.add_message(message, False) self.assertEquals(item._messages[message].text, 'test-message') item.set_message_text(message, 'test-message-changed', False) self.assertEquals(item._messages[message].text, 'test-message-changed') message = factory.create(UML.Message) message.name = 'test-message' item.add_message(message, True) self.assertEquals(item._inverted_messages[message].text, 'test-message') item.set_message_text(message, 'test-message-changed', True) self.assertEquals(item._inverted_messages[message].text, 'test-message-changed') def test_message_removal(self): """Test message removal """ factory = self.element_factory item = self.create(MessageItem, UML.Message) message = factory.create(UML.Message) item.add_message(message, False) self.assertTrue(message in item._messages) item.remove_message(message, False) self.assertTrue(message not in item._messages) message = factory.create(UML.Message) item.add_message(message, True) self.assertTrue(message in item._inverted_messages) item.remove_message(message, True) self.assertTrue(message not in item._inverted_messages) def test_messages_swapping(self): """Test messages swapping """ factory = self.element_factory item = self.create(MessageItem, UML.Message) m1 = factory.create(UML.Message) m2 = factory.create(UML.Message) item.add_message(m1, False) item.add_message(m2, False) item.swap_messages(m1, m2, False) m1 = factory.create(UML.Message) m2 = factory.create(UML.Message) item.add_message(m1, True) item.add_message(m2, True) item.swap_messages(m1, m2, True) def test_message_persistence(self): """Test message saving/loading """ factory = self.element_factory item = self.create(MessageItem, UML.Message) m1 = factory.create(UML.Message) m2 = factory.create(UML.Message) m3 = factory.create(UML.Message) m4 = factory.create(UML.Message) m1.name = 'm1' m2.name = 'm2' m3.name = 'm3' m4.name = 'm4' item.add_message(m1, False) item.add_message(m2, False) item.add_message(m3, True) item.add_message(m4, True) data = self.save() self.load(data) item = self.diagram.canvas.select(lambda e: isinstance(e, MessageItem))[0] self.assertEquals(len(item._messages), 2) self.assertEquals(len(item._inverted_messages), 2) # check for loaded messages and order of messages self.assertEquals(['m1', 'm2'], [m.name for m in item._messages]) self.assertEquals(['m3', 'm4'], [m.name for m in item._inverted_messages]) gaphor-0.17.2/gaphor/diagram/tests/test_objectnode.py000066400000000000000000000022161220151210700226240ustar00rootroot00000000000000import gaphor.UML as UML from gaphor.diagram import items from gaphor.tests.testcase import TestCase class ObjectNodeTestCase(TestCase): def test_object_node(self): self.create(items.ObjectNodeItem, UML.ObjectNode) def test_name(self): """ Test updating of object node name """ node = self.create(items.ObjectNodeItem, UML.ObjectNode) node.subject.name = 'Blah' self.assertEquals('Blah', node._name.text) node.subject = None # Undefined def test_upper_bound(self): """ TODO: Test upper bound """ pass def test_ordering(self): """ Test updating of ObjectNodeItem.ordering. """ node = self.create(items.ObjectNodeItem, UML.ObjectNode) node.subject.ordering = "unordered" self.assertEquals('{ ordering = unordered }', node._ordering.text) node.show_ordering = True self.assertEquals('{ ordering = unordered }', node._ordering.text) def test_persistence(self): """ TODO: Test connector item saving/loading """ pass # vim:sw=4:et:ai gaphor-0.17.2/gaphor/diagram/tests/test_simpleitem.py000066400000000000000000000011701220151210700226560ustar00rootroot00000000000000""" Unnit tests for simple items. """ import unittest from gaphor.tests import TestCase from gaphor import UML from gaphor.diagram.simpleitem import Line, Box, Ellipse from gaphas import View class SimpleItemTestCase(TestCase): def setUp(self): super(SimpleItemTestCase, self).setUp() self.view = View(self.diagram.canvas) def test_line(self): """ """ self.diagram.create(Line) def test_box(self): """ """ self.diagram.create(Line) def test_ellipse(self): """ """ self.diagram.create(Ellipse) # vim:sw=4:et:ai gaphor-0.17.2/gaphor/diagram/tests/test_style.py000066400000000000000000000221441220151210700216520ustar00rootroot00000000000000""" Test item styles. """ import unittest from gaphor.diagram.style import get_text_point, \ get_text_point_at_line, get_text_point_at_line2, get_min_size, \ ALIGN_LEFT, ALIGN_CENTER, ALIGN_RIGHT, ALIGN_TOP, ALIGN_MIDDLE, ALIGN_BOTTOM class StyleTestCase(unittest.TestCase): def test_min_size(self): """ Test minimum size calculation """ width, height = get_min_size(10, 10, (1, 2, 3, 4)) self.assertEqual(width, 16) self.assertEqual(height, 14) def test_align_box(self): """ Test aligned text position calculation """ extents = 80, 12 padding = (1, 2, 3, 4) data = { (ALIGN_LEFT, ALIGN_TOP, False): ( 4, 1), (ALIGN_LEFT, ALIGN_MIDDLE, False): ( 4, 14), (ALIGN_LEFT, ALIGN_BOTTOM, False): ( 4, 25), (ALIGN_CENTER, ALIGN_TOP, False): ( 42, 1), (ALIGN_CENTER, ALIGN_MIDDLE, False): ( 42, 14), (ALIGN_CENTER, ALIGN_BOTTOM, False): ( 42, 25), (ALIGN_RIGHT, ALIGN_TOP, False): ( 78, 1), (ALIGN_RIGHT, ALIGN_MIDDLE, False): ( 78, 14), (ALIGN_RIGHT, ALIGN_BOTTOM, False): ( 78, 25), (ALIGN_LEFT, ALIGN_TOP, True): (-84, -13), (ALIGN_LEFT, ALIGN_MIDDLE, True): (-84, 14), (ALIGN_LEFT, ALIGN_BOTTOM, True): (-84, 43), (ALIGN_CENTER, ALIGN_TOP, True): ( 40, -13), (ALIGN_CENTER, ALIGN_MIDDLE, True): ( 40, 14), (ALIGN_CENTER, ALIGN_BOTTOM, True): ( 40, 43), (ALIGN_RIGHT, ALIGN_TOP, True): (162, -13), (ALIGN_RIGHT, ALIGN_MIDDLE, True): (162, 14), (ALIGN_RIGHT, ALIGN_BOTTOM, True): (162, 43), } for halign in range(-1, 2): for valign in range(-1, 2): for outside in (True, False): align = (halign, valign) point_expected = data[(halign, valign, outside)] point = get_text_point(extents, 160, 40, \ align, padding, outside) self.assertEqual(point[0], point_expected[0], \ '%s, %s -> %s' % (align, outside, point[0])) self.assertEqual(point[1], point_expected[1], \ '%s, %s -> %s' % (align, outside, point[1])) def test_align_line(self): """ Test aligned at the line text position calculation """ p1 = 0, 0 p2 = 20, 20 extents = 10, 5 x, y = get_text_point_at_line(extents, p1, p2, (ALIGN_LEFT, ALIGN_TOP), (2, 2, 2, 2)) self.assertEqual(x, 5) self.assertEqual(y, -10) x, y = get_text_point_at_line(extents, p1, p2, (ALIGN_RIGHT, ALIGN_TOP), (2, 2, 2, 2)) self.assertEqual(x, 5) self.assertEqual(y, -10) p2 = -20, 20 x, y = get_text_point_at_line(extents, p1, p2, (ALIGN_LEFT, ALIGN_TOP), (2, 2, 2, 2)) self.assertEqual(x, -15) self.assertEqual(y, -10) x, y = get_text_point_at_line(extents, p1, p2, (ALIGN_RIGHT, ALIGN_TOP), (2, 2, 2, 2)) self.assertEqual(x, -15) self.assertEqual(y, -10) def test_align_line2_h(self): """ Test aligned at the line text position calculation, horizontal mode """ extents = 10, 5 p1 = 2.0, 2.0 # align top p2 = 22.0, 7.0 x, y = get_text_point_at_line2(extents, p1, p2, (ALIGN_CENTER, ALIGN_TOP), (3, 2, 3, 2)) self.assertAlmostEqual(x, 7) self.assertAlmostEqual(y, -4.75) p2 = 22.0, -3.0 x, y = get_text_point_at_line2(extents, p1, p2, (ALIGN_CENTER, ALIGN_TOP), (3, 2, 3, 2)) self.assertAlmostEqual(x, 7) self.assertAlmostEqual(y, -9.75) p2 = -18.0, 7.0 x, y = get_text_point_at_line2(extents, p1, p2, (ALIGN_CENTER, ALIGN_TOP), (3, 2, 3, 2)) self.assertAlmostEqual(x, -13) self.assertAlmostEqual(y, -4.75) p2 = -18.0, -3.0 x, y = get_text_point_at_line2(extents, p1, p2, (ALIGN_CENTER, ALIGN_TOP), (3, 2, 3, 2)) self.assertAlmostEqual(x, -13) self.assertAlmostEqual(y, -9.75) # align bottom p2 = 22.0, 7.0 x, y = get_text_point_at_line2(extents, p1, p2, (ALIGN_CENTER, ALIGN_BOTTOM), (3, 2, 3, 2)) self.assertAlmostEqual(x, 7) self.assertAlmostEqual(y, 8.75) p2 = 22.0, -3.0 x, y = get_text_point_at_line2(extents, p1, p2, (ALIGN_CENTER, ALIGN_BOTTOM), (3, 2, 3, 2)) self.assertAlmostEqual(x, 7) self.assertAlmostEqual(y, 3.75) p2 = -18.0, 7.0 x, y = get_text_point_at_line2(extents, p1, p2, (ALIGN_CENTER, ALIGN_BOTTOM), (3, 2, 3, 2)) self.assertAlmostEqual(x, -13) self.assertAlmostEqual(y, 8.75) p2 = -18.0, -3.0 x, y = get_text_point_at_line2(extents, p1, p2, (ALIGN_CENTER, ALIGN_BOTTOM), (3, 2, 3, 2)) self.assertAlmostEqual(x, -13) self.assertAlmostEqual(y, 3.75) def test_align_line2_v(self): """ Test aligned at the line text position calculation, vertical mode """ extents = 10, 5 p1 = 2.0, 2.0 # top align p2 = 7.0, 22.0 x, y = get_text_point_at_line2(extents, p1, p2, (ALIGN_CENTER, ALIGN_TOP), (3, 2, 3, 2)) self.assertAlmostEqual(x, 7.125) self.assertAlmostEqual(y, 9.5) p2 = 7.0, -18.0 x, y = get_text_point_at_line2(extents, p1, p2, (ALIGN_CENTER, ALIGN_TOP), (3, 2, 3, 2)) self.assertAlmostEqual(x, -8.125) self.assertAlmostEqual(y, -10.5) p2 = -3.0, 22.0 x, y = get_text_point_at_line2(extents, p1, p2, (ALIGN_CENTER, ALIGN_TOP), (3, 2, 3, 2)) self.assertAlmostEqual(x, -13.125) self.assertAlmostEqual(y, 9.5) p2 = -3.0, -18.0 x, y = get_text_point_at_line2(extents, p1, p2, (ALIGN_CENTER, ALIGN_TOP), (3, 2, 3, 2)) self.assertAlmostEqual(x, 2.125) self.assertAlmostEqual(y, -10.5) # bottom align p2 = 7.0, 22.0 x, y = get_text_point_at_line2(extents, p1, p2, (ALIGN_CENTER, ALIGN_BOTTOM), (3, 2, 3, 2)) self.assertAlmostEqual(x, -8.125) self.assertAlmostEqual(y, 9.5) p2 = 7.0, -18.0 x, y = get_text_point_at_line2(extents, p1, p2, (ALIGN_CENTER, ALIGN_BOTTOM), (3, 2, 3, 2)) self.assertAlmostEqual(x, 7.125) self.assertAlmostEqual(y, -10.5) p2 = -3.0, 22.0 x, y = get_text_point_at_line2(extents, p1, p2, (ALIGN_CENTER, ALIGN_BOTTOM), (3, 2, 3, 2)) self.assertAlmostEqual(x, 2.125) self.assertAlmostEqual(y, 9.5) p2 = -3.0, -18.0 x, y = get_text_point_at_line2(extents, p1, p2, (ALIGN_CENTER, ALIGN_BOTTOM), (3, 2, 3, 2)) self.assertAlmostEqual(x, -13.125) self.assertAlmostEqual(y, -10.5) def test_align_line2_o(self): """ Test aligned at the line text position calculation, orthogonal lines """ extents = 10, 5 p1 = 2.0, 2.0 # top align p2 = 22.0, 2.0 x, y = get_text_point_at_line2(extents, p1, p2, (ALIGN_CENTER, ALIGN_TOP), (3, 2, 3, 2)) self.assertAlmostEqual(x, 7) self.assertAlmostEqual(y, -6) p2 = -18.0, 2.0 x, y = get_text_point_at_line2(extents, p1, p2, (ALIGN_CENTER, ALIGN_TOP), (3, 2, 3, 2)) self.assertAlmostEqual(x, -13) self.assertAlmostEqual(y, -6) p2 = 2.0, 22.0 x, y = get_text_point_at_line2(extents, p1, p2, (ALIGN_CENTER, ALIGN_TOP), (3, 2, 3, 2)) self.assertAlmostEqual(x, -10.0) self.assertAlmostEqual(y, 9.5) p2 = 2.0, -18.0 x, y = get_text_point_at_line2(extents, p1, p2, (ALIGN_CENTER, ALIGN_TOP), (3, 2, 3, 2)) self.assertAlmostEqual(x, -10.0) self.assertAlmostEqual(y, -10.5) # bottom align p2 = 22.0, 2.0 x, y = get_text_point_at_line2(extents, p1, p2, (ALIGN_CENTER, ALIGN_BOTTOM), (3, 2, 3, 2)) self.assertAlmostEqual(x, 7) self.assertAlmostEqual(y, 5) p2 = -18.0, 2.0 x, y = get_text_point_at_line2(extents, p1, p2, (ALIGN_CENTER, ALIGN_BOTTOM), (3, 2, 3, 2)) self.assertAlmostEqual(x, -13.0) self.assertAlmostEqual(y, 5.0) p2 = 2.0, 22.0 x, y = get_text_point_at_line2(extents, p1, p2, (ALIGN_CENTER, ALIGN_BOTTOM), (3, 2, 3, 2)) self.assertAlmostEqual(x, 4.0) self.assertAlmostEqual(y, 9.5) p2 = 2.0, -18.0 x, y = get_text_point_at_line2(extents, p1, p2, (ALIGN_CENTER, ALIGN_BOTTOM), (3, 2, 3, 2)) self.assertAlmostEqual(x, 4.0) self.assertAlmostEqual(y, -10.5) gaphor-0.17.2/gaphor/diagram/textelement.py000066400000000000000000000315561220151210700206560ustar00rootroot00000000000000""" Support for editable text, a part of a diagram item, i.e. name of named item, guard of flow item, etc. """ import math import cairo, pango, pangocairo from gaphor.diagram.style import Style from gaphor.diagram.style import ALIGN_CENTER, ALIGN_TOP from gaphas.geometry import distance_rectangle_point, Rectangle DEFAULT_TEXT_FONT = 'sans 10' def swap(list, el1, el2): """ Swap two elements on the list. """ i1 = list.index(el1) i2 = list.index(el2) list[i1] = el2 list[i2] = el1 def _text_layout(cr, text, font, width): cr = pangocairo.CairoContext(cr) layout = cr.create_layout() if font: layout.set_font_description(pango.FontDescription(font)) layout.set_text(text) layout.set_width(int(width * pango.SCALE)) #layout.set_height(height) return layout def text_extents(cr, text, font=None, width=-1, height=-1): if not text: return 0, 0 layout = _text_layout(cr, text, font, width) return layout.get_pixel_size() def text_align(cr, x, y, text, font, width=-1, height=-1, align_x=0, align_y=0, padding_x=0, padding_y=0): """ Draw text relative to (x, y). x, y - coordinates text - text to print (utf8) font - The font to render in width height align_x - 1 (top), 0 (middle), -1 (bottom) align_y - 1 (left), 0 (center), -1 (right) padding_x - padding (extra offset), always > 0 padding_y - padding (extra offset), always > 0 """ if not isinstance(cr, cairo.Context): return if not text: return layout = _text_layout(cr, text, font, width) w, h = layout.get_pixel_size() if align_x == 0: x = 0.5 - (w / 2) + x elif align_x < 0: x = -w + x - padding_x else: x = x + padding_x if align_y == 0: y = 0.5 - (h / 2) + y elif align_y < 0: y = -h + y - padding_y else: y = y + padding_y cr.move_to(x, y) cr.update_layout(layout) cr.show_layout(layout) def text_center(cr, x, y, text, font): text_align(cr, x, y, text, font=font, align_x=0, align_y=0) def text_multiline(cr, x, y, text, font, width=-1, height=-1): text_align(cr, x, y, text, font=font, width=width, height=height, align_x=1, align_y=1) class EditableTextSupport(object): """ Editable text support to allow display and edit text parts of a diagram item. Attributes: - _texts: list of diagram item text elements - _text_groups: grouping information of text elements (None - ungrouped) """ def __init__(self): self._texts = [] self._text_groups = { None: [] } self._text_groups_sizes = {} def postload(self): super(EditableTextSupport, self).postload() def texts(self): """ Return list of diagram item text elements. """ return self._texts def add_text(self, attr, style=None, pattern=None, visible=None, editable=False): """ Create and add a text element. For parameters description and more information see TextElement class documentation. If style information contains 'text-align-group' data, then text element is grouped. Returns created text element. """ txt = TextElement(attr, style=style, pattern=pattern, visible=visible, editable=editable) self._texts.append(txt) # try to group text element gname = style and style.get('text-align-group') or None if gname not in self._text_groups: self._text_groups[gname] = [] group = self._text_groups[gname] group.append(txt) return txt def remove_text(self, txt): """ Remove a text element from diagram item. Parameters: - txt: text to be removed """ # remove from align group style = txt.style if style and hasattr(style, 'text_align_group'): gname = style.text_align_group else: gname = None group = self._text_groups[gname] group.remove(txt) # remove text element from diagram item self._texts.remove(txt) def swap_texts(self, txt1, txt2): """ Swap two text elements. """ swap(self._texts, txt1, txt2) style = txt1.style if style and hasattr(style, 'text_align_group'): gname = style.text_align_group else: gname = None group = self._text_groups[gname] swap(group, txt1, txt2) def _get_visible_texts(self, texts): """ Get list of visible texts. """ return [txt for txt in texts if txt.is_visible()] def _get_text_groups(self): """ Get text groups. """ tg = self._text_groups groups = self._text_groups return ((name, tg[name]) for name in groups if name) def _set_text_sizes(self, context, texts): """ Calculate size for every text in the list. Parameters: - context: cairo context - texts: list of texts """ cr = context.cairo for txt in texts: w, h = text_extents(cr, txt.text, font=txt.style.font) txt.bounds.width = max(15, w) txt.bounds.height = max(10, h) def _set_text_group_size(self, context, name, texts): """ Calculate size of a group. Parameters: - context: cairo context - name: group name - texts: list of group texts """ cr = context.cairo texts = self._get_visible_texts(texts) if not texts: self._text_groups_sizes[name] = (0, 0) return # find maximum width and total height width = max(txt.bounds.width for txt in texts) height = sum(txt.bounds.height for txt in texts) self._text_groups_sizes[name] = width, height def pre_update(self, context): """ Calculate sizes of text elements and text groups. """ cr = context.cairo # calculate sizes of text groups for name, texts in self._get_text_groups(): self._set_text_sizes(context, texts) self._set_text_group_size(context, name, texts) # calculate sizes of ungrouped texts texts = self._text_groups[None] texts = self._get_visible_texts(texts) self._set_text_sizes(context, texts) def _text_group_align(self, context, name, texts): """ Align group of text elements making vertical stack of strings. Parameters: - context: cairo context - name: group name - texts: list of group texts """ cr = context.cairo texts = self._get_visible_texts(texts) if not texts: return # align according to style of last text in the group style = texts[-1]._style extents = self._text_groups_sizes[name] x, y = self.text_align(extents, style.text_align, style.text_padding, style.text_outside) max_hint = 0 if style.text_align_str: for txt in texts: txt._hint = self._get_text_align_hint(cr, txt) max_hint = max(max_hint, txt._hint) # stack texts dy = 0 dw = extents[0] for txt in texts: bounds = txt.bounds width, height = bounds.width, bounds.height # center stacked texts if max_hint: txt.bounds.x = x + max_hint - txt._hint else: txt.bounds.x = x + (dw - width) / 2.0 txt.bounds.y = y + dy dy += height def _get_text_align_hint(self, cr, txt): """ Calculate hint value for text element depending on ``text_align_str`` style property. """ style = txt.style chunks = txt.text.split(style.text_align_str, 1) hint = 0 if len(chunks) > 1: hint, _ = text_extents(cr, chunks[0], font=txt.style.font) return hint def post_update(self, context): """ Calculate position and sizes of all text elements of a diagram item. """ cr = context.cairo # align groups of text elements for name, texts in self._get_text_groups(): assert name in self._text_groups_sizes, 'No text group "%s"' % name self._text_group_align(context, name, texts) # align ungrouped text elements texts = self._get_visible_texts(self._text_groups[None]) for txt in texts: style = txt.style extents = txt.bounds.width, txt.bounds.height x, y = self.text_align(extents, style.text_align, style.text_padding, style.text_outside) bounds = txt.bounds # fixme: gaphor rectangle problem width, height = bounds.width, bounds.height # fixme: gaphor rectangle problem txt.bounds.x = x txt.bounds.y = y def point(self, pos): """ Return the distance to the nearest editable and visible text element. """ def distances(): yield 10000.0 for txt in self._texts: if txt.is_visible() and txt.editable: yield distance_rectangle_point(txt.bounds, pos) return min(distances()) def draw(self, context): """ Draw all text elements of a diagram item. """ cr = context.cairo cr.save() # fixme: do it on per group basis if any(txt._style.text_rotated for txt in self._get_visible_texts(self._texts)): cr.rotate(-math.pi/2) if self.subject: for txt in self._get_visible_texts(self._texts): txt.draw(context) cr.restore() class TextElement(object): """ Representation of an editable text, which is part of a diagram item. Text element is aligned according to style information. It also displays and allows to edit value of an attribute of UML class (DiagramItem.subject). Attribute name can be recursive, all below attribute names are valid: - name (named item name) - guard.value (flow item guard) Attributes and properties: - attr: name of displayed and edited UML class attribute - bounds: text bounds - _style: text style (i.e. align information, padding) - text: rendered string to be displayed - pattern: print pattern of text - editable: True if text should be editable See also EditableTextSupport.add_text. """ bounds = property(lambda self: self._bounds) def __init__(self, attr, style=None, pattern=None, visible=None, editable=False): """ Create new text element with bounds (0, 0, 10, 10) and empty text. Parameters: - visible: function, which evaluates to True/False if text should be visible """ super(TextElement, self).__init__() self._bounds = Rectangle(0, 0, width=15, height=10) # create default style for a text element self._style = Style() self._style.add('text-padding', (2, 2, 2, 2)) self._style.add('text-align', (ALIGN_CENTER, ALIGN_TOP)) self._style.add('text-outside', False) self._style.add('text-rotated', False) self._style.add('text-align-str', None) self._style.add('font', DEFAULT_TEXT_FONT) if style: self._style.update(style) self.attr = attr self._text = '' if visible: self.is_visible = visible if pattern: self._pattern = pattern else: self._pattern = '%s' self.editable = editable def _set_text(self, value): """ Render text value using pattern. """ self._text = value and self._pattern % value or '' text = property(lambda s: s._text, _set_text) style = property(lambda s: s._style) def is_visible(self): """ Display text by default. """ return True def draw(self, context): bounds = self.bounds x, y = bounds.x, bounds.y width, height = bounds.width, bounds.height cr = context.cairo if isinstance(cr, cairo.Context) and self.text: cr = pangocairo.CairoContext(context.cairo) cr.move_to(x, y) layout = cr.create_layout() layout.set_font_description(pango.FontDescription(self._style.font)) layout.set_text(self.text) cr.show_layout(layout) if self.editable and (context.hovered or context.focused): cr.save() cr.set_source_rgb(0.6, 0.6, 0.6) cr.set_line_width(0.5) cr.rectangle(x - 5, y - 1, width + 10, height + 2) cr.stroke() cr.restore() # vim:sw=4:et:ai gaphor-0.17.2/gaphor/diagram/usecase.py000066400000000000000000000022411220151210700177350ustar00rootroot00000000000000""" Use case diagram item. """ from math import pi from gaphor import UML from gaphor.diagram.classifier import ClassifierItem from gaphor.diagram.style import ALIGN_CENTER, ALIGN_MIDDLE from textelement import text_extents from gaphas.util import path_ellipse class UseCaseItem(ClassifierItem): """ Presentation of gaphor.UML.UseCase. """ __uml__ = UML.UseCase __style__ = { 'min-size': (50, 30), 'name-align': (ALIGN_CENTER, ALIGN_MIDDLE), } def __init__(self, id=None): super(UseCaseItem, self).__init__(id) self.drawing_style = -1 def pre_update(self, context): cr = context.cairo text = self.subject.name if text: width, height = text_extents(cr, text) self.min_width, self.min_height = width + 10, height + 20 super(UseCaseItem, self).pre_update(context) def draw(self, context): cr = context.cairo rx = self.width / 2. ry = self.height / 2. cr.move_to(self.width, ry) path_ellipse(cr, rx, ry, self.width, self.height) cr.stroke() super(UseCaseItem, self).draw(context) # vim:sw=4:et gaphor-0.17.2/gaphor/event.py000066400000000000000000000030441220151210700160240ustar00rootroot00000000000000""" Application wide events are managed here. """ from zope import interface from gaphor.interfaces import * class ServiceInitializedEvent(object): """ This event is emitted every time a new service has been initialized. """ interface.implements(IServiceEvent) def __init__(self, name, service): self.name = name self.service = service class ServiceShutdownEvent(object): """ This event is emitted every time a service has been shut down. """ interface.implements(IServiceEvent) def __init__(self, name, service): self.name = name self.service = service class TransactionBegin(object): """ This event denotes the beginning of a transaction. Nested (sub-) transactions should not emit this signal. """ interface.implements(ITransactionEvent) class TransactionCommit(object): """ This event is emitted when a transaction (toplevel) is successfully commited. """ interface.implements(ITransactionEvent) class TransactionRollback(object): """ If a set of operations fail (e.i. due to an exception) the transaction should be marked for rollback. This event is emitted to tell the operation has failed. """ interface.implements(ITransactionEvent) class ActionExecuted(object): """ Once an operation has succesfully been executed this event is raised. """ interface.implements(IActionExecutedEvent) def __init__(self, name, action): self.name = name self.action = action # vim:sw=4:et:ai gaphor-0.17.2/gaphor/i18n.py000066400000000000000000000007561220151210700154710ustar00rootroot00000000000000"""Internationalization (i18n) support for Gaphor. Here the _() function is defined that is used to translate text into your native language.""" __all__ = [ '_' ] import os import gettext import pkg_resources localedir = os.path.join(pkg_resources.get_distribution('gaphor').location, \ 'gaphor', 'data', 'locale') try: catalog = gettext.Catalog('gaphor', localedir=localedir) _ = catalog.gettext except IOError, e: def _(s): return s gaphor-0.17.2/gaphor/interfaces.py000066400000000000000000000035721220151210700170340ustar00rootroot00000000000000""" Top level interface definitions for Gaphor. """ from zope import interface class IService(interface.Interface): """ Base interface for all services in Gaphor. """ def init(self, application): """ Initialize the service, this method is called after all services are instantiated. """ def shutdown(self): """ Shutdown the services, free resources. """ class IServiceEvent(interface.Interface): """ An event emitted by a service. """ service = interface.Attribute("The service that emits the event") class ITransaction(interface.Interface): """ The methods each transaction should adhere. """ def commit(self): """ Commit the transaction. """ def rollback(self): """ Roll back the transaction. """ class ITransactionEvent(interface.Interface): """ Events related to transaction workflow (begin/commit/rollback) implements this interface. """ class IActionProvider(interface.Interface): """ An action provider is a special service that provides actions (see gaphor/action.py) and the accompanying XML for the UI manager. """ menu_xml = interface.Attribute("The menu XML") action_group = interface.Attribute("The accompanying ActionGroup") class IActionExecutedEvent(interface.Interface): """ An event emited when an action has been performed. """ name = interface.Attribute("Name of the action performed, if any") action = interface.Attribute("The performed action") class IEventFilter(interface.Interface): """ Filter events when they're about to be handled. """ def filter(self): """ Return a value (e.g. message/reason) why the event is filtered. Returning `None` or `False` will propagate the event. """ # vim:sw=4:et gaphor-0.17.2/gaphor/misc/000077500000000000000000000000001220151210700152635ustar00rootroot00000000000000gaphor-0.17.2/gaphor/misc/__init__.py000066400000000000000000000005721220151210700174000ustar00rootroot00000000000000 """Provides a get_user_data_dir() function for retrieving the user's data directory.""" import os def get_user_data_dir(): """Return the directory where the user's data is stored. This varies depending on platform.""" if os.name == 'nt': home = 'USERPROFILE' else: home = 'HOME' return os.path.join(os.getenv(home), '.gaphor') gaphor-0.17.2/gaphor/misc/colorbutton.py000066400000000000000000000015201220151210700202050ustar00rootroot00000000000000""" A version of the standard gtk.ColorButton tweaked towards Gaphor. Gaphor is using color values from 0 to 1 (cairo standard), so that required some tweaks on the color widget. The standard format is `(red, green, blue, alpha)`. """ import gtk class ColorButton(gtk.ColorButton): __gtype_name__ = 'GaphorColorButton' def __init__(self, r, g, b, a): gtk.Button.__init__(self) self.set_color(gtk.gdk.Color(int(r * 65535), int(g * 65535), int(b * 65535))) self.set_use_alpha(True) self.set_alpha(int(a * 65535)) def get_color_float(self): c = self.get_color() return (c.red_float, c.green_float, c.blue_float, self.get_alpha() / 65535.) color = property(lambda s: s.get_color_float()) # vim:sw=4:et:ai gaphor-0.17.2/gaphor/misc/console.py000066400000000000000000000257211220151210700173060ustar00rootroot00000000000000#!/usr/bin/env python # GTK Interactive Console # (C) 2003, Jon Anderson # See www.python.org/2.2/license.html for # license details. # #import pygtk #pygtk.require('2.0') import gtk import gtk.gdk import code import sys import pango import __builtin__ import __main__ banner = """Gaphor Interactive Python Console %s """ % sys.version class Completer: """ Taken from rlcompleter, with readline references stripped, and a local dictionary to use. """ def __init__(self, locals): self.locals = locals def complete(self, text, state): """ Return the next possible completion for 'text'. This is called successively with state == 0, 1, 2, ... until it returns None. The completion should begin with 'text'. """ if state == 0: if "." in text: self.matches = self.attr_matches(text) else: self.matches = self.global_matches(text) try: return self.matches[state] except IndexError: return None def global_matches(self, text): """ Compute matches when text is a simple name. Return a list of all keywords, built-in functions and names currently defines in __main__ that match. """ import keyword matches = [] n = len(text) for list in [keyword.kwlist,__builtin__.__dict__.keys(),__main__.__dict__.keys(), self.locals.keys()]: for word in list: if word[:n] == text and word != "__builtins__": matches.append(word) return matches def attr_matches(self, text): """ Compute matches when text contains a dot. Assuming the text is of the form NAME.NAME....[NAME], and is evaluatable in the globals of __main__, it will be evaluated and its attributes (as revealed by dir()) are used as possible completions. (For class instances, class members are are also considered.) WARNING: this can still invoke arbitrary C code, if an object with a __getattr__ hook is evaluated. """ import re m = re.match(r"(\w+(\.\w+)*)\.(\w*)", text) if not m: return expr, attr = m.group(1, 3) object = eval(expr, __main__.__dict__, self.locals) words = dir(object) if hasattr(object,'__class__'): words.append('__class__') words = words + get_class_members(object.__class__) matches = [] n = len(attr) for word in words: if word[:n] == attr and word != "__builtins__": matches.append("%s.%s" % (expr, word)) return matches def get_class_members(klass): ret = dir(klass) if hasattr(klass,'__bases__'): for base in klass.__bases__: ret = ret + get_class_members(base) return ret class OutputStream: """ A Multiplexing output stream. It can replace another stream, and tee output to the original stream and too a GTK textview. """ def __init__(self,view,old_out,style): self.view = view self.buffer = view.get_buffer() self.mark = self.buffer.create_mark("End",self.buffer.get_end_iter(), False ) self.out = old_out self.style = style self.tee = 1 def write(self,text): if self.tee: self.out.write(text) end = self.buffer.get_end_iter() if not self.view == None: self.view.scroll_to_mark(self.mark, 0, True, 1, 1) self.buffer.insert_with_tags(end,text,self.style) class GTKInterpreterConsole(gtk.ScrolledWindow): """ An InteractiveConsole for GTK. It's an actual widget, so it can be dropped in just about anywhere. """ __gtype_name__ = 'GTKInterpreterConsole' def __init__(self, locals=None): gtk.ScrolledWindow.__init__(self) self.set_policy (gtk.POLICY_AUTOMATIC,gtk.POLICY_AUTOMATIC) self.text = gtk.TextView() self.text.set_wrap_mode(True) self.interpreter = code.InteractiveInterpreter(locals) self.completer = Completer(self.interpreter.locals) self.buffer = [] self.history = [] self.banner = banner self.ps1 = ">>> " self.ps2 = "... " self.text.add_events( gtk.gdk.KEY_PRESS_MASK ) self.text.connect( "key_press_event", self.key_pressed ) self.current_history = -1 self.mark = self.text.get_buffer().create_mark("End",self.text.get_buffer().get_end_iter(), False ) #setup colors self.style_banner = gtk.TextTag("banner") self.style_banner.set_property( "foreground", "saddle brown" ) self.style_ps1 = gtk.TextTag("ps1") self.style_ps1.set_property( "foreground", "DarkOrchid4" ) self.style_ps1.set_property( "editable", False ) self.style_ps1.set_property("font", "courier" ) self.style_ps2 = gtk.TextTag("ps2") self.style_ps2.set_property( "foreground", "DarkOliveGreen" ) self.style_ps2.set_property( "editable", False ) self.style_ps2.set_property("font", "courier" ) self.style_out = gtk.TextTag("stdout") self.style_out.set_property( "foreground", "midnight blue" ) self.style_err = gtk.TextTag("stderr") self.style_err.set_property( "style", pango.STYLE_ITALIC ) self.style_err.set_property( "foreground", "red" ) self.text.get_buffer().get_tag_table().add(self.style_banner) self.text.get_buffer().get_tag_table().add(self.style_ps1) self.text.get_buffer().get_tag_table().add(self.style_ps2) self.text.get_buffer().get_tag_table().add(self.style_out) self.text.get_buffer().get_tag_table().add(self.style_err) self.stdout = None #OutputStream(self.text,sys.stdout,self.style_out) self.stderr = None #OutputStream(self.text,sys.stderr,self.style_err) #sys.stderr = self.stderr #sys.stdout = self.stdout self.current_prompt = None self.write_line(self.banner, self.style_banner) self.prompt_ps1() self.add(self.text) self.text.show() def reset_history(self): self.history = [] def reset_buffer(self): self.buffer = [] def prompt_ps1(self): self.current_prompt = self.prompt_ps1 self.write_line(self.ps1,self.style_ps1) def prompt_ps2(self): self.current_prompt = self.prompt_ps2 self.write_line(self.ps2,self.style_ps2) def write_line(self,text,style=None): start,end = self.text.get_buffer().get_bounds() if style==None: self.text.get_buffer().insert(end,text) else: self.text.get_buffer().insert_with_tags(end,text,style) self.text.scroll_to_mark(self.mark, 0, True, 1, 1) def push(self, line): self.buffer.append(line) if len(line) > 0: self.history.append(line) source = "\n".join(self.buffer) more = self.interpreter.runsource(source, "<>") if not more: self.reset_buffer() return more def key_pressed(self,widget,event): if event.keyval == gtk.gdk.keyval_from_name('Return'): return self.execute_line() if event.keyval == gtk.gdk.keyval_from_name('Up'): self.current_history = self.current_history - 1 if self.current_history < - len(self.history): self.current_history = - len(self.history) return self.show_history() elif event.keyval == gtk.gdk.keyval_from_name('Down'): self.current_history = self.current_history + 1 if self.current_history > 0: self.current_history = 0 return self.show_history() elif event.keyval == gtk.gdk.keyval_from_name('Home'): l = self.text.get_buffer().get_line_count() - 1 start = self.text.get_buffer().get_iter_at_line_offset(l,4) self.text.get_buffer().place_cursor(start) return True elif event.keyval == gtk.gdk.keyval_from_name('space') and event.state & gtk.gdk.CONTROL_MASK: return self.complete_line() return False def show_history(self): if self.current_history == 0: return True else: self.replace_line( self.history[self.current_history] ) return True def current_line(self): start,end = self.current_line_bounds() return self.text.get_buffer().get_text(start,end, True) def current_line_bounds(self): txt_buffer = self.text.get_buffer() l = txt_buffer.get_line_count() - 1 start = txt_buffer.get_iter_at_line(l) if start.get_chars_in_line() >= 4: start.forward_chars(4) end = txt_buffer.get_end_iter() return start,end def replace_line(self,txt): start,end = self.current_line_bounds() self.text.get_buffer().delete(start,end) self.write_line(txt) def execute_line(self): line = self.current_line() self.write_line("\n") more = self.push(line) self.text.get_buffer().place_cursor(self.text.get_buffer().get_end_iter()) if more: self.prompt_ps2() else: self.prompt_ps1() self.current_history = 0 return True def complete_line(self): line = self.current_line() tokens = line.split() token = tokens[-1] completions = [] p = self.completer.complete(token,len(completions)) while p != None: completions.append(p) p = self.completer.complete(token, len(completions)) if len(completions) != 1: self.write_line("\n") self.write_line("\n".join(completions), self.style_ps1) self.write_line("\n") self.current_prompt() self.write_line(line) else: i = line.rfind(token) line = line[0:i] + completions[0] self.replace_line(line) return True def do_realize(self): gtk.ScrolledWindow.do_realize(self) self.stdout = OutputStream(self.text,sys.stdout,self.style_out) self.stderr = OutputStream(self.text,sys.stderr,self.style_err) sys.stdout = self.stdout sys.stderr = self.stderr def do_unrealize(self): sys.stdout = self.stdout.out sys.stderr = self.stderr.out gtk.ScrolledWindow.do_unrealize(self) def main(): w = gtk.Window() console = GTKInterpreterConsole() console.set_size_request(640,480) w.add(console) def destroy(arg=None): gtk.main_quit() def key_event(widget,event): if gtk.gdk.keyval_name(event.keyval) == 'd' and event.state & gtk.gdk.CONTROL_MASK: destroy() return False w.connect("destroy", destroy) w.add_events( gtk.gdk.KEY_PRESS_MASK ) w.connect( 'key_press_event', key_event) w.show_all() gtk.main() if __name__ == '__main__': main() # vim:sw=4:et:ai gaphor-0.17.2/gaphor/misc/errorhandler.py000066400000000000000000000021761220151210700203320ustar00rootroot00000000000000# vim:sw=4:et """A generic way to handle errors in GUI applications. This module also contains a ErrorHandlerAspect, which can be easely attached to a class' method and will raise the error dialog when the method exits with an exception. """ import gtk import sys import pdb from gaphor.i18n import _ def error_handler(message=None, exc_info=None): exc_type, exc_value, exc_traceback = exc_info or sys.exc_info() if not exc_type: return if not message: message = _('An error occured.') buttons = gtk.BUTTONS_OK message = '%s\n\nTechnical details:\n\t%s\n\t%s' % (message, exc_type, exc_value) if __debug__ and sys.stdin.isatty(): buttons = gtk.BUTTONS_YES_NO message += _('\n\nDo you want to debug?\n(Gaphor should have been started from the command line)') dialog = gtk.MessageDialog(None, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_ERROR, buttons, message) answer = dialog.run() dialog.destroy() if answer == gtk.RESPONSE_YES: pdb.post_mortem(exc_traceback) # vim:sw=4:et:ai gaphor-0.17.2/gaphor/misc/gidlethread.py000066400000000000000000000142361220151210700201170ustar00rootroot00000000000000# vim:sw=4:et: """This module contains some helpers that can be used to execute generator functions in the GObject main loop. This module provided the following classes: GIdleThread - Thread like behavior for generators in a main loop Queue - A simple queue implementation suitable for use with GIdleThread Exceptions: QueueEmpty - raised when one tried to get a value of an empty queue QueueFull - raised when the queue reaches it's max size and the oldest item may not be disposed. """ import sys import gobject import time import traceback class GIdleThread(object): """This is a pseudo-"thread" for use with the GTK+ main loop. This class does act a bit like a thread, all code is executed in the callers thread though. The provided function should be a generator (or iterator). It can be started with start(). While the "thread" is running is_alive() can be called to see if it's alive. wait([timeout]) will wait till the generator is finished, or timeout seconds. If an exception is raised from within the generator, it is stored in the exc_info property. Execution of the generator is finished. The exc_info property contains a tuple (exc_type, exc_value, exc_traceback), see sys.exc_info() for details. Note that this routine runs in the current thread, so there is no need for nasty locking schemes. Example (runs a counter through the GLib main loop routine): >>> def counter(max): ... for x in xrange(max): ... yield x >>> t = GIdleThread(counter(123)) >>> id = t.start() >>> main = gobject.main_context_default() >>> while t.is_alive(): ... main.iteration(False) # doctest: +ELLIPSIS True ... """ def __init__(self, generator, queue=None): assert hasattr(generator, 'next'), 'The generator should be an iterator' self._generator = generator self._queue = queue self._idle_id = 0 self._exc_info = (None, None, None) def start(self, priority=gobject.PRIORITY_LOW): """Start the generator. Default priority is low, so screen updates will be allowed to happen. """ idle_id = gobject.idle_add(self.__generator_executer, priority=priority) self._idle_id = idle_id return idle_id def wait(self, timeout=0): """Wait until the corouine is finished or return after timeout seconds. This is achieved by running the GTK+ main loop. """ clock = time.clock start_time = clock() main = gobject.main_context_default() while self.is_alive(): main.iteration(False) if timeout and (clock() - start_time >= timeout): return def interrupt(self): """Force the generator to stop running. """ if self.is_alive(): gobject.source_remove(self._idle_id) self._idle_id = 0 def is_alive(self): """Returns True if the generator is still running. """ return self._idle_id != 0 error = property(lambda self: self._exc_info[0], doc="Return a possible exception that had occured "\ "during execution of the generator") exc_info = property(lambda self: self._exc_info, doc="Return a exception information as provided by "\ "sys.exc_info()") def reraise(self): """Rethrow the error that occured during execution of the idle process. """ exc_info = self._exc_info if exc_info[0]: raise exc_info[0], exc_info[1], exc_info[2] def __generator_executer(self): try: result = self._generator.next() if self._queue: try: self._queue.put(result) except QueueFull: self.wait(0.5) # If this doesn't work... self._queue.put(result) return True except StopIteration: self._idle_id = 0 return False except: self._exc_info = sys.exc_info() #traceback.print_exc() self._idle_id = 0 return False class QueueEmpty(Exception): """Exception raised whenever the queue is empty and someone tries to fetch a value. """ pass class QueueFull(Exception): """Exception raised when the queue is full and the oldest item may not be disposed. """ pass class Queue(object): """A FIFO queue. If the queue has a max size, the oldest item on the queue is dropped if that size id exceeded. """ def __init__(self, size=0, dispose_oldest=True): self._queue = [] self._size = size self._dispose_oldest = dispose_oldest def put(self, item): """Put item on the queue. If the queue size is limited ... """ if self._size > 0 and len(self._queue) >= self._size: if self._dispose_oldest: self.get() else: raise QueueFull self._queue.insert(0, item) def get(self): """Get the oldest item off the queue. QueueEmpty is raised if no items are left on the queue. """ try: return self._queue.pop() except IndexError: raise QueueEmpty if __name__ == '__main__': def counter(max): for i in range(max): yield i def shower(queue): # Never stop reading the queue: while True: try: cnt = queue.get() print 'cnt =', cnt except QueueEmpty: pass yield None print 'Test 1: (should print range 0..22)' queue = Queue() c = GIdleThread(counter(23), queue) s = GIdleThread(shower(queue)) main = gobject.main_context_default() c.start() s.start() s.wait(2) print 'Test 2: (should only print 22)' queue = Queue(size=1) c = GIdleThread(counter(23), queue) s = GIdleThread(shower(queue)) main = gobject.main_context_default() c.start(priority=gobject.PRIORITY_DEFAULT) s.start() s.wait(3) gaphor-0.17.2/gaphor/misc/latepickle.py000066400000000000000000000065251220151210700177620ustar00rootroot00000000000000""" Modified pickler. This version uses forward declarations in the state machine, so recursion depth can be limited. """ import pickle, types, struct BUILD = pickle.BUILD INST = pickle.INST OBJ = pickle.OBJ MARK = pickle.MARK POP = pickle.POP STOP = pickle.STOP class LatePickler(pickle.Pickler): """ Pickler use breadth-first traversal. This approach limits the recursion depth of the pickler. For List types, a trick is to memorize all items at first, NOTE: this pickler does not work in binary mode! """ dispatch = dict(pickle.Pickler.dispatch) def __init__(self, file, protocol=0): pickle.Pickler.__init__(self, file, protocol) self.later = [] def dump(self, obj): if self.proto >= 2: self.write(PROTO + chr(self.proto)) self.save(obj) later = self.later memo = self.memo get = self.get save = self.save write = self.write index = 0 # Use while loop as objects may be added as we save more items: while later: obj, stuff = later[0] # First retrieve the object from the memo assert id(obj) in memo x = memo[id(obj)] write(get(x[0])) # Now populate it save(stuff) write(BUILD) write(POP) del later[0] self.write(STOP) def save_later(self, obj, stuff): self.later.append((obj, stuff)) def delay(self, obj): """ Use this to check if items can be saved later on, or need imediate saving. """ return True def save_inst(self, obj): """ For old-style classes! """ cls = obj.__class__ memo = self.memo write = self.write save = self.save if hasattr(obj, '__getinitargs__'): args = obj.__getinitargs__() len(args) # XXX Assert it's a sequence _keep_alive(args, memo) else: args = () write(MARK) if self.bin: save(cls) for arg in args: save(arg) write(OBJ) else: for arg in args: save(arg) write(INST + cls.__module__ + '\n' + cls.__name__ + '\n') self.memoize(obj) try: getstate = obj.__getstate__ except AttributeError: stuff = obj.__dict__ else: stuff = getstate() _keep_alive(stuff, memo) if self.delay(obj): # TODO: delay this until all is saved: self.save_later(obj, stuff) else: save(stuff) write(BUILD) dispatch[types.InstanceType] = save_inst def save_reduce(self, func, args, state=None, listitems=None, dictitems=None, obj=None): #print 'saving reduce', func, args, obj # We want to reduce nesting, hence the state should be saved later: if obj and state and self.delay(obj): self.save_later(obj, state) state = None pickle.Pickler.save_reduce(self, func, args, state, listitems, dictitems, obj) # vim:sw=4:et:ai gaphor-0.17.2/gaphor/misc/listmixins.py000066400000000000000000000172461220151210700200520ustar00rootroot00000000000000""" This module contains some support code for queries on lists. Two mixin classes are provided: 1. ``querymixin`` 2. ``recursemixin`` See the documentation on the mixins. """ __all__ = [ 'querymixin', 'recursemixin', 'getslicefix' ] import sys class Matcher(object): """ Returns True if the expression returns True. The context for the expression is the element. Given a class: >>> class A(object): ... def __init__(self, name): self.name = name We can create a path for each object: >>> a = A('root') >>> a.a = A('level1') >>> a.b = A('b') >>> a.a.text = 'help' If we want to match, ``it`` is used to refer to the subjected object: >>> Matcher('it.name=="root"')(a) True >>> Matcher('it.b.name=="b"')(a) True >>> Matcher('it.name=="blah"')(a) False >>> Matcher('it.nonexistent=="root"')(a) False NOTE: the object ``it`` was introduced since properties (descriptors) can not be executed from within a dictionary context. """ def __init__(self, expr): self.expr = compile(expr, '', 'eval') def __call__(self, element): try: return eval(self.expr, {}, { 'it': element }) except (AttributeError, NameError): # attribute does not (yet) exist #print 'No attribute', expr, d return False class querymixin(object): """ Implementation of the matcher as a mixin for lists. Given a class: >>> class A(object): ... def __init__(self, name): self.name = name We can do nice things with this list: >>> class MList(querymixin, list): ... pass >>> m = MList() >>> m.append(A('one')) >>> m.append(A('two')) >>> m.append(A('three')) >>> m[1].name 'two' >>> m['it.name=="one"'] # doctest: +ELLIPSIS [] >>> m['it.name=="two"', 0].name 'two' """ def __getitem__(self, key): try: # See if the list can deal with it (don't change default behaviour) return super(querymixin, self).__getitem__(key) except TypeError: # Nope, try our matcher trick if type(key) is tuple: key, remainder = key[0], key[1:] else: remainder = None matcher = Matcher(key) matched = filter(matcher, self) if remainder: return type(self)(matched).__getitem__(*remainder) else: return type(self)(matched) def issafeiterable(obj): """ Checks if the object is iterable, but not a string. >>> issafeiterable([]) True >>> issafeiterable(set()) True >>> issafeiterable({}) True >>> issafeiterable(1) False >>> issafeiterable('text') False """ try: return iter(obj) and not isinstance(obj, basestring) except TypeError: pass return False class recurseproxy(object): """ Proxy object (helper) for the recusemixin. The proxy has limited capabilities compared to a real list (or set): it can be iterated and a getitem can be performed. On the other side, the type of the original sequence is maintained, so getitem operations act as if they're executed on the original list. """ def __init__(self, sequence): self.__sequence = sequence def __getitem__(self, key): return self.__sequence.__getitem__(key) def __iter__(self): """ Iterate over the items. If there is some level of nesting, the parent items are iterated as well. """ return iter(self.__sequence) def __getattr__(self, key): """ Create a new proxy for the attribute. """ def mygetattr(): for e in self.__sequence: try: obj = getattr(e, key) if issafeiterable(obj): for i in obj: yield i else: yield obj except AttributeError: pass # Create a copy of the proxy type, inclusing a copy of the sequence type return type(self)(type(self.__sequence)(mygetattr())) class recursemixin(object): """ Mixin class for lists, sets, etc. If data is requested using ``[:]``, a ``recurseproxy`` instance is created. The basic idea is to have a class that can contain children: >>> class A(object): ... def __init__(self, name, *children): ... self.name = name ... self.children = list(children) ... def dump(self, level=0): ... print ' ' * level, self.name ... for c in self.children: c.dump(level+1) Now if we make a (complex) structure out of it: >>> a = A('root', A('a', A('b'), A('c'), A('d')), A('e', A('one'), A('two'))) >>> a.dump() # doctest: +ELLIPSIS root a b c d e one two >>> a.children[1].name 'e' Given ``a``, I want to iterate all grand-children (b, c, d, one, two) and the structure I want to do that with is: ``a.children[:].children`` In order to do this we have to use a special list class, so we can handle our specific case. ``__getslice__`` should be overridden, so we can make it behave like a normal python object (legacy, yes...). >>> class rlist(recursemixin, getslicefix, list): ... pass >>> class A(object): ... def __init__(self, name, *children): ... self.name = name ... self.children = rlist(children) ... def dump(self, level=0): ... print ' ' * level, self.name ... for c in self.children: c.dump(level+1) >>> a = A('root', A('a', A('b'), A('c'), A('d')), A('e', A('one'), A('two'))) >>> a.children[1].name 'e' Invoking ``a.children[:]`` should now return a recurseproxy object: >>> a.children[:] # doctest: +ELLIPSIS >>> list(a.children[:].name) # doctest: +ELLIPSIS ['a', 'e'] Now calling a child on the list will return a list of all children: >>> a.children[:].children # doctest: +ELLIPSIS >>> list(a.children[:].children) # doctest: +ELLIPSIS [, , , , ] And of course we're interested in the names: >>> a.children[:].children.name # doctest: +ELLIPSIS >>> list(a.children[:].children.name) ['b', 'c', 'd', 'one', 'two'] """ _recursemixin_trigger = slice(None, None, None) def proxy_class(self): return recurseproxy def __getitem__(self, key): if key == self._recursemixin_trigger: return self.proxy_class()(self) else: return super(recursemixin, self).__getitem__(key) class getslicefix(object): """ C-Python classes still use __getslice__. This behaviour is depricated and getitem should be called instead. """ def __getslice__(self, a, b, c=None): """ ``__getslice__`` is deprecated. Calls are redirected to ``__getitem__()``. """ if a == 0: a = None if b == sys.maxint: b = None return self.__getitem__(slice(a, b, c)) # vim: sw=4:et:ai gaphor-0.17.2/gaphor/misc/odict.py000066400000000000000000000031271220151210700167420ustar00rootroot00000000000000# from http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/107747 class odict(dict): def __init__(self, dict=()): self._keys = [] super(odict, self).__init__(dict) def __delitem__(self, key): dict.__delitem__(self, key) self._keys.remove(key) def __setitem__(self, key, item): dict.__setitem__(self, key, item) if key not in self._keys: self._keys.append(key) def clear(self): dict.clear(self) self._keys = [] def copy(self): dict = dict.copy(self) dict._keys = self._keys[:] return dict def items(self): return zip(self._keys, self.values()) def keys(self): return self._keys def popitem(self): try: key = self._keys[-1] except IndexError: raise KeyError('dictionary is empty') val = self[key] del self[key] return (key, val) def setdefault(self, key, failobj=None): dict.setdefault(self, key, failobj) if key not in self._keys: self._keys.append(key) def update(self, dict): dict.update(self, dict) for key in dict.keys(): if key not in self._keys: self._keys.append(key) def values(self): return map(self.get, self._keys) def swap(self, k1, k2): """ Swap two elements using their keys. """ i1 = self._keys.index(k1) i2 = self._keys.index(k2) self._keys[i1], self._keys[i2] = self._keys[i2], self._keys[i1] def __iter__(self): for k in self._keys: yield k gaphor-0.17.2/gaphor/misc/rattr.py000066400000000000000000000023551220151210700167760ustar00rootroot00000000000000""" Recursive attribute access functions. """ def rgetattr(obj, attr): """ Get named attribute from an object, i.e. getattr(obj, 'a.a') is equivalent to ``obj.a.a''. - obj: object - attr: attribute name(s) >>> class A(object): pass >>> a = A() >>> a.a = A() >>> a.a.a = 1 >>> rgetattr(a, 'a.a') 1 >>> rgetattr(a, 'a.c') Traceback (most recent call last): ... AttributeError: 'A' object has no attribute 'c' """ attrs = attr.split('.') obj = getattr(obj, attrs[0]) for name in attrs[1:]: obj = getattr(obj, name) return obj def rsetattr(obj, attr, val): """ Set named attribute value on an object, i.e. setattr(obj, 'a.a', 1) is equivalent to ``obj.a.a = 1''. - obj: object - attr: attribute name(s) - val: attribute value >>> class A(object): pass >>> a = A() >>> a.a = A() >>> a.a.a = 1 >>> rsetattr(a, 'a.a', 2) >>> print a.a.a 2 >>> rsetattr(a, 'a.c', 3) >>> print a.a.c 3 """ attrs = attr.split('.') if len(attrs) > 1: obj = getattr(obj, attrs[0]) for name in attrs[1:-1]: obj = getattr(obj, name) setattr(obj, attrs[-1], val) # vim:sw=4:et:ai gaphor-0.17.2/gaphor/misc/tests/000077500000000000000000000000001220151210700164255ustar00rootroot00000000000000gaphor-0.17.2/gaphor/misc/tests/test_xmlwriter.py000066400000000000000000000046031220151210700220760ustar00rootroot00000000000000 import sys import unittest from gaphor.misc.xmlwriter import XMLWriter class Writer: def __init__(self): self.s = '' def write(self, text): self.s += text class XMLWriterTestCase(unittest.TestCase): def test_elements_1(self): w = Writer() xml_w = XMLWriter(w) xml_w.startDocument() xml_w.startElement('foo', {}) xml_w.endElement('foo') xml = """\n""" % sys.getdefaultencoding() assert w.s == xml, w.s + ' != ' + xml def test_elements_2(self): w = Writer() xml_w = XMLWriter(w) xml_w.startDocument() xml_w.startElement('foo', {}) xml_w.startElement('bar', {}) xml_w.endElement('bar') xml_w.endElement('foo') xml = """\n\n\n""" % sys.getdefaultencoding() assert w.s == xml, w.s def test_elements_test(self): w = Writer() xml_w = XMLWriter(w) xml_w.startDocument() xml_w.startElement('foo', {}) xml_w.startElement('bar', {}) xml_w.characters('hello') xml_w.endElement('bar') xml_w.endElement('foo') xml = """\n\nhello\n""" % sys.getdefaultencoding() assert w.s == xml, w.s def test_elements_ns_default(self): w = Writer() xml_w = XMLWriter(w) xml_w.startDocument() xml_w.startPrefixMapping(None, 'http://gaphor.devjavu.com/schema') xml_w.startElementNS(('http://gaphor.devjavu.com/schema', 'foo'), 'qn', {}) xml_w.endElementNS(('http://gaphor.devjavu.com/schema', 'foo'), 'qn') xml = """\n""" % sys.getdefaultencoding() assert w.s == xml, w.s def test_elements_ns_1(self): w = Writer() xml_w = XMLWriter(w) xml_w.startDocument() xml_w.startPrefixMapping('g', 'http://gaphor.devjavu.com/schema') xml_w.startElementNS(('http://gaphor.devjavu.com/schema', 'foo'), 'qn', {}) xml_w.endElementNS(('http://gaphor.devjavu.com/schema', 'foo'), 'qn') xml = """\n""" % sys.getdefaultencoding() assert w.s == xml, w.s # vim:sw=4:et:ai gaphor-0.17.2/gaphor/misc/xmlwriter.py000066400000000000000000000112441220151210700176740ustar00rootroot00000000000000# vim:sw=4:et import sys from xml.sax.saxutils import escape, quoteattr import xml.sax.handler # See whether the xmlcharrefreplace error handler is # supported try: from codecs import xmlcharrefreplace_errors _error_handling = "xmlcharrefreplace" del xmlcharrefreplace_errors except ImportError: _error_handling = "strict" class XMLWriter(xml.sax.handler.ContentHandler): def __init__(self, out=None, encoding=None): if out is None: out = sys.stdout xml.sax.handler.ContentHandler.__init__(self) self._out = out self._ns_contexts = [{}] # contains uri -> prefix dicts self._current_context = self._ns_contexts[-1] self._undeclared_ns_maps = [] self._encoding = encoding or sys.getdefaultencoding() self._in_cdata = False self._in_start_tag = False self._next_newline = False def _write(self, text, start_tag=False, end_tag=False): """ Write data. Tags should not be escaped. They should be marked by setting either ``start_tag`` or ``end_tag`` to ``True``. Only the tag should be marked this way. Other stuff, such as namespaces and attributes can be written directly to the file. """ if self._next_newline: self._out.write('\n') self._next_newline = False if start_tag and not self._in_start_tag: self._in_start_tag = True self._out.write('<') elif start_tag and self._in_start_tag: self._out.write('>') self._out.write('\n') self._out.write('<') elif end_tag and self._in_start_tag: self._out.write('/>') self._in_start_tag = False self._next_newline = True return elif not start_tag and self._in_start_tag: self._out.write('>') self._in_start_tag = False elif end_tag: self._out.write('') self._in_start_tag = False self._next_newline = True return if isinstance(text, str): self._out.write(text) else: self._out.write(text.encode(self._encoding, _error_handling)) def _qname(self, name): """Builds a qualified name from a (ns_url, localname) pair""" if name[0]: # The name is in a non-empty namespace prefix = self._current_context[name[0]] if prefix: # If it is not the default namespace, prepend the prefix return prefix + ":" + name[1] # Return the unqualified name return name[1] # ContentHandler methods def startDocument(self): self._write('\n' % self._encoding) def startPrefixMapping(self, prefix, uri): self._ns_contexts.append(self._current_context.copy()) self._current_context[uri] = prefix self._undeclared_ns_maps.append((prefix, uri)) def endPrefixMapping(self, prefix): self._current_context = self._ns_contexts[-1] del self._ns_contexts[-1] def startElement(self, name, attrs): self._write(name, start_tag=True) for (name, value) in attrs.items(): self._out.write(' %s=%s' % (name, quoteattr(value))) def endElement(self, name): self._write(name, end_tag=True) def startElementNS(self, name, qname, attrs): self._write(self._qname(name), start_tag=True) for prefix, uri in self._undeclared_ns_maps: if prefix: self._out.write(' xmlns:%s="%s"' % (prefix, uri)) else: self._out.write(' xmlns="%s"' % uri) self._undeclared_ns_maps = [] for (name, value) in attrs.items(): self._out.write(' %s=%s' % (self._qname(name), quoteattr(value))) def endElementNS(self, name, qname): self._write('%s' % self._qname(name), end_tag=True) def characters(self, content): if self._in_cdata: self._write(content.replace(']]>', '] ]>')) else: self._write(escape(content)) def ignorableWhitespace(self, content): self._write(content) def processingInstruction(self, target, data): self._write('' % (target, data)) def comment(self, comment): self._write('', '- ->')) self._write(' -->') def startCDATA(self): self._write('') self._in_cdata = False # vim:sw=4:et:ai gaphor-0.17.2/gaphor/plugins/000077500000000000000000000000001220151210700160115ustar00rootroot00000000000000gaphor-0.17.2/gaphor/plugins/__init__.py000066400000000000000000000011601220151210700201200ustar00rootroot00000000000000""" Plugins ======= This module contains a bunch of standard plugins. Plugins can be registered in Gaphor by declaring them as service entry point:: entry_points = { 'gaphor.services': [ 'xmi_export = gaphor.plugins.xmiexport:XMIExport', ], }, There is a thin line between a service and a plugin. A service typically performs some basic need for the applications (such as the element factory or the undo mechanism). A plugin is more of an add-on. For example a plugin can depend on external libraries or provide a cross-over function between two applications. """ gaphor-0.17.2/gaphor/plugins/alignment/000077500000000000000000000000001220151210700177675ustar00rootroot00000000000000gaphor-0.17.2/gaphor/plugins/alignment/__init__.py000066400000000000000000000122201220151210700220750ustar00rootroot00000000000000""" This plugin extends Gaphor with XMI alignment actions. """ from zope import interface, component from gaphor.core import inject, transactional, action, build_action_group from gaphor.interfaces import IService, IActionProvider from gaphor.ui.interfaces import IDiagramSelectionChange class Alignment(object): interface.implements(IService, IActionProvider) component_registry = inject('component_registry') menu_xml = """ """ def __init__(self): self.action_group = build_action_group(self) self._last_update = None def init(self, app): self.component_registry.register_handler(self.update) def shutdown(self): self.component_registry.unregister_handler(self.update) @component.adapter(IDiagramSelectionChange) def update(self, event=None): self._last_update = event sensitive = event and len(event.selected_items) > 1 self.action_group.get_action('align-left').set_sensitive(sensitive) self.action_group.get_action('align-center').set_sensitive(sensitive) self.action_group.get_action('align-right').set_sensitive(sensitive) self.action_group.get_action('align-top').set_sensitive(sensitive) self.action_group.get_action('align-middle').set_sensitive(sensitive) self.action_group.get_action('align-bottom').set_sensitive(sensitive) def get_items(self): return (self._last_update and self._last_update.selected_items) or [] def get_focused_item(self): return self._last_update.focused_item def getXCoordsLeft(self, items): return [item.matrix[4] for item in items] def getXCoordsRight(self, items): return [item.matrix[4] + item.width for item in items] def getYCoordsTop(self, items): return [item.matrix[5] for item in items] def getYCoordsBottom(self, items): return [item.matrix[5] + item.height for item in items] @action(name='align-left', label='Left', tooltip="Vertically align diagram elements on the left", accel='l') @transactional def align_left(self): items = self.get_items() fitem = self.get_focused_item() target_x= fitem.matrix[4] for item in items: x = target_x - item.matrix[4] item.matrix.translate(x,0) item.request_update() @action(name='align-center', label='Center', tooltip="Vertically align diagram elements on their centers", accel='c') @transactional def align_center(self): items = self.get_items() fitem = self.get_focused_item() min_x=min(self.getXCoordsLeft(items)) max_x=max(self.getXCoordsRight(items)) center_x = fitem.matrix[4] + (fitem.width / 2) for item in items: x = center_x - (item.width / 2) - item.matrix[4] item.matrix.translate(x,0) item.request_update() @action(name='align-right', label='Right', tooltip="Vertically align diagram elements on the right", accel='r') @transactional def align_right(self): items = self.get_items() fitem = self.get_focused_item() target_x= fitem.matrix[4] + fitem.width for item in items: x = target_x - item.width - item.matrix[4] item.matrix.translate(x,0) item.request_update() @action(name='align-top', label='Top', tooltip="Horizontally align diagram elements on their tops", accel='t') @transactional def align_top(self): items = self.get_items() fitem = self.get_focused_item() target_y = fitem.matrix[5] for item in items: y = target_y - item.matrix[5] item.matrix.translate(0,y) item.request_update() @action(name='align-middle', label='Middle', tooltip="Horizontally align diagram elements on their middles", accel='m') @transactional def align_middle(self): items = self.get_items() fitem = self.get_focused_item() middle_y = fitem.matrix[5] + (fitem.height / 2) for item in items: y = middle_y - (item.height / 2) - item.matrix[5] item.matrix.translate(0,y) item.request_update() @action(name='align-bottom', label='Bottom', tooltip="Horizontally align diagram elements on their bottoms", accel='b') @transactional def align_bottom(self): items = self.get_items() fitem = self.get_focused_item() target_y = fitem.matrix[5] + fitem.height for item in items: y = target_y - item.height - item.matrix[5] item.matrix.translate(0,y) item.request_update() # vim:sw=4:et:ai gaphor-0.17.2/gaphor/plugins/checkmetamodel/000077500000000000000000000000001220151210700207565ustar00rootroot00000000000000gaphor-0.17.2/gaphor/plugins/checkmetamodel/__init__.py000066400000000000000000000000721220151210700230660ustar00rootroot00000000000000# vim:sw=4:et from checkmodelgui import CheckModelWindow gaphor-0.17.2/gaphor/plugins/checkmetamodel/checkmodel.py000066400000000000000000000075231220151210700234350ustar00rootroot00000000000000 from gaphor import UML from os import path def report(element, message): print '%s: %s' % (type(element).__name__, message) def get_subsets(tagged_value): subsets = [] if tagged_value.find('subsets') != -1: subsets = tagged_value[tagged_value.find('subsets') + len('subsets'):] subsets = subsets.replace(' ', '').replace('\n', '').replace('\r', '') subsets = subsets.split(',') return subsets def get_redefine(tagged_value): redefines = tag[tag.find('redefines') + len('redefines'):] # remove all whitespaces and stuff redefines = redefines.replace(' ', '').replace('\n', '').replace('\r', '') redefines = redefines.split(',') assert len(redefines) == 1 return redefines[0] def get_superclasses(class_): for superclass in class_.superClass: gen = 1 def check_classes(element_factory): classes = element_factory.select(lambda e: e.isKindOf(UML.Class)) names = [ c.name for c in classes ] for c in classes: if names.count(c.name) > 1: report(c, 'Class name %s used more than once' % c.name) def check_association_end_subsets(element_factory, end): # TODO: don't use Tagged values, use Stereotype values or something subsets = get_subsets(end.taggedValue and end.taggedValue[0].value or '') opposite_subsets = get_subsets(end.opposite.taggedValue and end.opposite.taggedValue[0].value or '') subset_properties = element_factory.select(lambda e: e.isKindOf(UML.Property) and e.name in subsets) # TODO: check if properties belong to a superclass of the end's class # TODO: check if the association end is navigable when the subsets are navigable for p in subset_properties: pass # Check if bi-directional derived unions are bi-directional on this association for p in subset_properties: if p.opposite.name and p.opposite.name not in opposite_subsets: report(end, 'Subset not defined on both sides. (%s, %s)' % (p.name, p.opposite.name)) # Check if multiplicity of the properties matches the multiplicity of the subsets for p in subset_properties: if p.upperValue: if not end.upperValue: report(end, 'Association end %s has no upper value, but the subset %s has' % (end.name, p.name)) elif p.upperValue.value < end.upperValue.value: report(end, 'Association end %s has has a bigger upper value than subse %s' % (end.name, p.name)) def check_association_end(element_factory, end): check_association_end_subsets(element_factory, end) def check_associations(element_factory): for a in element_factory.select(lambda e: e.isKindOf(UML.Association)): assert len(a.memberEnd) == 2 head = a.memberEnd[0] tail = a.memberEnd[1] check_association_end(element_factory, head) check_association_end(element_factory, tail) def check_attributes(element_factory): for a in element_factory.select(lambda e: e.isKindOf(UML.Property) and not e.association): if not a.typeValue or not a.typeValue.value: report(a,'Attribute has no type: %s' % a.name) elif a.typeValue.value.lower() not in ('string', 'boolean', 'integer', 'unlimitednatural'): report(a, 'Invalid attribute type: %s' % a.typeValue.value) # TODO: Check the sanity of the generated data model. def check_UML_module(): all_classes = map(getattr, [UML] * len(dir(UML)), dir(UML)) for c in all_classes: if not isinstance(c, UML.Element): continue # TODO: check derived unions. if __name__ == '__main__': from gaphor.UML import ElementFactory from gaphor import storage element_factory = ElementFactory() storage.load(path.join('gaphor', 'UML', 'uml2.gaphor'), factory=element_factory) check_associations(element_factory) check_attributes(element_factory) # vim:sw=4:et gaphor-0.17.2/gaphor/plugins/checkmetamodel/checkmodelgui.py000066400000000000000000000077331220151210700241450ustar00rootroot00000000000000""" A GUI for the checkmodel plugin. """ import sys import gobject import gtk from zope import interface, component from gaphor.core import _, inject, action, build_action_group from gaphor.interfaces import IService, IActionProvider import checkmodel PYELEMENT_COLUMN = 0 ELEMENT_COLUMN = 1 REASON_COLUMN = 2 class CheckModelWindow(object): interface.implements(IService, IActionProvider) element_factory = inject('element_factory') main_window = inject('main_window') menu_xml = """ """ def __init__(self): # Override the report method checkmodel.report = self.on_report self.action_group = build_action_group(self) def init(self, app): pass def shutdown(self): pass @action(name='tools-open-check-model', label='Check UML model') def open(self): self.construct() self.run() def construct(self): model = gtk.ListStore(gobject.TYPE_PYOBJECT, gobject.TYPE_STRING, gobject.TYPE_STRING) treeview = gtk.TreeView(model) treeview.connect('row-activated', self.on_row_activated) selection = treeview.get_selection() selection.set_mode('single') treeview.set_size_request(200, -1) scrolled_window = gtk.ScrolledWindow() scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) scrolled_window.set_shadow_type(gtk.SHADOW_IN) scrolled_window.add(treeview) scrolled_window.show() cell = gtk.CellRendererText() column = gtk.TreeViewColumn("Element", cell, text=ELEMENT_COLUMN) treeview.append_column(column) cell = gtk.CellRendererText() column = gtk.TreeViewColumn("Reason", cell, text=REASON_COLUMN) treeview.append_column(column) treeview.show() #self._construct_window(name='checkmodel', # title='Check Model', # size=(400, 400), # contents=scrolled_window) self.model = model self.treeview = treeview self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.connect('destroy', self.on_destroy) self.window.set_title('Gaphor - Check Model') self.window.add(scrolled_window) self.window.set_size_request(400, 400) self.window.show() def run(self): # TODO: Let this run in a Thread(?) checkmodel.check_classes(self.element_factory) checkmodel.check_attributes(self.element_factory) checkmodel.check_associations(self.element_factory) def on_report(self, element, message): log.info('%s: %s' % (type(element).__name__, message)) model = self.model iter = model.append() model.set_value(iter, PYELEMENT_COLUMN, element) model.set_value(iter, ELEMENT_COLUMN, type(element).__name__) model.set_value(iter, REASON_COLUMN, message) main = gobject.main_context_default() main.iteration(False) def on_row_activated(self, treeview, row, column): iter = self.model.get_iter(row) element = self.model.get_value(iter, PYELEMENT_COLUMN) print 'Looking for element', element if element.presentation: main_window = self.main_window presentation = element.presentation[0] try: diagram = presentation.canvas.diagram except AttributeError: presentation = element.namespace.presentation[0] diagram = presentation.canvas.diagram diagram_tab = main_window.show_diagram(diagram) diagram_tab.view.focused_item = presentation def on_destroy(self, window): self.window = None self.treeview = None # vim:sw=4:et gaphor-0.17.2/gaphor/plugins/diagramlayout/000077500000000000000000000000001220151210700206535ustar00rootroot00000000000000gaphor-0.17.2/gaphor/plugins/diagramlayout/__init__.py000066400000000000000000000170631220151210700227730ustar00rootroot00000000000000""" This module provides a means to automatocally layout diagrams. The layout is done like this: - First all nodes (Classes, packages, comments) on a digram are determined - A vertical ordering is determined based on the inheritance - A horizontal ordering is determined based on associations and dependencies - The nodes are moved to their place - Lines are reconnected to the nodes, so everything looks pretty. """ from zope import interface, component from gaphor.core import _, inject, action, build_action_group, transactional from gaphor.interfaces import IService, IActionProvider import random from gaphor.diagram import items import toposort class DiagramLayout(object): interface.implements(IService, IActionProvider) main_window = inject('main_window') menu_xml = """ """ def __init__(self): self.action_group = build_action_group(self) def init(self, app): pass def shutdown(self): pass def update(self): self.sensitive = bool(self.get_window().get_current_diagram()) @action(name='diagram-layout', label='Layout diagram', tooltip='simple diagram layout') def execute(self): d = self.main_window.get_current_diagram() self.layout_diagram(d) @transactional def layout_diagram(self, diag): layout_diagram(diag) MARGIN = 100 def layout_diagram(diag): """ So an attempt to layout (order) the items on a diagram. The items should already be placed on the diagram and the items should already be connected. This function works on the diagram items (hence it does not check relations in the datamodel, only the ones drawn on the diagram) to produce a decent layout. """ nodes = [] primary_nodes = [] relations = [] other_relations = [] # Make sure all items are updated diag.canvas.update_now() # First extract data from the diagram (which ones are the nodes, and # the relationships). for item in diag.canvas.get_root_items(): if isinstance(item, (items.GeneralizationItem, items.ImplementationItem)): # Primary relationships, should be drawn top-down try: relations.append((item.handles[0].connected_to, item.handles[-1].connected_to)) primary_nodes.extend(relations[-1]) except Exception, e: log.error(e) elif isinstance(item, items.DiagramLine): # Secondary (associations, dependencies) may be drawn top-down # or left-right try: other_relations.append((item.handles[0].connected_to, item.handles[-1].connected_to)) #other_relations.append((item.handles[-1].connected_to, # item.handles[0].connected_to)) except Exception, e: log.error(e) else: nodes.append(item) # Add some randomness: random.shuffle(other_relations) primary_nodes = uniq(primary_nodes) # Find out our horizontal and vertical sorting sorted = toposort.toposort(nodes, relations, 0) other_sorted = toposort.toposort(nodes, other_relations, 0) if not sorted: return; # Move nodes from the first (generalization) row to the other rows # if they are not superclasses for some other class # Run the list twice, just to ensure no items are left behind. for item in list(sorted[0]) * 2: if item not in primary_nodes and item in sorted[0]: # Find nodes that do have a relation to this one related = find_related_nodes(item, other_relations) # Figure out what row(s) they're on row = find_row(item, related, sorted[1:]) if row: #print 'moving', item.subject.name, 'to row', sorted.index(row) sorted[0].remove(item) row.append(item) # Order each row based on the sort order of other_sorted # (the secondary sort alg.). for row in sorted: for other_row in other_sorted: for other_item in other_row: if other_item in row: row.remove(other_item) row.append(other_item) # Place the nodes on the diagram. y = MARGIN / 2 for row in sorted: x = MARGIN / 2 maxy = 0 for item in row: if not item: continue maxy = max(maxy, item.height) a = item.matrix a = (a[0], a[1], a[2], a[3], x, y) item.matrix = a item.request_update() x += item.width + MARGIN y += maxy + MARGIN # Reattach the relationships to the nodes, in a way that it looks nice. simple_layout_lines(diag) def simple_layout_lines(diag): """ Just do the layout of the lines in a diagram. The nodes (class, package) are left where they are (use layout_diagram() if you want to reorder everything). The line layout is basically very simple: just draw straight lines between nodes on the diagram. """ lines = {} for item in diag.canvas.get_root_items(): if isinstance(item, items.DiagramLine): # Secondary (associations, dependencies) may be drawn top-down # or left-right try: lines[item] = (item.handles[0].connected_to, item.handles[-1].connected_to) except Exception, e: log.error(e) # Now we have the lines, let's first ensure we only have a begin and an # end handle for line in lines.keys(): while len(line.handles) > 2: line.set_property('del_segment', 0) # Strategy 1: # Now we have nice short lines. Let's move them to a point between # both nodes and let the connect() do the work: for line, nodes in lines.items(): if not nodes[0] or not nodes[1]: # loose end continue center0 = find_center(nodes[0]) center1 = find_center(nodes[1]) center = (center0[0] + center1[0]) / 2.0, (center0[1] + center1[1]) / 2.0 line.handles[0].set_pos_w(*center) line.handles[-1].set_pos_w(*center) nodes[0].connect_handle(line.handles[0]) nodes[1].connect_handle(line.handles[-1]) def uniq(lst): d = {} for l in lst: d[l] = None return d.keys() def find_related_nodes(item, relations): """ Find related nodes of item, given a list of tuples. References to itself are ignored. """ related = [] for pair in relations: if pair[0] is item: if pair[1] is not item: related.append(pair[1]) elif pair[1] is item: if pair[0] is not item: related.append(pair[0]) return uniq(related) def find_row(item, related_items, sorted): """ Find the row that contains the most references to item. """ max_refs = 0 max_row = None for row in sorted: cnt = len([ i for i in row if i in related_items ]) if cnt > max_refs: max_row = row max_refs = cnt return max_row def find_center(item): """ Find the center point of the item, in world coordinates """ x = item.width / 2.0 y = item.height / 2.0 return item.canvas.get_matrix_i2c(item).transform_point(x, y) # vim:sw=4:et gaphor-0.17.2/gaphor/plugins/diagramlayout/tests/000077500000000000000000000000001220151210700220155ustar00rootroot00000000000000gaphor-0.17.2/gaphor/plugins/diagramlayout/tests/__init__.py000066400000000000000000000000001220151210700241140ustar00rootroot00000000000000gaphor-0.17.2/gaphor/plugins/diagramlayout/tests/test_diagramlayout.py000066400000000000000000000016201220151210700262670ustar00rootroot00000000000000 import unittest from gaphor import UML from gaphor.diagram import items from gaphor.plugins.diagramlayout import DiagramLayout from gaphor.application import Application from gaphor.tests.testcase import TestCase class DiagramLayoutTestCase(TestCase): services = TestCase.services + ['main_window', 'ui_manager', 'properties', 'action_manager', 'diagram_layout'] def testDiagramLayout(self): elemfact = Application.get_service('element_factory') diagram_layout = Application.get_service('diagram_layout') diagram = elemfact.create(UML.Diagram) c1 = diagram.create(items.ClassItem, subject=elemfact.create(UML.Class)) c2 = diagram.create(items.ClassItem, subject=elemfact.create(UML.Class)) c2.matrix.translate(100, 100) c2.request_update() diagram_layout.layout_diagram(diagram) # vim:sw=4:et:ai gaphor-0.17.2/gaphor/plugins/diagramlayout/toposort.py000066400000000000000000000177411220151210700231300ustar00rootroot00000000000000class RecursionError( OverflowError, ValueError ): '''Unable to calculate result because of recursive structure''' def sort(nodes, routes, noRecursion=1): '''Passed a list of node IDs and a list of source,dest ID routes attempt to create a list of stages where each sub list is one stage in a process. ''' children, parents = _buildChildrenLists(routes) # first stage is those nodes # having no incoming routes... stage = [] stages = [stage] taken = [] for node in nodes: if (not parents.get(node)): stage.append (node) if nodes and not stage: # there is no element which does not depend on # some other element!!! stage.append( nodes[0]) taken.extend( stage ) nodes = filter ( lambda x, l=stage: x not in l, nodes ) while nodes: previousStageChildren = [] nodelen = len(nodes) # second stage are those nodes # which are direct children of the first stage for node in stage: for child in children.get (node, []): if child not in previousStageChildren and child not in taken: previousStageChildren.append(child) elif child in taken and noRecursion: raise RecursionError( (child, node) ) # unless they are children of other direct children... # TODO, actually do that... stage = previousStageChildren removes = [] for current in stage: currentParents = parents.get( current, [] ) for parent in currentParents: if parent in stage and parent != current: # might wind up removing current... if not current in parents.get(parent, []): # is not mutually dependent... removes.append( current ) for remove in removes: while remove in stage: stage.remove( remove ) stages.append( stage) taken.extend( stage ) nodes = filter ( lambda x, l=stage: x not in l, nodes ) if nodelen == len(nodes): if noRecursion: raise RecursionError( nodes ) else: stages.append( nodes[:] ) nodes = [] return stages def _buildChildrenLists (routes): childrenTable = {} parentTable = {} for sourceID,destinationID in routes: currentChildren = childrenTable.get( sourceID, []) currentParents = parentTable.get( destinationID, []) if not destinationID in currentChildren: currentChildren.append ( destinationID) if not sourceID in currentParents: currentParents.append ( sourceID) childrenTable[sourceID] = currentChildren parentTable[destinationID] = currentParents return childrenTable, parentTable def toposort (nodes, routes, noRecursion=1): '''Topological sort from Tim Peters, fairly efficient in comparison (it seems).''' #first calculate the recursion depth dependencies = {} inversedependencies = {} if not nodes: return [] if not routes: return [nodes] for node in nodes: dependencies[ node ] = (0, node) inversedependencies[ node ] = [] for depended, depends in routes: # is it a null rule try: newdependencylevel, object = dependencies.get ( depends, (0, depends)) except TypeError: print depends raise dependencies[ depends ] = (newdependencylevel + 1, depends) # "dependency (existence) of depended-on" newdependencylevel,object = dependencies.get ( depended, (0, depended) ) dependencies[ depended ] = (newdependencylevel, depended) # Inverse dependency set up dependencieslist = inversedependencies.get ( depended, []) dependencieslist.append (depends) inversedependencies[depended] = dependencieslist ### Now we do the actual sorting # The first task is to create the sortable # list of dependency-levels sortinglist = dependencies.values() sortinglist.sort () output = [] while sortinglist: deletelist = [] generation = [] output.append( generation) while sortinglist and sortinglist[0][0] == 0: number, object = sortinglist[0] generation.append ( object ) deletelist.append( object ) for inverse in inversedependencies.get(object, () ): try: oldcount, inverse = dependencies [ inverse] if oldcount > 0: # will be dealt with on later pass dependencies [ inverse] = (oldcount-1, inverse) else: # will be dealt with on this pass, # so needs not to be in the sorting list next time deletelist.append( inverse ) # just in case a loop comes through inversedependencies[object] = [] except KeyError: # dealing with a recursion-breaking run... pass del sortinglist [0] # if no elements could be deleted, then # there is something which depends upon itself if not deletelist: if noRecursion: raise RecursionError( sortinglist ) else: # hack so that something gets deleted... ## import pdb ## pdb.set_trace() dependencies[sortinglist[0][1]] = (0,sortinglist[0][1]) # delete the items that were dealt with for item in deletelist: try: del dependencies [ item ] except KeyError: pass # need to recreate the sortinglist sortinglist = dependencies.values() if not generation: output.remove( generation ) sortinglist.sort () return output if __name__ == "__main__": import pprint, traceback nodes= [ 0,1,2,3,4,5 ] testingValues = [ [ (0,1),(1,2),(2,3),(3,4),(4,5)], [ (0,1),(0,2),(1,2),(3,4),(4,5)], [ (0,1), (0,2), (0,2), (2,4), (2,5), (3,2), (0,3)], [ (0,1), # 3-element cycle test, no orphan nodes (1,2), (2,0), (2,4), (2,5), (3,2), (0,3)], [ (0,1), (1,1), (1,1), (1,4), (1,5), (1,2), (3,1), (2,1), (2,0)], [ (0,1), (1,0), (0,2), (0,3), ], [ (0,1), (1,0), (0,2), (3,1), ], ] print 'sort, no recursion allowed' for index in range(len(testingValues)): ## print ' %s -- %s'%( index, testingValues[index]) try: print ' ', sort( nodes, testingValues[index] ) except: print 'exception raised' print 'toposort, no recursion allowed' for index in range(len(testingValues)): ## print ' %s -- %s'%( index, testingValues[index]) try: print ' ', toposort( nodes, testingValues[index] ) except: print 'exception raised' print 'sort, recursion allowed' for index in range(len(testingValues)): ## print ' %s -- %s'%( index, testingValues[index]) try: print ' ', sort( nodes, testingValues[index],0 ) except: print 'exception raised' print 'toposort, recursion allowed' for index in range(len(testingValues)): ## print ' %s -- %s'%( index, testingValues[index]) try: print ' ', toposort( nodes, testingValues[index],0 ) except: print 'exception raised' gaphor-0.17.2/gaphor/plugins/liveobjectbrowser/000077500000000000000000000000001220151210700215435ustar00rootroot00000000000000gaphor-0.17.2/gaphor/plugins/liveobjectbrowser/__init__.py000066400000000000000000000020351220151210700236540ustar00rootroot00000000000000""" Plugin based on the Live Object browser (http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/300304). It shows the state of the data model at the time the browser is activated. """ from zope import interface from gaphor.core import _, inject, action, build_action_group from gaphor.interfaces import IService, IActionProvider from browser import Browser class LiveObjectBrowser(object): interface.implements(IService, IActionProvider) element_factory = inject('element_factory') menu_xml = """ """ def __init__(self): self.action_group = build_action_group(self) def init(self, app): pass def shutdown(self): pass @action(name='tools-life-object-browser', label='Life object browser') def execute(self): browser = Browser("resource", self.element_factory.lselect()) # vim:sw=4:et gaphor-0.17.2/gaphor/plugins/liveobjectbrowser/browser.py000077500000000000000000000076701220151210700236150ustar00rootroot00000000000000#!/usr/bin/env python # vim:sw=4:et: """ Title: Live Object Browser Submitter: Simon Burton (other recipes) Last Updated: 2004/08/18 Version no: 1.0 Category: Debugging Description: Given an object, this tool throws up a gtk tree widget that maps all the references found. It dynamically builds the tree, which means it can handle large amounts of data and circular references. """ import gtk class Browser(object): def make_row( self, piter, name, value ): info = repr(value) if not hasattr(value, "__dict__"): if len(info) > 80: # it's a big list, or dict etc. info = info[:80] + "..." _piter = self.treestore.append( piter, [ name, type(value).__name__, info ] ) return _piter def make_instance( self, value, piter ): if hasattr( value, "__dict__" ): for _name, _value in value.__dict__.items(): _piter = self.make_row( piter, "."+_name, _value ) _path = self.treestore.get_path( _piter ) self.otank[ _path ] = (_name, _value) def make_mapping( self, value, piter ): keys = [] if hasattr( value, "keys" ): keys = value.keys() elif hasattr( value, "__len__"): keys = range( len(value) ) for key in keys: _name = "[%s]"%str(key) _piter = self.make_row( piter, _name, value[key] ) _path = self.treestore.get_path( _piter ) self.otank[ _path ] = (_name, value[key]) def make(self, name=None, value=None, path=None, depth=1): if path is None: # make root node piter = self.make_row( None, name, value ) path = self.treestore.get_path( piter ) self.otank[ path ] = (name, value) else: name, value = self.otank[ path ] piter = self.treestore.get_iter( path ) if not self.treestore.iter_has_child( piter ): self.make_mapping( value, piter ) self.make_instance( value, piter ) if depth: for i in range( self.treestore.iter_n_children( piter ) ): self.make( path = path+(i,), depth = depth - 1 ) def row_expanded( self, treeview, piter, path ): self.make( path = path ) def delete_event(self, widget, event, data=None): self.window.destroy() #gtk.main_quit() return False def __init__(self, name, value): self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.set_title("Browser") self.window.set_size_request(512, 320) self.window.connect("delete_event", self.delete_event) # we will store the name, the type name, and the repr columns = [str,str,str] self.treestore = gtk.TreeStore(*columns) # the otank tells us what object we put at each node in the tree self.otank = {} # map path -> (name,value) self.make( name, value ) self.treeview = gtk.TreeView(self.treestore) self.treeview.connect("row-expanded", self.row_expanded ) self.tvcolumns = [ gtk.TreeViewColumn() for _type in columns ] i = 0 for tvcolumn in self.tvcolumns: self.treeview.append_column(tvcolumn) cell = gtk.CellRendererText() tvcolumn.pack_start(cell, True) tvcolumn.add_attribute(cell, 'text', i) i = i + 1 scrolled_window = gtk.ScrolledWindow() scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) scrolled_window.set_shadow_type(gtk.SHADOW_ETCHED_IN) scrolled_window.add(self.treeview) self.window.add(scrolled_window) self.window.show_all() def dump( name, value ): browser = Browser( name, value ) gtk.main() def test(): class Nil: pass a = Nil() b = Nil() c = Nil() d = Nil() a.b=b b.c=c c.d=d d.a=a # circular chain dump( "a", a ) if __name__ == "__main__": test() gaphor-0.17.2/gaphor/plugins/pynsource/000077500000000000000000000000001220151210700200405ustar00rootroot00000000000000gaphor-0.17.2/gaphor/plugins/pynsource/Readme.txt000066400000000000000000000073231220151210700220030ustar00rootroot00000000000000PyNSource http://www.atug.com/andypatterns/pynsource.htm (c) 2003, 2004, 2005, 2006 Andy Bulka License: Free to use as long as acknowledgement is made in source code. abulka@netspace.net.au http://www.atug.com/andypatterns Version 1.4c - Fixed some parsing bugs. - Parsing now more correct under python 2.4 (python changed token.py !!) - Unit tests now all pass Version 1.4b - Added wxpython 2.5 compatibility (thanks Thomas Margraf!) Version 1.4a GUI changes: - Right Click on a node to delete it. - Run Layout anytime from menu. - Left click on background will deselect any selected shapes Version 1.4 - Fixed indentation error causing more output than normal in text ouput - Module level functions not treated as classes any more - Smarter detection of composition relationships, as long as classname and variable name are the same (ignoring case) then PyNSource will detect e.g. class Cat: pass class A: def __init__(self, cat): self.cats.append(Cat()) # always has worked, composition detected. self.cats.append(cat) # new 1.4 feature, composition detected here too. Version 1.3a A reverse engineering tool for Python source code - UML diagram models that you can layout, arrange and print out. - UML text diagrams, which you can paste into your source code for documentation purposes. - Java or Delphi code (which can be subsequently imported into more sophisticated UML modelling tools, like Enterprise Architect or ESS-Model (free).) Features - Resilient: doesn't import the python files, thus will never get "stuck" when syntax is wrong.  - Fast - Recognises inheritance and composition relationships - Detects the cardinality of associations e.g. one to one or 1..* etc - Optionally treat modules as classes - creating a pseudo class for each module - module variables and functions are treated as attributes and methods of a class - Has been developed using unit tests (supplied) so that you can trust it just that little bit more ;-) GUI FRONT END ------------- The PyNSource Gui is started in two ways: * By running the standalone pynsourceGui.exe via the shortcut created by the standalone installer. or * By running pynsourceGui.py ( you need wxpython installed. See http://www.wxpython.org ) e.g. \Python22\python.exe \Python22\Lib\site-packages\pynsource\pyNsourceGui.py The PyNSource command line tool is pynsource.py Command line Usage ------------------  pynsource -v -m [-j outdir] sourceDirOrListOfPythonFiles...    no options - generate Ascii UML -j generate java files, specify output folder for java files -d generate pascal files, specify output folder for pascal files -v verbose -m create psuedo class for each module, module attrs/defs etc treated as class attrs/defs Examples e.g. \python22\python.exe \Python22\Lib\site-packages\pynsource\pynsource.py -d c:\delphiouputdir c:\pythoninputdir\*.py The above line will scan all the files in c:\pythoninputdir and generate a bunch of delphi files in the folder c:\delphiouputdir BASIC ASCII UML OUTPUT from PYTHON - EXAMPLES e.g. pynsource Test/testmodule01.py e.g. pynsource -m Test/testmodule03.py GENERATE JAVA FILES from PYTHON - EXAMPLES e.g. pynsource -j c:/try c:/try e.g. pynsource -v -m -j c:/try c:/try e.g. pynsource -v -m -j c:/try c:/try/s*.py e.g. pynsource -j c:/try c:/try/s*.py Tests/u*.py e.g. pynsource -v -m -j c:/try c:/try/s*.py Tests/u*.py c:\cc\Devel\Client\w*.py GENERATE DELPHI FILES from PYTHON - EXAMPLE e.g. pynsource -d c:/delphiouputdir c:/pythoninputdir/*.py see http://www.atug.com/andypatterns/pynsource.htm for more documentation. Bugs to abulka@netspace.net.au gaphor-0.17.2/gaphor/plugins/pynsource/__init__.py000066400000000000000000000175311220151210700221600ustar00rootroot00000000000000""" Code reverse engineer plugin for Python source code. This plugin uses PyNSource, written by Andy Bulka [http://www.atug.com/andypatterns/pynsource.htm]. Depends on the Diagram Layout plugin. """ import gobject import gtk from zope import interface, component from gaphor.core import _, inject, action, build_action_group from gaphor.interfaces import IService, IActionProvider from engineer import Engineer NAME_COLUMN = 0 class PyNSource(object): interface.implements(IService, IActionProvider) main_window = inject('main_window') menu_xml = """ """ def __init__(self): self.win = None self.action_group = build_action_group(self) def init(self, app): pass def shutdown(self): pass @action(name='file-import-pynsource', label='Python source code', tooltip='Import Python source code') def execute(self): dialog = self.create_dialog() response = dialog.run() if response != gtk.RESPONSE_OK: dialog.destroy() self.reset() return files = [] for row in self.filelist: files.append(row[0]) dialog.destroy() self.process(files) self.reset() def process(self, files): """Create a diagram based on a list of files. """ engineer = Engineer() engineer.process(files) main_window = self.main_window # Open and select the new diagram in the main window: main_window.select_element(engineer.diagram) main_window.show_diagram(engineer.diagram) def create_dialog(self): dialog = gtk.Dialog("Import Python files", self.main_window.window, gtk.DIALOG_MODAL, (gtk.STOCK_OK, gtk.RESPONSE_OK, gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)) dialog.set_default_response(gtk.RESPONSE_OK) filelist = gtk.ListStore(gobject.TYPE_STRING) filelist.connect('row-inserted', self.on_view_rows_changed) filelist.connect('row-deleted', self.on_view_rows_changed) hbox = gtk.HBox() frame = gtk.Frame('Files to reverse-engineer') frame.set_border_width(8) frame.set_size_request(500, 300) frame.show() hbox.pack_start(frame, expand=True) treeview = gtk.TreeView(filelist) treeview.set_property('headers-visible', False) selection = treeview.get_selection() selection.set_mode('single') treeview.set_size_request(200, -1) treeview.connect_after('cursor_changed', self.on_view_cursor_changed) scrolled_window = gtk.ScrolledWindow() scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) scrolled_window.set_shadow_type(gtk.SHADOW_IN) scrolled_window.set_border_width(4) scrolled_window.add(treeview) frame.add(scrolled_window) scrolled_window.show() cell = gtk.CellRendererText() column = gtk.TreeViewColumn("Filename", cell, text=NAME_COLUMN) treeview.append_column(column) bbox = gtk.VButtonBox() bbox.set_layout(gtk.BUTTONBOX_SPREAD) bbox.set_border_width(10) button = gtk.Button(stock='gtk-add') button.connect('clicked', self.on_add_dir_clicked) bbox.add(button) self.add_button = button #button = gtk.Button('Add dir...') #button.connect('clicked', self.on_add_dir_clicked) #bbox.add(button) #self.add_dir_button = button button = gtk.Button(stock='gtk-remove') button.connect('clicked', self.on_remove_clicked) button.set_property('sensitive', False) bbox.add(button) self.remove_button = button #button = gtk.Button(stock='gtk-execute') #button.connect('clicked', self.on_execute_clicked) #button.set_property('sensitive', False) #bbox.add(button) #self.execute_button = button hbox.pack_start(bbox, expand=False) hbox.show_all() dialog.vbox.pack_start(hbox, True, True, 0) self.filelist = filelist self.treeview = treeview return dialog def reset(self): self.add_button = None self.add_dir_button = None self.remove_button = None self.treeview = None self.filelist = None def Walk(self, root, recurse=0, pattern='*', return_folders=0): import fnmatch import os import string # initialize result = [] # must have at least root folder try: names = os.listdir(root) except os.error: return result # expand pattern pattern = pattern or '*' pat_list = string.splitfields(pattern, ';') # check each file for name in names: fullname = os.path.normpath(os.path.join(root, name)) # grab if it matches our pattern and entry type for pat in pat_list: if fnmatch.fnmatch(name, pat): if os.path.isfile(fullname) or ( return_folders and os.path.isdir(fullname)): result.append(fullname) continue # recursively scan other folders, appending results if recurse: if os.path.isdir(fullname) and not os.path.islink(fullname): result = result + self.Walk( fullname, recurse, pattern, return_folders) return result def on_view_cursor_changed(self, view): selection = view.get_selection() filelist, iter = selection.get_selected() if not iter: self.remove_button.set_property('sensitive', False) return #element = filelist.get_value(iter, NAME_COLUMN) self.remove_button.set_property('sensitive', True) #self.update_detail(element) def on_view_rows_changed(self, view, *args): iter = None try: iter = view.get_iter('0') except ValueError: pass #self.execute_button.set_property('sensitive', bool(iter)) def on_add_dir_clicked(self, button): import os filesel = gtk.FileChooserDialog(title='Add Source Code', action=gtk.FILE_CHOOSER_ACTION_OPEN, buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, gtk.RESPONSE_OK)) filesel.set_select_multiple(True) filesel.set_filename('~/') response = filesel.run() selection = filesel.get_filenames() filesel.destroy() if response == gtk.RESPONSE_OK: for filename in selection: if os.path.isdir(filename): list = self.Walk(filename, 1, '*.py', 1) for file in list: iter = self.filelist.append() self.filelist.set_value(iter, NAME_COLUMN, file) else: list = filename iter = self.filelist.append() self.filelist.set_value(iter, NAME_COLUMN, list) def on_remove_clicked(self, button): selection = self.treeview.get_selection() filelist, iter = selection.get_selected() if not iter: return element = filelist.remove(iter) self.remove_button.set_property('sensitive', False) # vim:sw=4:et:ai gaphor-0.17.2/gaphor/plugins/pynsource/engineer.py000066400000000000000000000226641220151210700222200ustar00rootroot00000000000000# vim:sw=4:et """The code reverse engineer. """ from zope import component from gaphor import UML from gaphor.diagram import items from gaphor.core import inject from gaphor.diagram.interfaces import IConnect from gaphas.aspect import ConnectionSink, Connector from pynsource import PySourceAsText BASE_CLASSES = ('object', 'type', 'dict', 'list', 'tuple', 'int', 'float') class Engineer(object): """ The Engineer class will create a Gaphor model based on a list of Python files. """ element_factory = inject('element_factory') diagram_layout = inject('diagram_layout') def process(self, files=None): # these are tuples between class names. #self.associations_generalisation = [] #self.associations_composition = [] p = PySourceAsText() self.parser = p if files: #u = PythonToJava(None, treatmoduleasclass=0, verbose=0) for f in files: # Build a shape with all attrs and methods, and prepare association dict p.Parse(f) print p try: self._root_package = self.element_factory.lselect(lambda e: isinstance(e, UML.Package) and not e.namespace)[0] except IndexError: pass # running as test? for m in p.modulemethods: print 'ModuleMethod:', m # Step 0: create a diagram to put the newly created elements on self.diagram = self.element_factory.create(UML.Diagram) self.diagram.name = 'New classes' self.diagram.package = self._root_package # Step 1: create the classes for name, clazz in p.classlist.items(): print type(clazz), dir(clazz) self._create_class(clazz, name) # Create generalization relationships: for name, clazz in p.classlist.items(): self._create_generalization(clazz) # Create attributes (and associations) on the classes for name, clazz in p.classlist.items(): self._create_attributes(clazz) # Create operations for name, clazz in p.classlist.items(): self._create_methods(clazz) self.diagram_layout.layout_diagram(self.diagram) def _create_class(self, clazz, name): c = self.element_factory.create(UML.Class) c.name = name c.package = self.diagram.namespace ci = self.diagram.create(items.ClassItem) ci.subject = c clazz.gaphor_class = c clazz.gaphor_class_item = ci def _create_generalization(self, clazz): if not clazz.ismodulenotrealclass: for superclassname in clazz.classesinheritsfrom: if superclassname in BASE_CLASSES: continue try: superclass = self.parser.classlist[superclassname].gaphor_class superclass_item = self.parser.classlist[superclassname].gaphor_class_item except KeyError, e: print 'No class found named', superclassname others = self.element_factory.lselect(lambda e: isinstance(e, UML.Class) and e.name == superclassname) if others: superclass = others[0] print 'Found class in factory: %s' % superclass.name superclass_item = self.diagram.create(items.ClassItem) superclass_item.subject = superclass else: continue # Finally, create the generalization relationship print 'Creating Generalization for %s' % clazz, superclass #gen = self.element_factory.create(UML.Generalization) #gen.general = superclass #gen.specific = clazz.gaphor_class geni = self.diagram.create(items.GeneralizationItem) #geni.subject = gen self.connect(geni, geni.tail, clazz.gaphor_class_item) self.connect(geni, geni.head, superclass_item) #adapter = component.queryMultiAdapter((superclass_item, geni), IConnect) #assert adapter #handle = geni.handles()[0] #adapter.connect(handle) #clazz.gaphor_class_item.connect_handle(geni.handles[-1]) #adapter = component.queryMultiAdapter((clazz.gaphor_class_item, geni), IConnect) #assert adapter #handle = geni.handles()[-1] #adapter.connect(handle) def connect(self, line, handle, item, port=None): """ Connect line's handle to an item. If port is not provided, then first port is used. """ canvas = line.canvas if port is None and len(item.ports()) > 0: port = item.ports()[0] sink = ConnectionSink(item, port) connector = Connector(line, handle) connector.connect(sink) def _create_attributes(self, clazz): for attrobj in clazz.attrs: # TODO: Check object type and figure out if it should be an # attribute or an association. self._create_attribute(clazz, attrobj) def _create_methods(self, clazz): for adef in clazz.defs: op = self.element_factory.create(UML.Operation) op.name = adef clazz.gaphor_class.ownedOperation = op def _find_class_by_name(self, classname): try: superclass = self.parser.classlist[classname].gaphor_class superclass_item = self.parser.classlist[classname].gaphor_class_item except KeyError, e: print 'No class found named', classname others = self.element_factory.lselect(lambda e: isinstance(e, UML.Class) and e.name == classname) if others: superclass = others[0] print 'Found class in factory: %s' % superclass.name superclass_item = self.diagram.create(items.ClassItem) superclass_item.subject = superclass else: return None, None return superclass, superclass_item def _visibility(self, attrname): if attrname.startswith('__'): return 'private' elif attrname.startswith('_'): return 'protected' return 'public' def _create_attribute(self, clazz, attr): static = False many = False if 'static' in attr.attrtype: static = True if 'many' in attr.attrtype: many = True compositescreated = self.parser.GetCompositeClassesForAttr(attr.attrname, clazz) tail_type = None if compositescreated: tail_type, tail_type_item = self._find_class_by_name(compositescreated[0]) if tail_type: # Create an association: #print "%s %s <@>----> %s" % (attr.attrname, static, str(compositescreated)) # The property on the tail of the association (tail_end) is owned # by the class connected on the head_end (head_type) head_type = clazz.gaphor_class head_type_item = clazz.gaphor_class_item #relation = self.element_factory.create(UML.Association) #head_end = self.element_factory.create(UML.Property) #head_end.lowerValue = self.element_factory.create(UML.LiteralSpecification) #tail_end = self.element_factory.create(UML.Property) #tail_end.name = attr.attrname #tail_end.visibility = self._visibility(attr.attrname) #tail_end.aggregation = 'composite' #tail_end.lowerValue = self.element_factory.create(UML.LiteralSpecification) #relation.package = self.diagram.namespace #relation.memberEnd = head_end #relation.memberEnd = tail_end #head_end.type = head_type #tail_end.type = tail_type #head_type.ownedAttribute = tail_end #tail_type.ownedAttribute = head_end # Now the subject #association.subject = relation #association.head_end.subject = head_end #association.tail_end.subject = tail_end # Create the diagram item: association = self.diagram.create(items.AssociationItem) adapter = component.queryMultiAdapter((head_type_item, association), IConnect) assert adapter handle = association.handles()[0] adapter.connect(handle) adapter = component.queryMultiAdapter((tail_type_item, association), IConnect) assert adapter handle = association.handles()[-1] adapter.connect(handle) # Apply attribute information to the association (ends) association.head_end.navigability = False tail_prop = association.tail_end.subject tail_prop.name = attr.attrname tail_prop.visibility = self._visibility(attr.attrname) tail_prop.aggregation = 'composite' else: # Create a simple attribute: #print "%s %s" % (attr.attrname, static) prop = self.element_factory.create(UML.Property) prop.name = attr.attrname prop.visibility = self._visibility(attr.attrname) prop.isStatic = static clazz.gaphor_class.ownedAttribute = prop #print many import pprint pprint.pprint(attr) #print dir(attr) gaphor-0.17.2/gaphor/plugins/pynsource/keywords.py000066400000000000000000000130021220151210700222550ustar00rootroot00000000000000""" Definitions of python, java and delphi keywords so that pynsource can skip these and not treat them like you are creating an instance of a locally defined class. """ pythonbuiltinfunctions_txt = """ ArithmeticError AssertionError AttributeError DeprecationWarning EOFError EnvironmentError Exception FloatingPointError FutureWarning IOError ImportError IndentationError IndexError KeyError KeyboardInterrupt LookupError MemoryError NameError NotImplementedError OSError OverflowError OverflowWarning PendingDeprecationWarning ReferenceError RuntimeError RuntimeWarning StandardError StopIteration SyntaxError SyntaxWarning SystemError SystemExit TabError TypeError UnboundLocalError UnicodeError UserWarning ValueError Warning WindowsError ZeroDivisionError Ellipsis False None NotImplemented True UnicodeDecodeError UnicodeEncodeError UnicodeTranslateError __debug__ __import__ abs apply basestring bool buffer callable chr classmethod cmp coerce compile complex copyright credits delattr dict dir divmod enumerate eval execfile exit file filter float getattr globals hasattr hash help hex id input int intern isinstance issubclass iter len license list locals long map max min object oct open ord pow property quit range raw_input reduce reload repr round setattr slice staticmethod str sum super tuple type unichr unicode vars xrange zip __base__ __bases__ __basicsize__ __class__ __dict__ __dictoffset__ __doc__ __flags__ __itemsize__ __module__ __mro__ __name__ __self__ __weakrefoffset__ __abs__ __add__ __and__ __call__ __cmp__ __coerce__ __complex__ __contains__ __del__ __delattr__ __delitem__ __delslice__ __div__ __divmod__ __eq__ __float__ __floordiv__ __ge__ __get__ __getattribute__ __getitem__ __getnewargs__ __getslice__ __gt__ __hash__ __hex__ __iadd__ __iand__ __idiv__ __ifloordiv__ __ilshift__ __imod__ __imul__ __init__ __int__ __invert__ __ior__ __ipow__ __irshift__ __isub__ __iter__ __itruediv__ __ixor__ __le__ __len__ __long__ __lshift__ __lt__ __mod__ __mul__ __ne__ __neg__ __new__ __nonzero__ __oct__ __or__ __pos__ __pow__ __radd__ __rand__ __rdiv__ __rdivmod__ __reduce__ __reduce_ex__ __repr__ __rfloordiv__ __rlshift__ __rmod__ __rmul__ __ror__ __rpow__ __rrshift__ __rshift__ __rsub__ __rtruediv__ __rxor__ __setattr__ __setitem__ __setslice__ __str__ __sub__ __subclasses__ __truediv__ __xor__ append capitalize center clear close conjugate copy count decode encode endswith expandtabs extend fileno find flush fromkeys get has_key index indices insert isalnum isalpha isatty isdecimal isdigit islower isnumeric isspace istitle isupper items iteritems iterkeys itervalues join keys ljust lower lstrip mro next pop popitem read readinto readline readlines remove replace reverse rfind rindex rjust rstrip seek setdefault sort split splitlines startswith strip swapcase tell title translate truncate update upper values write writelines xreadlines zfill closed co_argcount co_cellvars co_code co_consts co_filename co_firstlineno co_flags co_freevars co_lnotab co_name co_names co_nlocals co_stacksize co_varnames f_back f_builtins f_code f_exc_traceback f_exc_type f_exc_value f_globals f_lasti f_lineno f_locals f_restricted f_trace func_closure func_code func_defaults func_dict func_doc func_globals func_name gi_frame gi_running im_class im_func im_self imag mode name newlines real softspace start step stop BooleanType BufferType BuiltinFunctionType BuiltinMethodType ClassType CodeType ComplexType DictProxyType DictType DictionaryType EllipsisType FileType FloatType FrameType FunctionType GeneratorType InstanceType IntType LambdaType ListType LongType MethodType ModuleType NoneType NotImplementedType ObjectType SliceType StringType StringTypes TracebackType TupleType TypeType UnboundMethodType UnicodeType XRangeType __builtins__ __file__ """ pythonbuiltinfunctions = pythonbuiltinfunctions_txt.split() javakeywords_txt = """ abstract boolean break byte case catch char class continue default delegate do double else extends false final finally float for if implements import instanceof int interface long native new null package private protected public return short static super switch synchronized this throw throws transient true try void volatile while goto const strictfp """ javakeywords = javakeywords_txt.split() delphikeywords_txt = """ And Array As Begin Case Class Const Constructor Destructor Div Do DownTo Else End Except File Finally For System Goto If Implementation In Inherited Interface System Is Mod Not System Of On Or Packed System System System Raise Record Repeat Set Shl Shr Then ThreadVar To Try Type Unit Until Uses Var While With Xor """ delphikeywords = delphikeywords_txt.split() delphikeywords = [ x.lower() for x in delphikeywords ] # delphi is case insensitive, so convert everything to lowercase for comparisons # See Token.py in \python2x\Lib TOKEN_MEANINGS_FORDOCO_ONLY = """ AMPER = 19 AMPEREQUAL = 42 AT = 50 BACKQUOTE = 25 CIRCUMFLEX = 33 CIRCUMFLEXEQUAL = 44 COLON = 11 COMMA = 12 COMMENT = 53 DEDENT = 6 DOT = 23 DOUBLESLASH = 48 DOUBLESLASHEQUAL = 49 DOUBLESTAR = 36 DOUBLESTAREQUAL = 47 ENDMARKER = 0 EQEQUAL = 28 EQUAL = 22 ERRORTOKEN = 52 GREATER = 21 GREATEREQUAL = 31 INDENT = 5 LBRACE = 26 LEFTSHIFT = 34 LEFTSHIFTEQUAL = 45 LESS = 20 LESSEQUAL = 30 LPAR = 7 LSQB = 9 MINEQUAL = 38 MINUS = 15 NAME = 1 NEWLINE = 4 NL = 54 NOTEQUAL = 29 NT_OFFSET = 256 NUMBER = 2 N_TOKENS = 55 OP = 51 PERCENT = 24 PERCENTEQUAL = 41 PLUS = 14 PLUSEQUAL = 37 RBRACE = 27 RIGHTSHIFT = 35 RIGHTSHIFTEQUAL = 46 RPAR = 8 RSQB = 10 SEMI = 13 SLASH = 17 SLASHEQUAL = 40 STAR = 16 STAREQUAL = 39 STRING = 3 TILDE = 32 VBAR = 18 VBAREQUAL = 43 """ gaphor-0.17.2/gaphor/plugins/pynsource/pynsource.py000066400000000000000000001212761220151210700224520ustar00rootroot00000000000000""" PyNSource Version 1.4c (c) Andy Bulka 2004-2006 abulka@netspace.net.au http://www.atug.com/andypatterns/pynsource.htm A python source code scanner that generates - UML pictures (as text) - Java code (which can be imported into UML modelling tools.) - UML diagrams in wxpython (see associated module pyNsourceGui.py) GUI FRONT END ------------- Simply run C:\Python22\Lib\site-packages\pynsource\pyNsourceGui.py you need wxpython installed. See http://www.wxpython.org SOURCE GENERATOR ---------------- Example Usage: C:\Python22\Lib\site-packages\pynsource\pynsource -v -m -j outdir sourcedirorpythonfiles... -j generate java files, specify output folder for java files -v verbose -m create psuedo class for each module, module attrs/defs etc treated as class attrs/defs BASIC EXAMPLES e.g. pynsource Test/testmodule01.py e.g. pynsource -m Test/testmodule03.py JAVA EXAMPLES e.g. pynsource -j c:/try c:/try e.g. pynsource -v -m -j c:/try c:/try e.g. pynsource -v -m -j c:/try c:/try/s*.py e.g. pynsource -j c:/try c:/try/s*.py Tests/u*.py e.g. pynsource -v -m -j c:/try c:/try/s*.py Tests/u*.py c:\cc\Devel\Client\w*.py DELPHI EXAMPLE e.g. pynsource -d c:/delphiouputdir c:/pythoninputdir/*.py INSTALLATION ------------- python setup.py install or run the windows .exe installer. JBUILDER TIPS ------------- Consider some folder e.g. .../Tests/ and create a jbuilder project based off there called PythonToJavaTest01 which will actually create a folder called PythonToJavaTest01 plus subfolders called src and classes etc. The Borland project file will also live in .../Tests/PythonToJavaTest01/ with a name PythonToJavaTest01.jpx Run pynsource so that it dumps the output into the src folder e.g. assuming the batch file is in the PythonToJavaTest01 folder and the python source is in .../Tests/PythonToJavaTest01/pythoninput01 then the command is pynsource -j src pythoninput01\*.py """ #import pychecker.checker import tokenize, token import pprint import os from keywords import pythonbuiltinfunctions, javakeywords, delphikeywords DEBUG_DUMPTOKENS = False class AndyBasicParseEngine(object): def __init__(self): self.meat = 0 self.tokens = None self.isfreshline = 1 self.indentlevel = 0 def _ReadAllTokensFromFile(self, file): fp = open(file, 'r') try: self.tokens = [ x[0:2] for x in tokenize.generate_tokens(fp.readline) ] finally: fp.close() if DEBUG_DUMPTOKENS: pprint.pprint( self.tokens ) def Parse(self, file): self._ReadAllTokensFromFile(file) self.meat = 0 self._ParseLoop() def _ParseLoop(self): maxtokens = len(self.tokens) for i in range(0, maxtokens): tokentype, token = self.tokens[i] if tokentype == 5: self.indentlevel += 1 continue elif tokentype == 6: self.indentlevel -= 1 self.On_deindent() continue if tokentype == 0: # End Marker. break assert token, ("Not expecting blank token, once have detected in & out dents. tokentype=%d, token=%s" %(tokentype, token)) self.tokentype, self.token = tokentype, token if i+1 < maxtokens: self.nexttokentype, self.nexttoken = self.tokens[i+1] else: self.nexttokentype, self.nexttoken = (0,None) if self._Isblank(): continue else: #print 'MEAT', self.token self._Gotmeat() def On_deindent(self): pass def On_newline(self): pass def On_meat(self): pass def _Gotmeat(self): self.meat = 1 self.On_meat() self.isfreshline = 0 # must be here, at the end. def _Isblank(self): if self._Isnewline(): return 1 if self._Ispadding(): return 1 return 0 def _Isnewline(self): if (self.token == '\n' or self.tokentype == token.N_TOKENS): if self.tokentype == token.N_TOKENS: assert '#' in self.token self.meat = 0 self.isfreshline = 1 self.On_newline() return 1 else: return 0 def _Ispadding(self): if not self.token.strip(): self.meat = 0 return 1 else: return 0 class ClassEntry: def __init__(self): self.defs = [] self.attrs = [] self.classdependencytuples = [] self.classesinheritsfrom = [] self.ismodulenotrealclass = 0 def FindAttribute(self, attrname): """ Return boolean hit, index pos """ for attrobj in self.attrs: if attrname == attrobj.attrname: return 1, attrobj return 0, None def AddAttribute(self, attrname, attrtype): """ If the new info is different to the old, and there is more info in it, then replace the old entry. e.g. oldattrtype may be ['normal' and new may be ['normal', 'many'] """ haveEncounteredAttrBefore, attrobj = self.FindAttribute(attrname) if not haveEncounteredAttrBefore: self.attrs.append(Attribute(attrname, attrtype)) else: # See if there is more info to add re this attr. if len(attrobj.attrtype) < len(attrtype): attrobj.attrtype = attrtype # Update it. # OLD CODE #if not self.FindAttribute(attrname): # self.attrs.append(Attribute(attrname, attrtype)) class Attribute: def __init__(self, attrname, attrtype='normal'): self.attrname = attrname self.attrtype = attrtype class HandleClasses(AndyBasicParseEngine): def __init__(self): AndyBasicParseEngine.__init__(self) self.currclasslist = [] self._currclass = None self.nexttokenisclass = 0 self.classlist = {} self.modulemethods = [] self.optionModuleAsClass = 0 self.inbetweenClassAndFirstDef = 0 def On_deindent(self): if self.indentlevel <= self.currclassindentlevel: ## print 'popping class', self.currclass, 'from', self.currclasslist self.PopCurrClass() ## print ## print 'deindent!!', self.indentlevel, 'class indentlevel =', self.currclassindentlevel def _DeriveNestedClassName(self, currclass): if not self.currclasslist: return currclass else: classname, indentlevel = self.currclasslist[-1] return classname + '_' + currclass # Cannot use :: since java doesn't like this name, nor does the file system. def PushCurrClass(self, currclass): #print 'pushing currclass', currclass, 'self.currclasslist', self.currclasslist currclass = self._DeriveNestedClassName(currclass) self.currclasslist.append( (currclass, self.indentlevel) ) #print 'result of pushing = ', self.currclasslist def PopCurrClass(self): #__import__("traceback").print_stack(limit=6) self.currclasslist.pop() def GetCurrClassIndentLevel(self): if not self.currclasslist: return None currclassandindentlevel = self.currclasslist[-1] return currclassandindentlevel[1] def GetCurrClass(self): if not self.currclasslist: return None currclassandindentlevel = self.currclasslist[-1] return currclassandindentlevel[0] currclass = property(GetCurrClass) currclassindentlevel = property(GetCurrClassIndentLevel) def _JustThenGotclass(self): self.PushCurrClass(self.token) self.nexttokenisclass = 0 if self.currclass not in self.classlist: self.classlist[self.currclass] = ClassEntry() #print 'class', self.currclass self.inbetweenClassAndFirstDef = 1 def On_newline(self): pass def On_meat(self): if self.token == 'class': ## print 'meat found class', self.token self.nexttokenisclass = 1 elif self.nexttokenisclass: ## print 'meat found class name ', self.token self._JustThenGotclass() class HandleInheritedClasses(HandleClasses): def __init__(self): HandleClasses.__init__(self) self._ClearwaitingInheriteClasses() def _JustThenGotclass(self): HandleClasses._JustThenGotclass(self) self.currsuperclass = '' self.nexttokenisBracketOpenOrColon = 1 def _ClearwaitingInheriteClasses(self): self.nexttokenisBracketOpenOrColon = 0 self.nexttokenisSuperclass = 0 self.nexttokenisComma = 0 def On_newline(self): self._ClearwaitingInheriteClasses() def On_meat(self): HandleClasses.On_meat(self) if self.nexttokenisBracketOpenOrColon and self.token == '(': assert self.tokentype == token.OP # unecessary, just practicing refering to tokens via names not numbers self.nexttokenisBracketOpen = 0 self.nexttokenisSuperclass = 1 elif self.nexttokenisBracketOpenOrColon and self.token == ':': self._ClearwaitingInheriteClasses() elif self.nexttokenisSuperclass and self.token == ')': self._ClearwaitingInheriteClasses() elif self.nexttokenisSuperclass: self.currsuperclass += self.token if self.token == '.' or self.nexttoken == '.': #print 'processing multi part superclass detected!', self.token, self.nexttoken self.nexttokenisSuperclass = 1 else: self.nexttokenisSuperclass = 0 self.nexttokenisComma = 1 self.classlist[self.currclass].classesinheritsfrom.append(self.currsuperclass) elif self.nexttokenisComma and self.token == ',': self.nexttokenisSuperclass = 1 self.nexttokenisComma = 0 class HandleDefs(HandleInheritedClasses): def __init__(self): HandleInheritedClasses.__init__(self) self.currdef = None self.nexttokenisdef = 0 def _Gotdef(self): self.currdef = self.token self.nexttokenisdef = 0 #print 'ADDING def', self.currdef, 'to', self.currclass ## if self.currclass and self.indentlevel == 1: if self.currclass: self.classlist[self.currclass].defs.append(self.currdef) elif self.optionModuleAsClass and self.indentlevel == 0: assert self.moduleasclass assert self.classlist[self.moduleasclass] self.classlist[self.moduleasclass].defs.append(self.currdef) else: self.modulemethods.append(self.currdef) self.inbetweenClassAndFirstDef = 0 def On_meat(self): HandleInheritedClasses.On_meat(self) ## if self.token == 'def' and self.indentlevel == 1: if self.token == 'def': ## print 'DEF FOUND AT LEVEL', self.indentlevel self.nexttokenisdef = 1 elif self.nexttokenisdef: self._Gotdef() ## self.meat = 1 class HandleClassAttributes(HandleDefs): def __init__(self): HandleDefs.__init__(self) self.attrslist = [] self._Clearwaiting() def On_newline(self): HandleInheritedClasses.On_newline(self) self._Clearwaiting() def _Clearwaiting(self): self.waitingfordot = 0 self.waitingforsubsequentdot = 0 self.waitingforvarname = 0 self.waitingforequalsymbol = 0 self.currvarname = None self.lastcurrvarname = None self.waitforappendopenbracket = 0 self.nextvarnameisstatic = 0 self.nextvarnameismany = 0 def JustGotASelfAttr(self, selfattrname): pass def On_meat(self): HandleDefs.On_meat(self) if self.isfreshline and self.token == 'self' and self.nexttoken == '.': self.waitingfordot = 1 elif self.waitingfordot and self.token == '.': self.waitingfordot = 0 self.waitingforvarname = 1 elif self.waitingforvarname: # We now have the possible class attribute name. :-) self.waitingforvarname = 0 self.currvarname = self.token """ At this point we have the x in the expression self.x A. We could find self.x = in which case we have a valid class attribute. B. We could find self.x.append( in which case we have a valid class attribute list/vector. C. We could find self.__class__.x = in which case we have a valid STATIC class attribute. D. We could find self.x.y = in which case we skip. E. We could find self.x.y.append( in which case we skip. F. We could find self.x.y.Blah( in which case we skip. G. We could find self.numberOfFlags = read16(fp) - skip cos read16 is a module function. """ if self.nexttoken == '=': self.waitingforequalsymbol = 1 # Case A elif self.nexttoken == '.': self.waitingforsubsequentdot = 1 # Cases B,C, D,E,F pending elif self.waitingforsubsequentdot and self.token == '.': self.waitingfordot = 0 self.waitingforsubsequentdot = 0 self.waitingforequalsymbol = 0 if self.nexttoken.lower() in ('append','add','insert'): # Case B # keep the class attribute name we have, wait till bracket self.waitforappendopenbracket = 1 elif self.currvarname in ('__class__',): # Case C self.currvarname = None self.waitingforvarname = 1 self.nextvarnameisstatic = 1 else: # Skip cases D, E, F self._Clearwaiting() elif self.waitforappendopenbracket and self.token == '(': self.waitforappendopenbracket = 0 self.nextvarnameismany = 1 self._AddAttribute() self._Clearwaiting() elif self.waitingforequalsymbol and self.token == '=': self.waitingforequalsymbol = 0 self._AddAttribute() self._Clearwaiting() def _AddAttribute(self): classentry = self.classlist[self.currclass] if self.nextvarnameisstatic: attrtype = ['static'] else: attrtype = ['normal'] if self.nextvarnameismany: attrtype.append('many') classentry.AddAttribute(self.currvarname, attrtype) #print ' ATTR ', self.currvarname self.JustGotASelfAttr(self.currvarname) class HandleComposites(HandleClassAttributes): def __init__(self): HandleClassAttributes.__init__(self) self._ClearwaitingOnComposites() self.dummy = ClassEntry() self.dummy2 = [()] def JustGotASelfAttr(self, selfattrname): assert selfattrname <> 'self' self.lastselfattrname = selfattrname self.waitingforclassname = 1 self.waitingforOpenBracket = 0 self.possibleclassname = None self.dontdoanythingnow = 1 def _ClearwaitingOnComposites(self): self.lastselfattrname = None self.waitingforclassname = 0 self.possibleclassname = None self.waitingforOpenBracket = 0 self.dontdoanythingnow = 0 def On_newline(self): HandleClassAttributes.On_newline(self) self._ClearwaitingOnComposites() def On_meat(self): self.dontdoanythingnow = 0 HandleClassAttributes.On_meat(self) # At this point we may have had a "self.blah = " encountered, and blah is saved in self.lastselfattrname if self.dontdoanythingnow: pass elif self.waitingforclassname and self.token not in ( '(', '[' ) and \ self.token not in pythonbuiltinfunctions and\ self.tokentype not in (token.NUMBER, token.STRING) and\ self.token not in self.modulemethods: self.possibleclassname = self.token self.waitingforclassname = 0 self.waitingforOpenBracket = 1 elif self.waitingforOpenBracket and self.token == '(': self.waitingforclassname = 0 self.waitingforOpenBracket = 0 dependency = (self.lastselfattrname, self.possibleclassname) self.classlist[self.currclass].classdependencytuples.append(dependency) #print '*** dependency - created instance of', self.possibleclassname, 'assigned to', self.lastselfattrname elif self.waitingforOpenBracket and self.token == ')': """ New - we haven't got a class being created but instead have a variable. Note that the above code detects self.flag.append(Flag()) # notice instance creation inside append but the following code detects self.flag.append(flag) # and assumes flag variable is an instance of Flag class """ # we don't have class being created but have a variable name instead variablename = self.possibleclassname # try to find a class with the same name. correspondingClassName = variablename[0].upper() + variablename[1:] # HACK #print 'correspondingClassName', correspondingClassName dependency = (self.lastselfattrname, correspondingClassName) self.classlist[self.currclass].classdependencytuples.append(dependency) else: self._ClearwaitingOnComposites() class HandleClassStaticAttrs(HandleComposites): def __init__(self): HandleComposites.__init__(self) self.__Clearwaiting() def __Clearwaiting(self): self.__waitingforequalsymbol = 0 self.__staticattrname = '' def On_meat(self): HandleComposites.On_meat(self) if self.isfreshline and self.currclass and self.inbetweenClassAndFirstDef and self.tokentype == 1 and self.indentlevel != 0 and self.nexttoken == '=': self.__waitingforequalsymbol = 1 self.__staticattrname = self.token elif self.__waitingforequalsymbol and self.token == '=': self.__waitingforequalsymbol = 0 #print 'have static level attr', self.__staticattrname self.__AddAttrModuleLevel() self.__Clearwaiting() def __AddAttrModuleLevel(self): # Should re-use the logic in HandleClassAttributes for both parsing # (getting more info on multiplicity but not static - cos static not relevant?) and # also should be able to resuse most of _AddAttr() # classentry = self.classlist[self.currclass] attrtype = ['static'] classentry.AddAttribute(self.__staticattrname, attrtype) #print ' STATIC ATTR ', self.__staticattrname class HandleModuleLevelDefsAndAttrs(HandleClassStaticAttrs): def __init__(self): HandleClassStaticAttrs.__init__(self) self.moduleasclass = '' self.__Clearwaiting() def __Clearwaiting(self): self.waitingforequalsymbolformoduleattr = 0 self.modulelevelattrname = '' def Parse(self, file): self.moduleasclass = 'Module_'+os.path.splitext(os.path.basename(file))[0] if self.optionModuleAsClass: self.classlist[self.moduleasclass] = ClassEntry() self.classlist[self.moduleasclass].ismodulenotrealclass = 1 HandleComposites.Parse(self, file) def On_meat(self): HandleClassStaticAttrs.On_meat(self) if self.isfreshline and self.tokentype == 1 and self.indentlevel == 0 and self.nexttoken == '=': self.waitingforequalsymbolformoduleattr = 1 self.modulelevelattrname = self.token elif self.waitingforequalsymbolformoduleattr and self.token == '=': self.waitingforequalsymbolformoduleattr = 0 #print 'have module level attr', self.modulelevelattrname self._AddAttrModuleLevel() self.__Clearwaiting() def On_newline(self): HandleClassStaticAttrs.On_newline(self) self.__Clearwaiting() def _AddAttrModuleLevel(self): if not self.optionModuleAsClass: return # Should re-use the logic in HandleClassAttributes for both parsing # (getting more info on multiplicity but not static - cos static not relevant?) and # also should be able to resuse most of _AddAttr() # classentry = self.classlist[self.moduleasclass] attrtype = ['normal'] ## if self.nextvarnameisstatic: ## attrtype = ['static'] ## else: ## attrtype = ['normal'] ## ## if self.nextvarnameismany: ## attrtype.append('many') classentry.AddAttribute(self.modulelevelattrname, attrtype) #print ' ATTR ', self.currvarname #self.JustGotASelfAttr(self.currvarname) class PySourceAsText(HandleModuleLevelDefsAndAttrs): def __init__(self): HandleModuleLevelDefsAndAttrs.__init__(self) self.listcompositesatend = 0 self.embedcompositeswithattributelist = 1 self.result = '' self.aclass = None self.classentry = None self.staticmessage = "" self.manymessage = "" self.verbose = 0 def GetCompositeClassesForAttr(self, classname, classentry): resultlist = [] for dependencytuple in classentry.classdependencytuples: if dependencytuple[0] == classname: resultlist.append(dependencytuple[1]) return resultlist def _GetCompositeCreatedClassesFor(self, classname): return self.GetCompositeClassesForAttr(classname, self.classentry) def _DumpAttribute(self, attrobj): compositescreated = self._GetCompositeCreatedClassesFor(attrobj.attrname) if compositescreated and self.embedcompositeswithattributelist: self.result += "%s %s <@>----> %s" % (attrobj.attrname, self.staticmessage, str(compositescreated)) else: self.result += "%s %s" % (attrobj.attrname, self.staticmessage) self.result += self.manymessage self.result += '\n' def _DumpCompositeExtraFooter(self): if self.classentry.classdependencytuples and self.listcompositesatend: for dependencytuple in self.classentry.classdependencytuples: self.result += "%s <*>---> %s\n" % dependencytuple self.result += '-'*20 +'\n' def _DumpClassNameAndGeneralisations(self): self._Line() if self.classentry.ismodulenotrealclass: self.result += '%s (file)\n' % (self.aclass,) else: self.result += '%s --------|> %s\n' % (self.aclass, self.classentry.classesinheritsfrom) self._Line() def _DumpAttributes(self): for attrobj in self.classentry.attrs: self.staticmessage = "" self.manymessage = "" if 'static' in attrobj.attrtype: self.staticmessage = " static" if 'many' in attrobj.attrtype: self.manymessage = " 1..*" self._DumpAttribute(attrobj) def _DumpMethods(self): for adef in self.classentry.defs: self.result += adef +'\n' def _Line(self): self.result += '-'*20 +'\n' def _DumpClassHeader(self): self.result += '\n' def _DumpClassFooter(self): self.result += '\n' self.result += '\n' def _DumpModuleMethods(self): if self.modulemethods: self.result += ' ModuleMethods = %s\n' % `self.modulemethods` ## self.result += '\n' def __str__(self): self.result = '' self._DumpClassHeader() self._DumpModuleMethods() optionAlphabetic = 0 classnames = self.classlist.keys() if optionAlphabetic: classnames.sort() else: def cmpfunc(a,b): if a.find('Module_') <> -1: return -1 else: if a < b: return -1 elif a == b: return 0 else: return 1 classnames.sort(cmpfunc) for self.aclass in classnames: self.classentry = self.classlist[self.aclass] ## for self.aclass, self.classentry in self.classlist.items(): self._DumpClassNameAndGeneralisations() self._DumpAttributes() self._Line() self._DumpMethods() self._Line() self._DumpCompositeExtraFooter() self._DumpClassFooter() return self.result class PySourceAsJava(PySourceAsText): def __init__(self, outdir=None): PySourceAsText.__init__(self) self.outdir = outdir self.fp = None def _DumpClassFooter(self): self.result += "}\n" if self.fp: self.fp.write(self.result) self.fp.close() self.fp = None self.result = '' def _DumpModuleMethods(self): self.result += '/*\n' PySourceAsText._DumpModuleMethods(self) self.result += '*/\n' def _OpenNextFile(self): filepath = "%s\\%s.java" % (self.outdir, self.aclass) self.fp = open(filepath, 'w') def _NiceNameToPreventCompilerErrors(self, attrname): """ Prevent compiler errors on the java side by checking and modifying attribute name """ # only emit the rhs of a multi part name e.g. undo.UndoItem will appear only as UndoItem if attrname.find('.') <> -1: attrname = attrname.split('.')[-1] # take the last # Prevent compiler errors on the java side by avoiding the generating of java keywords as attribute names if attrname in javakeywords: attrname = '_' + attrname return attrname def _DumpAttribute(self, attrobj): compositescreated = self._GetCompositeCreatedClassesFor(attrobj.attrname) if compositescreated: compositecreated = compositescreated[0] else: compositecreated = None # Extra processing on the attribute name, to avoid java compiler errors attrname = self._NiceNameToPreventCompilerErrors(attrobj.attrname) if compositecreated and self.embedcompositeswithattributelist: self.result += " public %s %s %s = new %s();\n" % (self.staticmessage, compositecreated, attrname, compositecreated) else: ## self.result += " public %s void %s;\n" % (self.staticmessage, attrobj.attrname) ## self.result += " public %s int %s;\n" % (self.staticmessage, attrname) self.result += " public %s variant %s;\n" % (self.staticmessage, attrname) """ import java.util.Vector; private java.util.Vector lnkClass4; private Vector lnkClass4; """ def _DumpCompositeExtraFooter(self): pass def _DumpClassNameAndGeneralisations(self): if self.verbose: print ' Generating Java class', self.aclass self._OpenNextFile() self.result += "// Generated by PyNSource http://www.atug.com/andypatterns/pynsource.htm \n\n" ## self.result += "import javax.swing.Icon; // Not needed, just testing pyNSource's ability to generate import statements.\n\n" # NEW package support! self.result += 'public class %s ' % self.aclass if self.classentry.classesinheritsfrom: self.result += 'extends %s ' % self._NiceNameToPreventCompilerErrors(self.classentry.classesinheritsfrom[0]) self.result += '{\n' def _DumpMethods(self): for adef in self.classentry.defs: self.result += " public void %s() {\n }\n" % adef def _Line(self): pass def unique(s): """ Return a list of the elements in list s in arbitrary order, but without duplicates """ n = len(s) if n == 0: return [] u = {} try: for x in s: u[x] = 1 except TypeError: del u # move onto the next record else: return u.keys() raise "uniqueness algorithm failed .. type more of it in please - see http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52560" class PySourceAsDelphi(PySourceAsText): """ Example Delphi source file: unit test000123; interface uses SysUtils, Windows, Messages, Classes, Graphics, Controls, Forms, Dialogs; type TDefault1 = class (TObject) private field0012: Variant; public class var field0123434: Variant; procedure Member1; class procedure Member2; end; procedure Register; implementation procedure Register; begin end; { ********************************** TDefault1 *********************************** } procedure TDefault1.Member1; begin end; class procedure TDefault1.Member2; begin end; end. """ def __init__(self, outdir=None): PySourceAsText.__init__(self) self.outdir = outdir self.fp = None def _DumpClassFooter(self): self.result += "\n\n" self.result += "implementation\n\n" self.DumpImplementationMethods() self.result += "\nend.\n\n" if self.fp: self.fp.write(self.result) self.fp.close() self.fp = None self.result = '' def _DumpModuleMethods(self): self.result += '(*\n' PySourceAsText._DumpModuleMethods(self) self.result += '*)\n\n' def _OpenNextFile(self): filepath = "%s\\unit_%s.pas" % (self.outdir, self.aclass) self.fp = open(filepath, 'w') def _NiceNameToPreventCompilerErrors(self, attrname): """ Prevent compiler errors on the java side by checking and modifying attribute name """ # only emit the rhs of a multi part name e.g. undo.UndoItem will appear only as UndoItem if attrname.find('.') <> -1: attrname = attrname.split('.')[-1] # take the last # Prevent compiler errors on the Delphi side by avoiding the generating of delphi keywords as attribute names if attrname.lower() in delphikeywords: # delphi is case insensitive, so convert everything to lowercase for comparisons attrname = '_' + attrname return attrname def _DumpAttribute(self, attrobj): """ Figure out what type the attribute is only in those cases where we are later going to assign to these variables using .Create() in the constructor. The rest we make Variants. """ compositescreated = self._GetCompositeCreatedClassesFor(attrobj.attrname) if compositescreated: compositecreated = compositescreated[0] else: compositecreated = None # Extra processing on the attribute name, to avoid delphi compiler errors attrname = self._NiceNameToPreventCompilerErrors(attrobj.attrname) self.result += " " if self.staticmessage: self.result += "class var" if compositecreated: vartype = compositecreated else: vartype = 'Variant' self.result += "%s : %s;\n"%(attrname, vartype) # generate more complex stuff in the implementation section... ## if compositecreated and self.embedcompositeswithattributelist: ## self.result += " public %s %s %s = new %s();\n" % (self.staticmessage, compositecreated, attrname, compositecreated) ## else: ## self.result += "%s : Variant;\n"%attrname def _DumpCompositeExtraFooter(self): pass def _DumpClassNameAndGeneralisations(self): if self.verbose: print ' Generating Delphi class', self.aclass self._OpenNextFile() self.result += "// Generated by PyNSource http://www.atug.com/andypatterns/pynsource.htm \n\n" self.result += "unit unit_%s;\n\n" % self.aclass self.result += "interface\n\n" uses = unique(self.GetUses()) if uses: self.result += "uses\n " self.result += ", ".join(uses) self.result += ";\n\n" self.result += 'type\n\n' self.result += '%s = class' % self.aclass if self.classentry.classesinheritsfrom: self.result += '(%s)' % self._NiceNameToPreventCompilerErrors(self.classentry.classesinheritsfrom[0]) self.result += '\n' self.result += 'public\n' def _DumpMethods(self): if self.classentry.attrs: # if there were any atributes... self.result += "\n" # a little bit of a separator between attributes and methods. for adef in self.classentry.defs: if adef == '__init__': self.result += " constructor Create;\n" else: ## self.result += " function %s(): void; virtual;\n" % adef self.result += " procedure %s(); virtual;\n" % adef self.result += "end;\n" # end of class def DumpImplementationMethods(self): for adef in self.classentry.defs: if adef == '__init__': self.result += "constructor %s.Create;\n" % self.aclass # replace __init__ with the word 'Create' else: ## self.result += "function %s.%s(): void;\n" % (self.aclass, adef) self.result += "procedure %s.%s();\n" % (self.aclass, adef) self.result += "begin\n" if adef == '__init__': self.CreateCompositeAttributeClassCreationAndAssignmentInImplementation() self.result += "end;\n\n" def CreateCompositeAttributeClassCreationAndAssignmentInImplementation(self): # Only do those attributes that are composite and need to create an instance of something for attrobj in self.classentry.attrs: compositescreated = self._GetCompositeCreatedClassesFor(attrobj.attrname) if compositescreated and self.embedcompositeswithattributelist: # latter variable always seems to be true! Never reset!? compositecreated = compositescreated[0] self.result += " %s := %s.Create();\n" % (attrobj.attrname, compositecreated) def GetUses(self): result = [] for attrobj in self.classentry.attrs: compositescreated = self._GetCompositeCreatedClassesFor(attrobj.attrname) if compositescreated and self.embedcompositeswithattributelist: # latter variable always seems to be true! Never reset!? compositecreated = compositescreated[0] result.append(compositecreated) # Also use any inherited calss modules. if self.classentry.classesinheritsfrom: result.append(self._NiceNameToPreventCompilerErrors(self.classentry.classesinheritsfrom[0])) return [ 'unit_'+u for u in result ] def _Line(self): pass class PythonToJava: def __init__(self, directories, treatmoduleasclass=0, verbose=0): self.directories = directories self.optionModuleAsClass = treatmoduleasclass self.verbose = verbose def _GenerateAuxilliaryClasses(self): classestocreate = ('variant', 'unittest', 'list', 'object', 'dict') # should add more classes and add them to a jar file to avoid namespace pollution. for aclass in classestocreate: fp = open(os.path.join(self.outpath, aclass+'.java'), 'w') fp.write(self.GenerateSourceFileForAuxClass(aclass)) fp.close() def GenerateSourceFileForAuxClass(self, aclass): return '\npublic class %s {\n}\n'%aclass def ExportTo(self, outpath): self.outpath = outpath self._GenerateAuxilliaryClasses() for directory in self.directories: if '*' in directory or '.' in directory: filepath = directory else: filepath = os.path.join(directory, "*.py") if self.verbose: print 'Processing directory', filepath globbed = glob.glob(filepath) #print 'Java globbed is', globbed for f in globbed: self._Process(f) def _Process(self, filepath): if self.verbose: padding = ' ' else: padding = '' thefile = os.path.basename(filepath) if thefile[0] == '_': print ' ', 'Skipped', thefile, 'cos begins with underscore.' return print '%sProcessing %s...'%(padding, thefile) p = self._CreateParser() p.Parse(filepath) str(p) # triggers the output. def _CreateParser(self): p = PySourceAsJava(self.outpath) p.optionModuleAsClass = self.optionModuleAsClass p.verbose = self.verbose return p class PythonToDelphi(PythonToJava): def _GenerateAuxilliaryJavaClasses(self): pass def _CreateParser(self): p = PySourceAsDelphi(self.outpath) p.optionModuleAsClass = self.optionModuleAsClass p.verbose = self.verbose return p def _GenerateAuxilliaryClasses(self): # Delphi version omits the class 'object' and 'variant' since these already are pre-defined in Delphi. classestocreate = ('unittest', 'list', 'dict') # should add more classes for aclass in classestocreate: fp = open(os.path.join(self.outpath, 'unit_'+aclass+'.pas'), 'w') fp.write(self.GenerateSourceFileForAuxClass(aclass)) fp.close() def GenerateSourceFileForAuxClass(self, aclass): template = """ unit unit_%s; interface type %s = class public end; implementation end. """ return template%(aclass,aclass) def run(): #FILE = 'testmodule01.py' #FILE = 'C:\\Documents and Settings\\Administrator\\Desktop\\try\\PyutXmlV6.py' #FILE = 'testmodule02.py' #FILE = 'andyparse9.py' FILE = "c:\\cc\devel\storyline\\battle.py" #FILE = "c:\\cc\devel\storyline\\battleresult.py" #FILE = "c:\\cc\devel\storyline\\battlestabs.py" p = PySourceAsText() #p = JavaDumper("c:\\try") p.Parse(FILE) print '*'*20, 'parsing', FILE, '*'*20 print p print 'Done.' if __name__ == '__main__': #run() import sys, glob, getopt SIMPLE = 0 globbed = [] optionVerbose = 0 optionModuleAsClass = 0 optionExportToJava = 0 optionExportToDelphi = 0 optionExportTo_outdir = '' if SIMPLE: params = sys.argv[1] globbed = glob.glob(params) else: listofoptionvaluepairs, params = getopt.getopt(sys.argv[1:], "mvj:d:") print listofoptionvaluepairs, params def EnsurePathExists(outdir, outlanguagemsg): assert outdir, 'Need to specify output folder for %s output - got %s.'%(outlanguagemsg, outdir) if not os.path.exists(outdir): raise RuntimeError, ('Output directory %s for %s file output does not exist.'%(outdir,outlanguagemsg)) for optionvaluepair in listofoptionvaluepairs: if '-m' == optionvaluepair[0]: optionModuleAsClass = 1 if '-v' == optionvaluepair[0]: optionVerbose = 1 if optionvaluepair[0] in ('-j', '-d'): if optionvaluepair[0] == '-j': optionExportToJava = 1 language = 'Java' else: optionExportToDelphi = 1 language = 'Delphi' optionExportTo_outdir = optionvaluepair[1] EnsurePathExists(optionExportTo_outdir, language) for param in params: files = glob.glob(param) globbed += files if globbed: if optionExportToJava or optionExportToDelphi: if optionExportToJava: u = PythonToJava(globbed, treatmoduleasclass=optionModuleAsClass, verbose=optionVerbose) else: u = PythonToDelphi(globbed, treatmoduleasclass=optionModuleAsClass, verbose=optionVerbose) u.ExportTo(optionExportTo_outdir) else: p = PySourceAsText() p.optionModuleAsClass = optionModuleAsClass p.verbose = optionVerbose for f in globbed: p.Parse(f) print p else: print """Usage: pynsource -v -m -j outdir sourcedirorpythonfiles... -j generate java files, specify output folder for java files -v verbose -m create psuedo class for each module, module attrs/defs etc treated as class attrs/defs BASIC EXAMPLES e.g. pynsource Test/testmodule01.py e.g. pynsource -m Test/testmodule03.py JAVA EXAMPLES e.g. pynsource -j c:/try c:/try e.g. pynsource -v -m -j c:/try c:/try e.g. pynsource -v -m -j c:/try c:/try/s*.py e.g. pynsource -j c:/try c:/try/s*.py Tests/u*.py e.g. pynsource -v -m -j c:/try c:/try/s*.py Tests/u*.py c:\cc\Devel\Client\w*.py DELPHI EXAMPLE e.g. pynsource -d c:/delphiouputdir c:/pythoninputdir/*.py """ gaphor-0.17.2/gaphor/plugins/xmiexport/000077500000000000000000000000001220151210700200505ustar00rootroot00000000000000gaphor-0.17.2/gaphor/plugins/xmiexport/__init__.py000066400000000000000000000034341220151210700221650ustar00rootroot00000000000000""" This plugin extends Gaphor with XMI export functionality. """ import gtk from zope import interface, component from gaphor.core import _, inject, action, build_action_group from gaphor.interfaces import IService, IActionProvider from gaphor.ui.filedialog import FileDialog import exportmodel class XMIExport(object): interface.implements(IService, IActionProvider) element_factory = inject('element_factory') main_window = inject('main_window') menu_xml = """ """ def __init__(self): self.action_group = build_action_group(self) def init(self, app): pass def shutdown(self): pass @action(name='file-export-xmi', label=_('Export to XMI'), tooltip=_('Export model to XMI (XML Model Interchange) format')) def execute(self): filename = self.main_window.get_filename() if filename: filename = filename.replace('.gaphor', '.xmi') else: filename = 'model.xmi' file_dialog = FileDialog(_('Export model to XMI file'),\ action='save',\ filename=filename) filename = file_dialog.selection if filename and len(filename) > 0: log.debug('Exporting XMI model to: %s' % filename) export = exportmodel.XMIExport(self.element_factory) try: export.export(filename) except Exception, e: log.error('Error while saving model to file %s: %s' % (filename, e)) # vim:sw=4:et gaphor-0.17.2/gaphor/plugins/xmiexport/exportmodel.py000066400000000000000000000233751220151210700227760ustar00rootroot00000000000000 from gaphor.misc.xmlwriter import XMLWriter class XMIExport(object): XMI_VERSION = '2.1' XMI_NAMESPACE = 'http://schema.omg.org/spec/XMI/2.1' UML_NAMESPACE = 'http://schema.omg.org/spec/UML/2.1' XMI_PREFIX = 'XMI' UML_PREFIX = 'UML' def __init__(self, element_factory): self.element_factory = element_factory self.handled_ids = list() def handle(self, xmi, element): log.debug('Handling %s'%element.__class__.__name__) try: handler_name = 'handle%s'%element.__class__.__name__ handler = getattr(self, handler_name) idref = element.id in self.handled_ids handler(xmi, element, idref=idref) if not idref: self.handled_ids.append(element.id) except AttributeError, e: log.warning('Missing handler for %s:%s'%(element.__class__.__name__,e)) except Exception, e: log.error('Failed to handle %s:%s'%(element.__class__.__name__, e)) def handlePackage(self, xmi, element, idref=False): attributes = dict() attributes['%s:id'%self.XMI_PREFIX] = element.id attributes['name'] = element.name attributes['visibility'] = element.visibility xmi.startElement('%s:Package'%self.UML_PREFIX, attrs=attributes) for ownedMember in element.ownedMember: xmi.startElement('ownedMember', attrs=dict()) self.handle(xmi, ownedMember) xmi.endElement('ownedMember') xmi.endElement('%s:Package'%self.UML_PREFIX) def handleClass(self, xmi, element, idref=False): attributes = dict() if idref: attributes['%s:idref'%self.XMI_PREFIX] = element.id else: attributes['%s:id'%self.XMI_PREFIX] = element.id attributes['name'] = element.name attributes['isAbstract'] = str(element.isAbstract) xmi.startElement('%s:Class'%self.UML_PREFIX, attrs=attributes) if not idref: for ownedAttribute in element.ownedAttribute: xmi.startElement('ownedAttribute', attrs=dict()) self.handle(xmi, ownedAttribute) xmi.endElement('ownedAttribute') for ownedOperation in element.ownedOperation: xmi.startElement('ownedOperation', attrs=dict()) self.handle(xmi, ownedOperation) xmi.endElement('ownedOperation') xmi.endElement('%s:Class'%self.UML_PREFIX) def handleProperty(self, xmi, element, idref=False): attributes = dict() attributes['%s:id'%self.XMI_PREFIX] = element.id attributes['isStatic'] = str(element.isStatic) attributes['isOrdered'] = str(element.isOrdered) attributes['isUnique'] = str(element.isUnique) attributes['isDerived'] = str(element.isDerived) attributes['isDerivedUnion'] = str(element.isDerivedUnion) attributes['isReadOnly'] = str(element.isReadOnly) if element.name is not None: attributes['name'] = element.name xmi.startElement('%s:Property'%self.UML_PREFIX, attrs=attributes) #TODO: This should be type, not typeValue. if element.typeValue is not None: xmi.startElement('type', attrs=dict()) self.handle(xmi, element.typeValue) xmi.endElement('type') xmi.endElement('%s:Property'%self.UML_PREFIX) def handleOperation(self, xmi, element, idref=False): attributes = dict() attributes['%s:id'%self.XMI_PREFIX] = element.id attributes['isStatic'] = str(element.isStatic) attributes['isQuery'] = str(element.isQuery) attributes['name'] = element.name xmi.startElement('%s:Operation'%self.XMI_PREFIX, attrs=attributes) for ownedParameter in element.parameter: xmi.startElement('ownedElement', attrs=dict()) self.handle(xmi, ownedParameter) xmi.endElement('ownedElement') xmi.endElement('%s:Operation'%self.XMI_PREFIX) def handleParameter(self, xmi, element, idref=False): attributes = dict() attributes['%s:id'%self.XMI_PREFIX] = element.id attributes['isOrdered'] = str(element.isOrdered) attributes['isUnique'] = str(element.isUnique) attributes['direction'] = element.direction attributes['name'] = element.name xmi.startElement('%s:Parameter'%self.XMI_PREFIX, attrs=attributes) xmi.endElement('%s:Parameter'%self.XMI_PREFIX) def handleLiteralSpecification(self, xmi, element, idref=False): attributes = dict() attributes['%s:id'%self.XMI_PREFIX] = element.id attributes['value'] = element.value xmi.startElement('%s:LiteralSpecification'%self.UML_PREFIX, attrs=attributes) xmi.endElement('%s:LiteralSpecification'%self.UML_PREFIX) def handleAssociation(self, xmi, element, idref=False): attributes = dict() attributes['%s:id'%self.XMI_PREFIX] = element.id attributes['isDerived'] = str(element.isDerived) xmi.startElement('%s:Association'%self.UML_PREFIX, attrs=attributes) for memberEnd in element.memberEnd: xmi.startElement('memberEnd', attrs=dict()) self.handle(xmi, memberEnd) xmi.endElement('memberEnd') for ownedEnd in element.ownedEnd: xmi.startElement('ownedEnd', attrs=dict()) self.handle(xmi, ownedEnd) xmi.endElement('ownedEnd') xmi.endElement('%s:Association'%self.UML_PREFIX) def handleDependency(self, xmi, element, idref=False): attributes = dict() attributes['%s:id'%self.XMI_PREFIX] = element.id xmi.startElement('%s:Dependency'%self.UML_PREFIX, attrs=attributes) for client in element.client: xmi.startElement('client', attrs=dict()) self.handle(xmi, client) xmi.endElement('client') for supplier in element.supplier: xmi.startElement('supplier', attrs=dict()) self.handle(xmi, supplier) xmi.endElement('supplier') xmi.endElement('%s:Dependency'%self.UML_PREFIX) def handleGeneralization(self, xmi, element, idref=False): attributes = dict() attributes['%s:id'%self.XMI_PREFIX] = element.id attributes['isSubstitutable'] = str(element.isSubstitutable) xmi.startElement('%s:Generalization'%self.UML_PREFIX, attrs=attributes) if element.general: xmi.startElement('general', attrs=dict()) self.handle(xmi, element.general) xmi.endElement('general') if element.specific: xmi.startElement('specific', attrs=dict()) self.handle(xmi, element.specific) xmi.endElement('specific') xmi.endElement('%s:Generalization'%self.UML_PREFIX) def handleRealization(self, xmi, element, idref=False): attributes = dict() attributes['%s:id'%self.XMI_PREFIX] = element.id xmi.startElement('%s:Realization'%self.UML_PREFIX, attrs=attributes) for client in element.client: xmi.startElement('client', attrs=dict()) self.handle(xmi, client) xmi.endElement('client') for supplier in element.supplier: xmi.startElement('supplier', attrs=dict()) self.handle(xmi, supplier) xmi.endElement('supplier') xmi.endElement('%s:Realization'%self.UML_PREFIX) def handleInterface(self, xmi, element, idref=False): attributes = dict() attributes['%s:id'%self.XMI_PREFIX] = element.id xmi.startElement('%s:Interface'%self.UML_PREFIX, attrs=attributes) for ownedAttribute in element.ownedAttribute: xmi.startElement('ownedAttribute', attrs=dict()) self.handle(ownedAttribute) xmi.endElement('ownedAttribute') for ownedOperation in element.ownedOperation: xmi.startElement('ownedOperation', attrs=dict()) self.handle(ownedOperation) xmi.endElement('ownedOperation') xmi.endElement('%s:Interface'%self.UML_PREFIX) def export(self, filename): out = open(filename, 'w') xmi = XMLWriter(out) attributes = dict() attributes['xmi.version'] = self.XMI_VERSION attributes['xmlns:xmi'] = self.XMI_NAMESPACE attributes['xmlns:UML'] = self.UML_NAMESPACE xmi.startElement('XMI', attrs=attributes) for package in self.element_factory.select(self.select_package): self.handle(xmi, package) for generalization in self.element_factory.select(self.select_generalization): self.handle(xmi, generalization) for realization in self.element_factory.select(self.select_realization): self.handle(xmi, realization) xmi.endElement('XMI') log.debug(self.handled_ids) def select_package(self, element): return element.__class__.__name__ == 'Package' def select_generalization(self, element): return element.__class__.__name__ == 'Generalization' def select_realization(self, element): return element.__class__.name__ == 'Implementation' gaphor-0.17.2/gaphor/services/000077500000000000000000000000001220151210700161535ustar00rootroot00000000000000gaphor-0.17.2/gaphor/services/__init__.py000066400000000000000000000000001220151210700202520ustar00rootroot00000000000000gaphor-0.17.2/gaphor/services/actionmanager.py000066400000000000000000000061541220151210700213430ustar00rootroot00000000000000""" """ import gtk from zope import interface, component from logging import getLogger from gaphor.core import inject from gaphor.interfaces import IService, IActionProvider from gaphor.event import ServiceInitializedEvent, ActionExecuted class ActionManager(object): """ This service is responsible for maintaining actions. """ interface.implements(IService) logger = getLogger('ActionManager') component_registry = inject('component_registry') ui_manager = inject('ui_manager') def __init__(self): pass def init(self, app): self.logger.info('Loading action provider services') for name, service in self.component_registry.get_utilities(IActionProvider): self.logger.debug('Service is %s' % service) self.register_action_provider(service) self.component_registry.register_handler(self._service_initialized_handler) def shutdown(self): self.logger.info('Shutting down') self.component_registry.unregister_handler(self._service_initialized_handler) def execute(self, action_id, active=None): self.logger.debug('Executing action, action_id is %s' % action_id) a = self.get_action(action_id) if a: a.activate() self.component_registry.handle(ActionExecuted(action_id, a)) else: self.logger.warning('Unknown action %s' % action_id) def update_actions(self): self.ui_manager.ensure_update() def get_action(self, action_id): for g in self.ui_manager.get_action_groups(): a = g.get_action(action_id) if a: return a def register_action_provider(self, action_provider): self.logger.debug('Registering action provider %s' % action_provider) action_provider = IActionProvider(action_provider) try: # Check if the action provider is not already registered action_provider.__ui_merge_id except AttributeError: assert action_provider.action_group self.ui_manager.insert_action_group(action_provider.action_group, -1) try: menu_xml = action_provider.menu_xml except AttributeError: pass else: action_provider.__ui_merge_id = \ self.ui_manager.add_ui_from_string(menu_xml) @component.adapter(ServiceInitializedEvent) def _service_initialized_handler(self, event): self.logger.debug('Handling ServiceInitializedEvent') self.logger.debug('Service is %s' % event.service) if IActionProvider.providedBy(event.service): self.logger.debug('Loading registered service %s' % event.service) self.register_action_provider(event.service) class UIManager(gtk.UIManager): """ Service version of gtk.UIManager. """ interface.implements(IService) def init(self, app=None): pass def shutdown(self): pass # vim:sw=4:et:ai gaphor-0.17.2/gaphor/services/adapterloader.py000066400000000000000000000010401220151210700213270ustar00rootroot00000000000000 import pkg_resources from zope import interface from gaphor.interfaces import IService class AdapterLoader(object): """ Initiate adapters from the gaphor.adapters module. """ interface.implements(IService) def init(self, app): import gaphor.adapters import gaphor.adapters.connectors import gaphor.adapters.editors import gaphor.adapters.grouping import gaphor.adapters.propertypages import gaphor.adapters.states def shutdown(self): pass # vim:sw=4:et:ai gaphor-0.17.2/gaphor/services/backupservice.py000066400000000000000000000025541220151210700213610ustar00rootroot00000000000000""" """ from zope import interface from gaphor.UML import Element from gaphor.application import Application from gaphor.interfaces import IService, IActionProvider from gaphor.core import _, inject # Register application specific picklers: import gaphas.picklers from gaphor.misc.latepickle import LatePickler import pickle class MyPickler(LatePickler): """ Customize the pickler to only delay instantiations of Element objects. """ def delay(self, obj): return isinstance(obj, Element) class BackupService(object): """ This service makes backups every *x* minutes. """ interface.implements(IService) element_factory = inject('element_factory') def __init__(self): self.tempname = '.backup.gaphor.tmp' def init(self, app): pass def shutdown(self): pass def backup(self): f = open(self.tempname, 'w') try: pickler = MyPickler(f) pickler.dump(self.element_factory.lselect()) finally: f.close() def restore(self): f = open(self.tempname, 'r') try: elements = pickle.Unpickler(f).load() finally: f.close() self.element_factory.flush() map(self.element_factory.bind, elements) # vim: sw=4:et:ai gaphor-0.17.2/gaphor/services/componentregistry.py000066400000000000000000000136621220151210700223300ustar00rootroot00000000000000""" TODO: Move component information (event handling, and other stuff done by zope.component) to this service. Maybe we should split the ComponentRegistry in a Dispatcher (register_handler, unregister_handler, handle), a AdapterRegistry and a Subscription registry. """ from zope import interface, component from zope.component import registry from gaphor.core import inject from gaphor.interfaces import IService, IEventFilter class ZopeComponentRegistry(object): """ The ZopeComponentRegistry provides a subset of the ``zope.component.registry.Components`` interface. This part is mainly enough to get the work done and keeps stuff simpler. This service should not be called directly, but through more specific service such as Dispatcher and AdapterRegistry. """ interface.implements(IService) def __init__(self): pass def init(self, app): self._components = registry.Components( name='component_registry', bases=(component.getGlobalSiteManager(),)) # Make sure component.handle() and query methods works. # TODO: eventually all queries should be done through the Application # instance. # Used in collection.py, transaction.py, diagramtoolbox.py: component.handle = self.handle #component.getMultiAdapter = self._components.getMultiAdapter # Used all over the place: component.queryMultiAdapter = self._components.queryMultiAdapter #component.getAdapter = self._components.getAdapter #component.queryAdapter = self._components.queryAdapter # Used in propertyeditor.py: component.getAdapters = self._components.getAdapters #component.getUtility = self._components.getUtility # Used in test cases (test_application.py) component.queryUtility = self._components.queryUtility #component.getUtilitiesFor = self._components.getUtilitiesFor def shutdown(self): pass def get_service(self, name): return self.get_utility(IService, name) # Wrap zope.component's Components methods def register_utility(self, component=None, provided=None, name=''): """ Register a component (e.g. Service) """ self._components.registerUtility(component, provided, name) def unregister_utility(self, component=None, provided=None, name=''): """ Unregister a component (e.g. Service) """ self._components.unregisterUtility(component, provided, name) def get_utility(self, provided, name=''): """ Get a component from the registry. zope.component.ComponentLookupError is thrown if no such component exists. """ return self._components.getUtility(provided, name) def get_utilities(self, provided): """ Iterate over all components that provide a certain interface. """ for name, utility in self._components.getUtilitiesFor(provided): yield name, utility def register_adapter(self, factory, adapts=None, provides=None, name=''): """ Register an adapter (factory) that adapts objects to a specific interface. A name can be used to distinguish between different adapters that adapt to the same interfaces. """ self._components.registerAdapter(factory, adapts, provides, name, event=False) def unregister_adapter(self, factory=None, required=None, provided=None, name=u''): """ Unregister a previously registered adapter. """ self._components.unregisterAdapter(factory, required, provided, name) def get_adapter(self, objects, interface): """ Obtain an adapter that adheres to a specific interface. Objects can be either a single object or a tuple of objects (multi adapter). If nothing is found `None` is returned. """ if isinstance(objects, (list, tuple)): return self._components.queryMultiAdapter(objects, interface) return self._components.queryAdapter(objects, interface) def register_subscription_adapter(self, factory, adapts=None, provides=None): """ Register a subscription adapter. See registerAdapter(). """ self._components.registerSubscriptionAdapter(factory, adapts, provides, event=False) def unregister_subscription_adapter(self, factory=None, required=None, provided=None, name=u''): """ Unregister a previously registered subscription adapter. """ self._components.unregisterSubscriptionAdapter(factory, required, provided, name) def subscribers(self, objects, interface): return self._components.subscribers(objects, interface) def register_handler(self, factory, adapts=None): """ Register a handler. Handlers are triggered (executed) when specific events are emitted through the handle() method. """ self._components.registerHandler(factory, adapts, event=False) def unregister_handler(self, factory=None, required=None): """ Unregister a previously registered handler. """ self._components.unregisterHandler(factory, required) def _filter(self, objects): filtered = list(objects) for o in objects: for adapter in self._components.subscribers(objects, IEventFilter): if adapter.filter(): # event is blocked filtered.remove(o) break return filtered def handle(self, *events): """ Send event notifications to registered handlers. """ objects = self._filter(events) if objects: map(self._components.handle, events) # vim:sw=4:et:ai gaphor-0.17.2/gaphor/services/copyservice.py000066400000000000000000000125261220151210700210660ustar00rootroot00000000000000""" Copy / Paste functionality """ from zope import interface, component import gaphas from gaphor.UML import Element from gaphor.UML.collection import collection from gaphor.interfaces import IService, IActionProvider from gaphor.ui.interfaces import IDiagramSelectionChange from gaphor.core import _, inject, action, build_action_group, transactional class CopyService(object): """ Copy/Cut/Paste functionality required a lot of thinking: Store a list of DiagramItems that have to be copied in a global 'copy-buffer'. - in order to make copy/paste work, the load/save functions should be generatlised to allow a subset to be saved/loaded (which is needed anyway for exporting/importing stereotype Profiles). - How many data should be saved? (e.g. we copy a diagram item, remove it (the underlaying UML element is removed) and the paste the copied item. The diagram should act as if we have placed a copy of the removed item on the canvas and make the uml element visible again. """ interface.implements(IService, IActionProvider) component_registry = inject('component_registry') element_factory = inject('element_factory') main_window = inject('main_window') menu_xml = """ """ def __init__(self): self.copy_buffer = set() self.action_group = build_action_group(self) def init(self, app): self.action_group.get_action('edit-copy').props.sensitive = False self.action_group.get_action('edit-paste').props.sensitive = False self.component_registry.register_handler(self._update) def shutdown(self): self.copy_buffer = set() self.component_registry.unregister_handler(self._update) @component.adapter(IDiagramSelectionChange) def _update(self, event): diagram_view = event.diagram_view self.action_group.get_action('edit-copy').props.sensitive = bool(diagram_view.selected_items) def copy(self, items): if items: self.copy_buffer = set(items) self.action_group.get_action('edit-paste').props.sensitive = True def copy_func(self, name, value, reference=False): """ Copy an element, preferbly from the list of new items, otherwise from the element factory. If it does not exist there, do not copy it! """ def load_element(): item = self._new_items.get(value.id) if item: self._item.load(name, item) else: item = self.element_factory.lookup(value.id) if item: self._item.load(name, item) if reference or isinstance(value, Element): load_element() elif isinstance(value, collection): values = value for value in values: load_element() elif isinstance(value, gaphas.Item): load_element() else: # Plain attribute self._item.load(name, str(value)) @transactional def paste(self, diagram): """ Paste items in the copy-buffer to the diagram """ canvas = diagram.canvas if not canvas: return copy_items = [ c for c in self.copy_buffer if c.canvas ] # Mapping original id -> new item self._new_items = {} # Create new id's that have to be used to create the items: for ci in copy_items: self._new_items[ci.id] = diagram.create(type(ci)) # Copy attributes and references. References should be # 1. in the ElementFactory (hence they are model elements) # 2. refered to in new_items # 3. canvas property is overridden for ci in copy_items: self._item = self._new_items[ci.id] ci.save(self.copy_func) # move pasted items a bit, so user can see result of his action :) # update items' matrix immediately # TODO: if it is new canvas, then let's not move, how to do it? for item in self._new_items.values(): item.matrix.translate(10, 10) canvas.update_matrix(item) # solve internal constraints of items immediately as item.postload # reconnects items and all handles has to be in place canvas.solver.solve() for item in self._new_items.values(): item.postload() @action(name='edit-copy', stock_id='gtk-copy') def copy_action(self): view = self.main_window.get_current_diagram_view() if view.is_focus(): items = view.selected_items copy_items = [] for i in items: copy_items.append(i) self.copy(copy_items) @action(name='edit-paste', stock_id='gtk-paste') def paste_action(self): view = self.main_window.get_current_diagram_view() diagram = self.main_window.get_current_diagram() if not view: return self.paste(diagram) view.unselect_all() for item in self._new_items.values(): view.select_item(item) # vim:sw=4:et:ai gaphor-0.17.2/gaphor/services/diagramexportmanager.py000066400000000000000000000153351220151210700227350ustar00rootroot00000000000000""" Service dedicated to exporting diagrams to a varyity of file formats. """ import os import cairo from zope import interface, component from logging import getLogger from gaphor.core import _, inject, action, build_action_group from gaphor.interfaces import IService, IActionProvider from gaphor.ui.filedialog import FileDialog from gaphor.ui.questiondialog import QuestionDialog from gaphas.view import View from gaphas.painter import ItemPainter, BoundingBoxPainter from gaphas.freehand import FreeHandPainter from gaphas.geometry import Rectangle class DiagramExportManager(object): """ Service for exporting diagrams as images (SVG, PNG, PDF). """ interface.implements(IService, IActionProvider) main_window = inject('main_window') properties = inject('properties') logger = getLogger('ExportManager') menu_xml = """ """ def __init__(self): self.action_group = build_action_group(self) def init(self, app): pass def shutdown(self): pass def update(self): self.logger.info('Updating') tab = self.get_window().get_current_diagram_tab() self.sensitive = tab and True or False def save_dialog(self, diagram, title, ext): filename = (diagram.name or 'export') + ext file_dialog = FileDialog(title, action='save', filename=filename) save = False while True: filename = file_dialog.selection if os.path.exists(filename): question = _("The file %s already exists. Do you want to "\ "replace it with the file you are exporting "\ "to?") % filename question_dialog = QuestionDialog(question) answer = question_dialog.answer question_dialog.destroy() if answer: save = True break else: save = True break file_dialog.destroy() if save and filename: return filename def update_painters(self, view): self.logger.info('Updating painters') self.logger.debug('View is %s' % view) sloppiness = self.properties('diagram.sloppiness', 0) self.logger.debug('Sloppiness is %s' % sloppiness) if sloppiness: view.painter = FreeHandPainter(ItemPainter(), sloppiness) view.bounding_box_painter = FreeHandPainter(BoundingBoxPainter(), sloppiness) else: view.painter = ItemPainter() def save_svg(self, filename, canvas): self.logger.info('Exporting to SVG') self.logger.debug('SVG path is %s' % filename) view = View(canvas) self.update_painters(view) # Update bounding boxes with a temporaly CairoContext # (used for stuff like calculating font metrics) tmpsurface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 0, 0) tmpcr = cairo.Context(tmpsurface) view.update_bounding_box(tmpcr) tmpcr.show_page() tmpsurface.flush() w, h = view.bounding_box.width, view.bounding_box.height surface = cairo.SVGSurface(filename, w, h) cr = cairo.Context(surface) view.matrix.translate(-view.bounding_box.x, -view.bounding_box.y) view.paint(cr) cr.show_page() surface.flush() surface.finish() def save_png(self, filename, canvas): self.logger.info('Exporting to PNG') self.logger.debug('PNG path is %s' % filename) view = View(canvas) self.update_painters(view) # Update bounding boxes with a temporaly CairoContext # (used for stuff like calculating font metrics) tmpsurface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 0, 0) tmpcr = cairo.Context(tmpsurface) view.update_bounding_box(tmpcr) tmpcr.show_page() tmpsurface.flush() w, h = view.bounding_box.width, view.bounding_box.height surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, int(w+1), int(h+1)) cr = cairo.Context(surface) view.matrix.translate(-view.bounding_box.x, -view.bounding_box.y) view.paint(cr) cr.show_page() surface.write_to_png(filename) def save_pdf(self, filename, canvas): self.logger.info('Exporting to PDF') self.logger.debug('PDF path is %s' % filename) view = View(canvas) self.update_painters(view) # Update bounding boxes with a temporaly CairoContext # (used for stuff like calculating font metrics) tmpsurface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 0, 0) tmpcr = cairo.Context(tmpsurface) view.update_bounding_box(tmpcr) tmpcr.show_page() tmpsurface.flush() w, h = view.bounding_box.width, view.bounding_box.height surface = cairo.PDFSurface(filename, w, h) cr = cairo.Context(surface) view.matrix.translate(-view.bounding_box.x, -view.bounding_box.y) view.paint(cr) cr.show_page() surface.flush() surface.finish() @action(name='file-export-svg', label='Export to SVG', tooltip='Export the diagram to SVG') def save_svg_action(self): title = 'Export diagram to SVG' ext = '.svg' diagram = self.main_window.get_current_diagram() filename = self.save_dialog(diagram, title, ext) if filename: self.save_svg(filename, diagram.canvas) @action(name='file-export-png', label='Export to PNG', tooltip='Export the diagram to PNG') def save_png_action(self): title = 'Export diagram to PNG' ext = '.png' diagram = self.main_window.get_current_diagram() filename = self.save_dialog(diagram, title, ext) if filename: self.save_png(filename, diagram.canvas) @action(name='file-export-pdf', label='Export to PDF', tooltip='Export the diagram to PDF') def save_pdf_action(self): title = 'Export diagram to PDF' ext = '.pdf' diagram = self.main_window.get_current_diagram() filename = self.save_dialog(diagram, title, ext) if filename: self.save_pdf(filename, diagram.canvas) # vim:sw=4:et: gaphor-0.17.2/gaphor/services/elementdispatcher.py000066400000000000000000000305751220151210700222370ustar00rootroot00000000000000""" """ from zope import interface, component from logging import getLogger from gaphor.core import inject from gaphor.interfaces import IService from gaphor.UML.interfaces import IElementChangeEvent, IModelFactoryEvent from gaphor import UML from gaphor.UML.interfaces import IAssociationSetEvent,\ IAssociationAddEvent,\ IAssociationDeleteEvent class EventWatcher(object): """ A helper for easy registering and unregistering event handlers. """ element_dispatcher = inject('element_dispatcher') logger = getLogger('EventWatcher') def __init__(self, element, default_handler=None): super(EventWatcher, self).__init__() self.element = element self.default_handler = default_handler self._watched_paths = dict() def watch(self, path, handler=None): """ Watch a certain path of elements starting with the DiagramItem. The handler is optional and will default the default provided at construction time. Watches should be set in the constructor, so they can be registered and unregistered in one shot. This interface is fluent(returns self). """ #self.logger.info('Watching element path') #self.logger.debug('Path is %s' % path) #self.logger.debug('Handler is %s' % handler) if handler: self._watched_paths[path] = handler elif self.default_handler: self._watched_paths[path] = self.default_handler else: raise ValueError('No handler provided for path ' + path) return self def register_handlers(self): #self.logger.info('Registering handlers') dispatcher = self.element_dispatcher element = self.element for path, handler in self._watched_paths.iteritems(): #self.logger.debug('Path is %s' % path) #self.logger.debug('Handler is %s' % handler) dispatcher.register_handler(handler, element, path) def unregister_handlers(self, *args): """ Unregister handlers. Extra arguments are ignored (makes connecting to destroy signals much easier though). """ #self.logger.info('Unregistering handlers') dispatcher = self.element_dispatcher for path, handler in self._watched_paths.iteritems(): #self.logger.debug('Path is %s' % path) #self.logger.debug('Handler is %s' % handler) dispatcher.unregister_handler(handler) class ElementDispatcher(object): """ The Element based Dispatcher allows handlers to receive only events related to certain elements. Those elements should be registered to. Also a path should be provided, that is used to find those changes. The handlers are registered on their property attribute. This avoids subclass lookups and is pretty specific. As a result this dispatcher is tailored for dispatching events from the data model (IElementChangeEvent) For example: if you're a TransitionItem (UML.Presentation instance) and you're interested in the value of the guard attribute of the model element that's represented by this item (gaphor.UML.Transition), you can register a handler like this:: dispatcher.register_handler(element, 'guard.specification.value', self._handler) Note the '<' and '>'. This is because guard references ValueSpecification, which does not have a value attribute. Therefore the default reference type is overruled in favour of the LiteralSpecification. This dispatcher keeps track of the kind of events that are dispatched. The dispatcher table is updated accordingly (so the right handlers are fired every time). """ interface.implements(IService) logger = getLogger('ElementDispatcher') component_registry = inject('component_registry') def __init__(self): # Table used to fire events: # (event.element, event.property): { handler: set(path, ..), ..} self._handlers = dict() # Fast resolution when handlers are disconnected # handler: [(element, property), ..] self._reverse = dict() def init(self, app): self.component_registry.register_handler(self.on_model_loaded) self.component_registry.register_handler(self.on_element_change_event) def shutdown(self): self.component_registry.unregister_handler(self.on_element_change_event) self.component_registry.unregister_handler(self.on_model_loaded) def _path_to_properties(self, element, path): """ Given a start element and a path, return a tuple of UML properties (association, attribute, etc.) representing the path. >>> from gaphor import UML >>> dispatcher = ElementDispatcher() >>> map(str, dispatcher._path_to_properties(UML.Class(), ... 'ownedOperation.parameter.name')) # doctest: +NORMALIZE_WHITESPACE ['-> class_>', "-> ownerReturnParam>', '-> ownerFormalParam>'>", "[0..1] = None>"] Should also work for elements that use subtypes of a certain class: >>> map(str, dispatcher._path_to_properties(UML.Transition(), ... 'guard.specification')) # doctest: +NORMALIZE_WHITESPACE ['', "[0..1] = None>"] """ c = type(element) tpath = [] for attr in path.split('.'): cname = '' if '<' in attr: assert attr.endswith('>'), '"%s" should end with ">"' % attr attr, cname = attr[:-1].split('<') prop = getattr(c, attr) tpath.append(prop) if cname: c = getattr(UML, cname) assert issubclass(c, prop.type), '%s should be a subclass of %s' % (c, prop.type) else: c = prop.type return tuple(tpath) def _add_handlers(self, element, props, handler): """ Provided an element and a path of properties (props), register the handler for each property. """ property, remainder = props[0], props[1:] key = (element, property) # Register key try: handlers = self._handlers[key] except KeyError: handlers = dict() self._handlers[key] = handlers # Register handler and it's remaining paths try: remainders = handlers[handler] except KeyError: remainders = handlers[handler] = set() if remainder: remainders.add(remainder) # Also add them to the reverse table, easing disconnecting try: reverse = self._reverse[handler] except KeyError: reverse = [] self._reverse[handler] = reverse reverse.append(key) # Apply remaining path if remainder: if property.upper > 1: for e in property._get(element): self._add_handlers(e, remainder, handler) else: e = property._get(element) if e and remainder: self._add_handlers(e, remainder, handler) def _remove_handlers(self, element, property, handler): """ Remove the handler of the path of elements. """ key = element, property handlers = self._handlers.get(key) if not handlers: return if property.upper > 1: for remainder in handlers.get(handler, ()): for e in property._get(element): #log.debug(' Remove handler %s for key %s, element %s' % (handler, str(remainder[0].name), e)) self._remove_handlers(e, remainder[0], handler) else: for remainder in handlers.get(handler, ()): e = property._get(element) if e: #log.debug('*Remove handler %s for key %s, element %s' % (handler, str(remainder[0].name), e)) self._remove_handlers(e, remainder[0], handler) try: del handlers[handler] except KeyError: self.logger.warning('Handler %s is not registered for %s.%s' % (handler, element, property)) if not handlers: del self._handlers[key] def register_handler(self, handler, element, path): #self.logger.info('Registering handler') #self.logger.debug('Handler is %s' % handler) #self.logger.debug('Element is %s' % element) #self.logger.debug('Path is %s' % path) props = self._path_to_properties(element, path) self._add_handlers(element, props, handler) def unregister_handler(self, handler): """ Unregister a handler from the registy. """ #self.logger.info('Unregistering handler') #self.logger.debug('Handler is %s' % handler) try: reverse = reversed(self._reverse[handler]) except KeyError: return for key in reverse: try: handlers = self._handlers[key] except KeyError: pass else: try: del handlers[handler] except KeyError: pass if not handlers: del self._handlers[key] del self._reverse[handler] @component.adapter(IElementChangeEvent) def on_element_change_event(self, event): #self.logger.info('Handling IElementChangeEvent') #self.logger.debug('Element is %s' % event.element) #self.logger.debug('Property is %s' % event.property) handlers = self._handlers.get((event.element, event.property)) if handlers: #log.debug('') #log.debug('Element change for %s %s [%s]' % (str(type(event)), event.element, event.property)) #if hasattr(event, 'old_value'): # log.debug(' old value: %s' % (event.old_value)) #if hasattr(event, 'new_value'): # log.debug(' new value: %s' % (event.new_value)) for handler in handlers.iterkeys(): try: handler(event) except Exception, e: self.logger.error('Problem executing handler %s' % handler, e) # Handle add/removal of handlers based on the kind of event # Filter out handlers that have no remaining properties if IAssociationSetEvent.providedBy(event): for handler, remainders in handlers.iteritems(): if remainders and event.old_value: for remainder in remainders: self._remove_handlers(event.old_value, remainder[0], handler) if remainders and event.new_value: for remainder in remainders: self._add_handlers(event.new_value, remainder, handler) elif IAssociationAddEvent.providedBy(event): for handler, remainders in handlers.iteritems(): for remainder in remainders: self._add_handlers(event.new_value, remainder, handler) elif IAssociationDeleteEvent.providedBy(event): for handler, remainders in handlers.iteritems(): for remainder in remainders: self._remove_handlers(event.old_value, remainder[0], handler) @component.adapter(IModelFactoryEvent) def on_model_loaded(self, event): #self.logger.info('Handling IModelFactoryEvent') #self.logger.debug('Event is %s' % event) for key, value in self._handlers.items(): for h, remainders in value.items(): for remainder in remainders: self._add_handlers(key[0], (key[1],) + remainder, h) # for h in self._reverse.iterkeys(): # h(None) # vim:sw=4:et:ai gaphor-0.17.2/gaphor/services/filemanager.py000066400000000000000000000404321220151210700210020ustar00rootroot00000000000000""" The file service is responsible for loading and saving the user data. """ from logging import getLogger import gtk from zope import interface, component from gaphor.interfaces import IService, IActionProvider, IServiceEvent from gaphor.core import _, inject, action, build_action_group from gaphor.storage import storage, verify from gaphor import UML from gaphor.misc.gidlethread import GIdleThread, Queue, QueueEmpty from gaphor.misc.errorhandler import error_handler from gaphor.misc.xmlwriter import XMLWriter from gaphor.ui.statuswindow import StatusWindow from gaphor.ui.questiondialog import QuestionDialog from gaphor.ui.filedialog import FileDialog DEFAULT_EXT = '.gaphor' MAX_RECENT = 10 class FileManagerStateChanged(object): """ Event class used to send state changes on the ndo Manager. """ interface.implements(IServiceEvent) def __init__(self, service): self.service = service class FileManager(object): """ The file service, responsible for loading and saving Gaphor models. """ interface.implements(IService, IActionProvider) component_registry = inject('component_registry') element_factory = inject('element_factory') main_window = inject('main_window') properties = inject('properties') logger = getLogger('FileManager') menu_xml = """ """ def __init__(self): """File manager constructor. There is no current filename yet.""" self._filename = None def init(self, app): """File manager service initialization. The app parameter is the main application object. This method builds the action group in the file menu. The list of recent Gaphor files is then updated in the file menu.""" self.action_group = build_action_group(self) for name, label in (('file-recent-files', '_Recent files'),): action = gtk.Action(name, label, None, None) action.set_property('hide-if-empty', False) self.action_group.add_action(action) for i in xrange(0, (MAX_RECENT-1)): action = gtk.Action('file-recent-%d' % i, None, None, None) action.set_property('visible', False) self.action_group.add_action(action) action.connect('activate', self.load_recent, i) self.update_recent_files() def shutdown(self): """Called when shutting down the file manager service.""" self.logger.info('Shutting down') def get_filename(self): """Return the current file name. This method is used by the filename property.""" return self._filename def set_filename(self, filename): """Sets the current file name. This method is used by the filename property. Setting the current filename will update the recent file list.""" self.logger.info('Setting current file') self.logger.debug('Filename is %s' % filename) if filename != self._filename: self._filename = filename self.update_recent_files(filename) filename = property(get_filename, set_filename) def get_recent_files(self): """Returns the recent file list from the properties service. This method is used by the recent_files property.""" try: return self.properties.get('recent-files', []) except component.interfaces.ComponentLookupError: return [] def set_recent_files(self, recent_files): """Updates the properties service with the supplied list of recent files. This method is used by the recent_files property.""" self.logger.info('Storing recent files') self.logger.debug('Recent files are %s' % recent_files) try: self.properties.set('recent-files', recent_files) except component.interfaces.ComponentLookupError: return recent_files = property(get_recent_files, set_recent_files) def update_recent_files(self, new_filename=None): """Updates the list of recent files. If the new_filename parameter is supplied, it is added to the list of recent files. The default recent file placeholder actions are hidden. The real actions are then built using the recent file list.""" self.logger.info('Updating recent files') self.logger.debug('New file is %s' % new_filename) recent_files = self.recent_files if new_filename and new_filename not in recent_files: recent_files.insert(0, new_filename) recent_files = recent_files[0:(MAX_RECENT-1)] self.recent_files = recent_files for i in xrange(0, (MAX_RECENT-1)): action = self.action_group.get_action('file-recent-%d' % i) action.set_property('visible', False) for i, filename in enumerate(recent_files): id = 'file-recent%d' % i action = self.action_group.get_action('file-recent-%d' % i) action.props.label = '_%d. %s' % (i+1, filename.replace('_', '__')) action.props.tooltip = 'Load %s.' % filename action.props.visible = True def load_recent(self, action, index): """Load the recent file at the specified index. This will trigger a FileManagerStateChanged event. The recent files are stored in the recent_files property.""" self.logger.info('Loading recent file') self.logger.debug('Action is %s' % action) self.logger.debug('Index is %s' % index) filename = self.recent_files[index] self.load(filename) self.component_registry.handle(FileManagerStateChanged(self)) def load(self, filename): """Load the Gaphor model from the supplied file name. A status window displays the loading progress. The load generator updates the progress queue. The loader is passed to a GIdleThread which executes the load generator. If loading is successful, the filename is set.""" self.logger.info('Loading file') self.logger.debug('Path is %s' % filename) queue = Queue() try: main_window = self.main_window status_window = StatusWindow(_('Loading...'),\ _('Loading model from %s') % filename,\ parent=main_window.window,\ queue=queue) except component.interfaces.ComponentLookupError: status_window = None try: loader = storage.load_generator(filename.encode('utf-8'), self.element_factory) worker = GIdleThread(loader, queue) worker.start() worker.wait() if worker.error: worker.reraise() self.filename = filename except: error_handler(message=_('Error while loading model from file %s') % filename) raise finally: if status_window is not None: status_window.destroy() def verify_orphans(self): """Verify that no orphaned elements are saved. This method checks of there are any orphan references in the element factory. If orphans are found, a dialog is displayed asking the user if it is OK to unlink them.""" orphans = verify.orphan_references(self.element_factory) if orphans: main_window = self.main_window dialog = QuestionDialog(_("The model contains some references"\ " to items that are not maintained."\ " Do you want to clean this before"\ " saving the model?"),\ parent=main_window.window) answer = dialog.answer dialog.destroy() if not answer: for orphan in orphans: orphan.unlink() def verify_filename(self, filename): """Verify that the supplied filename is using the proper default extension. If not, the extension is added to the filename and returned.""" self.logger.debug('Verifying file name') self.logger.debug('File name is %s' % filename) if not filename.endswith(DEFAULT_EXT): filename = filename + DEFAULT_EXT return filename def save(self, filename): """Save the current UML model to the specified file name. Before writing the model file, this will verify that there are no orphan references. It will also verify that the filename has the correct extension. A status window is displayed while the GIdleThread is executed. This thread actually saves the model.""" self.logger.info('Saving file') self.logger.debug('File name is %s' % filename) if not filename or not len(filename): return self.verify_orphans() filename = self.verify_filename(filename) main_window = self.main_window queue = Queue() status_window = StatusWindow(_('Saving...'),\ _('Saving model to %s') % filename,\ parent=main_window.window,\ queue=queue) try: with open(filename.encode('utf-8'), 'w') as out: saver = storage.save_generator(XMLWriter(out), self.element_factory) worker = GIdleThread(saver, queue) worker.start() worker.wait() if worker.error: worker.reraise() self.filename = filename except: error_handler(message=_('Error while saving model to file %s') % filename) raise finally: status_window.destroy() def _open_dialog(self, title): """Open a file chooser dialog to select a model file to open.""" filesel = gtk.FileChooserDialog(title=title, action=gtk.FILE_CHOOSER_ACTION_OPEN, buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, gtk.RESPONSE_OK)) filesel.set_transient_for(self.main_window.window) filter = gtk.FileFilter() filter.set_name("Gaphor models") filter.add_pattern("*.gaphor") filesel.add_filter(filter) filter = gtk.FileFilter() filter.set_name("All files") filter.add_pattern("*") filesel.add_filter(filter) if self.filename: filesel.set_current_name(self.filename) response = filesel.run() filename = filesel.get_filename() filesel.destroy() if not filename or response != gtk.RESPONSE_OK: return return filename @action(name='file-new', stock_id='gtk-new') def action_new(self): """The new model menu action. This action will create a new UML model. This will trigger a FileManagerStateChange event.""" element_factory = self.element_factory main_window = self.main_window if element_factory.size(): dialog = QuestionDialog(_("Opening a new model will flush the"\ " currently loaded model.\nAny changes"\ " made will not be saved. Do you want to"\ " continue?"),\ parent=main_window.window) answer = dialog.answer dialog.destroy() if not answer: return element_factory.flush() model = element_factory.create(UML.Package) model.name = _('New model') diagram = element_factory.create(UML.Diagram) diagram.package = model diagram.name= _('main') self.filename = None element_factory.notify_model() #main_window.select_element(diagram) #main_window.show_diagram(diagram) self.component_registry.handle(FileManagerStateChanged(self)) @action(name='file-new-template', label=_('New from template')) def action_new_from_template(self): """This menu action opens the new model from template dialog.""" filters = [{'name':_('Gaphor Models'), 'pattern':'*.gaphor'},\ {'name':_('All Files'), 'pattern':'*'}] file_dialog = FileDialog(_('New Gaphor Model From Template'),\ filters = filters) filename = file_dialog.selection file_dialog.destroy() log.debug(filename) if filename: self.load(filename) self.filename = None self.component_registry.handle(FileManagerStateChanged(self)) @action(name='file-open', stock_id='gtk-open') def action_open(self): """This menu action opens the standard model open dialog.""" filters = [{'name':_('Gaphor Models'), 'pattern':'*.gaphor'},\ {'name':_('All Files'), 'pattern':'*'}] file_dialog = FileDialog(_('Open Gaphor Model'),\ filters = filters) filename = file_dialog.selection file_dialog.destroy() log.debug(filename) if filename: self.load(filename) self.component_registry.handle(FileManagerStateChanged(self)) @action(name='file-save', stock_id='gtk-save') def action_save(self): """ Save the file. Depending on if there is a file name, either perform the save directly or present the user with a save dialog box. Returns True if the saving actually succeeded. """ filename = self.filename if filename: self.save(filename) self.component_registry.handle(FileManagerStateChanged(self)) return True else: return self.action_save_as() @action(name='file-save-as', stock_id='gtk-save-as') def action_save_as(self): """ Save the model in the element_factory by allowing the user to select a file name. Returns True if the saving actually happened. """ file_dialog = FileDialog(_('Save Gaphor Model As'),\ action='save',\ filename=self.filename) filename = file_dialog.selection file_dialog.destroy() if filename: self.save(filename) self.component_registry.handle(FileManagerStateChanged(self)) return True return False # vim:sw=4:et:ai gaphor-0.17.2/gaphor/services/helpservice.py000066400000000000000000000060331220151210700210400ustar00rootroot00000000000000"""About and help services. (help browser anyone?)""" from logging import getLogger import os import pkg_resources import gtk from zope import interface from gaphor.application import Application from gaphor.interfaces import IService, IActionProvider from gaphor.core import _, inject, action, build_action_group class HelpService(object): interface.implements(IService, IActionProvider) menu_xml = """ """ main_window = inject('main_window') logger = getLogger('HelpService') def __init__(self): pass def init(self, app): self.logger.info('Starting') self.action_group = build_action_group(self) def shutdown(self): self.logger.info('Shutting down') @action(name='help-about', stock_id='gtk-about') def about(self): logo_file = os.path.join(pkg_resources.get_distribution('gaphor').location, 'gaphor', 'ui', 'pixmaps', 'logo.png') logo = gtk.gdk.pixbuf_new_from_file(logo_file) version = Application.distribution.version about = gtk.Dialog(_('About Gaphor'), self.main_window.window, gtk.DIALOG_MODAL, (gtk.STOCK_OK, gtk.RESPONSE_OK)) about.set_default_response(gtk.RESPONSE_OK) vbox = about.vbox image = gtk.Image() image.set_from_pixbuf(logo) vbox.pack_start(image) notebook = gtk.Notebook() notebook.set_scrollable(True) notebook.set_border_width(4) notebook.set_tab_pos(gtk.POS_BOTTOM) vbox.pack_start(notebook) tab_vbox = gtk.VBox() def add_label(text, padding_x=0, padding_y=0): label = gtk.Label(text) label.set_property('use-markup', True) label.set_padding(padding_x, padding_y) label.set_justify(gtk.JUSTIFY_CENTER) tab_vbox.pack_start(label) add_label('version %s' % version) add_label('UML Modeling tool for GNOME', 8, 8) add_label('Copyright (c) 2001-2007 Arjan J. Molenaar', 8, 8) notebook.append_page(tab_vbox, gtk.Label(_('About'))) tab_vbox = gtk.VBox() add_label('This software is published\n' 'under the terms of the\n' 'GNU General Public License v2.\n' 'See the COPYING file for details.', 0, 8) notebook.append_page(tab_vbox, gtk.Label(_('License'))) tab_vbox = gtk.VBox() add_label('Gaphor is written by:\n' 'Arjan Molenaar\n' 'Artur Wroblewski\n' 'Jeroen Vloothuis') add_label('') notebook.append_page(tab_vbox, gtk.Label(_('Authors'))) vbox.show_all() about.run() about.destroy() # vim:sw=4:et:ai gaphor-0.17.2/gaphor/services/properties.py000066400000000000000000000130271220151210700207240ustar00rootroot00000000000000"""The properties module allows Gaphor properties to be saved to the local file system. These are things like preferences.""" import sys import os import pprint from zope import interface from gaphor.core import inject from logging import getLogger from gaphor.interfaces import IService from gaphas.decorators import async from gaphor.misc import get_user_data_dir class IPropertyChangeEvent(interface.Interface): """A property changed event has a name, an old value, and a new value.""" name = interface.Attribute("The property name") old_value = interface.Attribute("The property value before the change") new_value = interface.Attribute("The property value after the change") class PropertyChangeEvent(object): """This event is triggered any time a property is changed. This event holds the property name, the current value, and the new value.""" interface.implements(IPropertyChangeEvent) def __init__(self, name, old_value, new_value): self.name = name self.old_value = old_value self.new_value = new_value _no_default = object() class Properties(object): """The Properties class holds a collection of application wide properties. Properties are persisted to the local file system.""" interface.implements(IService) component_registry = inject('component_registry') logger = getLogger('Properties') def __init__(self, backend=None): """Constructor. Initialize the Gaphor application object, the dictionary for storing properties in memory, and the storage backend. This defaults to FileBackend""" self._resources = {} self._backend = backend or FileBackend() def init(self, app): """Initialize the properties service. This will load any stored properties from the file system.""" self._backend.load(self._resources) def shutdown(self): """Shutdown the properties service. This will ensure that all properties are saved.""" self._backend.save(self._resources) def __call__(self, key, default=_no_default): """Retrieve the specified property. If the property doesn't exist, the default parameter is returned. This defaults to _no_default.""" return self.get(key, default) def save(self): """Save all properties by calling save() on the properties storage backend.""" self._backend.save(self._resources) def _items(self): """Return an iterator for all stored properties.""" return self._resources.iteritems() def dump(self, stream=sys.stdout): """ TODO: define resources that are persistent (have to be saved and loaded. """ pprint.pprint(self._resources.items(), stream) def get(self, key, default=_no_default): """Locate a property. Resource should be the class of the resource to look for or a string. In case of a string the resource will be looked up in the GConf configuration.""" try: return self._resources[key] except KeyError: if default is _no_default: raise KeyError('No resource with name "%s"' % key) self.set(key, default) return default def set(self, key, value): """Set a property to a specific value. No smart things are done with classes and class names (like the resource() method does).""" resources = self._resources old_value = resources.get(key) if value != old_value: resources[key] = value self.component_registry.handle(PropertyChangeEvent(key, old_value, value)) self._backend.update(resources, key, value) class FileBackend(object): """Resource backend that stores data to a resource file ($HOME/.gaphor/resource).""" RESOURCE_FILE='resources' def __init__(self, datadir=get_user_data_dir()): """Constructor. Initialize the directory used for storing properties.""" self.datadir = datadir def get_filename(self, create=False): """Return the current file used to store Gaphor properties. If the created parameter is set to True, the file is created if it doesn't exist. This defaults to False.""" datadir = self.datadir if create and not os.path.exists(datadir): os.mkdir(datadir) return os.path.join(datadir, self.RESOURCE_FILE) def load(self, resource): """Load resources from a file. Resources are saved like you do with a dict().""" filename = self.get_filename() if os.path.exists(filename) and os.path.isfile(filename): with open(filename) as ifile: data = ifile.read() for key, value in eval(data).iteritems(): resource[key] = value def save(self, resource): """Save persist resources from the resources dictionary. @resource is the Resource instance @persistent is a list of persistent resource names. """ filename = self.get_filename(create=True) with open(filename, 'w') as ofile: pprint.pprint(resource, ofile) @async(single=True, timeout=500) def update(self, resource, key, value): """Update the properties file with any changes in the background.""" self.save(resource) gaphor-0.17.2/gaphor/services/propertydispatcher.py000066400000000000000000000045221220151210700224630ustar00rootroot00000000000000""" """ from zope import interface, component from logging import getLogger from gaphor.core import inject from gaphor.interfaces import IService from gaphor.UML.interfaces import IElementChangeEvent class PropertyDispatcher(object): """ The Propery Dispatcher allows classes to register on events originated by a specific element property, instead of the event type. This makes it easier for (for example) items to register on events from a specific class, rather than just AssociationChangeEvents, which could originate on a whole lot of classes. """ interface.implements(IService) logger = getLogger('PropertyDispatcher') component_registry = inject('component_registry') def __init__(self): # Handlers is a dict of sets self._handlers = {} def init(self, app): self.logger.info('Starting') self.component_registry.register_handler(self.on_element_change_event) def shutdown(self): self.logger.info('Shutting down') self.component_registry.unregister_handler(self.on_element_change_event) def register_handler(self, property, handler, exact=False): self.logger.info('Registring handler') self.logger.debug('Property is %s' % property) self.logger.debug('Handler is %s' % handler) try: self._handlers[property].add(handler) except KeyError: self._handlers[property] = set([handler]) def unregister_handler(self, property, handler): self.logger.info('Unregistering handler') self.logger.debug('Property is %s' % property) self.logger.debug('Handler is %s' % handler) s = self._handlers.get(property) if s: s.discard(handler) @component.adapter(IElementChangeEvent) def on_element_change_event(self, event): self.logger.info('Handling IElementChangeEvent') property = event.property self.logger.debug('Property is %s' % property) s = self._handlers.get(property) if not s: return for handler in s: try: handler(event) except Exception, e: log.error('problem executing handler %s' % handler, exc_info=True) # vim:sw=4:et:ai gaphor-0.17.2/gaphor/services/sanitizerservice.py000066400000000000000000000116631220151210700221250ustar00rootroot00000000000000""" The Sanitize module is dedicated to adapters (stuff) that keeps the model clean and in sync with diagrams. """ from zope import interface from zope import component from logging import getLogger from gaphor import UML from gaphor.UML.interfaces import IAssociationDeleteEvent, IAssociationSetEvent from gaphor.interfaces import IService from gaphor.core import inject from gaphor.diagram import items class SanitizerService(object): """ Does some background cleanup jobs, such as removing elements from the model that have no presentations (and should have some). """ interface.implements(IService) logger = getLogger('Sanitizer') component_registry = inject('component_registry') element_factory = inject('element_factory') property_dispatcher = inject('property_dispatcher') def __init__(self): pass def init(self, app=None): self.component_registry.register_handler(self._unlink_on_presentation_delete) self.component_registry.register_handler(self._unlink_on_stereotype_delete) self.component_registry.register_handler(self._unlink_on_extension_delete) self.component_registry.register_handler(self._disconnect_extension_end) def shutdown(self): self.component_registry.unregister_handler(self._unlink_on_presentation_delete) self.component_registry.unregister_handler(self._unlink_on_stereotype_delete) self.component_registry.unregister_handler(self._unlink_on_extension_delete) self.component_registry.unregister_handler(self._disconnect_extension_end) @component.adapter(IAssociationDeleteEvent) def _unlink_on_presentation_delete(self, event): """ Unlink the model element if no more presentations link to the `item`'s subject or the deleted item was the only item currently linked. """ self.logger.debug('Handling IAssociationDeleteEvent') #self.logger.debug('Property is %s' % event.property.name) #self.logger.debug('Element is %s' % event.element) #self.logger.debug('Old value is %s' % event.old_value) if event.property is UML.Element.presentation: old_presentation = event.old_value if old_presentation and not event.element.presentation: event.element.unlink() def perform_unlink_for_instances(self, st, meta): self.logger.debug('Performing unlink for instances') #self.logger.debug('Stereotype is %s' % st) #self.logger.debug('Meta is %s' % meta) inst = UML.model.find_instances(self.element_factory, st) for i in list(inst): for e in i.extended: if not meta or isinstance(e, meta): i.unlink() @component.adapter(IAssociationDeleteEvent) def _unlink_on_extension_delete(self, event): """ Remove applied stereotypes when extension is deleted. """ self.logger.debug('Handling IAssociationDeleteEvent') #self.logger.debug('Property is %s' % event.property.name) #self.logger.debug('Element is %s' % event.element) #self.logger.debug('Old value is %s' % event.old_value) if isinstance(event.element, UML.Extension) and \ event.property is UML.Association.memberEnd and \ event.element.memberEnd: p = event.element.memberEnd[0] ext = event.old_value if isinstance(p, UML.ExtensionEnd): p, ext = ext, p st = ext.type meta = p.type and getattr(UML, p.type.name) self.perform_unlink_for_instances(st, meta) @component.adapter(IAssociationSetEvent) def _disconnect_extension_end(self, event): self.logger.debug('Handling IAssociationSetEvent') #self.logger.debug('Property is %s' % event.property.name) #self.logger.debug('Element is %s' % event.element) #self.logger.debug('Old value is %s' % event.old_value) if event.property is UML.ExtensionEnd.type and event.old_value: ext = event.element p = ext.opposite if not p: return st = event.old_value meta = getattr(UML, p.type.name) self.perform_unlink_for_instances(st, meta) @component.adapter(IAssociationDeleteEvent) def _unlink_on_stereotype_delete(self, event): """ Remove applied stereotypes when stereotype is deleted. """ self.logger.debug('Handling IAssociationDeleteEvent') #self.logger.debug('Property is %s' % event.property) #self.logger.debug('Element is %s' % event.element) #self.logger.debug('Old value is %s' % event.old_value) if event.property is UML.InstanceSpecification.classifier: if isinstance(event.old_value, UML.Stereotype): event.element.unlink() # vim:sw=4:et:ai gaphor-0.17.2/gaphor/services/serviceregistry.py000066400000000000000000000045661220151210700217710ustar00rootroot00000000000000""" The service registry is the place where services can be registered and retrieved. Our good old NameServicer. """ from zope import interface, component from logging import getLogger from gaphor.interfaces import IService from gaphor.core import inject class ServiceRegistry(object): component_registry = inject('component_registry') logger = getLogger('ServiceRegistry') def __init__(self): self._uninitialized_services = {} def init(self, app=None): self.logger.info('Starting') def shutdown(self): self.logger.info('Shutting down') def load_services(self, services=None): """ Load services from resources. Services are registered as utilities in zope.component. Service should provide an interface gaphor.interfaces.IService. """ self.logger.info('Loading services') for ep in pkg_resources.iter_entry_points('gaphor.services'): cls = ep.load() if not IService.implementedBy(cls): raise 'MisConfigurationException', 'Entry point %s doesn''t provide IService' % ep.name if services is None or ep.name in services: srv = cls() self._uninitialized_services[ep.name] = srv def init_all_services(self): self.logger.info('Initializing services') while self._uninitialized_services: self.init_service(self._uninitialized_services.iterkeys().next()) def init_service(self, name): """ Initialize a not yet initialized service. Raises ComponentLookupError if the service has nor been found """ self.logger.info('Initializing service') self.logger.debug('Service name is %s' % name) try: srv = self._uninitialized_services.pop(name) except KeyError: raise component.ComponentLookupError(IService, name) else: srv.init(self) self.component_registry.register_utility(srv, IService, name) self.handle(ServiceInitializedEvent(name, srv)) return srv def get_service(self, name): try: return self.component_registry.get_utility(IService, name) except component.ComponentLookupError: return self.init_service(name) # vim: sw=4:et:ai gaphor-0.17.2/gaphor/services/tests/000077500000000000000000000000001220151210700173155ustar00rootroot00000000000000gaphor-0.17.2/gaphor/services/tests/__init__.py000066400000000000000000000000001220151210700214140ustar00rootroot00000000000000gaphor-0.17.2/gaphor/services/tests/test_actionmanager.py000066400000000000000000000005411220151210700235360ustar00rootroot00000000000000 import unittest class ActionManagerTestCase(unittest.TestCase): def setUp(self): pass def tearDown(self): pass def testLoadAll(self): from gaphor.application import Application Application.init() am = Application.get_service('action_manager') ui = am.ui_manager.get_ui() print ui gaphor-0.17.2/gaphor/services/tests/test_backupservice.py000066400000000000000000000043541220151210700235620ustar00rootroot00000000000000""" Test the backup service. """ import unittest from StringIO import StringIO from gaphor.storage import storage from gaphor.application import Application from gaphor.misc.xmlwriter import XMLWriter #class BackupServiceTestCase(unittest.TestCase): class BackupServiceTestCase: services = ['element_factory', 'backup_service'] def setUp(self): Application.init(services=self.services) self.element_factory = Application.get_service('element_factory') self.backup_service = Application.get_service('backup_service') def tearDown(self): Application.shutdown() def save_and_load(self, filename): factory = self.element_factory f = open(filename, 'r') storage.load(f, factory=self.element_factory) f.close() self.backup_service.backup() elements = map(factory.lookup, factory.keys()) orig = StringIO() storage.save(XMLWriter(orig), factory=self.element_factory) self.backup_service.restore() restored = map(factory.lookup, factory.keys()) assert len(elements) == len(restored) assert elements != restored copy = StringIO() storage.save(XMLWriter(copy), factory=self.element_factory) orig = orig.getvalue() copy = copy.getvalue() assert len(orig) == len(copy) #assert orig == copy, orig + ' != ' + copy def test_simple(self): self.save_and_load('test-diagrams/simple-items.gaphor') def test_namespace(self): self.save_and_load('test-diagrams/namespace.gaphor') def test_association(self): self.save_and_load('test-diagrams/association.gaphor') def test_interactions(self): self.save_and_load('test-diagrams/interactions.gaphor') def test_bicycle(self): self.save_and_load('test-diagrams/bicycle.gaphor') def test_line_align(self): self.save_and_load('test-diagrams/line-align.gaphor') # def test_gaphas_canvas(self): # self.save_and_load('../gaphas/gaphor-canvas.gaphor') def test_stereotype(self): self.save_and_load('test-diagrams/stereotype.gaphor') # vim: sw=4:et:ai gaphor-0.17.2/gaphor/services/tests/test_copyservice.py000066400000000000000000000061651220151210700232710ustar00rootroot00000000000000 from gaphor import UML from gaphor.diagram import items from gaphor.services.copyservice import CopyService from gaphor.application import Application from gaphor.tests.testcase import TestCase class CopyServiceTestCase(TestCase): services = TestCase.services + ['main_window', 'action_manager', 'properties', 'undo_manager', 'ui_manager'] def test_init(self): service = CopyService() service.init(Application) # No exception? ok! def test_copy(self): service = CopyService() service.init(Application) ef = self.element_factory diagram = ef.create(UML.Diagram) ci = diagram.create(items.CommentItem, subject=ef.create(UML.Comment)) service.copy([ci]) assert diagram.canvas.get_all_items() == [ ci ] service.paste(diagram) assert len(diagram.canvas.get_all_items()) == 2, diagram.canvas.get_all_items() def test_copy_named_item(self): service = CopyService() service.init(Application) ef = self.element_factory diagram = ef.create(UML.Diagram) c = diagram.create(items.ClassItem, subject=ef.create(UML.Class)) c.subject.name = 'Name' import gobject self.assertEquals(0, gobject.main_depth()) diagram.canvas.update_now() i = list(diagram.canvas.get_all_items()) self.assertEquals(1, len(i), i) self.assertEquals('Name', i[0]._name.text) service.copy([c]) assert diagram.canvas.get_all_items() == [ c ] service.paste(diagram) i = diagram.canvas.get_all_items() self.assertEquals(2, len(i), i) diagram.canvas.update_now() self.assertEquals('Name', i[0]._name.text) self.assertEquals('Name', i[1]._name.text) def _skip_test_copy_paste_undo(self): """ Test if copied data is undoable. """ from gaphor.storage.verify import orphan_references service = CopyService() service.init(Application) # Setting the stage: ci1 = self.create(items.ClassItem, UML.Class) ci2 = self.create(items.ClassItem, UML.Class) a = self.create(items.AssociationItem) self.connect(a, a.head, ci1) self.connect(a, a.tail, ci2) self.assertTrue(a.subject) self.assertTrue(a.head_end.subject) self.assertTrue(a.tail_end.subject) # The act: copy and paste, perform undo afterwards service.copy([ci1, ci2, a]) service.paste(self.diagram) all_items = list(self.diagram.canvas.get_all_items()) self.assertEquals(6, len(all_items)) self.assertFalse(orphan_references(self.element_factory)) self.assertSame(all_items[0].subject, all_items[3].subject) self.assertSame(all_items[1].subject, all_items[4].subject) self.assertSame(all_items[2].subject, all_items[5].subject) undo_manager = self.get_service('undo_manager') undo_manager.undo_transaction() self.assertEquals(3, len(self.diagram.canvas.get_all_items())) self.assertFalse(orphan_references(self.element_factory)) # vim:sw=4:et:ai gaphor-0.17.2/gaphor/services/tests/test_diagramexportmanager.py000066400000000000000000000012301220151210700251230ustar00rootroot00000000000000 import unittest from gaphor.application import Application from gaphor.services.diagramexportmanager import DiagramExportManager class DiagramExportManagerTestCase(unittest.TestCase): def setUp(self): Application.init(services=['main_window', 'properties', 'element_factory', 'diagram_export_manager', 'action_manager', 'ui_manager' ]) def shutDown(self): Application.shutdown() def test_init(self): des = DiagramExportManager() des.init(None) def test_init_from_application(self): Application.get_service('diagram_export_manager') Application.get_service('main_window') # vim:sw=4:et:ai gaphor-0.17.2/gaphor/services/tests/test_elementdispatcher.py000066400000000000000000000324311220151210700244310ustar00rootroot00000000000000 from gaphor.tests import TestCase from gaphor import UML from gaphor.application import Application from gaphor.services.elementdispatcher import ElementDispatcher class ElementDispatcherTestCase(TestCase): def setUp(self): super(ElementDispatcherTestCase, self).setUp() self.events = [] self.dispatcher = ElementDispatcher() self.dispatcher.init(Application) def tearDown(self): self.dispatcher.shutdown() super(ElementDispatcherTestCase, self).tearDown() def _handler(self, event): self.events.append(event) def test_register_handler(self): dispatcher = self.dispatcher element = UML.Class() dispatcher.register_handler(self._handler, element, 'ownedOperation.parameter.name') assert len(dispatcher._handlers) == 1 assert dispatcher._handlers.keys()[0] == (element, UML.Class.ownedOperation) # Add some properties: # 1: element.ownedOperation = UML.Operation() # 2: p = element.ownedOperation[0].formalParameter = UML.Parameter() # 3: p.name = 'func' dispatcher.register_handler(self._handler, element, 'ownedOperation.parameter.name') self.assertEquals(3, len(self.events)) self.assertEquals(3, len(dispatcher._handlers)) def test_register_handler_twice(self): """ Multiple registrations have no effect. """ dispatcher = self.dispatcher element = UML.Class() # Add some properties: element.ownedOperation = UML.Operation() p = element.ownedOperation[0].formalParameter = UML.Parameter() dispatcher.register_handler(self._handler, element, 'ownedOperation.parameter.name') n_handlers = len(dispatcher._handlers) self.assertEquals(0, len(self.events)) dispatcher.register_handler(self._handler, element, 'ownedOperation.parameter.name') self.assertEquals(n_handlers, len(dispatcher._handlers)) dispatcher.register_handler(self._handler, element, 'ownedOperation.parameter.name') self.assertEquals(n_handlers, len(dispatcher._handlers)) dispatcher.register_handler(self._handler, element, 'ownedOperation.parameter.name') self.assertEquals(n_handlers, len(dispatcher._handlers)) p.name = 'func' self.assertEquals(1, len(self.events)) def test_unregister_handler(self): # First some setup: dispatcher = self.dispatcher element = UML.Class() o = element.ownedOperation = UML.Operation() p = element.ownedOperation[0].formalParameter = UML.Parameter() p.name = 'func' dispatcher.register_handler(self._handler, element, 'ownedOperation.parameter.name') assert len(dispatcher._handlers) == 3 assert dispatcher._handlers[element, UML.Class.ownedOperation] assert dispatcher._handlers[o, UML.Operation.parameter] assert dispatcher._handlers[p, UML.Parameter.name] dispatcher.unregister_handler(self._handler) assert len(dispatcher._handlers) == 0, dispatcher._handlers assert len(dispatcher._reverse) == 0, dispatcher._reverse #assert dispatcher._handlers.keys()[0] == (element, UML.Class.ownedOperation) # Should not fail here too: dispatcher.unregister_handler(self._handler) def test_notification(self): """ Test notifications with Class object. """ dispatcher = self.dispatcher element = UML.Class() o = element.ownedOperation = UML.Operation() p = element.ownedOperation[0].formalParameter = UML.Parameter() p.name = 'func' dispatcher.register_handler(self._handler, element, 'ownedOperation.parameter.name') assert len(dispatcher._handlers) == 3 assert not self.events element.ownedOperation = UML.Operation() assert len(self.events) == 1, self.events assert len(dispatcher._handlers) == 4 p.name = 'othername' assert len(self.events) == 2, self.events del element.ownedOperation[o] assert len(dispatcher._handlers) == 2 def test_notification_2(self): """ Test notifications with Transition object. """ dispatcher = self.dispatcher element = UML.Transition() g = element.guard = UML.Constraint() dispatcher.register_handler(self._handler, element, 'guard.specification') assert len(dispatcher._handlers) == 2 assert not self.events assert (element.guard, UML.Constraint.specification) in dispatcher._handlers.keys(), dispatcher._handlers.keys() g.specification = 'x' assert len(self.events) == 1, self.events element.guard = UML.Constraint() assert len(self.events) == 2, self.events assert len(dispatcher._handlers) == 2, len(dispatcher._handlers) assert (element.guard, UML.Constraint.specification) in dispatcher._handlers.keys() def test_notification_of_change(self): """ Test notifications with Transition object. """ dispatcher = self.dispatcher element = UML.Transition() g = element.guard = UML.Constraint() dispatcher.register_handler(self._handler, element, 'guard.specification') assert len(dispatcher._handlers) == 2 assert not self.events g.specification = 'x' assert len(self.events) == 1, self.events element.guard = UML.Constraint() assert len(self.events) == 2, self.events def test_notification_with_composition(self): """ Test unregister with composition. Use Class.ownedOperation.precondition. """ dispatcher = self.dispatcher element = UML.Class() o = element.ownedOperation = UML.Operation() p = element.ownedOperation[0].precondition = UML.Constraint() p.name = 'func' dispatcher.register_handler(self._handler, element, 'ownedOperation.precondition.name') assert len(dispatcher._handlers) == 3 assert not self.events del element.ownedOperation[o] assert len(dispatcher._handlers) == 1 def test_notification_with_incompatible_elements(self): """ Test unregister with composition. Use Class.ownedOperation.precondition. """ dispatcher = self.dispatcher element = UML.Transition() g = element.guard = UML.Constraint() dispatcher.register_handler(self._handler, element, 'guard.specification') assert len(dispatcher._handlers) == 2 assert not self.events assert (element.guard, UML.Constraint.specification) in dispatcher._handlers.keys(), dispatcher._handlers.keys() g.specification = 'x' assert len(self.events) == 1, self.events g.specification = 'a' assert len(self.events) == 2, self.events from gaphor.UML import Element from gaphor.UML.properties import association from gaphor.services.elementdispatcher import EventWatcher class A(Element): pass A.one = association('one', A, lower=0, upper=1, composite=True) A.two = association('two', A, lower=0, upper=2, composite=True) class ElementDispatcherAsServiceTestCase(TestCase): services = TestCase.services + ['element_dispatcher'] def setUp(self): super(ElementDispatcherAsServiceTestCase, self).setUp() self.events = [] self.dispatcher = Application.get_service('element_dispatcher') def tearDown(self): super(ElementDispatcherAsServiceTestCase, self).tearDown() def _handler(self, event): self.events.append(event) def test_notification(self): """ Test notifications with Class object. """ dispatcher = self.dispatcher element = UML.Class() o = element.ownedOperation = UML.Operation() p = element.ownedOperation[0].formalParameter = UML.Parameter() p.name = 'func' dispatcher.register_handler(self._handler, element, 'ownedOperation.parameter.name') assert len(dispatcher._handlers) == 3 assert not self.events element.ownedOperation = UML.Operation() assert len(self.events) == 1, self.events assert len(dispatcher._handlers) == 4 p.name = 'othername' assert len(self.events) == 2, self.events del element.ownedOperation[o] assert len(dispatcher._handlers) == 2 def test_association_notification(self): """ Test notifications with Class object. Tricky case where no events are fired. """ dispatcher = self.dispatcher element = UML.Association() p1 = element.memberEnd = UML.Property() p2 = element.memberEnd = UML.Property() assert len(element.memberEnd) == 2 print element.memberEnd dispatcher.register_handler(self._handler, element, 'memberEnd.name') assert len(dispatcher._handlers) == 3, len(dispatcher._handlers) assert not self.events p1.name = 'foo' assert len(self.events) == 1, (self.events, dispatcher._handlers) assert len(dispatcher._handlers) == 3 p1.name = 'othername' assert len(self.events) == 2, self.events p1.name = 'othername' assert len(self.events) == 2, self.events def test_association_notification_complex(self): """ Test notifications with Class object. Tricky case where no events are fired. """ dispatcher = self.dispatcher element = UML.Association() p1 = element.memberEnd = UML.Property() p2 = element.memberEnd = UML.Property() p1.lowerValue = '0' p1.upperValue = '1' p2.lowerValue = '1' p2.upperValue = '*' assert len(element.memberEnd) == 2 print element.memberEnd base = 'memberEnd.' dispatcher.register_handler(self._handler, element, base + 'name') dispatcher.register_handler(self._handler, element, base + 'aggregation') dispatcher.register_handler(self._handler, element, base + 'classifier') dispatcher.register_handler(self._handler, element, base + 'lowerValue') dispatcher.register_handler(self._handler, element, base + 'upperValue') assert len(dispatcher._handlers) == 11, len(dispatcher._handlers) assert not self.events p1.name = 'foo' assert len(self.events) == 1, (self.events, dispatcher._handlers) assert len(dispatcher._handlers) == 11 p1.name = 'othername' assert len(self.events) == 2, self.events def test_diamond(self): """ Test diamond shaped dependencies a -> b -> c, a -> b' -> c """ a = A() watcher = EventWatcher(a, self._handler) watcher.watch('one.two.one.two') #watcher.watch('one.one.one.one') watcher.register_handlers() a.one = A() a.one.two = A() a.one.two = A() a.one.two[0].one = A() a.one.two[1].one = a.one.two[0].one a.one.two[0].one.two = A() self.assertEquals(6, len(self.events)) a.unlink() watcher.unregister_handlers() watcher.unregister_handlers() def test_big_diamond(self): """ Test diamond shaped dependencies a -> b -> c -> d, a -> b' -> c' -> d """ a = A() watcher = EventWatcher(a, self._handler) watcher.watch('one.two.one.two') #watcher.watch('one.one.one.one') watcher.register_handlers() a.one = A() a.one.two = A() a.one.two = A() a.one.two[0].one = A() a.one.two[1].one = A() a.one.two[0].one.two = A() a.one.two[1].one.two = a.one.two[0].one.two[0] self.assertEquals(7, len(self.events)) a.unlink() watcher.unregister_handlers() watcher.unregister_handlers() self.assertEquals(0, len(self.dispatcher._handlers)) def test_braking_big_diamond(self): """ Test diamond shaped dependencies a -> b -> c -> d, a -> b' -> c' -> d """ a = A() watcher = EventWatcher(a, self._handler) watcher.watch('one.two.one.two') #watcher.watch('one.one.one.one') watcher.register_handlers() a.one = A() a.one.two = A() a.one.two = A() a.one.two[0].one = A() a.one.two[1].one = A() a.one.two[0].one.two = A() a.one.two[1].one.two = a.one.two[0].one.two[0] self.assertEquals(7, len(self.events)) self.assertEquals(6, len(self.dispatcher._handlers)) del a.one.two[0].one #a.unlink() watcher.unregister_handlers() watcher.unregister_handlers() self.assertEquals(0, len(self.dispatcher._handlers)) def test_cyclic(self): """ Test cyclic dependency a -> b -> c -> a. """ a = A() watcher = EventWatcher(a, self._handler) watcher.watch('one.two.one.two') #watcher.watch('one.one.one.one') watcher.register_handlers() a.one = A() a.one.two = A() a.one.two = A() a.one.two[0].one = a self.assertEquals(4, len(self.events)) #a.one.two[0].one.two = A() #a.one.two[0].one.two = A() a.unlink() self.assertEquals(1, len(self.dispatcher._handlers)) # vim: sw=4:et:ai gaphor-0.17.2/gaphor/services/tests/test_filemanager.py000066400000000000000000000026721220151210700232070ustar00rootroot00000000000000 import unittest from gaphor.application import Application from gaphor.services.filemanager import FileManager class FileManagerTestCase(unittest.TestCase): def setUp(self): Application.init(services=['file_manager', 'element_factory', 'properties', 'main_window', 'action_manager', 'ui_manager']) self.recent_files_backup = Application.get_service('properties').get('recent-files') def tearDown(self): Application.get_service('properties').set('recent-files', self.recent_files_backup) Application.shutdown() def test_recent_files(self): fileman = Application.get_service('file_manager') properties = Application.get_service('properties') # ensure the recent_files list is empty: properties.set('recent-files', []) fileman.update_recent_files() for i in range(0, 9): a = fileman.action_group.get_action('file-recent-%d' % i) assert a assert a.get_property('visible') == False, '%s, %d' % (a.get_property('visible'), i) fileman.filename = 'firstfile' a = fileman.action_group.get_action('file-recent-%d' % 0) assert a assert a.get_property('visible') == True assert a.props.label == '_1. firstfile', a.props.label for i in range(1, 9): a = fileman.action_group.get_action('file-recent-%d' % i) assert a assert a.get_property('visible') == False gaphor-0.17.2/gaphor/services/tests/test_properties.py000066400000000000000000000027431220151210700231300ustar00rootroot00000000000000 from unittest import TestCase from gaphor.services.properties import Properties, FileBackend import tempfile #class MockApplication(object): # # def __init__(self): # self.events = [] # # def handle(self, event): # self.events.append(event) # class TestProperties(TestCase): def setUp(self): self.tmpdir = tempfile.mkdtemp() backend = FileBackend(self.tmpdir) self.properties = Properties(backend) # self.app = MockApplication() self.properties.init(self.app) def shutDown(self): self.properties.shutdown() os.remove(os.path.join(self.tmpdir, FileBackend.RESOURCE_FILE)) os.rmdir(self.tmpdir) # def test_properties(self): # prop = self.properties # assert not self.app.events # # prop.set('test1', 2) # assert len(self.app.events) == 1, self.app.events # event = self.app.events[0] # assert 'test1' == event.name # assert None is event.old_value # assert 2 is event.new_value # assert 2 == prop('test1') # # prop.set('test1', 2) # assert len(self.app.events) == 1 # # prop.set('test1', 'foo') # assert len(self.app.events) == 2 # event = self.app.events[1] # assert 'test1' == event.name # assert 2 is event.old_value # assert 'foo' is event.new_value # assert 'foo' == prop('test1') # # assert 3 == prop('test2', 3) # assert 3 == prop('test2', 4) # vim:sw=4:et:ai gaphor-0.17.2/gaphor/services/tests/test_sanitizerservice.py000066400000000000000000000130301220151210700243140ustar00rootroot00000000000000 from gaphor.tests import TestCase from gaphor import UML from gaphor.diagram import items class SanitizerServiceTest(TestCase): services = TestCase.services + [ 'sanitizer' ] def test_presentation_delete(self): """ Remove element if the last instance of an item is deleted. """ ef = self.element_factory klassitem = self.create(items.ClassItem, UML.Class) klass = klassitem.subject assert klassitem.subject.presentation[0] is klassitem assert klassitem.canvas # Delete presentation here: klassitem.unlink() assert not klassitem.canvas assert klass not in self.element_factory.lselect() def test_stereotype_attribute_delete(self): """ This test was applicable to the Sanitizer service, but is now resolved by a tweak in the data model (Instances diagram). """ factory = self.element_factory create = factory.create # Set the stage #metaklass = create(UML.Class) #metaklass.name = 'Class' klass = create(UML.Class) stereotype = create(UML.Stereotype) st_attr = self.element_factory.create(UML.Property) stereotype.ownedAttribute = st_attr #ext = UML.model.create_extension(factory, metaklass, stereotype) # Apply stereotype to class and create slot instspec = UML.model.apply_stereotype(factory, klass, stereotype) slot = UML.model.add_slot(factory, instspec, st_attr) # Now, what happens if the attribute is deleted: self.assertTrue(st_attr in stereotype.ownedMember) self.assertTrue(slot in instspec.slot) st_attr.unlink() self.assertEquals([], list(stereotype.ownedMember)) self.assertEquals([], list(instspec.slot)) def test_extension_disconnect(self): factory = self.element_factory create = factory.create # Set the stage metaklass = create(UML.Class) metaklass.name = 'Class' klass = create(UML.Class) stereotype = create(UML.Stereotype) st_attr = self.element_factory.create(UML.Property) stereotype.ownedAttribute = st_attr ext = UML.model.create_extension(factory, metaklass, stereotype) # Apply stereotype to class and create slot instspec = UML.model.apply_stereotype(factory, klass, stereotype) slot = UML.model.add_slot(factory, instspec, st_attr) self.assertTrue(stereotype in klass.appliedStereotype[:].classifier) # Causes set event del ext.ownedEnd.type self.assertEquals([], list(klass.appliedStereotype)) def test_extension_deletion(self): factory = self.element_factory create = factory.create # Set the stage metaklass = create(UML.Class) metaklass.name = 'Class' klass = create(UML.Class) stereotype = create(UML.Stereotype) st_attr = self.element_factory.create(UML.Property) stereotype.ownedAttribute = st_attr ext = UML.model.create_extension(factory, metaklass, stereotype) # Apply stereotype to class and create slot instspec = UML.model.apply_stereotype(factory, klass, stereotype) slot = UML.model.add_slot(factory, instspec, st_attr) self.assertTrue(stereotype in klass.appliedStereotype[:].classifier) ext.unlink() self.assertEquals([], list(klass.appliedStereotype)) def test_extension_deletion_with_2_metaclasses(self): factory = self.element_factory create = factory.create # Set the stage metaklass = create(UML.Class) metaklass.name = 'Class' metaiface = create(UML.Class) metaiface.name = 'Interface' klass = create(UML.Class) iface = create(UML.Interface) stereotype = create(UML.Stereotype) st_attr = self.element_factory.create(UML.Property) stereotype.ownedAttribute = st_attr ext1 = UML.model.create_extension(factory, metaklass, stereotype) ext2 = UML.model.create_extension(factory, metaiface, stereotype) # Apply stereotype to class and create slot instspec1 = UML.model.apply_stereotype(factory, klass, stereotype) instspec2 = UML.model.apply_stereotype(factory, iface, stereotype) slot = UML.model.add_slot(factory, instspec1, st_attr) self.assertTrue(stereotype in klass.appliedStereotype[:].classifier) self.assertTrue(klass in self.element_factory) ext1.unlink() self.assertEquals([], list(klass.appliedStereotype)) self.assertTrue(klass in self.element_factory) self.assertEquals([instspec2], list(iface.appliedStereotype)) def test_stereotype_deletion(self): factory = self.element_factory create = factory.create # Set the stage metaklass = create(UML.Class) metaklass.name = 'Class' klass = create(UML.Class) stereotype = create(UML.Stereotype) st_attr = self.element_factory.create(UML.Property) stereotype.ownedAttribute = st_attr ext = UML.model.create_extension(factory, metaklass, stereotype) # Apply stereotype to class and create slot instspec = UML.model.apply_stereotype(factory, klass, stereotype) slot = UML.model.add_slot(factory, instspec, st_attr) self.assertTrue(stereotype in klass.appliedStereotype[:].classifier) stereotype.unlink() self.assertEquals([], list(klass.appliedStereotype)) # vim:sw=4:et:ai gaphor-0.17.2/gaphor/services/tests/test_undomanager.py000066400000000000000000000265461220151210700232430ustar00rootroot00000000000000""" Test the UndoManager. """ from gaphor.tests.testcase import TestCase from gaphor.services.undomanager import UndoManager from gaphor.transaction import Transaction from gaphor.application import Application class TestUndoManager(TestCase): def test_transactions(self): undo_manager = UndoManager() undo_manager.init(None) assert not undo_manager._current_transaction #undo_manager.begin_transaction() tx = Transaction() #assert undo_manager._transaction_depth == 1 assert undo_manager._current_transaction current = undo_manager._current_transaction #undo_manager.begin_transaction() tx2 = Transaction() #assert undo_manager._transaction_depth == 2 #assert undo_manager._transaction_depth == 1 assert undo_manager._current_transaction is current #undo_manager.commit_transaction() tx2.commit() #assert undo_manager._transaction_depth == 1 assert undo_manager._current_transaction is current #undo_manager.commit_transaction() tx.commit() #assert undo_manager._transaction_depth == 0 assert undo_manager._current_transaction is None undo_manager.shutdown() def test_not_in_transaction(self): undo_manager = UndoManager() undo_manager.init(Application) action = object() undo_manager.add_undo_action(action) assert undo_manager._current_transaction is None undo_manager.begin_transaction() undo_manager.add_undo_action(action) assert undo_manager._current_transaction assert undo_manager.can_undo() assert len(undo_manager._current_transaction._actions) == 1 undo_manager.shutdown() def test_actions(self): undone = [ 0 ] def undo_action(undone=undone): #print 'undo_action called' undone[0] = 1 undo_manager.add_undo_action(redo_action) def redo_action(undone=undone): #print 'redo_action called' undone[0] = -1 undo_manager.add_undo_action(undo_action) undo_manager = UndoManager() undo_manager.init(Application) #undo_manager.begin_transaction() tx = Transaction() undo_manager.add_undo_action(undo_action) assert undo_manager._current_transaction assert undo_manager.can_undo() assert len(undo_manager._current_transaction._actions) == 1 #undo_manager.commit_transaction() tx.commit() undo_manager.undo_transaction() assert not undo_manager.can_undo(), undo_manager._undo_stack assert undone[0] == 1, undone undone[0] = 0 assert undo_manager.can_redo(), undo_manager._redo_stack undo_manager.redo_transaction() assert not undo_manager.can_redo() assert undo_manager.can_undo() assert undone[0] == -1, undone undo_manager.shutdown() def test_undo_attribute(self): import types from gaphor.UML.properties import attribute from gaphor.UML.element import Element undo_manager = UndoManager() undo_manager.init(Application) class A(Element): attr = attribute('attr', types.StringType, default='default') a = A() assert a.attr == 'default', a.attr undo_manager.begin_transaction() a.attr = 'five' undo_manager.commit_transaction() assert a.attr == 'five' undo_manager.undo_transaction() assert a.attr == 'default', a.attr undo_manager.redo_transaction() assert a.attr == 'five' undo_manager.shutdown() def test_undo_association_1_x(self): from gaphor.UML.properties import association from gaphor.UML.element import Element undo_manager = UndoManager() undo_manager.init(Application) class A(Element): pass class B(Element): pass A.one = association('one', B, 0, 1, opposite='two') B.two = association('two', A, 0, 1) a = A() b = B() assert a.one is None assert b.two is None undo_manager.begin_transaction() a.one = b undo_manager.commit_transaction() assert a.one is b assert b.two is a assert len(undo_manager._undo_stack) == 1 assert len(undo_manager._undo_stack[0]._actions) == 2, undo_manager._undo_stack[0]._actions undo_manager.undo_transaction() assert a.one is None assert b.two is None assert undo_manager.can_redo() assert len(undo_manager._redo_stack) == 1 assert len(undo_manager._redo_stack[0]._actions) == 2, undo_manager._redo_stack[0]._actions undo_manager.redo_transaction() assert len(undo_manager._undo_stack) == 1 assert len(undo_manager._undo_stack[0]._actions) == 2, undo_manager._undo_stack[0]._actions assert b.two is a assert a.one is b undo_manager.shutdown() def test_undo_association_1_n(self): from gaphor.UML.properties import association from gaphor.UML.element import Element undo_manager = UndoManager() undo_manager.init(Application) class A(Element): pass class B(Element): pass A.one = association('one', B, lower=0, upper=1, opposite='two') B.two = association('two', A, lower=0, upper='*', opposite='one') a1 = A() a2 = A() b1 = B() b2 = B() undo_manager.begin_transaction() b1.two = a1 undo_manager.commit_transaction() assert a1 in b1.two assert b1 is a1.one assert len(undo_manager._undo_stack) == 1 assert len(undo_manager._undo_stack[0]._actions) == 2, undo_manager._undo_stack[0]._actions undo_manager.undo_transaction() assert len(b1.two) == 0 assert a1.one is None assert undo_manager.can_redo() assert len(undo_manager._redo_stack) == 1 assert len(undo_manager._redo_stack[0]._actions) == 2, undo_manager._redo_stack[0]._actions undo_manager.redo_transaction() assert a1 in b1.two assert b1 is a1.one undo_manager.begin_transaction() b1.two = a2 undo_manager.commit_transaction() assert a1 in b1.two assert a2 in b1.two assert b1 is a1.one assert b1 is a2.one undo_manager.shutdown() def test_element_factory_undo(self): from gaphor.UML.element import Element ef = self.element_factory ef.flush() undo_manager = UndoManager() undo_manager.init(Application) undo_manager.begin_transaction() p = ef.create(Element) assert undo_manager._current_transaction assert undo_manager._current_transaction._actions assert undo_manager.can_undo() undo_manager.commit_transaction() assert undo_manager.can_undo() assert ef.size() == 1 undo_manager.undo_transaction() assert not undo_manager.can_undo() assert undo_manager.can_redo() assert ef.size() == 0 undo_manager.redo_transaction() assert undo_manager.can_undo() assert not undo_manager.can_redo() assert ef.size() == 1 assert ef.lselect()[0] is p undo_manager.shutdown() def test_element_factory_rollback(self): from gaphor.UML.element import Element ef = self.element_factory ef.flush() undo_manager = UndoManager() undo_manager.init(Application) undo_manager.begin_transaction() p = ef.create(Element) assert undo_manager._current_transaction assert undo_manager._current_transaction._actions assert undo_manager.can_undo() undo_manager.rollback_transaction() assert not undo_manager.can_undo() assert ef.size() == 0 undo_manager.shutdown() def test_uml_associations(self): from zope import component from gaphor.UML.interfaces import IAssociationChangeEvent from gaphor.UML.properties import association, derivedunion from gaphor.UML import Element class A(Element): is_unlinked = False def unlink(self): self.is_unlinked = True Element.unlink(self) A.a1 = association('a1', A, upper=1) A.a2 = association('a2', A, upper=1) A.b1 = association('b1', A, upper='*') A.b2 = association('b2', A, upper='*') A.b3 = association('b3', A, upper=1) A.derived_a = derivedunion('derived_a', 0, 1, A.a1, A.a2) A.derived_b = derivedunion('derived_b', 0, '*', A.b1, A.b2, A.b3) events = [] @component.adapter(IAssociationChangeEvent) def handler(event, events=events): events.append(event) compreg = Application.get_service('component_registry') compreg.register_handler(handler) try: a = A() undo_manager = UndoManager() undo_manager.init(Application) undo_manager.begin_transaction() a.a1 = A() undo_manager.commit_transaction() assert len(events) == 1, events assert events[0].property is A.a1 assert undo_manager.can_undo() undo_manager.undo_transaction() assert not undo_manager.can_undo() assert undo_manager.can_redo() assert len(events) == 2, events assert events[1].property is A.a1 finally: compreg.unregister_handler(handler) undo_manager.shutdown() def test_redo_stack(self): from gaphor.UML.element import Element undo_manager = UndoManager() undo_manager.init(Application) undo_manager.begin_transaction() ef = self.element_factory ef.flush() p = ef.create(Element) assert undo_manager._current_transaction assert undo_manager._current_transaction._actions assert undo_manager.can_undo() undo_manager.commit_transaction() assert undo_manager.can_undo() assert ef.size() == 1, ef.size() with Transaction(): q = ef.create(Element) assert undo_manager.can_undo() assert not undo_manager.can_redo() assert ef.size() == 2 undo_manager.undo_transaction() assert undo_manager.can_undo() self.assertEquals(1, len(undo_manager._undo_stack)) self.assertEquals(1, len(undo_manager._redo_stack)) assert undo_manager.can_redo() assert ef.size() == 1 undo_manager.undo_transaction() assert not undo_manager.can_undo() assert undo_manager.can_redo() self.assertEquals(0, len(undo_manager._undo_stack)) self.assertEquals(2, len(undo_manager._redo_stack)) #assert ef.size() == 0 undo_manager.redo_transaction() self.assertEquals(1, len(undo_manager._undo_stack)) self.assertEquals(1, len(undo_manager._redo_stack)) assert undo_manager.can_undo() assert undo_manager.can_redo() assert ef.size() == 1 undo_manager.redo_transaction() assert undo_manager.can_undo() assert not undo_manager.can_redo() assert ef.size() == 2 assert p in ef.lselect() undo_manager.shutdown() # vim:sw=4:et:ai gaphor-0.17.2/gaphor/services/undomanager.py000066400000000000000000000334611220151210700210340ustar00rootroot00000000000000# vim:sw=4:et: """ Undo management for Gaphor. Undoing and redoing actions is managed through the UndoManager. An undo action should be a callable object (called with no arguments). An undo action should return a callable object that acts as redo function. If None is returned the undo action is considered to be the redo action as well. NOTE: it would be nice to use actions in conjunction with functools.partial. """ from zope import interface, component from gaphas import state from gaphor.core import inject from logging import getLogger from gaphor.interfaces import IService, IServiceEvent, IActionProvider from gaphor.event import TransactionBegin, TransactionCommit, TransactionRollback from gaphor.transaction import Transaction, transactional from gaphor.UML.event import ElementCreateEvent, ElementDeleteEvent, \ ModelFactoryEvent, AssociationSetEvent, \ AssociationAddEvent, AssociationDeleteEvent from gaphor.UML.interfaces import IElementDeleteEvent, \ IAttributeChangeEvent, IModelFactoryEvent from gaphor.action import action, build_action_group from gaphor.event import ActionExecuted class ActionStack(object): """ A transaction. Every action that is added between a begin_transaction() and a commit_transaction() call is recorded in a transaction, so it can be played back when a transaction is executed. This executing a transaction has the effect of performing the actions recorded, which will typically undo actions performed by the user. """ def __init__(self): self._actions = [] def add(self, action): self._actions.append(action) def can_execute(self): return self._actions and True or False @transactional def execute(self): self._actions.reverse() for action in self._actions: try: action() except Exception, e: log.error('Error while undoing action %s' % action, exc_info=True) class UndoManagerStateChanged(object): """ Event class used to send state changes on the ndo Manager. """ interface.implements(IServiceEvent) def __init__(self, service): self.service = service class UndoManager(object): """ Simple transaction manager for Gaphor. This transaction manager supports nested transactions. The Undo manager sports an undo and a redo stack. Each stack contains a set of actions that can be executed, just by calling them (e.i action()) If something is returned by an action, that is considered the callable to be used to undo or redo the last performed action. """ interface.implements(IService, IActionProvider) menu_xml = """ """ component_registry = inject('component_registry') logger = getLogger('UndoManager') def __init__(self): self._undo_stack = [] self._redo_stack = [] self._stack_depth = 20 self._current_transaction = None self.action_group = build_action_group(self) def init(self, app): self.logger.info('Starting') self.component_registry.register_handler(self.reset) self.component_registry.register_handler(self.begin_transaction) self.component_registry.register_handler(self.commit_transaction) self.component_registry.register_handler(self.rollback_transaction) self.component_registry.register_handler(self._action_executed) self._register_undo_handlers() self._action_executed() def shutdown(self): self.logger.info('Shutting down') self.component_registry.unregister_handler(self.reset) self.component_registry.unregister_handler(self.begin_transaction) self.component_registry.unregister_handler(self.commit_transaction) self.component_registry.unregister_handler(self.rollback_transaction) self.component_registry.unregister_handler(self._action_executed) self._unregister_undo_handlers() def clear_undo_stack(self): self._undo_stack = [] self._current_transaction = None def clear_redo_stack(self): del self._redo_stack[:] @component.adapter(IModelFactoryEvent) def reset(self, event=None): self.clear_redo_stack() self.clear_undo_stack() self._action_executed() @component.adapter(TransactionBegin) def begin_transaction(self, event=None): """ Add an action to the current transaction """ assert not self._current_transaction self._current_transaction = ActionStack() def add_undo_action(self, action): """ Add an action to undo. An action """ if self._current_transaction: self._current_transaction.add(action) self.component_registry.handle(UndoManagerStateChanged(self)) # TODO: should this be placed here? self._action_executed() @component.adapter(TransactionCommit) def commit_transaction(self, event=None): assert self._current_transaction if self._current_transaction.can_execute(): # Here: self.clear_redo_stack() self._undo_stack.append(self._current_transaction) while len(self._undo_stack) > self._stack_depth: del self._undo_stack[0] self._current_transaction = None self.component_registry.handle(UndoManagerStateChanged(self)) self._action_executed() @component.adapter(TransactionRollback) def rollback_transaction(self, event=None): """ Roll back the transaction we're in. """ assert self._current_transaction # Store stacks undo_stack = list(self._undo_stack) errorous_tx = self._current_transaction self._current_transaction = None try: with Transaction(): try: errorous_tx.execute() except Exception, e: self.logger.error('Could not roolback transaction') self.logger.error(e) finally: # Discard all data collected in the rollback "transaction" self._undo_stack = undo_stack self.component_registry.handle(UndoManagerStateChanged(self)) self._action_executed() def discard_transaction(self): self._current_transaction = None self.component_registry.handle(UndoManagerStateChanged(self)) self._action_executed() @action(name='edit-undo', stock_id='gtk-undo', accel='z') def undo_transaction(self): if not self._undo_stack: return if self._current_transaction: log.warning('Trying to undo a transaction, while in a transaction') self.commit_transaction() transaction = self._undo_stack.pop() # Store stacks undo_stack = list(self._undo_stack) redo_stack = list(self._redo_stack) self._undo_stack = [] try: with Transaction(): transaction.execute() finally: # Restore stacks and put latest tx on the redo stack self._redo_stack = redo_stack if self._undo_stack: self._redo_stack.extend(self._undo_stack) self._undo_stack = undo_stack while len(self._redo_stack) > self._stack_depth: del self._redo_stack[0] self.component_registry.handle(UndoManagerStateChanged(self)) self._action_executed() @action(name='edit-redo', stock_id='gtk-redo', accel='y') def redo_transaction(self): if not self._redo_stack: return transaction = self._redo_stack.pop() redo_stack = list(self._redo_stack) try: with Transaction(): transaction.execute() finally: self._redo_stack = redo_stack self.component_registry.handle(UndoManagerStateChanged(self)) self._action_executed() def in_transaction(self): return self._current_transaction is not None def can_undo(self): return bool(self._current_transaction or self._undo_stack) def can_redo(self): return bool(self._redo_stack) @component.adapter(ActionExecuted) def _action_executed(self, event=None): self.action_group.get_action('edit-undo').set_sensitive(self.can_undo()) self.action_group.get_action('edit-redo').set_sensitive(self.can_redo()) ## ## Undo Handlers ## def _gaphas_undo_handler(self, event): self.add_undo_action(lambda: state.saveapply(*event)); def _register_undo_handlers(self): self.logger.debug('Registering undo handlers') self.component_registry.register_handler(self.undo_create_event) self.component_registry.register_handler(self.undo_delete_event) self.component_registry.register_handler(self.undo_attribute_change_event) self.component_registry.register_handler(self.undo_association_set_event) self.component_registry.register_handler(self.undo_association_add_event) self.component_registry.register_handler(self.undo_association_delete_event) # # Direct revert-statements from gaphas to the undomanager state.observers.add(state.revert_handler) state.subscribers.add(self._gaphas_undo_handler) def _unregister_undo_handlers(self): self.logger.debug('Unregistering undo handlers') self.component_registry.unregister_handler(self.undo_create_event) self.component_registry.unregister_handler(self.undo_delete_event) self.component_registry.unregister_handler(self.undo_attribute_change_event) self.component_registry.unregister_handler(self.undo_association_set_event) self.component_registry.unregister_handler(self.undo_association_add_event) self.component_registry.unregister_handler(self.undo_association_delete_event) state.observers.discard(state.revert_handler) state.subscribers.discard(self._gaphas_undo_handler) @component.adapter(ElementCreateEvent) def undo_create_event(self, event): factory = event.service # A factory is not always present, e.g. for DiagramItems if not factory: return element = event.element def _undo_create_event(): try: del factory._elements[element.id] except KeyError: pass # Key was probably already removed in an unlink call self.component_registry.handle(ElementDeleteEvent(factory, element)) self.add_undo_action(_undo_create_event) @component.adapter(IElementDeleteEvent) def undo_delete_event(self, event): factory = event.service # A factory is not always present, e.g. for DiagramItems if not factory: return element = event.element assert factory, 'No factory defined for %s (%s)' % (element, factory) def _undo_delete_event(): factory._elements[element.id] = element self.component_registry.handle(ElementCreateEvent(factory, element)) self.add_undo_action(_undo_delete_event) @component.adapter(IAttributeChangeEvent) def undo_attribute_change_event(self, event): attribute = event.property element = event.element value = event.old_value def _undo_attribute_change_event(): attribute._set(element, value) self.add_undo_action(_undo_attribute_change_event) @component.adapter(AssociationSetEvent) def undo_association_set_event(self, event): association = event.property element = event.element value = event.old_value #print 'got new set event', association, element, value def _undo_association_set_event(): #print 'undoing action', element, value # Tell the assoctaion it should not need to let the opposite # side connect (it has it's own signal) association._set(element, value, from_opposite=True) self.add_undo_action(_undo_association_set_event) @component.adapter(AssociationAddEvent) def undo_association_add_event(self, event): association = event.property element = event.element value = event.new_value def _undo_association_add_event(): #print 'undoing action', element, value # Tell the assoctaion it should not need to let the opposite # side connect (it has it's own signal) association._del(element, value, from_opposite=True) self.add_undo_action(_undo_association_add_event) @component.adapter(AssociationDeleteEvent) def undo_association_delete_event(self, event): association = event.property element = event.element value = event.old_value def _undo_association_delete_event(): #print 'undoing action', element, value # Tell the assoctaion it should not need to let the opposite # side connect (it has it's own signal) association._set(element, value, from_opposite=True) self.add_undo_action(_undo_association_delete_event) # vim:sw=4:et:ai gaphor-0.17.2/gaphor/storage/000077500000000000000000000000001220151210700157745ustar00rootroot00000000000000gaphor-0.17.2/gaphor/storage/__init__.py000066400000000000000000000001161220151210700201030ustar00rootroot00000000000000""" Storage module, for loading and saving of model files. """ # vim:sw=4:et gaphor-0.17.2/gaphor/storage/parser.py000066400000000000000000000254311220151210700176470ustar00rootroot00000000000000"""Gaphor file reader. This module contains only one interesting function: parse(filename) which returns a dictionary of ID -> pairs. A parsed_object is one of element, canvas or canvasitem. A parsed_object contains values and references. values is a dictionary of name -> value pairs. A value contains a string with the value read from the model file. references contains a list of name -> reference_list pairs, where reference_list is a list of ID's. element objects can contain a canvas object (which is the case for elements of type Diagram). Each element has a type, which corresponds to a class name in the gaphor.UML module. Elements also have a unique ID, by which they are referered to in the dictionary returned by parse(). canvas does not have an ID, but contains a list of canvasitems (which is a list of real canvasitem objects, not references). canvasitem objects can also contain a list of canvasitems (canvasitems can be nested). They also have a unique ID by which they have been added to the dictionary returned by parse(). Each canvasitem has a type, which maps to a class name in the gaphor.diagram module. The generator parse_generator(filename, loader) may be used if the loading takes a long time. The yielded values are the percentage of the file read. """ __all__ = [ 'parse', 'ParserException' ] import os import types from xml.sax import handler from cStringIO import InputType from gaphor.misc.odict import odict class base(object): """Simple base class for element, canvas and canvasitem. """ def __init__(self): self.values = { } self.references = { } def __getattr__(self, key): return self[key] def __getitem__(self, key): try: return self.values[key] except: return self.references[key] def get(self, key): try: return self.__getitem__(key) except: return None class element(base): def __init__(self, id, type): base.__init__(self) self.id = id self.type = type self.canvas = None class canvas(base): def __init__(self): base.__init__(self) self.canvasitems = [] class canvasitem(base): def __init__(self, id, type): base.__init__(self) self.id = id self.type = type self.canvasitems = [] XMLNS='http://gaphor.sourceforge.net/model' class ParserException(Exception): pass # Loader state: [ ROOT, # Expect 'gaphor' element GAPHOR, # Expect UML classes (tag name is the UML class name) ELEMENT, # Expect properties of UML object DIAGRAM, # Expect properties of Diagram object + canvas CANVAS, # Expect canvas properties + tags ITEM, # Expect item attributes and nested items ATTR, # Reading contents of an attribute (such as a or ) VAL, # Redaing contents of a tag REFLIST, # In a REF # Reading contents of a tag ] = xrange(10) class GaphorLoader(handler.ContentHandler): """Create a list of elements. an element may contain a canvas and a canvas may contain canvas items. Each element can have values and references to other elements. """ def __init__(self): handler.ContentHandler.__init__(self) # make sure all variables are initialized: self.startDocument() def push(self, element, state): """Add an element to the item stack. """ self.__stack.append((element, state)) def pop(self): """Return the last item on the stack. The item is removed from the stack. """ return self.__stack.pop()[0] def peek(self, depth=1): """Return the last item on the stack. The item is not removed. """ return self.__stack[-1 * depth][0] def state(self): """Return the current state of the parser. """ try: return self.__stack[-1][1] except IndexError: return ROOT def endDTD(self): pass def startDocument(self): """Start of document: all our attributes are initialized. """ self.version = None self.gaphor_version = None self.elements = odict() # map id: element/canvasitem self.__stack = [] self.text = '' def endDocument(self): if len(self.__stack) != 0: raise ParserException, 'Invalid XML document.' def startElement(self, name, attrs): self.text = '' state = self.state() # Read a element class. The name of the tag is the class name: if state == GAPHOR: id = attrs['id'] e = element(id, name) assert id not in self.elements.keys(), '%s already defined' % (id)#, self.elements[id]) self.elements[id] = e self.push(e, name == 'Diagram' and DIAGRAM or ELEMENT) # Special treatment for the tag in a Diagram: elif state == DIAGRAM and name == 'canvas': c = canvas() self.peek().canvas = c self.push(c, CANVAS) # Items in a canvas are referenced by the tag: elif state in (CANVAS, ITEM) and name == 'item': id = attrs['id'] c = canvasitem(id, attrs['type']) assert id not in self.elements.keys(), '%s already defined' % id self.elements[id] = c self.peek().canvasitems.append(c) self.push(c, ITEM) # Store the attribute name on the stack, so we can use it later # to store the , or content: elif state in (ELEMENT, DIAGRAM, CANVAS, ITEM): # handle 'normal' attributes self.push(name, ATTR) # Reference list: elif state == ATTR and name == 'reflist': self.push(self.peek(), REFLIST) # Reference with multiplicity 1: elif state == ATTR and name == 'ref': n = self.peek(1) # Fetch the element instance from the stack r = self.peek(2).references[n] = attrs['refid'] self.push(None, REF) # Reference with multiplicity *: elif state == REFLIST and name == 'ref': n = self.peek(1) # Fetch the element instance from the stack r = self.peek(3).references refid = attrs['refid'] try: r[n].append(refid) except KeyError: r[n] = [refid] self.push(None, REF) # We need to get the text within the tag: elif state == ATTR and name == 'val': self.push(None, VAL) # The tag is the toplevel tag: elif state == ROOT and name == 'gaphor': assert attrs['version'] in ('3.0',) self.version = attrs['version'] self.gaphor_version = attrs.get('gaphor-version') if not self.gaphor_version: self.gaphor_version = attrs.get('gaphor_version') self.push(None, GAPHOR) else: raise ParserException, 'Invalid XML: tag <%s> not known (state = %s)' % (name, state) def endElement(self, name): # Put the text on the value if self.state() == VAL: # Two levels up: the attribute name n = self.peek(2) # Three levels up: the element instance (element or canvasitem) self.peek(3).values[n] = self.text self.pop() def startElementNS(self, name, qname, attrs): if not name[0] or name[0] == XMLNS: a = { } for key, val in attrs.items(): a[key[1]] = val self.startElement(name[1], a) def endElementNS(self, name, qname): if not name[0] or name[0] == XMLNS: self.endElement(name[1]) def characters(self, content): """Read characters.""" self.text = self.text + content def parse(filename): """Parse a file and return a dictionary ID:element/canvasitem. """ loader = GaphorLoader() for x in parse_generator(filename, loader): pass return loader.elements def parse_generator(filename, loader): """The generator based version of parse(). parses the file filename and load it with ContentHandler loader. """ assert isinstance(loader, GaphorLoader), 'loader should be a GaphorLoader' from xml.sax import make_parser parser = make_parser() parser.setFeature(handler.feature_namespaces, 1) parser.setContentHandler(loader) for percentage in parse_file(filename, parser): yield percentage class ProgressGenerator(object): """A generator that yields the progress of taking from a file input object and feeding it into an output object. The supplied file object is neither opened not closed by this generator. The file object is assumed to already be opened for reading and that it will be closed elsewhere.""" def __init__(self, input, output, block_size=512): """Initialize the progress generator. The input parameter is a file object. The ouput parameter is usually a SAX parser but can be anything that implements a feed() method. The block size is the size of each block that is read from the input.""" self.input = input self.output = output self.block_size = block_size if isinstance(self.input, types.FileType): self.file_size = os.fstat(self.input.fileno())[6] elif isinstance(self.input, InputType): self.file_size = len(self.input.getvalue()) self.input.reset() def __iter__(self): """Return a generator that yields the progress of reading data from the input and feeding it into the output. The progress yielded in each iteration is the percentage of data read, relative to the to input file size.""" block = self.input.read(self.block_size) read_size = len(block) while block: self.output.feed(block) block = self.input.read(self.block_size) read_size += len(block) yield (read_size * 100) / self.file_size def parse_file(filename, parser): """Parse the supplied file using the supplied parser. The parser parameter should be a GaphorLoader instance. The filename parameter can be an open file descriptor instance or the name of a file. The progress percentage of the parser is yielded.""" is_fd = True if isinstance(filename, (types.FileType, InputType)): file_obj = filename else: is_fd = False file_obj = open(filename, 'rb') for progress in ProgressGenerator(file_obj, parser): yield progress parser.close() if not is_fd: file_obj.close() # vim:sw=4:et:ai gaphor-0.17.2/gaphor/storage/storage.py000066400000000000000000000756371220151210700200340ustar00rootroot00000000000000""" Load and save Gaphor models to Gaphors own XML format. Three functions are exported: load(filename) load a model from a file save(filename) store the current model in a file """ from cStringIO import StringIO, InputType from xml.sax.saxutils import escape import types import sys import os.path import gc import gaphas from gaphor import UML from gaphor.UML.collection import collection from gaphor.UML.elementfactory import ElementChangedEventBlocker from gaphor import diagram from gaphor.storage import parser from gaphor.application import Application, NotInitializedError from gaphor.diagram import items from gaphor.i18n import _ #from gaphor.misc.xmlwriter import XMLWriter # import gaphor.adapters.connectors package, so diagram items can find # their appropriate connectors (i.e. diagram line requires this); # this allows external scripts to load diagram properly... or should # this be done using services? i.e. request storage service, which should # depend on connectors service? from gaphor.adapters import connectors __all__ = [ 'load', 'save' ] FILE_FORMAT_VERSION = '3.0' NAMESPACE_MODEL = 'http://gaphor.sourceforge.net/model' def save(writer=None, factory=None, status_queue=None): for status in save_generator(writer, factory): if status_queue: status_queue(status) def save_generator(writer, factory): """ Save the current model using @writer, which is a gaphor.misc.xmlwriter.XMLWriter instance. """ # Maintain a set of id's, one for elements, one for references. # Write only to file if references is a subset of elements def save_reference(name, value): """ Save a value as a reference to another element in the model. This applies to both UML as well as canvas items. """ # Save a reference to the object: if value.id: writer.startElement(name, {}) writer.startElement('ref', { 'refid': value.id }) writer.endElement('ref') writer.endElement(name) def save_collection(name, value): """ Save a list of references. """ if len(value) > 0: writer.startElement(name, {}) writer.startElement('reflist', {}) for v in value: #save_reference(name, v) if v.id: writer.startElement('ref', { 'refid': v.id }) writer.endElement('ref') writer.endElement('reflist') writer.endElement(name) def save_value(name, value): """ Save a value (attribute). """ if value is not None: writer.startElement(name, {}) writer.startElement('val', {}) if isinstance(value, types.StringTypes): writer.characters(value) elif isinstance(value, bool): # Write booleans as 0/1. writer.characters(str(int(value))) else: writer.characters(str(value)) writer.endElement('val') writer.endElement(name) def save_element(name, value): """ Save attributes and references from items in the gaphor.UML module. A value may be a primitive (string, int), a gaphor.UML.collection (which contains a list of references to other UML elements) or a gaphas.Canvas (which contains canvas items). """ #log.debug('saving element: %s|%s %s' % (name, value, type(value))) if isinstance (value, (UML.Element, gaphas.Item)): save_reference(name, value) elif isinstance(value, collection): save_collection(name, value) elif isinstance(value, gaphas.Canvas): writer.startElement('canvas', {}) value.save(save_canvasitem) writer.endElement('canvas') else: save_value(name, value) def save_canvasitem(name, value, reference=False): """ Save attributes and references in a gaphor.diagram.* object. The extra attribute reference can be used to force UML """ #log.debug('saving canvasitem: %s|%s %s' % (name, value, type(value))) if isinstance(value, collection) or \ (isinstance(value, (list, tuple)) and reference == True): save_collection(name, value) elif reference: save_reference(name, value) elif isinstance(value, gaphas.Item): writer.startElement('item', { 'id': value.id, 'type': value.__class__.__name__ }) value.save(save_canvasitem) # save subitems for child in value.canvas.get_children(value): save_canvasitem(None, child) writer.endElement('item') elif isinstance(value, UML.Element): save_reference(name, value) else: save_value(name, value) writer.startDocument() writer.startPrefixMapping('', NAMESPACE_MODEL) writer.startElementNS((NAMESPACE_MODEL, 'gaphor'), None, { (NAMESPACE_MODEL, 'version'): FILE_FORMAT_VERSION, (NAMESPACE_MODEL, 'gaphor-version'): Application.distribution.version }) size = factory.size() n = 0 for e in factory.values(): clazz = e.__class__.__name__ assert e.id writer.startElement(clazz, { 'id': str(e.id) }) e.save(save_element) writer.endElement(clazz) n += 1 if n % 25 == 0: yield (n * 100) / size #writer.endElement('gaphor') writer.endElementNS((NAMESPACE_MODEL, 'gaphor'), None) writer.endPrefixMapping('') writer.endDocument() def load_elements(elements, factory, status_queue=None): for status in load_elements_generator(elements, factory): if status_queue: status_queue(status) def load_elements_generator(elements, factory, gaphor_version=None): """ Load a file and create a model if possible. Exceptions: IOError, ValueError. """ # TODO: restructure loading code, first load model, then add canvas items log.debug(_('Loading %d elements...') % len(elements)) # The elements are iterated three times: size = len(elements) * 3 def update_status_queue(_n=[0]): n = _n[0] = _n[0] + 1 if n % 30 == 0: return (n * 100) / size #log.info('0%') # Fix version inconsistencies version_0_6_2(elements, factory, gaphor_version) version_0_7_2(elements, factory, gaphor_version) version_0_9_0(elements, factory, gaphor_version) version_0_14_0(elements, factory, gaphor_version) version_0_15_0_pre(elements, factory, gaphor_version) version_0_17_0(elements, factory, gaphor_version) #log.debug("Still have %d elements" % len(elements)) # First create elements and canvas items in the factory # The elements are stored as attribute 'element' on the parser objects: def create_canvasitems(canvas, canvasitems, parent=None): """ Canvas is a read gaphas.Canvas, items is a list of parser.canvasitem's """ for item in canvasitems: cls = getattr(items, item.type) item.element = diagram.create_as(cls, item.id) canvas.add(item.element, parent=parent) assert canvas.get_parent(item.element) is parent create_canvasitems(canvas, item.canvasitems, parent=item.element) for id, elem in elements.items(): st = update_status_queue() if st: yield st if isinstance(elem, parser.element): cls = getattr(UML, elem.type) #log.debug('Creating UML element for %s (%s)' % (elem, elem.id)) elem.element = factory.create_as(cls, id) if elem.canvas: elem.element.canvas.block_updates = True create_canvasitems(elem.element.canvas, elem.canvas.canvasitems) elif not isinstance(elem, parser.canvasitem): raise ValueError, 'Item with id "%s" and type %s can not be instantiated' % (id, type(elem)) # load attributes and create references: for id, elem in elements.items(): st = update_status_queue() if st: yield st # Ensure that all elements have their element instance ready... assert hasattr(elem, 'element') # load attributes and references: for name, value in elem.values.items(): try: elem.element.load(name, value) except: log.error('Loading value %s (%s) for element %s failed.' % (name, value, elem.element)) raise for name, refids in elem.references.items(): if type(refids) == list: for refid in refids: try: ref = elements[refid] except: raise ValueError, 'Invalid ID for reference (%s) for element %s.%s' % (refid, elem.type, name) else: try: elem.element.load(name, ref.element) except: log.error('Loading %s.%s with value %s failed' % (type(elem.element).__name__, name, ref.element.id)) raise else: try: ref = elements[refids] except: raise ValueError, 'Invalid ID for reference (%s)' % refids else: try: elem.element.load(name, ref.element) except: log.error('Loading %s.%s with value %s failed' % (type(elem.element).__name__, name, ref.element.id)) raise # Fix version inconsistencies version_0_5_2(elements, factory, gaphor_version) version_0_7_1(elements, factory, gaphor_version) version_0_15_0_post(elements, factory, gaphor_version) # Before version 0.7.2 there was only decision node (no merge nodes). # This node could have many incoming and outgoing flows (edges). # According to UML specification decision node has no more than one # incoming node. # # Now, we have implemented merge node, which can have many incoming # flows. We also support combining of decision and merge nodes as # described in UML specification. # # Data model, loaded from file, is updated automatically, so there is # no need for special function. for d in factory.select(lambda e: isinstance(e, UML.Diagram)): # update_now() is implicitly called when lock is released d.canvas.block_updates = False # do a postload: for id, elem in elements.items(): st = update_status_queue() if st: yield st elem.element.postload() factory.notify_model() def load(filename, factory, status_queue=None): """ Load a file and create a model if possible. Optionally, a status queue function can be given, to which the progress is written (as status_queue(progress)). """ for status in load_generator(filename, factory): if status_queue: status_queue(status) def load_generator(filename, factory): """ Load a file and create a model if possible. This function is a generator. It will yield values from 0 to 100 (%) to indicate its progression. """ if isinstance(filename, (file, InputType)): log.info('Loading file from file descriptor') else: log.info('Loading file %s' % os.path.basename(filename)) try: # Use the incremental parser and yield the percentage of the file. loader = parser.GaphorLoader() for percentage in parser.parse_generator(filename, loader): pass if percentage: yield percentage / 2 else: yield percentage elements = loader.elements gaphor_version = loader.gaphor_version #elements = parser.parse(filename) #yield 100 except Exception, e: log.error('File could no be parsed', exc_info=True) raise try: component_registry = Application.get_service('component_registry') except NotInitializedError: component_registry = None try: factory.flush() gc.collect() log.info("Read %d elements from file" % len(elements)) if component_registry: component_registry.register_subscription_adapter(ElementChangedEventBlocker) try: for percentage in load_elements_generator(elements, factory, gaphor_version): if percentage: yield percentage / 2 + 50 else: yield percentage except Exception, e: raise finally: if component_registry: component_registry.unregister_subscription_adapter(ElementChangedEventBlocker) gc.collect() yield 100 except Exception, e: log.info('file %s could not be loaded' % filename) raise def version_lower_than(gaphor_version, version): """ if version_lower_than('0.3.0', (0, 15, 0)): ... """ parts = gaphor_version.split('.') try: return tuple(map(int, parts)) < version except ValueError: # We're having a -dev, -pre, -beta, -alpha or whatever version parts = parts[:-1] return tuple(map(int, parts)) <= version def version_0_15_0_pre(elements, factory, gaphor_version): """ Fix association navigability UML metamodel to comply with UML 2.2 using Association.navigableOwnedEnd among others (see model factory for details). Convert tagged values into comment items as tagged values are no longer supported by UML specification (stereotypes attributes shall be used instead). Comment item contains information about used tagged values. It means, that full conversion of tagged values into stereotype attributes is not supported at the moment. This function is called before the actual elements are constructed. """ ATTRS = set(['class_', 'interface_', 'actor', 'useCase', 'owningAssociation']) if version_lower_than(gaphor_version, (0, 14, 99)): # update associations values = (v for v in elements.values() if type(v) is parser.element and v.type == 'Property' and 'association' in v.references) for et in values: # get association assoc = elements[et.references['association']] attrs = set(set(ATTRS) & set(et.references)) if attrs: assert len(attrs) == 1 attr = attrs.pop() if attr == 'owningAssociation': assoc.references['ownedEnd'].remove(et.id) if not assoc.references['ownedEnd']: del assoc.references['ownedEnd'] elif attr in ('actor', 'useCase'): if 'navigableOwnedEnd' not in assoc.references: assoc.references['navigableOwnedEnd'] = [] assoc.references['navigableOwnedEnd'].append(et.id) el = elements[et.references[attr]] el.references['ownedAttribute'].remove(et.id) if not el.references['ownedAttribute']: del el.references['ownedAttribute'] del et.references[attr] else: if 'ownedEnd' not in assoc.references: assoc.references['ownedEnd'] = [] assoc.references['ownedEnd'].append(et.id) # - get rid of tagged values for e in elements.values(): if 'taggedValue' in e.references: taggedvalue = [elements[i].values['value'] for i in e.references['taggedValue'] if elements[i].values.get('value')] #convert_tagged_value(e, elements, factory) if taggedvalue: e.taggedvalue = taggedvalue # Remove obsolete elements for t in e.references['taggedValue']: del elements[t] del e.references['taggedValue'] # - rename EventOccurrence to MessageOccurrenceSpecification values = (v for v in elements.values() if type(v) is parser.element and v.type == 'EventOccurrence') for et in values: et.type = 'MessageOccurrenceSpecification' def version_0_15_0_post(elements, factory, gaphor_version): """ Part two: create stereotypes and what more for the elements that have a taggedvalue property. """ def update_elements(element): e = elements[element.id] = parser.element(element.id, element.__class__.__name__) e.element = element if version_lower_than(gaphor_version, (0, 14, 99)): stereotypes = {} profile = None for e in elements.values(): if hasattr(e, 'taggedvalue'): if not profile: profile = factory.create(UML.Profile) profile.name = 'version 0.15 conversion' update_elements(profile) st = stereotypes.get(e.type) if not st: st = stereotypes[e.type] = factory.create(UML.Stereotype) st.name = 'Tagged' st.package = profile update_elements(st) cl = factory.create(UML.Class) cl.name = str(e.type) cl.package = profile update_elements(cl) ext = UML.model.extend_with_stereotype(factory, cl, st) update_elements(ext) for me in ext.memberEnd: update_elements(me) # Create instance specification for the stereotype: instspec = UML.model.apply_stereotype(factory, e.element, st) update_elements(instspec) def create_slot(key, val): for attr in st.ownedAttribute: if attr.name == key: break else: attr = st.ownedAttribute = factory.create(UML.Property) attr.name = str(key) update_elements(attr) slot = UML.model.add_slot(factory, instspec, attr) slot.value.value = str(val) update_elements(slot) tviter = iter(e.taggedvalue or []) for tv in tviter: try: try: key, val = tv.split('=', 1) key = key.strip() except ValueError: log.info('Tagged value "%s" has no key=value format, trying key_value ' % tv) try: key, val = tv.split(' ', 1) key = key.strip() except ValueError: # Fallback, deal with it as if it were a boolean key = tv.strip() val = 'true' # This syntax is used with the UML meta model: if key in ('subsets', 'redefines'): rest = ', '.join(tviter) val = ', '.join([val, rest]) if rest else val val = val.replace('\n', ' ') log.info('Special case: UML metamodel "%s %s"' % (key, val)) create_slot(key, val) except Exception, e: log.warning('Unable to process tagged value "%s" as key=value pair' % tv, exc_info=True) def find(messages, attr): occurrences = set(getattr(m, attr) for m in messages if hasattr(m, attr) and getattr(m, attr)) assert len(occurrences) <= 1 if occurrences: return occurrences.pop() else: return None def update_msg(msg, sl, rl): if sl: s = factory.create(UML.MessageOccurrenceSpecification) s.covered = sl m.sendEvent = s if rl: r = factory.create(UML.MessageOccurrenceSpecification) r.covered = rl m.receiveEvent = r for e in elements.values(): if e.type == 'MessageItem': msg = e.element send = msg.subject.sendEvent receive = msg.subject.receiveEvent if not send: send = find(msg._messages.keys(), 'sendEvent') if not receive: receive = find(msg._messages.keys(), 'receiveEvent') if not send: send = find(msg._inverted_messages.keys(), 'reveiveEvent') if not receive: receive = find(msg._inverted_messages.keys(), 'sendEvent') sl = send.covered if send else None rl = receive.covered if receive else None for m in msg._messages: update_msg(m, sl, rl) for m in msg._inverted_messages: update_msg(m, rl, sl) msg.subject.sendEvent = send msg.subject.receiveEvent = receive def convert_tagged_value(element, elements, factory): """ Convert ``element.taggedValue`` to something supported by the UML 2.2 model (since Gaphor version 0.15). For each item having a Tagged value a Stereotype is linked. This is done like this: item -> InstanceSpecification -> Stereotype Each tagged value will be replaced by a Slot: item -> InstanceSpecification -> Slot -> Attribute -> Stereotype """ import uuid diagrams = [e for e in elements.values() if e.type == 'Diagram'] presentation = element.get('presentation') or [] tv = [elements[i] for i in element.references['taggedValue']] for et in presentation: et = elements[et] m = eval(et.values['matrix']) w = eval(et.values['width']) tagged = 'upgrade to stereotype attributes' \ ' following tagged values:\n%s' % '\n'.join(t.values['value'] for t in tv) item = parser.canvasitem(str(uuid.uuid1()), 'CommentItem') comment = parser.element(str(uuid.uuid1()), 'Comment') item.references['subject'] = comment.id item.values['matrix'] = str((1.0, 0.0, 0.0, 1.0, m[4] + w + 10.0, m[5])) comment.references['presentation'] = [item.id] comment.values['body'] = tagged elements[item.id] = item elements[comment.id] = comment # Where to place the comment? How to find the Diagram? for d in diagrams: for ci in d.canvas.canvasitems: if ci.id == et.id: d.canvas.canvasitems.append(item) break def version_0_17_0(elements, factory, gaphor_version): """ As of version 0.17.0, ValueSpecification and subclasses is dealt with as if it were attributes. This function is called before the actual elements are constructed. """ valspec_types = [ 'ValueSpecification', 'OpaqueExpression', 'Expression', 'InstanceValue', 'LiteralSpecification', 'LiteralUnlimitedNatural', 'LiteralInteger', 'LiteralString', 'LiteralBoolean', 'LiteralNull' ] print 'version', gaphor_version if version_lower_than(gaphor_version, (0, 17, 0)): valspecs = dict((v.id, v) for v in elements.values() if v.type in valspec_types) for id in valspecs.keys(): del elements[id] for e in elements.values(): for name, ref in list(e.references.items()): # ValueSpecifications are always defined in 1:1 relationships if type(ref) != list and ref in valspecs: del e.references[name] assert not name in e.values try: e.values[name] = valspecs[ref].values['value']; except KeyError: pass # Empty LiteralSpecification def version_0_14_0(elements, factory, gaphor_version): """ Fix applied stereotypes UML metamodel. Before Gaphor 0.14.0 applied stereotypes was a collection of stereotypes classes, but now the list needs to be replaced with collection of stereotypes instances. This function is called before the actual elements are constructed. """ import uuid if version_lower_than(gaphor_version, (0, 14, 0)): values = (v for v in elements.values() if type(v) is parser.element) for et in values: try: if 'appliedStereotype' in et.references: data = tuple(et.references['appliedStereotype']) applied = [] # collect stereotypes instances in `applied` list for refid in data: st = elements[refid] obj = parser.element(str(uuid.uuid1()), 'InstanceSpecification') obj.references['classifier'] = [st.id] elements[obj.id] = obj applied.append(obj.id) assert obj.id in applied and obj.id in elements # replace stereotypes with their instances assert len(applied) == len(data) et.references['appliedStereotype'] = applied except Exception, e: log.error('Error while updating stereotypes', exc_info=True) def version_0_9_0(elements, factory, gaphor_version): """ Before 0.9.0, we used DiaCanvas2 as diagram widget in the GUI. As of 0.9.0 Gaphas was introduced. Some properties of elements have changed, renamed or been removed at all. This function is called before the actual elements are constructed. """ if version_lower_than(gaphor_version, (0, 9, 0)): for elem in elements.values(): try: if type(elem) is parser.canvasitem: # Rename affine to matrix if elem.values.get('affine'): elem.values['matrix'] = elem.values['affine'] del elem.values['affine'] # No more 'color' attribute: if elem.values.get('color'): del elem.values['color'] except Exception, e: log.error('Error while updating from DiaCanvas2', exc_info=True) def version_0_7_2(elements, factory, gaphor_version): """ Before 0.7.2, only Property and Parameter elements had taggedValues. Since 0.7.2 all NamedElements are able to have taggedValues. However, the multiplicity of taggedValue has changed from 0..1 to *, so all elements should be converted to a list. """ import uuid if version_lower_than(gaphor_version, (0, 7, 2)): for elem in elements.values(): try: if type(elem) is parser.element \ and elem.type in ('Property', 'Parameter') \ and elem.taggedValue: tvlist = [] tv = elements[elem.taggedValue] if tv.value: for t in map(str.strip, str(tv.value).split(',')): #log.debug("Tagged value: %s" % t) newtv = parser.element(str(uuid.uuid1()), 'LiteralSpecification') newtv.values['value'] = t elements[newtv.id] = newtv tvlist.append(newtv.id) elem.references['taggedValue'] = tvlist except Exception, e: log.error('Error while updating taggedValues', exc_info=True) def version_0_7_1(elements, factory, gaphor_version): """ Before version 0.7.1, there were two states for association navigability (in terms of UML 2.0): unknown and navigable. In case of unknown navigability Property.owningAssociation was set. Now, we have three states: unknown, non-navigable and navigable. In case of unknown navigability the Property.owningAssociation should not be set. """ def fix(end1, end2): if isinstance(end2.type, UML.Interface): type = end1.interface_ else: # isinstance(end2.type, UML.Class): type = end1.class_ # if the end of association is not navigable (in terms of UML 1.x) # then set navigability to unknown (in terms of UML 2.0) if not (type and end1 in type.ownedAttribute): del end1.owningAssociation if version_lower_than(gaphor_version, (0, 7, 1)): log.info('Fix navigability of Associations (file version: %s)' % gaphor_version) for elem in elements.values(): try: if elem.type == 'Association': asc = elem.element end1 = asc.memberEnd[0] end2 = asc.memberEnd[1] if end1 and end2: fix(end1, end2) fix(end2, end1) except Exception, e: log.error('Error while updating Association', exc_info=True) def version_0_6_2(elements, factory, gaphor_version): """ Before 0.6.2 an Interface could be represented by a ClassItem and a InterfaceItem. Now only InterfaceItems are used. """ if version_lower_than(gaphor_version, (0, 6, 2)): for elem in elements.values(): try: if type(elem) is parser.element and elem.type == 'Interface': for p_id in elem.presentation: p = elements[p_id] if p.type == 'ClassItem': p.type = 'InterfaceItem' p.values['drawing-style'] = '0' elif p.type == 'InterfaceItem': p.values['drawing-style'] = '2' except Exception, e: log.error('Error while updating InterfaceItems', exc_info=True) def version_0_5_2(elements, factory, gaphor_version): """ Before version 0.5.2, the wrong memberEnd of the association was holding the aggregation information. """ if version_lower_than(gaphor_version, (0, 5, 2)): log.info('Fix composition on Associations (file version: %s)' % gaphor_version) for elem in elements.values(): try: if elem.type == 'Association': a = elem.element agg1 = a.memberEnd[0].aggregation agg2 = a.memberEnd[1].aggregation a.memberEnd[0].aggregation = agg2 a.memberEnd[1].aggregation = agg1 except Exception, e: log.error('Error while updating Association', exc_info=True) # vim: sw=4:et:ai gaphor-0.17.2/gaphor/storage/tests/000077500000000000000000000000001220151210700171365ustar00rootroot00000000000000gaphor-0.17.2/gaphor/storage/tests/__init__.py000066400000000000000000000000001220151210700212350ustar00rootroot00000000000000gaphor-0.17.2/gaphor/storage/tests/test_storage.py000066400000000000000000000434301220151210700222170ustar00rootroot00000000000000""" Unittest the storage and parser modules """ import os, re import os.path import pkg_resources from gaphor.tests.testcase import TestCase from gaphor import UML from gaphor.UML.elementfactory import ElementFactory from gaphor.application import Application from gaphor.storage import storage from gaphor.misc.xmlwriter import XMLWriter from gaphor.diagram import items from gaphor.diagram.interfaces import IConnect from zope import component from cStringIO import StringIO #__module__ = 'test_storage' class PseudoFile(object): def __init__(self): self.data = '' def write(self, data): self.data += data def close(self): pass class StorageTestCase(TestCase): def test_version_check(self): from gaphor.storage.storage import version_lower_than self.assertTrue(version_lower_than('0.3.0', (0, 15, 0))) self.assertTrue(version_lower_than('0', (0, 15, 0))) self.assertTrue(version_lower_than('0.14', (0, 15, 0))) self.assertTrue(version_lower_than('0.14.1111', (0, 15, 0))) self.assertFalse(version_lower_than('0.15.0', (0, 15, 0))) self.assertFalse(version_lower_than('1.33.0', (0, 15, 0))) self.assertTrue(version_lower_than('0.15.0.b123', (0, 15, 0))) self.assertTrue(version_lower_than('0.14.0.b1', (0, 15, 0))) self.assertTrue(version_lower_than('0.15.b1', (0, 15, 0))) self.assertFalse(version_lower_than('0.16.b1', (0, 15, 0))) self.assertFalse(version_lower_than('0.15.0.b2', (0, 14, 99))) def test_save_uml(self): """Saving gaphor.UML model elements. """ self.element_factory.create(UML.Package) self.element_factory.create(UML.Diagram) self.element_factory.create(UML.Comment) self.element_factory.create(UML.Class) out = PseudoFile() storage.save(XMLWriter(out), factory=self.element_factory) out.close() assert '' in out.data assert ' type="CommentItem" ' in out.data, out.data def test_load_uml(self): """ Test loading of a freshly saved model. """ self.element_factory.create(UML.Package) # diagram is created in TestCase.setUp #self.element_factory.create(UML.Diagram) self.element_factory.create(UML.Comment) self.element_factory.create(UML.Class) data = self.save() self.load(data) assert len(self.element_factory.lselect()) == 4 assert len(self.element_factory.lselect(lambda e: e.isKindOf(UML.Package))) == 1 # diagram is created in TestCase.setUp assert len(self.element_factory.lselect(lambda e: e.isKindOf(UML.Diagram))) == 1 assert len(self.element_factory.lselect(lambda e: e.isKindOf(UML.Comment))) == 1 assert len(self.element_factory.lselect(lambda e: e.isKindOf(UML.Class))) == 1 def test_load_uml_2(self): """ Test loading of a freshly saved model. """ self.element_factory.create(UML.Package) self.create(items.CommentItem, UML.Comment) self.create(items.ClassItem, UML.Class) iface = self.create(items.InterfaceItem, UML.Interface) iface.subject.name = 'Circus' iface.matrix.translate(10, 10) data = self.save() self.load(data) assert len(self.element_factory.lselect()) == 5 assert len(self.element_factory.lselect(lambda e: e.isKindOf(UML.Package))) == 1 assert len(self.element_factory.lselect(lambda e: e.isKindOf(UML.Diagram))) == 1 d = self.element_factory.lselect(lambda e: e.isKindOf(UML.Diagram))[0] assert len(self.element_factory.lselect(lambda e: e.isKindOf(UML.Comment))) == 1 assert len(self.element_factory.lselect(lambda e: e.isKindOf(UML.Class))) == 1 assert len(self.element_factory.lselect(lambda e: e.isKindOf(UML.Interface))) == 1 c = self.element_factory.lselect(lambda e: e.isKindOf(UML.Class))[0] assert c.presentation assert c.presentation[0].subject is c #assert c.presentation[0].subject.name.startwith('Class') iface = self.element_factory.lselect(lambda e: e.isKindOf(UML.Interface))[0] assert iface.name == 'Circus' assert len(iface.presentation) == 1 assert tuple(iface.presentation[0].matrix) == (1, 0, 0, 1, 10, 10), tuple(iface.presentation[0].matrix) # Check load/save of other canvas items. assert len(d.canvas.get_all_items()) == 3 for item in d.canvas.get_all_items(): assert item.subject, 'No subject for %s' % item d1 = d.canvas.select(lambda e: isinstance(e, items.ClassItem))[0] assert d1 #print d1, d1.subject def test_load_with_whitespace_name(self): difficult_name = ' with space before and after ' diagram = self.element_factory.lselect()[0] diagram.name = difficult_name data = self.save() self.load(data) elements = self.element_factory.lselect() assert len(elements) == 1, elements assert elements[0].name == difficult_name, elements[0].name def test_load_uml_metamodel(self): """ Test if the meta model can be loaded. """ dist = pkg_resources.get_distribution('gaphor') path = os.path.join(dist.location, 'gaphor/UML/uml2.gaphor') with open(path) as ifile: storage.load(ifile, factory=self.element_factory) def test_load_uml_relationships(self): """ Test loading of a freshly saved model with relationship objects. """ self.element_factory.create(UML.Package) self.create(items.CommentItem, UML.Comment) c1 = self.create(items.ClassItem, UML.Class) a = self.diagram.create(items.AssociationItem) a.handles()[0].pos = (10, 20) a.handles()[1].pos = (50, 60) assert 10 == a.handles()[0].pos.x, a.handles()[0].pos assert a.handles()[0].pos.y == 20, a.handles()[0].pos assert tuple(a.handles()[1].pos) == (50, 60), a.handles()[1].pos data = self.save() self.load(data) assert len(self.element_factory.lselect()) == 4 assert len(self.element_factory.lselect(lambda e: e.isKindOf(UML.Package))) == 1 assert len(self.element_factory.lselect(lambda e: e.isKindOf(UML.Diagram))) == 1 d = self.element_factory.lselect(lambda e: e.isKindOf(UML.Diagram))[0] assert len(self.element_factory.lselect(lambda e: e.isKindOf(UML.Comment))) == 1 assert len(self.element_factory.lselect(lambda e: e.isKindOf(UML.Class))) == 1 assert len(self.element_factory.lselect(lambda e: e.isKindOf(UML.Association))) == 0 # Check load/save of other canvas items. assert len(d.canvas.get_all_items()) == 3 for item in d.canvas.get_all_items(): if isinstance(item, items.AssociationItem): aa = item assert aa assert map(float, aa.handles()[0].pos) == [0, 0], aa.handles()[0].pos assert map(float, aa.handles()[1].pos) == [40, 40], aa.handles()[1].pos d1 = d.canvas.select(lambda e: isinstance(e, items.ClassItem))[0] assert d1 #print d1, d1.subject def test_connection(self): """ Test connection loading of an association and two classes. (Should count for all line-like objects alike if this works). """ c1 = self.create(items.ClassItem, UML.Class) c2 = self.create(items.ClassItem, UML.Class) c2.matrix.translate(200, 200) self.diagram.canvas.update_matrix(c2) assert tuple(self.diagram.canvas.get_matrix_i2c(c2)) == (1, 0, 0, 1, 200, 200) a = self.create(items.AssociationItem) self.connect(a, a.head, c1) head_pos = a.head.pos self.connect(a, a.tail, c2) tail_pos = a.tail.pos self.diagram.canvas.update_now() assert a.head.pos.y == 0, a.head.pos assert a.tail.pos.x == 10, a.tail.pos #assert a.tail.y == 200, a.tail.pos assert a.subject fd = StringIO() storage.save(XMLWriter(fd), factory=self.element_factory) data = fd.getvalue() fd.close() old_a_subject_id = a.subject.id self.element_factory.flush() assert not list(self.element_factory.select()) fd = StringIO(data) storage.load(fd, factory=self.element_factory) fd.close() diagrams = list(self.kindof(UML.Diagram)) self.assertEquals(1, len(diagrams)) d = diagrams[0] a = d.canvas.select(lambda e: isinstance(e, items.AssociationItem))[0] self.assertTrue(a.subject is not None) self.assertEquals(old_a_subject_id, a.subject.id) cinfo_head = a.canvas.get_connection(a.head) self.assertTrue(cinfo_head.connected is not None) cinfo_tail = a.canvas.get_connection(a.tail) self.assertTrue(cinfo_tail.connected is not None) self.assertTrue(not cinfo_head.connected is cinfo_tail.connected) #assert a.head_end._name def test_load_save(self): """Test loading and saving models""" dist = pkg_resources.get_distribution('gaphor') path = os.path.join(dist.location, 'test-diagrams/simple-items.gaphor') with open(path, 'r') as ifile: storage.load(ifile, factory=self.element_factory) pf = PseudoFile() storage.save(XMLWriter(pf), factory=self.element_factory) with open(path, 'r') as ifile: orig = ifile.read() copy = pf.data with open('tmp.gaphor', 'w') as ofile: ofile.write(copy) expr = re.compile('gaphor-version="[^"]*"') orig = expr.sub('%VER%', orig) copy = expr.sub('%VER%', copy) self.assertEquals(copy, orig, 'Saved model does not match copy') class FileUpgradeTestCase(TestCase): def test_association_upgrade(self): """Test association navigability upgrade in Gaphor 0.15.0 """ dist = pkg_resources.get_distribution('gaphor') path = os.path.join(dist.location, 'test-diagrams/associations-pre015.gaphor') with open(path) as ifile: storage.load(ifile, factory=self.element_factory) diagrams = list(self.kindof(UML.Diagram)) self.assertEquals(1, len(diagrams)) diagram = diagrams[0] assocs = diagram.canvas.select(lambda e: isinstance(e, items.AssociationItem)) assert len(assocs) == 8 a1, a2 = [a for a in assocs if a.subject.name == 'nav'] self.assertTrue(a1.head_end.subject.navigability) self.assertTrue(a1.tail_end.subject.navigability) self.assertTrue(a2.head_end.subject.navigability) self.assertTrue(a2.tail_end.subject.navigability) self.assertTrue(len(a1.head_end.subject.type.ownedAttribute) == 1) self.assertTrue(len(a1.tail_end.subject.type.ownedAttribute) == 2) # association end and an attribute # use cases and actors - no owned attributes as navigability is realized # by association's navigable owned ends self.assertTrue(len(a2.head_end.subject.type.ownedAttribute) == 0) self.assertTrue(len(a2.tail_end.subject.type.ownedAttribute) == 0) a1, a2 = [a for a in assocs if a.subject.name == 'nonnav'] self.assertTrue(a1.head_end.subject.navigability is False) self.assertTrue(a1.tail_end.subject.navigability is False) self.assertTrue(a2.head_end.subject.navigability is False) self.assertTrue(a2.tail_end.subject.navigability is False) a1, a2 = [a for a in assocs if a.subject.name == 'unk'] self.assertTrue(a1.head_end.subject.navigability is None) self.assertTrue(a1.tail_end.subject.navigability is None) self.assertTrue(a2.head_end.subject.navigability is None) self.assertTrue(a2.tail_end.subject.navigability is None) a, = [a for a in assocs if a.subject.name == 'sided'] assert a.head_end.subject.name == 'cs' assert a.tail_end.subject.name == 'int' self.assertTrue(a.head_end.subject.navigability is False) self.assertTrue(a.tail_end.subject.navigability is True) a, = [a for a in assocs if a.subject.name == 'sided2'] assert a.head_end.subject.name == 'cs' assert a.tail_end.subject.name == 'int' self.assertTrue(a.head_end.subject.navigability is None) self.assertTrue(a.tail_end.subject.navigability is True) def test_tagged_values_upgrade(self): """Test tagged values upgrade in Gaphor 0.15.0 """ dist = pkg_resources.get_distribution('gaphor') path = os.path.join(dist.location, 'test-diagrams/taggedvalues-pre015.gaphor') with open(path) as ifile: storage.load(ifile, factory=self.element_factory) diagrams = list(self.kindof(UML.Diagram)) self.assertEquals(1, len(diagrams)) diagram = diagrams[0] classes = diagram.canvas.select(lambda e: isinstance(e, items.ClassItem)) profiles = self.element_factory.lselect(lambda e: isinstance(e, UML.Profile)) stereotypes = self.element_factory.lselect(lambda e: isinstance(e, UML.Stereotype)) self.assertEquals(2, len(classes)) c1, c2 = classes self.assertEquals(1, len(profiles)) profile = profiles[0] self.assertEquals('version 0.15 conversion', profile.name) self.assertEquals(1, len(stereotypes)) stereotype = stereotypes[0] self.assertEquals('Tagged', stereotype.name) self.assertEquals(profile, stereotype.namespace) self.assertEquals('c1', c1.subject.name) self.assertEquals('c2', c2.subject.name) self.assertEquals(stereotype, c1.subject.appliedStereotype[0].classifier[0]) self.assertEquals(stereotype, c2.subject.appliedStereotype[0].classifier[0]) self.assertEquals('t1', c1.subject.appliedStereotype[0].slot[0].definingFeature.name) self.assertEquals('t2', c1.subject.appliedStereotype[0].slot[1].definingFeature.name) self.assertEquals('t5', c2.subject.appliedStereotype[0].slot[0].definingFeature.name) self.assertEquals('t6', c2.subject.appliedStereotype[0].slot[1].definingFeature.name) self.assertEquals('t7', c2.subject.appliedStereotype[0].slot[2].definingFeature.name) def test_lifeline_messages_upgrade(self): """Test message occurrence specification upgrade in Gaphor 0.15.0 """ dist = pkg_resources.get_distribution('gaphor') path = os.path.join(dist.location, 'test-diagrams/lifelines-pre015.gaphor') with open(path) as ifile: storage.load(ifile, factory=self.element_factory) diagrams = list(self.kindof(UML.Diagram)) self.assertEquals(1, len(diagrams)) diagram = diagrams[0] lifelines = diagram.canvas.select(lambda e: isinstance(e, items.LifelineItem)) occurrences = self.kindof(UML.MessageOccurrenceSpecification) messages = self.kindof(UML.Message) self.assertEquals(2, len(lifelines)) self.assertEquals(12, len(messages)) # 2 * 12 but there are 4 lost/found messages self.assertEquals(20, len(set(occurrences))) l1, l2 = lifelines if l1.subject.name == 'a2': l1, l2 = l2, l1 def find(name): return (m for m in messages if m.name == name).next() m1 = find('call()') m2 = find('callx()') m3 = find('cally()') # inverted messages m4 = find('calla()') m5 = find('callb()') self.assertTrue(m1.sendEvent.covered is l1.subject) self.assertTrue(m2.sendEvent.covered is l1.subject) self.assertTrue(m3.sendEvent.covered is l1.subject) self.assertTrue(m1.receiveEvent.covered is l2.subject) self.assertTrue(m2.receiveEvent.covered is l2.subject) self.assertTrue(m3.receiveEvent.covered is l2.subject) # test inverted messages self.assertTrue(m4.sendEvent.covered is l2.subject) self.assertTrue(m5.sendEvent.covered is l2.subject) self.assertTrue(m4.receiveEvent.covered is l1.subject) self.assertTrue(m5.receiveEvent.covered is l1.subject) m = find('simple()') self.assertTrue(m.sendEvent.covered is l1.subject) self.assertTrue(m.receiveEvent.covered is l2.subject) m = find('found1()') self.assertTrue(m.sendEvent is None) self.assertTrue(m.receiveEvent.covered is l1.subject) m = find('found2()') self.assertTrue(m.sendEvent is None) self.assertTrue(m.receiveEvent.covered is l1.subject) m = find('rfound1()') self.assertTrue(m.sendEvent.covered is l1.subject) self.assertTrue(m.receiveEvent is None) m = find('lost1()') self.assertTrue(m.sendEvent.covered is l1.subject) self.assertTrue(m.receiveEvent is None) m = find('lost2()') self.assertTrue(m.sendEvent.covered is l1.subject) self.assertTrue(m.receiveEvent is None) m = find('rlost1()') self.assertTrue(m.sendEvent is None) self.assertTrue(m.receiveEvent.covered is l1.subject) # vim:sw=4:et:ai gaphor-0.17.2/gaphor/storage/tests/test_verify.py000066400000000000000000000011371220151210700220550ustar00rootroot00000000000000 from gaphor.tests.testcase import TestCase from gaphor.storage.verify import orphan_references from gaphor import UML class VerifyTestCase(TestCase): def test_verifier(self): factory = self.element_factory c = factory.create(UML.Class) p = factory.create(UML.Property) c.ownedAttribute = p assert not orphan_references(factory) # Now create a separate item, not part of the factory: m = UML.Comment(id="acd123") m.annotatedElement = c assert m in c.ownedComment assert orphan_references(factory) # vim:sw=4:et:ai gaphor-0.17.2/gaphor/storage/verify.py000066400000000000000000000046001220151210700176520ustar00rootroot00000000000000""" Verify the content of an element factory before it is saved. """ from gaphor import UML from gaphor.UML.collection import collection import gaphas def orphan_references(factory): """ Verify the contents of the element factory. Only checks are done that ensure the model can be loaded back again. TODO: Okay, now I can predict if a model can be loaded after it's saved, but I have no means to correct or fix the model. """ # Maintain a set of id's, one for elements, one for references. # Write only to file if references is a subset of elements refs = set() elements = set() def verify_reference(name, value): """ Store the reference """ # Save a reference to the object: if value.id: refs.add((value.id, value)) def verify_collection(name, value): """ Store a list of references. """ for v in value: if v.id: refs.add((v.id, v)) def verify_element(name, value): """ Store the element id. """ if isinstance (value, (UML.Element, gaphas.Item)): verify_reference(name, value) elif isinstance(value, collection): verify_collection(name, value) elif isinstance(value, gaphas.Canvas): value.save(verify_canvasitem) def verify_canvasitem(name, value, reference=False): """ Verify attributes and references in a gaphor.diagram.* object. The extra attribute reference can be used to force UML """ #log.debug('saving canvasitem: %s|%s %s' % (name, value, type(value))) if isinstance(value, collection) or \ (isinstance(value, (list, tuple)) and reference == True): verify_collection(name, value) elif reference: verify_reference(name, value) elif isinstance(value, gaphas.Item): elements.add(value.id) value.save(verify_canvasitem) # save subitems for child in value.canvas.get_children(value): verify_canvasitem(None, child) elif isinstance(value, UML.Element): verify_reference(name, value) for e in factory.values(): assert e.id elements.add(e.id) e.save(verify_element) return [r[1] for r in refs if not r[0] in elements] # vim:sw=4:et:ai gaphor-0.17.2/gaphor/tests/000077500000000000000000000000001220151210700154725ustar00rootroot00000000000000gaphor-0.17.2/gaphor/tests/__init__.py000066400000000000000000000001051220151210700175770ustar00rootroot00000000000000""" """ from testcase import TestCase, main # vim:sw=4:et:ai gaphor-0.17.2/gaphor/tests/test_action.py000066400000000000000000000003231220151210700203560ustar00rootroot00000000000000 import doctest from gaphor import action def test_suite(): return doctest.DocTestSuite(action) if __name__ == '__main__': import unittest unittest.main(defaultTest='test_suite') # vim:sw=4:et:ai gaphor-0.17.2/gaphor/tests/test_application.py000066400000000000000000000021721220151210700214100ustar00rootroot00000000000000"""Application service test cases.""" import unittest from zope import component from gaphor import UML from gaphor.application import Application from gaphor.interfaces import IService class LoadServiceTestCase(unittest.TestCase): """Test case for loading Gaphor services.""" def test_service_load(self): """Test loading services and querying utilities.""" Application.init(['undo_manager', 'file_manager', 'properties']) self.assertTrue(Application.get_service('undo_manager') is not None,\ 'Failed to load the undo manager service') self.assertTrue(Application.get_service('file_manager') is not None,\ 'Failed to load the file manager service') self.assertTrue(component.queryUtility(IService, 'undo_manager') is not None,\ 'Failed to query the undo manager utility') self.assertTrue(component.queryUtility(IService, 'file_manager') is not None,\ 'Failed to query the file manager utility') Application.shutdown() gaphor-0.17.2/gaphor/tests/test_transaction.py000066400000000000000000000133311220151210700214310ustar00rootroot00000000000000"""Unit tests for transactions in Gaphor.""" from unittest import TestCase from zope.component.globalregistry import base from gaphor.application import Application from gaphor.transaction import Transaction, transactional, TransactionError from gaphor.event import TransactionBegin, TransactionCommit, TransactionRollback begins = [] commits = [] rollbacks = [] def handle_begins(event): """Store TransactionBegin events in begins.""" begins.append(event) def handle_commits(event): """Store TransactionCommit events in commits.""" commits.append(event) def handle_rollback(event): """Store TransactionRollback events in rollbacks.""" rollbacks.append(event) class TransactionTestCase(TestCase): """Test case for transactions with the component registry enabled.""" def setUp(self): """Initialize Gaphor services and register transaction event handlers.""" Application.init(services=['component_registry']) component_registry = Application.get_service('component_registry') component_registry.register_handler(handle_begins, [TransactionBegin]) component_registry.register_handler(handle_commits, [TransactionCommit]) component_registry.register_handler(handle_rollback, [TransactionRollback]) del begins[:] del commits[:] del rollbacks[:] def tearDown(self): """Finished with the test case. Unregister event handlers that store transaction events.""" component_registry = Application.get_service('component_registry') component_registry.unregister_handler(handle_begins, [TransactionBegin]) component_registry.unregister_handler(handle_commits, [TransactionCommit]) component_registry.unregister_handler(handle_rollback, [TransactionRollback]) def test_transaction_commit(self): """Test committing a transaction.""" tx = Transaction() self.assertTrue(tx._stack, 'Transaction has no stack') self.assertEquals(1, len(begins), 'Incorrect number of TrasactionBegin events') self.assertEquals(0, len(commits), 'Incorrect number of TransactionCommit events') self.assertEquals(0, len(rollbacks), 'Incorrect number of TransactionRollback events') tx.commit() self.assertEquals(1, len(begins), 'Incorrect number of TrasactionBegin events') self.assertEquals(1, len(commits), 'Incorrect number of TransactionCommit events') self.assertEquals(0, len(rollbacks), 'Incorrect number of TransactionRollback events') self.assertFalse(tx._stack, 'Transaction stack is not empty') try: tx.commit() except TransactionError: pass else: self.fail('Commit should not have succeeded') def test_transaction_rollback(self): """Test rolling back a transaction.""" tx = Transaction() self.assertTrue(tx._stack, 'Transaction has no stack') self.assertEquals(1, len(begins), 'Incorrect number of TrasactionBegin events') self.assertEquals(0, len(commits), 'Incorrect number of TransactionCommit events') self.assertEquals(0, len(rollbacks), 'Incorrect number of TransactionRollback events') tx.rollback() self.assertEquals(1, len(begins), 'Incorrect number of TrasactionBegin events') self.assertEquals(0, len(commits), 'Incorrect number of TransactionCommit events') self.assertEquals(1, len(rollbacks), 'Incorrect number of TransactionRollback events') self.assertFalse(tx._stack, 'Transaction stack is not empty') def test_transaction_commit_after_rollback(self): """Test committing one transaction after rolling back another transaction.""" tx1 = Transaction() tx2 = Transaction() tx2.rollback() tx1.commit() self.assertEquals(1, len(begins), 'Incorrect number of TrasactionBegin events') self.assertEquals(0, len(commits), 'Incorrect number of TransactionCommit events') self.assertEquals(1, len(rollbacks), 'Incorrect number of TransactionRollback events') def test_transaction_stack(self): """Test the transaction stack.""" tx1 = Transaction() tx2 = Transaction() try: tx1.commit() except TransactionError, e: pass else: self.fail('Commit should not have succeeded') def test_transaction_context(self): """Test the transaction context manager.""" with Transaction() as tx: self.assertTrue(isinstance(tx, Transaction), 'Context is not a Transaction instance') self.assertTrue(Transaction._stack, 'Transaction instance has no stack inside a context') self.assertFalse(Transaction._stack, 'Transaction stack should be empty') def test_transaction_context_error(self): """Test the transaction context manager with errors.""" try: with Transaction(): raise TypeError('transaction error') except TypeError, e: self.assertEquals('transaction error', str(e), 'Transaction context manager did no raise correct exception') else: self.fail('Transaction context manager did not raise exception when it should have') class TransactionWithoutComponentRegistryTestCase(TestCase): """Test case for transactions with no component registry.""" def test_transaction(self): """Test basic transaction functionality.""" tx = Transaction() tx.rollback() tx = Transaction() tx.commit() gaphor-0.17.2/gaphor/tests/testcase.py000066400000000000000000000136531220151210700176670ustar00rootroot00000000000000""" Basic test case for Gaphor tests. Everything is about services so the TestCase can define it's required services and start off. """ import unittest import logging from cStringIO import StringIO from zope import component from gaphas.aspect import ConnectionSink, Connector from gaphor import UML from gaphor.application import Application from gaphor.diagram.interfaces import IConnect from gaphor.diagram.interfaces import IGroup # For DiagramItemConnector aspect: import gaphor.ui.diagramtools # Increment log level log.setLevel(logging.WARNING) class TestCaseExtras(object): """ Mixin for some extra tests. """ def failIfIdentityEqual(self, first, second, msg=None): """Fail if the two objects are equal as determined by the 'is operator. """ if first is second: raise self.failureException, \ (msg or '%r is not %r' % (first, second)) assertNotSame = failIfIdentityEqual def failUnlessIdentityEqual(self, first, second, msg=None): """Fail if the two objects are not equal as determined by the 'is operator. """ if first is not second: raise self.failureException, \ (msg or '%r is not %r' % (first, second)) assertSame = failUnlessIdentityEqual class TestCase(TestCaseExtras, unittest.TestCase): services = ['element_factory', 'adapter_loader', 'element_dispatcher', 'sanitizer'] def setUp(self): Application.init(services=self.services) self.element_factory = Application.get_service('element_factory') assert len(list(self.element_factory.select())) == 0, list(self.element_factory.select()) self.diagram = self.element_factory.create(UML.Diagram) assert len(list(self.element_factory.select())) == 1, list(self.element_factory.select()) def tearDown(self): self.element_factory.shutdown() Application.shutdown() def get_service(self, name): return Application.get_service(name) def create(self, item_cls, subject_cls=None, subject=None): """ Create an item with specified subject. """ if subject_cls is not None: subject = self.element_factory.create(subject_cls) item = self.diagram.create(item_cls, subject=subject) self.diagram.canvas.update() return item def allow(self, line, handle, item, port=None): """ Glue line's handle to an item. If port is not provided, then first port is used. """ if port is None and len(item.ports()) > 0: port = item.ports()[0] query = (item, line) adapter = component.queryMultiAdapter(query, IConnect) return adapter.allow(handle, port) def connect(self, line, handle, item, port=None): """ Connect line's handle to an item. If port is not provided, then first port is used. """ canvas = line.canvas assert line.canvas is item.canvas if port is None and len(item.ports()) > 0: port = item.ports()[0] sink = ConnectionSink(item, port) connector = Connector(line, handle) connector.connect(sink) cinfo = canvas.get_connection(handle) self.assertSame(cinfo.connected, item) self.assertSame(cinfo.port, port) def disconnect(self, line, handle): """ Disconnect line's handle. """ canvas = self.diagram.canvas # disconnection on adapter level is performed due to callback, so # no adapter look up here canvas.disconnect_item(line, handle) assert not canvas.get_connection(handle) def get_connected(self, handle): """ Get item connected to line via handle. """ cinfo = self.diagram.canvas.get_connection(handle) if cinfo: return cinfo.connected return None def get_connection(self, handle): """ Get connection information. """ return self.diagram.canvas.get_connection(handle) def can_group(self, parent, item): """ Check if an item can be grouped by parent. """ query = (parent, item) adapter = component.queryMultiAdapter(query, IGroup) return adapter.can_contain() def group(self, parent, item): """ Group item within a parent. """ self.diagram.canvas.reparent(item, parent) query = (parent, item) adapter = component.queryMultiAdapter(query, IGroup) adapter.group() def ungroup(self, parent, item): """ Remove item from a parent. """ query = (parent, item) adapter = component.queryMultiAdapter(query, IGroup) adapter.ungroup() self.diagram.canvas.reparent(item, None) def kindof(self, cls): """ Find UML metaclass instances using element factory. """ return self.element_factory.lselect(lambda e: e.isKindOf(cls)) def save(self): """ Save diagram into string. """ from gaphor.storage import storage from gaphor.misc.xmlwriter import XMLWriter f = StringIO() storage.save(XMLWriter(f), factory=self.element_factory) data = f.getvalue() f.close() self.element_factory.flush() assert not list(self.element_factory.select()) assert not list(self.element_factory.lselect()) return data def load(self, data): """ Load data from specified string. Update ``TestCase.diagram`` attribute to hold new loaded diagram. """ from gaphor.storage import storage f = StringIO(data) storage.load(f, factory=self.element_factory) f.close() self.diagram = self.element_factory.lselect(lambda e: e.isKindOf(UML.Diagram))[0] main = unittest.main # vim:sw=4:et:ai gaphor-0.17.2/gaphor/tools/000077500000000000000000000000001220151210700154705ustar00rootroot00000000000000gaphor-0.17.2/gaphor/tools/README.txt000066400000000000000000000002131220151210700171620ustar00rootroot00000000000000Tools ===== This package (``gaphor.tools``) is the place to put (command-line) utilities that are part of the Gaphor distribution. gaphor-0.17.2/gaphor/tools/__init__.py000066400000000000000000000000001220151210700175670ustar00rootroot00000000000000gaphor-0.17.2/gaphor/tools/gaphorconvert.py000077500000000000000000000071701220151210700207330ustar00rootroot00000000000000#!/usr/bin/python import gaphor from gaphor.storage import storage import gaphor.UML as UML from gaphas.painter import ItemPainter from gaphas.view import View import cairo import optparse import os import re import sys def pkg2dir(package): """ Return directory path from UML package class. """ name = [] while package: name.insert(0, package.name) package = package.package return '/'.join(name) def message(msg): """ Print message if user set verbose mode. """ global options if options.verbose: print >> sys.stderr, msg usage = 'usage: %prog [options] file1 file2...' parser = optparse.OptionParser(usage=usage) parser.add_option('-v', '--verbose', dest='verbose', action='store_true', help='verbose output') parser.add_option('-u', '--use-underscores', dest='underscores', action='store_true', help='use underscores instead of spaces for output filenames') parser.add_option('-d', '--dir', dest='dir', metavar='directory', help='output to directory') parser.add_option('-f', '--format', dest='format', metavar='format', help='output file format, default pdf', default='pdf', choices=['pdf', 'svg', 'png']) parser.add_option('-r', '--regex', dest='regex', metavar='regex', help='process diagrams which name matches given regular expresion;' \ ' name includes package name; regular expressions are case insensitive') (options, args) = parser.parse_args() if not args: parser.print_help() sys.exit(1) factory = UML.ElementFactory() name_re = None if options.regex: name_re = re.compile(options.regex, re.I) # we should have some gaphor files to be processed at this point for model in args: message('loading model %s' % model) storage.load(model, factory) message('\nready for rendering\n') for diagram in factory.select(lambda e: e.isKindOf(UML.Diagram)): odir = pkg2dir(diagram.package) # just diagram name dname = diagram.name # full diagram name including package path pname = '%s/%s' % (odir, dname) if options.underscores: odir = odir.replace(' ', '_') dname = dname.replace(' ', '_') if name_re and not name_re.search(pname): message('skipping %s' % pname) continue if options.dir: odir = '%s/%s' % (options.dir, odir) outfilename = '%s/%s.%s' % (odir, dname, options.format) if not os.path.exists(odir): message('creating dir %s' % odir) os.makedirs(odir) message('rendering: %s -> %s...' % (pname, outfilename)) view = View(diagram.canvas) view.painter = ItemPainter() tmpsurface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 0, 0) tmpcr = cairo.Context(tmpsurface) view.update_bounding_box(tmpcr) tmpcr.show_page() tmpsurface.flush() w, h = view.bounding_box.width, view.bounding_box.height if options.format == 'pdf': surface = cairo.PDFSurface(outfilename, w, h) elif options.format == 'svg': surface = cairo.SVGSurface(outfilename, w, h) elif options.format == 'png': surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, int(w+1), int(h+1)) else: assert False, 'unknown format %s' % options.format cr = cairo.Context(surface) view.matrix.translate(-view.bounding_box.x, -view.bounding_box.y) view.paint(cr) cr.show_page() if options.format == 'png': surface.write_to_png(outfilename) surface.flush() surface.finish() def main(): pass # vim:sw=4:et:ai gaphor-0.17.2/gaphor/transaction.py000066400000000000000000000103651220151210700172340ustar00rootroot00000000000000""" Transation support for Gaphor """ from logging import getLogger from zope import interface, component from gaphor.interfaces import ITransaction from gaphor.event import TransactionBegin, TransactionCommit, TransactionRollback from gaphor import application logger = getLogger('transaction') def transactional(func): """The transactional decorator makes a function transactional. A Transaction instance is created before the decorated function is called. If calling the function leads to an exception being raised, the transaction is rolled-back. Otherwise, it is committed.""" def _transactional(*args, **kwargs): r = None tx = Transaction() try: r = func(*args, **kwargs) except Exception, e: log.error('Transaction terminated due to an exception, performing a rollback', exc_info=True) try: tx.rollback() except Exception, e: log.error('Rollback failed', exc_info=True) raise else: tx.commit() return r return _transactional class TransactionError(Exception): """ Errors related to the transaction module. """ class Transaction(object): """ The transaction. On start and end of a transaction an event is emited. Transactions can be nested. If the outermost transaction is committed or rolled back, an event is emitted. Events can be handled programmatically: >>> tx = Transaction() >>> tx.commit() It can be assigned as decorator: >>> @transactional ... def foo(): ... pass Or with the ``with`` statement: >>> with Transaction(): ... pass """ interface.implements(ITransaction) component_registry = application.inject('component_registry') _stack= [] def __init__(self): """Initialize the transaction. If this is the first transaction in the stack, a TransactionBegin event is emited.""" self._need_rollback = False if not self._stack: self._handle(TransactionBegin()) self._stack.append(self) def commit(self): """Commit the transaction. First, the transaction is closed. If it needs to be rolled-back, a TransactionRollback event is emited. Otherwise, a TransactionCommit event is emited.""" self._close() if not self._stack: if self._need_rollback: self._handle(TransactionRollback()) else: self._handle(TransactionCommit()) def rollback(self): """Roll-back the transaction. First, the transaction is closed. Every transaction on the stack is then marked for roll-back. If the stack is empty, a TransactionRollback event is emited.""" self._close() for tx in self._stack: tx._need_rollback = True else: if not self._stack: self._handle(TransactionRollback()) def _close(self): """Close the transaction. If the stack is empty, a TransactionError is raised. If the last transaction on the stack isn't this transaction, a Transaction error is raised.""" try: last = self._stack.pop() except IndexError: raise TransactionError, 'No Transaction on stack.' if last is not self: self._stack.append(last) raise TransactionError, 'Transaction on stack is not the transaction being closed.' def _handle(self, event): try: component_registry = self.component_registry except (application.NotInitializedError, component.ComponentLookupError): logger.warning('Could not lookup component_registry. Not emiting events.') else: component_registry.handle(event) def __enter__(self): """Provide with-statement transaction support.""" return self def __exit__(self, exc_type=None, exc_val=None, exc_tb=None): """Provide with-statement transaction support. If an error occured, the transaction is rolled back. Otherwise, it is committed.""" if exc_type: self.rollback() else: self.commit() # vim: sw=4:et:ai gaphor-0.17.2/gaphor/transaction.txt000066400000000000000000000060011220151210700174130ustar00rootroot00000000000000Transaction support for Gaphor ============================== Transaction support is located in module gaphor.transaction: >>> from gaphor import transaction >>> from gaphor.application import Application Do some basic initialization, so event emission will work: >>> Application.init(services=['component_registry']) The Transaction class is used mainly to signal the begin and end of a transaction. This is done by the TransactionBegin, TransactionCommit and TransactionRollback events: >>> from zope import component >>> @component.adapter(transaction.TransactionBegin) ... def transaction_begin_handler(event): ... print 'tx begin' >>> component.provideHandler(transaction_begin_handler) Same goes for commit and rollback events: >>> @component.adapter(transaction.TransactionCommit) ... def transaction_commit_handler(event): ... print 'tx commit' >>> component.provideHandler(transaction_commit_handler) >>> @component.adapter(transaction.TransactionRollback) ... def transaction_rollback_handler(event): ... print 'tx rollback' >>> component.provideHandler(transaction_rollback_handler) A Transaction is started by initiating a Transaction instance: >>> tx = transaction.Transaction() tx begin On success, a transaction can be committed: >>> tx.commit() tx commit After a commit, a rollback is no longer allowed (the transaction is closed): >>> tx.rollback() ... # doctest: +ELLIPSIS Traceback (most recent call last): ... TransactionError: No Transaction on stack. Transactions may be nested: >>> tx = transaction.Transaction() tx begin >>> tx2 = transaction.Transaction() >>> tx2.commit() >>> tx.commit() tx commit Transactions should be closed in the right order (subtransactions first): >>> tx = transaction.Transaction() tx begin >>> tx2 = transaction.Transaction() >>> tx.commit() ... # doctest: +ELLIPSIS Traceback (most recent call last): ... TransactionError: Transaction on stack is not the transaction being closed. >>> tx2.commit() >>> tx.commit() tx commit The transactional decorator can be used to mark functions as transactional: >>> @transaction.transactional ... def a(): ... print 'do something' >>> a() tx begin do something tx commit If an exception is raised from within the decorated function a rollback is performed: >>> @transaction.transactional ... def a(): ... raise IndexError, 'bla' >>> a() ... # doctest: +ELLIPSIS Traceback (most recent call last): ... IndexError: bla >>> transaction.Transaction._stack [] All transactions are marked for rollback once an exception is raised: >>> tx = transaction.Transaction() tx begin >>> a() ... # doctest: +ELLIPSIS Traceback (most recent call last): ... IndexError: bla >>> tx._need_rollback True >>> tx.commit() tx rollback Cleanup: >>> Application.shutdown() gaphor-0.17.2/gaphor/ui/000077500000000000000000000000001220151210700147455ustar00rootroot00000000000000gaphor-0.17.2/gaphor/ui/__init__.py000066400000000000000000000010731220151210700170570ustar00rootroot00000000000000""" This module contains user interface related code, such as the main screen and diagram windows. """ import gtk import pkg_resources import os.path icon_theme = gtk.icon_theme_get_default() icon_theme.append_search_path(os.path.abspath( pkg_resources.resource_filename('gaphor.ui', 'pixmaps'))) import re def _repl(m): v = m.group(1).lower() return len(v) == 1 and v or '%c-%c' % tuple(v) _repl.expr = '(.?[A-Z])' def icon_for_element(element): return re.sub(_repl.expr, _repl, type(element).__name__) # vim:sw=4:et: gaphor-0.17.2/gaphor/ui/accelmap.py000066400000000000000000000015731220151210700170720ustar00rootroot00000000000000""" This module contains user interface related code, such as the main screen and diagram windows. """ import os, os.path import gtk from gaphor.misc import get_user_data_dir def _get_accel_map_filename(): """ The Gaphor accelMap file ($HOME/.gaphor/accelmap). """ user_data_dir = get_user_data_dir() if not os.path.exists(user_data_dir): os.mkdir(user_data_dir) return os.path.join(user_data_dir, 'accelmap') def load_accel_map(): """ Load the user accelerator map from the gaphor user home directory """ filename = _get_accel_map_filename() if os.path.exists(filename) and os.path.isfile(filename): gtk.accel_map_load(filename) def save_accel_map(): """ Save the contents of the GtkAccelMap to a file. """ filename = _get_accel_map_filename() gtk.accel_map_save(filename) # vim:sw=4:et: gaphor-0.17.2/gaphor/ui/consolewindow.py000066400000000000000000000041261220151210700202140ustar00rootroot00000000000000#!/usr/bin/env python import sys import os import gtk from zope import interface from gaphor.core import inject from gaphor.interfaces import IActionProvider from gaphor.ui.interfaces import IUIComponent from gaphor.action import action, open_action, build_action_group from gaphor.misc.console import GTKInterpreterConsole from gaphor.misc import get_user_data_dir class ConsoleWindow(object): interface.implements(IUIComponent, IActionProvider) component_registry = inject('component_registry') menu_xml = """ """ title = 'Gaphor Console' size = (400, 400) placement = 'floating' def __init__(self): self.action_group = build_action_group(self) self.console = None self.ui_manager = None # injected def load_console_py(self): """ Load default script for console. Saves some repetative typing. """ console_py = os.path.join(get_user_data_dir(), 'console.py') try: with open(console_py) as f: for line in f: self.console.push(line) except IOError: log.info('No initiation script %s' % console_py) @open_action(name='ConsoleWindow:open', label='_Console') def open_console(self): if not self.console: return self else: self.console.set_property('has-focus', True) def open(self): self.construct() self.load_console_py() return self.console @action(name='ConsoleWindow:close', stock_id='gtk-close', accel='w') def close(self, dock_item=None): self.console.destroy() self.console = None def construct(self): console = GTKInterpreterConsole(locals={ 'service': self.component_registry.get_service }) console.show() self.console = console return console # vim:sw=4:et:ai gaphor-0.17.2/gaphor/ui/diagramtab.py000066400000000000000000000265171220151210700174250ustar00rootroot00000000000000#!/usr/bin/env python import gtk from cairo import Matrix from zope import component from etk.docking import DockItem from gaphas.view import GtkView from gaphas.painter import PainterChain, ItemPainter, HandlePainter, \ FocusedItemPainter, ToolPainter, BoundingBoxPainter from gaphas.freehand import FreeHandPainter from gaphas import segment, guide from gaphor import UML from gaphor.core import _, inject, transactional, action, toggle_action, build_action_group from gaphor.UML.interfaces import IAttributeChangeEvent, IElementDeleteEvent from gaphor.diagram import get_diagram_item from gaphor.diagram.items import DiagramItem from gaphor.transaction import Transaction from gaphor.ui.diagramtoolbox import DiagramToolbox from gaphor.ui.event import DiagramSelectionChange from gaphor.services.properties import IPropertyChangeEvent class DiagramTab(object): component_registry = inject('component_registry') element_factory = inject('element_factory') action_manager = inject('action_manager') menu_xml = """ """ VIEW_TARGET_STRING = 0 VIEW_TARGET_ELEMENT_ID = 1 VIEW_TARGET_TOOLBOX_ACTION = 2 VIEW_DND_TARGETS = [ ('gaphor/element-id', 0, VIEW_TARGET_ELEMENT_ID), ('gaphor/toolbox-action', 0, VIEW_TARGET_TOOLBOX_ACTION)] def __init__(self, diagram): self.diagram = diagram self.view = None #self.owning_window = owning_window self.action_group = build_action_group(self) self.toolbox = None self.component_registry.register_handler(self._on_element_change) self.component_registry.register_handler(self._on_element_delete) title = property(lambda s: s.diagram and s.diagram.name or _('')) def get_diagram(self): return self.diagram def get_view(self): return self.view def get_canvas(self): return self.diagram.canvas def construct(self): """ Create the widget. Returns: the newly created widget, a DockItem. """ assert self.diagram view = GtkView(canvas=self.diagram.canvas) view.drag_dest_set(gtk.DEST_DEFAULT_MOTION, DiagramTab.VIEW_DND_TARGETS, gtk.gdk.ACTION_MOVE | gtk.gdk.ACTION_COPY | gtk.gdk.ACTION_LINK) scrolled_window = gtk.ScrolledWindow() scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) scrolled_window.set_shadow_type(gtk.SHADOW_IN) scrolled_window.add(view) scrolled_window.show_all() view.connect('focus-changed', self._on_view_selection_changed) view.connect('selection-changed', self._on_view_selection_changed) view.connect_after('key-press-event', self._on_key_press_event) view.connect('drag-drop', self._on_drag_drop) view.connect('drag-data-received', self._on_drag_data_received) self.view = view self.toolbox = DiagramToolbox(self.diagram, view) item = DockItem(title=self.title, stock_id='gaphor-diagram') item.add(scrolled_window) self.widget = item return item @component.adapter(IAttributeChangeEvent) def _on_element_change(self, event): if event.element is self.diagram and \ event.property is UML.Diagram.name: self.widget.title = self.title @component.adapter(IElementDeleteEvent) def _on_element_delete(self, event): if event.element is self.diagram: self.close() @action(name='diagram-close', stock_id='gtk-close') def close(self): """ Tab is destroyed. Do the same thing that would be done if File->Close was pressed. """ self.widget.destroy() self.component_registry.unregister_handler(self._on_element_delete) self.component_registry.unregister_handler(self._on_element_change) self.view = None @action(name='diagram-zoom-in', stock_id='gtk-zoom-in') def zoom_in(self): self.view.zoom(1.2) @action(name='diagram-zoom-out', stock_id='gtk-zoom-out') def zoom_out(self): self.view.zoom(1 / 1.2) @action(name='diagram-zoom-100', stock_id='gtk-zoom-100') def zoom_100(self): zx = self.view.matrix[0] self.view.zoom(1 / zx) @action(name='diagram-select-all', label='_Select all', accel='a') def select_all(self): self.view.select_all() @action(name='diagram-unselect-all', label='Des_elect all', accel='a') def unselect_all(self): self.view.unselect_all() @action(name='diagram-delete', stock_id='gtk-delete') @transactional def delete_selected_items(self): items = self.view.selected_items for i in list(items): if isinstance(i, DiagramItem): i.unlink() else: if i.canvas: i.canvas.remove(i) def set_drawing_style(self, sloppiness=0.0): """Set the drawing style for the diagram. 0.0 is straight, 2.0 is very sloppy. If the sloppiness is set to be anything greater than 0.0, the FreeHandPainter instances will be used for both the item painter and the box painter. Otherwise, by default, the ItemPainter is used for the item and BoundingBoxPainter for the box.""" view = self.view if sloppiness: item_painter = FreeHandPainter(ItemPainter(),\ sloppiness=sloppiness) box_painter = FreeHandPainter(BoundingBoxPainter(),\ sloppiness=sloppiness) else: item_painter = ItemPainter() box_painter = BoundingBoxPainter() view.painter = PainterChain().\ append(item_painter).\ append(HandlePainter()).\ append(FocusedItemPainter()).\ append(ToolPainter()) view.bounding_box_painter = box_painter view.queue_draw_refresh() def may_remove_from_model(self, view): """ Check if there are items which will be deleted from the model (when their last views are deleted). If so request user confirmation before deletion. """ items = self.view.selected_items last_in_model = filter(lambda i: i.subject and len(i.subject.presentation) == 1, items) log.debug('Last in model: %s' % str(last_in_model)) if last_in_model: return self.confirm_deletion_of_items(last_in_model) return True def confirm_deletion_of_items(self, last_in_model): """ Request user confirmation on deleting the item from the model. """ s = '' for item in last_in_model: s += '%s\n' % str(item) dialog = gtk.MessageDialog( None, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_WARNING, gtk.BUTTONS_YES_NO, 'This will remove the following selected items from the model:\n%s\nAre you sure?' % s ) dialog.set_transient_for(self.get_toplevel()) value = dialog.run() dialog.destroy() if value == gtk.RESPONSE_YES: return True return False def _on_key_press_event(self, view, event): """ Handle the 'Delete' key. This can not be handled directly (through GTK's accelerators) since otherwise this key will confuse the text edit stuff. """ if view.is_focus(): if event.keyval == gtk.keysyms.Delete and \ (event.state == 0 or event.state & gtk.gdk.MOD2_MASK): self.delete_selected_items() elif event.keyval == gtk.keysyms.BackSpace and \ (event.state == 0 or event.state & gtk.gdk.MOD2_MASK): self.delete_selected_items() def _on_view_selection_changed(self, view, selection_or_focus): self.component_registry.handle(DiagramSelectionChange(view, view.focused_item, view.selected_items)) def _on_drag_drop(self, view, context, x, y, time): print 'drag_drop on', context.targets if self.VIEW_DND_TARGETS[0][0] in context.targets: target = gtk.gdk.atom_intern(self.VIEW_DND_TARGETS[0][0]) view.drag_get_data(context, target, time) return True elif self.VIEW_DND_TARGETS[1][0] in context.targets: target = gtk.gdk.atom_intern(self.VIEW_DND_TARGETS[1][0]) view.drag_get_data(context, target, time) return True return False def _on_drag_data_received(self, view, context, x, y, data, info, time): """ Handle data dropped on the canvas. """ print 'DND data received', view if data and data.format == 8 and info == DiagramTab.VIEW_TARGET_TOOLBOX_ACTION: tool = self.toolbox.get_tool(data.data) tool.create_item((x, y)) context.finish(True, False, time) elif data and data.format == 8 and info == DiagramTab.VIEW_TARGET_ELEMENT_ID: #print 'drag_data_received:', data.data, info n, p = data.data.split('#') element = self.element_factory.lookup(n) assert element # TODO: use adapters to execute code below q = type(element) if p: q = q, p item_class = get_diagram_item(q) if isinstance(element, UML.Diagram): self.action_manager.execute('OpenModelElement') elif item_class: tx = Transaction() item = self.diagram.create(item_class) assert item x, y = view.get_matrix_v2i(item).transform_point(x, y) item.matrix.translate(x, y) item.subject = element tx.commit() view.unselect_all() view.focused_item = item else: log.warning('No graphical representation for UML element %s' % type(element).__name__) context.finish(True, False, time) #else: # context.finish(False, False, time) # vim: sw=4:et:ai gaphor-0.17.2/gaphor/ui/diagramtoolbox.py000066400000000000000000000474301220151210700203420ustar00rootroot00000000000000""" This module contains the actions used in the Toolbox (lower left section of the main window. The Toolbox is bound to a diagram. When a diagram page (tab) is switched, the actions bound to the toolbuttons should change as well. """ from zope import component from gaphor.UML.event import DiagramItemCreateEvent from gaphor import UML from gaphor.diagram import items from gaphor.core import _, inject, radio_action, build_action_group from diagramtools import PlacementTool, GroupPlacementTool, DefaultTool from gaphas.item import SE __all__ = [ 'DiagramToolbox', 'TOOLBOX_ACTIONS' ] # Actions: ((section (name, label, stock_id, shortcut)), ...) TOOLBOX_ACTIONS = ( ('', ( ('toolbox-pointer', _('Pointer'), 'gaphor-pointer', 'Escape'), ('toolbox-line', _('Line'), 'gaphor-line', 'l'), ('toolbox-box', _('Box'), 'gaphor-box', 'b'), ('toolbox-ellipse', _('Ellipse'), 'gaphor-ellipse', 'e'), ('toolbox-comment', _('Comment'), 'gaphor-comment', 'k'), ('toolbox-comment-line', _('Comment line'), 'gaphor-comment-line', 'K'), )), (_('Classes'), ( ('toolbox-class', _('Class'), 'gaphor-class', 'c'), ('toolbox-interface', _('Interface'), 'gaphor-interface', 'i'), ('toolbox-package', _('Package'), 'gaphor-package', 'p'), ('toolbox-association', _('Association'), 'gaphor-association', 'A'), ('toolbox-dependency', _('Dependency'), 'gaphor-dependency', 'D'), ('toolbox-generalization', _('Generalization'), 'gaphor-generalization', 'G'), ('toolbox-implementation', _('Implementation'), 'gaphor-implementation', 'I'), )), (_('Components'), ( ('toolbox-component', _('Component'), 'gaphor-component', 'o'), ('toolbox-artifact', _('Artifact'), 'gaphor-artifact', 'h'), ('toolbox-node', _('Node'), 'gaphor-node', 'n'), ('toolbox-device', _('Device'), 'gaphor-device', 'd'), ('toolbox-subsystem', _('Subsystem'), 'gaphor-subsystem', 'y'), ('toolbox-connector', _('Connector'), 'gaphor-connector', 'C'), )), (_('Actions'), ( ('toolbox-action', _('Action'), 'gaphor-action', 'a'), ('toolbox-initial-node', _('Initial node'), 'gaphor-initial-node', 'j'), ('toolbox-activity-final-node', _('Activity final node'), 'gaphor-activity-final-node', 'f'), ('toolbox-flow-final-node', _('Flow final node'), 'gaphor-flow-final-node', 'w'), ('toolbox-decision-node', _('Decision/merge node'), 'gaphor-decision-node', 'g'), ('toolbox-fork-node', _('Fork/join node'), 'gaphor-fork-node', 'R'), ('toolbox-object-node', _('Object node'), 'gaphor-object-node', 'O'), ('toolbox-partition', _('Partition'), 'gaphor-partition', 'P'), ('toolbox-flow', _('Control/object flow'), 'gaphor-control-flow', 'F'), ('toolbox-send-signal-action', _('Send signal action'), 'gaphor-send-signal-action', None), ('toolbox-accept-event-action', _('Accept event action'), 'gaphor-accept-event-action', None), )), (_('Interactions'), ( ('toolbox-lifeline', _('Lifeline'), 'gaphor-lifeline', 'v'), ('toolbox-message', _('Message'), 'gaphor-message', 'M'), ('toolbox-interaction', _('Interaction'), 'gaphor-interaction', 'N'), )), (_('States'), ( ('toolbox-state', _('State'), 'gaphor-state', 's'), ('toolbox-initial-pseudostate', _('Initial Pseudostate'), 'gaphor-initial-pseudostate', 'S'), ('toolbox-final-state', _('Final State'), 'gaphor-final-state', 'x'), ('toolbox-history-pseudostate', _('History Pseudostate'), 'gaphor-history-pseudostate', 'q'), ('toolbox-transition', _('Transition'), 'gaphor-transition', 'T'), )), (_('Use Cases'), ( ('toolbox-usecase', _('Use case'), 'gaphor-usecase', 'u'), ('toolbox-actor', _('Actor'), 'gaphor-actor', 't'), ('toolbox-usecase-association', _('Association'), 'gaphor-association', 'B'), ('toolbox-include', _('Include'), 'gaphor-include', 'U'), ('toolbox-extend', _('Extend'), 'gaphor-extend', 'X'), )), (_('Profiles'), ( ('toolbox-profile', _('Profile'), 'gaphor-profile', 'r'), ('toolbox-metaclass', _('Metaclass'), 'gaphor-metaclass', 'm'), ('toolbox-stereotype', _('Stereotype'), 'gaphor-stereotype', 'z'), ('toolbox-extension', _('Extension'), 'gaphor-extension', 'E'), )), ) def itemiter(toolbox_actions): """ Iterate toolbox items, irregardless section headers """ for name, section in toolbox_actions: for e in section: yield e class DiagramToolbox(object): """ Composite class for DiagramTab (diagramtab.py). """ element_factory = inject('element_factory') properties = inject('properties') def __init__(self, diagram, view): self.view = view self.diagram = diagram self.action_group = build_action_group(self) namespace = property(lambda s: s.diagram.namespace) def get_tool(self, tool_name): """ Return a tool associated with an id (action name). """ return getattr(self, tool_name.replace('-', '_'))() @radio_action(names=zip(*list(itemiter(TOOLBOX_ACTIONS)))[0], labels=zip(*list(itemiter(TOOLBOX_ACTIONS)))[1], stock_ids=zip(*list(itemiter(TOOLBOX_ACTIONS)))[2]) def _set_toolbox_action(self, id): """ Activate a tool based on its index in the TOOLBOX_ACTIONS list. """ tool_name = list(itemiter(TOOLBOX_ACTIONS))[id][0] self.view.tool = self.get_tool(tool_name) def _item_factory(self, item_class, subject_class=None, extra_func=None): """ ``extra_func`` may be a function accepting the newly created item. """ def factory_method(parent=None): if subject_class: subject = self.element_factory.create(subject_class) else: subject = None item = self.diagram.create(item_class, subject=subject, parent=parent) if extra_func: extra_func(item) return item factory_method.item_class = item_class return factory_method def _namespace_item_factory(self, item_class, subject_class, stereotype=None, name=None): """ Returns a factory method for Namespace classes. To be used by the PlacementTool. """ def factory_method(parent=None): subject = self.element_factory.create(subject_class) item = self.diagram.create(item_class, subject=subject, parent=parent) subject.package = self.namespace if name is not None: subject.name = name elif stereotype: subject.name = 'New%s' % stereotype.capitalize() else: subject.name = 'New%s' % subject_class.__name__ return item factory_method.item_class = item_class return factory_method def _after_handler(self, new_item): if self.properties('reset-tool-after-create', False): self.action_group.get_action('toolbox-pointer').activate() component.handle(DiagramItemCreateEvent(new_item)) ## ## Toolbox actions ## def toolbox_pointer(self): if self.view: return DefaultTool() def toolbox_line(self): return PlacementTool(self.view, item_factory=self._item_factory(items.Line), after_handler=self._after_handler) def toolbox_box(self): return PlacementTool(self.view, item_factory=self._item_factory(items.Box), handle_index=SE, after_handler=self._after_handler) def toolbox_ellipse(self): return PlacementTool(self.view, item_factory=self._item_factory(items.Ellipse), handle_index=SE, after_handler=self._after_handler) def toolbox_comment(self): return PlacementTool(self.view, item_factory=self._item_factory(items.CommentItem, UML.Comment), handle_index=SE, after_handler=self._after_handler) def toolbox_comment_line(self): return PlacementTool(self.view, item_factory=self._item_factory(items.CommentLineItem), after_handler=self._after_handler) # Classes: def toolbox_class(self): return PlacementTool(self.view, item_factory=self._namespace_item_factory(items.ClassItem, UML.Class), handle_index=SE, after_handler=self._after_handler) def toolbox_interface(self): return PlacementTool(self.view, item_factory=self._namespace_item_factory(items.InterfaceItem, UML.Interface), handle_index=SE, after_handler=self._after_handler) def toolbox_package(self): return PlacementTool(self.view, item_factory=self._namespace_item_factory(items.PackageItem, UML.Package), handle_index=SE, after_handler=self._after_handler) def toolbox_association(self): return PlacementTool(self.view, item_factory=self._item_factory(items.AssociationItem), after_handler=self._after_handler) def toolbox_dependency(self): return PlacementTool(self.view, item_factory=self._item_factory(items.DependencyItem), after_handler=self._after_handler) def toolbox_generalization(self): return PlacementTool(self.view, item_factory=self._item_factory(items.GeneralizationItem), after_handler=self._after_handler) def toolbox_implementation(self): return PlacementTool(self.view, item_factory=self._item_factory(items.ImplementationItem), after_handler=self._after_handler) # Components: def toolbox_component(self): return GroupPlacementTool(self.view, item_factory=self._namespace_item_factory(items.ComponentItem, UML.Component), handle_index=SE, after_handler=self._after_handler) def toolbox_artifact(self): return GroupPlacementTool(self.view, item_factory=self._namespace_item_factory(items.ArtifactItem, UML.Artifact), handle_index=SE, after_handler=self._after_handler) def toolbox_node(self): return GroupPlacementTool(self.view, item_factory=self._namespace_item_factory(items.NodeItem, UML.Node), handle_index=SE, after_handler=self._after_handler) def toolbox_device(self): return GroupPlacementTool(self.view, item_factory=self._namespace_item_factory(items.NodeItem, UML.Device), handle_index=SE, after_handler=self._after_handler) def toolbox_subsystem(self): return GroupPlacementTool(self.view, item_factory=self._namespace_item_factory(items.SubsystemItem, UML.Component, 'subsystem'), handle_index=SE, after_handler=self._after_handler) def toolbox_connector(self): return PlacementTool(self.view, item_factory=self._item_factory(items.ConnectorItem), after_handler=self._after_handler) # Actions: def toolbox_action(self): return GroupPlacementTool(self.view, item_factory=self._namespace_item_factory(items.ActionItem, UML.Action), handle_index=SE, after_handler=self._after_handler) def toolbox_send_signal_action(self): return GroupPlacementTool(self.view, item_factory=self._namespace_item_factory(items.SendSignalActionItem, UML.SendSignalAction), handle_index=SE, after_handler=self._after_handler) def toolbox_accept_event_action(self): return GroupPlacementTool(self.view, item_factory=self._namespace_item_factory(items.AcceptEventActionItem, UML.AcceptEventAction), handle_index=SE, after_handler=self._after_handler) def toolbox_initial_node(self): return GroupPlacementTool(self.view, item_factory=self._item_factory(items.InitialNodeItem, UML.InitialNode), handle_index=SE, after_handler=self._after_handler) def toolbox_activity_final_node(self): return GroupPlacementTool(self.view, item_factory=self._item_factory(items.ActivityFinalNodeItem, UML.ActivityFinalNode), handle_index=SE, after_handler=self._after_handler) def toolbox_flow_final_node(self): return GroupPlacementTool(self.view, item_factory=self._item_factory(items.FlowFinalNodeItem, UML.FlowFinalNode), handle_index=SE, after_handler=self._after_handler) def toolbox_decision_node(self): return GroupPlacementTool(self.view, item_factory=self._item_factory(items.DecisionNodeItem, UML.DecisionNode), handle_index=SE, after_handler=self._after_handler) def toolbox_fork_node(self): return GroupPlacementTool(self.view, item_factory=self._item_factory(items.ForkNodeItem, UML.JoinNode), handle_index=1, after_handler=self._after_handler) def toolbox_object_node(self): return GroupPlacementTool(self.view, item_factory=self._namespace_item_factory(items.ObjectNodeItem, UML.ObjectNode), handle_index=SE, after_handler=self._after_handler) def toolbox_partition(self): # note no subject, which is created by grouping adapter return GroupPlacementTool(self.view, item_factory=self._item_factory(items.PartitionItem), handle_index=SE, after_handler=self._after_handler) def toolbox_flow(self): return PlacementTool(self.view, item_factory=self._item_factory(items.FlowItem), after_handler=self._after_handler) # Interactions: def toolbox_interaction(self): return PlacementTool(self.view, item_factory=self._namespace_item_factory(items.InteractionItem, UML.Interaction), handle_index=SE, after_handler=self._after_handler) def toolbox_lifeline(self): return GroupPlacementTool(self.view, item_factory=self._namespace_item_factory(items.LifelineItem, UML.Lifeline), handle_index=SE, after_handler=self._after_handler) def toolbox_message(self): return PlacementTool(self.view, item_factory=self._item_factory(items.MessageItem), after_handler=self._after_handler) # States: def toolbox_state(self): return PlacementTool(self.view, item_factory=self._namespace_item_factory(items.StateItem, UML.State), handle_index=SE, after_handler=self._after_handler) def _toolbox_pseudostate(self, kind): def set_state(item): item.subject.kind = kind return PlacementTool(self.view, item_factory=self._item_factory(items.InitialPseudostateItem, UML.Pseudostate, set_state), handle_index=SE, after_handler=self._after_handler) def toolbox_initial_pseudostate(self): def set_state(item): item.subject.kind = 'initial' return PlacementTool(self.view, item_factory=self._item_factory(items.InitialPseudostateItem, UML.Pseudostate, set_state), handle_index=SE, after_handler=self._after_handler) def toolbox_history_pseudostate(self): def set_state(item): item.subject.kind = 'shallowHistory' return PlacementTool(self.view, item_factory=self._item_factory(items.HistoryPseudostateItem, UML.Pseudostate, set_state), handle_index=SE, after_handler=self._after_handler) def toolbox_final_state(self): return PlacementTool(self.view, item_factory=self._item_factory(items.FinalStateItem, UML.FinalState), handle_index=SE, after_handler=self._after_handler) def toolbox_transition(self): return PlacementTool(self.view, item_factory=self._item_factory(items.TransitionItem), after_handler=self._after_handler) # Use cases: def toolbox_usecase(self): return GroupPlacementTool(self.view, item_factory=self._namespace_item_factory(items.UseCaseItem, UML.UseCase), handle_index=SE, after_handler=self._after_handler) def toolbox_actor(self): return PlacementTool(self.view, item_factory=self._namespace_item_factory(items.ActorItem, UML.Actor), handle_index=SE, after_handler=self._after_handler) def toolbox_usecase_association(self): return PlacementTool(self.view, item_factory=self._item_factory(items.AssociationItem), after_handler=self._after_handler) def toolbox_include(self): return PlacementTool(self.view, item_factory=self._item_factory(items.IncludeItem), after_handler=self._after_handler) def toolbox_extend(self): return PlacementTool(self.view, item_factory=self._item_factory(items.ExtendItem), after_handler=self._after_handler) # Profiles: def toolbox_profile(self): return PlacementTool(self.view, item_factory=self._namespace_item_factory(items.PackageItem, UML.Profile), handle_index=SE, after_handler=self._after_handler) def toolbox_metaclass(self): return PlacementTool(self.view, item_factory=self._namespace_item_factory(items.MetaclassItem, UML.Class, 'metaclass', name='Class'), handle_index=SE, after_handler=self._after_handler) def toolbox_stereotype(self): return PlacementTool(self.view, item_factory=self._namespace_item_factory(items.ClassItem, UML.Stereotype), handle_index=SE, after_handler=self._after_handler) def toolbox_extension(self): return PlacementTool(self.view, item_factory=self._item_factory(items.ExtensionItem), after_handler=self._after_handler) # vim:sw=4:et:ai gaphor-0.17.2/gaphor/ui/diagramtools.py000066400000000000000000000366331220151210700200170ustar00rootroot00000000000000""" Tools for handling items on the canvas. Although Gaphas has quite a few useful tools, some tools need to be extended: - PlacementTool: should perform undo - HandleTool: shoudl support adapter based connection protocol - TextEditTool: should support adapter based edit protocol """ import gtk from zope import component from gaphas.geometry import distance_point_point, distance_point_point_fast, \ distance_line_point, distance_rectangle_point from gaphas.tool import Tool, HandleTool, PlacementTool as _PlacementTool, \ ToolChain, HoverTool, ItemTool, RubberbandTool, ConnectHandleTool from gaphas.aspect import Connector, InMotion from gaphas.guide import GuidedItemInMotion from gaphor.core import inject, Transaction, transactional from gaphor.diagram.interfaces import IEditor, IConnect, IGroup from gaphor.diagram.diagramline import DiagramLine from gaphor.diagram.elementitem import ElementItem # cursor to indicate grouping IN_CURSOR = gtk.gdk.Cursor(gtk.gdk.DIAMOND_CROSS) # cursor to indicate ungrouping OUT_CURSOR = gtk.gdk.Cursor(gtk.gdk.SIZING) @Connector.when_type(DiagramLine) class DiagramItemConnector(Connector.default): """ Handle Tool (acts on item handles) that uses the IConnect protocol to connect items to one-another. It also adds handles to lines when a line is grabbed on the middle of a line segment (points are drawn by the LineSegmentPainter). """ def allow(self, sink): adapter = component.queryMultiAdapter((sink.item, self.item), IConnect) return adapter and adapter.allow(self.handle, sink.port) @transactional def connect(self, sink): """ Create connection at handle level and at model level. """ handle = self.handle item = self.item cinfo = item.canvas.get_connection(handle) try: callback = DisconnectHandle(self.item, self.handle) if cinfo and cinfo.connected is sink.item: # reconnect only constraint - leave model intact log.debug('performing reconnect constraint') constraint = sink.port.constraint(item.canvas, item, handle, sink.item) item.canvas.reconnect_item(item, handle, constraint=constraint) elif cinfo: # first disconnect but disable disconnection handle as # reconnection is going to happen adapter = component.queryMultiAdapter((sink.item, item), IConnect) try: connect = adapter.reconnect except AttributeError: connect = adapter.connect else: cinfo.callback.disable = True self.disconnect() # new connection self.connect_handle(sink, callback=callback) # adapter requires both ends to be connected. connect(handle, sink.port) else: # new connection adapter = component.queryMultiAdapter((sink.item, item), IConnect) self.connect_handle(sink, callback=callback) adapter.connect(handle, sink.port) except Exception, e: log.error('Error during connect', exc_info=True) @transactional def disconnect(self): super(DiagramItemConnector, self).disconnect() class DisconnectHandle(object): """ Callback for items disconnection using the adapters. This is an object so disconnection data can be serialized/deserialized using pickle. :Variables: item Connecting item. handle Handle of connecting item. disable If set, then disconnection is disabled. """ def __init__(self, item, handle): self.item = item self.handle = handle self.disable = False def __call__(self): handle = self.handle item = self.item canvas = self.item.canvas cinfo = canvas.get_connection(handle) if self.disable: log.debug('Not disconnecting %s.%s (disabled)' % (item, handle)) else: log.debug('Disconnecting %s.%s' % (item, handle)) if cinfo: adapter = component.queryMultiAdapter((cinfo.connected, item), IConnect) adapter.disconnect(handle) class TextEditTool(Tool): """ Text edit tool. Allows for elements that can adapt to the IEditable interface to be edited. """ def create_edit_window(self, x, y, text, editor): """ Create a popup window with some editable text. """ view = self.view window = gtk.Window() window.set_property('decorated', False) window.set_property('skip-taskbar-hint', True) window.set_resize_mode(gtk.RESIZE_IMMEDIATE) #window.set_modal(True) window.set_parent_window(view.window) buffer = gtk.TextBuffer() if text: buffer.set_text(text) startiter, enditer = buffer.get_bounds() buffer.move_mark_by_name('selection_bound', startiter) buffer.move_mark_by_name('insert', enditer) text_view = gtk.TextView() text_view.set_buffer(buffer) #text_view.set_border_width(2) text_view.set_left_margin(2) text_view.set_right_margin(2) text_view.show() frame = gtk.Frame() frame.set_shadow_type(gtk.SHADOW_IN) #frame.set_border_width(1) frame.add(text_view) frame.show() window.add(frame) #window.set_border_width(1) window.size_allocate(gtk.gdk.Rectangle(int(x), int(y), 50, 50)) #window.move(int(x), int(y)) cursor_pos = view.get_toplevel().get_screen().get_display().get_pointer() window.move(cursor_pos[1], cursor_pos[2]) window.connect('focus-out-event', self._on_focus_out_event, buffer, editor) text_view.connect('key-press-event', self._on_key_press_event, buffer, editor) #text_view.set_size_request(50, 50) window.show() #text_view.grab_focus() #window.set_uposition(event.x, event.y) #window.focus @transactional def submit_text(self, widget, buffer, editor): """ Submit the final text to the edited item. """ text = buffer.get_text(buffer.get_start_iter(), buffer.get_end_iter()) editor.update_text(text) widget.get_toplevel().destroy() def on_double_click(self, event): view = self.view item = view.hovered_item if item: try: editor = IEditor(item) except TypeError: # Could not adapt to IEditor return False log.debug('Found editor %r' % editor) x, y = view.get_matrix_v2i(item).transform_point(event.x, event.y) if editor.is_editable(x, y): text = editor.get_text() # get item at cursor self.create_edit_window(event.x, event.y, text, editor) return True def _on_key_press_event(self, widget, event, buffer, editor): if event.keyval == gtk.keysyms.Return and \ not event.state & (gtk.gdk.CONTROL_MASK | gtk.gdk.SHIFT_MASK): self.submit_text(widget, buffer, editor) elif event.keyval == gtk.keysyms.Escape: widget.get_toplevel().destroy() def _on_focus_out_event(self, widget, event, buffer, editor): self.submit_text(widget, buffer, editor) class PlacementTool(_PlacementTool): """ PlacementTool is used to place items on the canvas. """ def __init__(self, view, item_factory, after_handler=None, handle_index=-1): """ item_factory is a callable. It is used to create a CanvasItem that is displayed on the diagram. """ _PlacementTool.__init__(self, view, factory=item_factory, handle_tool=ConnectHandleTool(), handle_index=handle_index) self.after_handler = after_handler self._tx = None @transactional def create_item(self, pos): return self._create_item(pos) def on_button_press(self, event): assert not self._tx self._tx = Transaction() view = self.view view.unselect_all() if _PlacementTool.on_button_press(self, event): try: opposite = self.new_item.opposite(self.new_item.handles()[self._handle_index]) except (KeyError, AttributeError): pass else: # Connect opposite handle first, using the HandleTool's # mechanisms # First make sure all matrices are updated: view.canvas.update_matrix(self.new_item) view.update_matrix(self.new_item) vpos = event.x, event.y item = self.handle_tool.glue(self.new_item, opposite, vpos) if item: self.handle_tool.connect(self.new_item, opposite, vpos) return True return False def on_button_release(self, event): try: if self.after_handler: self.after_handler(self.new_item) return _PlacementTool.on_button_release(self, event) finally: self._tx.commit() self._tx = None class GroupPlacementTool(PlacementTool): """ Try to group items when placing them on diagram. """ def __init__(self, view, item_factory, after_handler=None, handle_index=-1): super(GroupPlacementTool, self).__init__(view, item_factory, after_handler, handle_index) self._parent = None def on_motion_notify(self, event): """ Change parent item to dropzone state if it can accept diagram item object to be created. """ view = self.view if view.focused_item: view.unselect_item(view.focused_item) view.focused_item = None try: parent = view.get_item_at_point((event.x, event.y)) except KeyError: parent = None if parent: # create dummy adapter adapter = component.queryMultiAdapter((parent, self._factory.item_class()), IGroup) if adapter and adapter.can_contain(): view.dropzone_item = parent view.window.set_cursor(IN_CURSOR) self._parent = parent else: view.dropzone_item = None view.window.set_cursor(None) self._parent = None parent.request_update(matrix=False) else: if view.dropzone_item: view.dropzone_item.request_update(matrix=False) view.dropzone_item = None view.window.set_cursor(None) def _create_item(self, pos, **kw): """ Create diagram item and place it within parent's boundaries. """ parent = self._parent view = self.view try: adapter = component.queryMultiAdapter((parent, self._factory.item_class()), IGroup) if parent and adapter and adapter.can_contain(): kw['parent'] = parent item = super(GroupPlacementTool, self)._create_item(pos, **kw) adapter = component.queryMultiAdapter((parent, item), IGroup) if parent and item and adapter: adapter.group() canvas = view.canvas parent.request_update(matrix=False) finally: self._parent = None view.dropzone_item = None view.window.set_cursor(None) return item @InMotion.when_type(ElementItem) class DropZoneInMotion(GuidedItemInMotion): def move(self, pos): """ Move the item. x and y are in view coordinates. """ super(DropZoneInMotion, self).move(pos) item = self.item view = self.view x, y = pos current_parent = view.canvas.get_parent(item) over_item = view.get_item_at_point((x, y), selected=False) if not over_item: view.dropzone_item = None view.window.set_cursor(None) return if current_parent and not over_item: # are we going to remove from parent? adapter = component.queryMultiAdapter((current_parent, item), IGroup) if adapter: view.window.set_cursor(OUT_CURSOR) view.dropzone_item = current_parent current_parent.request_update(matrix=False) if over_item: # are we going to add to parent? adapter = component.queryMultiAdapter((over_item, item), IGroup) if adapter and adapter.can_contain(): view.dropzone_item = over_item view.window.set_cursor(IN_CURSOR) over_item.request_update(matrix=False) def stop_move(self): """ Motion stops: drop! """ super(DropZoneInMotion, self).stop_move() item = self.item view = self.view canvas = view.canvas old_parent = view.canvas.get_parent(item) new_parent = view.dropzone_item try: if new_parent is old_parent: if old_parent is not None: old_parent.request_update(matrix=False) return if old_parent: adapter = component.queryMultiAdapter((old_parent, item), IGroup) if adapter: adapter.ungroup() canvas.reparent(item, None) m = canvas.get_matrix_i2c(old_parent) item.matrix *= m old_parent.request_update() if new_parent: adapter = component.queryMultiAdapter((new_parent, item), IGroup) if adapter and adapter.can_contain(): adapter.group() canvas.reparent(item, new_parent) m = canvas.get_matrix_c2i(new_parent) item.matrix *= m new_parent.request_update() finally: view.dropzone_item = None view.window.set_cursor(None) class TransactionalToolChain(ToolChain): """ In addition to a normal toolchain, this chain begins an undo-transaction at button-press and commits the transaction at button-release. """ def __init__(self, view=None): super(TransactionalToolChain, self).__init__(view) self._tx = None def handle(self, event): # For double click: button_press, double_click, button_release #print 'event', self.EVENT_HANDLERS.get(event.type) if self.EVENT_HANDLERS.get(event.type) in ('on_button_press',): assert not self._tx self._tx = Transaction() try: super(TransactionalToolChain, self).handle(event) finally: if self._tx and self.EVENT_HANDLERS.get(event.type) in ('on_button_release', 'on_double_click', 'on_triple_click'): self._tx.commit() self._tx = None def DefaultTool(): """ The default tool chain build from HoverTool, ItemTool and HandleTool. """ chain = TransactionalToolChain() chain.append(HoverTool()) chain.append(ConnectHandleTool()) chain.append(ItemTool()) chain.append(TextEditTool()) chain.append(RubberbandTool()) return chain # vim:sw=4:et:ai gaphor-0.17.2/gaphor/ui/elementeditor.py000066400000000000000000000044551220151210700201670ustar00rootroot00000000000000"""The element editor is a utility window used for editing elements.""" import gtk from zope import interface from gaphor.interfaces import IService, IActionProvider from gaphor.core import _, inject, open_action, build_action_group from gaphor.ui.propertyeditor import PropertyEditor from gaphor.ui.interfaces import IUIComponent class ElementEditor(object): """The ElementEditor class is a utility window used to edit UML elements. It will display the properties of the currently selected element in the diagram.""" interface.implements(IUIComponent, IActionProvider) element_factory = inject('element_factory') properties = inject('properties') title = _("Element Editor") size = (275, -1) resizable = True placement = 'floating' menu_xml = """ """ def __init__(self): """Constructor. Build the action group for the element editor window. This will place a button for opening the window in the toolbar. The widget attribute is a PropertyEditor.""" self.action_group = build_action_group(self) self.property_editor = PropertyEditor() self.widget = self.property_editor.construct() @open_action(name='ElementEditor:open', label=_('Editor'), stock_id='gtk-edit', accel='e') def open_elementeditor(self): """Display the element editor when the toolbar button is toggled. If active, the element editor is displayed. Otherwise, it is hidden.""" if not self.widget.get_parent(): return self def open(self): """Display and return the PropertyEditor widget.""" self.widget.show() return self.widget def close(self): """Hide the element editor window and deactivate the toolbar button. Both the widget and event parameters default to None and are idempotent if set.""" log.debug('ElementEditor.close') #self.action_group.get_action('ElementEditor:open').set_active(False) self.widget.unparent() #self.dock_item.destroy() #return True # vim:sw=4:et:ai gaphor-0.17.2/gaphor/ui/event.py000066400000000000000000000010401220151210700164330ustar00rootroot00000000000000from zope import interface from interfaces import * class DiagramTabChange(object): interface.implements(IDiagramTabChange) def __init__(self, item): self.item = item self.diagram_tab = item.diagram_tab class DiagramSelectionChange(object): interface.implements(IDiagramSelectionChange) def __init__(self, diagram_view, focused_item, selected_items): self.diagram_view = diagram_view self.focused_item = focused_item self.selected_items = selected_items # vim:sw=4:et:ai gaphor-0.17.2/gaphor/ui/filedialog.py000066400000000000000000000052651220151210700174260ustar00rootroot00000000000000"""This module has a generic FileDialog class that is used to open or save files.""" import gtk class FileDialog(object): """This is a file dialog that is used to open or save a file.""" def __init__(self, title, filename=None, action='open', parent=None, multiple=False, filters=[]): """Initialize the file dialog. The title parameter is the title displayed in the dialog. The filename parameter will set the current file name in the dialog. The action is either open or save and changes the buttons isplayed. If the parent window parameter is supplied, the file dialog is set to be transient for that window. The multiple parameter should be set to true if sultiple files can be opened at once. This means that a list of filenames instead of a single filename string will be returned by the selection property. The filters is a list of dictionaries that have a name and pattern key. This restricts what is visible in the dialog.""" self.multiple = multiple if action == 'open': action = gtk.FILE_CHOOSER_ACTION_OPEN response_button = gtk.STOCK_OPEN else: action = gtk.FILE_CHOOSER_ACTION_SAVE response_button = gtk.STOCK_SAVE buttons = (gtk.STOCK_CANCEL,\ gtk.RESPONSE_CANCEL,\ response_button,\ gtk.RESPONSE_OK) self.dialog = gtk.FileChooserDialog(title=title, action=action, buttons=buttons) if parent: self.dialog.set_transient_for(parent) if filename: self.dialog.set_current_name(filename) for filter in filters: _filter = gtk.FileFilter() _filter.set_name(filter['name']) _filter.add_pattern(filter['pattern']) self.dialog.add_filter(_filter) def get_selection(self): """Return the selected file or files from the dialog. This is used by the selection property.""" response = self.dialog.run() selection = None if response == gtk.RESPONSE_OK: if self.multiple: selection = self.dialog.get_filenames() else: selection = self.dialog.get_filename() return selection def destroy(self): """Destroy the GTK dialog.""" self.dialog.destroy() selection = property(get_selection) gaphor-0.17.2/gaphor/ui/iconoption.py000066400000000000000000000013621220151210700175020ustar00rootroot00000000000000""" Module dealing with options (typing) of icons. """ from gaphor import UML from simplegeneric import generic @generic def get_icon_option(element): """ Default behaviour: no options. """ return @get_icon_option.when_type(UML.Class) def get_option_class(element): if element.extension: return 'metaclass' @get_icon_option.when_type(UML.Component) def get_option_component(element): for p in element.presentation: try: if p.__stereotype__ == 'subsystem': return 'subsystem' except AttributeError: pass @get_icon_option.when_type(UML.Property) def get_option_property(element): if element.association: return 'association-end' # vim:sw=4:et:ai gaphor-0.17.2/gaphor/ui/icons.xml000066400000000000000000000122601220151210700166030ustar00rootroot00000000000000 pointer.png line.png box.png ellipse.png activity-final-node.png ActivityFinalNode action.png Action send-signal-action.png SendSignalAction accept-event-action.png AcceptEventAction actor.png Actor association.png Association artifact.png Artifact class.png Class class.png Class comment.png Comment comment-line.png component.png Component connector.png Connector control-flow.png ControlFlow decision-node.png DecisionNode fork-node.png ForkNode object-node.png ObjectNode flow-final-node.png FlowFinalNode dependency.png Dependency diagram.png Diagram extend.png Extend extension.png Extension generalization.png Generalization implementation.png Implementation include.png Include initial-node.png InitialNode interaction.png Interaction interface.png Interface lifeline.png Lifeline message.png Message node.png Node device.png Device operation.png Operation package.png Package pointer.png Parameter profile.png Profile attribute.png Property association.png Property state.png State final-state.png FinalState pseudostate.png Pseudostate transition.png Transition stereotype.png Stereotype use-case.png UseCase subsystem.png Component activity-partition.png ActivityPartition gaphor-0.17.2/gaphor/ui/interfaces.py000066400000000000000000000034151220151210700174450ustar00rootroot00000000000000""" Interfaces related to the user interface. """ from zope import interface class IDiagramTabChange(interface.Interface): """ The selected diagram changes. """ item = interface.Attribute('The newly selected DockItem') diagram_tab = interface.Attribute('The newly selected diagram tab') class IDiagramSelectionChange(interface.Interface): """ The selection of a diagram changed. """ diagram_view = interface.Attribute('The diagram View that emits the event') focused_item = interface.Attribute('The diagram item that received focus') selected_items = interface.Attribute('All selected items in the diagram') class IUIComponent(interface.Interface): """ A user interface component. """ ui_name = interface.Attribute('The UIComponent name, provided by the loader') title = interface.Attribute('Title of the component') size = interface.Attribute('Size used for floating the component') placement = interface.Attribute('placement. E.g. ("left", "diagrams")') def open(self): """ Create and display the UI components (windows). """ def close(self): """ Close the UI component. The component can decide to hide or destroy the UI components. """ class IPropertyPage(interface.Interface): """ A property page which can display itself in a notebook """ order = interface.Attribute('Order number, used for ordered display') def construct(self): """ Create the page (gtk.Widget) that belongs to the Property page. Returns the page's toplevel widget (gtk.Widget). """ def destroy(self): """ Destroy the page and clean up signal handlers and stuff. """ # vim:sw=4:et:ai gaphor-0.17.2/gaphor/ui/layout.py000066400000000000000000000141441220151210700166400ustar00rootroot00000000000000# -*- coding: utf-8 -*- # vim:sw=4:et:ai # Copyright © 2010 etk.docking Contributors # # This file is part of etk.docking. # # etk.docking is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # etk.docking is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with etk.docking. If not, see . from __future__ import absolute_import import sys from simplegeneric import generic from xml.etree.ElementTree import Element, SubElement, tostring, fromstring import gtk from etk.docking import DockFrame, DockPaned, DockGroup, DockItem from gaphor.core import _ SERIALIZABLE = ( DockFrame, DockPaned, DockGroup, DockItem ) def serialize(layout): def _ser(widget, element): if isinstance(widget, SERIALIZABLE): sub = SubElement(element, type(widget).__name__.lower() , attributes(widget)) widget.foreach(_ser, sub) else: sub = SubElement(element, 'widget', attributes(widget)) tree = Element('layout') map(_ser, layout.frames, [tree] * len(layout.frames)) return tostring(tree, encoding=sys.getdefaultencoding()) widget_factory = {} def deserialize(layout, container, layoutstr, itemfactory): ''' Return a new layout with it's attached frames. Frames that should be floating already have their gtk.Window attached (check frame.get_parent()). Transient settings and such should be done by the invoking application. ''' def _des(element, parent_widget=None): if element.tag == 'widget': name = element.attrib['name'] widget = itemfactory(name) widget.set_name(name) parent_widget.add(widget) else: factory = widget_factory[element.tag] widget = factory(parent=parent_widget, **element.attrib) assert widget, 'No widget (%s)' % widget if len(element): map(_des, element, [widget] * len(element)) return widget tree = fromstring(layoutstr) map(layout.add, map(_des, tree, [ container ] * len(tree))) return layout def parent_attributes(widget): """ Add properties defined in the parent widget specific for this instance (like weight). """ container = widget.get_parent() d = {} if isinstance(container, DockPaned): paned_item = [i for i in container._items if i.child is widget][0] if paned_item.weight: d['weight'] = str(int(paned_item.weight * 100)) return d @generic def attributes(widget): raise NotImplementedError @attributes.when_type(gtk.Widget) def widget_attributes(widget): return { 'name': widget.get_name() or 'empty' } @attributes.when_type(DockItem) def dock_item_attributes(widget): d = { 'title': widget.props.title, 'tooltip': widget.props.title_tooltip_text } if widget.props.icon_name: d['icon_name'] = widget.props.icon_name if widget.props.stock: d['stock_id'] = widget.props.stock return d @attributes.when_type(DockGroup) def dock_group_attributes(widget): d = parent_attributes(widget) name = widget.get_name() if name != widget.__gtype__.name: d['name'] = name return d @attributes.when_type(DockPaned) def dock_paned_attributes(widget): return dict(orientation=(widget.get_orientation() == gtk.ORIENTATION_HORIZONTAL and 'horizontal' or 'vertical'), **parent_attributes(widget)) @attributes.when_type(DockFrame) def dock_frame_attributes(widget): a = widget.allocation d = dict(width=str(a.width), height=str(a.height)) parent = widget.get_parent() if isinstance(parent, gtk.Window) and parent.get_transient_for(): d['floating'] = 'true' d['x'], d['y'] = map(str, parent.get_position()) return d def factory(typename): ''' Simple decorator for populating the widget_factory dictionary. ''' def _factory(func): widget_factory[typename] = func return func return _factory @factory('dockitem') def dock_item_factory(parent, title, tooltip, icon_name=None, stock_id=None, pos=None, vispos=None, current=None, name=None): item = DockItem(_(title), _(tooltip), icon_name, stock_id) if name: item.set_name(name) if pos: pos = int(pos) if vispos: vispos = int(vispos) parent.insert_item(item, pos, vispos) item.show() return item @factory('dockgroup') def dock_group_factory(parent, weight=None, name=None): group = DockGroup() if name: group.set_name(name) if weight is not None: parent.insert_item(group, weight=float(weight) / 100.) else: parent.add(group) group.show() return group @factory('dockpaned') def dock_paned_factory(parent, orientation, weight=None, name=None): paned = DockPaned() if name: paned.set_name(name) if orientation == 'horizontal': paned.set_orientation(gtk.ORIENTATION_HORIZONTAL) else: paned.set_orientation(gtk.ORIENTATION_VERTICAL) if weight is not None: item = parent.insert_item(paned, weight=float(weight) / 100.) else: parent.add(paned) paned.show() return paned @factory('dockframe') def dock_frame_factory(parent, width, height, floating=None, x=None, y=None): frame = DockFrame() frame.set_size_request(int(width), int(height)) if floating == 'true': window = gtk.Window(gtk.WINDOW_TOPLEVEL) #self.window.set_type_hint(gdk.WINDOW_TYPE_HINT_UTILITY) window.set_property('skip-taskbar-hint', True) window.move(int(x), int(y)) window.add(frame) window.set_transient_for(parent) window.show() else: parent.add(frame) frame.show() return frame gaphor-0.17.2/gaphor/ui/layout.xml000066400000000000000000000012161220151210700170040ustar00rootroot00000000000000 gaphor-0.17.2/gaphor/ui/mainwindow.py000066400000000000000000000762121220151210700175030ustar00rootroot00000000000000""" The main application window. """ import os.path import gobject, gtk from logging import getLogger import pkg_resources from zope import interface, component from gaphor.interfaces import IService, IActionProvider from interfaces import IUIComponent from etk.docking import DockLayout, DockGroup, DockItem from etk.docking.docklayout import add_new_group_floating from gaphor import UML from gaphor.core import _, inject, action, toggle_action, open_action, build_action_group, transactional from namespace import NamespaceModel, NamespaceView from diagramtab import DiagramTab from toolbox import Toolbox as _Toolbox from diagramtoolbox import TOOLBOX_ACTIONS from etk.docking import DockItem, DockGroup, add_new_group_left, add_new_group_right, \ add_new_group_above, add_new_group_below, add_new_group_floating, settings from layout import deserialize from interfaces import IDiagramTabChange from gaphor.interfaces import IServiceEvent, IActionExecutedEvent from gaphor.UML.event import ModelFactoryEvent from event import DiagramTabChange, DiagramSelectionChange from gaphor.services.filemanager import FileManagerStateChanged from gaphor.services.undomanager import UndoManagerStateChanged from gaphor.ui.accelmap import load_accel_map, save_accel_map logger = getLogger(name='MainWindow') ICONS = ( 'gaphor-24x24.png', 'gaphor-48x48.png', 'gaphor-96x96.png', 'gaphor-256x256.png', ) settings['diagrams'].expand = True settings['diagrams'].auto_remove = False settings['diagrams'].inherit_settings = False settings['EtkDockGroup'].expand = False settings['EtkDockPaned'].expand = False STATIC_MENU_XML = """ """ class MainWindow(object): """ The main window for the application. It contains a Namespace-based tree view and a menu and a statusbar. """ interface.implements(IService, IActionProvider) component_registry = inject('component_registry') properties = inject('properties') element_factory = inject('element_factory') action_manager = inject('action_manager') file_manager = inject('file_manager') ui_manager = inject('ui_manager') title = 'Gaphor' size = property(lambda s: s.properties.get('ui.window-size', (760, 580))) menubar_path = '/mainwindow' toolbar_path = '/mainwindow-toolbar' resizable = True menu_xml = """ """ def __init__(self): self.window = None self.model_changed = False # Map tab contents to DiagramTab #self.notebook_map = {} self._current_diagram_tab = None self.layout = None def init(self, app=None): #self.init_pygtk() self.init_stock_icons() self.init_action_group() self.init_ui_components() def init_pygtk(self): """ Make sure we have GTK+ >= 2.0 """ import pygtk pygtk.require('2.0') del pygtk def init_stock_icons(self): # Load stock items import gaphor.ui.stock gaphor.ui.stock.load_stock_icons() def init_ui_components(self): component_registry = self.component_registry for ep in pkg_resources.iter_entry_points('gaphor.uicomponents'): log.debug('found entry point uicomponent.%s' % ep.name) cls = ep.load() if not IUIComponent.implementedBy(cls): raise NameError, 'Entry point %s doesn''t provide IUIComponent' % ep.name uicomp = cls() uicomp.ui_name = ep.name component_registry.register_utility(uicomp, IUIComponent, ep.name) if IActionProvider.providedBy(uicomp): self.action_manager.register_action_provider(uicomp) def shutdown(self): if self.window: self.window.destroy() self.window = None save_accel_map() cr = self.component_registry cr.unregister_handler(self._on_file_manager_state_changed) cr.unregister_handler(self._on_undo_manager_state_changed) cr.unregister_handler(self._new_model_content) #self.ui_manager.remove_action_group(self.action_group) def init_action_group(self): self.action_group = build_action_group(self) for name, label in (('file', '_File'), ('file-export', '_Export'), ('file-import', '_Import'), ('edit', '_Edit'), ('diagram', '_Diagram'), ('tools', '_Tools'), ('window', '_Window'), ('help', '_Help')): a = gtk.Action(name, label, None, None) a.set_property('hide-if-empty', False) self.action_group.add_action(a) self._tab_ui_settings = None self.action_group.get_action('diagram-drawing-style').set_active(self.properties('diagram.sloppiness', 0) != 0) self.action_manager.register_action_provider(self) def get_filename(self): """ Return the file name of the currently opened model. """ return self.file_manager.filename def get_current_diagram_tab(self): """ Get the currently opened and viewed DiagramTab, shown on the right side of the main window. See also: get_current_diagram(), get_current_diagram_view(). """ return self._current_diagram_tab def get_current_diagram(self): """ Return the Diagram associated with the viewed DiagramTab. See also: get_current_diagram_tab(), get_current_diagram_view(). """ tab = self._current_diagram_tab return tab and tab.get_diagram() def get_current_diagram_view(self): """ Return the DiagramView associated with the viewed DiagramTab. See also: get_current_diagram_tab(), get_current_diagram(). """ tab = self._current_diagram_tab return tab and tab.get_view() def ask_to_close(self): """ Ask user to close window if the model has changed. The user is asked to either discard the changes, keep the application running or save the model and quit afterwards. """ if self.model_changed: dialog = gtk.MessageDialog(self.window, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_WARNING, gtk.BUTTONS_NONE, _('Save changed to your model before closing?')) dialog.format_secondary_text( _('If you close without saving, your changes will be discarded.')) dialog.add_buttons('Close _without saving', gtk.RESPONSE_REJECT, gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_SAVE, gtk.RESPONSE_YES) dialog.set_default_response(gtk.RESPONSE_YES) response = dialog.run() dialog.destroy() if response == gtk.RESPONSE_YES: # On filedialog.cancel, the application should not close. return self.file_manager.action_save() return response == gtk.RESPONSE_REJECT return True def show_diagram(self, diagram): """ Show a Diagram element in a new tab. If a tab is already open, show that one instead. """ # Try to find an existing window/tab and let it get focus: for tab in self.get_tabs(): if tab.get_diagram() is diagram: self.set_current_page(tab) return tab tab = DiagramTab(diagram) dock_item = tab.construct() dock_item.set_name('diagram-tab') dock_item.diagram_tab = tab assert dock_item.get_name() == 'diagram-tab' tab.set_drawing_style(self.properties('diagram.sloppiness', 0)) self.add_tab(dock_item) return tab def open(self): load_accel_map() self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.set_title(self.title) self.window.set_size_request(*self.size) self.window.set_resizable(self.resizable) # set default icons of gaphor windows icon_dir = os.path.abspath(pkg_resources.resource_filename('gaphor.ui', 'pixmaps')) icons = (gtk.gdk.pixbuf_new_from_file(os.path.join(icon_dir, f)) for f in ICONS) self.window.set_icon_list(*icons) self.window.add_accel_group(self.ui_manager.get_accel_group()) # Create a full featured window. vbox = gtk.VBox() self.window.add(vbox) vbox.show() menubar = self.ui_manager.get_widget(self.menubar_path) if menubar: vbox.pack_start(menubar, expand=False) toolbar = self.ui_manager.get_widget(self.toolbar_path) if toolbar: vbox.pack_start(toolbar, expand=False) def _factory(name): comp = self.component_registry.get_utility(IUIComponent, name) logger.debug('open component %s' % str(comp)) return comp.open() filename = pkg_resources.resource_filename('gaphor.ui', 'layout.xml') self.layout = DockLayout() with open(filename) as f: deserialize(self.layout, vbox, f.read(), _factory) self.layout.connect('item-closed', self._on_item_closed) self.layout.connect('item-selected', self._on_item_selected) vbox.show() # TODO: add statusbar self.window.show() self.window.connect('delete-event', self._on_window_delete) # We want to store the window size, so it can be reloaded on startup self.window.set_property('allow-shrink', True) self.window.connect('size-allocate', self._on_window_size_allocate) self.window.connect('destroy', self._on_window_destroy) #self.window.connect_after('key-press-event', self._on_key_press_event) cr = self.component_registry cr.register_handler(self._on_file_manager_state_changed) cr.register_handler(self._on_undo_manager_state_changed) cr.register_handler(self._new_model_content) # TODO: register on ElementCreate/Delete event def open_welcome_page(self): """ Create a new tab with a textual welcome page, a sort of 101 for Gaphor. """ pass def set_title(self): """ Sets the window title. """ filename = self.file_manager.filename if self.window: if filename: title = '%s - %s' % (self.title, filename) else: title = self.title if self.model_changed: title += ' *' self.window.set_title(title) # Notebook methods: def add_tab(self, item): """ Create a new tab on the notebook with window as its contents. Returns: The page number of the tab. """ group = list(self.layout.get_widgets('diagrams'))[0] group.insert_item(item) item.show_all() #item.diagram_tab = tab #self.notebook_map[item] = tab def set_current_page(self, tab): """ Force a specific tab (DiagramTab) to the foreground. """ for i in self.layout.get_widgets('diagram-tab'): if i.diagram_tab is tab: g = i.get_parent() g.set_current_item(g.item_num(i)) return #for p, t in self.notebook_map.iteritems(): # if tab is t: # num = self.notebook.page_num(p) # self.notebook.set_current_page(num) # return pass def get_tabs(self): tabs = map(lambda i: i.diagram_tab, self.layout.get_widgets('diagram-tab')) return tabs # Signal callbacks: @component.adapter(ModelFactoryEvent) def _new_model_content(self, event): """ Open the toplevel element and load toplevel diagrams. """ # TODO: Make handlers for ModelFactoryEvent from within the GUI obj for diagram in self.element_factory.select(lambda e: e.isKindOf(UML.Diagram) and not (e.namespace and e.namespace.namespace)): self.show_diagram(diagram) @component.adapter(FileManagerStateChanged) def _on_file_manager_state_changed(self, event): # We're only interested in file operations if event.service is self.file_manager: self.model_changed = False self.set_title() @component.adapter(UndoManagerStateChanged) def _on_undo_manager_state_changed(self, event): """ """ undo_manager = event.service if not self.model_changed and undo_manager.can_undo(): self.model_changed = True self.set_title() def _on_window_destroy(self, window): """ Window is destroyed... Quit the application. """ self.window = None if gobject.main_depth() > 0: gtk.main_quit() cr = self.component_registry cr.unregister_handler(self._on_undo_manager_state_changed) cr.unregister_handler(self._on_file_manager_state_changed) cr.unregister_handler(self._new_model_content) def _on_window_delete(self, window = None, event = None): return not self.ask_to_close() def _clear_ui_settings(self): try: ui_manager = self.ui_manager except component.ComponentLookupError, e: log.warning('No UI manager service found') else: if self._tab_ui_settings: action_group, ui_id = self._tab_ui_settings self.ui_manager.remove_action_group(action_group) self.ui_manager.remove_ui(ui_id) self._tab_ui_settings = None def _on_item_closed(self, layout, group, item): self._clear_ui_settings() try: ui_component = item.ui_component except AttributeError: log.warning('No ui component defined on item') else: ui_component.close() item.destroy() def _on_item_selected(self, layout, group, item): """ Another page (tab) is put on the front of the diagram notebook. A dummy action is executed. """ # TODO: Here the magic happens! # TODO: Need to see what the active view is, or make toolbox actions global self._clear_ui_settings() # Is it a diagram view? try: tab = item.diagram_tab except AttributeError: # Not a diagram tab return self._current_diagram_tab = tab #content = self.notebook.get_nth_page(page_num) #tab = self.notebook_map.get(content) #assert isinstance(tab, DiagramTab), str(tab) self.ui_manager.insert_action_group(tab.action_group, -1) ui_id = self.ui_manager.add_ui_from_string(tab.menu_xml) self._tab_ui_settings = tab.action_group, ui_id log.debug('Menus updated with %s, %d' % self._tab_ui_settings) # Make sure everyone knows the selection has changed. self.component_registry.handle(DiagramTabChange(item), DiagramSelectionChange(tab.view, tab.view.focused_item, tab.view.selected_items)) def _on_window_size_allocate(self, window, allocation): """ Store the window size in a property. """ self.properties.set('ui.window-size', (allocation.width, allocation.height)) # Actions: @action(name='file-quit', stock_id='gtk-quit') def quit(self): # TODO: check for changes (e.g. undo manager), fault-save self.ask_to_close() and gtk.main_quit() self.shutdown() @toggle_action(name='diagram-drawing-style', label='Hand drawn style', active=False) def hand_drawn_style(self, active): """ Toggle between straight diagrams and "hand drawn" diagram style. """ if active: sloppiness = 0.5 else: sloppiness = 0.0 for tab in self.get_tabs(): tab.set_drawing_style(sloppiness) self.properties.set('diagram.sloppiness', sloppiness) def create_item(self, ui_component): #, widget, title, placement=None): """ Create an item for a ui component. This method can be called from UIComponents. """ item = DockItem(ui_component.title) item.add(ui_component.open()) group = DockGroup() group.insert_item(item) placement = ui_component.placement if placement: if placement == 'floating': add_new_group_floating(group, self.layout, ui_component.size) else: location = self.layout.get_widgets(placement[1])[0] { 'left': add_new_group_left, 'right': add_new_group_right, 'above': add_new_group_above, 'below': add_new_group_below }[placement[0]](location, group) else: add_new_group_floating(group) item.show() item.ui_component = ui_component group.show() gtk.accel_map_add_filter('gaphor') class Namespace(object): interface.implements(IUIComponent, IActionProvider) title = _('Namespace') placement = ('left', 'diagrams') component_registry = inject('component_registry') element_factory = inject('element_factory') ui_manager = inject('ui_manager') action_manager = inject('action_manager') main_window = inject('main_window') menu_xml = STATIC_MENU_XML % ('window', 'open-namespace') _menu_xml = """ """ def __init__(self): self._namespace = None self._ui_id = None self.action_group = build_action_group(self) @open_action(name='open-namespace', label=_('_Namespace')) def open_namespace(self): if not self._namespace: #self.main_window.create_item(self) #self.open(), self.title, self.placement) return self else: self._namespace.set_property('has-focus', True) def open(self): widget = self.construct() self.component_registry.register_handler(self.expand_root_nodes) return widget def close(self): if self._namespace: self._namespace.destroy() self._namespace = None # TODO: How to ensure stuff is removed properly from services? #self.ui_manager.remove_ui(self._ui_id) self.component_registry.unregister_handler(self.expand_root_nodes) def construct(self): self._ui_id = self.ui_manager.add_ui_from_string(self._menu_xml) model = NamespaceModel(self.element_factory) view = NamespaceView(model, self.element_factory) scrolled_window = gtk.ScrolledWindow() scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) scrolled_window.set_shadow_type(gtk.SHADOW_IN) scrolled_window.add(view) scrolled_window.show() view.show() view.connect_after('event-after', self._on_view_event) view.connect('row-activated', self._on_view_row_activated) view.connect_after('cursor-changed', self._on_view_cursor_changed) view.connect('destroy', self._on_view_destroyed) self._namespace = view self.expand_root_nodes() return scrolled_window @component.adapter(ModelFactoryEvent) def expand_root_nodes(self, event=None): """ """ # Expand all root elements: self._namespace.expand_root_nodes() self._on_view_cursor_changed(self._namespace) def _on_view_event(self, view, event): """ Show a popup menu if button3 was pressed on the TreeView. """ # handle mouse button 3: if event.type == gtk.gdk.BUTTON_PRESS and event.button == 3: menu = self.ui_manager.get_widget('/namespace-popup') menu.popup(None, None, None, event.button, event.time) def _on_view_row_activated(self, view, path, column): """ Double click on an element in the tree view. """ self.action_manager.execute('tree-view-open') def _on_view_cursor_changed(self, view): """ Another row is selected, execute a dummy action. """ element = view.get_selected_element() self.action_group.get_action('tree-view-create-diagram').props.sensitive = isinstance(element, UML.Package) self.action_group.get_action('tree-view-create-package').props.sensitive = isinstance(element, UML.Package) self.action_group.get_action('tree-view-delete-diagram').props.visible = isinstance(element, UML.Diagram) self.action_group.get_action('tree-view-delete-package').props.visible = isinstance(element, UML.Package) and not element.presentation self.action_group.get_action('tree-view-open').props.sensitive = isinstance(element, UML.Diagram) def _on_view_destroyed(self, widget): self.close() def select_element(self, element): """ Select an element from the Namespace view. The element is selected. After this an action may be executed, such as OpenModelElement, which will try to open the element (if it's a Diagram). """ path = self._namespace.get_model().path_from_element(element) # Expand the first row: if len(path) > 1: self._namespace.expand_row(path[:-1], False) selection = self._namespace.get_selection() selection.select_path(path) self._on_view_cursor_changed(self._namespace) @action(name='tree-view-open', label='_Open') def tree_view_open_selected(self): element = self._namespace.get_selected_element() # TODO: Candidate for adapter? if isinstance(element, UML.Diagram): self.main_window.show_diagram(element) else: log.debug('No action defined for element %s' % type(element).__name__) @action(name='tree-view-rename', label=_('Rename'), accel='F2') def tree_view_rename_selected(self): view = self._namespace element = view.get_selected_element() path = view.get_model().path_from_element(element) column = view.get_column(0) cell = column.get_cell_renderers()[1] cell.set_property('editable', 1) cell.set_property('text', element.name) view.set_cursor(path, column, True) cell.set_property('editable', 0) @action(name='tree-view-create-diagram', label=_('_New diagram'), stock_id='gaphor-diagram') @transactional def tree_view_create_diagram(self): element = self._namespace.get_selected_element() diagram = self.element_factory.create(UML.Diagram) diagram.package = element if element: diagram.name = '%s diagram' % element.name else: diagram.name = 'New diagram' self.select_element(diagram) self.main_window.show_diagram(diagram) self.tree_view_rename_selected() @action(name='tree-view-delete-diagram', label=_('_Delete diagram'), stock_id='gtk-delete') @transactional def tree_view_delete_diagram(self): diagram = self._namespace.get_selected_element() m = gtk.MessageDialog(None, gtk.DIALOG_MODAL, gtk.MESSAGE_QUESTION, gtk.BUTTONS_YES_NO, 'Do you really want to delete diagram %s?\n\n' 'This will possibly delete diagram items\n' 'that are not shown in other diagrams.' % (diagram.name or '')) if m.run() == gtk.RESPONSE_YES: for i in reversed(diagram.canvas.get_all_items()): s = i.subject if s and len(s.presentation) == 1: s.unlink() i.unlink diagram.unlink() m.destroy() @action(name='tree-view-create-package', label=_('New _package'), stock_id='gaphor-package') @transactional def tree_view_create_package(self): element = self._namespace.get_selected_element() package = self.element_factory.create(UML.Package) package.package = element if element: package.name = '%s package' % element.name else: package.name = 'New model' self.select_element(package) self.tree_view_rename_selected() @action(name='tree-view-delete-package', label=_('Delete pac_kage'), stock_id='gtk-delete') @transactional def tree_view_delete_package(self): package = self._namespace.get_selected_element() assert isinstance(package, UML.Package) package.unlink() @action(name='tree-view-refresh', label=_('_Refresh')) def tree_view_refresh(self): self._namespace.get_model().refresh() class Toolbox(object): interface.implements(IUIComponent, IActionProvider) title = _('Toolbox') placement = ('left', 'diagrams') component_registry = inject('component_registry') main_window = inject('main_window') properties = inject('properties') menu_xml = """ """ def __init__(self): self._toolbox = None self.action_group = build_action_group(self) self.action_group.get_action('reset-tool-after-create').set_active(self.properties.get('reset-tool-after-create', True)) @open_action(name='open-toolbox', label=_('T_oolbox')) def open_toolbox(self): if not self._toolbox: #self.main_window.create_item(self) #.open(), self.title, self.placement) return self else: self._toolbox.set_property('has-focus', True) def open(self): widget = self.construct() self.main_window.window.connect_after('key-press-event', self._on_key_press_event) self.component_registry.register_handler(self._on_diagram_tab_change) if self.main_window.get_current_diagram_tab(): self.update_toolbox(self.main_window.get_current_diagram_tab().toolbox.action_group) return widget def close(self): if self._toolbox: self.component_registry.unregister_handler(self._on_diagram_tab_change) self._toolbox.destroy() self._toolbox = None def construct(self): toolbox = _Toolbox(TOOLBOX_ACTIONS) toolbox.show() toolbox.connect('destroy', self._on_toolbox_destroyed) self._toolbox = toolbox return toolbox def _on_key_press_event(self, view, event): """ Grab top level window events and select the appropriate tool based on the event. """ if event.state & gtk.gdk.SHIFT_MASK or \ (event.state == 0 or event.state & gtk.gdk.MOD2_MASK): keyval = gtk.gdk.keyval_name(event.keyval) self.set_active_tool(shortcut=keyval) def _on_toolbox_destroyed(self, widget): self._toolbox = None @toggle_action(name='reset-tool-after-create', label=_('_Reset tool'), active=False) def reset_tool_after_create(self, active): self.properties.set('reset-tool-after-create', active) #def _insensivate_toolbox(self): # for button in self._toolbox.buttons: # button.set_property('sensitive', False) @component.adapter(IDiagramTabChange) def _on_diagram_tab_change(self, event): self.update_toolbox(event.diagram_tab.toolbox.action_group) def update_toolbox(self, action_group): """ Update the buttons in the toolbox. Each button should be connected by an action. Each button is assigned a special _action_name_ attribute that can be used to fetch the action from the ui manager. """ if not self._toolbox: return for button in self._toolbox.buttons: action_name = button.action_name action = action_group.get_action(action_name) if action: action.connect_proxy(button) def set_active_tool(self, action_name=None, shortcut=None): """ Set the tool based on the name of the action """ # HACK: toolbox = self._toolbox if shortcut and toolbox: action_name = toolbox.shortcuts.get(shortcut) log.debug('Action for shortcut %s: %s' % (shortcut, action_name)) if not action_name: return self.main_window.get_current_diagram_tab().toolbox.action_group.get_action(action_name).activate() # vim:sw=4:et:ai gaphor-0.17.2/gaphor/ui/namespace.py000066400000000000000000000513231220151210700172570ustar00rootroot00000000000000""" This is the TreeView that is most common (for example: it is used in Rational Rose). This is a tree based on namespace relationships. As a result only classifiers are shown here. """ import gobject import gtk import operator import stock from zope import component from gaphor.core import inject from gaphor import UML from gaphor.UML.event import ElementCreateEvent, ModelFactoryEvent, FlushFactoryEvent, DerivedSetEvent from gaphor.UML.interfaces import IAttributeChangeEvent, IElementDeleteEvent from gaphor.transaction import Transaction from iconoption import get_icon_option # The following items will be shown in the treeview, although they # are UML.Namespace elements. _default_filter_list = ( UML.Class, UML.Interface, UML.Package, UML.Component, UML.Device, UML.Node, UML.Artifact, UML.Interaction, UML.UseCase, UML.Actor, UML.Diagram, UML.Profile, UML.Stereotype, UML.Property, UML.Operation ) # TODO: update tree sorter: # Diagram before Class & Package. # Property before Operation _tree_sorter = operator.attrgetter('name') def catchall(func): def catchall_wrapper(*args, **kwargs): try: func(*args, **kwargs) except Exception, e: log.error('Exception in %s. Try to refresh the entire model' % (func,), exc_info=True) try: args[0].refresh() except Exception, e: log.error('Failed to refresh') return catchall_wrapper class NamespaceModel(gtk.GenericTreeModel): """ The NamespaceModel holds a view on the data model based on namespace relationships (such as a Package containing a Class). NamedElement.namespace[1] -- Namespace.ownedMember[*] NOTE: when a model is loaded no IAssociation*Event's are emitted. """ component_registry = inject('component_registry') def __init__(self, factory): # Init parent: gtk.GenericTreeModel.__init__(self) # We own the references to the iterators. self.set_property ('leak-references', 0) self.factory = factory self._nodes = { None: [] } self.filter = _default_filter_list cr = self.component_registry cr.register_handler(self.flush) cr.register_handler(self.refresh) cr.register_handler(self._on_element_change) cr.register_handler(self._on_element_create) cr.register_handler(self._on_element_delete) cr.register_handler(self._on_association_set) self._build_model() def close(self): """ Close the namespace model, unregister handlers. """ cr = self.component_registry cr.unregister_handler(self.flush) cr.unregister_handler(self.refresh) cr.unregister_handler(self._on_element_change) cr.unregister_handler(self._on_element_create) cr.unregister_handler(self._on_element_delete) cr.unregister_handler(self._on_association_set) def path_from_element(self, e): if e: ns = e.namespace n = self._nodes.get(ns) if n: return self.path_from_element(ns) + (n.index(e),) else: return () else: return () def element_from_path(self, path): """ Get the node form a path. None is returned if no node is found. """ try: nodes = self._nodes node = None for index in path: node = nodes[node][index] return node except IndexError: return None @component.adapter(IAttributeChangeEvent) @catchall def _on_element_change(self, event): """ Element changed, update appropriate row. """ element = event.element if element not in self._nodes: return if event.property is UML.Classifier.isAbstract or \ event.property is UML.BehavioralFeature.isAbstract: path = self.path_from_element(element) if path: self.row_changed(path, self.get_iter(path)) if event.property is UML.NamedElement.name: try: path = self.path_from_element(element) except KeyError: # Element not visible in the tree view return if not path: return self.row_changed(path, self.get_iter(path)) parent_nodes = self._nodes[element.namespace] parent_path = self.path_from_element(element.namespace) if not parent_path: return original = list(parent_nodes) parent_nodes.sort(key=_tree_sorter) if parent_nodes != original: # reorder the list: self.rows_reordered(parent_path, self.get_iter(parent_path), map(list.index, [original] * len(parent_nodes), parent_nodes)) def _add_elements(self, element): """ Add a single element. """ if type(element) not in self.filter: return if element.namespace not in self._nodes: return self._nodes.setdefault(element, []) parent = self._nodes[element.namespace] parent.append(element) parent.sort(key=_tree_sorter) path = self.path_from_element(element) self.row_inserted(path, self.get_iter(path)) # Add children if isinstance(element, UML.Namespace): for e in element.ownedMember: # check if owned member is indeed within parent's namespace # the check is important in case on Node classes if element is e.namespace: self._add_elements(e) def _remove_element(self, element): """ Remove elements from the nodes. No update signal is emitted. """ def remove(n): for c in self._nodes.get(n, []): remove(c) try: del self._nodes[n] except KeyError: pass remove(element) @component.adapter(ElementCreateEvent) @catchall def _on_element_create(self, event): element = event.element if event.service is self.factory: self._add_elements(element) @component.adapter(IElementDeleteEvent) @catchall def _on_element_delete(self, event): element = event.element #log.debug('Namespace received deleting element %s' % element) if event.service is self.factory and \ type(element) in self.filter: path = self.path_from_element(element) #log.debug('Deleting element %s from path %s' % (element, path)) # Remove all sub-elements: if path: self.row_deleted(path) if path[:-1]: self.row_has_child_toggled(path[:-1], self.get_iter(path[:-1])) self._remove_element(element) parent_node = self._nodes.get(element.namespace) if parent_node: parent_node.remove(element) # if path and parent_node and len(self._nodes[parent_node]) == 0: # self.row_has_child_toggled(path[:-1], self.get_iter(path[:-1])) @component.adapter(DerivedSetEvent) @catchall def _on_association_set(self, event): element = event.element if type(element) not in self.filter: return if event.property is UML.NamedElement.namespace: # Check if the element is actually in the element factory: if element not in self.factory: return old_value, new_value = event.old_value, event.new_value # Remove entry from old place if self._nodes.has_key(old_value): try: path = self.path_from_element(old_value) + (self._nodes[old_value].index(element),) except ValueError: log.error('Unable to create path for element %s and old_value %s' % (element, self._nodes[old_value])) else: self._nodes[old_value].remove(element) self.row_deleted(path) path = path[:-1] #self.path_from_element(old_value) if path: self.row_has_child_toggled(path, self.get_iter(path)) # Add to new place. This may fail if the type of the new place is # not in the tree model (because it's filtered) log.debug('Trying to add %s to %s' % (element, new_value)) if self._nodes.has_key(new_value): if self._nodes.has_key(element): parent = self._nodes[new_value] parent.append(element) parent.sort(key=_tree_sorter) path = self.path_from_element(element) self.row_inserted(path, self.get_iter(path)) else: self._add_elements(element) elif self._nodes.has_key(element): # Non-existent: remove entirely self._remove_element(element) @component.adapter(ModelFactoryEvent) def refresh(self, event=None): self.flush() self._build_model() @component.adapter(FlushFactoryEvent) def flush(self, event=None): for n in self._nodes[None]: self.row_deleted((0,)) self._nodes = {None: []} def _build_model(self): toplevel = self.factory.select(lambda e: isinstance(e, UML.Namespace) and not e.namespace) for element in toplevel: self._add_elements(element) # TreeModel methods: def on_get_flags(self): """ Returns the GtkTreeModelFlags for this particular type of model. """ return 0 def on_get_n_columns(self): """ Returns the number of columns in the model. """ return 1 def on_get_column_type(self, index): """ Returns the type of a column in the model. """ return gobject.TYPE_PYOBJECT def on_get_path(self, node): """ Returns the path for a node as a tuple (0, 1, 1). """ path = self.path_from_element(node) return path def on_get_iter(self, path): """ Returns the node corresponding to the given path. The path is a tuple of values, like (0 1 1). Returns None if no iterator can be created. """ return self.element_from_path(path) def on_get_value(self, node, column): """ Returns the model element that matches 'node'. """ assert column == 0, 'column can only be 0' return node def on_iter_next(self, node): """ Returns the next node at this level of the tree. None if no next element. """ try: parent = self._nodes[node.namespace] index = parent.index(node) return parent[index + 1] except (IndexError, ValueError), e: return None def on_iter_has_child(self, node): """ Returns true if this node has children, or None. """ n = self._nodes.get(node) return n or len(n) > 0 def on_iter_children(self, node): """ Returns the first child of this node, or None. """ try: return self._nodes[node][0] except KeyError: pass def on_iter_n_children(self, node): """ Returns the number of children of this node. """ return len(self._nodes[node]) def on_iter_nth_child(self, node, n): """ Returns the nth child of this node. """ try: nodes = self._nodes[node] return nodes[n] except TypeError, e: return None def on_iter_parent(self, node): """ Returns the parent of this node or None if no parent """ return node.namespace # TreeDragDest def row_drop_possible(self, dest_path, selection_data): print 'row_drop_possible', dest_path, selection_data return True def drag_data_received(self, dest, selection_data): print 'drag_data_received', dest_path, selection_data class NamespaceView(gtk.TreeView): TARGET_STRING = 0 TARGET_ELEMENT_ID = 1 DND_TARGETS = [ ('STRING', 0, TARGET_STRING), ('text/plain', 0, TARGET_STRING), ('gaphor/element-id', 0, TARGET_ELEMENT_ID)] def __init__(self, model, factory): assert isinstance (model, NamespaceModel), 'model is not a NamespaceModel (%s)' % str(model) self.__gobject_init__() gtk.TreeView.__init__(self, model) self.factory = factory self.icon_cache = {} self.set_property('headers-visible', False) self.set_property('search-column', 0) def search_func(model, column, key, iter, data=None): assert column == 0 element = model.get_value(iter, column) if element.name: return not element.name.startswith(key) self.set_search_equal_func(search_func) self.set_rules_hint(True) selection = self.get_selection() selection.set_mode(gtk.SELECTION_BROWSE) column = gtk.TreeViewColumn ('') # First cell in the column is for an image... cell = gtk.CellRendererPixbuf () column.pack_start (cell, 0) column.set_cell_data_func (cell, self._set_pixbuf, None) # Second cell if for the name of the object... cell = gtk.CellRendererText () #cell.set_property ('editable', 1) cell.connect('edited', self._text_edited) column.pack_start (cell, 0) column.set_cell_data_func (cell, self._set_text, None) assert len (column.get_cell_renderers()) == 2 self.append_column (column) # drag self.drag_source_set(gtk.gdk.BUTTON1_MASK | gtk.gdk.BUTTON3_MASK, NamespaceView.DND_TARGETS, gtk.gdk.ACTION_COPY | gtk.gdk.ACTION_MOVE) self.connect('drag-begin', NamespaceView.on_drag_begin) self.connect('drag-data-get', NamespaceView.on_drag_data_get) self.connect('drag-data-delete', NamespaceView.on_drag_data_delete) # drop self.drag_dest_set (gtk.DEST_DEFAULT_ALL, [NamespaceView.DND_TARGETS[-1]], gtk.gdk.ACTION_MOVE) self.connect('drag-motion', NamespaceView.on_drag_motion) self.connect('drag-drop', NamespaceView.on_drag_drop) self.connect('drag-data-received', NamespaceView.on_drag_data_received) def get_selected_element(self): selection = self.get_selection() model, iter = selection.get_selected() if not iter: return return model.get_value(iter, 0) def expand_root_nodes(self): self.expand_row((0,), False) def _set_pixbuf(self, column, cell, model, iter, data): value = model.get_value(iter, 0) q = t = type(value) p = get_icon_option(value) if p is not None: q = (t, p) try: icon = self.icon_cache[q] except KeyError: stock_id = stock.get_stock_id(t, p) if stock_id: icon = self.render_icon(stock_id, gtk.ICON_SIZE_MENU, '') else: icon = None self.icon_cache[q] = icon cell.set_property('pixbuf', icon) def _set_text(self, column, cell, model, iter, data): """ Set font and of model elements in tree view. """ value = model.get_value(iter, 0) text = value and (value.name or '').replace('\n', ' ') or '<None>' if isinstance(value, UML.Diagram): text = '%s' % text elif (isinstance(value, UML.Classifier) or isinstance(value, UML.Operation)) and value.isAbstract: text = '%s' % text cell.set_property('markup', text) def _text_edited(self, cell, path_str, new_text): """ The text has been edited. This method updates the data object. Note that 'path_str' is a string where the fields are separated by colons ':', like this: '0:1:1'. We first turn them into a tuple. """ try: model = self.get_property('model') iter = model.get_iter_from_string(path_str) element = model.get_value(iter, 0) tx = Transaction() element.name = new_text tx.commit() except Exception, e: log.error('Could not create path from string "%s"' % path_str) def on_drag_begin(self, context): return True def on_drag_data_get(self, context, selection_data, info, time): """ Get the data to be dropped by on_drag_data_received(). We send the id of the dragged element. """ selection = self.get_selection() model, iter = selection.get_selected() if iter: element = model.get_value(iter, 0) p = get_icon_option(element) p = p if p else '' # 'id#stereotype' is being send if info == NamespaceView.TARGET_ELEMENT_ID: selection_data.set(selection_data.target, 8, '%s#%s' % (element.id, p)) else: selection_data.set(selection_data.target, 8, '%s#%s' % (element.name, p)) return True def on_drag_data_delete (self, context): """ Delete data from original site, when `ACTION_MOVE` is used. """ self.emit_stop_by_name('drag-data-delete') # Drop def on_drag_motion(self, context, x, y, time): try: path, pos = self.get_dest_row_at_pos(x, y) self.set_drag_dest_row(path, pos) except TypeError: self.set_drag_dest_row(len(self.get_model()) - 1, gtk.TREE_VIEW_DROP_AFTER) kind = gtk.gdk.ACTION_COPY context.drag_status(kind, time) return True def on_drag_drop(self, context, x, y, time): """ Determine if drop is allowed. """ if 'gaphor/element-id' in context.targets: self.emit_stop_by_name('drag-drop') self.drag_get_data(context, context.targets[-1], time) return True return False def on_drag_data_received(self, context, x, y, selection, info, time): """ Drop the data send by on_drag_data_get(). """ #print 'data-received', NamespaceView.TARGET_ELEMENT_ID, 'in', context.targets self.emit_stop_by_name('drag-data-received') if 'gaphor/element-id' in context.targets: #print 'drag_data_received' n, p = selection.data.split('#') drop_info = self.get_dest_row_at_pos(x, y) else: drop_info = None if drop_info: model = self.get_model() element = self.factory.lookup(n) path, position = drop_info iter = model.get_iter(path) dest_element = model.get_value(iter, 0) assert dest_element # Add the item to the parent if it is dropped on the same level, # else add it to the item. if position in (gtk.TREE_VIEW_DROP_BEFORE, gtk.TREE_VIEW_DROP_AFTER): parent_iter = model.iter_parent(iter) if parent_iter is None: dest_element = None else: dest_element = model.get_value(parent_iter, 0) try: # Check if element is part of the namespace of dest_element: ns = dest_element while ns: if ns is element: raise AttributeError ns = ns.namespace # Set package. This only works for classifiers, packages and # diagrams. Properties and operations should not be moved. tx = Transaction() if dest_element is None: del element.package else: element.package = dest_element tx.commit() except AttributeError, e: #log.info('Unable to drop data', e) context.drop_finish(False, time) else: context.drop_finish(True, time) # Finally let's try to select the element again. path = model.path_from_element(element) if len(path) > 1: self.expand_row(path[:-1], False) selection = self.get_selection() selection.select_path(path) # vim: sw=4:et:ai gaphor-0.17.2/gaphor/ui/pixmaps/000077500000000000000000000000001220151210700164265ustar00rootroot00000000000000gaphor-0.17.2/gaphor/ui/pixmaps/accept-event-action.png000066400000000000000000000016241220151210700227700ustar00rootroot00000000000000‰PNG  IHDRÚ}\ˆsRGB®ÎébKGDáäæ uj! pHYs  šœtIMEÛ;¶k­tEXtCommentCreated with The GIMPïd%nëIDATHÇÝ•ÉkAƯfÓ80‚&ƒ&Íè Æˆâ¾<¨xqOÑŠñp2(¸D¢¸("’“ËA·“ûŠKÀ1:fœ~º{º['"Š|MÓÕU¯ê{ïû^UÁ_6™3w^ltc£IÄŠ8½ªÎhÙ)€zƒýøf3™<ÿÂdâÄæÎx"^D{±þd1˲$2ÖàÁU¹ǵH}CÃ]#2vÔèF3°¢AÑâ#ÃGK WK¾ÑXŒÏŸ>qöÌi€ñÀMŸ1ãRssËÔ ííD"T½I·ßèîðÜY3yøðÁ.`¯z¯\¾¼üÚµ+OöíÙíJ†ª=YÕ} ¶Çƒ·Ç …¡ÄMÛÆö(ðàÆõëËΟ;÷úøÑ#ˆ¸•ò“bêÇÔ²\¾–Éfvô8Ø®öö~l;}êd®««ËéA|ðeªØ¶" a€5«Vô=zúÜŒ¬KýœÏ×íß»{û!UéôµùDlêÜG í?Uãf–íÈ€W/_^0ƼéûÒgûûBuRõ­èO¥LZáGOŸËÖ-›ÃÙÌŽBuMMU:îX²téЖɓQUDJ1x•¢êФeø ¦Y—4Ÿ½øzøP' £vNšÔ²`Ñâ%¾hÔ‹ ô»k–0J <~öBG ¯elSÓ¦d29åê5v)ZŠ—ôÒ§´ü8nÛˆ < Â#†×ZÀ¼êêêµëZ××T&*íHD½Í#ØêŠ_Ou·++€ã S©Tfæ¬ÙM¹Üº»o¢x•ó+G…ëO$èyÿŽwoßú4¨¯ßņݹ}ûã­[Ý-ªñ ‰yé߯¡Ÿ7ŠÅ¢ÔÔÖJOOÏ N¥Æ,‹Ü¿oZ>ŸO b~÷T@]z9  À ` Ð D¯x‡úWÀEi۰Ѷhi«úo+ ¶•÷Ívd,þ û•BßÏ… IEND®B`‚gaphor-0.17.2/gaphor/ui/pixmaps/action.png000066400000000000000000000014341220151210700204130ustar00rootroot00000000000000‰PNG  IHDRàw=øbKGDùC» pHYs ‰ ‰7ÉË­tIMEÔ . DÍÄ©IDATxÚí•ÍKTQÆ÷ܱë½×{uÊÑprp¢\h˜é"]DQàßТmE$QDµ(¢ÌŠp£´ÊMm„Æ"EEGgZÎÌU™Ñ™Qçë´I¨¿¡MïêœÅy~œ÷<çyáýëR~ß´··Û‘H¤mllì’Çã9‘Ïç7?¬(,,,Ì”——¬ªªêéêêšäÀää¤èèè¸ …žEc±Ã¦aâ=r„âââMù|žßgqœylÛZóûý ø×ßß¿ü ­­íR0ìÕuýÀí;w9ÙØHYYªªn R’L.311Á“ÇúúŸÏ÷nhhè ¤ÚÙÙi ¼W].÷“§ÏijnÆ0 „[÷WQÐ4 ¯×˹ó‡§=nšæ|*• Yá8ÎeÇq¼×®ßÀôè®Ó4M®Ý¸‰«¨H¸ÝîëÀq@ˆ@ pβlZZ[÷옚šêêêI¥R•Ài Høýþ¦ŠŠ JJ¬=„ÔÕסëzp 0…¢(.Û¶Ñ4m_|ïR] (RJÖÖÖØÊó»¹€‡ÃÓs‘9’É侨ÎÎ΢ªª\ÿp¢²²r ‹1šÜ³x"‘`dd˜¥¥¥4rÂãñôÙ–•í~ÙE:Þµx.—ãí›×Dææ¢À TÇÇÇãµµµ5ŸO-..ÒÐpÃ0v$¾²²Â«î—<|pMÓ’Žã¥”}À”àóùJUUíM$gåboâ^œÏ•™5bc/T9S!{,ˆý#~É£4äÏ8PIEND®B`‚gaphor-0.17.2/gaphor/ui/pixmaps/activity-final-node.xcf000066400000000000000000000054301220151210700230000ustar00rootroot00000000000000gimp xcf v002  BB*/ gimp-commentCreated with The GIMPS gimp-commentCreated with The GIMPgimp-image-grid(style intersections) (fgcolor (color-rgba 0.000000 0.000000 0.000000 1.000000)) (bgcolor (color-rgba 1.000000 1.000000 1.000000 1.000000)) (xspacing 10.000000) (yspacing 10.000000) (spacing-unit inches) (xoffset 0.000000) (yoffset 0.000000) (offset-unit inches) ‰FîàEmpty Layer#3ÿ     ½Ñá@@@€÷qãúýýúãq ý¸ýÿÿýý¸ þ¸ÿÿþ¸ ýqýÿÿýýqþãÿ ÿþãþúÿ ÿþúþýÿ ÿþýþýÿ ÿþýþúÿ ÿþúþãÿ ÿþãýqýÿÿýýq þ¸ÿÿþ¸ ý¸ýÿÿýý¸ ÷väúýýúäv~ Drop-ShadowÌ     1EUõ"(()("ó'r¶ÍÑÑͶs'ê/˜Ýöüýýüöݘ/'˜æûýþþöüç™'rÝûþþÿþöûÝs"¶öþþÿÿöþ÷·#(Îüþþÿÿ÷þüÎ))ÒýþÿÿøþÒ*)ÒýþÿÿøþÒ*(Îüþÿÿ÷ýÎ)"·öþþÿÿöþ÷¸#sÝüþÿÿôüÞs'™çüþþÿÿêþüçš(/™Þ÷ýýþý÷ßš0ó(u¸ÎÒÒθv)õ#())(#Empty Layer#1ÿ     ð@@@Nõ²õýþþýõ² ýšùÿÿýùš ý¶þÿÿýþ¶ýšþÿ ÿýþšýùÿ ÿýùþ²ÿÿþ²þõÿÿþõþýÿÿþýþþÿÿþþþþÿÿþþþýÿÿþýþõÿÿþõþ²ÿÿþ²ýùÿ ÿýùýšþÿ ÿýþšý¶þÿÿýþ¶ ýšùÿÿýùš õ#¶õýþþýõ¶#M Empty Layerÿ      ž²Â@@@6üSÛüþþüüÛS óÏãM  MãÏû;ïeûeï;üïIüIïüÏe üeÏüSã üãSýÛMýMÛýü ý üýþýþýþýþýþýþýþýþýü ý üýÛMýMÛüSã üãSüÏe üeÏüïIüIïû;ïeûeï;óÏÛI  IÛÏ ü\ÛüþþüüÛ\5Drop-Shadow#1Ì     % Š ž ®äääú (())û(!ù%]”¦¨©©ú§”]&í 4ˆ–]5+**+5]–ˆ5 ø Pf.ø.fP ù5\ò\5%ˆfôfˆ%]–. ö.–]!•^ ÷^•!(§5 ÷5§()©+ ù+©))©*ù*©))©*ø*©))©+ ø+©)(§5 ÷5§(!•] ö]•!^–. ô.–^&ˆfòfˆ&5\ù\5ø Pf-ø-fQ í 5‡“[5+**+5Z“ˆ5 ù&`•¦¨©©ú§•`'ú!(())û(!gaphor-0.17.2/gaphor/ui/pixmaps/activity-partition.png000066400000000000000000000007531220151210700230040ustar00rootroot00000000000000‰PNG  IHDR``â˜w8sBIT|dˆ pHYs × ×B(›xtEXtSoftwarewww.inkscape.org›î<hIDATxœíÝÁIÄ@€Q#V!‚%Ø…X …Ø%²mŒWÍA’ø|ï¶° ÿÎÎ&ËãŠÎuýþ;bĈ  &@ìfö‚eYl>c,[®71bĦ׀µ·÷ËŸã4îïn¿¼^¯‰³k‚ ˆ  &@L€˜1b›÷?YÿnÞj½ï8úþG31bÄ_Öž_^§Þÿôøð§î¿7 &@L€˜1bĈ  &@L€˜1bĈ  &@L€˜1bĈ  &@L€˜1bĈ  &@L€˜1bĈ  &@L€˜1bÄ~ý¼ £Ïç©Ïÿ™ebĈ¾}çÙŸ_`bĈ  &@L€Ø2ûLyÏ’üžçˆŒ1bÓÿ­¿ã¬ Û˜€˜1bÓûöebĈ}¦²/§X;¼=IEND®B`‚gaphor-0.17.2/gaphor/ui/pixmaps/actor.png000066400000000000000000000013521220151210700202450ustar00rootroot00000000000000‰PNG  IHDRàw=øbKGDáäæ uj! pHYs  šœtIMEÔ ,‹ övtEXtCommentCreated with The GIMPïd%nNIDATxÚ¥ÖËjSQà/I­'`/¦˜WQ:t àÀ°dTÚ™göœ9)¢>€·‘u"JÕ ‚0m©¢m¬ÖKkÛ'+rMRÛ ‡}Ø;ùÿµþõ¯•Ðyõ`ó¨á ŽÄyÁWæ’¤˜ ¤CÃ#i’S,â ¶B’Ãh’Ó‰—“iuf6­Î̦/'$÷p2‚ȵÉ·!Ècd°RQ* ü;,• V*pG±³N;‚B»Èân/úÚÉ”ï Óµñ±1µÚü¿ƒZmÞøØT;ÐqÂ-‹ëùnáöo¶Ð¹(à™(èR¿ðK)r®…l ý»ÐÃx÷7ðOð XC=³·uNºÉç0z›ëÚµÁS| m‹ñ^¡¸ÿg}!Û4Vq Ëø•Í¢k,ÎãÊØ–‘ñrŒŠGøï¹ÿRý‰lZÖ Q—Ä}.jr?´ûLj´À¿ñ=²H[eP—2àBŽ~ìÁ7Ìb +-êñ_k7&2_žÆ9ìj!ñ–À›IÊ[!É‚Oã&®‡‹Ò¨#I¡ ømð»xÍõ(\¶§ñ sQÜúFšÁïÄxxîy‹8µQ’f›> ª¸Š‹1ûËèŽÞ(‡p ø\ì³îV󯯻_D1øµÅvŒu@ðég›‡Ÿø;yIEND®B`‚gaphor-0.17.2/gaphor/ui/pixmaps/artifact.xcf000066400000000000000000000120601220151210700207240ustar00rootroot00000000000000gimp xcf file88BB / gimp-commentCreated with The GIMP®¬‰ oÜ00 New Layer#4ÿ      V00j00z   Ïÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ'ÿÙ00 New Layer#3ÿ     T00h00xÃ¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥éÃÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂéÃÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑéà ÿ÷þåb ( ÿùîh ÿüë\ ÿüéV ÿüâS ÿüþæbÿþìÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿé00 New Layer#2ÿ     900M00]   aÿüÚ<ÿûþÞDÿüåDÿüá:ÿüÜ5ÿý×9ÿýþÝÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ‡ )Drop-Shadow#2Ì      ¿ )Ó )ã   û    û÷  $%%&&%ø$! ÷":NX\\]]\÷YSE1 ö:eˆ™Ÿ ¡¡ öœ“a>!ö NˆµÍÕÖ××ÖõÓʶ•lB"ö %X™ÍèðòóóòéñëÝÜnB" &\ŸÕðùûüüûêøñáÄ›lA  &\ ÖòûýþþýëúòáÙi=  &]¡×óüþÿÿþëýúòàÁ”_0 &]¡×óüþÿÿþìýúñܶ~D &]¡×óüþÿÿþíýùëÊ“S"  &]¡×óüþÿ ÿþîûñÓZ%  &]¡×óüþÿ ÿþîüóÖ \&  &]¡×óüþÿ ÿþîüóס]'  &]¡×óüþÿ ÿþîüóס]'  &]¡×óüþÿ ÿîýôØ¡]'  &]¡×óüþÿ ÿîýôØ¡]'  &]¡×óüþÿ ÿîýôØ¡]'  &]¡×óüþÿ ÿîýôØ¡]'  &]¡×óüþÿ ÿîýôØ¡]'  &]¡×óüþÿ ÿîýôØ¡]'  &]¡×óüþÿ ÿîýôØ¡]'  &]¡×óüþÿ ÿîýôØ¡]'  &]¡×óüþÿ ÿîýôØ¡]'  &]¡×óüþÿ ÿîýôØ¡]'  &]¡×óüþÿ ÿîýôØ¡]'  &]¡×óüþÿ ÿîýôØ¡]'  &]¡×óüþÿ ÿîýôØ¡]'  &]¡×óüþÿ ÿîýôØ¡]'  &]¡×óüþÿ ÿîýôØ¡]'  &]¡×óüþÿ ÿîýôØ¡]'  &]¡×óüþÿ ÿîýôØ¡]'  &\ Öñúüý ýîûòÖ \&  %YšÎéñóô ôîòéΚY%  Oˆ¶ÎÖר ØîÖηˆO! :eˆ™Ÿ ¡ ¡îŸšˆf;":NX\\] ]î\YN:"!%&&' 'ø&%!û    û 00 New Layerÿ      00 )00 9a+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+áaa+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+äaa+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æaa+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿa00 Backgroundÿ     ¨00¼00Ì    ÿ88 Drop-ShadowÌ      „88˜88¨ @ @ @& û  &  û÷  $%%&%&î%$  ":NX\\]%]î\YN:":eˆ™Ÿ ¡%¡îŸšˆf; NˆµÍÕÖ×%×îÕͶˆN!  %X™Íèðòó%óîñèÍšY%  &\ŸÕðùûü%üîúñÕŸ\&  &\ Öòûýþ%þîüóס]'  &]¡×óüþÿ%ÿîýôØ¡]'  &]¡×óüþÿ%ÿîýôØ¡]'  &]¡×óüþÿ%ÿîýôØ¡]'  &]¡×óüþÿ%ÿîýôØ¡]'  &]¡×óüþÿ%ÿîýôØ¡]'  &]¡×óüþÿ%ÿîýôØ¡]'  &]¡×óüþÿ%ÿîýôØ¡]'  &]¡×óüþÿ%ÿîýôØ¡]'  &]¡×óüþÿ%ÿîýôØ¡]'  &]¡×óüþÿ%ÿîýôØ¡]'  &]¡×óüþÿ%ÿîýôØ¡]'  &]¡×óüþÿ%ÿîýôØ¡]'  &]¡×óüþÿ%ÿîýôØ¡]'  &]¡×óüþÿ%ÿîýôØ¡]'  &]¡×óüþÿ%ÿîýôØ¡]'  &]¡×óüþÿ%ÿîýôØ¡]'  &]¡×óüþÿ%ÿîýôØ¡]'  &]¡×óüþÿ%ÿîýôØ¡]'  &]¡×óüþÿ%ÿîýôØ¡]'  &]¡×óüþÿ%ÿîýôØ¡]'  &]¡×óüþÿ%ÿîýôØ¡]'  &]¡×óüþÿ%ÿîýôØ¡]'  &]¡×óüþÿ%ÿîýôØ¡]'  &]¡×óüþÿ%ÿîýôØ¡]'  &]¡×óüþÿ%ÿîýôØ¡]'  &]¡×óüþÿ%ÿîýôØ¡]'  &]¡×óüþÿ%ÿîýôØ¡]'  &]¡×óüþÿ%ÿîýôØ¡]'  &]¡×óüþÿ%ÿîýôØ¡]'  &]¡×óüþÿ%ÿîýôØ¡]'  &]¡×óüþÿ%ÿîýôØ¡]'  &]¡×óüþÿ%ÿîýôØ¡]'  &]¡×óüþÿ%ÿîýôØ¡]'  &]¡×óüþÿ%ÿîýôØ¡]'  &]¡×óüþÿ%ÿîýôØ¡]'  &]¡×óüþÿ%ÿîýôØ¡]'  &]¡×óüþÿ%ÿîýôØ¡]'  &]¡×óüþÿ%ÿîýôØ¡]'  &]¡×óüþÿ%ÿîýôØ¡]'  &\ Öñúüý%ýîûòÖ \&  %YšÎéñóô%ôîòéΚY%  Oˆ¶ÎÖר%ØîÖηˆO! :eˆ™Ÿ ¡%¡îŸšˆf;":NX\\]%]î\YN:"!%&&'%'ø&%!û  &  û &gaphor-0.17.2/gaphor/ui/pixmaps/association.png000066400000000000000000000012161220151210700214500ustar00rootroot00000000000000‰PNG  IHDRàw=øbKGDÿÿÿ ½§“ pHYs  ÒÝ~ütIMEÔ _0tEXtCommentCreated with The GIMPïd%nòIDATxÚÕÕ9hTaà/™˜(A Lw¬\¨DlD Á"`¡ˆ¶Vbë‚ZØ©©Uì„@À%6FDS¸ ‚;Q£¢b\â–ÌØa8“4^øy¼ÿÁ9÷œ»¼‚É©X‹ø€_“Z‹¹Ø‹”ðmh¨›p=a¶¢©ì[ yVìÂ' ÄŠRÎo\Åʨ«(öã¾£/–œÇ—2‚>Àl¨Ä¢éØ.\K!† Íx‰QWÁÖ§Cú10ÂuèÅu7‹»‘A^œçEÑ*(SR¦ÅxÂrÓ$2Èò¢przÖ*˜Ï¦Ê´c‚KÜãcU³µU†ao[¿Èà§ Ô?±µ×_„ôDcªLÉ|6 ë]D£x„cŒÕ£8hѽà¸ÃÛj1zÁÔ!ÚwuÀ–êu±Fô‚Lž}-!j¨ðŠçæ^[]iÍšÆ]ÃPiYv=ÿÏ~1DyîwIEND®B`‚gaphor-0.17.2/gaphor/ui/pixmaps/box.png000066400000000000000000000005201220151210700177210ustar00rootroot00000000000000‰PNG  IHDRàw=øbKGDÿÿÿ ½§“ pHYs  šœtIME×#é‘§1tEXtCommentCreated with The GIMPïd%n´IDATxÚíÖ±Š1…á/ÖÎF¶×§Ý^ŸÉ÷°³p ÙJd4^e´°p"XÌ„ó‡[Ü“äª2RŒ¾:£Å9aŒõô‹9¶9^Þà/æcOó¬°À.wJÒ`”7ͦ8ÝJž;‡Ç0ßW,•ü´/Oó;z¸;òa €0À7úÆezÕ®'}ÚuêxÜ%2tITC³ý’c±ˆÎ•-6ho5w¾-5Trº£"½½2¦èIEND®B`‚gaphor-0.17.2/gaphor/ui/pixmaps/class.png000066400000000000000000000005451220151210700202450ustar00rootroot00000000000000‰PNG  IHDRàw=øbKGDÿÿÿ ½§“ pHYs  šœtIMEÔ ²Üê tEXtCommentCreated with The GIMPïd%nÉIDATxÚíÕ= 1†á7² &ÑÆB ìì<÷ð&Þ@/±'ðŠXÚØ(bب,±VûÙŸi¿‡0D)¥‚¶$'uB]`¯ªµz˜'+Q`:s>—À¬DqÓâ"¾¸]¯â¡o™Zëˆî33z‘1†õv'ú‚á Oš¦ßräÏ|5}ÍŒ²,£ÓnŠe­sÀ怚úMˆ¼÷â5µÖæ€snŒ+8çÀ]e Ä€’üw€ýrÕP´ÖÎIEND®B`‚gaphor-0.17.2/gaphor/ui/pixmaps/comment-line.png000066400000000000000000000015261220151210700215270ustar00rootroot00000000000000‰PNG  IHDRàw=øbKGDÿÿÿ ½§“ pHYs  ÒÝ~ütIMEÔ22>ÐÑtEXtCommentCreated with The GIMPïd%nºIDATxÚ­”ÝK“aÆmÊÞ•èbˆ+´|æGéAÄ„„QêIDþ å? xÉì „:‘¢À„4Š‘3Ç´3g³-5µYûòѽû´“ ¥Ó^ïãçù]Ï}Ý×sk9ŲX,†¾¾¾ÍÍÍ›³³³A ¥9-¸Õj­B¼êïï¿<Úý©À;::.·¶¶Žƃ²²²UFóxUwÐÝÝ]‡ommÝQÅ»···’ÍfQ%ÐÓÓcˆÅbO@÷îî®w?N§çq`Hª¨­­½áv»»¢Ñ誢(ß àK@@•@$Ymkkû’H$3™Ì'àmüàÌI¡£££g=.†'&&…BWÂá°˜–dáù T¥R©g~¿¿ËápÜòx<®Ük% dþÛŽ¡¡¡êÞÞÞ7²,455H’ô¸Zì‘ÇšÁðððŸÏ÷hffæ®N§£¥¥ågiié9 TµÀÈÈHÅúúú“©©©{™LY–·'''½‘HdØÉó°Ò¨¨¨0ÛíöÁx<Ž,ËÛ.—ëëÎÎÎû\Å|?ŽE¯C¡Ûsss+Á`pˤb—‹vÐÐÐà2›Í¦ñññ5!ÄÇ\Ö?4¦uuuU‹¥Ì`0øl6›hLÀ"°v\ø?-2²âE6›]r8í¹3 €øvø_™L¦zEQµZímI’VÜn·”H$>¿€t±ÄÙAMMÍ¥d2iÓétåååËN§Ó‹Å~q5ûJP__>N?:õzýòÆÆF@1—KKPµ€Á`° !nêõzßææ¦_á,Øé)5%•••×JJJ6œN§7•J-äàÞüNWSù˜šûÀïœ-§/€‹À>°}Ò(Uÿá2Œ$LcIEND®B`‚gaphor-0.17.2/gaphor/ui/pixmaps/comment.png000066400000000000000000000010161220151210700205740ustar00rootroot00000000000000‰PNG  IHDRàw=øbKGDÿÿÿ ½§“ pHYs  šœtIMEÔ LŸ#¦tEXtCommentCreated with The GIMPïd%nrIDATxÚí•O+DQÆçθ3šÒˆÄÌØ!ùYHÍ dÁ¤$¤YYÙ±±S“|vvvÃwPd¾€R¤c£nÓÕÌUæÞs, Ê¿2síæÝ½õöüÎ{ž§s„ÇãQÁ`JJ÷ùK‘H"¾HÙ¶m}7#B¡°:=;ÿ³¸eY¥8>Ü/y4±›Ídö¤”…Ïs–RŠ®¾Aâkëu m3 oׯûÃpt’©ÅŸTj;Ž$€z÷€Ð4F§g›_òÛ¶lko_tך¦1>·ÀÈL<`‹I ÔU ¥g,3OÑÌc=št÷aF° ´Â[©¸®ë4ÊÙ«K¤T˜²ìC3«8¦oIRJ½÷77×LÄ¢&pì·Þª !ćâýÆŸP®˜üÓ‚®¥èׄÕ5@ ðÿo.wÇ@o+bŽã|8ŽÓiÆÐäâÁ À)ÿªÔ­€·Ü»ñÈYÀ=à¼EOwå óPýIEND®B`‚gaphor-0.17.2/gaphor/ui/pixmaps/component.png000066400000000000000000000011211220151210700211310ustar00rootroot00000000000000‰PNG  IHDRàw=øbKGDáäæ uj! pHYs  ÒÝ~ütIMEÔ  5H£eÞIDATxœÕ–ÏKaÇ?³óN»Ã„8*H‡.»v(³ƒÒATðl,‚ÕEð…xB½D^ûõ°þ8(jtí–ùãРàlÕuÝw§ÃÌBÚL¼;jà^˜çûý¼<Ìû>£,þZ ©9/éûŠ*ù ó;˲ÞÚ¶­˜û·ÇAJÙ ¬Š€÷zÇã' E´45²¹±ÑŒ1¼–¸€9ù¬ªSº¦i»¦uS÷ñôô4ó ®BAR“J1:6®Ñ *«o• Í*mkýû7F‡û•jÁkêWI±« ÿ/p°·ËûÞn%ÃñaæÌiRÈÜIöÙ—Ï‹õÀ&Ðù¨½½áÕë7¡&Ó4Kä4ð ÈwËËí†ÚÚû%ìó߀€䀪ýý=VV¾†šLÓ$™L)4À¸Oäê6*Ž3èò„ôÌ\hUŒ —Ùô |PD9hWªë€»³ådºÚš ¼)7 #–H$ ÅÛ´@ÞuÝ{G™_w€m ïù‹—O/2pÎ$ðÓ/k—’ìÉ-4é?§S“,/-FNu` È͸ ÔqÔçÅ é¯]àX 3 JÿmÉÿá¯Ý§¿„ªf pDIEND®B`‚gaphor-0.17.2/gaphor/ui/pixmaps/component.xcf000066400000000000000000000041131220151210700211310ustar00rootroot00000000000000gimp xcf file00ÿÿÿÿÿÿÿÿ0BB"/ gimp-commentCreated with The GIMP½IË`00 New Layer#2ÿ     "m0000‘²¥¥¥¥¥¥¥¥¥¥…²ÂÂÂÂÂÂÂÂÂÂ…²ÑÑÑÑÑÑÑÑÑÑ…²ÿÿÿÿÿÿÿÿÿÿ…00 New Layerÿ     ï0000r¥¥¥¥¥¥¥¥¥¥ÅrÂÂÂÂÂÂÂÂÂÂÅrÑÑÑÑÑÑÑÑÑÑÅrÿÿÿÿÿÿÿÿÿÿÅ,0Pasted Layer#5ÿ     v,0Š,0šþá)áþá)áþá)áþá)áþá)áþá)áþá)áþá)áþá)áþá)áþá)áþá)áþá)áþá)áþá)áþá)áþá)áþáRáþä)äþä)äþä)äþä)äþä)äþä)äþä)äþä)äþä)äþä)äþä)äþä)äþä)äþä)äþä)äþä)äþä)äþäRäþæ)æþæ)æþæ)æþæ)æþæ)æþæ)æþæ)æþæ)æþæ)æþæ)æþæ)æþæ)æþæ)æþæ)æþæ)æþæ)æþæ)æþæRæ]#ÿ#ÿ#ÿ#ÿ#ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ#ÿ#ÿ#ÿ#ÿ#ÿY00Pasted Layer#4ÿ      0000/   'ÿ'ÿ'ÿ'ÿ'ÿ'ÿÈÿ'ÿ'ÿ'ÿ'ÿ'ÿÈÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ00Selection Maskù00 00²ÿÿÿÿÿÿÿÿÿÿ…gaphor-0.17.2/gaphor/ui/pixmaps/connector.png000066400000000000000000000004441220151210700211300ustar00rootroot00000000000000‰PNG  IHDRàw=øsBIT|dˆ pHYsvv}Õ‚ÌtEXtSoftwarewww.inkscape.org›î<¡IDATH‰Õ”Á Â0 EßGìÃ-ƒ0 ƒ°S0Æç’F µ!Äõ¡–rˆä¼§Ä±e›Ì8¥Òsä°¤ÕëÛÖ.€ûãùµ¿]/Hò"I¢ã °=´€ðÖªyƒð xµ$ÂøÔ•Ÿ ÿK0ïŒÂ»øOAÞ”/‚WA£aæÜ6²$¯MÅÒé"Çvé‚Zƒ­„]j…´B囦Å@ÇŠE? bÊIEND®B`‚gaphor-0.17.2/gaphor/ui/pixmaps/control-flow.png000066400000000000000000000011001220151210700215510ustar00rootroot00000000000000‰PNG  IHDRàw=øbKGDùC» pHYs  ÒÝ~ütIMEÔ , ØèvtEXtCommentCreated with The GIMPïd%n¤IDATxÚ½ÖÍ‹Íað{™11Ê,4eŠ”HYyIƒ¦D“²´²R²‘b£(+“—²óò`§” c#I³“”—1šb0‘÷æÚ|oÝ&ÊïΓo==‹§¾çœïùžÓSW ±«1…%HkèÅ!Œ ؇Îùs îÀÄôµ¼5Й»2ºC8Œi¼‹œŸ¸ƒ©®Žâ)¾b"’\ŧ–8ލ"Ñ"É0C 0‰•x…û©®R€/؇Lb9®ãWnÃ8îá9fTÔ¨½bYÈ¿ã6F± ¯ñ •ÑFØ‚ø–̯`S´„'©ª-ô'»q\Æ $ØHÓû³‡ä_1€›øŒ[¸˜Œû#Õ3<®:½órïÀûÈpëQÏ[_†m7ºª/ˆËñ!²œÃº?TÞ‘S K±+šá,Ö¶d>'ô`'Þâ%ÎÄÿµ佨‹7!?¥È»p,šáT&vI ò:ÎÇr£8ŒÍí4ïoŽ9‘}3“¥²n’_Ëÿˆý¥ônb0;f GJ“×ÓØn\Êâš)üPÃâÒ™ÿ7üÚhέ? IEND®B`‚gaphor-0.17.2/gaphor/ui/pixmaps/decision-node.png000066400000000000000000000014301220151210700216520ustar00rootroot00000000000000‰PNG  IHDRàw=øbKGDùC» pHYs  šœtIMEÔ Ñ •tEXtCommentCreated with The GIMPïd%n|IDATxÚµ•ÏKÓqÇ_ߺÀ,=Œú"¥m'#“XȤƒØÁm^ò ‘Ù!õ8ÈÚYëPºHLíÇX)Ø”e·(Ì?À(ÉoÎfßĹ‹¥ëK0òÓe‹5æÜÚzàÃ÷òð<ßçÅó~?ðŸ£$ŸdMÓZÌfsïüüü2 xQÿÆn·PE¶··/n ¢hŽ^ï°B655I‹Å"ÇkàPZpñ`0x^UUÙÛ×'µå©ªªlhhøÜj¥`4V«U®ê_åZ$*ŸMLJEQd[[Ûà p¸`4/f_ʵHôÏëêê’V«U¶¶¶Î-@YAhR‹¯E¢é¨î–L¨²®©®ë³ñxüèèØ8¢äïT“ÉÄɺ:ŒŒp¹\GÂáð6ð ø™S¯×;.<~â£úØñŒ95µ'ˆnDX\\¬²Ùlº®‡uàW2Gì…Æï÷{.÷ôpÆv6+ÆÁ¡›†Áæææià"P›Š*ã[[[³±X,#šôHEÕÑÑaÖ4Í>&Qeœ@J)òY™øîîî–§€CYuww_3 ƒ;·oí[üGl‡ëW©¯¯ß™™™Y–€¬ Ün÷«ÎÎÎGÇÇyÿn!kƒCƒ”——SYY¹„€ð-—ÉEccãçT§¿§“R!Nç€)g¶>ŸÏ©ªªìéÍ$´PRhÛÀ0Tå+f¥¿¿,“U\JXEss³ÜßKɹ¢ZIE•†æù?{Q2ü~¿#‰* Í›‚Ý4‰ÊãñŒ !¤ÝnO¢Y*Ú=HABH—˵LärÑò9úrzz:¤(JÙÔÔÔRbsÞ¦»g1â P8øû¢ù ›sP˜¶âIEND®B`‚gaphor-0.17.2/gaphor/ui/pixmaps/dependency.png000066400000000000000000000017611220151210700212570ustar00rootroot00000000000000‰PNG  IHDRàw=øbKGDÿÿÿ ½§“ pHYs  ÒÝ~ütIMEÔ 5‰£ÅÚtEXtCommentCreated with The GIMPïd%nUIDATxÚ­•]h[eÇÿçœ49ç$iÒ„f4Ía(C™m6«l¦hŠ®­(Œ1EAdVðÊa¯v' Ao¼z9Ø…l2d:ÌtÑt² n¦Ûú‘BKšsÒ9ù899ïùØM Û liú\½Ïóÿ½Ïÿ}Ÿ÷e°‡qvö,‰Gž?\-­ê\ß^Ïž›¥Ôº*är¹ãâ†øÙÐÐàK×zœš9Õ'VÅýWºú‰ü¿Ÿé/üUx±ðgátKk}`h†Íñœ …B!Dp@£+Àäôd°¡4Þ/ÌŸ–$é0˲.MÑ«‰} ŸŸõ¯1 3î8Ž[¯××, L7S3¿ÝÜÚüÆj[aŸÏW¢iúÆÇF³ñrꊢ샇ªªæ\ ÓÝŽ¼v¤2, ÿ¦jê%Y–¿Çâå–ÚšàYþ?¥¦ô±~Ö2 ³ìyÞM 誃¥¥¥å-¹hÛöïç¿>¯‰’øºe[!dÃ&ö(Ïó늢Üñ<ï—mÝåùšc‡ÆÒ£éã.^ø9À–$Qšã8nÍu]¾V«IŽãÜ otÏŒ3ívû‹õõõï´–VÍçóñH$rO7õ‰h$*†Qp€µ+€Ñ2"åJù+>Ì—ª›Õ»„ëÙlö¹¶Þ~Å0 BÈUÞvÍ3]Ó©·¦h]Ó§™>FáÌòÊò«ŽãÜð7âιž»\«ÕD·h]™ž}3K|éà‡Ñh´‘NÝð6€!NJÀ;Þ0ðdýS;ñ¡‰¢Xœ Ò–¼µÑÙá&·“bø£³n?3àØô1*N¿°²¸’ŠÄ—+b¥dYÖåÎà>’ê0wÒÙq¢áèdq¡x%•LUçoÎÿh;ö-yjÏÏïÈá‘7b±X9‘H¬ Ä~0 À¿g˱s‰D¢Äsü%èß­ÖceŽfÆ‚\ðS³mæt]W Ó¸à×í±ï)2G3#ƒƒƒ÷“Éäb8~À~!T/º4œ›kµá®=Þ³'[·ð¢© K»ví*—¬k‹îÑC¦nJ“J? Ö¾}ûËÇŽŸp‹cØ_³hñ» 4¸J¿ÈýáÇã5NÀ™¬,ž0 fЗ'¥¨ö »Eç‰,¾ ¾yófuŽ»‡x|¨¼¼œ'(¤WKŠ‹é‰b5ìõƒ1½{!„XíïïÿPdöK/ÚßÔ"°˜æFAÀ¯ Ý~Ô9éÆªpWýös1Zh®ú©Å˜ æo§z¿Ô­£_Tt´eÊ”©²Ð^xoyÒ²nÀ¹.]ºXöø†-Zx-î+6ãǾDAÁÊÊÊþl®*gBâHç›sÿذuèØ‘¯¢eË–^3²Z­L2‰ÜK—ø<9…~ýûWZa5‹Bhã;Öö:pðзyÍÈn·3eÒDr²³ùlýú?óŒ«õ^ ¬ò W¹Ø éÕ#Êgk·eëvúõï_ë8Ó"z\èÞø o˜áN¥Ýù+wäɇ5˜±i2júúúRII ‰#†¹Z—ê ë#K€ÒÒR⇠áòåËG€ à; p 5U%ÅÅÄŠÕj8*„È0 c¿Ãáø(4ƒ1ŒéÝ à(pØ'¥<êp8®}Ù™qs'`4Ðð×l¢7dÚJðOÅôª›pFºFš†f }ehz‡»^FªMGB[N7®åš 7ý®3!\P›cM×ät5Bkæ .zé¡=µÁÊ1øé–F"}מ@€„ÄD?å­»þeÉRj ݵKyù¼5ïmÌà1sET1m‰ñÃo'$&Zv¦§gO.X´˜q&x¯=R’²!™%~—Ñc~gvwZêmåøY³ç°3=ýµ`á"¦¼:!¼SX§ÓÉÚ5k˜?ïOŒˆç½÷?@sîîméò¤eg€¨7æ¾É”©Ó|B¶!y=ï,^HBb"I® yóæµïS•È|`æt:IÛ´‘…óßfD|<Ë?ZU#3}JO½æÍ_à3€©)¼5÷ †ÆŠ•{d¦Ïq½TÜø ò\ÎÅjº¿ ˆÍ)5êã–"ÂBY²ô}Ÿ<¾hJÓf¸IȽ¢Ðˆn=|¸þ!†·X]Ã&¢þÿ"J)Ù˜’‚æÝ3¬oˆhÒgëÖ²hÁ|€2 ¸e2nð)]÷éÙ—æ™ ¯ÑÌþºj%Kß[P&„8 ¥<üÈ7£8KC"SÌÊ5f{àvƒjéšOV³há€;ŠÙánݺœWò«·êc¼•_«|b/p¸£ʲ Ãz‚¼¥dõ-°_1»­º«#ôW!þhòû:ÍR©þ ¥•g3é&:¯,&wR¡\Ë:"¼­T?ßUf5EÞ~Z¾/ê€Pªi«µÛØÎ·±ßïSâ䧉T¸Ô™ ížNÑJGän¡@[ …^ rµ¤úl³fÏ‘öï,Kj±gÏî;Ë“–‰\«M~˜´Ì²_>kö‘˜("ÂB£Ý@W€¦Íx—ÆŽ£uëÖ£ƒRb·ÛÙ™þSSÈ>žV­Z‘8’é_™ÃúYʶÞPW¡gu‹S)ÉY³ç˜UGe{™›ùúïyaÔ(ŸŠ¶¾J¬Èngÿþ}lJM%3óAAA^ùW¯^­$pBq°I)/ªÊäE%Å3þôZE›šJKKII^Ϻuk±êEÉ !„ÝÖ¥K~öùóy Ø=îWE^+ú\Ü ìv»n Ä'AÒêÁ/i‹ˆ!’IEND®B`‚gaphor-0.17.2/gaphor/ui/pixmaps/ellipse.png000066400000000000000000000013161220151210700205720ustar00rootroot00000000000000‰PNG  IHDRàw=øbKGDÿÿÿ ½§“ pHYs  šœtIME×VAÆ'tEXtCommentCreated with The GIMPïd%n2IDATxÚ­Ö½oNaðß9ÕF%US[_‰¯Ô"a@HºˆÄG˜,Åj`0¬6Iÿƒ$T« ¾ZBê£}-ב“ÆÛ·/îäÍIž÷<÷ýÜ×uÝ×s ­£È¯#Oh`>ÏF«ÍÍ¢;p ;±«ñÏ1†<ÄW,´S`.&ñLàu’¯Â±㸄ûøÑª@#¸‚»Iþ/ðsyo%Ö`+Žá .ãFºi ÛQ<ÂyÇvôbÊeÖzãpºN‡E3XÆp‡ÐšÁXìÌ»§1…ÃY#ʨ½Š'¸‡QÌD)ÍbE ú†/x‰õ8›á«Qæå5BÇñ®™*jñ³ÆÉBö\ÇFìCW½ÍS5B_es«¨ÏBUp·q=(ª;CÒË´ÛÐ~4²÷6EeE6Gçï[àÞ*æ#”¾tÐQ¦jBçþòôõ.f"Õn”ÉÕ„þè T¿µ¼r"»â’Ø‚7•?•5ÜCLÇ?(1kùˆù2¸Ä¸¶.5êË8ýÚxÙÌVƒÖˆåŽÇ¸ÖeJÛNœÁtœàS]0%öâi «?kí@³“8›NÊf'˜ˆq ÔMk‰èJòǹ—Ú·*LáödFšÙõ†\L“¹v-æðON7àBŒëvÆ,CÔ)…Ðéˆäž-¶šb ÂúãŠ'ã-}9Ý—èüE’ަã·Ë¹2ו÷ªîü÷5:ŸZ¾·{éÿ·Ï–_»º’­ß5ÛòIEND®B`‚gaphor-0.17.2/gaphor/ui/pixmaps/extend.png000066400000000000000000000020271220151210700204240ustar00rootroot00000000000000‰PNG  IHDRàw=øbKGD¥ÂÑ"{ õ pHYs  d_‘tIMEÔ 5¶Ö\~¤IDATxœ­•]h[eÇÉIóÕ¤IÚÑ6aèŠ2ÛZ«l¦h‹®­( ÙªÁ g¯œL{µ;aîÂy+ìràÅp^ŒÉ°S«éÄ ëìÖï@k>ÛH>zÎIÎG’ãEŒ¥­ÍÜ/ïó¾üŸçÿž÷áGhç&Ï™<>»ëñ®Žðj8”-"ñäùI“˜SSS'ãÑø{íííàCàæÿ"83q¦!žŒ¾ñíwR¥&²Ùl@×t“ÙdŽÀôCL¼?Ñú%ôdèçÐYI–Þ,È…¢ÃéP0Àårº®§€{@®.‚áÑáÆ\:÷ÚLhæl"‘è³Ûíe³Én;Ôf±Ú­k‚ ”J¥r6›]îºP"+Ÿoln|¦©šÛb±¬šÍæß‹ ç¶rÏ4:óétú ¢(N·€¼¹‚cÏ‹u:¿eñ›T*uÉ×â‹H¢4ä´;ÿHgÒ v«]S JÄ0ŒÛ@ ® –––î¦6SsÅbñ‡ Ÿ^ã‰ø ZQ“u]õbÓé\O§Ó³†a|W%¨«@éºÿ~oOïÉ+__¹n³Û–ñÄe‡Ã±V.—™L&Q*•î©j@]ÁAUÕÖ××/Ê’œœžžöy<žy%?äõxã…B!ÌÚC¤‚'‹|ât;W“É{º®ß|LͫϔBT×õ Õ˜éÈË#漜„@ øhyeù¹R©ôð«Ãå8_6ÊË™L&ÜäzD3øÒ éèSGǽ^oÎßéÿ xhlˆ xšwÆÿg.§kh.>wÙf³%6S›ÑŠÂ  \hÀ_=0Á‰Ñ¦ÞÞÞ'VWü¾fßr,[Õ4íÛŸ€2{„ߢû*ïïë>Òu$9vzì"ðÛ­iª\eû1«ë*ðgÅÿbßäÝ}Ý/¶´´DÚÚÚÖš[š¿zë.Ð*Éxå<^I¾ÿ…—Ãï6¹›TI”fó…ütE™¶¶ÆüBSÍ9jªEû3éÌh2•¼«ªê¢(€›@¦VÕŽ ÆwÜ™vÁAðx°»µµu¾££cÑívŸ®=þ!¨¶èãþ*À pêõSX,v ï–¸µ(Šb HÒÊw³/·*þ%*m˜_˜7ì QÃ’(]B€~€¤þ{˜aûwù¯¶ ‚0¼ ´²[ª¶sLk×®cjìL~`û\§‘¬œ“âêIEND®B`‚gaphor-0.17.2/gaphor/ui/pixmaps/extension.png000066400000000000000000000012171220151210700211510ustar00rootroot00000000000000‰PNG  IHDRàw=øbKGDÿÿÿ ½§“ pHYs  ÒÝ~ütIMEÔ !#]\(DtEXtCommentCreated with The GIMPïd%nóIDATxÚí•¿‹AÇ?n2Ùü ¹M²&‹G!…„ÓX(±¬B* ‹t±ÕÆÂ&â?`ï_ ׋Øj£…¥ŒâzÁ¸˜Éº“ÜØ¤È—°¹‹Ý=æýøÎû¾7o"l Ýn7[(ʃÁàpÆÇ¼ßï']×}6ß4›Í@†mJ¹\¾V,ýJ¥¢-ËúÇo‘­ïõz—*•Ê—3MÍfêõúk`8wªàµZM8Žó¤T*Íc±˜t&“Ñ–eýN§Ó÷óT5¨V«ÝD"qdA€çy†‘²mû¡mÛWè‰nßétvs¹ÜÛT*¥C˶mËåfŽã<Ïçó…U1V©ÝnG\×}êûþM)%J©Cz)%ÉdÒ0M³ |›L&š¢étz{0Üò<)å±6£Ñß÷ î+øÊ ´Ö{RJg<¯¥Qaiß÷ï€Ù²~]‹}BœB „`M¦(¥ÐZ¿ºÀ×eýºê_VJÝQJÙK¹4À'`¾dÿ#ìøX¦/X@v±à1paé<»°ý‡òuÌï(#‹6ýŒ¹µawR988ò0"‘Y4ÿ7€V«õ¾Ñh|‚°>›~w1àúbÿj MAbaÿ >‰7cIEND®B`‚gaphor-0.17.2/gaphor/ui/pixmaps/final-state.png000066400000000000000000000013211220151210700213400ustar00rootroot00000000000000‰PNG  IHDRàw=øbKGDÿÿÿ ½§“ pHYs  šœtIMEÔ &ò¼x+tEXtCommentCreated with The GIMPïd%n5IDATxÚÝÕOhÎqð×ólžfj2†v’vqpEI)©\œ”²Ë(ENœÄ”ÃJNV$"r˜!j’?mO©Y¶e›mžÇåóèÛϳٳœ|ë[Ïïé÷ý¼?ŸßûÏ—ÿ}À{Ì ”Ù3øˆ#X‚|µ¹9Š¿ÀJ\ÁT©œÉ¡-Ø…¯Ø‰bÏ Ð†^\À(ð2ºÌ¼Û€Vt =¦¹‰ ”EÙÕ‹sÑÉglE7V$Ÿ¡„!\Æ%ôãmL;ˆ§Ÿí³œÂqìÆüŒnªíRL·kpØ„EÕ-âöGñòåboâ^œÏ•™5bc/T9S!{,ˆý#~É£4äÏ8PIEND®B`‚gaphor-0.17.2/gaphor/ui/pixmaps/flow-final-node.png000066400000000000000000000015341220151210700221200ustar00rootroot00000000000000‰PNG  IHDRàw=øsBIT|dˆtEXtSoftwarewww.inkscape.org›î<îIDATH‰Õ–MHaÇïîŒ;‘ ~°+ê!B/Õ=FZDyèÒ)ĬS”TVH_ë†APb§èPxW“,[ƒÊ¨4+ÝÚö¢°æ¶–»mOw¶ÙÙYíÒ¡˜yþ¿÷ùxŸw”ˆð/—럪Úz(¥ª½@°,€ ˆÌ­KGö_€XÍ0Œ@š iˆÊ^¥”¸´†AcÓv¶56’L&YŒÅˆF£ô÷ßg9‘0ÝÆ€Ý"_3 D)%§:NË›é G¢y6ùú­=Ö.ÇŒìàµG`/r°C×uD„áàPÁÔj𯸸VVVÐ4  ¸§”ò8FTßéî¹.>Ÿ_iò¢x3=# €âóùåj÷5k$§s²bt²kw³„#Q ŽŒ:BìâÁ‘Q G¢Òº¿Í<69– gwj‡<{þÒQ<‰Ê½þ& ìÉE€¸Ýn™ûü%'VH±×ë(nZee¥ ¹ ¸­E.(-+C)•S£šÚZnõÝF×u¾ÅãèºNoßmjjkó oñueææ‹·¸8Ï)‘Hpá\'©T —ËE*•âü¹NÎ@v-,,˜ß3–¼¾ÎÎÎòéS8G¼­µ…‰PŸÏÏÍÞ>|>?¡m­-9…ùy’É$ÀO i¬E¾Èñ'×ì–BÝuèð3ÿï°´ª°ŠŠ yøh¬`·8AFFYÏAÐìP¬Îñ®Ó-vHii©)> œ6æ Y¥4³ñxÃ08ÓÙéØ-°Ú]»ºÐ4X,†Rj ÆE$[§iZ ¼üuõõ´8HUU5%%%èEEŒŽ 300@èé8étà3p'ãwWD’YÁwA9pXÆvØ,Íê½ì47¼æ}`‹f pFÆ<@˜ÊtÌÇLZ^9jüÍ¥ŸIÛf`CR|¦DdiMßÿþ¯â7…£²É…°ˆIEND®B`‚gaphor-0.17.2/gaphor/ui/pixmaps/fork-node.png000066400000000000000000000003301220151210700210140ustar00rootroot00000000000000‰PNG  IHDRàw=øsBIT|dˆtEXtSoftwarewww.inkscape.org›î<jIDATH‰íÕ± €@…áÿ ±p[GrDZtÇpGx¶×y1Gød›ÈèB·}M“¤ X‹Ònûl°ù#i°}| †Ÿ(H Tûô%À\”nÛW3ào„Ÿèï­7¸ƒö IEND®B`‚gaphor-0.17.2/gaphor/ui/pixmaps/gaphor-24x24.png000066400000000000000000000052311220151210700211760ustar00rootroot00000000000000‰PNG  IHDR°çá»bKGDùC» pHYsHHFÉk> vpAgxL¥¦ $IDATXÃÝ—yt–ÕµÆû|™ßŒ™C ` !$ ‘(1€2X„¢•"-Z`{¥ÞVÑJ¡–pµ¨Uz J€ ÊjR‘!@¤’V$H S ï—éÞÝ?ŠÈZzï¥ë¶««ÝÿœuÖ:çÙÏsöÞçìÿâ&/  ã¢ò†^O9[Àd¹Þ• žhÂA;ýô›@EtœI õŸôû' xystiòÐ2-6ÿ2Ôd8€nlÑ7ÀuŽÊ(biç*Ãü;XëݤyÜÃ3ǃ <êdxleý‰£CþÖ?ݖܤwp›+ÑÉÞ™þ§QZ‰§Äèd¶ ’iÜ¥‡•(–Ì ¾B "÷s¾éqëNýHr›{ê§rŒ1Þí d'OK|¯éóÀH×4i€yߪ?ttëÿCÀú–˜ÎÉIÀfùfã"… ÆàG0 ¹‘¸Šq´q¹c =i¤96˜š© ~Y×à¼p‰&Béå­â<ÑD\B!¡ÄÔVèa>Öêö¸ps˜iîâÛ»ü€Î‹«÷hÍÿ"àåÀèºäe ëM!› …·qáãM´™úš. Ü'èE\¡ƒD“Úk/.B%1b ‚`DQ@xõ¬ï+þPîGQ”qh§ª¹˜FmÔ¢ª}z–s\8Éû2G–¶53!x<\È}P|iý¯¾AÀ¥·šNæøÊ•¿éØçø¼_Ç/j5îÀŽº&aˆô ‚¦/©v®tž…ŽyÕÑ×`n25õÚò×Õ’F×JADÕUêY:Òõø:W/ùçž*ù³Óô§Ô„Öü-«}w~§S;R—ã\wTùÁx×!ˆ‘AŸùÓÞy¿.mìà ñ^mº×uzBùÔLbF÷IÄÊpþ(â캶=àfké0Šˆ‡[ £Ì5Nö2ZjÒžê\ ù¼ðîóÞÈx÷ØiËç °ßhOzå ¿oÝŽ¾±ÌŸÍ/T½ «GMy»ùÏ »ªzççu¶‹–œiÛô`·q}èFo³ôKÛÏV#äv¬&LzÑ¢¨ç ¸› †ýt#X/˜zÙÇH’Jüj¢kfÉ…êK­U é§/\ljKŒÑëÓ’ÕÛÚâw"æ”ýe˜ëÉ9EÞDAúÐ#ífD@¡}¶Ä÷›ñŸ—:É9fkAI´Ï×mõԔ˳· gÙHêç'žüp¨ÑÓ ‹†:ê¸=.’:jIî1äúXËe’ã>ÁG ñ³ä÷ôÓGc´@_6ÓÊÞ«îßzgüäÂñ‡ªê%ãOþïž+kÛØ{ܰDuó”Ëêúޤ!r·zÅ…cò¾<‰ë0óÉ` Ñ8Ç à4fhç ö ïz’#'dÏ©8?гJ<ױʊˆ5ëâwmÜR;í£ÈØýcæJ]®Åwfó„y’UÑXÓ ÉgæØg´M·Jó¡áhèz¾xdE\‹§ï¢ÂýCÉN{ȵÄïav#4×Ü/٨ѴDŽ¢òCϽT) øFe¤ðŽÄg³\ß»]ËdÔJ’H¡Sé༎týÄ53(Bçß»·ï÷–c:¦keÀ&k‹#ßÉJ<ùlÅ)j'µþ¢&6ð¿~³î•ƒÓ“ ‰ n«rއvšþŠù6˜ÓÖP³ ÑÆšR“Žšž5Yòjº4ÈÇ —ìárLŠ;WŠ@w•t ñüŠ ’á_M¡fà"ßO®óe`·H:SÁ²LªÌ„°HíÀD°¢´‹ÎKt$wà„uÒ%üBo!Æ? ÷žé°Î·7óÇ—øƒ¤Ä”€Ävû@º„ž“BÔ hòPÒÃ^(k@ܹs«»@V¤Ø…R Rj–P[¼¿Ò:à¡kðº#ð’¼ +éžìѧÁÞ¥E2¬(]®‹ ,\ V¤DH&X‘2€ÉiÒt1X :ˆIаTÖêyƵQVË[øxréÓxŸlñÙÝ¥¤«{±œÁ](G@€¾É)h?ßá*ŽÈOÉ‚ýÒ"éP2T–’—'š—d6¸ɳçìç™™$ód2™$g½^ÉóÌynçìµÖg­õYûì •T²rÅ=Ói÷ómð˳翸õØϯýÕ?ûÌ_¸µ÷œ÷ÜÚ;'üVÌYøÇã=Ùóîü¯í>Ëçöüïíµ“¥ëÖ\=îXÑÝó¡‰6Øóú×üã6Àò¹ >³£¼fɺ¾nñ\ͲWœÌ¥n|m⮾ï¯þ’/»–‹/ÍÇ#†D©?°š`é>âT÷¡åsüÇv\ñÎæÃÌ0¾>â 4¬RGè¼ïf¾¨J÷Q§»/¹¼{ÁmÛlxý}'¼]-˜~†`ê[<1ÔÅÒ©PèT¡~ÏJ¾“YfÎ?Ý}éòîŽyÅý}‡¼…÷ýDˆ@úF„4©lc‹'AÝ:5s?.âN¡síͬÌ,?[xFß!ËçýB4úE1r"¯=wÝšÛñÞ»#ÌzqW\7ЊÖÓ°îïpÄ›E•†(u1Ô#C]”º@Ã@¨‹ðÇ?¿™Gæ]wwç’þ¾½ùÇ%ý}{ÿ÷º“ÎYÌ{1ÈH¤¦¦!˜º5@Ñ–U J=:­RO…N#Ô­Ò)B§?™NQ:k7Ýv#Ý'œ±fúY\°Y ß`@]ÜI4­¤!Hà[WASO¡S¦ t tFþdwB(ÓTét¾, ƒÔi¨ÿqH=Â4 u‹4 µ­Œ€Ðˆ,ub:QêjéCg„;1,ø“§p ºx RÛ0ÄuAZOI·<V©èTÿhÅy^*tªÿq‘rò“ȯ?ìâG&Bë¦Ñ–O`fJ}£ÐÁ´ :ÎÜ5s·4Щ0M ˜ºAêŒÎ«Bê¦øqê™ÿq[ŽÚè'°OB#¨7ü°[ÜÕ[C=‚N±t¦Î½:ýU#аH#öÃ.ÅhHC1õ iÿã3éÞòÈ™¤À³¿½ƒÎ¡!: Ô3èôW]Çâ4«t¯¼Z8u0(ð}ƒi(Ô¿NïïFýXˆöÂãxxòñ[9Þ¨ ké)ÔQϽ 4> ®\<~d«ødßX¿o Ó_z"·G¿<¶ÑÇ&⇭rÕÒ¾•ÉGÏψV‹‹îõ^ÅÙ©Ð tožÆE ’\ÞÝó)=¦­_Vž^ÒßwÎ[ùЫ#Ì©é:~v5ŸºÔŒHq…)ÐÙÕà“÷®dÎQ§®9o¼Ü@Î,âý]góê ãËtMÇLÉŒq?®™«zÄÒyÏÍ|Ý*ÝÇl7òoåß3f@\}шˆ&cŸ€gFW‹q'!B}íJ~b-szNw?°¼{Á†–Þàͼïç‚éwÕ²5b^¸¦tŽ= £Ž 0¶$(Dè4–ºBç7q¯Uº;Ãý`.‹XöñC­¾@ ‹UãK¶†ÇX]¤¸«|ù¥êjAGÐ)BýöYgá·Ÿ?ã‚W Òï~ÜUGžðìHXºk=Ù’ hz¤t’Ò™yc,®Þ?ZÏøBdÐÕâ ixJ¦Ò°P)Ž70N²-œ€ÐÀR·‘; |in˜f,êl¢î‰ŠN« Jðãù‰ˆ§fŒ¯ÕDÔ¢dLŽho§çF¬ÔMä&£žhêF¨‹¡¡Ž;>èôlðB®&Ô:ž[lÃ` õ:¤cž@wƒz—?‰H©ç OTùcF‹cƒî‡Õó9;bê†ç¸ƒZ=f¸ñÄ–N@Î$5ÃÔ§'¡x¦Ìø“pE]„F~Ž È±ÜµéÇ#ÒÆ0éM·Xîw&›ž]…ؘÎ$¡Ó¸“èÌÙSQLèÄ8ø¡.ÜN0uTÊic=ó~}=‹³m*N÷=…ÀÆGoá¨(£3‹Jö¥㈠Uý*(‡Fä<åÉ+¹ðɶªãCNâ^€Çoáu#ØSGR †h§ïGįâ“?›òü¥'ñc€‡VóWb=aièŒ,ƒ°#‘/Ó¯á¢oì~àå'sÀ«ù[,u5Í#p —|nRë‚»oæoã×WR%•TRÉ.+²3~ôÒ½æï;ÖkË6­}v·€ÝóhŸÓXûÀ.7Ëg=_1{OøZÚßwÛ”€«ö=ö€¡,=eÒ})Ò»Ï{ö®‡wÊ,ŸÓ³dªšàÊsY“ìXÑÝó÷»Ÿ׿æ“Û5­ä5/?•ÿ ã®ÍCŸê×j¾û™ÛŸÿý>6ÿ.Y;þXÍ;-t teЕ5¸ò¨×P|ñesü¹(‹wºé‹Ü|Þº5EYöv>r¶ÂK""?o$Z} Ý3î‚Ô( ÐT:ù›VÒ¥Jg&tÍ;µïà{.t/ø0kbwz^ßëò¿ßÆû_v6¯þ²›¡cI"u‹¢†b’¶*rUêFPë~TAQ4R¸ïf¾” ]@—ZºŽ9½ïÏ›€²{Á½uÑKúûŽ ÿ~ üWƒº© Å„†Aƒ` ƒ¦… Ûv ˆ¨ ¨5¬¢´° jPô®•ü«ZºÔ ÊÓ N+OºÌþÝóŸÚÖ Nèxù²þÛ üͼ÷VƒiXG :ò'j€–̤B䣠M·iÐ@QãA¢VP±n÷š0Ö „µÂknâVuÓ°º°üí±¯^{@asüÊw ³VùÄyk>S^ð²Õ‹9an8Í"ƒÓ0Ž,µ.ƒ9hŠ —0hÆP»¹ézš:<ÐLP1NóÆ€S>ª~02ïTü@¡(D.¼ýF>t)tÍ|¦oŸ#St]Ïâ‚¥gqÒzÇ+Ë M¤7¹_7 æ-§}i¨#@#ÏÈ»ÏWfZ»P‹©£‘xÿWÈüE[PTTÕ¥ š©Ã ã-Īw!uߣ÷ãÙ[oäÑÏ`ÁY\0ä/¸Q^4´,þ¹zçÇÁ æs%-ø÷hÃQ”Ù Á4†Úsµšø“6©»@¼ˆõæï/Îú¿ XÔ ¨º2ê¬Ä@ê5K9Ó°¼Ø¼×âLÝä¯  P?8Ò×(ÌÞÏ  KÜÈ`°“á´í(  5šDNËÞ À ’¹‹Rï9hª·1¨µþ½î²Ý@ˆºù¼y7K½ƒ6ëm¼UhÜ!¿)žKÃ`‚m€´˜†!k(ñ !mÄÄ èj³2 «³ãÍP(j¬×®Ó0ˆÓ´æ®¢¥µ€·aÈ£›}Y^T~±Žøöÿ)€.=ã5n­·ãÍ>‚†&t46µkŸDãñPû g ‰×¤‘2Ú½…FD¼æ=0.à纾\ö½Èãx~¼¡0¨hø‰¸~€Lþq£ƒ)I£F­ÑÅðàUüC½ã€—žÉ ^Gòľ¨ÌD¥Æâ;­¥…K8Í{0´^ë™ ðKî ªnÄ›±z´/µoŠ Ï/Ö †i uîó¶‘x³WâFÚàWè}®ÈBÚ±€ “ÏûéΔ-­c¶C`Åœ…oUtöÔJédhIÿ_Û¡°¢{Á¼jJç¶*¿=oàÎoO謘=ÿhŒyã®”ã«èª%ëú&æÎ9Ìé 3صFàdàgê—w÷|^T;¦vu'zÞÀšeùß‹øè¬ëùÌú¶à—+YŠ¡óð“ùB |m*^|¾œ¢ËD{Íc ½tð.¹¼½XÍ»ºüã]¯<™ÿ¯Ù"üËT¸ð¥ý}o ÿ~;ùñ3ȀƵ\üÏma€@¢GJd!~`%Ÿ²‚}Õ)üCøÃÛ³åvi|]ßÿ Sã³ùð?–SgMj±IëMóã²€‡Wq¶….+t‰[ˠ˪› üúÈSK׸‚žšíÖÿ;ž%ñâ°\>›­(gŽæó…Êy„[ºykQ ÁY@¬+$âŸgÂËîYÉWUùÑѧñÝsY“ÐÏ|iÖ¼—FQüíråʇ—ô­Ìÿ|+üª ê'R% ‚$ ˜Tܱö ØZwѱ,±QbbQÞpÏJ¸èU§r÷{Ößó8pÂåÝ=‹»LT¹fé@ßçÊ ÿÀçAÊg”•,i„$© ‰`·ÃVò":-t‰¥Ëúy¾Ö-ñÑ¥–®bº5’áab;ñX î17}Ä%Fäǔ؊s„تw‘8ãæß.´X\¼=?0ƃŸKx ’úÇðÊïÐLùÏg[©¶8)$QÍl»‹´Ä„W$ùPéÒeñÙ"Îçó÷z³.5üm)´jF ¼Í_°s¡2"„On9.b(¤Q¢-ËØkbo扻Xò¢­Ë Š0¦Èª$bü{J­'-VPX€xT7!yn/>¶/ÞñnS¢¿I"4µþâãí‰ÃÞœ×X*>)Ê#B™%8Í»üÀ:ȨÂÔ ¢H« h·Õ»üÔYîÿÍO3fØÄ )ؤÉ0¤µö- f½)@'”šJ`T B¡!6–X ‘X_4I`þ~ÀÊP”ƒ—¤¸Û’!J=&¦©Â+ L*X÷ãÔ%`’ M§' í‡Á醴¦$‰’¤Þ3—ˆ¤¢$^ÃÞ}¢þŸ¡1ã[)ÂdŠ–Q@ýÅ+Ö›³ø$‡Â5r³7>0Þmðb =òG:ÏN:]xmÏêÖi*,AJ+ˆ(Kdç ãËe´´¬O¨4ˆ”wæ¾->sL‘ç‡1?·ÁIz·1üÜÅG‰’$ {¥ñnšÜª4DÒiI:„4V’Ôe†‰ñV$GNãîâScüû¼5äÖ!ZAॶMž¼Œ(xÊ Ñ¤Æ[Ó¼»x ©zð«¡©2œDtoG&x&馄dÈ’¤F>#Ìü£Hé 9:G¦Ô–m¾è0Äåï˜'IÇ*xò¤¨4{óm âkwñšBG’1#™Kû î¶¹gn Î"gê¶F’ {PËé1-‡þ¸l1Ê“#ÿž¼XJ|ræ[-xÔ—ÁÕcFYð¢¤ÃÅü4¥–l†áoòáÍÒÚïL6›ž¸“Hƒ8ª9L(Rä ðÉùCŸ1ºtY ® LƒƒØrÁR\ ‰Á¤­O†³Ò˜8m{í€\^²§ž¼Ã|ú¦È†$ä1ÄjIþ0¶n "  +xÀÔ”©±Ï]t€i}æW†½M¯äÂq¯)ÔÖMSŸÀÃzñ¯:™—Æ>2¤.áCbl™î†‘!3Þ|B„ÁQ žf³·Iä‘¿µàù*ŸZÛ.áÚö]c~¾{V2gfļ0E––¤ëÁäÖáSäæZ ¹à‘"tž`‚Ò×VJ‚5ˆÆi%§±<Å•“!’ìÏ&úžÂ ¿qò°“¸ à‘Õ¼%káqa0ÊÊlÒ[€cnœä…‘&Šä¬ÐÏ¿Á§ïÞͦvçè¡'ó/®fIÎRFWB h|aÖ&}ì>óýÙgÜá·Î¾âd–<°š‹>c0©¡ µ¼Ùo¼†Kþ‰I˜I’#N泬äS­ŒEÜ7Ã×rÉ?°»‹*æþ•üSÐ׿”J*©¤’J*©¤’J*©¤’J*©¤’J*©¤’Jv Èž|ñWÐSËŸŸËš” ^•¡2€)&ŸÙçð™i×Vw’H?¶ƒm© `2<|NϬáÈn÷Íî2Tš¬åŒ*عlß#÷Ѝ½`GÿN<”¬{×$/ôUÀ(ÒË‘/œ3í™êN½ŽéÓ²GÞñû{6W0 rù> zÔÚiSõüTlöܺî5½Ü˜V0ò¥9=GEÆî¿ëB¬Ý°dÝÝwT°­ Ÿ5掠ØÌÛ]SµöÑ%÷ÜW€—/Î>fv—Èë÷T."’lå»ûïùÍc½œï7gÃ[餒eˆÝTþ·'1¡œX>wÁŸ\©xœáÖ/Y·æÙ å1€+æôœ¢†S+N¬X±÷/}ní¦œ\Ú}üÞ¤®T4É‘™«–®ÿùcS zÁìÛ½àï ì]©hÂñ:™§ŸœHÒ©mxhçe× }‡žÄ-c¢Ãó÷­ ˧dï<¶­ ‘/ž»n̓£½öÞ3íyf¾K±b`õ5\²vÒ àÁUüq7À׊Հ  ×v2Ž™ÎYx²{^¥Ú1?Õ_Ò¿ö_Çzýl>únƒî£~¥·H€${Û7øÌ¸ï¢lÿ.iC"ùÄñ©jª,z`5uKÈéæ3GɦüsKî\ ån¬Ëçô,Aôµ{pªÿÀ’¾¿ëå·ñÑ78Î/Š–j±`Šñ &™Ô­e“ná­¢ÄX¯lŠÇ¡¦nq„š7²šBl•þy§òiÆ.kdy÷‚+ØÖ%6wMÙ4·èm‹¹x´ß·(¼SŠeBL*åÚ+i°ðLš/ á—ºó*.~`òÀ-†¸åfTl^ººÍ”Áo#l=n`ν«øÿs£°pûѧpeèKúûÎÉÿøâìcfO‹Ì÷Pj»²ÆMdÿú¼gïuwðñ¹)ßóK#¥åRˆê¬ n…@¿b¦R<—4X^ѶsnÛs‹t"JŽüù~"Î Šu/ˆÛS¿É6ùg¬€(ÇÝ³Š“ó›ç+>Ÿæ?ðþçïzxuþ÷Šîù'ªÊ—w\ÿÜ’µ£./¿ˆEQ}Ya¶AÒ”$_+-—Ó@ÙnÍãC€)Ö×3©ú…R·_ù$†€GWó'~­ÌØ0JðÉaf©å [ëŽYŠÇXñaBÝgƒ¥†óžÂ¯Ç:‡Ë»ç@s¦@âö“¥k—õúbÞÿwNÈWƒ£\1U¿è*>–›r•¸– ~å÷Ü!‚I›¢4%½ï\òȤÀc«ùCu‹Õü‚ɱÊHC0P˃P·@J-ó¯Ùr)Íš_MªF`D~A…Êf;ƒ¿X¸1WˆYÞ½à{ÀÂ_ŽóÔïú÷>e¬žÿ"Þûæ˜h©úµNÊ%‹ÕqýóbÙŸ4_ 6ý~ ÐÔ¨#…|Ù°Ô¥ŠMj˜®äÂÇ'-øÕòÔ(XÐü9AÌG.EŠfþ˜÷fÿ>b-òŸP¯ ÓÙÌÿôÝä°Ö,<¥á9-éïûóüù5ûÏ›±)‰î˜5*·F¥çÜ5£¢Ñ_ðþ#cô§<§pu±;_72¥Xö<÷l)”*Ôû%“<Ôç9MÀ¤Æç¤Ö¯¥X|’s€,&1‰SržXçEò'ê˜Fþ˜*ˆ ¨_®PQ#ù4 ˆõ9‚Ïü²†j¬ºß2JÏš›X“£ pÙ±§sY‘\9¶ìeùßWÌêY`#]½íÕ™¼ciÿšëG{íz§¯g`¥BW¾Î•” ú¦ù2%Ô›”b9X—Ü•kbka¦R¬•“gø¶@‚|,S®—拆á brs€Çoåx+ÔLBlcbÍÊÒ/_HT}‰˜‡ JB ¦eòWó æ¤RM,±5>7p‹Ž¡Aµ¹¼ÌÃNJü⤋?ƒ[·.Þå.0*²béº5KÆzÿ"ÞûC°ó¤ÌÖ‹5þÜÚ^%Ôç‹âáÝ/…U$qaìwËkš ´+ã}™ù‡yB¨_cÔ-g»†OÿvÒ à‰[Y ^±QJÍÆna4ͨIä•o¼r,µÑXC¥äüâŠNÉâb¿±Ôl@’s ;XԴ٨ʼ!H*-dÚÁ'Ä6Ïäý .¸Ð çS¬Qê`š¦%û%õeY¾ÂuZ.ð¨-1~dìÖIJ;‚Ðà²ýòxiøòÝÚj6ÚãY)s»C€R£`Íbç­‘ ¦ä00k( yùˆuœ‚úRÒ€Zâ _q¹†/vTÄ% Ôj.ȿו™y‰u˜Go»Ñ…òà‰gº­MJ…¿ç ‚þ2óçei¾Æ³”ë<{¥æ Ÿi‰ñ¦ð쑱ߡ…ñåœkD—õ½)ÐÂ&ê>WöÅiLæ«Ò¤Í2p»’ÀÈ+u1<ò ¡–Ð’ëDm&óÇ|¢§y‡HKC€S0žSP뾩0uß!Æ'Ÿ¸Ü@ ¥"…%¿•ÿçã·ÞÀ]¡ú½@ùùòìù²©1X|̶A힇€ÒSË = ¢&Í7c¿ñÇ5 y(ÖÂwÕiIøÜ±Yj‹e¦I»&Û"·šºÆE-ì2}ã=Ü{fSE€)ÃÂÔ{3-¬¡H@.È@@$å ¤ ¼]|%áÁ}7Bã”åd¯¡rÏ'5źýÅâæžŸ7ž¨)–¾ òš„FO‹œ@G@=RHjÑ4v‹+§Æÿ¦Û!Â-—M¶$Jûô7ò δ„ä ù6¨BÈ/ !÷p vïÓÊ s1®B+«A¸°¥Aå¿©ÚÌFö*´4O$ aß™|ŽaVŸ+Ï+=Å'c\}ù~_âå Á{£ ½¼lê˜i xIbOç¿Y.OïŒ+Iê3ºT¨+ùÜžêž›¢Ý¿|ê2ö{7 ™mR–óôÀã½û–=oê9-_+óü÷°¶àÔØæuØ/¼9ØÐ¦%‰sÆ¡M¡A8hÎêËØÏˆ¶(óLðû6@!S(>7ˆ(ðþ4QHâÉ5€š%Áí«¢ø«Œ(@#?ÒÆ+Ïø$ÑR†‰06[ÐÈ8äS(N ž#ò‹"¬P"”!¤ˆ >‘,‘ •ÅË=?HЊýjÒÖ’¯dîJOƒäNÆÙÔ yýfÅ»ß´ÆØÄRK3H;Hý:ùn£ÃŒlR `0#醦£ƒe©²x^ykè ¢mÖÐ#BÁæúϑƈCŽÉŸ–¯‰i5i"gtDÙ*¸Lú|Ã9FÙ IDAT|ÏûôãnêPn~â3üÈS½Íe^ ©¥–*YÒ¦i±C i é Ñä"À H7g0 t¸je¢—£g ˜jSÈb4JK¦ ÂA+k˜Qû¬¡†¬aJD0F 9¼çŸÇxIK^¿XÁ¾ˆñe’ØÜà)©ÜÖÒ®D™’×·¯ù2² \¥1šªW¼óþáIJWzÀd‡€õ ’™/Dý0m& vLs$x,®¥“F±Ïb0‰WŒWf®¸0Gˆ´T0„c}B„•<©4¬)’¾<¼AŽÚŒ-PféÔèž×OË¦ŽŒÚÔ1ASÇl¡©³-¼¾£w³"iÄþ RK’€¦Ãì•f 'CÜ7¹pèëIxÛ ›´ËÀ…šn‘ì"Ô $ö•|Išâ›Ú‘O&%h å%¤úca±ÆqÖxˆÈ"ð > à'ZÊÀ`ߺ&^?,ÇÛÔ)y}/¯ŸF¾äË÷âxž$Ò4U:’Œát¤ràäæ"XUÒ€9 Ú¦)šøoŽªeÌß*k˜—ˆkè5ÖDV Ú̪À¶°†ÎüÆ ’nOS‡È$á,ž×—ÄWQáýn7HMH§SKH!ݽ“ÍÌH{9wÒg!‚†ï¿æ4œ ƒvä{îúcQI mk¨M¬aHk9©ÈZXCŸæÅ–ÐNS‡¦¦NÈŽÖÇ×7Åä|°|SèZS™çb-™îjþTéJºéþ"ˆ¶G‡²kÆ‘G2ìôú0Ó~ó,3†Mb\52óŒÖ0äÇÉ2J?¼Ü+欹ÏÔqÚ´©äÓ"ÞçM[Ô÷¦È\ìo—×Ï›:ÚÔÔqÙ~,ÉÐ4ƒtq’B:ƒŽäK|ü9‡{#¾mŠÆPXåÃ7³¯FL¯yŒÌ‘ kX D[¬¡)Œ¥` ꘖRǪ¶½©Ó.¯¿¥¦˜¤F–º]ãdoºû¥&/ÿÄ„êk‡ï›sØi{]õöbþòL©ÅÎKsJ9÷î04´L8m‹54e/À±†f´*`Ûš:íòú² M%}êŸ'x°j¡ôöb{{)¦G?y ]Ö#£rÚx+k¨&œ{iʆN+kha´í¬¡¶ÖÔi—×/«Š‘Mg¿Î…¿ÜYZcv¢| àÎüï_¯â@”ÃGa 5d ÕGäs <Í«¶<ÖÂ2‚54­`’-4uÚâõGkêÔ ~0ñª^z-S@b¦¼èžž*ò‡›˜/o…5ÔLüýžø 'š¬aN Ú ­©Ó.¯/-Mµÿ¾žÞMLA‰™ÂrØé¬ÖzÝËÃ+ù#Ìje £2o(XCãÑ"rÔqQäÔ±Žè†M¶ëf %ZumS´+ØòäEåT~˜ÿý»»™±a‹#Ý2kH™h6±†Ùˆf&íòú‚Þw5Ÿ¹™]PbvQ9àh6Wáb5‡dʶ•5ŒìXeàÖy}ß_Ë%_g7˜ÝDüšÅޤ¿¼…?È,'ÅÚIàØ¼¾ÀÐz¾ð|¥În&1»©~?…òFÓûV²xqÎF:2h™pÅ7ùìÃT²ûÉã7Ðyß*Þ{;ý“jd*©¤’J*©¤’J*©¤’J*©¤’J*©¤’J*©¤’J*©¤’J*©¤’J*©¤’J*©¤’J*©¤’]OöØÝ<¯cQ4Àc`/³‹¹>« `^Έ_pàÆŽÑ^{zÎ`Ú{ÿèûùU°‹ËôÔê³³ÛòÞèùMe<2TÀnâñÝ{­ŸÓÖ‡7mܰ»‚쾊Ǽ`Ÿžý‡±Û}Ï™gÏeMRÀ. ry÷±©MÍD÷~Ïú›Ý-YÜ­ àÊÌqšjÇ0‰ìÓϬy¼·Í *ØòåÙó_fLæoÆ‘ ¿ëÙ¾G*؉rY÷±G6íÞ™çYÙ|þ†]×vIøÒ>ó¬ÁS霢([÷îßßóxe;P¾¶×ü}zØT>ÇX£ßŒµÇpemÊg3{rô®d¬Ql:÷¹5OW°rÅ=Ó³!{Ò®œ§ØáÚÚ 6Þ±®2€ñ(žžšíÖW‹šhw)Q³¸qóùÏÞ¿©2€-H/˜ý»¼VÄv²;ŠÛýÜÐ3uNSƾ2wþk¬2›=AŒ ýî¹¾ÿìdÒN7€³çŸ†ðBöHÑõç Üõ_{¤|ynÏñ^A%•§Îë¿ó¿÷¸bNÏQ™p|¥öQð@yðü5«vK¸tVÏ!5ÃVjÞD°Ü~îú5}»…üó~ÇïŸfé›+µ¶"?^úÜ¿Ü% àÒîã÷î }~ÛßJ¶ÇÒo/Yw÷ow èåÈŽý÷é|(•ê&Ð@;³lÅ_=×óSÒäŠîžeÀ¬J]; îœ9óŸþꉧŒ\ѽàE®Ô3‰Ù»ê†sú>7eà+û÷òLÓ¿©T3 bÍ– ܹzJæ+ºçŸ¨È¢JK;þ­Þ¸ôùµ?Øé9ÀC«9F";ì6Œis¼A©êþ qxÑ»Îï_ûÕ±^;Ÿxí7¸è¿'Íî_Í1±ÛI£ýøÚa‡1´Døã*5¶£yâ¼uk.ëåwðÑS³@Ðô.¹|Ò à—+9Z„2¨ ÄC‡ÿ7WHïØÝ­sz>ŽLíé\SHÖý®¿ï£½ct ßɇ±D¯ w)¿–KVLšòAEqJv›UûílSÀ+Ü= ´x]’«ùô·'¯ XÅá ÔlX ø\ 7Ê\ †7‚¬Ì¢Ž!>úÊ×0æœùåsç/SÝåYÅÛ–ö÷}xlÅèÝ=Á MÛÕ+øÌI¤P¾ÛÔÚohjP \ç¿3ið‹•¼<6Ì“Ðës$0ÄêŸCñX„øGobIgÇ,;ø$cýÞåÝ > œ¶kq8úè’ukÏëõ·ðÁ³"äZ(4ß´:ß¹Üm_/Hj WŸ:å+š_çÓ×OZX‹°ÖºdD ™ZŒ ™c+B&ybFÝ{3À dV0Ö*™(ýWÞ}3ëù;Þ³x1#š¥ý}¸|Îüo 2µ'’ ý¦_þø\úF]Qäm¼ÿ5Šy·÷ä$3Eb'™A}bçJ=ÀFEò&Z$‚m; lË:§P£X•²0£+Y‹Á8žÜ1á@‰bæ~ß¹ûf?ú4F…Ì¥kϾŽEѺîGì7ÅT?¤YòÚ¥ëï]ñï;ÆbzIp œU7ÄJ \㟻ì¿T¾A­øçÖ§›Ad'Õ-*`%òÞopc1FȬz… ŠÁ‘DFÁŠG Q2ã/$áBf-‡Üu?P¸}þé|ºõ·s}F?pi÷ñ{Ç:|“ˆtít/‹þdéúŸ?6Ú‹‹xï‹b¢åêJµÄ9‚Z‹dR(Y3ë¼ß)\+hfLâŸ3òq2À¢Yä­BfœH î3Ï 0¹±ˆb­yˆ@°x>A<²'öÝÌÿ¾¿à4–·žÃ²þÛ7ó¯˜Óó"kô§èN˜{¨öKîZ=ºâ?:+bø_Ï×gâ!¯DõÆ/”K PÍS0}9°¬A3‹Z!ò(0‰аh­†5”Ðï­Éä±³ÎL¢JxÄëˆ#¼Ï£B†õÇ Öw3‹UYÞs:#êg/þaËç.<µß$ŸÿÈ’µÿ2‰³éÿ& OÏ5ÞÅJóçγËãy|'Ë‘¡•òÕâsÎ(œ¡L2üØj^Œòrõe z(§…G£ˆ%笫|9[_BZ<_Ð\=Ôlþ>uÕƒUþná#YÅ\.ëž–/ïÅË– ô}v¬±| ïû¶"‹ƒúT]fŸø®(éÜs—ídûyý¿%ÊW‘Ä@jÝgASéU\ôãICUã€ÄCºÃ<3 ElÕ a:XŒŸ/8+Ïð("ês j.Zs5Þ±à4h=·óû×~øîòîù>11zç;K×­]:vI÷ÞåŠ,t/‰ƒw  ^ò±ò±½ôfSx9þ¸nòõÏM3¹Tð÷qP-å0ë<·àr¨‰ÚŠ8'‡¬î ŸuŸ/Ž)ˆZ^Ü™ünlzyÁ—þw›ìÝê¥ý}¯ëõż¾‘¦žOÚP8&Á‡ƒ’á+ AŠúk”oŽy+LâàŸMšÿH<=[°pEõR@ziy]^z¶)*‚2L¸÷›Q®!ä¾Ã±„­°É¢"L´OµÅžÙ.42dj°6ÂFâž«ójkij‚ù£bbźç"d¢Ø òcªXqÿr‹Ïò5Ü?¹RBoîMù÷æaƒò;ÞsÛlºõF¶8Ç`½ÁO(鯦¹ïë½9Ã7jœšösŽÞ)¼|®¶äñ)”o‚R/73jl× ´ùct°ÛÚ2€éÓ°j±6#‹­{edFÜ«W®±Á1Áõ¼7 ÄKaB&‚E°‘W>Zþu •Äß%ü{JÃ(?ë ßzãÈüà,Þs·òÜF}[¿»dò$0„¼Îw†`‹]‹ÇüRÑac'LM“¡˜ØÏ>¥Âc¡V°™Ëj“l†ÆBæÍ3³ÆÇ_ŸÔ™`^`N±cþ`ÙGê1äg!‹ú.£ë7¸ü"-'Äç$•Bž4ZàßÂk?‹ î}¥¯ßƒø\&yê8Æ‚bƒp ™x…kal£7|F£|¥[kø4{¾-žg“[h†Zëb­Š‹Ý>†[öÄ·‹}ö®êÁ%Ë|Ù(âæ Æ’7•´!ÆC¿S O*å罆¦œÀRöšæ'´@¦–eÛT‘Â{ó“‡ã !oÚh‘ÍÕðÉ_ÏXÏèmsÃÇol…,S_€É²ÉNm†Úˆ¬&óm!C‘Z(b+9­«Abèkü€"¶SÄ6h'ç±ó$ º‘Î@DsƒoHZV 4#APªâ!Z‚XLáøø+-1ß…i £7|F£|ÇÛðÉ=?7“E`k“Ø­EXkÈl† Yd±ùn õÄ4Ö%pÖçâ“F#Þ;ÄõÄ)ÖŠÏÔçøÄ07?ÈV”,Rýù1Ÿ[ž,a,oÊ B~ Åó% s¤‰Øñx¬™ï ?+AÂVeÌ7A.PA¦;&S_ꉞ÷bo Ñ$A)ªŽ°±qe]^ªñü€ ˆEkX@ 6Жõ¼Þ©A¬ÏÉ¡¶#_Eð^dá¦$Ž èJHÿ’V[ ß'hv4CЦھ,Ó¶µáS*S‚ç´Õð±ùcDˆ²xÒCÀL4<S&„Q‚ÍÉ¡ÞGÌla ›(b_òyr¨$z|bXPÄ>Ÿ'¢àiè‚È¿¯9 4]Rÿ¥@‹’Ø!¦IAÒT)ä0>Z—>”…Æâ“¹Ðàò\ 48SpøçNéQf vpCÀ¬Ö¦X›’Y‹3WZ_ªq¥¡2cJXÎK¼ 4e’gUKîÀSÄÍå d9žÀ£CóKY†_”#`¼©dþ¹Â¥ |(»2Ô4úÉÄ;ŒBùâ“À°¼S°¶¨íMAþ´‚ø8Ÿ\þÙÄ+¿VH'™êF;"2» ‘‹ÿ9”ç9”“9!9$Ác@âd9”“Eb½7úc!9æ9Tz¦Ÿiå FA€ÉcTžŸ1>͉š61† 'Plëô¯ÑBCsÃ' `¿æŸ“æåã$†€µ)¶²ÔáSûŽžõÏ ŠØÃq[ó…÷7AóG‚Üfj8¬ûó\  ˆGBfP—KQömkÃgk”ïD5|råg…ñ¥™«j“‹Ù0šu`mŒÍ Y-v^¯6Š\¶ßD›fè÷(P0…EœÓÀ­q€ -±C˜fŠØÁº×›)â°ÿ¯xø=RŠ’­• pIá¶Q¾Í ¡(¨ù[=%å;ž9þí6|,5Wy‘zˆwN˜6Ðl6]`clÍ#@,dQä¼Þš²I¤k<ÇòÄÐsj}¼.'˜„½ƒ&TÈ —¼w ”9DŽ…Çjà¹2 vYf½çw%bÇ䆀áMhR'ëvFÐ9BÈsÖ¦ŽŠl™ë’Á2¶0†’¢ ‹ r(o5‘Câ›,.1ÌrHÕGN*ÑB •CÓ4×ñ4|rî¿•ò%˜ã¿c>™7ÄÒó ÓO±/¶¿ [·d6ÅXër“a¬i¦ˆÕ `H—KËh™Ôõ¼–”oNIpŸApCIyÇQï!Elµ(‹×d䀣áÓÞÿ‰hø¸Úß)¿, g {ÙÎh'xMºÈÒ!lgL–u`§Õ°YD¶Í±OþZ(âò ŠXÊ2° ˆ=*„qM¼-qK/@›zê¡Ü-Ý­S¾e¶_Öÿ­tq3Õ›ÏùSKÐðÑ–†OÎ&ñ3H’yذšÜÀ"4ٌݫF– c;‡È<1T’CŽõ³VÈrrÈ YdJ‚(¯çó90ïÉ¡‚;ÈÉ! â~A•B ïóÈ\ Y@;ã¥àcP¾å† MAiê†  $iÊ ´e®ÀÖ>QKÖþB’u¢6c/kÎfMvèàóØl&v¸ƒÌú„pZÍ%„99d Y$„Q«Y è‘@Ä£C`FÊQ²±™rCFGaK$Ê@c·ÔðIù–±=OwdÃGˆ2-¾$Ë‚øŸ€M'ÛDÐC»È’ÍØ½±Iƒ¬+Áfà 4Ãf)YHÇÖ+º…"Î)áVŠ8dû ŠXJ(Ï)b¡¸Å¼¤ˆÇè2F70ÌüKþ=7„‘”¯ M ŸXF¡t5hø0FÃG å—}þÑ(_ ¶µ†Ž,; ì4º³ÉF8»aY6›v9È:°9EÜDsþ ýVЏ‰B¶4S®ã˜?ØŠ¦)¦›Œ-Ìñ§i–.M%]õ¦àùíÂÇ4Âè ŸÑ(ß å'`§±w6Ä´É­< X½ûd{×0uÅvª#‡rŠØzBÈ3%Ž"Žq^H9“x‚n1/îU`$]Ü|«KKçÝ9þáN¹6ÈöÃ’14„²í*¿•òuʯÉŸaF–ÛæL¾xÈê`¦5ÈR0Q†MÕ3zJVsfmcm{þ`¦ áüÁ6o11p”$0¼Ã§­9þA[»í;|".,‘uÞŸúRP­P˦yÏ7tesˆìf¦ÙëY¼B€ÏŽ8‚th#væFWääд kW6‘CYIäPæg—äA!'‡(Ã0„9¶$†yRCc0¹âBî?/ÑLÀ– šrãWô=\þߌÒ,>å÷ØL=å›Ç|ÿkE Ñ™%;‹ C_áÜd{t¸}àC0øä-tíe0 ÁØÔCV05?ù³¦ÊóùƒyWn;æfâç 2Î[ÌuD¨#¸mRxÞð™ämiQ§ñÞáÓJùf ÅY ¶¬0-˘S¿v;?aËÁ'ÑPep` {GuÌÐtl‡%K‡0qY–øø_#;Y”ºFO°ÂH1CÈ3zl0»8_KHó;ƒò›Kû‘4¸±Ô‡ «~Ör9»¸¥,™Ÿåë§skÓDÍÝð¡˜ìYR¾¶¥ácaÓWøx ”x"¿LÖ_wѱϨ±-o'Ë)â\1áÒ2…ÂÛ˜?¸ŠØ1#¦…ë¸çø½¨Ó–—tS¢ ácF¥|3ïù1ØM nþ— °$Þ_ê×úûýý÷Ó1{3û'ZÌÌl‚‰kNáÖ`Ú¸Å|[çfj¦ùƒÁ-æ-¦;î9þc-êÄ6,éæÐÂfcÍñ7¾á3Äpãk\ô4;PâùåGÉ0ðäïîfÆð&°‚)fçåàÄÝbn›æ DåmäyÞbÞÚ Ïÿ‰jøŒ5Ç_að«ô>Î$Ȥ,­vÀÑl~ÑÉ<Ã39EÜ:0§ˆ[æ6BMñvÌ4:rBȶÎñŸ¨†Ïh”¯%/$úÅ•\8)Êßá0J¢ØôÿzfÂ~#æNÀ-æã˜?ØÂ´¿¨Ó¶ÜáãØÅâÖñ¦9þB”EcQ¾1µû¾Jo?;Yb¦€|÷¨"¿ºƒ4ÃxŠØ†ëNô-æÍe ¨s·mQ§m¹Ãg4Ê×?ø5zÑxªœˆçn½óNjs꜆ *€< Lä-æ#x€‰mø´Îñ7ðè׸ð—L1‰§Ú -\Hüôî3c¯™œ9êüAß$jš?8Î[Ì[©`¹l[Û Ÿ%TxòZ.¼“)*ñT=±£_Çfà‡¿¼}¤Æi99PĶeýÁ-SÄaˆŽvkØÄÜá$Ï]ÍE?eŠK<ÕOðð3yøÞc«y±…›n1ï8¦©Ì“pýÁÑn1¥ Üî†hmü:~ßE–©/1»ˆ¼ìd~üꑟó*;DO[Ô4ßb>¶·á£ƒ/£ó_zéMÙ…$f“Cå>à¾GnåäÌrÄx¶¨ )â‘ól» Ÿ´Aç·®§w» Äì¢r艬V?´’?µÊKØÖ-jüj%ÍU€»3h¼ !¹îZþñva‰ÙÅåå§òC€WóvûmóüÁæ†HfÇÑð±ðýopñ#ì³›È+NæzÑ/^Ày"ÌÜÚüÁQx€`Q§Ñ—tSÌÿ|“K~În$ñît1âæ!\öä-tmV–)cßb>’ »á£È­ßä’Ÿ°J¼;^”ß…ô3¿¼“}ìøE%›¶¨iþÄè Ð{¯å³ßf7–xw¾¸ÃòÐûðj¶¼Kóû ¤µ(JÐð±È¯¿Ég/e³'\äa'óè‘§òq£šiFå IDAT\§å­cað ŸöÊŒ}kQþ+÷¯¢i#¨·ñÑ-âý]ÕÈTRI%•TRI%•TRI%•TRI%•TRI%•TRI%•TRI%•TRI%•TRI%•TRI%•TRI%•TRI%•TRI%•TRI%•TRI%•TRI%•TRI%•TRI%•TRI%•TRI%•TRI%•T²ÃEAzݺéRÆž)•â÷ éåŒø¸C»¢ß§-ê}xîÆìœ5kRÙEöº«¤€JF‘+è©uí?·#É6·½ÄL©)Ï>;¼˜û*@¨ ’©+7pF|owcúôÔì°"3Ín|¾«±Œ U#^@%;Q®cQ40ç±½L-îÜiÔa’½gmØ´øþû‡+TPÉ­á1/˜Ó3sHâ™vJêN©?·qúú^nL+UPÉvêç«ûœ¼Wš ÎɬÙå6ôÌíÒhýo6Üú|/Íû2VR@%£ÈöLOêv_G»ÛµEFÓŽálÝ;Ÿ¿k}Õa¨ àR6mάýY{Úµk– Ö:füîož[½±²„ ö:þŒø…Ý4²û¨ZSH †õ›;:žúÀonmTƒQÀîå@.ß÷Øýcµ¢ÔªÙ6±ˆ’¥Ï˜ç£ßžË𤑠vùòÌãæÖLöb ӫјÉDÓµßü¶ÿö§z+B±€©$ŸÙçð™³Ìô—i&s«Ñ˜¤ÌJ©K-}lÉïïy¦ &U.=ôÐiý3É0´éP2ˆímôйkÖW£QÀ„Êu,ŠžÝûÑ—š˜Cª:~È5jSß«ó¡ŠP¬ ­q¹¬û؃À¾ÒˆÎ¨†cW¦ŠÊÃO¯›ùH5C±€Q劙=û˜—)ûV£±Û§õȦ÷½{ý=O°‡OHÚcà²}Ü‹¬6Ϩyiµ ÆŽÂsqmí»Ÿ¹ý÷ì¶uü‘ÏìÓù*£zDUÇW²E§0æñÄF}ËúoßPÀ.*½`^0·çå™Õ…U_I›ü¦¹?íß°v U0•Óúîc6d'"ìW™n%;Äa”Í¢rçœ5,†¬€(Wî}b÷P4t²­L³’„ÏÅi¶êÝëïy¼€,_8èÄ®®C'J¤Ç¨J\Y_%S6Ìͽií³l§\Ç¢hÝ܇zTÍ)H5¯¾’]M4S5k‰†Vÿìý›*عbnÏ+Rô5¢Zõã+ÙÍð€º «Ì@tÇT¹ÃqJ¥Ñ—u{p¦É)¢r`e-•ì†2K1'ÙÙi]Ÿç®©°*ÒT.dÅìùólÄ¡²Oe;•ì¢aá¦tÝÆ•S±8© Šiï^î+è©é\{šZ^Sñ•LÙ¨…XTú£¿zþ®çÇûùE,Š®çúl·€‡VsŒ…¢™áùóè×±¹-@˜Ó3Ëb_/†“tŠ•2•ìqò`–ñý ž_û«ñ~ð/ùÐ %>ÖÀŠŠ ·\Ã%kwKøåJŽ8Þ"±k0b1†³Œ5Ïdôy&mÝ­uY÷±G’¾I•y•=V²ƒåicÌ¿ŸûÜšµŒ³Žï¥7~”M=Š9V05E­«þ_Ltë×¹è®Ý»™y‰á8Q ‚Q%ÊA@c£†H•õXVy´û[_™}Ì1™˜³=¨²×J¶GŒÊÆTøÏgû‡~ÖËøw@:›aÐÓ,:Û€ÉÛêü’eèíßà3wï–ð‹›8*6k€€É3‚D0 ¸×Ex2ÍøÙQ§ód;¿ÛËñ º7¾Ú oB™]™t%[ö Mó?Ãý{;7ý88%ú‹yQ«ƒ«[ϰÉùA²ò¹¹ã.ºg·€GVóªLéÉÝ(Q¢üc¤Áß(Ü› ?w*íœÇW÷9yæ°½IÑ?¥£²øJT¹SÅ|ëüþŸ;ȼÍì5fž¢ê\ÆpðÒùñ€8¿Íˆ~þM>uïn ߯dô´8¸uNnÅýºò )#ÿVŒ ˆÒHYÕ™qÓKÏd°óZ>÷èªÞf“ªµö˜(ÿ˜Éäç®_Ó7Þ¾“ÞΔÍgrŠEk#\¼ƒkæÜfçß:¿{¿¬¹–‹îÛ-à‰[yå°2_c[\¼Ó‡ ;~,š@À=Fùߌá'¯8‰ÛÛm9®˜=ÿhŒ¼SáðÊSv‡§_¬ùV÷ÀK¼xœm¶^zÍ#4NPxèÞÎqu”îœÔ;x³ó»÷—©þ(þ{ܱ¸ïj>uÿn ßÂ+2嘜°¡CÇòhO)äÇ4‡ŒH[À¢åoc ¿,?h—P¼ŽEѺîG_ƒÊ;­n3Þeü]†@þ-‹ßigþÙ|ð‹¼ÑÀAÎIwp¬@&#"8Á4•­u¿kƒLÀ3¶†ô]É…¿Ø-àÁUæIéK(2‚â˜u&ÈË…p@Ë÷iY&D9äå‚ÄZîI…[p Oµs WØ3]õÍÀbº*W›2oQnTI¿ºdÝÝ¿ïçßÂG4Ø?9Ú;³Ô†ÎšØQœ?£é˜iªñG«û >Ÿg¾l¸ëë\üàn ÞÎ˳”£Ä;¸5.uÏA`ı|®@P.¨é8HK–ÐÊ/„ ‘A$`¬Zn¨oæ?NøcÚZþé²}=@lú.£æuŠV{ýM®Ük­]qþów»o~6ïÙ[éx#p¦"F‚HÝêü‚z§mvþ<¥ou~W˜–º¿ùXkvP–zÏî 7q˜ÖxUᜃ7€«å£&°1®\ÈA\»°•̳„V~¡©<ð%ŠQF¡nàûCÓùÉÂ…´u·Ö—æô£ç#ôTþ9±¢ÊÓ W<3°æG½ãÜìΩm`ï?ôMÀ i©ÙË(_:3¹W:y¼ÙùKNÀeqlìL@-pÏ5\üÐn Ü¡j9BLòžGÿ‚9M†pÎ\€€–à’ƒÈH~¡µ\P_B ` è0êtniëÎ-YÞ½àU8_”U.<îáÛ¤è5Q§|ãܧÖÔÇûá·ð· æ€æ¨Þ\³·:ÿHrO¬ŒhÛ•ÎßÌêÿ6Ó–cŒÒ 3÷^¹ïë\øðn ¯æ#¼Òºhnücó´`? (-ÍlM‚ KHÆ’#ÈA@0j]i€@Qj(£Ü«†kŽ9•¶º÷%/éÜwܳ ò.`ïÿ×ÞùÇJV–wüó¾çÌÜ{aï+((ÈRP¬¿ÀVÑV—Ôšö&]l5˜6©Å”ÐÓ´©X±¶Z“ÖVªˆ 6]LÓ¤‰V›l¡îÚjÝUYØÖ‚ÀröÞ;sÞ÷éïûžóž3gæÎÜ;÷î½³çI&ËfæÎ½3ßïyžçý<çmÞÔ?ÛDòºÇ¿õ“qŸ¼›ë/IHßò2•׿Úö‹_úê{×Ü£¯¾/2„ªø«Fá¾Ò¶¯±wú«â'Zl ³ÿ3|äþ©4€ïå¬ǥJ“”L ¬D&@¡UPƒî8EÊ/øÌ ê/HÜ#ˆL@'úØB©g ÅJsÛË_Ǫ6¨¼yÛ+ÏZn«ë€Ýp²^ª\Ý­…¿¼ö©}cÃ/×ð¾çöÈÞÉU *:»•¯­â×yÚÝ'~S”ýͽªøËËvñR^íÕ¾@éùý™@9P>LJ˜J8üM.@¸ÄNb ˆ42ŠM`Dj0ˆ¾dQ!<6” È%7ÀPêh‰Ñ,ÑãKË3üã•W²ªýèþþ9¯zq&¼Ø5ÅŠ@D>öî§þû_ǽ Ænn˜Óð›ÊæŒÊkx"‹­Š?>ƒÓ×ÜS¶*þPTÅo¡rvÎõÀ§nÍ¿*þ`šì7ó‘CSi‡¾Áù¢¹X In­ŸTÖô©¼®p#Pƒe€H†LÜÏÈM ô (—ÕŒÐVyórÇÃÍ—ïâ+«’v\ñ&Áþpéü“>ÑÞ¾ýÖßyðαHÍýŽþŠÀï‚:K•ÎÊ…ø —Å­Š¿8û–Å­Š?Å*¸þ£è_þ‹ŒÇøám|èðTÀwñBQìT¡é„êF‚ áAøHèðÔ`iY°Ü3ÐJ‘TLÀ™Cl•Œ€ðol!ƒP|_[>ùÊ7ò­Uõ¸¬}öŽö5 oM $)è‚ܪºúã×Û÷ÓqŸÿV®µB_'ÈKb¡ëhÉMEÝûB¨±øÃñ²øË5»ŠšáxYüáuVËõûŸktÕÓDQzß-tœÎU€¯s^¢¹ÈhWÿG¾$6äF Ž‚çæP}5— á쮬ïTŸ=(Ã(‰ÞA¥ŸøyƒÿH,óÊ]¬êÃþôö×îè¦Ýö fO°î¿’*>øÎ'¾=6Á¶›÷^¨Iß \"AÌ”êùò1‹2*¿‹¿¼Ž_ˆ¿ÚÜS¥ÇWůKg÷µpý¡ ÝHlUün¹0µ=Ìý_䯇¦ÒÞùIÆ…J£¯ÿsðAÉT™‹®dR)‚ „³ŸaD´!Å¿IÅJæ7£û]`O[sÓj'ÿî¬W\L¦?`¿®Öÿsý®FýɵOîûÚ¸O|ï>£GûZ…z+HÛ \YrÁvÕw̉°*~÷XlUü¶¶¹W^÷/‹¿¨gm\¿ŽÊ ëI¿²ø‹åÂ4˜Ç·ÒùÑTÀïæËŠ ´)2“DK~> èZ5¸:tXŠÕ€`‚xÅ0BIB|,}ñ³“Š äeE¥\8*†Oéy¾°Z éoϼ|—¶ò!àÕøèŽܘ<©>;îå¬ßÅ»ZO1óvz ÎpBW¹8cñ;‹Å‹<¿_¶+‹ŸJ¯¼Ž?¸¹WÝPàú Ó eB5þ¬IòǧÞ<ŒiÑ:ôi:Oç2à^Α%Î7 IÉb0˜@Dþ­58&:\¡û ƒ¨I)*êiPɪ¢¯ËÂÒ£{¯÷‹ðÑŸ¿Š¯®®ðÆô¹;®QБ€¤E¥Ô_/ó‘VqaË«¹þÍ‚}?¨ª\³çb‹ÅÎàUñ“#¸Uñ«¼6/‹Ÿ¾ãá¬\åú‡ õŒÌõס¼Aü®Š?±ø] 6›Áá[¹ñ‘©4€ÃÿÅÙ’ñ zm‹2ÀÄ4`ÙN58":œ/=–˜Šcy©B%sˆÎþ‘ Ô– V¼·¯[ÍŸ½æYÕ•c>sæëæ—íñ÷)ÔïÛ¼€öØ„?¾îño ¡¼…ë_¦°¤à å:¼Ô­7‘ð¼øãŽ~!þªâW&Fxc¢¯þ¸2Œ8Ô3×?ÊPOUü!ˆÅ¯Ð¹IÇZÞLçÑ©4€C÷ð<É8¯$ú`‘ x6 )™Àf¢¡Ãª’%x ¯gœ‰%± ¨2ÊM ;UË…ºÕ×9ûb¶ÌG_ÿËlÈç×xïó[è÷kämþ=”p±øËgÎ\ü%“(ÄB!~òr¡,r•§ôUñ>Ô3×?ÎPO¿ûZ»ûAü…ðËâmz˜}žÙ(Mnèå´­ARQÚ­¯$Öâ” ‰'-° 5‹hO`„Qˆ¶þ9”û&ˆöß%îÀú±?÷XÜÿ¯>ë2ñÏܕݵS™ ´ŠrÃßW QÆ] ,Ð,Jð:áç"þy‰ä¿Ÿ(„Çáç^#š?P¸÷ˆ„·“ÿ\­„kÒ6¿}Ïhà˜‚¿:.|j×.&²ÝnÞ³MÐï®5_ÔÚ!eV¶*þ¸Ã «ôØHüù<?hS6“ ò8¥/7ì0ÔýwéýÕqýÔ2üÖ?¾¨‡Ú¡mlôúጯ¢l!ˆ?üžîªÀí Ý-hC àTƒ,*¬ Y=.¶´q¬{Œq–Š ä"U6r!ð°ÊIFû¿¼òߺ’ HôzÁ ¬`n’«.®$þã÷?×›A›‘¸ssˆL€BÓQ*¦ ÃïqF£â'”þ32˜øÀœ¢óMg?BñÁ×ÜÁ?¨Îh@R‡ŽþOüبó¢Îwiy.>V®Ãã3¬{¬®<6fõûů ¨(c¯£}&Ð?ÔS4ËÍ@j¸~pýT¸þ•†z0Ô~ÏvO*â…n-ºj»/À^Μ[æìÌ’èÄ¥ö™AëxYÐåöǦT˜ÐðŒ~}@‘ï”zq¹P¿¤Øß_ˆÊÿôÚ7ðÖä¿$ðJï¿©ó_öšôºHé+FaT¥^Wyç]UÊ…I õŒÎõOb¨§Jÿ%%ôWé3¬"1½ÎcS[H‚ML»:M 3îl¯ b÷ØP(…䙀vY‚Òî,ÎÄyàÿÚqF ¼ ¬·;åSò8#ð\¢ NçUžiˆñÏ i¾ˆ“¼òW™+¥éÑû#¼ž?kkw_Â)î=Ù”åé-ΙÃ>¹€IRH d>íO4žg:*¬öe€e€ÒEÊ/þ4g]«\l$ð ú`Ê‹PE†Rë<ÓzÊ‹40Q Q¹òn4¢lYuá‘ 8AÇ"÷}¤,Ü’xÃ{ &@Þ‹¨ÑSâò /QdÈu *iuäôBH»cñ‡îºTįj0ÞA\ÿJC=zÄ¡ž8; ÂõÛ ×¯£ß³~¨GFê©rýAèÚ$þ½”W¬–/þØ„™é51H+Åö2Ð LŸƒèRëŽûx1Ö%°¡¦& ÚŸý½"(eD¢¯f!#ˆL¥LýY7¿ï{ŠHuÚ÷ªglÊ?—j]OÔ_¨œ±cñª¨G™âŸWynm€!WÒQá3Àüo‹ßÀG*)½;V=×O$ðþ¡ õ¸óBÿÒ_ׯ*\ÿJC=É€¡U“5PÃõƒ6تø•o(JEüŠÌR;µð¤AZÛJ¡—¹*5É“zHÀø¿dQyàM­Tt¶÷âM»ü_ÿm!:~ÑJ€s,Üħ÷”ϺÁlT”³+)Vò3}8V˜@n±ˆ*Ÿé}ÃÒ½g¤øÅÙ_TÔýS 7ߌjuê+â:øNüűªø•©¢½*oöÂj†z‹ë—×ß/ðòú>†z¨áúÕ®O÷ÅâWØh ÿ ƈësÛ–¦78=C4F÷VË™€v&€ÖHf °®p¢7îìš0Ú‹<¬¸lÀeAèn P"@ùeÁø¹V—WâÚ?6‡8KˆÍFQ2×# êHôܸȯ3‘¨û~¶Hùì/Ôdþg•EbÐo ¢‡­ôuékºýR"ñ*âÈõ×Âð¡0Ô£ú¸~Fäúu$亡 õPÃõ3ןäeŠ­¬´üë3ã¾õf°Èìô€Í°Ë)ЃV‚ôŒ3k ÕÐË$u&P½/Bs0¬óG&€öŸ~^.D+•Á ücˆ $â¤by–Pí/ôõ¢åÈ<ã—hí¿ÈÇË<€7 [næâUåuþ_ùÌAÕ?j>šÁ=€ Þê*€Ššlª„öâú4SÃõ[c õPÃõÛÒzÜÜ“Z®Ÿ›u0`¨§¼üW¾ÂO•ëgׯ*\‚D©~Yü cħý±ø5©I§Ùë!Ûg03–S ƒVâRþÌ­ÐJ\f$EퟯhW×G&€ÒeF dpµ³ŽJˆ TDÁä >…køi]^!@Š ä͵j“®b®ˆŒ&@E±Rµc[2œ9ªC"Ñb®Ò ­‹!]šAÝ~Óö–ÅYêaÌ¡j¹~UËõ3€ë/geF`-C=ø~*\êQ¥>Cþ‚Ékþ ~Ejt^Lq u3¥ íéâL@i=q_úV=ߨÔ`0‡2œ“§÷qA¢Ž~L Š­tæO 5ˆVƒ3€Á ¿:®_Ùº¡'Îþ¡õÈÈ\¿Àõ«17ë`@ÓÏÖtõU^×¹~*\ê!B{ãU€ÔßůHs£°`Û`ŸåøôÀŽ—`{`i]h§°l]Ç]ã…ïºéÒ™À¦£ATepEtXü²e?:¼ñÔ  ½Ÿ]‰ë§ŸëÏ ¡n¨§èØê™ׯ+ýƒêP†z¨¡ùÔ@®_lÝPØ×¯TÅß#1-?WôÌØmœ2½pn†I1zYšq&0Ó‚åH‚$¦8û£!$3E†ÅË‚ÒQ 2¬_RôgóôA@Ö3*:‹S…Фò\ T8_> ÷}ù1*:œ/ÑU@!ŸH0À”W£jv¢*åB‘ØáÐ/þ:®å¡U;ÔC)¥Ÿ ×_Mé©1„º¡ÖÀõ'¹ðë‡z ¶?ybzÞlþwì÷uÅÎ"öÏL¯ÜŸ!³],30» ‹-$Í  !­ÔŸý÷3/ð­J R4 jпV.ΪpË»¼á¹VjPm`×?©¡žIpý“ê‡ëu¨'¿"ÉßÊÍ 3ø@¿{&ÏŸ^`g9ÔÆ¤‹Hkæœ t‘™vad&nYP'5£Ã%j°Ò#Ø‚Ôà(è05èp?ù·5¨†€ø†Y×?©¡žUpýªv³Žµõ ãúû a¼¡ž´&Õoí’™¸įiÀ>ÅÂôf,"Û—° sÀ"$md®‹EO@té¶@g¾1藡é…ÌÕßE` RƒÑÙ_Vד5Ô oŽ d5ÇêaÀPëÀõK%CXíPÏ8\Aÿ©Ú¡PµC=ÕT2Ó­tÿîÒÊW lèÙà àÎcÈ%ó˜íKL€6Ì.!K³ÎH¡ÝƒåÐHs`HJÔàèèpÞ½5©AŠŒ@ÆA‡cj0oHÚÁMÀ‚Ò›ÜPÏ$¸þI õŒÃõ¯u¨'Nõ%ÊŠ¥?'üÙHüì³lj?Ž\–y˜?†›÷&0³K°Øv=R˜É`9Q¨A¥‘˜ìC‡# šòë˜Ü‚Ô`@‡k¨Áþé@AÔнÄd†z&ÁõOj¨g®RC=ŠÌ¨Júïþ­üx,þ„Y#({§Oo °{7öÀ,7’9ä™®3¹eWèIñ&°j°¸/ÑáÊ3‚R !…l9j°@‡óLAìЀ ëê«ê™×?™¡žq¸þI õÄ\,~Mj±Xü 3”ÍP¶‡¶/äÈô€RˆìÁì{òœcÎ8Ûæ`á8Â)Þ€,…Ä"ëL Ž‚SƒçƼ¸E¨AÔð @¯r¨g\ÿ¸C=AÈɸþI õÔqýR¡übñ§mNEY‰ÿL»Ÿýjz£›ìÆ^~ûÄ<æÙÇ0Ù<Ö,b·Ïa{Ç1YŠ™ëbm†5m¬Ñ˜v‚±)Ö¬lª1Vc¬Å¶w<јDcDc‹W«0‰ÅŠvǴƈÅèp_¹û¢0Ú?N[ŒÂ½–¬(,zŒˆ[Sî¹î ®°bq4…U‚EaÿzáµÜq#ÊSâž‹Â(]b£ƒ*ºëJ•_/¬hŒFÅs4ã߃%¼§$`i¨ÇÔõ”wêq\¿ø}ƒ¬o§ß§íÛ©Ç—¦~¨§¼S/#L=×ß¿Sÿ]ŒÔõôïÔ£üñº¡Uºž¿ãúÁ˜:®_‘švEüж©³æaÌe\6Å«EwÝ^~;ì{ðÌžŽÌ,Âö9X=YÊ\“ðDQƒ¨¡×ï‚£ëL ÊÐ ˜ÆHC=cpý“ê™×?©¡žQ¸þXüš®¶Y6Ê¢±¾ùwØé6€`€‘=… p:´æíxfÛ°i©Á€ÇŒÀ:_ptÔà°YFê“ëŸÔPÏš¹þI õŒÃõỊµ,þ”%Øn$~ÿ»L¿ä_æ«1"Ⱦ}ÞN…mò̲¿¿u©A)QƒUt¸0ƒŽ©AÊ&01jP†Ì ê‡ëŸÔPÏ$¸þI õŒÃõ·Á.Ñ3³ùz!þ–Íin ÛÛæQs 7e·o°ð7…ø/³ûuàvZÛÏuÍÁmÇ`¡´ã‚é¡×û‚£ƒ©Á!%€¯ÇÍê¹þÉmÖ±V®RC=ãpýñPOUüš®Y{*d {'ŒM)›$üÙ¬›×£{iÉ1fŽÍC ލÁn–g[ŽÌÑáÈl6â‚£Ê]°ÅrßɱYÇ ¡žq¸þº¡ž þY²Efo¡³Ì :Ëo è3·+nÏÉÕ‘}Ìe)st˜Ž©ÁU^ptsPƒ6^ÑœÔGõÐk 'Éfƒ†zÆáú õåu|cëÕPtýƒ¼\8x™ö\Ôµœ§5é*¨A‰©Á±My²ÂeÁÇêÙL›u(LéCÐ>ø9:KÍ··1€Ég³ ð7ø7N³s¼Xàyk¡Uq%ߢÈúlSÎÛƒ¯<Ô³96ëPpfößBçhóíl `Ãã¢7ñ4pO¸ÿà^Îé.ñR4§OŠœôGíP0LùÕõœøÍ:ä¨EçVn|¤ùö5°éâ‚+8B$éCw±Ó(^ªá” QƒR¢}Æ1ÖGFêÚ¡NÜfÇòÝ ™¹¯CÇ6߰ƶNÿÀ­·ßçoü23ú9ül–ñbmi­5(158¶+”'r³¨ý 3ß¹¥©ã˜ªþãöúìå´¬ËÚpÑPƒ¨A=ôzÊ:OÚ¸Í:€ûSæîmêøÆN®þÁ< ü»¿ñûyÊx­$œ³®Ô`6¬°Vû‰ÀõÚ¬äѽ»?ËÇÜ| hÂÇ%Wòð%€Nýö«¸Í•V8mÂÔà`=6ëPGAßµ“ö¦Žo  ‰¢ÓÁv:Årãþ=´“³yµUüœRÌ®‰T+í ÄZ7ëXô7—™¹÷v:ÝæÓl  ‰5ÆeWÓ¾áo¼‡íË=Þ ¯÷ZƒÈJÓ€˜17ëÈ€ÿYFÝq;þtói5ÐÄ:Çůaøã;wpn;á—¬bçŠÔà $à(C=uŸ…¯ÞÆ_<Ü|4q‚ãå»x¸%œÆ|ƒË´åÍVñ¼*5hÔ°a $©êù‰`¿òy>ö¿LÙ1hbÚB^ò:¾|`ï^Zíã\™XÞ¤4óZ†.?´êk§rô®›¶àuîšh¢‰±?íAÿo7vój¢‰&šh¢‰&šh¢‰&šh¢‰&šh¢‰&šh¢‰&šh¢‰&šh¢‰&šh¢‰&šh¢‰&šh¢‰&šh¢‰&šh¢‰&šh¢‰&šØLñÿ¥}L!}=IEND®B`‚gaphor-0.17.2/gaphor/ui/pixmaps/gaphor-48x48.png000066400000000000000000000161461220151210700212210ustar00rootroot00000000000000‰PNG  IHDR00’%ÄbKGDùC» pHYsHHFÉk> vpAg00ÎîŒWñIDATxÚí›wt”ÕÖÿ?{fž„B( ¡¨T¥ "EŠ J@‘bô*½Aà""‚tA ¤š™tJ€„^!!¤?gfžóû#ñ÷Þ彬{ñ¾^½ïò»ÖÌ3gÖÌÙß½¿{Ÿ}άgàOü‰?ñ'~7ÈïMà^¡õ?qè¿Ì£?<Ý9ë}‹¾ þƒÊ?@C ÛŸ5@¼­oUQlÖéW¥-œ[ êD᪠MÛS\ÑkÑú&™2$Y3<ô´w´†éL›N]• z.9ž!Ó2tyÝ}?OàÒ]´x”ÕÈ‘‰øÌõ:PT =h}ÆÈãc~o/ÿŽß›Àb{¥;M¼@‚¥ãb>_`Q-+u‡Ñ¶vÆNÏaÆxv¸ç9î7fè2ÖV÷hïËî ù˜†ölüÅ›“4¡Àãâ’e〬!J/û}Z͵UÆí馿7·»R¼ªÃ’+çµ BÔ+º'&úí¦Ùûý~þÿn°$¤²_Ë„š:BŸEënòÜg¤±m =Œï=K­ƒe*Ó‹óúªßuÜJsïxt1Á¶~hLÜ\¤¤'x°áÀN"åÉ×ýñÕÙ§ZK›óç[ŸÚæÛŠds›‰ú3jë,lÀ«rœ–ˆuoqÅ ‡O¹óÑQÛÿA¾ð«Ô¹E<¢¯è¥Ä¡Ùe{Tþ™ø¶¥3TiPPWá³…Ãz$E•¿Ã’zÜñ{_Ïi{E,Üh~@sã.üuéÃÁtÃ#~„è!ÜÑ—¨^ð.äyYK&½¨šßÛJ“m ÏÐV÷ÒçAºR[‚è0õhÖ-ôÚEù•/øþ ðÅ+3šOâÈ!xH.òˆKèOÁ Õçíg}Cd õ¦ª[y€ÆHÀ{8p3°pû°‘ø+ùêÒçêØ4£ñP»ç8™ÂÊÉÐᬖV#ôšÚêÝYf»À~÷"úKC®ש†?bŸO}¯‘è!C²³ccÿ€,ޝX¶Ù<ˆªûB™ÅýàÉu›†ØlÑÓ¤¢|M‚OÉúiS£9Hk^ ~˜"Êc”9„`þÿÌÖ|ó¿çâ?ZDfâlj †>HÔÍË[zü¥ÓA¯ÕšíYrg経‹3ì)’I<¡ó'áȰœìóI§Ð¿–È¿-Àç½+®h^ŽÊ¶)r›µO¿Fc,ê±ù ¤³Î,s7õ t©ýµÜ'í1êtÁ—2Øü\.lŠ Ñú½Òi=ÿ.¯{B—Zë…-ãqáÀ*š¦¯êý¸.ïàyƒKgkÛFé²ö˹]u[ilÍÏl†X7Àö1 ä2|GöФ€{ä_vté°ŠŸµX®®Ò‹©8$ƒ¸qÓ”4ùŠBNz/&U÷¦lÝÇ8EM} á{äáÀS©&‚iRbU?Rê~ñoè_ -³;Ð/¼Ñù ¨Kª4OïMKyÓuôX*Ð/o<ÀlöNVÄ[°dA€Ñ†Êr'þ/ °¨b倿›°ÙîÓ÷ËD,°†«^½¹ŸvÔº/”fØ0›‘ ¼ªµGc¢mSJ^\jëÖ½Úýðs&;–Âbwö ÈÄq<–4r+u°n­+Q­Àß–Î2qB¥.ö+Ù-acî­£/|ëê§/݃i]£¿oì€NÅêà.pÉùC‹ªm+F3ØÇægÞ%ƒ ¬Ec"ö(4úÃtzétÿÁÝo*ˆ/‚[‚ðÆ7Öµ’ÊQ]s×ñ38¿°îÎnU=ÐÌo´·O²:fæýýu÷ƒØ ²Mel ëëí{cëÕ½¾ 3çÆ,ª0 ð‹Âè«ýÊô>~DÞr ÑË`“jXu Jæt¼hD·(½žÄþ{Gòƒ®KxãSò޼†F#zá¥ã®/±Íá+=âøÀÌGÔwU¹9×Ê›ujÖœgk º^ÖJÖUk³þÞÈÝ+à‰èCô†CW' Gך µ Å]´Çq©âY8Zqg`ó ­j{Ñ+_Òl€„9Âõ•.Uð¢‚.¨… m£Ä«}éô×J¯ÔÊ(‹<XØBml\½akÁx½ü _Þw%?ÿø—^ÌíÖþÛroÚ«îפôA8¨§JZRw–! t²ëöês0«Ìߺ{¬’TéF+ÐiÅS¸½VhÃÚõ3Ôº¬ÃØÝèÚdï–øá§öã*í;&5˜ÙzÁñŽË;܇}NŽÙÇóÒ“«±pãéZrJŒè¾¥Ö þYBüÆp—š¯T’áÒ*x‘™ùº´`еå@-õ¬åã]é`èáâœ~õ5zK#W;n·Ï«K­=’ rÕº sÑRáls@Æê54iÊLüdžï*€$Ñ@nIK`3Gô ZêTêBqy‹kXå6[ë­Ðs×ÍHË–]ýcRœ#·c+=‡×î|¹ÁÐ6añ—¼¦T¨ä {<™-¶|Æ>F6¬ùxÑß–è¡ÃKÍ[¿qÀU‰“r¡Dr›ƒ@ 0ûu¹C'Íþ¡žÅºÈ^´ËmÜþ°ó§žó´"ÇžÙ¾‡mÙà™dÉÙr#ïaöÉÅÅÔéhlã èïK­>ôâ|7†égb¾úØÁ~I¤:tªmL‡šú#=Êî³Þ§ÊÑá¼ ~¬ùz;øöÒKÑ}‹WíAt;{qÙ(ˆ[ñã©fþ­cÊW4fø—u'ôìD¨­;ë»MÇBŒjnlX_ €ÖûKéü»½C•¬æ²±$ÃmâÁ (wµ¤ò”n¼?Dá4ïGtŽ9u;¯Ã[yCܳõe¯QíšØ4Ùœ©ƒ¦÷Y?ª™HóÆe‡Ô‹+|ûè´ö!ÑÃâX_÷ò 5›ª5ú°óåE˦×hóÃXÛì2­¬=Ã*ã#C)|¢6,~ÇKÙ.+Mü³2½4¶¾ìc¥ÕáËNv/øªžWBÌå^æÄ±­oÛ_½a¦¶?Žèä[‡–ì¢e-È3yä Hjqg¢@6“rE…² äŠ ’7A*™;Ù rA "$X°a®•© ÜA ©« ±Þ…0¼rås½’Ž@v&€ë¶ÍOÏõ–UŸL0jpŽÑ œr ˜NNÐ §”Á`L!UÏ•"‘< *޽ò˜N®¿‚¢h™Iº\K6ê5ÐñˆgeѤݻAg_.ÈH=”£oÚwQvûT¿hX~ëÄ©Aíê¯è½ê|tú·!¯ùg¥ÖiгÉJûcÀÚO¦ØƒåÓÂG2Aºz²dZBrOÉ5Âât¹i†ËŽªŸl9£jr¤±ÅUBÆý ×U ô±Ì(‰ÉQ¡RlRÁÌN¨H 2C¼ájÆ|Öl¡&°çW £ á䀎°º‘Ê"ŒÎ ü ¦1]d=gA%Ïx0SäE|Áˆ#•ú`„‹S†Š%A*^ÿÈ“`•HyŒxNêE`„Ëvzƒrr’§ÀL“|…qì¡X~ƒõRý& \îîŸ{?ò\݇ö.:Ýa˜ßÛ[þ¥Û6¿@/\û—Œ—2 åå¤|,ȵ³¨Q”A œ9˜IÀ 5±@;U/ຉ¨§ÈL@«9¤›ÛÙTU©RUèòŠ$d‚ªÊ; ×Í(n›Í[T[[×zì ØÌ‹ CÀÀ{`«<À ºrA®®ú7ÀÃ9y}ä¯zN½Š¦`L‘#ò¨dÎè% â%¦¤H¦±’N“ùš7@¥èd‚JIƒË †S^Ôσ1ƒS< fŠDK(Ja“ÞŽÌÑIîeâ+g}ºß‚õJ% $;w@*˜d>H š/gArUuÚ‚(3R¾IT¡² 䶪"û@*š;¸ ’¯Â¨R¤äM·Š’Ý eT€kÍH§T¨|ÜRU$dƒ:.'Å®û©:Rof:°Ÿöw‹óÝ{ÀªÈPRt¶àj./êy`Žæ º‚1ŠFÜÊ)‘œ3–Æ‚¯QŒ£ò!åÀ×?ÉpP±ò™.YšŽÐŒY/¯€‘Ì =Œp½ƒn œ²M^Ó©Sõb0âe<¯qLŠ3¤þàºÌhåì‘ÀVs©–J'¨©ª‰x›Q ÒU?™tTÕà†Å< ² £ˆ¿ ”’ÏEÉ÷@EH$pÐŒäYI*”±*XÞ¹cFq øN…‘ 2ÎÜG4pŸk €OØBc Ðê^— mRòÓñn–øëËàzOŠyÔ_¸Ê 0çèäE0â%RÏc¶þ’ bÅ)C@Å“¢ç‚™" ¼ FIÒŒp9YºkÚÌë âpÒÌdIb/Ilf,áÒ—º¥K“•(å9P rŒò`ùÊD`$NÖL4"¤ªy¥4óûò2È-D[o3ŠÎ 1j ë@rT | RGEI0L² dºE-"*ï‚ÔRÕ%XcF±8¦ñ$pRÊf”†ªµt&¸‚eèd¾dHÓ_ÑlßZ_0ŒY #´ W¾µL†ƒ9Šúm0&Ë{t•BSÚ‚Jh€éä'†‚Ëyj€1ENaJѱŒ3^æâ †“#4é·J/0¦Ê÷z¨d½›¶`&Ê.é †“T=ŒIbxu ‚<ðÀNNJ8d"«6I2PGe‰ÀŒ$ 8£úÉd'T€×Í, Rè ¢Ì]ì²T RE± 8dFÒdªêÏ6÷T€Œ¹aÀfÕŸhT5¶çÔÙÒÙ5'x­Ô›ñ€5žL¸— xH&ˆ~ˆ°:èFàzA‹Ôx.H;0·H=Œx=Dž#YNëi`„ë… b&£ÀtJ¸Fœæ90¦+Ý@%é3z¨Xùˆ×ÁtrHÃ)©z3S‰à-PÉ:•F ä=\ vK™ Vž$SÉcÈbU•; Áª€= ×U%Ú”5wÒ ˜¥Â˜2[ÈDàs)éÀMÀ× óÌ]}T?™ r@ÊE`¡¹“ÅÀiƃÀ)ÈQ(µKÆJ¾¯¹šŽ`/p)ª½[Øl¹×fÙ\z¼€ö*Ž4pu’›ä€9Fïæ/`Ì–4+% T<µi f²T' ŒD62Œpy”SÒHÇÞ3YœT#‘c”#\¯£(§DÊ©÷á3I6ÉP0ŽqB¯†H>x®Q d5edÈ1ó1Ä۴褩~²ä)(g@ÒTë@Qä€\1£øä¢ •ñ !ª:›€Ãf$… Ô ~®eÜË.((¥Î«gë¥Ínù.³î¿f uKö1ÔUÎH0÷K;=ŒX=Dz‚1EŠõ' ’XA6˜Iú˜¼†Sè`8õQ:1E>•W@%‘¢—‚™,»x '?Êc`8¥‡^ÆTv2 T §i ª¾”ÃVk-Ô$تrØ d(o:ƒlVQ &¨*Ì 3JfÞ*Tâ<Ì6 œI¬É U d3ÍHÂÓªTU8‹*J†åTM‰¦›itû^ÕÔ*{/÷,ï…°ÿ} s߇çÒ~EàÑ¢Á•úûUpìóŠ+;ïrÞ>0/‰–oÁ‘¤W2ŒHÙN'Pkå N‚¹…ªò0ñúš^ÆtùŠ 9.A9%]Gƒé$†Q`8u<5Á˜*S¹*Ioà5Pqò#uÀtò# Œ½_Þ¯ Áƒà9&3¹.Ói%@6«yô.«Š²äi3RŽ‚œTýXòªJ.ˆÇŒdÈi5PÂ@Ê«¢ãf$7AÂU(«AV¨ yDÔ.¹²^U—« Ÿ¨Þ¶«Xk2ÈÁâ¾Vk°&ÎÊ©ês÷N5 ÚNbuÍp5ùW ‡ï˸±ãH/W®ŽÒ|Œò}ÁU¥ptÞjÈ ‘/iT Xã­¿ÃÁ‹„‚Zc{Ÿ 6Z È3‰>#Ezâ F8I´å”xé ¦“sz* ²›^`¤°[º®ÏêÅ âeA•c~`Áb·¬¢€)j+}@v«š慠,*›‘2ðV}妪°(on§6p\ ’ñ 7U€œ™ª"ù¸¨ªð2pX9Ø|¬Dwºl×=Ášý°ZzaþÅ« ö“Öƒ)gàò[ +€c%l~|Î îµJQ¥B;7Rn6PWÍÎÀMBy<~Ö`=r6HO€YM¾¡>¸z§ÁÑŠÒ¼ÚÈX½Œåz Ú&»å90StºþŒ8™Äp0Âu M@ÅÊ4.€JäÂÀL–4# 'Åàe“e² <»å2ðƒ¨ ò©ùš¬édÖ—ëª߃TVAÜ)2£ØrG ’ åT [@®ªHúƒTS| ŒS¾2äCõ#€æët*ðNF˜•ž±Y‹WM‡i½U—ÁG—`M1Ä4†z“¡²ü0¦tùgÑý©Qú2àú…”|í1ïB#—y½P·u#¸’ÈÓ²ò®QDW¨qK¼höñ¼Ê‘ÈX0IåAýH"õ@%H¼¼ f,'ô›`8õcªl–î ŽqFÏ¥$›óà‰`)0RÆq xUÙñ\ªóo3J¦A*LNgU ßÞ*ŠZ€SU–ù o«2 2ŵ’¡ÀÊü\]ºî•'õMÐÓ®¶Ïܽ fUi~ÇYÛ'F'€wxÝ7×BÄlHîøçÿ—ø%‚j7-÷7C}ufÂ+®‹ 2GÞÝ‚­ ‚‰‚¼ïx›º5XæêH¨;]/'ªGÉ^#8£ƒY,‡xŒpbi Ê)©$€édÃÀHÒÉ4¯B ì1L¹ÁlY²X=({Anª›ìi *q 䌊b H®ª,ã@ü•Gv‚ä».r ˜SÐK×ÝýÂ!ÆγsÌþàé•s¥ÌipLÔûËOŸ`î+q£7ú»WaöKÀ=ýßà—žÐrýß r~tÌ^+N›<ˆ <½uÝ®Ãdy²êÊóú0Ô|‚FAÃs:Žjü£LF@ÍÕñ2Ì Y¨_#\æP ²JzÊLz€g™D)e*‡€±æ&*ÕTG™ ¸”¿üd›Y²¨áú€ÉÀ¬Âzz°ö\y t—Ô$w°^¸ñ¢ñ28¦Y8ŠÀöè] —¥á¶?:"–d£Ùü KÌo.À/Qg^»ÜÒ—Ãë”#Sò®ºwâ›gêsk:Káìçò% áú32à)\GAÓçå4¡àuƒ8y TºœÐ»ÀÌf ýÀë4˨ Öh*-Ø*@>R“$¤µy™+ ·\+ñ9VÜ\Ï9pî<é ×'î¶€õÉ¥ÓÆ£àxνQ†ƒD³Q¼@Žbs…€;ëk3×ÿû7áþÇø%jüÈÇ3Ôg÷Þ Pá„÷w Š'CáNk®®);%ÓÖ .¼.£õFhÒBÿÄÛÐò3Ù€/xu#™& zÈfÙaÈrYËP#®¸ |i¾À%`ê¹á„ƒždŸŽrÓ6Û_©ivÔi g–D@îÃæ> žŽX_w†Yã~ó»2þsü÷umûìߎÓâc*âH°  =t ÜYf%Èmø 骟€SŠ“Tƒ6Ã)xäyyŠ9`­‘p]z;€‡/Œd{`µ>ºiÊ-û[àø° È“V_(Û¼Fn·‚#诿‚é^X¬¿w_þ+ø%êµjW†ÿ¹÷’“Ë£÷b{Ž&€¶öñdzqÁ3¶”žv8VlýèÎWÕ‘WŠûj?Ðꔯc6ØÜÙÝèpñ#¸ÕêC…ã°ø˜ü³½G~oïÿü ·ï èS§Jÿ‡ÒI%6|m@C.±.b{Á1¨n }¤ð$ÅE~` û‡@#DjAÍjèïvÁ¤5­ß 8~FƒmÚüÍÐJ}3º;€´'ÀÓ½¸@æZ°×‚ 'Á\|ž`ØP>ÿâ·kžâOü‰?ñÿ \•h›ü‚7IEND®B`‚gaphor-0.17.2/gaphor/ui/pixmaps/gaphor-96x96.png000066400000000000000000000520501220151210700212210ustar00rootroot00000000000000‰PNG  IHDR``²«{bKGDùC» pHYsHHFÉk> vpAg``xÛÙôS³IDATxÚí½wtUÕöøû™ûœô½÷Þ{ï½w©Ò{GEEņ+ØPD±!MA:Ò{ÉôFI€@Úž{½?Nü}ï»o¼÷ý¾«÷‹÷Þ|Ƈ32²÷ZkϹ×Z³-È'Ÿ|òÉ'Ÿ|òÉ'Ÿ|òÉ'Ÿ|òÉ'Ÿ|òÉ'Ÿ|òÉ'Ÿ|òùGYð&4ÙÆx>í =³á›ÁÐ9ž†Á)°á{˜ìó°[ûŸƒõ°ðïÊÏÙ<ÛÆ†!%v4•>+lJǧK6{üîAAó½ $ѾBN³GÏDB¦«HÙSsaË¿MM>„ME ÜhÒüa÷æßyØ øwá‹/`Ð ¸Ñ6ìÁ¥ ð©åÌê¹X5³¢ÀO}‹2ļfJ:£¬îe9‹Ím«štÞ¦ÌR?ŸõÒÙy?7Ä'GŠ;óîÎY’ñ‰Ù] ÌYá!“r÷W«R]›’ëÂø:÷ªìzãa÷ú_÷ÃnÀ¿:o*\ Þ>äÖºyc|fsÞ÷Ep:¤óý€ñ.O@™œÂîtg\îw¹s}Z›ÊÖVSÀµ=÷÷`â(B6ÛÉ`±¹LEFqNÒrýimBC·›Q~Sr³íÎvåÆÜöòÉ×MeË“Û? Œ¹Ç”FðfpÑ5Gž‡©÷“žhû°Gã_üàÈσx¦Íz¸ôãí-àô7Ñ,Ÿ4¿¡. œv¹ÐÁ]8sºïSÌ,ÑAò¾?RÍ—¼æSžÊ<àØ¸¸? ä’‹!‡ßŸÁïOÂ`\&ðàÁ–y¼­5©gM6ÁÙoH«>ïgLíœgÌ5ÇÑÌE“a³Âú¥ ž°Ø>§1O|n&Ÿ¯ø°Gí¯O¾ü7¼ã*X¶á$p¾0œ¥àôw­â¸rËïÄBBŠÍ(x»Î6ŸßøZ~•ç‚Í"jIŒ(JyŠZ‰(>\Â`aHÏ»ì?îŠM Bqpñ6ì4Oä6Bä˜Æ÷û:×í%æ“L‹GýÇúì·ÜG çYßÂö Ÿû¢ZØ9ò/JaS#¨¥(F¨«÷¹I&Q€bˆýÓfƒ/>àK{,ŠáCY²Í[R0ë‹­Ñ–Ͻ2ºÜõ¿ÜÿÐeÛniaÆÈs|j‚€]R]ö 9gï ÏØ‹™þfnéKsöh?|þãà­Âáëv«°Ó1s8­ÓA¥ÀIw`M«Cö &Þç[ÚJs:ìWÆÈg”ý‚ÒøpÁÇMÙ¤“‰ƒ`™ãÿ‹cê`(@d8Y¦–3œ3|JÖƒsfƒ)I¡4Û}Gë³óþn§«ï›ò¹9æ´u:Aݲ\2G¦7¿0S°’ÎË*û°ŸÆÿ>ÿq ðÖ¬ðÑu¯Ãݹw÷~)cƃñ1©œs9Dû””³Ç\(8“ 3ž…Úp’ Ü~uð!›iŽÁÁ2[ó–*ÎCëÉûW‡Ø 20—v¤]°ï½Ãë¢Rëf ÙïžgzdLau®/ËéæÍùXÎR™0:í§c3ó®úÀ¿½¼Z'¸WÅ$ð´¨ðM,}Âî–]Çlaì×~{¶¹îÆ V?šZcÞ—î)r‹òdF“’#_#XóZÞ˜å<ì~ý7 ¤¾\Lç_û=˜D`Z!Î`¾My*ìýÌ1&óþ i ‚—X÷ùUïé·êÙ-ǬLdÚÚ´V'þû*Ä¿¼Y?tnãÀYèn‰Åj®:gqØiõ¶jý¤£\‹¤1¹áÃäœÙjïJ¸ÙŒoT|¥–LÄ6Š1 ð¾]ïý‹—ñ*°t@ÈEŠY9qf±Ü‘7ºò¡ÄòCR3ß|åîý nÎÛNRî p&è3Z$ÙJ—¢0u^zS3vwþ<þUèÿaýõ‚íß{µûznQ,ê9mHÄ‘·ùŠ×@×Úóåë «¬Õ˜"ž”&²Š:%w˜ºà|Ž;¸¹mMÆ…`콪IýwŸ¿ã÷7¹xÿ/c1¸ ¢”â*²VÊd%•4>&„§Ö¸jús+s€~¡¥¹—²ÚÈnp÷ù€/ajñô»'2v·þqþåð»ï†„4k:Ë}#ë „}L”XŒiÁ ãf†«EäsñÕÎ|^žM.Ê¥A²¨ð«$€p×%,²QÚca€«ÿªãñ'áBq#¬ °(RØñ&éožŽ»G†uŒ c¹ç„p#kS¤<÷û;Á9å‘ÏÉp¿ó˜‰Ï¤½uüâÃîÎÿœ¿üûmhМ¦…Ïk/„’¦*G1ÌuR$üüÍZ+C9rÚ„â’\¤lù>¡¡Åb0¤ì»ŘV€Áa^ïóc¡þŸà‡à`±üñ7É ˜"w*’fúóÛu·Üg=]ãë<)ͤœæ’(¡| ÎIë.ßc¹gb%Îä‘wžüõawéÿ»³IÞx;ìëúó3š]LÅXW­+Œ¿G&•;±—€¬}ow­w¨`i9m.S±âjHb©¥(aøÃ\f`^/?É»l¾ÀÿÿÃäýÌsÊ] ‡LrLü¸·ò(}©~µ’ùܵßørÈi es¿çqs‰`ÞÂb7–ëë+¹3iÒÍ›Ç?ìnýxý›ˆ2u¦ ú¶u%7ãó‰é7\âò“^ ‡ì/Íw¡èÌ0^®œÁ%™C©òõÈá,áAn ˜×óÙÒ¿j?ÿÅñš{…r€ƒH6Ù#K¢<9Ä%_ÂÏL¡À¥—ͺJtüh«¥ì ]+™Ö’íôI3Ge®œ…>»W 93***êáuè¡ Æú®¡U‡+¯—úij©0%Á)»#KÌ72Ì"k˜™|ˆ fª|˯œ'¡ŠPë!‹ƒ#!f@ÞöNv¿þÃp Ð0Š…Mxn*¹K‰øºÊ—T8÷„óˆµÊ L|Κåt—÷eRÜ„X/Ÿ1ŠíX®ƒV1'g|“[ÓNžüßëÀÿš ¼Ú§ð¸:ð›]Ô§:–ý•Œr*â˜}rÜüÖzg› œlÊ—üX%Y†Òƒ5ÖáO.áËÉ%•#‚˜Šy­Ïúߪ|þ8x÷X}‰ÀŸ‚hö:NEôõó­y‹œ3·ÜÛ´ƒU=©ª]Ëý•ÓÏìå3yÌ”ùÅT’X9:vw?Î?{†ø§)ÀkNèœz‹Áï1W+ÚãÒÓ€wP9 qÀ`Ýà[ÿªÌ7é®ÜóÕëÆDinF–'°¬¯KǤåµ5-ïòùkù¿6¿›[½3„0ƒ…#¸Å‚|Ž?Ü¿ü û$ޤӕ3)蟖R=`ÈmÇg Çûì1ë@¾eO!SK 9UäÏwÈýÓòÜã.åÁ'Õ{4ê\É~Ãõ‹ß ågúWÜÂ*3„Š žà&!¸KuÂ’¹ášXfoÞ0ÞÈ»ÜïúÏjo>ÿD ïÞ2|7`ႦŸP7¡Õ·´J»äp®®<ãÔ;eË|ÁÑ[¯ÉQÆ’lÖü³šöO›Þ:z«È “ÀÇViçU×A³¬yqܲ»ÝSJX`ÀÁÖÀ¼Ö¼ž÷çùY±ÿÞüÁäËÚ7|ã3’Ía’<í¥›)̨­5ì7ØçÚ{óÓ»ÓÛµ ÷j0çÀ©¸à“³ðBÜ?Þ?<D¨P8Þ7 ¥kö—šj¿r¢.œtùÆ¿©›øìêª7Žÿb5g¶TyWI%³Y˜¹ÀU¤LÁ¸2½S¦³0o¢ón“¿äù÷@½a{²Í«R@pî&IšSèT³P›WJx;÷¦äçoU9¡·ë <¦5˜ÆÖÓ ¸ã 3ÌÎÓþÏkóà+€Æ›v)å²ïŸš³ü Å*ZO»šckíhýjf™{›¯õM~"!'³Âµù¾WrÔ ˆ¤4u.ïe6KêùÊ1*´ˆ!”¢–ø !yÖÔeÖåÝîNÞg¾•ç_ƒ\ï“’1€#oãCÎýÃ\#‚˜Ó-hLªí·Üü áQ¿Äæ¨v{I»Ý´Úóâƒâ¾ñ uoA\Ë.sךø r÷É<óšEç?ÚÀ?¬²Vo6QN7ê@õ &7&²ˆ^pîÁùwJŸX+ÕïÊ€Ò J5™áW¾q¹ô—÷Û$nÞöfázûáåœMçÚ’(ñ2ºqªSO˾WÉ*Ú ÆÌž¼ÛÿÕ£2ÿÓ°ñ¾œ&¬óØ@3;‘I5*Á³ˆ´=³²åêƒJQa1󳾬ü­ÓäçIiÛÚeølõý–Ÿ\Ÿ”>áÓäv³ò*†¨â Y²Öó…UÜkØêl»rÞ]ÿñÿñMðÊ81bÑD?0/ð)w ÐmÒÍqhsNfQSóUHœ™R)bêÞX®ÿ|lï‡ôð~ÈM„tÜ9ñÁ!ðB„´;Õ§¶|`Çœ?·#Ë§ì »äÅÀ5ªs[îvXô.ÓÂ÷È£µ'12¦ôV™2ÇU‚w-¹Ýd.È>-kóY1¼\fÈ!Ùí®¨©iO爼`>€Üœ! |ÆšµØP´ 6÷ 82BhÓDz²óÄK5§-Mò\íê„Ù©â“[íT‘…·*—ºÑ>õ•£Y¾å·8·Ps9ú“ÙJ/ó } q\i~_ÒpÜŒƒ c¶xeJçµ.ïðçò»£±Þ7|2 `WÇ¢šÉ¹¼’ædHÀŽŸ¬0š:SOú'É~±Dïl94ñ¾ƒtëãBµq=ØXz¦Ïu {½e%Ž,«P†@,ß*òÈŽœ®ø>ÙÐøÜNÀ8ï qÝ2)¬Ã÷€mÀçÿx‡þøè¸Þr*¶C!pb¹B3Ðoy•M ïLU°eYà[Åô& Êv¡;¡Ì Fñ6™±³d¹»¢É<ڤξŸ»<ÔsÆYš™i½agžØ6kQµ{ñG²'ß\ðÆÆÚæY;Åéxà2ˆ%­Ë·d‘Ž_ÓfØ’: ãÔñÎ ¦×ŸÕßÿ0~Ït+íõ¸[Çrqku|qÀ“AEÑí3$QVóãánÉe³}#cï|sõþ#=—HPµ§p]³Ä ð&Ý`­|…Z%ª^’7ôÀ´™–;T¦TÈ®MM ™ÝŸÁ€èiòž^¦"È[NïƒÌCyäê?ܯÿÃ^*\}í`ak‹< q*:OC‹'L{^‚ʽyÏ´÷ó€Lð5OS|cÍMüÁ7ž™ÄoOÓÅ´¿©¦‡AL_Ò ª4çèŸÖ#fÍ®Ÿ·«¤?æþÔùàdÇWÿrß¹ÓßÕÞN¶””wªM š´åhFîãÛxnüpuã\Ç»‡Xœ×ü|sëÿß—!€€õ!6b¼ã–³;Øý©*5ÍcìÙûxJ½œFE»§7~*mˆëÇïaظ÷ÛÍëW´åÂÈ¢{d?Èñ:»Xò\ˆ[¾~Ê æ6P4§“A¾Ë©.k@~É©A=`oN=ù8˜S… 9öo ùæêÛ¾[À]`›Ü,°áó6Á*ß¼ÃX¢äÀxÞp|æQ«4›È BÅR¦.íÁgœi~ß&¢à›Ä$ƒO´ æ6øÆ˜)ìß8z˜.à·È´Ä ~}L,ÈyÓt¢6œ›C4a°?8ë  cOÌp'>~—_ÂN%Ï©¶Ì×9OËŸÍæŠÙ#/Öû˜` ‘Ýç"ŠáAýd‰?ê?Îë”q~΋%ú4ïáÿ§Í&Ï,éÊ3KŽÏ˨A°ÉÂ7á÷€Ÿž•zÜeçÎí }³?(>2µ_‰}.ÚËá—³wÞ¯¾6â–5du£—$dqÊrD¿àE`‰]C–ƒÌ®D WN8MAFäT“m ®ÜædÅrª1 (S_ƒ„äTa>È~{—¬^ŽÚé¾ 2î`O{5˜õ|ùÑXüÀ~àfmô†u °]p.1›B çqX Ö1™JØq”ã"XñfeÁ:D66h’ ¦?h¬&ÀnHM³¬ÃôÇü¢d ç ¹ËDuçûÕ”h{¢‹J†eóë¼ö¿]yŠ„“?9q#øƒÜ:=Ù¶Ò˜£˜¦öW’|ÖÇô–‘mچߨOÉ~¡¤q“ÐÚC¸`|§{óœƒy½:×=×CË>ê b³*㋃‘Ž„â‡OÊmŠS”3;¾’URÈï§zF<ø¾|p\`h=×Þ¬p¥öƒe•Î|ýê\> »Z/<ß|øƒ¸‹ádgCdzmÚƒÔ²ØlÑ$n)Z—±Àtõå*k{ÈÖj5’¤¡tñQ·ŒvëIú_kšImË0Ü`=óÇâᄂ€Ïû¦'`;U%œ8rˆýMÖR d·.=ÁÚ'SÌ[ ©æYlÐxYŽš`Fà½É V€&K%ïï9Ì}°-3œ°b)J"‘A€NGLQ*A£¶™1Y_¡!µ«oϰÝÚdù×]O¾›²éÖÕ½Ÿ•™{õîÚåæþ²Ëø;ËØt,œH²‰h{…³à; …dr¿Úk¸°±Ü½aÕN²·—æ÷Õæ¿ê¦:Ï<)7°ËºÎ-pní„‚»›Qx¶ì™òhZ¡…®6|ªRà¼E§ÙûNV»çCÉ p}Ö`¹òN›Òø¶ô×òy… “²“¥6ðUîJÆ´³kÈXàumBC \ ®¤+ˆ£nJ4’e@GÛÃM \ëÓ¸ªnl É>Áu »“®×Àïq”`ÿ ¡\ÜœpÈm@s›NœB3°w3 äeBY C{JƒÆÉGœM2£©šN9’@¤ mÀŽ33e(XÉ 7ÛÁ:*幚À|ÀŽ6s( VS ËóPèµÍ#Ðo1BghqU>s~„½Å#âZMÛðëÙð­­{§qkW¶¯æF>yoS¬kÃÆâõ¿ƒÆæÉ:ðe¨„§ë|\’5 …±®t…\üQÉõF¡:Eóº}3ï󯪹yKšc À"ëN0QÀÝ=ßR‰ëD~±72Ä÷‹ÔJg¶ž,{ï‡zI¦á àÝ÷ƒ†\ªö`mÅØúQ²dwû•òˆTZ(€7¬6y=ûÍ€µ¹ƒh \Öútüµ]€6êÏ`í!¤ŒÖ”W-@`ƒº)lÓH©²W}™âQÌ~Q7@ZëIbÃNió!8£±å$Ð$¯·þãõ'˜A `|}¾lùɾÎU>f;èWæj€õ«¬ hŠi$‚&È:3ìh3™,°bäeŠ‚•aÚáÖqæ24Zj‘ cFs4‰çMÁæh’´ 7ØŸ›uR¬ ©hîC±u¦•aDU3ŸmÐîsÇ$ÃŽ©ÅýMY¹Ï*¾¼ÿ;)¾wÖ]ûê¸Òn÷ºqî§…»~ ³äŠ)ÿK°Ù€‹ô>ÙF8¾ý±°É.›B.7€Á˜ay£ðû»èaùróî½ÒÛ×+øâÂd¼H0î_È*büä]¿G];³_ú­àµ¶—»h_ÙU,mOë§üuCèTwœõ0¸ó#r#¯Ô¨"ãA|øhË }ö& ¸ý(A†h}©,Ö0ž4P¦Ô À£ÔZÛ×ò¬Öæ>pTÝ k$;€zêKOà¬ÅuàV¥40LOüâ„`ƒý6Áj $ÿ±ûã3€¿7BS&:Q€Ê{L'‘(ÞÝmÂ) Ö~æ v¬4`XIæJ€uXÞ# 4Ñô'4FZq 4Ž“”Í0sdh2CÍã 1R7h4mð;ѼJ1°RhÄZ°’À}°bXLi?J↲ó¤™0¹ŒYd~Áé<Û¬bYß_*,ÿdÏs@1Ì[îÄõ¹8ÙŽ»(ÖÁîTâ2g7~èZ+mµç¾Ä¸—²^-y(«Ýék¯TÁ'Á¿4– «¿ÜÝä»îC冴ú=xäÛ€ä p1ûÛsÊË ®F2Hwb&°K¤ 0À¾Ê §Ö 7ÈD à2ð´úr(­e4ð¼ú0Xj{è ¤hY²R Ó$M}dð† ”´=ÐQ§òpÛ©î: Ö8ÔœÂòFaãCTªñ€s,÷ —îΠédž­"³8rØ£sÉÛY .ø?öJ•þÑ…unFó-×ôÏ>{³¼¾ûÝ“d‘É;#B  *{z×Â"'òÀAœ¼ÁüÎý uÀ9 z?¬€ 'w Y\£ØñÁ¤2é£g¬-²Ï¤î(“p4˷ئŒ9GüÜ­öŠë¹Ð\µŸ¯ð.˜W{6gŽÜiÖF¶= Ä•ó#£ŠÙýP5vuÅõ Afä-YÞÖB ©¢ülÖ`ڃإ%pU[P¤šú2X«!ÉU¸dj-€÷ôßÙÊdଂJÊmêC @Ës NðC¥-dùßÿ±ýã3À^ðý–T@sšAà$pœV`oç"ûA~¦µ@Í ®‚¦Hg†€&™!Ô£ûA£©LØñ2„8°np‚Š`¥›y²¬=t5C@ã¥7 )f:‘ )Ô$ 4¬¦) Ë*‚DYJ•*­Ù 9‰f"ÃÁ¯&.é ëj^€ºµÍç÷O’pßmfÄæî5ÞZ›~©ØoýôËÌÇ]©¹ÎõšUíìÅ-9ÜLw }²?rÊjúõ^г‹ÖcWÓœ³¸º7@pð+t0õ†lêüâ÷ÜÛR±zâÂAµ I„“sv=»éÁÍE’¨dn;(1&g`1ŸÛ)œ»ÿuÇú2±ÉJ¬˜zU …̆܄ž¤=Žåi7­@B¡Çå-ÏrWDfŸ¥ÈX»À#zkÀÛê'Í€B¶‡«@/­Î*Ù!M¹ÀD ¨`(ÐCÝ^¶=¤¢Õä ç5”ÖÀTõ¡pO‹J;7l£@îi=yxV}R^#±€WÕGÖØ'iD9ŸëÈÝí>¼’7Z·¢85d9ÀùÁÛÿÍÌ1àœ%“+ d €•bžå0h,AÔ½Î+„€Æcd&Ø)²Ô +Á:fÖP4š$rAce<Ó@oÓÚ é¦1 ×éAhœôÐ$¾§Ø©fˆô+A5ß‚u>41Kù4Qz“ö8v²rÍsÒ|ÇÓÜì…Ö¯JU,hRÌ ’”±VNS6m¨óø»AçÞ;µ$»ã­·ÜÛtë…;³ž¨¾úöÔŒŸÝÏ^ÍE³›œ/^å0±.©RŸÀÎì?äZÅ<õļaüÝ!â]œ*¯HcóŦé ã³cŠ®J.³üšmûýtät«å?nꘚ½NÊy®Ëeùä»bÝ™LÏýTy1;Š¡@ãÜm¼tÕ¼<Ð\é ²ZÃø(¡!| Ô@ª¹¶‡a@ª6á;`šÐ¤±À7êÆ|5’îÀõð-«¡(°^ݲxQ#é ©chÛÃ%àªVã*0J#™¨¯Øz’TNºÏ<ÈÞm‚ÁµxþHLèV«<ßï˼ØöÙN<‹ú=“È+Q~0O‚¦›¹øƒ}‹°â¥“ÁúÕt—N qL5ïƒ&£¸@SeÁ  ¦…@c˜,OFSÑ Í®8`ß0aC'Àú™^ÌM –[ )ô¤h²Ô£h¬éÉ6ÐZ24ŽÆ Ý,s †œ8³ˆ¢àÿ•‰„·¥¡µcÆšŠüº{¢Ï΂OÚAŸ=Vgå[GÎ<_8óЭçüåÆ^ªº°OÕ}{û:ñÙÇdï‘›ø0^†uìŽ/§ˆ˜†´½€à¸Ë+ðf7°€ƒ?þ×Ç!T$}Ã(*ÒÌØÓ6{s‰K×—D”s÷»?„%öߡ⥺ :r„½z¶°¦ÜèDO‘e¢Ù <®»9f¢ÐÅžËxàÖåW DmÊ,²=Òä€Öäyà •@ !dž2x^ý¤^»ý8ÀÖštfk>êª›Š í4R6€`{p­µ>Õ>ê¦<Èn¤%ÈUõ‘éÀ+Ba™¶‡`àim.Ó€ÚêCˆ¿sÌ'ÝZÖ¯¨ó°ýJïŸá‹ã$@nEFj…± œk¦3ÀÞÊ`ÓÄÈ»T+Y¾e7X»LmJ‚&ó™ )„ðh‚L$4–²AcÌKD‚&"œ;EFÐ ¬$ZH°bÍëæ(X;©D&èM©‹šÄaÚ‚Æš.24Z†™÷@ÌKTM!”Ó`§Ð™h°âdƒuÝÌ”F`í—!æg°³)E!ȹazpB2©Å:†Z2:Wgcà“öý³þ­ofíøì@½—·®<Å•ï&¹”ÕbÅÜJ=¿Ç„Ø.Ù¿÷sn‘Fõ^ $ŒkS[áÂâ~™>d‘IÉÍKˆDa}×Sw3jWÝtá½bÃ||Ó®ÃÕ€“Ê>U´]ÔÜî‹dÐ`K¹Ò·²0d ‰–e­‘Å ³r3 t±”q k– µ‰ÀH çi ”úñPÍ»$!Zë³d¶†x+fk £AF«¿s5‚v@1õÈp ®s¸¦nÆS5’ÀV_.oÛQŒ¾Ôªü·Ä‹× tÈÑüîêËD`«zød‹ºIó:ò$â1¤6e€BÀ$à‡‡¨VYú¸GYA€ÛÝ4'Žp.‚þHLË—Õ(h²ülV€ÞcI qæAcd K@“Léš"Ÿš§ÀN6ë +FæS ¬_L}Š&0‘É )Ò@oS 4Öl!47çAS¨Êc ÉLÄ;ž‘”+FjÒ ¬]ah²YÈÐê²4‘é¡6•@oS‰{`§Êpƒõ Y“MgZÀÄžÒ+Ðëùà"lþ*xf-ɨñùèºI_Fž$:ãÖ#?o-´þÞÐØëj”)ý)üdÊËôeyÀ2¯wØR¨ä‰SWl˜Ø¸g ù.µqÎú°Âákn~˜#…Þéú@ÒAb‡'//תH>®–â–dõ& ˉa 0RßåSàž6£10A}øª‡O€0 ¢!HO —¾ wÕ€¹ ]zy©“Z u4Pš›½ktZi¸ yÑö0è¥u¸ÒSÝÒ$G‹P¨ª¾ÌÂ5D†,µ£h tÖò¡nî´Õº¨¯ÌYn{¨’¥UxLMg'îÔrº€ëY”é üQéý3¬@Ýy@ïäÖT^r7ç”ÝnFóXÅl*‚ÆHk"A£Í^¾MAizŸå$‚KFP¬]2 Ðó~ É% 4NFÐ4ÆL’Þ qŒ5/€ÞÖ”½Cq°£DQ°âÌ ™ Ö^ig^M5OP4rüMcŽ‚ÆJGÆ&š•Rô†Œ0ÛÀ¾AY o& V"ãhV292d/ƒÍ (F+ÊÀ‚=4#6áMó|ü~¡íúßž¸©T¡sí·ß6IÖŪÓVÿòæëw$6.Ôç&‹b£Þ^;´žÜæŒù¶c’Ux0z¤ìÆÈë¿“ÀdŸýT“ÝYFeÍÇ íjXÀR­'/å™!kݵˆL™§>tªØQœfjm^Šj8ñÀ=õ“À]õ'd’†ÐxS=t’5@!õ¥=Hsàg`§’RÚöð"pD«à5cFâ;Õ¦ »4„pàŽº™ü¤–‚4U7?»5„©@†ú±8®‘Lóí‘®@°íÆd®ÓÇý*ø.À¶ý!û«¿€X•™ Pà¨ß!À¾ë£ÛÁ‰3[éz‘ûDƒU…ƒ´Me.¡`'É%j€Ç Üëºù‚H°vÉrúÞ1%¥/hœ¼fž6›) '«)šl…0ƒ`'Hj‚cêâ€Ãc(X{¨Ç-Ðt©Ìc ÑæY‰•ÇÌ]Ð$R 4…œM4/ÓìXê°¬hæS¬Ôàè*p4V:°4š& M0-©¹I2+ ¾t#*Õ0]ˆƒgVPå˜GšÓÖ‡—/wÎÚ×ò OŸœV%¬Ãìn®@Ÿâ`—j ·1=¦€Îþee308ë$ýN9 iì´Pª—٠Ĩ›µÀ£y›ÎËêG+`¨†“ ÜT7û€ÒÄh±¶GZÒÆ¬F©€&êO0ÙëВɶGsµK_õ•á@O-H- A}(ÔÔ`™ò‚íÁˆÐ¦ ª”&iO¬U·ô†ÛQ´ºiÚ ‘§€êGy ^#e!`«›j ¥ôY V['-ëÈ-Šº\ Ùp–Êf€›qÖ4ÀöýT«‚^“\û"¿â€405dX1Œ3ƒÀÚ- èšl¶24¡hà M¤8σÞä,-ASd5@ãMwJM5|ÀŠ•¡tk™*C@S©nV&ÈXÂAchÈ=Ðd3‚Ðt:04‰ö¤‚Æ2”`GS–8°¤3[Á:@Cz‚&›Ò 4NÆ› Ñ”¡ hªÅEÐ{Ì h¬øÊjÐ³È ;Ž.9‰4ÃßÒÒ–BP¿ˆy…Û0¾Â-ž °ù7'\ÉÒÊ`—¯·Z¨L'i÷çpèj—Àd‚zä`¸º™2Yƒ &«<ælºm´º¬yV( ¸Õ0\ƒY”Ñ@böiYˆ6&d€úÓ8¢èxÔM`¤í¡ Ho­Æ à% ÁS‹!À-È)àKõ¡.ð¥}Qž‰Òj425Læ{ó~S pèk_f5PZ³ Ä­~LÎh<€­÷AÞÕT« ˜7ô0pÎà¾× $7Ozˆ àšÇV€‚Ë\a€ÞYe/ç:9¦ èW¼ÎE°BeÐSA€Æ3Ãì–› åEÒA“Ìü@Óä+Ê‚&™^ÒìXYnž+‰5(XGx™ÐDÓÆ+È2‰ùy±F @ÌDЌdÐTi;–“Ì+Ùô—ú`Ý”Ñ&¬ct¡*hœ™Æ/ Ñ B@ã¥K@SÌ"*€¦ÐŠû ñ”'ìëÒƒ«`Ý6Ë ÖmªS ¬c2šÃ 1tǺÉ,À@î·4DÁÞmŒ™ RÍÌgÕÉ`" v4À(»œ,bô퀮jÑ8¢šýÕM Á²§A¬<N{`œíÁ¬Öš,êj(@fiˆ´IÓ@>Ú¨Àõp¸§Œjjiˆ×ª3 ðÓi,·=tjj}úƒÌQy$K#É|Ô‡æÀ3Âw€¿zè²X# ªñ ¯i0MŽ }A>±=”êjuY ìÓ@Ü e4ˆw€õE"pÇI¿ý=°Ÿ0÷ÛÀŸpPÇ·m7Sn»ó)`»_ókN"%¥8ènz›¥`s• ÐXNÑ4‰V”M•EÒ4ífØ1f%MÀŠ¡/Vey¬£ì  h<£óÌ—•‰M:M5©šÌHKC³ìhé‰Xñæ5ÒÁÚ‹‰ ita1h<‰DÆÓ‹ ÉÔ!4Eºñ#h¼B/¯bIo°ceŠy¬$ªQ¬£f(§A“d*A),_ƒ&›1¦h*ƒÈM”æØ;™ à,’â&†“ÀWd£À‡z’^ ´7.ê¡HWõ“µÀLÛÃAZ…À2 fÈ#DQ ¥†ËdÙÞµ7¶‡B “µ>c= à{ ”þ€¥~Ü`{rHkÒ¸ ÁÔ¦©›Î u5’Àõ% È´£d.°T«2 Ø¥‘¼¦néâÑú‚<°=2øT›3ðQ7!@} –åÀ>  0T#ƒ×ú³x^y$MCäkà7=Ið´¦Û¯€äRÊ¥Àx ßÃV€É<`úÍlóáàœ‘ghögÒ¬9\á,h¨ c¶Ë°c˜n¶•È“$‚uHVs4É,  h¬|JqÐhó¨ôM–æ1Ðû\Ç4Ö|†A¹v¼Lc6X)T§X¿™Åœ§3 ÑR’,Ð8úÒô¾™*­AS¤¯¹ zÁÔ;Ötä+°’é@X©ÒŽ9`3ÏS4†Nd‚ÆHù¼™h8  ÷èÊ`ÐD H[Ðo¾;ÁLÄ úè[ò&IF‘MI§2è`Ÿ¢0DŸf°_ýhLÒÂü ÄÛùd¶ÖáIàQ ’ Ïk8+€ÑêÇ@; `!¤^;ÿsÂv žr è§¾Þ£’4œ¡ÀÛ#u€%Z‡ ÍÔ-“·´E_õ¥:È#Â=[£xÄh$ÏcÕMPDC\Q_R€ë¶‡³ /ifý4’£”êi„,¾³£˜ ò¢ÖâW ‹ 8hŸ`0ÝIó›æ<¶4ù=óû·‡¨.«@Á³~e½ãŸµœxS–ƒ ;.ƒÁÞAEÓ¬¾ ,X©Ü¡"XÇ( 4VÞQ 1\4 ALiB@Shš,-X gZÑì8>b#Xéy –* ë7ÓŒ’ ÑŒ”1^A7k@“¤ào4Pm 2É•‰@!݈¤§™ü¬âµÒœ¢´ˆ¼ 2K}X Ó ò„zØ Ö~úª?O‘ê/µAi?•lÝðZu eÕ—1 ƒ4‚mÀ“HÀ¥Á2h¦^On¼F2¤~ ¤† lU7}¶‡-@y­Â à7 !ÈU?г4’FÀ'êæÈ>õpÌ(sËÁ°Æª/o‡4ož¶‡§’ºÑœJ;MîùÛê–WeöCV÷—Ú YxÔ<ÀŽï[8œ$qì“f‚”k Ëh'-Mлæ]rAoP‚pÐëÜä2hœ|EI°S0”+ÅkO·Ž™£d€FË£ô55¥h"¯›¯AÓ)@%ÐYÈMÐÓ‰l°™&óÀJ‘~fXI¦þ`f”w CSÜ  R›W@“ÍL|Ao°’ ‰RÒ ‰|Ìu°SÌSŒ+™–2¬ß˜bæƒFS‹; qÔ É»¤âh å™ š` öf:q4…T H à˼ j[œ½”é§þò90Õö2T«Ó X¬E(  >TÚh ?ÛÃV H›2@ýd§þ,ÞÒ0‚wi’ÒÁö° øU«I3`²FÐè­2dš3˜m{8RF›ËP Sýè LÔF÷Õ-SAkQš‚ܰ=2 øHë ”T?*…4RVãÔ͘<…™ ¼«µ8 tÑÊ~ê+¿Ç5à¼û Ó^OòðŠS¹L= MH“w¤áCMˆq6sàÐÉvéMfèE³A¾ý’Á¦)èoRÏüš`á½.?qì8’‰ë¾i#/ƒ•Ì(³¬ýR› ±f=k@“È h s(šH/‚ÆÊÊxß´\ûŽ™AI°n“K)°ËXF‚F›áÒ4Rf'hªŒ'4™V\3C(Íhé +Í;`ß1 èÖ-*ó9X¿J~ãuf‚&›7¥1hŠt5ß&PšÒ Ñf4WAc™AcÐ$l™ Z[V˜'ÀÙ-i¸€ÌÀæGd²~Ìd ‰6â"Èè<ëÌã¶G>™®Íé P2Gƒ˜t× ^FÙWyðÑš¤<£þ²ð×0š‚,Óª¯i }AJåù ¶i@*åí i0) åÔÍt`Cž"¾¬ÕÉnk­€Íêæ U‹0 8­.ÞÊ«‡£ {ÕÁ@1äkÃê&Ò`™œË»nmÂz%êK 0k oË4„% Uõ¤¼ÖJ§Ë­©Pç lêÁ…wÿ `Õñ†s5›TÐЄ6f$8ñ’É0ИÍÇ »M3) v’|E °Rp™f`3_‘K8}Ao¡RôOš(ÐD¹NÐVc¶áMuÔ;Åt¯Gv‚ÙÖ/Üõ&ÌÈrnƒÞ5ÐLd9h¬4#4ÆLCAã©/h2H;Yz³¬3YJƒµK¦àx×øf7è}Óƒ )ô¢9h4ô¼<†Æ„ƒ¦P™°“¤?‰`ÅP†y ƒLOj€¾)×Ì`ÑL†¡´"õ²èaŸ  p]÷0œç¨º¡©´W;Äq ¨Òd‚úJOÃv/˵&É@Y-ÀQàuñÈ ”úÀ,ÛÃtÀÒ†2è .Úê­³¡nyä= ÐÖ¾BS ¨6”† ÔÍ\d- cù AÚÛ3µº¬ªj05·Š,¾Ñ`šdk÷Aüís² Ìx­‡ Äi €Z@N›ì#|V˜óAB6Üíˆ]p!Ha`Ù“ß?¬>]/H³Î€&­>hƒ£—Ì ;(f=ö1Ùi¦€uдbh }Y š([™šJ,> 7M]:áÒ¼š,3dhŠÙb–€LmÁŠe`íÁÍ;  ÒÒx`.{ÍTM$˜{ 1Ò‹Þ if¬tM¡‹Ùv‚´ ¬h&p¬Ýf8EóÌ´O¦òˆšÈ`¯âP.o©6ˆc ìe:ØIf¼´+F›OÀŠ¥"•ÀÚmzäÅ$ &ì½”–×Á™b®³È !˜+y Xâ¼Ï# “´ g™ê‘çAf©}€Nö9™ò¼6 PNƒÙ ôÒ@>ÜêKÐAÃå P=zi0õu1ˆÓ6“Õ—& Mí+ 6iÕpz ÕÍà¬7¡…÷Ud°E=´‚¼9®h0?ç4€d —F0pÙQ¼’©µ¨DhU? ÅùZýåEàIõåI ³zx˜­A³Hƒä(¸WéÓ,?'íd,ho´ÝB½ÈÜœ`LÌ«I³‹ƒã‘æs°¿æˆéÖϦ­ÌM!ÔIM’9R 4–öfh4Gð7S û6¹2¬4æ™$°®ËOlk¯³4ÅüDÐy‘Ú^± –gÍsy-Ð$YGØ7Lo²ÁJe!KÁŠ“ Ø`í1ÓIMaŠ×¯€›XÐÄÐó¸TMoÞA mͰ“MO~+æ”k7µšÊdÂA“iCh¥ ½.£H^`!è‹rÛúèiõé—wŽåsúÑ@Œ`%ÐO£ø¸«~Ô†¨?c€{vH­Om…(]A.ª/¯ 5‚` ­ía?È|­Ã`µº¥.^³å£€¥~Ò ïRc(Þ ¸Æ ½´…¬±ÔMP\CLR?Žƒ|g{èrY«ÈD`¥“²T) LÒá‘È.ÚIh=*€Dk(]€KêOMàœú1˜oGÉ)à´Ö¦0X½4Èt×ãz’gA’œô„ ðöÔõ.ô»öG¥÷φkb,€¸n¶p’ô88 ~ºzƒ&è.óè^4ZvËnÐx3/o©Ð—±`'ãðâe3•ÁúÙT¥5h Øš!Ó G ë@£M0@c™4™ŠLMå0eÀN/¸VŒJe°vJM< 7ØK%Ð ³@a¸y4F*’šÀo‚YL$huxì8Æ™a`E3_°vQšÃ 7¤AÓ8ÂTÐh3Rú€&È(óh µ(šbúŠ4â p.É86‰Ô#Dôv6b³ôžÌ™¢Iô:ª¿L™¯þ< TÐ0²€ÉêÝLVÐ@îûÕW†‰AHw ¤5ð›íaÈ­Â`†FÒ¤´úÑ ø9ÏA–¬nÖ+ÔóxÛ¯à×ïò¹ú±$T#e°ÇöðàÖ:„àÝD/öh‹êKAàs $xZ=¼rXC%HÓÊ[Ôz@_ g˜¶‡ßÀuXë–éIwXï:A-gCÃ(p/—þ÷âùOW€Ò/ø5öîãUø} ÎÙÜY¦ è%ëš|z›Ã¤€¦˜.²ì8Š› `ESd°vKOú&“KQÐTžàÐD³KÞ½ÎS ½Á³4M&ò3h‚yZ`ÇÈãt+Ö”“U`ý"/š± ), 4ѼFAÐ8²½Î4ƃ¦K+Â@ÓÌHbÀŽa%¾`EÈ °¤<ÁÚg>“Ê )ÒÙăÆÑž Ñf:[AcéÏEÐ;R9 ·Í[”û:mI+ŽBd‚Öà  ™0 À¬$‹½² çNNY>Æi9Þ’ôùêÇ`•úó1PNÃè ÒÅöH÷¼%Æ—Àõå&ÐR#8Žw-= ¤‘KCà3ÛÃX Ž6—ÑÀõ£-à«!HouKG—í¡àã G¦¢†H?¼Uú75’\ NÝœy$ϼºTkñ(ÈrI}åi0s¼›ZùÔöððšÖg pJØ ¸Õ3` j¸<Ö, 2߃µÀöÈU°>Ó¼ VE§Vø÷P°.Xï»Pë!+Ô½Ðh÷…ú>aÏÓ*©VrØoÉ^æ‚•Æ]g=Xñf°tk/-hš"CM0ëdh åÍ@ÐXÞÆ4‰ÂŒM¡˜yì™F5°bHá°R©L!°™³Dƒ&I×<ëQ?ʃFÓ3  Ôò:Öd01 )¦*Øqf¾Ì+Z†š‘`ýÊi ƒ¦RÐxó~ Ñ4àeÐxZâ€&±„² )÷æ'H ΀cžf$X1´—ž`”éæÐT*öv3Ÿ4p^’™´z˜d C.ºQVƒŒÕ>Tº©‘ù Óòìé‘êOÈ, Ë áh{d!p_«qdŠFÐx]iÄ{“Ö U7íA«‡ŸÏ¼v})«!¼\T7ý€Wµ(AÀÛ#-@öj=F{gy ø^#ÒEÝ~Òà<—‡âÀ)ïìV_ö•5@îhˆì ÚÞökM¹lS?òŒ†›àš¯AÒ¬ÎZ@VUGOÛ'!¨S""Ò¿…ŒG ²|žž~¨ à%`lõ׊3¶GgÉýàœgAW»êËÐ,y’é 1T’" 1æyÊ€&ÉŠ¦Ò™ ‰üÆ)°cy›ó`Ř'dX;N h*­ÍÐ4Ö°4&°4o3¬â­0·Ò #—k`G›•Þ¸~â( Ö^ïM¬1}òÌ•UHMd6 š"mHM¡@Íi v´7OÀŠ¡ íÁJ2Ëø¬Ã2˜Ë ‰¬ghœy]‚Þ ®ÙZDšaƒ³Éä*PW& qËFéÎFMíÓtÆÚ8À5­Î—@}õg 0QC¥!ÈZ€ @ `Ð×öð(¤M˜2N¤!°PƒY8ê–¡xLe€$­FM £†Èd×Ôni$}~êæ{ Vžgø²VãPY‹PxTÝtÎh0]@–ØQöj™«/¹ 4f 5„–` j(/áõ8û‚Ù­Õå[°²5œ«‘•rÀ·Û ¸doº=\øðOÀ5–7 „§¼²7ò¡ùãU!D¼Y­æAM€C£NLp-Šg`Fý[í@×™^,-KW‚Ú2TÚƒ^¢+{ÁŽ6¦(X1,—Î`%e¦‚u€„‚&#,¥¹ -s1  œ#4Ó¬$4·À¾.cè Vœé/ÀJ¦‡ù¬ßÂ4^^&5ÏÓì Ò%«@“¨mz€fÈÜ ±f ¾`ÇR‰Ë`%I7æƒuÈ,EA“E)ЩBÐÓˆ  tb(h*µé zKæQ³â «åUé$°äG|øè¢) TÑ/eÈ<-Bxž äƒµ&Sð– IJiˆLB4$êÇà¢z¤« i0]AF¨›CÀÉy¼Õ† ´û€WÕM'àg”% ›ÔMC[Cd&ð­í¡¯ÍIª©›•@ f¬L¾±¯q(¯Õ8ò¦Š/˜—5„*@’†2 Ì£Àk ½5aàÚ£÷œxpÚ9v)pÌ}Á³Òíwú–‡Ç†,’'ð+þ„CSÿÄjÈg>y§¾4Œùq&ð~гaKÀ~˺Á$ À “TÞNuç}°ü9gµËÃÓt=%S£uó¬CË@㉥2h²™-½ASiž—žL;Z>å[°RLc€õ+G˜ -3 ¥) Éf 5@S¸ÍYÐDI7ÐÚÑìh3…X°nИT°ŽK·<…˜L¯ý_f&ÈXó&hŠy…z É”ò:Ö¤? `Gs`Å™EÒlGF™À/-q¤&Яðg£ô™­—x˜¡eyh§~Ì™e{¤#H®ÖgPN p£ØxCƒ·/ó°E«Ò¤¤Kkàp^¸r9 gÈjµd2Hz^qÚûZ…þÀ:-Ì~À¥.F‡5„ ¼úrä-b4ÐJÝÒä”Ó(¡þ²8¦”©oGñð˜Ö¢°_ 0øRKSd†;ƒA*çnµ?gRÖâkáþɬ3Î]Hèªw Í‚ S”]pÑ+øá- Z/¸uà¿á/¡ÿ5dìÔ–TjÜ ˜\¢sƒ`Krð±Åà sºä.ó…•@X…y]€ëUãïõÐFJ€ž¥fh‚9MY°£9K°b©&Óó dEuTvš§@“˜Fh´¹H8hªôaèmSOZ‚&òªy4†Tn+ÏV‚iI$X)Ì”ù`—ffh]¼!æ-M#‡Ó iÒš) ±y!Ñô“J`ÇK³¬3ž}`£€ÆSƒá  Ì$t_|aŽ€¹ÎŒÃW ¸õ.•Aæhý@ð™}Eš´*I #4ˆ9 ?æÕñ¥!Ì ÙWè4ÖlP¤ †Èj`Zk5œpà3ûšÔù^ë0$YEÆ/k3@*©€í3²¸¬u©´Ð`–›4€gA¾P?¾ëe •m`õG€´¢ù8b¯×I༔¹(÷gЧ3žI®ÙVΪê= ÈZ¥6€{—!`?¤.…²ÏÃõù½Ê½ðgIí?¡¾HÀ}¯"˜‚qE¯º[&ŒµÌ´çÓ{?:‡àA‘<9ëL¦µ‚Ÿd–Ô#/Q\÷̧ô{üÊ;`Æh,o9uº˜ê 1ò9õ@ÍQ‚¦ASy™Û ñ”f ØI2аR1(XÇÌâ¼™!Ìò*Œt爔ÀÈä¤~²d‘n¤?¬ XôPÃsÀ® Ïj ¿%5˜@o;Šâ@€6gp[ýØ <¡!4iàÕa½íÁ¹Z…fÀð<³g–úá¬Ò¢ò,°TÝô"Õ[â²romÏ+À@õe p×›#,Ù¶‡@ DëËãÀ]  ð•–ài0INsZ¯äîu6ƒóÁý‰ö°sï4½·îÏ|¬Ä3Pì‚zÏ‚Ü@X÷!<– … Ë.H› ;:_À–?[Xÿ ð_ŠðŸ Ž‡»žœ5À½ë༫ºl»]®m·€ô×ešøBf/³LšCØKøÈ0é/O˜GÁ½‹KT]bÖ³ ô4×½Ew™˜—ëûƒùìhÞá)¨nFû4˜D ÀŽBǵ c€Õ‡Ý ·´(n0o9ýð^·:ÜŒdsœn7ŠšX0'Ó¿k“•®Ù­÷̃«1¸n“~² þ ±ïBL Äï¿ñ¿'øÿë ð÷  Wx– ©f…Aä-š€F›Ú24Y^1ÓÀ¾Ç:2ÁŠg-á`0uój‹Nch¢t¢hªéÍ=Ð$Fx­HÒ”û 1üÄ<Ð 3Nªƒ*SL4X1´¡6X¿˜eìÝÎt®€ó®ŒbIËyŒÖót/õ@ºëny$]+°X¤éTÌ‹µi§Á,©çÝ„²^#t¶=2d±Öc0Zݤ€”ÖHâ§nÚ]lÓK«ÉHùI+ »ºe$P@ƒ)”Î[Òäh’<²V—lÕ•f>˜.ã€Óøkño£O¹'[ìÁ{øõ€+Ãö·Äœò™l3…L8¤ã.h= ÇÈí@3ˆ™É"Óª”‰”„rïà" ÜÕÍ|Ê€^!€ °7ÊLƃuÃŒ¢X©Æ I2ŠB q'4šf„ƒ&˜Òìm5³Ž™tl¶ó¤yoyðÎ~F«‡x [=Li­!Rø@ÝL*äÅã?©nÊì(Ê€ôÑQd€ø;·Ø ìÊùÞlóz¼·„ø;>ÏiZ îùà_ Ì7ÛQôSps¤„‚³ p×/Ÿ?ì§øÏçßVþžÊ>-÷Æó ÀÕ÷XÎ93LŒ¬2×áÆ„kÃ¥–±!~-Éâ‚ŠÉæir¡áûLf5”é,u wª™œw®q €î§(™`o•'¥ 89$Þ‚~I|g¶qß$‹œò,Hû”tÞÔ3Œz:§h ÒÕ~Öl¤d˜c`‚NÝãU0…ΖJž§ŸN«Z¼4ØÇ+gªÂƒ³`M¦"A?@öf̆ûðº?Þ‚RËöè?|þãàï©Òµq›¿ùªWüz@K¸k.yI"!g.çx.@3ÖÂõ¯¤6 zY,}¡ñj:šÏ ô8©M 8‹K.èió„9 úºlâ0ýø ÄWÂä°Öùš·¾N8?Õu°·jóÍŸé æ‡“%EÀ–=ïö¬Ð!a¿€ÿó ²6Ôì vȽ…T9A1Ï…åƒÿý6¯ù ðßPiCë¿Í:Õ³>ûá{Œª9 s€sÔùŽ#?ZÕáÊë”æm¨»™4€¦?2ƒòPêE sÌdNñЈ¯A>ÇK ëªü æbÚ|S¤Â±:¦$Ðë誜>àÔO(ð¸Ö:‚ï6püAS‘Ü+P`%fó>XþQ¾ÀÿOÉW€ÿŸÔÊm¹øÝÿ`.%jˆïý`0¹3ÊÀ½iø»æÀ>ÈÑõp~‚Pšîg+M¡PKç'»PÑéÍék2™¦ü c6yXΦ¦§–ï[` ÐËîÞ ?s¹ÀП»Áo)fûÛ°ê0†·Ã{tþõø_ <ýÏàÒ¢_ëÂ×ÎYÀèÛ®/\Ÿø“õ­œû¼Ïjgý÷Œ£ù»Ïß3÷‹M€¤ÂÖW¾£ÁuÂÁ© æ9(êþHL<¸ùº ¬ý‡ËÇK¾ü“¸~ýäI€ûú` ò#CCcÓÀkÅLŒlÞçù†Pl$ßw8èMð™ f|ä‚E¼×\¶ìa÷*Ÿ|òÉ'Ÿ|òÉ'Ÿ|òÉ'Ÿ|òÉ'Ÿ|òÉ'Ÿ|òÉ'Ÿ|òù—àÿh7çØêûIEND®B`‚gaphor-0.17.2/gaphor/ui/pixmaps/generalization.png000066400000000000000000000012771220151210700221560ustar00rootroot00000000000000‰PNG  IHDRàw=øbKGDáäæ uj! pHYs  ÒÝ~ütIMEÔØ)ÿ.tEXtCommentCreated with The GIMPïd%n#IDATxÚí•MkSA†s“)7©¦7’¦•X¬¡b‚Ùm\®B­à"`Ûø±r¥ØªAÿ€{H«u!âFP¬Õ]K iÓ+˜…ÌMš{“q“EúašhÜõÀaæ=ï;gΙ‰,•J9Ýn·?›ÍþêíĘÚ%Ïd2vUUçr¹·ÑhôÐC7Íï÷Ÿðx<å`0(dY^¶Ùl§©+äétz8¬ŒOˆÙçsÂçóÕÃáð`ØóOä¡PÈ¢(ÊÑ‘c~á³ÈåU1=}[ȲüËáp\ö¶Šß1ÅX,v©P(Ü¿ykÊ|xøÁ¡!>Í[m6Û1Ã0>hš¶ÚnÑ7X2™p¹\ï&&'ÅòJ^äòªÈåU±²öU<›{!¼^¯¡(Ê“ÞÞ^wÇ$ IUÕGN§óôÔ»8z66M_Ÿ‚¨×LK‹‹‡t]_+•J_½í6-—ËcÙlöÌ•«×PBˆ-˜scèï÷™ªÕêeÀ»]Áÿ˜âéèh\9{~ ]×·uI23xpÙ™G¥Rù¼ŒfžV-öÍn·ï·X,;ÖªX,"„x¤€Õæ5s‹¸£š¦]ö5mä8^K@­ _è´“¤Æs ΆßJÀà@Ó¼³Ýrä­2¨Å͵°ü´®=vk»»›.†$f³¹ößâñøÇH$2TÛéô»³'ã×u*bmül¦¾ìS}ò9IEND®B`‚gaphor-0.17.2/gaphor/ui/pixmaps/implementation.png000066400000000000000000000013001220151210700221530ustar00rootroot00000000000000‰PNG  IHDRàw=øbKGDÿÿÿ ½§“ pHYs  ÒÝ~ütIMEÔ3:ÇÚƒütEXtCommentCreated with The GIMPïd%n$IDATxÚí”MH”AÇó¾Û*»Ð쇛‹—0 ÅîE/Ñ!B'Ñ[±ROQxˆˆn¥µã¦¡f‡.%A!ÒF–H ¸nd%í«‹½í¼ÓåÝX¶ˆØ5ºøƒa˜áþÿç™gvÙå`MÀÇ–––•ÎÎÎ]]]æN ìÎù|>LžÔ‘Häk{{ûtww÷þCRJ½¸´¬Ç'nè¶¶6 …žöôôôÆãñ=µ a)¥~»üNgÞÐ é纷¯O777F£ƒýýý‡JÁ¢J RÊ' Ïðûý!p”bnî—‡‡T¡PÈ9Ž“L&“ 8ïô60å®W˜tçSÀ)åá’ÀO·BðùÓ:×FG™½»H$Ó4·LÓ¬sgË0 ¿mÛ¼^ïŠmÛÛeÝy ðH)½•%^½|ÁÀÙ3!,Rê’RÊtÛÏ.Ù¶}Ñͪ’«@8V¾)„À²,f¦&™˜×ù|>‹ÅRå1^À÷£ ¸RºäÕlN¯fszöÞ}}´£CÍÖÖÖùúúú@zÊìò þ@  ÃÀ²òܺ9ÍõTJ544|QJ ¦ÓéÛ@PžZúõÍâkÆFFX[Ë8ÅbñN6›³,kÞ5ªkzB …¶ƒÁàRccãép8¼øÅp5(`]kýpccã±ÖzÆ-GáwÁ¢ÊÏ. À:ð­–rìòïùžœÐ°…·ïØIEND®B`‚gaphor-0.17.2/gaphor/ui/pixmaps/include.png000066400000000000000000000017661220151210700205710ustar00rootroot00000000000000‰PNG  IHDRàw=øbKGD¥ÂÑ"{ õ pHYs  d_‘tIMEÔ ú¿L ƒIDATxœ­”MleÇ;³ÛÝnÙm7–Ôvc”Fƒm-Õ€m´ÒÖhB LL<ˆ5ñ$í©7b"<›àè¡‘ƒ‚!µºH„°@éÇÆÖí~´£3»;û1;³;ã¡»¤ ´ÝÂ?y3ó¾yžÿïyÞy2"PÇÆŽ9üACûSíG"yÀr> ã±ñ1‡–ÒB“““âËñ÷[ZZBÀGÀù‡=âŠ'ãOœûþÜ»ò?òh*• ™†éB覶ý`tGø·ð3á_ÃG³¹ìáB®PòJ^|>Ÿmš¦ \Ò5‡ëÓJú‹á‹G‰D·Ç㱇iÞÙì¬óÔ-Š¢ØW.—­T*µÜL±€žÓ¿XY]ùÜ( N§sA„+¢S4Ó™ôžz©>¯(ÊNlf4M›.y¡ÀÞ÷ÆZC­?j9í;Y–¿ 6£Y-; y¤?Uqyê<†^У¶mÿ¤jê`vvöª¼*O—J¥ŸŽv<OÄ_2JFÎ4Íå’Yê”$iIQ”k¶mÿPÔÔ ÷<×s£«³ëÀéoOŸu{ܳ‰xâ”×ë]´,KRU5Q.—/r5¡&@_oŸX,?\ZZ:‘Ëæ’SSSA¿ß3¯çþ@¼P(Ä€k€±-@![ðGcÑO¥i!¹’¼nšæ…þþþ'‹ùâó½°lšæ ìjΖÆtèÕ!!ŸË‹.1 …>ž›Ÿ{¡\.ÿ\òú¼ã–mÍ©ª.¹ZЦÿ•~Çîgw¿Òm­m¿¯-€»â^F€Æ{ó7íÀ'ù¦ãÓ§ÜnwbU^]®T¸X•ø¹ò^Ü2`ÿð~GWW×Óó·çÛ‚Á¹X<¶`ÆÖ~ÖºPØ„ÖEKVÞÓÝ3¸«}Wrä­‘À!Ö®fÇÂOV@ÕurCóŽîŽ—›šš¢ÍÍÍ‹M_@݆I0Q1ÿdýá}Ç42yÏét³ZöŠª¨SÀ߬›íM´¼~s×7èÝ×Û£*êpRNNd2™¤®ë7ó€¶Eóÿé w_oGä¯È7.—K°,k\×õ¯Y›û›H8øæÁP,;ƒM £enkšV@öaÌïnÍܲ]WÂ,™W³Zö,̽ÚîyÞ¥:Q‡€w€ÇGæÕ ª®‰ûBÏ6Ì7ÔÄ‹{Ç÷ºEIEND®B`‚gaphor-0.17.2/gaphor/ui/pixmaps/initial-node.png000066400000000000000000000006251220151210700215130ustar00rootroot00000000000000‰PNG  IHDRàw=øbKGDÿÿÿ ½§“ pHYs  šœtIMEÔ 9sûI]tEXtCommentCreated with The GIMPïd%nùIDATxÚíÔ=JQÅñ_t›DP°bå´t%®"m·á6ÁÔ–v)­‰6A‘Ñ›$óõlsà”sþïÝwî°ÑF˜`?á/¸Ä[©á³,k\`Ž1²”ð²£pÒ2i9ùº›ÜÄM:kÑ#|å\`¯TG;Lx¯]œaØÒаóß &m¨P$„—}FñšÈ+Ík\Õ¨AÏX¨U£ØÐ®}ÃõºšjhÑ86´è>ÃÇ}Š“Åúßâó¨ÀãäÓº_Å äç8Å~TQ<è÷¸Ã¾ûVㆳÊ7e¾‡ ¥èš›n{·¼~4IEND®B`‚gaphor-0.17.2/gaphor/ui/pixmaps/initial-pseudo-state.png000066400000000000000000000006251220151210700232030ustar00rootroot00000000000000‰PNG  IHDRàw=øbKGDÿÿÿ ½§“ pHYs  šœtIMEÔ 9sûI]tEXtCommentCreated with The GIMPïd%nùIDATxÚíÔ=JQÅñ_t›DP°bå´t%®"m·á6ÁÔ–v)­‰6A‘Ñ›$óõlsà”sþïÝwî°ÑF˜`?á/¸Ä[©á³,k\`Ž1²”ð²£pÒ2i9ùº›ÜÄM:kÑ#|å\`¯TG;Lx¯]œaØÒаóß &m¨P$„—}FñšÈ+Ík\Õ¨AÏX¨U£ØÐ®}ÃõºšjhÑ86´è>ÃÇ}Š“Åúßâó¨ÀãäÓº_Å äç8Å~TQ<è÷¸Ã¾ûVㆳÊ7e¾‡ ¥èš›n{·¼~4IEND®B`‚gaphor-0.17.2/gaphor/ui/pixmaps/interaction.png000066400000000000000000000006211220151210700214520ustar00rootroot00000000000000‰PNG  IHDRàw=øbKGDùC» pHYs  ÒÝ~ütIMEÕ *‡Æ(IDATxœí–±JAEÏì +#ˆ ‹ˆˆ)$"iü?ÃÎ* ©D;±¶²³²ôKÒÁ…Y!dƒ ¸;c± ‹I4Y-ç6ÃÀ½÷Àc`ž “(À|žE”2w7@")•JVV×&&‡ƒ>s¥Rʉž¼´Ö¤iº´ —–9¿¹k¶ÖryrÈYë”juk*@£^# Ã}àÖÄOæ(ì=wð„7UyN>0¯¬1¼ãG¯q}Ñb³²A³„ˆû=®šc»;Û4ŽY(—‹²ãû>÷… ~Ó̃upp€QYX­5zíßZµÖ/À› Û tõ‚½O¹¬b ýõKþ¾¶$ß²xÿDÛHpLè<œIEND®B`‚gaphor-0.17.2/gaphor/ui/pixmaps/interaction.xcf000066400000000000000000000036331220151210700214540ustar00rootroot00000000000000gimp xcf file00BB / gimp-commentCreated with The GIMP¢ 0Ä00 New Layer#3ÿ     J00^00na¥¥¥¥¥¥¥maÂÂÂÂÂÂÂmaÑÑÑÑÑÑÑmaÿÿþþÿþð ÿüþñž ÿüò ÿûþîš ÿûþéŒn00 New Layerÿ     ¸00Ì00Ü   /ÿÿÿÿÿþýÿþèÿüþí“ÿüí“ÿûþê”ÿûýä‰ 00 New Layer#2ÿ     Ø00ì00üa+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+áaa+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+äaa+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æaa+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿa00 Backgroundÿ     k0000   gaphor-0.17.2/gaphor/ui/pixmaps/interface.png000066400000000000000000000013321220151210700210730ustar00rootroot00000000000000‰PNG  IHDRàw=øbKGDùC» pHYs ‰ ‰7ÉË­tIMEÔ !­SÉ„gIDATxœí”ÏOÒaÇ__Ch£l”9ÓVóÇFç¥}G]§–“t‹5ÿ€¶¬K³ ³‹ÞóJÜê­•ÓbŽ0\h*”“Íè;¾êÓAJ—AÚêæ{{ï¹|ö~=Ïçù<è@ÿE®úúz·N§s;÷ÈÈȹ<::ê°ÛíÏšššY–Å%YN§SÔÔÔ¤:::†#‘Hã_‡ûýþNY–³×º»Å‹—¯ÄlbNÌ/&ÅûØŒðùýÂf» úûû¿´¶¶ÖÚ}ÚÛÛÇ{zÜ"öaVÌ/&w90uuuÂn·?Κ½äðz½—'&&n{‡†$˱ã¿-¬2™¨4x ž"Ùl6¬ÿ  ðù|6›Í`µž,[ÜÙÕ…¢(G2™Œ¨ €¾„·áp˜Ãz’T`2%N“Ïçû€ ¬•°ÐT´´´Ï­±®ªe!™¯«X, …BPUÕIà# J”_Ƶn·; …r©T²ì ža4s’$m°ÕµŒ“ÐÖÖ6ÕÐpöâýÁ*+w…'—–¸ÞÓ^¯ÿ‹ÅÂÀ00l–ØÏ& þœgÇóðóç”zïîbѪZ@Q¾ñöÍ$·<7q¹\…D"± ÄÙºƒ5 WÂ@ìœeMooo_ NX­V­676ˆÇãªÙlNk4š•h4šO‹Aeõëc©®677ßXXX8¥(ŠT]]V«Í&“IX¦ÇÀ¥/¸$@S„œœÅU*%×@X¡tïË~H ÛÿNXe¯÷@ûÒwŸÉ½±)zIEND®B`‚gaphor-0.17.2/gaphor/ui/pixmaps/lifeline.png000066400000000000000000000007151220151210700207260ustar00rootroot00000000000000‰PNG  IHDRàw=øbKGDáäæ uj! pHYs  ÒÝ~ütIMEÕ  5tËZIDATxœÝÔÁJAÐ3íDÅ…hÀȈ¸|‚A®ÅÈÊ/ñÏt+„dpEN‹*G©´S³ñ£‹®î÷êÞ÷nÕô¢@µ9hÇx@)‘ @«Ñ˜ËÌÐlÀ2~ã e=QÀÖöÎ@æ³°†kœ£,ÊTiŒ‰êtÈÕüM ƒÁ;+Ð=EèŒÛP ´±»òmé3&ô6ýéÝ­`¤vâÿSÜ=í¥Œö ‹ñYt}óSø_¼¿DK0ØVƽDòVãÉöñS0ÓK혣sU¤zPâ_â=ÜǸq£W¦W©ÚLàMâ¤â ä:·ŽÉ¸¾¤¨D.ƒM¬ ¾äªD®ÑêøˆyŒëCAœ\È`ž+Ѩ Í †ªDn“?Á…>zË`+ø‹+}LQnð_6o"W¢QÌÄõ±!­æ™uê6íÁ#GqP!ZÍ IEND®B`‚gaphor-0.17.2/gaphor/ui/pixmaps/lifeline.xcf000066400000000000000000000023541220151210700207230ustar00rootroot00000000000000gimp xcf file00BB / gimp-commentCreated with The GIMPžÜ¸00 New Layer#4ÿ     F00Z00j   ×ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿîÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-----ÿ-ÿ-ÿ-ÿ00 New Layer#3ÿ     „00˜00¨iááááááááááááááááªiääääääääääääääääªiææææææææææææææææªiÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿª00 New Layer#2ÿ     h00|00Œ   ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿHgaphor-0.17.2/gaphor/ui/pixmaps/line.png000066400000000000000000000005251220151210700200650ustar00rootroot00000000000000‰PNG  IHDRàw=øbKGDÿÿÿ ½§“ pHYs  šœtIME×¥ø·otEXtCommentCreated with The GIMPïd%n¹IDATxÚíÔ1Aaàï½^p½( ïH:…à0®¡Óº€P sš­$ºýU¦ü“f2³»üñ#T¢.E>Å£l‘ -.Xb)PaŽ3¶hЉ÷ò÷ Ÿ —I¾À«êf’·Ë&œ§’Ï¢Ð]‰Xf¸aq‰X.AžË"2ß•pÞÄ´)ts¾*Qhû±¡©Î§Qè6#–Ï5ö8„xe°ý¸Š)‡«ú""\§9ÿã+Þ ³"f%ð ?IEND®B`‚gaphor-0.17.2/gaphor/ui/pixmaps/logo.png000066400000000000000000000144501220151210700201000ustar00rootroot00000000000000‰PNG  IHDR]d%ŠãfbKGDÿÿÿ ½§“ pHYs  šœtIMEÔ *J½WGtEXtCommentCreated with The GIMPïd%nŒIDATxÚíPSçšÇO‚Š¢‘ˆE“¨ˆÎH-m)« Û]¬-ÌV¯{é0k]—Ê(w™M¤ÝÑÖ»+³^É™u¼Ñì:7w1¹ãêÎÀ\¯?f«,A‹´:T;þ¬ ”«ñg©9ûGsÜ4ž÷Ç99 ùñ|fœi!'œ¼yÏ÷}ÞçyÞçaHxÉúÁÙÆ&ýÏži½¾>#ÿ³®ËWŒ¨×çåê<™= Ã0:mŽ{bjªÏ¼eCL¡Ä¥žµ»¯^7߸å5ä»÷[ëL0*ˆ.%6‡Ss÷Þ€ñ»þûœ¸Š%/Wç™7GçVMÍô€';wï3_¼ô•¹`Püóõ嫵Օ=0Bˆ.Á¢íü²Ûzã–×é¿¥ÎRùÞzýUvFv–Îøœ+§>ó¸BÅ–gMÙrÓ6s•F Ña­D‹¥†"ëÜÙ³XßøØµ_øÂEZ˜At9—¨–í‰3mÔ–maA¾›a~ôÕ ýž÷ûŠqKœótZ®^»i¬gí[áAmZOžm«ÅK7®©Ý±§áœ§ÓBk‰ÎÊÉv‹õòMúÞ¾{FڿË:ab—%+LÍëÀÒ@tEnyŸë®í5¬\.ŒgÚ¬ðÀÆ7õ¬ÝØÖÑÕ@²vá;ä@™î„Ö“gÛI‚»ÔPdm9b×É%¸ Ã0»¶×°ëËWky÷Žî«×Í0Ýb“mæ*wË»®|m©A¥òÁˆ º ¥ùh«g¡¨³T¾òµ¥†½¿údk$²¢g¿µÎ´jY ÖÝpã–×`s850åbó– %ú­0ˆ.Bpµœvá^“—«ó”®|»8ù³»¶×°kÊ–cý¶?1À”‹}«FÑ• ¸…ùnç½ÅÑLÙÚf®rã„7øô ºqÍáÔ´ut5,ܱÊØf®rÓøxÑ H9•ê,•¯xñ›cšžµßZg‚€ q/º›-u.RZOéÊ·‹cáØ[¯¿ÊÂô nE·žµI'ÂV-+±ÄʱÛ]ÛkØPkWÎB;€èF?®œ9¸‘°vÁ× @\ˆníŽ=ÄÓBEo,²ÄÚ}ïÚ^Ãæåê<0͈ѵ9œRƒÂ‚|w¬Ö²-^ü¦I¥ò©³T¾Eùó-0åâŽá`€p‰ù*c·ïôÏ.È›cÕûø˜u Ã0-Gì0ãâ…DH|K÷êµ›ØàS^®:6¢+;wï#!_´p>¤fÑ€cÆÃ$´èÞüÆKL±‚³ò@”P(•Šç0 @¸Ä¬O׿pj7Lj),ÈwŸ?•Ü_ ßÕxhè™æ»þû‚㕞žæËš6¥#Z‹O Ã0™“3œ9ê÷§d«§ÝÉ«=9ó•ìã~?7‘a8%Ãü òööéIBÑuùŠqçî}f9›¦k9m%í-œÏâ|Ÿ| á⥯é›ýƒÚÖ“gÛëYûV?*m1ÿ@ïÅûº}S ©XŸuR‹. “&¥&…¥ûôé÷ZœØÒúÕƒ›ÆÖzòl;8EG#ø´nGŸêY;ƒÛV仃³-‚ÔGOžhh¬6^Â^AŸ.Çq) F‘Â0 “:aB^®îSí¬WŽg¤§]ú‡Í^B¼—›·ìI>ÕgÚ¬6‡3ì ©Kµ:Kå[ñŽÁDÄ Ü Ë0 ‹{ßþAí±–Ó®zÖN `-È›c¥Yˆâübüš¶&Ä1zI¢Ë66é¯ÝøæÅ,ÊŸOUR‘öº¡¡gÄ­ÊÄÔÔ¤(®Óæ¸C­É¼\§èE)‘ïÀxãÜýƒÚÚ{h<×låÓvp–/ÊÒæaR_<|½}÷Œ$¡ïÔ¶ìR Ú+?å8†óDyxjæä¶>4 Ó¼aà»*&¹c;ɵÀ.ÑŠ[ÔhŸ×Pvm¯am§×H ­£«ÁæpbÓúÌ[6tØÎbR`88®Óùe·õ  æD^®Îs>ÄjvãÐfH½NÜäéÃ+,Èw“Ú㈹2„+¹s«?ÞõÛ®K_•ûý\ŠÐï¥Dù@íÖnÕ²‹”-5ÛØ¤?õ™‡èë–’:„Ê^X]ºüƒiS§ü1%Eù˜VliŸ…à¨åˆ]'už4mõàWŽ6V¤ïXÌgà 5çO¹¡Ÿ õ9ø¼c¯¯Ï˜žžæ ÝFC»d]Ôd)_[jÀ ØëbYtëY»dç|,æ nüåö?|}íÖòQ¿œë@J:i{Ë?˜ávù x)"†ݵµòýÌÉm›7~0®hn>ŽÜ©‘ž'e몼r¡ŸAìÜ©ØT+˜‘pþ”Kü»H,J¤{E¥¶I5”bÄFH—শÔëb ›Ã©)[Wå]²ÂÄk9í’úoÉ ÉèºÞxmáÖôô´Bw©Ñ_ÕÔLO¤—w•”®|»—¡Ñ?0¨Ý¹{Ÿ,©C##£SÆ¥¤< {‡QYѳÔP„\”<ÚEªSRºòíb9çMueEϪe%H×*ûª†JíŽ= Áb¼âƒdKݼeC‡Ð¸ŸótZpƪÙì‰3mV)F•è²Mz!¥/,ÈwKY!H×Åwï 劄ò©Y±òÙjªþæ«ÙÚ™&L,±)dAþK$+Þ1˜äJZ¯®¬è)ÑbçÒÅK_É–¯Ž[!˜Y9ÙÈ]ê8·ÔϸÔPdÄ!R‘~1y²™“3<(ñæÿ[ŽŠ‚{õÉV!=ÖrÚ…›ë¨f³ÇZN»ÄQDѵ9œšSŸy\BÖ ®(7ªU:ó–.f‘bÞ\íáŒô´A¹¬.‘(ÅIj{ß?0¨ Ç-Äã÷ûÇËuϸ1›»L²rçΞ±J|¸*b¬]šEA®î0‹òç[„ôÔg¬€¢vULjá%Š.ÊgFÚJ½.™‘å–»¿Yh!—±$#=íbæäŒ?Éeua~Ž."~mÒBN›Š#5uÂ]9ïY®9…«Æ)+7xÁùwîÞ倂œ‹5ï–Zœq»PÔ®*(S&|ÑEµ<_S¶»=”z¢é­®¬èÙo­3?åRý[S¶Ü´¦l¹IÌ °hQÏÚõVû/öìs”þKÃo*öÿǪúÿ äÁC®q Ã0Ó³TÝ‚bE8Ž-–HåWWWVô`¶ºœ··oI¸ctÔ?)Zßíü¶9œØÜeœC..˜ç–² Œåb]]Yѳ¦l¹IH@7[ê\¸EFÈ—ÝuùŠw•èîܽό €á¢ðR¯ †/'‡ƒ/I l3W¹·™«Üû­u¦–#vÝXtýµ9œšzÖnÜl©sUlªm_²ÂĽüµžvo=ý_ŸùÏw¿ø`dttrð®ybjjÏŒlõÔÖ<^v$˜SÁŸˆ“úÞ …ÂÏÈ\Äwª’v~“^`uèA–`äª÷@ ÎJ}n…küé@Ôu»¶×°BÏx×å+F7–e ¥üÐΤ\—h œî¡_¸B»s÷>sŦÚöÃÍÇ}ÇZN»º._,ùè÷ûS†~x–ùðáã×ü~ÿ ÑýèC“Ÿa˜áŒô´Ëñ>î3²³pcªg¡æ8NÉpÂùÌciõãÜ&ÑZüIsYz¤T`”™€ ¬‘>¯RèAÊ8ˆfàŒæ´™>ºH¢›•1 Ãæpjjwìi8Ü|ÜwâL±\ŸB¡ð§§¥=œ­yA5uÊEóÓ}hↇGT‘|h¢åŠèP0Is '–ˆôâ ¬‘2PµÀ©< •èòÉæ¨?€«o)gàŒfU{ôä‰&–'JzzZD|»¼Ø"ŽOrB‹^áëù®?/Yl~ãµ…593Ô¿?~\èëÆOy”(.…)Öj)à2hÜtÑ÷ûõ±<†¨ÀÃà3p5\@î'§PeÓH0©×‘¾Dœ'gmÐH€ósIµn5u‘>V¾€öüÜÙžÜ9Úß‹©ðÿüùp¶B¡ðs§ŒgJ–’Ÿñ4M4D£¢` ú)t—Oªá±Í\åÞ¹{Ÿ%Ô­Ê䄎 ¿xÀØÆ&½ÐªYXïÆùlP׉ œ ‘3#›8Ðñ²í•KpIA­¥ú¢†õ嫵¿=ðëŸíÚ^#ªª?Çq@:€X$ZQùÞ]—¯qyõ¨C"¨ë^ˆ.*o”´MŽT¾)MªK¬o[¢%¸y¹:ÏúòÕÚ=»>þÇêÊŠ^)Ç?êOw+ÂeU“te$ ]§ Vy!§ð9O§§ò¸ëÂIÑ1oÙÐAʈDûšXƒ$¸…ùnç½á8 äØNÐqju–ÊGÚí i꺟X6(g2Íñ8”`„#¼¸¤k¹Š™Ä"¤ãr•êmààj{ Ê$0 º8ÏODwJwÌMêu$hÎŒËYÌ$ÖÀ}¶¼\÷epÑüDlñ+Yñ>î¨S´«–•XpVnû…/§oQYXJ!wê”®ó'îxœÔŽ¡„£/„=­ÝzÖŽ­l&Wñd"[H8Öt$ •D$¥ àÈ}ð‚æ~ã\E«±(B HY>r•V”:î4Áð±BÊ)Z\}qÚÓ·XÑ­®¬èŠÀÑÖ¤\‡{?\kÒ™çhëÇ”Çj+I«·P%bFØ"ízc›ôc]ZR RËÏ¢g%ú­´=1Þ¼eC‡Pf©üî:)®”¿8TÔÃMS“‹X?¦úàÄS G!H­aÈâ\–O¤»”ôöÝ‹»Ø*pFÊT@ÎÄvœ¦:$µ1ê:©®T5 X^šlŠpŠ¯È tíÆ7q as85¸ƒ2‘îž0Ö²|Py¤r,Ö¸qǵò+pågq™ ¸À™ØrÔÇ>÷[ëLBJÚÒï·Ö™„&„”ÀM÷×±^Ô©–PHÇ«qQßÝ{9'¡Øž\aÁ1œÜoÙ~á —TK0QÀ~"hƉy^®Î#GÍh¹ŸM¡À)SUîVêá$Qgí…,M?mñâ73¤úwIÝ_ùû:Ü|ÜíàªâZ($_.CáÆ-¯AŽ-#ª[sDQ¼Üê=P[¾d±rƒŸ RKt9Ÿ”O”f+nßé}é„')S¿.ôgá4ØUŠýbKW¾]|xB¥ò‘êvJ½ç¶ò q¬å´k³¥Îi«—mlÒ—­«òÒX¸Ó*´¢L:šM¢vÇž†`ÁVo7¥ò§ÔÃ}ðqã­ÎRù’©c )îAÓÕ@Žq—³"ÃȗΚõ³ÔPd¥É8º®åˆ]'õ3Æu‘¶±Iê3‹6´ÔPd;{+ç„°9œšÛwzÍ4ÅwÔY*_‰¾p+í¶«l]•÷ÙĤ©ÆlMÙrÊêÍËÕyœö‹—%+L‚nãêw—×þýÆ3áŽy÷•ëV’àJ) os85‡› .@«–•Xp~?±ìܽÏ,ä_ çoÑT¥[j(²JYŒl§¦ýÂØ…Ô-7ÛØ¤o>Úê‘kþÅ2q_YŠf"M 6ǎωmlÒ÷öÝ3ÒV:+,Èw/ÊŸo+¨‰(f1±9œš»÷Œ7¿ñqÑW”Pò¿xñ›Ô ê½Öýü½ŸYªÿ¶=¨#ñ¸á‘‘i##£êÑÑÑ4ó– Ÿ‡;æR—dÅ©³T¾ïLrDäIâÎg ^u–Ê÷Ö믲4ÂNkXHÜzÖnlëèj •/]´p>k~â¤]«4Q¦«§y&MJ환šêz l§†ojèõõ½½}Ô)Vy¹:OÑ‹,RÔÚ{h…]¥ò…öf#¹;‚Ó]p¢+v· ð^Ã0Šòµ¥‹Í[6|~ð+å£M£¹& ÜðÞÛÞw|ß.yúýДiª)ßjg¾r¿ðñ“§³®ßºc ™¯RÅŠæÁ—kÇ$æ; Ç*%킟ôô4_è6Úëë3>zòDCcÐH±ÌÙÆ&}ç—ÝV±é•…ùîys¬ñzÂ0¡j¨J±z#…œƒäC“‚ÕFrg_ÛrÄ®#º|+ òµ¥fÞ²¡óà!×D†a†GFG'{{úÌ]—®T=|ôx:Çq ©óR¬5,¸bŠR·¼R¾Ëp¶×R‘pæQ¸nZÊ×–âQxªS@ueEóÀÞâòµ¥†±8ö©ÎRù–ЬëËWk÷[ëLrMˆýÖ:͉<<³çÛ»›¾¾vkÝ£ÇO²‡†~˜<ê÷§æg^®Î3oŽÎnp‹W1¥¥îbØÆ&½ØÃ(rí˜øÏyõÚM£Ôˆü˜‹izŠ³Â¥Öü7&¢%êY»qàþCýÓ§ßk¥X¼Ï”÷eNÎðŒE(ïcÆùÜòružÌŒŒžéêiÕÔLQÇ1ÙÆ&}°òu‹]¥R1ê÷s)Æ5ïþEí/7þOðïr)‡GFÔßÿÃÂ黿¾¾ñéÓ¡iχ‡'ŽKIf†yöüyÚk ü7£`8&ç÷c5ö‰Ûؤ|ðÈð]ÿ}ÎoË[È:mŽÆDWVý&"¦ÑåÓýÅû«V}\ów„’•qÉ>•D5ÂdŒŸó§ÂhÉ ´Ü¢º«âüÜx D¢gñœ@t Šö®Ñ@t@t] vÀÕlM„Vï¢ ÄÝW¯#ÛEº;-€èI©}NÿÀ ¶l]•—¦y'$"¾È‚˜z´·XGÄ¢W-7ùoxÅMfú«c‰=¾ñ‚ëZœaƒYavø8â¸D<á13»1²$ýØÓR ZõÄ“j¿Ç«Ììê=`‡/,NÑ3f¿ô*qŒ8¦yxÇ]Ÿ§>×1Çgfn{õö4@8&@¿SQÊ´ûIEND®B`‚gaphor-0.17.2/gaphor/ui/pixmaps/meta-class.png000066400000000000000000000005451220151210700211710ustar00rootroot00000000000000‰PNG  IHDRàw=øbKGDÿÿÿ ½§“ pHYs  šœtIMEÔ ²Üê tEXtCommentCreated with The GIMPïd%nÉIDATxÚíÕ= 1†á7² &ÑÆB ìì<÷ð&Þ@/±'ðŠXÚØ(bب,±VûÙŸi¿‡0D)¥‚¶$'uB]`¯ªµz˜'+Q`:s>—À¬DqÓâ"¾¸]¯â¡o™Zëˆî33z‘1†õv'ú‚á Oš¦ßräÏ|5}ÍŒ²,£ÓnŠe­sÀ怚úMˆ¼÷â5µÖæ€snŒ+8çÀ]e Ä€’üw€ýrÕP´ÖÎIEND®B`‚gaphor-0.17.2/gaphor/ui/pixmaps/node.png000066400000000000000000000036701220151210700200670ustar00rootroot00000000000000‰PNG  IHDR88¨†;bKGDáäæ uj! pHYs  ÒÝ~ütIMEÔ  CÆmEIDATxœíšÛO\ÇÇ?XØ5·ª6†LP+,µ‰¨yèC½ØQ›§ÖÅ&‰±¹$Û©-W²l©jÿ…¦ÔJq|K[Õ\ ‰ƒsóƒû–—¨±ÓÆ'XÌm¹³Ó‡9ÃK 1kïZ|¥£9ÌÌæs~ó›ßì™\Zˆ·/6Ò‹þÀ’ž/ô4LÏ[«AMV õ4ÀkjËœ®§_fs„áZ2ܯR¸HÓ¯SfzfðÈôOžÇµ Øl3ôuÊ(õvUÙ’å5¼-UoÐ üÐßÛzÆj"‘ý[ÄgI" •ŒC'üÂÅKân{{Äû¥e‘--wæÊËJ½ßö9ù ¬T+/+Mj4€ÔÔTÞ;{Žœ#G‰‰‰ ×ëÅétÒÜÔHmM5]Æâ×€~½ï³ÀÒ(Â8ÖÕ}8àMß³'âƒò?G<¸á¿ÿÝâ…‹—´®ÎGZSccPìÂvïÞÍÙsç9’›Ë¶mÛ'„`p`€††Ï¨©ªÂáè&::Ç£ªü辺`fŸZ–|ÿOáíó&¸t ÈÂ(|ç]~yøp@á\.wî4s«¶†žñññ<ôº±Ú÷8À†Ï5X9iD`ð¹{÷î-•—•z p)ȱžœœÌ¹ó¿%/ï8qqqƒ¡­µ…šê*º»ºˆÿ?ÿÅœ,.6VÔû¯æ”å‘iôÁEN”—•.^¸x €¦ÆÆD HésgÏ'çèÑ€YÎëõ2ìrÑÞÞFue%>$::šƒ‡QPXÄÞ}ûŒÕÃðkEˆ1ªá©âÂÅK@yYi"ð)ðc€Ä¤$N—p8'' pN§“–æfêêjyôð!6› {v6'ò xuï^,‹ñ‘5cªpE0//+À€z`/@BB§NŸá­cy›-…¸††hnj¤¦ª‡ÃÍfãgìä‘‘™IddäºÛó&”R€;èÃrÇŽ—œâí·»,«$„ÀívÓÚÚ­Úº»»‰‹‹ã€ÝΉü²ögµ¡6€jU IÀgèp)))”œ>Cnî›ÄÅÇoÎJ !¥½­•šª*º:;‰‰‰áà¡×),*bßkX­Ö ·kÔËž6tŸÛ™˜HÉ©ÓË;Nttôf°¬’×ëÅívÓÑÞFeÅM¾þú?Øl6Øíä’‘ù“ K£ÌC4 hB‡Û¾};ÅÅ%ä¾ùV@á†imm¡¶ºzΞMaÑ»ö9³Œ€»‘¡à€;wræ7gÉ;~"`>06ö„»w;¨ª¬Xž-Øí½s’ýY÷9³Ôô|üèi;ü4ŠÅžM~Aû³²þ¯Ï½œ²KÝ~‚ìÿçÀ¿0x•5ž3À¯ÿšœ#GÉÈÌ|jË)©uÛòϦŠ~¹) ¯Ws³3¿ñSŠO&55uÓàÀçƒþ´°¦¢¬¶åûôôôMoßïúíE’ùçÒ §-ÀP×`¨k 0ÔµêÚ VMMM­«^HNOOsûÓOŒYkîF¬UŒB011AcCW.¸¢ˆ5öC pb|œêê*þñ÷¿ÑÛÛ«²=Ȥ%ülÔ† àôô4õõu\»òÃÃÃ*Û|Ü2›Aî(-b°hHNMMÑpû6ׯ^1ÂM#·ËÜÈí²Q` ¹7:œœœäVM5ýøýýý*ÛƒnèœÀäÇ&¿;¼A%!ãccÔÖÖpýÚU†UÑ,p_Ó4· |ƒÜôìÆôòà·àÄø8•7ùøÆu³Ï}¥iÚ00€„ëÒÓ!½|ß™‚àôx<Ô××qãú5Ün·ÊžB·àB'„xl±Xº^ziW_oo?nü*¨,xõ£ËF8ð/dBôX­Öž¤¤¤þ§sWrònÑÐĪï»AeA—Ë¥n=À}dœÐ'ÇììlOooï0ÞãpO4™w§—T€ºæ;D*ô=5úœZ’áVAª3QHg~Þš cš Õ/ F"#¦#Ï ¥ëGêJê­{‘ˬQäÚÒ\¡ ãÜ$ösÖýó¯C$ökÓþïªD $öeÎýñ¦=$öeËüó¬?$ölÎüò¬B%÷hÑýó­A&ø9µüðªB'ù1šË™>(ú 3K)Û00 New Layer#4ÿ     ÿÿÿÿ 0000/‚ á# á# á# á# á# á# á# á# á# á# á# á# á# á# á# á# á# á# á# á# á# á# á# á# á# á# á# á# á# á# á# á# á# á# á# á# á# á# á# á# á# á‚ ä# ä# ä# ä# ä# ä# ä# ä# ä# ä# ä# ä# ä# ä# ä# ä# ä# ä# ä# ä# ä# ä# ä# ä# ä# ä# ä# ä# ä# ä# ä# ä# ä# ä# ä# ä# ä# ä# ä# ä# ä# ä‚ æ# æ# æ# æ# æ# æ# æ# æ# æ# æ# æ# æ# æ# æ# æ# æ# æ# æ# æ# æ# æ# æ# æ# æ# æ# æ# æ# æ# æ# æ# æ# æ# æ# æ# æ# æ# æ# æ# æ# æ# æ# æÂ‚ ÿ# ÿ# ÿ# ÿ# ÿ# ÿ# ÿ# ÿ# ÿ# ÿ# ÿ# ÿ# ÿ# ÿ# ÿ# ÿ# ÿ# ÿ# ÿ# ÿ# ÿ# ÿ# ÿ# ÿ# ÿ# ÿ# ÿ# ÿ# ÿþò" ÿýô—"ÿûþï˜""ÿüî‹!#ÿüô‘$ÿüò™%ÿüñ"&ÿûþíŒþ"ÿüî‡#ÿüó‘$ûÿî ý"üñˆü "ýû"þùÁ00 New Layer#3ÿ     ÿÿÿÿ ü0000 c)á)á)á)á)á)á)á)á)á)á)á)ábc)ä)ä)ä)ä)ä)ä)ä)ä)ä)ä)ä)äbc)æ)æ)æ)æ)æ)æ)æ)æ)æ)æ)æ)æbcø ü"—òÿÿùü!˜ôÿÿúü‹ïÿÿûû‘îþÿÿü"™ôÿÿüòÿÿ üŒñÿ ÿ ü‡íÿ!ÿû ‘îþÿ!ÿüóÿ#ÿüˆîÿ$ÿüñÿ%ÿb00 New Layer#6ÿ      00 00 (2¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥p2ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂp2ÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑp2ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿp00 New Layer#2ÿ      °00 Ä00 Ô¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥oÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂoÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑoÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿo00 New Layerÿ     j00~00Ž   öò õý$Øÿÿõý#ÞÿÿøýÎÿ ÿ÷ü Íýÿ ÿøý%Þÿ"ÿôØÿ#ÿüýÓÿ$ÿûýÇÿ%ÿþü Îýÿ%ÿøÙÿ'ÿùÌÿ(ÿýÓÿ)ÿýÎÿ*ÿüÒÿ+ÿýÕÿ,ÿþÿ-ÿþÿ-ÿþÿ-ÿþÿ-ÿþÿ-ÿþÿ-ÿþÿ-ÿþÿ-ÿþÿ-ÿþÿ-ÿþÿ-ÿþÿ-ÿþÿ-ÿþÿ-ÿþÿ-ÿþÿ-ÿþÿ-ÿþÿ,ÿýÒÿ+ÿüÎÿ*ÿûÓÿ)ÿúÌÿ(ÿùÙÿ&ÿ÷ýÎ ÿ&ÿýÇþÿ%ÿûÓýÿ$ÿõØÿ#ÿýÞ%ûÿ!ÿüýÍ úÿ!ÿûÎùÿ ÿýÞ#øÿÿýØ$õÿÿðÙ gaphor-0.17.2/gaphor/ui/pixmaps/object-node.png000066400000000000000000000004061220151210700213250ustar00rootroot00000000000000‰PNG  IHDRàw=øsBIT|dˆ pHYsvv}Õ‚ÌtEXtSoftwarewww.inkscape.org›î<ƒIDATH‰í•A A »íù†þÿM‚ø‹ašö²#zZdwuê”CH’&ÁHC«OÁJHF’÷(j[Iø"äËõ¦=çÓ$“„e=ý}$€_ò|^Àþ–Cë+¶‰Z+$¹µVw„$ƒe· ¶‹ïéêSÆ/‚Jü¿é89ÐDIÀ²¥”œÎ£CGCâ( €åHi|+EæPVÃ÷í3m­ TÀÖZ3ó'FCµÖoA–eƒ0 ×@Ïððwà)ŠÏ»taPð®/¸¸=ál‚(IEND®B`‚gaphor-0.17.2/gaphor/ui/pixmaps/partition.png000066400000000000000000000007531220151210700211520ustar00rootroot00000000000000‰PNG  IHDR``â˜w8sBIT|dˆ pHYs × ×B(›xtEXtSoftwarewww.inkscape.org›î<hIDATxœíÝÁIÄ@€Q#V!‚%Ø…X …Ø%²mŒWÍA’ø|ï¶° ÿÎÎ&ËãŠÎuýþ;bĈ  &@ìfö‚eYl>c,[®71bĦ׀µ·÷ËŸã4îïn¿¼^¯‰³k‚ ˆ  &@L€˜1b›÷?YÿnÞj½ï8úþG31bÄ_Öž_^§Þÿôøð§î¿7 &@L€˜1bĈ  &@L€˜1bĈ  &@L€˜1bĈ  &@L€˜1bĈ  &@L€˜1bĈ  &@L€˜1bÄ~ý¼ £Ïç©Ïÿ™ebĈ¾}çÙŸ_`bĈ  &@L€Ø2ûLyÏ’üžçˆŒ1bÓÿ­¿ã¬ Û˜€˜1bÓûöebĈ}¦²/§X;¼=IEND®B`‚gaphor-0.17.2/gaphor/ui/pixmaps/pointer.png000066400000000000000000000005231220151210700206140ustar00rootroot00000000000000‰PNG  IHDRÚ}\ˆbKGDÿÿÿ ½§“ pHYs  ÒÝ~ütIMEÔ:´à°–àIDATxœí”» Â0†¿C$#Ð$ =4t,Á0 ,À4dV¡¦!…)â+8/Ë‘(ø¥“ü¼ï|gþúy)- ÂQ%Xz‡”€Ñ &`Hàbx…4¼AÚ^ ]€.ÈĉZx­LkÌêë¦CŠHÕ‘¸¹6§HÍ4\,o ,€×éÈàj™ÞO ^.‡ˆ€p¤(äÜrе-ú¾ 4¤üMC àD‘¢/õ)rÜ̉•R©n'm›Ý"C™ˆ¬€îŸÌ¶Plƒ=ð¹÷ ü»»ü ²uûIEND®B`‚gaphor-0.17.2/gaphor/ui/pixmaps/pointer.xcf000066400000000000000000000026571220151210700206220ustar00rootroot00000000000000gimp xcf fileBBg+ÍColorÿ      -gÿüÿÿûÿÿþÿþÿþÿþÿþÿþÿþÿþÿþÿþÿ þÿþÿ þÿÿ øÿÿÿ÷ÿÿÿÿÿûÿÿûÿÿûÿÿÿ7gÿüÿÿûÿÿþÿþÿþÿþÿþÿþÿþÿþÿþÿþÿ þÿþÿ þÿÿ øÿÿÿ÷ÿÿÿÿÿûÿÿûÿÿûÿÿÿ7gÿüÿÿûÿÿþÿþÿþÿþÿþÿþÿþÿþÿþÿþÿ þÿþÿ þÿÿ øÿÿÿ÷ÿÿÿÿÿûÿÿûÿÿûÿÿÿ7gÿÿÿÿÿÿÿÿ ÿ ÿ ÿÿþÿÿÿÿÿÿÿ7  Drop-ShadowÌ     Ó ç ÷ØØØû""ú"°·?ù)Óö¿?ø*Ôþ÷¿?÷*Ôþþ÷¿?ö*Ôþÿþ÷¿?ð*Ôþÿÿþ÷¿?*Ôþÿÿöþ÷¿?*Ôþÿÿ÷þ÷¿?*Ôþÿÿøþ÷¸#*ÔþÿÿË÷ÛÔ±#*Ôþ÷â÷þâi1#*Ô÷¿iÛþ÷¸#"°·?*·÷þÛF""FÛþ÷¸#ù"·÷÷¸#ù?··?û"" New Layerÿ     {Ÿ@ÿ@ÿ@ÿ@ÿgaphor-0.17.2/gaphor/ui/pixmaps/profile.png000066400000000000000000000011251220151210700205730ustar00rootroot00000000000000‰PNG  IHDRàw=øbKGD¥ÂÑ"{ õ pHYs  ÒÝ~ütIMEÔ½†ØðâIDATxœÕ•KOQ†Ÿ3©S5Ú45К¸qÍ€Fjb5B\”%†´é†Ò…PtQÓ…ÅËBåRIºéªÈÅ †+·À?¨™dÒE[H´™²(N†±41<É$ïÉ|ßûÎ9srŽBÔÚÚ´BµZå \Z†€€fVkkkw°»·ßRÀõkž.`x˜ÕI-;ÿæ"àhæñ/§âü Ûí5à,l²,“ÞüféGú{(—JC@N²Ë²¥æÇžwÇYþ ÿg-gæOÔÍÞ*`k)ËöZΠl¯åØZÊ´¶?;«,gxö.ÍÎÆ*Ÿ3óL¾Mëb¯>ðüÉc.]¾B,õ‘cuÝíó7ŸÁ\rš`8Š»ÃÃ\ršÁðîN¯®ÁÝée0<Ál2Ž»ÃC0e67. 4>ÅâL¥¯ë×/Q y]ƒRȳ8“`x<ŽRÈó)U×fè–¨ÇwÃJ…Dd„Tö‹N7HDFxð(D·ÏO$p—¾`Èty ½ý+eƒnpóþCn÷ Ú ár¹jor_ÿZÔ*Oûo¡ªê =ÿǵ$©æt:-5-‹hš¶Dmš¦ÝPU5\µ4à— ~y{ €°0à'ðý…,µYÏ6OÉIEND®B`‚gaphor-0.17.2/gaphor/ui/pixmaps/pseudostate.png000066400000000000000000000006251220151210700214770ustar00rootroot00000000000000‰PNG  IHDRàw=øbKGDÿÿÿ ½§“ pHYs  šœtIMEÔ 9sûI]tEXtCommentCreated with The GIMPïd%nùIDATxÚíÔ=JQÅñ_t›DP°bå´t%®"m·á6ÁÔ–v)­‰6A‘Ñ›$óõlsà”sþïÝwî°ÑF˜`?á/¸Ä[©á³,k\`Ž1²”ð²£pÒ2i9ùº›ÜÄM:kÑ#|å\`¯TG;Lx¯]œaØÒаóß &m¨P$„—}FñšÈ+Ík\Õ¨AÏX¨U£ØÐ®}ÃõºšjhÑ86´è>ÃÇ}Š“Åúßâó¨ÀãäÓº_Å äç8Å~TQ<è÷¸Ã¾ûVㆳÊ7e¾‡ ¥èš›n{·¼~4IEND®B`‚gaphor-0.17.2/gaphor/ui/pixmaps/send-signal-action.png000066400000000000000000000016041220151210700226140ustar00rootroot00000000000000‰PNG  IHDRÚ}\ˆsRGB®ÎébKGDùC» pHYs  šœtIMEÛ{detEXtCommentCreated with The GIMPïd%nÛIDATHÇÝ•KH”QÇçÎ Ó€ ùhT&jg`­Ëví*ˆ ZT´¯(=¤E›ÀÉ…»ö¡EE`Ð+ z!è:Aj‚6£Îãžß÷ÍÜQ [¸éÂpÏpÏ=ÿÿädGGP¼PõO—½RòUe]]}¡óìéÜjÊèXjùröí?Ð3=ý³²P°Æ£ÿTU׌ §“É‘.`ðýîÚ½‡ht Yï"ꊜ¡oÏÍÍQ[[;ÿúÕ«—ÉäÈQ` 0€-&ˆÇ7èó/½K*K”xX”@=.T ;‘ÈÜ»{çYj||ïèXʶÄ%HbB!C.—CÕ ©?Êmñþ£pìø‰è–¶­í‘HäjK¬Q}>À¨*ÖÚ¿KmyPÀZKEEçÎ_X»qÓ¦ÃÀÁ–XcÁoã†Ñgð¹AŒ  ±¦gÎvÖÇã.mc1øpË‎-Rî‹*"‚ªE€ööæÐ‘#ñªªêk@  Æ‘\)¢ÛŠ–¡Ræ+bÊ}ª*+àëF€0ˆ×j±uý>ZR•ji¢EPŸÃƒƒöÖ­›¿fff&€ ?¯·\7Zªu*öà±…¡PˆL&Õ˗2#ÃÃS@¿?ðT$82´ŠµêËPý$žíîÖú݉®ì§ÓÀ“êêêûÀ€1ƉDJªp†Ë³%°@<8‹»ZÄõõÚ§OϧÓéÏÀƒmÛ·&3<99Å»·o¨©©evv¶…,y \Ë;·Z •ún»]™d2™ú£Ñèû‡}}…€$imÝœnjn‡B!±Öz/²Cd‘XŸ1"ÙlV¾~ùšýömôÐ ôŸ\‰„Î íÏçsu"’_á+Š1†ìBVR©ñY`¸½8xPb Ø Ôy÷%\Á2ÀB8Èçóo–ÿ¢ê0Å ¦IÙ¤ÜvÖë ˽~Ñrï~¨IEND®B`‚gaphor-0.17.2/gaphor/ui/pixmaps/signalactions.xcf000066400000000000000000000047611220151210700217760ustar00rootroot00000000000000gimp xcf file40BBK gimp-commentCreated with The GIMPgimp-image-grid(style solid) (fgcolor (color-rgba 0.000000 0.000000 0.000000 1.000000)) (bgcolor (color-rgba 1.000000 1.000000 1.000000 1.000000)) (xspacing 10.000000) (yspacing 10.000000) (spacing-unit inches) (xoffset 0.000000) (yoffset 0.000000) (offset-unit inches) ¾ÕÀ00 New Layer#2ÿ     f00z00Š   }ý,ü‡ÿa*ü`ÿ›*ûBùÀ)û$ìÜ)ûÛð-)û½üM*üžÿu*üxÿ *ûPþÅ)û7õá)ûãô-)û ÒþN)û±ÿx*ûŒÿ¤*übÿn,ü›ù=+ûËè+ûëÇ+üFü›,üzÿd+û±ù8+ûÚã+û-õÂ+üZÿ“,ü‘ÿ`+ûÄö3+ûçá+û>û½+üg»S00 New Layer#1ÿ     '00;00K   €.ÿþéÿ,ÿûæÿÿ,ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-.ÿþ6ÿ.ÿ00 New Layerÿ     ƒ00—00§€pá€pä€pæ€]ÿýAÿ+ÿü]ÿ*ÿû¿ÿ)ÿýæ&*ÿþ"(ÿýæ7'ÿý¬'ÿþ &ÿþûÿ%ÿþ þ$ÿýÏ#ÿý¿#ÿþ0 #ÿþ #ÿþsþ#ÿüæfûÿ$ÿý˜üÿ%ÿþ\&ÿýæ'ÿþz'ÿýæJ(ÿýæ.)ÿý¿þÿ)ÿþ\*ÿýæE,ÿü@ÿ+ÿü¿'ÿ-ÿþ¿4! Drop ShadowÌ     h4!|4!Œ´´´ü $    ÷ 3CF#FGFGöE>-3…±¸#¸¹¸õµ¦‡Y  D±ìõ#õöõôóã³o8 F¸õÿ&ÿóþôÉy1 F¸õÿ%ÿòþøÛ•@ F¸õÿ%ÿùúã¨Vû F¸õÿ#ÿ÷þøä¯`"û F¸õÿ"ÿ÷þõÚ§b& û F¸õÿ"ÿøúÝ›T" û F¸õÿ!ÿöþîµZû F¸õÿ ÿõþøÔ-û F¸õÿÿôþ÷ߢLû F¸õÿÿøþöÙ Xû F¸õÿÿøüã£Tû F¸õÿÿùùÎq$û F¸õÿÿúøÉf û F¸õÿÿùú؇8û F¸õÿÿøýí¾y:ú F¸õÿÿøúêÄ7 û F¸õÿ ÿøüîÂp&û F¸õÿ!ÿùüç¨Nû F¸õÿ"ÿù÷Ò‚3 û F¸õÿ"ÿøýì¹j'û F¸õÿ#ÿùúä¬Zû F¸õÿ#ÿøþøÜ–@û F¸õÿ$ÿøþôÉw/ û F¸õÿ%ÿòüê·l+  F¸õÿ&ÿóúæ²d&  D²íö&öóõðØŸ^.3†²¹'¹ô¸±œyJ3DG(GöFC:&  + ügaphor-0.17.2/gaphor/ui/pixmaps/state.png000066400000000000000000000006761220151210700202650ustar00rootroot00000000000000‰PNG  IHDRàw=øsBIT|dˆ pHYsvv}Õ‚ÌtEXtSoftwarewww.inkscape.org›î<;IDATH‰í•±JA†¿ ½ÂBA>ƒ…á:A°,D±Òˆ>@°: A$ \ÒFìò‚’D8NH±I“ŒM²wZ܆–æÿwgþÝQ"Bž6‘+úw " Ëä  ˆ…÷‡ùnŒ)"¨¸J©fqs»°µ³ÇìÜ|æƒFaÀÝõ%uÿöX‘gÀ «ûSÓŽ])€^W³ë®‹È$z0x"&^½®‹à³|ƒ â•‰ÂÀ < *^9Q‘r¾òRŒ;9Jæ_°^ÜÀq²÷BkÍcýÃ}cßPÑkë-3pÒ– '"r á«ø'øY­ZÕGk»ïBkM­êtŒ@b¼ì^ïh7ÀQj(¥–5`P  <¥æA^öûUô$¼\¼ ƒWIEND®B`‚gaphor-0.17.2/gaphor/ui/pixmaps/stereotype.png000066400000000000000000000011731220151210700213410ustar00rootroot00000000000000‰PNG  IHDRàw=øbKGDùC» pHYs  šœtIMEÔ $í³›tEXtCommentCreated with The GIMPïd%nßIDATxÚ픿kaÇ?R°wE”#Ò&‚‹‹ƒnRÍ`ýÑâÉ!5diÚAMë’ÁÔà6ÖB–L©±u(E2ô®7Eúri%œCLú—Òå-8øƒïÃÝóýò¼÷Üiš¦yýÆ*qÅ6žç¶´£ÇŽ{oku¥n]â×Ï%`.ÄáqèëP+—zêƒîõ"°±R¡¾V èõµ*+•€Þ°\l®¯R+äÉ›%6×Wù\.ñôõ’¯aúÅ{fÝ£_`ºøçÛz8>Ò{‚Ýn±X˜!™ÎŒ²X˜a<=Ed(ækˆ ÅOO±PÈŒ’LgY(ä|ÏÈž! ¯[¤&Ÿ±<ŸÇ±­¶~9‡c[¾fǶXžÏs2‡c[|*¶µŒìÖu½[\ŒßÀ‚|f‚bå«OwÈg&¸}7Åp|„Lâ:£ÉTàxt]ÇuÝà;¸6–ÀÛÝáòÍ;\Mô~h¦iz¯ªß”~Ç®Ðh4¾YLÓô¥×_Ïp& ðݲ•NpþÜÙ½-jJ+¥ Ù3Ôjµ”Èž!CZSUÈž‡ù7ýð„›Í&§NFÕn‘aì!ÞCU€À~kÀ ôšÂAv­?Á Å£{ cIEND®B`‚gaphor-0.17.2/gaphor/ui/pixmaps/subsystem.png000066400000000000000000000007421220151210700211750ustar00rootroot00000000000000‰PNG  IHDR``â˜w8sBIT|dˆ pHYs × ×B(›xtEXtSoftwarewww.inkscape.org›î<_IDATxœíÝÁIÃ`€a#N!‚#¸ƒ8 ƒ¸#Ò5~¯ö?!iߟçÖCHàåË×ôÐ,cŒ;:÷õüwĈ  &@L€ØÃÚ–eñàðËcÙr¼ ˆ  ¶z̾¾O{\ÇaÅ™·×—«žo+ &@L€˜1bÄ.þp´ïå×fbĈí¾ný÷÷[cbĈ  &@L€˜1bĈ  &@L€˜1bĈ  &@L€˜1bĈ  &@L€˜1bĈ  &@L€˜1bĈ  &@L€˜1bĈ-kß)ï]’ó±ƒ &@lõÿ†Î÷8;a &@lõsû21bÄ~Í›&5_o¶IEND®B`‚gaphor-0.17.2/gaphor/ui/pixmaps/transition.png000066400000000000000000000004121220151210700213230ustar00rootroot00000000000000‰PNG  IHDRàw=øsBIT|dˆ pHYs × ×B(›xtEXtSoftwarewww.inkscape.org›î<‡IDATH‰í•1 ƒPDß| ¤‰…w°6gð@¹ONû¤Ó‹XŽ… ’_D>$° Ë3ìƒÙbe›’•Šn@~ ©ÏlÝ@ LÀ¸¼õ¬€¸¾ê²¤xH* pn¶ï›PíLç-Ö3§¥uùÞ»TéˆþâÈ}NWüƒ€ÁJX-¾2IEND®B`‚gaphor-0.17.2/gaphor/ui/pixmaps/uml_icons.xcf000066400000000000000000000122041220151210700211170ustar00rootroot00000000000000gimp xcf file00BBg/ gimp-commentCreated with The GIMP«UnnamedHAÆ?@AÆ?@@ÈA²@ÈA²@ÈA²A’A°A’A°A’A°A’B8A’B8A’B8AôB9AôB9AôB9AôA²AôA²AôA²B+A°B+A°B+A°AÆ?@noteA¬ÁpA¬ÁpBhA¬BhA¬BhA¬B„ÁàB„ÁàB„ÁàS gimp-commentCreated with The GIMPgimp-image-grid(style intersections) (fgcolor (color-rgba 0.000000 0.000000 0.000000 1.000000)) (bgcolor (color-rgba 1.000000 1.000000 1.000000 1.000000)) (xspacing 10.000000) (yspacing 10.000000) (spacing-unit inches) (xoffset 0.000000) (yoffset 0.000000) (offset-unit inches) Èä¥ L ö ÌÞð°00note#3ÿ     9k0000   !þÿ-þÿ-þÿ-þÿ-þÿ-þÿ-þÿ-þÿ-þÿ-þÿ-þÿ-þÿ-þÿ- ÿ`00note#2ÿ     B‡00›00«#ýOá-ýOá-ýOá-ýKá-ýOá-ýOá-ýOá-ýOá-ýOá-ýOá-ýOá-þO¿#ýPä-ýPä-ýPä-ýLä-ýPä-ýPä-ýPä-ýPä-ýPä-ýPä-ýPä-þP¿#ýQæ-ýQæ-ýQæ-ýLæ-ýQæ-ýQæ-ýQæ-ýQæ-ýQæ-ýQæ-ýQæ-þQ¿#ÿýæ$ÿýæÿ ú<ûÿæÿ!ú<ûÿéÿ"ú<ýÿæÿ#ú<ýÿæÿ$ú<ûÿæÿ%ú<ûÿæÿ&ö<ûÿæÿÿ&÷<ûÿæÿÿ'ø<ûÿæÿÿ(ù<ûÿæÿÿ)þ<ÿÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+aÿ00note#1ÿ     @H00\00l"¥,¥+¥*¥)¥(¥'¥& ¥% ¥$ ¥# ¥" ¥" ¥"Â,Â+Â*Â)Â(Â'Â& Â% Â$ Â# Â" Â" Â"Ñ,Ñ+Ñ*Ñ)Ñ(Ñ'Ñ& Ñ% Ñ$ Ñ# Ñ" Ñ" Ñ"ÿ,ÿ+ÿ*ÿ)ÿ(ÿ'ÿ& ÿ% ÿ$ ÿ# ÿ" ÿ" ÿ00noteÿ     < í00 00 %á &á'á(á)á*á+á,á-á.áþáá%ä &ä'ä(ä)ä*ä+ä,ä-ä.äþää%æ &æ'æ(æ)æ*æ+æ,æ-æ.æþææ#ÿýÃ$ÿýÃ%ÿýÃ&ÿýÇ'ÿýÃ(ÿýÃ)ÿýÃ*ÿýÃ+ÿûÃÿ+ÿüÃÿ,ÿýÃÿ-ÿþÃÿ¿ÿ00packageÿ      š00 ®00 ¾ ¥` Â` Ñ`aÿÿÿÿÿÿÿÿÿÿÿÿr00 package#1ÿ      °00 Ä00 ÔÒ+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á’Ò+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä’Ò+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ’Ò+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ’00 package#2ÿ      r00†00–   ÿÿÿÿÿÿÿÿÿÿÿÿÿÿ`ÿ00classÿ     €00”00¤ ¥` Â` Ñ`a+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿb00class#1ÿ     œ00°00ÀÒ+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á’Ò+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä’Ò+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ’Ò+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿc+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ’00class#2ÿ     T00h00x   gaphor-0.17.2/gaphor/ui/pixmaps/use-case.png000066400000000000000000000011741220151210700206440ustar00rootroot00000000000000‰PNG  IHDRàw=øbKGDáäæ uj! pHYs  šœtIMEÔ  û<tEXtCommentCreated with The GIMPïd%nàIDATxÚí”ßKSaÆ?g;mc¸íºؠÚq)]„fDB+«»ºHýB$þŠhˆôäÕmE.Ø ]5CVÑQØ.ܹ8a[;s`îí¢#HˆÙEО»÷}áùÀ÷û%s' % name) expander.add(page) expander.show_all() expander.set_expanded(self._expanded_pages.get(name, False)) expander.connect_after('activate', self.on_expand, name) self.vbox.pack_start(expander, expand=False) page.show_all() except Exception, e: log.error('Could not construct property page for ' + name, exc_info=True) def clear_pages(self): """ Remove all tabs from the notebook. """ for page in self.vbox.get_children(): page.destroy() def on_expand(self, widget, name): self._expanded_pages[name] = widget.get_expanded() @component.adapter(IDiagramSelectionChange) def _selection_change(self, event=None): """ Called when a diagram item receives focus. This reloads all tabs based on the current selection. """ item = event and event.focused_item if item is self._current_item: return self._current_item = item self.clear_pages() if item is None: label = gtk.Label() label.set_markup('No item selected') self.vbox.pack_start(label, expand=False, padding=10) label.show() return self.create_pages(item) @component.adapter(IAssociationChangeEvent) def _element_changed(self, event): element = event.element if event.property is Presentation.subject: if element is self._current_item: self.clear_pages() self.create_pages(self._current_item) #@component.adapter(Presentation, IElementCreateEvent) def _new_item_on_diagram(self, item, event): if self.notebook.get_n_pages() > 0: self.select_tab(self._default_tab) page = self.notebook.get_nth_page(self.notebook.get_current_page()) default = page.get_data('default') if default: default.grab_focus() # vim:sw=4:et:ai gaphor-0.17.2/gaphor/ui/questiondialog.py000066400000000000000000000023611220151210700203500ustar00rootroot00000000000000 """Defines a QuestionDialog class used to get a yes or no answer from the user. """ import gtk class QuestionDialog(object): """A dialog that displays a GTK MessageDialog to get a yes or no answer from the user.""" def __init__(self, question, parent=None): """Create the QuestionDialog. The question parameter is a question string to ask the user. The parent parameter is the parent window of the dialog.""" self.dialog = gtk.MessageDialog(parent,\ gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,\ gtk.MESSAGE_QUESTION,\ gtk.BUTTONS_YES_NO,\ question) def get_answer(self): """Return answer to the question by running the dialog. The answer is accessed via the answer attribute.""" answer = self.dialog.run() if answer == gtk.RESPONSE_YES: return True return False def destroy(self): """Destroy the GTK dialog.""" self.dialog.destroy() answer = property(get_answer) gaphor-0.17.2/gaphor/ui/statuswindow.py000066400000000000000000000073251220151210700201010ustar00rootroot00000000000000 """Defines a status window class for displaying the progress of a queue.""" import gobject import pango import gtk from gaphor.misc.gidlethread import QueueEmpty class StatusWindow(object): """Create a borderless window on the parent, usually the main window, with a label and a progress bar. The progress bar is updated as the queue is updated.""" def __init__(self, title, message, parent=None, queue=None, display=True): """Create the status window. The title parameter is the title of the window. The message parameter is a string displayed near the progress bar to indicate what is happening. The parent parameter is the parent window to display the window in. The queue parameter is a queue that is used to update the progress bar. The display parameter will display the window if true. This is the default.""" self.title = title self.message = message self.parent = parent self.queue = queue self.init_window() if display: self.display() def init_window(self): """Create the window GUI component. This will set the window and progress bar attributes so they can be referenced later.""" frame = gtk.Frame() vbox = gtk.VBox(spacing=12) label = gtk.Label(self.message) self.progress_bar = gtk.ProgressBar() self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.set_title(self.title) self.window.set_position(gtk.WIN_POS_CENTER_ON_PARENT) self.window.set_transient_for(self.parent) self.window.set_modal(True) self.window.set_resizable(False) self.window.set_decorated(False) self.window.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_SPLASHSCREEN) self.window.add(frame) self.progress_bar.set_size_request(400, -1) frame.set_shadow_type(gtk.SHADOW_IN) frame.add(vbox) label.set_ellipsize(pango.ELLIPSIZE_MIDDLE) vbox.set_border_width(12) vbox.pack_start(label) vbox.pack_start(self.progress_bar, expand=False, fill=False, padding=0) def display(self): """Display the status window. If a queue has been supplied to the StatusWindow instance, a new gobject idle handle is created. Once the window is destroyed, this handler is removed.""" self.window.show_all() if self.queue: self.idle_id = gobject.idle_add(progress_idle_handler,\ self.progress_bar,\ self.queue,\ priority=gobject.PRIORITY_LOW) self.window.connect('destroy', remove_idle_handler, self.idle_id) def destroy(self): """Destroy the status window. This will also remove the gobject handler.""" self.window.destroy() def progress_idle_handler(progress_bar, queue): """This is a gobject idle handler that updates the supplied progress bar. The percentage is retrieved from the queue until it is empty. The progress bar is then updated with the current percentage.""" percentage = 0 try: while True: percentage = queue.get() except QueueEmpty: pass if percentage: progress_bar.set_fraction(min(percentage / 100.0, 100.0)) return True def remove_idle_handler(window, idle_id): """This removes the supplied gobject idle id. This handle is required by StatusWindow when it is destroyed.""" gobject.source_remove(idle_id) gaphor-0.17.2/gaphor/ui/stock.py000066400000000000000000000101351220151210700164420ustar00rootroot00000000000000"""Icons that are used by Gaphor. """ import os.path import pkg_resources from xml.sax import handler import gtk from gaphor import UML from gaphor.storage.parser import ParserException XMLNS='http://gaphor.sourceforge.net/gaphor/stock-icons' _icon_factory = gtk.IconFactory() _icon_factory.add_default() _uml_to_stock_id_map = { } def get_stock_id(element, option=None): global _uml_to_stock_id_map if issubclass(element, UML.Element): t = element, option if t in _uml_to_stock_id_map: return _uml_to_stock_id_map[t] else: log.warning('Stock id for %s (%s) not found' % (element, option)) return None #STOCK_POINTER def add_stock_icon(id, icon_dir, icon_files, uml_class=None, option=None): global _uml_to_stock_id_map global _icon_factory set = gtk.IconSet() for icon in icon_files: source = gtk.IconSource() if icon.find('16') != -1: source.set_size(gtk.ICON_SIZE_MENU) elif icon.find('24') != -1: source.set_size(gtk.ICON_SIZE_SMALL_TOOLBAR) elif icon.find('48') != -1: source.set_size(gtk.ICON_SIZE_LARGE_TOOLBAR) source.set_filename(os.path.join(icon_dir, icon)) set.add_source(source) _icon_factory.add(id, set) if uml_class: _uml_to_stock_id_map[(uml_class, option)] = id class StockIconLoader(handler.ContentHandler): """Load stock icons from an xml file in the icons directory. """ def __init__(self, icon_dir): handler.ContentHandler.__init__(self) self.icon_dir = icon_dir def endDTD(self): pass def startDocument(self): """Start of document: all our attributes are initialized. """ self.id = '' self.files = [] self.data = '' self.element = None self.option = None def endDocument(self): pass def startElement(self, name, attrs): self.data = '' # A new icon is found if name == 'icon': self.id = attrs['id'] self.files = [] self.element = None elif name not in ('element', 'option', 'file', 'stock-icons'): raise ParserException, 'Invalid XML: tag <%s> not known' % name def endElement(self, name): if name == 'icon': assert self.id assert self.files add_stock_icon(self.id, self.icon_dir, self.files, self.element, self.option) self.option = None elif name == 'element': try: self.element = getattr(UML, self.data) except: raise ParserException, 'No element found with name %s' % self.data elif name == 'option': self.option = self.data elif name == 'file': self.files.append(self.data) elif name == 'stock-icons': pass def startElementNS(self, name, qname, attrs): if not name[0] or name[0] == XMLNS: a = { } for key, val in attrs.items(): a[key[1]] = val self.startElement(name[1], a) def endElementNS(self, name, qname): if not name[0] or name[0] == XMLNS: self.endElement(name[1]) def characters(self, content): """Read characters.""" self.data = self.data + content def load_stock_icons(): """Load stock icon definitions from the DataDir location (usually /usr/local/share/gaphor). """ from xml.sax import make_parser parser = make_parser() icon_dir = os.path.abspath(pkg_resources.resource_filename('gaphor.ui', 'pixmaps')) log.info('Icon dir: %s' % icon_dir) #icon_dir = 'gaphor/data/pixmaps' loader = StockIconLoader(icon_dir) parser.setFeature(handler.feature_namespaces, 1) parser.setContentHandler(loader) filename = pkg_resources.resource_filename('gaphor.ui', 'icons.xml') # Make the filename a full URL filename = 'file:' + filename.replace('\\\\', '/') #try: parser.parse(filename) #except IOError, e: # log.error('Unable to load icons', exc_info=True) #load_stock_icons() # vim:sw=4:et gaphor-0.17.2/gaphor/ui/tests/000077500000000000000000000000001220151210700161075ustar00rootroot00000000000000gaphor-0.17.2/gaphor/ui/tests/__init__.py000066400000000000000000000000151220151210700202140ustar00rootroot00000000000000# unit tests gaphor-0.17.2/gaphor/ui/tests/test_consolewindow.py000066400000000000000000000007001220151210700224070ustar00rootroot00000000000000 from gaphor.ui.consolewindow import ConsoleWindow from gaphor.tests.testcase import TestCase class ConsoleWindowTestCase(TestCase): services = TestCase.services + ['main_window', 'ui_manager', 'action_manager', 'properties'] def test1(self): import gtk window = ConsoleWindow() assert len(window.action_group.list_actions()) == 2, window.action_group.list_actions() window.open() window.close() gaphor-0.17.2/gaphor/ui/tests/test_diagramtab.py000066400000000000000000000032311220151210700216120ustar00rootroot00000000000000 import unittest from gaphor import UML from gaphor.application import Application from gaphor.ui.diagramtab import DiagramTab from gaphor.ui.mainwindow import MainWindow class DiagramTabTestCase(unittest.TestCase): def setUp(self): Application.init(services=['element_factory', 'main_window', 'ui_manager', 'action_manager', 'properties', 'element_dispatcher']) main_window = Application.get_service('main_window') main_window.open() element_factory = Application.get_service('element_factory') self.element_factory = element_factory self.diagram = element_factory.create(UML.Diagram) self.tab = main_window.show_diagram(self.diagram) self.assertEquals(self.tab.diagram, self.diagram) self.assertEquals(self.tab.view.canvas, self.diagram.canvas) self.assertEquals(len(element_factory.lselect()), 1) def tearDown(self): self.tab.close() del self.tab self.diagram.unlink() del self.diagram Application.shutdown() #assert len(self.element_factory.lselect()) == 0 def test_creation(self): pass def test_placement(self): tab = self.tab diagram = self.diagram from gaphas import Element from gaphas.examples import Box box = Box() diagram.canvas.add(box) diagram.canvas.update_now() tab.view.request_update([box]) from gaphor.diagram.comment import CommentItem comment = self.diagram.create(CommentItem, subject=self.element_factory.create(UML.Comment)) self.assertEquals(len(self.element_factory.lselect()), 2) # vim:sw=4:et:ai gaphor-0.17.2/gaphor/ui/tests/test_diagramtoolbox.py000066400000000000000000000116451220151210700225420ustar00rootroot00000000000000 import gtk from gaphor.tests.testcase import TestCase from gaphor.application import Application from gaphor.ui.diagramtab import DiagramTab from gaphor.ui.diagramtoolbox import DiagramToolbox, TOOLBOX_ACTIONS from gaphor import UML class WindowOwner(object): """ Placeholder object for a MainWindow. Should provide just enough methods to make the tests work. """ def remove_tab(self, other): pass class DiagramToolboxTestCase(TestCase): services = ['element_factory', 'properties', 'element_dispatcher'] def setUp(self): TestCase.setUp(self) diagram = self.diagram tab = DiagramTab(WindowOwner()) tab.diagram = diagram tab.construct() self.tab = tab def tearDown(self): TestCase.tearDown(self) def test_toolbox_actions_shortcut_unique(self): shortcuts = {} for category, items in TOOLBOX_ACTIONS: for action_name, label, stock_id, shortcut in items: try: shortcuts[shortcut].append(action_name) except KeyError: shortcuts[shortcut] = [action_name] for key, val in shortcuts.items(): if key is not None: self.assertEqual(len(val), 1, 'Duplicate toolbox shortcut') def test_standalone_construct_with_diagram(self): pass # is setUp() def _test_placement_action(self, action): self.tab.toolbox.action_group.get_action(action).activate() assert self.tab.view.tool # Ensure the factory is working self.tab.view.tool._factory() self.diagram.canvas.update() def test_placement_pointer(self): self.tab.toolbox.action_group.get_action('toolbox-pointer').activate() def test_placement_comment(self): self._test_placement_action('toolbox-comment') def test_placement_comment_line(self): self._test_placement_action('toolbox-comment-line') # Classes: def test_placement_class(self): self._test_placement_action('toolbox-class') def test_placement_interface(self): self._test_placement_action('toolbox-interface') def test_placement_package(self): self._test_placement_action('toolbox-package') def test_placement_association(self): self._test_placement_action('toolbox-association') def test_placement_dependency(self): self._test_placement_action('toolbox-dependency') def test_placement_generalization(self): self._test_placement_action('toolbox-generalization') def test_placement_implementation(self): self._test_placement_action('toolbox-implementation') # Components: def test_placement_component(self): self._test_placement_action('toolbox-component') def test_placement_node(self): self._test_placement_action('toolbox-node') def test_placement_artifact(self): self._test_placement_action('toolbox-artifact') # Actions: def test_placement_action(self): self._test_placement_action('toolbox-action') def test_placement_initial_node(self): self._test_placement_action('toolbox-initial-node') def test_placement_activity_final_node(self): self._test_placement_action('toolbox-activity-final-node') def test_placement_flow_final_node(self): self._test_placement_action('toolbox-flow-final-node') def test_placement_decision_node(self): self._test_placement_action('toolbox-decision-node') def test_placement_fork_node(self): self._test_placement_action('toolbox-fork-node') def test_placement_object_node(self): self._test_placement_action('toolbox-object-node') self.assertEquals(1, len(self.kindof(UML.ObjectNode))) def test_placement_partition(self): self._test_placement_action('toolbox-partition') self.assertEquals(0, len(self.kindof(UML.ActivityPartition))) def test_placement_flow(self): self._test_placement_action('toolbox-flow') # Use cases: def test_usecase(self): self._test_placement_action('toolbox-usecase') def test_actor(self): self._test_placement_action('toolbox-actor') def test_usecase_association(self): self._test_placement_action('toolbox-usecase-association') def test_include(self): self._test_placement_action('toolbox-include') def test_extend(self): self._test_placement_action('toolbox-extend') # Profiles: def test_profile(self): self._test_placement_action('toolbox-profile') def test_metaclass(self): self._test_placement_action('toolbox-metaclass') def test_stereotype(self): self._test_placement_action('toolbox-stereotype') def test_extension(self): self._test_placement_action('toolbox-extension') # vim:sw=4:et:ai gaphor-0.17.2/gaphor/ui/tests/test_diagramtools.py000066400000000000000000000031111220151210700222010ustar00rootroot00000000000000 import gtk import logging from gaphor.tests import TestCase from gaphor import UML from gaphor.diagram import items from gaphas.canvas import Context Event = Context logging.basicConfig(level=logging.DEBUG) class DiagramItemConnectorTestCase(TestCase): services = TestCase.services + [ 'main_window', 'ui_manager', 'action_manager', 'properties' ] def setUp(self): super(DiagramItemConnectorTestCase, self).setUp() mw = self.get_service('main_window') mw.open() mw.show_diagram(self.diagram) self.main_window = mw def test_item_reconnect(self): # Setting the stage: ci1 = self.create(items.ClassItem, UML.Class) ci2 = self.create(items.ClassItem, UML.Class) a = self.create(items.AssociationItem) self.connect(a, a.head, ci1) self.connect(a, a.tail, ci2) self.assertTrue(a.subject) self.assertTrue(a.head_end.subject) self.assertTrue(a.tail_end.subject) the_association = a.subject # The act: perform button press event and button release view = self.main_window.get_current_diagram_view() self.assertSame(self.diagram.canvas, view.canvas) p = view.get_matrix_i2v(a).transform_point(*a.head.pos) event = Event(x=p[0], y=p[1], type=gtk.gdk.BUTTON_PRESS, state=0) view.do_event(event) self.assertSame(the_association, a.subject) event = Event(x=p[0], y=p[1], type=gtk.gdk.BUTTON_RELEASE, state=0) view.do_event(event) self.assertSame(the_association, a.subject) # vim:sw=4:et:ai gaphor-0.17.2/gaphor/ui/tests/test_elementeditor.py000066400000000000000000000007001220151210700223550ustar00rootroot00000000000000 from gaphor.ui.elementeditor import ElementEditor from gaphor.tests.testcase import TestCase class ElementEditorTestCase(TestCase): services = TestCase.services + ['main_window', 'ui_manager', 'action_manager', 'properties'] def test1(self): import gtk window = ElementEditor() assert len(window.action_group.list_actions()) == 1, window.action_group.list_actions() window.open() window.close() gaphor-0.17.2/gaphor/ui/tests/test_handletool.py000066400000000000000000000251241220151210700216550ustar00rootroot00000000000000""" Test handle tool functionality. """ import unittest import gtk from gaphor import UML from gaphor.diagram.comment import CommentItem from gaphor.diagram.commentline import CommentLineItem from gaphor.diagram.actor import ActorItem from gaphor.ui.diagramtools import ConnectHandleTool, DiagramItemConnector from gaphas.canvas import Context from gaphas.aspect import Connector, ConnectionSink from gaphor.application import Application Event = Context class DiagramItemConnectorTestCase(unittest.TestCase): def setUp(self): Application.init(services=['adapter_loader', 'element_factory', 'main_window', 'ui_manager', 'properties_manager', 'action_manager', 'properties', 'element_dispatcher']) self.main_window = Application.get_service('main_window') self.main_window.init() self.main_window.open() self.element_factory = Application.get_service('element_factory') self.diagram = self.element_factory.create(UML.Diagram) self.comment = self.diagram.create(CommentItem, subject=self.element_factory.create(UML.Comment)) self.commentline = self.diagram.create(CommentLineItem) self.view = self.main_window.show_diagram(self.diagram).view def test_aspect_type(self): aspect = Connector(self.commentline, self.commentline.handles()[0]) assert type(aspect) is DiagramItemConnector def test_query(self): from zope import component from gaphor.diagram.interfaces import IConnect assert component.queryMultiAdapter((self.comment, self.commentline), IConnect) def test_allow(self): aspect = Connector(self.commentline, self.commentline.handles()[0]) assert aspect.item is self.commentline assert aspect.handle is self.commentline.handles()[0] sink = ConnectionSink(self.comment, self.comment.ports()[0]) assert aspect.allow(sink) def test_connect(self): sink = ConnectionSink(self.comment, self.comment.ports()[0]) aspect = Connector(self.commentline, self.commentline.handles()[0]) aspect.connect(sink) canvas = self.diagram.canvas cinfo = canvas.get_connection(self.commentline.handles()[0]) assert cinfo, cinfo class HandleToolTestCase(unittest.TestCase): """ Handle connection tool integration tests. """ def setUp(self): Application.init(services=['adapter_loader', 'element_factory', 'main_window', 'ui_manager', 'properties_manager', 'action_manager', 'properties', 'element_dispatcher']) self.main_window = Application.get_service('main_window') self.main_window.open() def shutDown(self): Application.shutdown() def get_diagram_view(self, diagram): """ Get a view for diagram. """ self.main_window.show_diagram(diagram) view = self.main_window.get_current_diagram_view() # realize view, forces bounding box recalculation while gtk.events_pending(): gtk.main_iteration() return view def test_iconnect(self): """ Test basic glue functionality using CommentItem and CommentLine items. """ element_factory = Application.get_service('element_factory') diagram = element_factory.create(UML.Diagram) comment = diagram.create(CommentItem, subject=element_factory.create(UML.Comment)) #assert comment.height == 50 #assert comment.width == 100 actor = diagram.create(ActorItem, subject=element_factory.create(UML.Actor)) actor.matrix.translate(200, 200) diagram.canvas.update_matrix(actor) line = diagram.create(CommentLineItem) view = self.get_diagram_view(diagram) assert view, 'View should be available here' tool = ConnectHandleTool(view) # select handle: handle = line.handles()[-1] tool._grabbed_item = line tool._grabbed_handle = handle # Should glue to (238, 248) handle.pos = 245, 248 item = tool.glue(line, handle, (245, 248)) self.assertTrue(item is not None) self.assertEquals((238, 248), view.canvas.get_matrix_i2c(line).transform_point(handle.x, handle.y)) handle.pos = 245, 248 tool.connect(line, handle, (245, 248)) cinfo = diagram.canvas.get_connection(handle) self.assertTrue(cinfo.constraint is not None) self.assertTrue(cinfo.connected is actor, cinfo.connected) self.assertEquals((238, 248), view.get_matrix_i2v(line).transform_point(handle.x, handle.y)) Connector(line, handle).disconnect() #tool.disconnect(line, handle) cinfo = diagram.canvas.get_connection(handle) self.assertTrue(cinfo is None) def test_iconnect_2(self): """Test connect/disconnect on comment and actor using comment-line. """ element_factory = Application.get_service('element_factory') diagram = element_factory.create(UML.Diagram) #self.main_window.show_diagram(diagram) comment = diagram.create(CommentItem, subject=element_factory.create(UML.Comment)) actor = diagram.create(ActorItem, subject=element_factory.create(UML.Actor)) actor.matrix.translate(200, 200) diagram.canvas.update_matrix(actor) line = diagram.create(CommentLineItem) view = self.get_diagram_view(diagram) assert view, 'View should be available here' tool = ConnectHandleTool(view) # select handle: handle = line.handles()[0] tool.grab_handle(line, handle) # Connect one end to the Comment #handle.pos = view.get_matrix_v2i(line).transform_point(45, 48) sink = tool.glue(line, handle, (0, 0)) assert sink is not None assert sink.item is comment tool.connect(line, handle, (0, 0)) cinfo = diagram.canvas.get_connection(handle) self.assertTrue(cinfo is not None, None) self.assertTrue(cinfo.item is line) self.assertTrue(cinfo.connected is comment) pos = view.get_matrix_i2v(line).transform_point(handle.x, handle.y) self.assertAlmostEquals(0, pos[0], 0.00001) self.assertAlmostEquals(0, pos[1], 0.00001) # Connect the other end to the actor: handle = line.handles()[-1] tool.grab_handle(line, handle) handle.pos = 140, 150 sink = tool.glue(line, handle, (200, 200)) self.assertTrue(sink.item is actor) tool.connect(line, handle, (200, 200)) cinfo = view.canvas.get_connection(handle) self.assertTrue(cinfo.item is line) self.assertTrue(cinfo.connected is actor) self.assertEquals((200, 200), view.get_matrix_i2v(line).transform_point(handle.x, handle.y)) # Try to connect far away from any item will only do a full disconnect self.assertEquals(len(comment.subject.annotatedElement), 1, comment.subject.annotatedElement) self.assertTrue(actor.subject in comment.subject.annotatedElement) sink = tool.glue(line, handle, (500, 500)) self.assertTrue(sink is None, sink) tool.connect(line, handle, (500, 500)) self.assertEquals((200, 200), view.canvas.get_matrix_i2c(line).transform_point(handle.x, handle.y)) cinfo = view.canvas.get_connection(handle) self.assertTrue(cinfo is None) def skiptest_connect_3(self): """Test connecting through events (button press/release, motion). """ element_factory = Application.get_service('element_factory') diagram = element_factory.create(UML.Diagram) comment = diagram.create(CommentItem, subject=element_factory.create(UML.Comment)) #self.assertEquals(30, comment.height) #self.assertEquals(100, comment.width) actor = diagram.create(ActorItem, subject=element_factory.create(UML.Actor)) actor.matrix.translate(200, 200) diagram.canvas.update_matrix(actor) #assert actor.height == 60, actor.height #assert actor.width == 38, actor.width line = diagram.create(CommentLineItem) assert line.handles()[0].pos, (0.0, 0.0) assert line.handles()[-1].pos, (10.0, 10.0) view = self.get_diagram_view(diagram) assert view, 'View should be available here' tool = ConnectHandleTool(view) tool.on_button_press(Event(x=0, y=0, state=0)) tool.on_button_release(Event(x=0, y=0, state=0)) handle = line.handles()[0] self.assertEquals((.0, .0), view.canvas.get_matrix_i2c(line).transform_point(*handle.pos)) cinfo = diagram.canvas.get_connection(handle) self.assertTrue(cinfo.connected is comment) #self.assertTrue(handle.connected_to is comment, 'c = ' + str(handle.connected_to)) #self.assertTrue(handle.connection_data is not None) # Grab the second handle and drag it to the actor tool.on_button_press(Event(x=10, y=10, state=0)) tool.on_motion_notify(Event(x=200, y=200, state=0xffff)) tool.on_button_release(Event(x=200, y=200, state=0)) handle = line.handles()[-1] self.assertEquals((200, 200), view.canvas.get_matrix_i2c(line).transform_point(handle.x, handle.y)) cinfo = diagram.canvas.get_connection(handle) self.assertTrue(cinfo.connected is actor) #self.assertTrue(handle.connection_data is not None) self.assertTrue(actor.subject in comment.subject.annotatedElement) # Press, release, nothing should change tool.on_button_press(Event(x=200, y=200, state=0)) tool.on_motion_notify(Event(x=200, y=200, state=0xffff)) tool.on_button_release(Event(x=200, y=200, state=0)) handle = line.handles()[-1] self.assertEquals((200, 200), view.canvas.get_matrix_i2c(line).transform_point(handle.x, handle.y)) cinfo = diagram.canvas.get_connection(handle) self.assertTrue(cinfo.connected is actor) #self.assertTrue(handle.connection_data is not None) self.assertTrue(actor.subject in comment.subject.annotatedElement) # Move second handle away from the actor. Should remove connection tool.on_button_press(Event(x=200, y=200, state=0)) tool.on_motion_notify(Event(x=500, y=500, state=0xffff)) tool.on_button_release(Event(x=500, y=500, state=0)) handle = line.handles()[-1] self.assertEquals((500, 500), view.canvas.get_matrix_i2c(line).transform_point(handle.x, handle.y)) cinfo = diagram.canvas.get_connection(handle) self.assertTrue(cinfo is None) #self.assertTrue(handle.connection_data is None) self.assertEquals(len(comment.subject.annotatedElement), 0) # vim:sw=4:et:ai gaphor-0.17.2/gaphor/ui/tests/test_mainwindow.py000066400000000000000000000016771220151210700217070ustar00rootroot00000000000000 import gtk import unittest from gaphor.application import Application from gaphor.ui.mainwindow import MainWindow from gaphor import UML class MainWindowTestCase(unittest.TestCase): def setUp(self): Application.init(services=['element_factory', 'properties', 'main_window', 'ui_manager', 'action_manager']) def tearDown(self): Application.shutdown() def test_creation(self): # MainWindow should be created as resource main_w = Application.get_service('main_window') main_w.open() self.assertEqual(main_w.get_current_diagram(), None) def test_show_diagram(self): main_w = Application.get_service('main_window') element_factory = Application.get_service('element_factory') diagram = element_factory.create(UML.Diagram) main_w.open() self.assertEqual(main_w.get_current_diagram(), None) main_w.show_diagram(diagram) # vim:sw=4:et:ai gaphor-0.17.2/gaphor/ui/tests/test_namespace.py000066400000000000000000000125211220151210700214550ustar00rootroot00000000000000# vim:sw=4:et:ai from gaphor.tests.testcase import TestCase import gaphor.UML as UML from gaphor.ui.namespace import NamespaceModel from gaphor.application import Application class NamespaceTestCase(object): ##TestCase): services = [ 'element_factory' ] def test_all(self): factory = Application.get_service('element_factory') m = factory.create(UML.Package) m.name = 'm' a = factory.create(UML.Package) a.name = 'a' a.package = m assert a.package is m assert a in m.ownedMember assert a.namespace is m b = factory.create(UML.Package) b.name = 'b' b.package = a assert b in a.ownedMember assert b.namespace is a c = factory.create(UML.Class) c.name = 'c' c.package = b d = factory.create(UML.Class) d.name = 'd' d.package = a e = factory.create(UML.Class) e.name = 'e' e.package = b assert c in b.ownedMember assert c.namespace is b assert d in a.ownedMember assert d.namespace is a assert e in b.ownedMember assert e.namespace is b ns = NamespaceModel(factory) # We have a model loaded. Use it! factory.notify_model() print '---' print ns.root ns.dump() assert ns.path_from_element(m) == (0,) assert ns.path_from_element(a) == (0, 0) assert ns.path_from_element(b) == (0, 0, 0), ns.path_from_element(b) assert ns.path_from_element(c) == (0, 0, 0, 0) assert ns.path_from_element(d) == (0, 0, 1) assert ns.path_from_element(e) == (0, 0, 0, 1) return print '--- del.b.ownedClassifier[c]' del b.ownedClassifier[c] ns.dump() assert ns.path_from_element(m) == (0,) assert ns.path_from_element(a) == (0, 0) assert ns.path_from_element(b) == (0, 0, 0) assert ns.path_from_element(d) == (0, 0, 1) assert ns.path_from_element(e) == (0, 0, 0, 0), ns.path_from_element(e) try: ns.path_from_element(c) except AttributeError: pass # Yes, should raise an exception else: assert ns.path_from_element(c) is not None print '--- c.package = a' c.package = a ns.dump() assert ns.path_from_element(m) == (0,) assert ns.path_from_element(a) == (0, 0) assert ns.path_from_element(b) == (0, 0, 0) assert ns.path_from_element(c) == (0, 0, 1) assert ns.path_from_element(d) == (0, 0, 2) assert ns.path_from_element(e) == (0, 0, 0, 0) print '--- b.package = m' b.package = m ns.dump() assert ns.path_from_element(m) == (0,) assert ns.path_from_element(a) == (0, 0) assert ns.path_from_element(b) == (0, 1) assert ns.path_from_element(c) == (0, 0, 0) assert ns.path_from_element(d) == (0, 0, 1) assert ns.path_from_element(e) == (0, 1, 0) print '--- e.unlink()' e.unlink() ns.dump() print '--- a.unlink()' # def on_unlink(name, element): # print 'unlink: %s' % element.name # a.connect('__unlink__', on_unlink, a) # b.connect('__unlink__', on_unlink, b) # c.connect('__unlink__', on_unlink, c) # d.connect('__unlink__', on_unlink, d) # a.unlink() ns.dump() print '--- TODO: e.relink()' print UML.Class.package print UML.Package.ownedClassifier class NewNamespaceTestCase(TestCase): services = [ 'element_factory' ] def tearDown(self): pass def test(self): factory = Application.get_service('element_factory') ns = NamespaceModel(factory) m = factory.create(UML.Package) m.name = 'm' assert ns._nodes.has_key(m) assert ns.path_from_element(m) == (1,) assert ns.element_from_path((1,)) is m a = factory.create(UML.Package) a.name = 'a' assert a in ns._nodes assert a in ns._nodes[None] assert m in ns._nodes assert ns.path_from_element(a) == (1,), ns.path_from_element(a) assert ns.path_from_element(m) == (2,), ns.path_from_element(m) a.package = m assert a in ns._nodes assert a not in ns._nodes[None] assert a in ns._nodes[m] assert m in ns._nodes assert a.package is m assert a in m.ownedMember assert a.namespace is m assert ns.path_from_element(a) == (1, 0), ns.path_from_element(a) c = factory.create(UML.Class) c.name = 'c' assert c in ns._nodes assert ns.path_from_element(c) == (1,), ns.path_from_element(c) assert ns.path_from_element(m) == (2,), ns.path_from_element(m) assert ns.path_from_element(a) == (2, 0), ns.path_from_element(a) c.package = m assert c in ns._nodes assert c not in ns._nodes[None] assert c in ns._nodes[m] c.package = a assert c in ns._nodes assert c not in ns._nodes[None] assert c not in ns._nodes[m] assert c in ns._nodes[a] c.unlink() assert c not in ns._nodes assert c not in ns._nodes[None] assert c not in ns._nodes[m] assert c not in ns._nodes[a] if __name__ == '__main__': import unittest unittest.main() gaphor-0.17.2/gaphor/ui/toolbox.py000066400000000000000000000076521220151210700170170ustar00rootroot00000000000000""" Toolbox. """ import gobject import gtk from gaphor.core import inject from wrapbox import Wrapbox class Toolbox(gtk.VBox): """ A toolbox is a widget that contains a set of buttons (a Wrapbox widget) with a name above it. When the user clicks on the name the box's content shows/hides. The 'toggled' signal is emited everytime a box shows/hides. The toolbox is generated based on a definition with the form: ('name', ('boxAction1', 'boxAction2',...), 'name2', ('BoxActionN',...)) 1 Create action pool for placement actions 2 Create gtk.RadioButtons for each item. 3 connect to action """ TARGET_STRING = 0 TARGET_TOOLBOX_ACTION = 1 DND_TARGETS = [ ('STRING', gtk.TARGET_SAME_APP, TARGET_STRING), ('text/plain', gtk.TARGET_SAME_APP, TARGET_STRING), ('gaphor/toolbox-action', gtk.TARGET_SAME_APP, TARGET_TOOLBOX_ACTION)] __gsignals__ = { 'toggled': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, (gobject.TYPE_STRING, gobject.TYPE_INT)) } properties = inject('properties') def __init__(self, toolboxdef): """ Create a new Toolbox instance. Wrapbox objects are generated using the menu_factory and based on the toolboxdef definition. """ self.__gobject_init__() self.buttons = [] self.shortcuts = {} self._construct(toolboxdef) def make_wrapbox_decorator(self, title, content): """ Create a gtk.VBox with in the top compartment a label that can be clicked to show/hide the lower compartment. """ expander = gtk.Expander() expander.set_label(title) prop = 'ui.toolbox.%s' % title.replace(' ', '-').lower() expanded = self.properties.get(prop, False) expander.set_expanded(expanded) expander.connect('activate', self.on_expander_toggled, prop) expander.add(content) expander.show_all() return expander def on_expander_toggled(self, widget, prop): # Save the property (inverse value as handler is called before the # action takes place): self.properties.set(prop, not widget.get_expanded()) def toolbox_button(self, action_name, stock_id, icon_size=gtk.ICON_SIZE_LARGE_TOOLBAR): button = gtk.ToggleButton() button.set_relief(gtk.RELIEF_NONE) if stock_id: icon = gtk.Image() icon.set_from_stock(stock_id, icon_size) button.add(icon) icon.show() else: button.props.label = action_name button.action_name = action_name # Enable DND (behaviour like tree view) button.drag_source_set(gtk.gdk.BUTTON1_MASK, self.DND_TARGETS, gtk.gdk.ACTION_COPY | gtk.gdk.ACTION_LINK) button.drag_source_set_icon_stock(stock_id) button.connect('drag-data-get', self._button_drag_data_get) return button def _construct(self, toolboxdef): shortcuts = self.shortcuts for title, items in toolboxdef: wrapbox = Wrapbox() for action_name, label, stock_id, shortcut in items: button = self.toolbox_button(action_name, stock_id) if label: button.set_tooltip_text('%s (%s)' % (label, shortcut)) self.buttons.append(button) wrapbox.add(button) button.show() shortcuts[shortcut] = action_name if title: wrapbox_dec = self.make_wrapbox_decorator(title, wrapbox) self.pack_start(wrapbox_dec, expand=False) else: self.pack_start(wrapbox, expand=False) wrapbox.show() def _button_drag_data_get(self, button, context, selection_data, info, time): selection_data.set(selection_data.target, 8, button.action_name) # vim:sw=4:et:ai gaphor-0.17.2/gaphor/ui/toplevelwindow.py000066400000000000000000000037111220151210700204030ustar00rootroot00000000000000""" Basic stuff for toplevel windows. """ import os.path import pkg_resources import gtk from etk.docking import DockGroup, DockItem from etk.docking.docklayout import add_new_group_floating from zope import interface from interfaces import IUIComponent from gaphor.core import inject ICONS = ( 'gaphor-24x24.png', 'gaphor-48x48.png', 'gaphor-96x96.png', 'gaphor-256x256.png', ) class ToplevelWindow(object): interface.implements(IUIComponent) menubar_path = '' toolbar_path = '' resizable = True def __init__(self): self.window = None def ui_component(self): raise NotImplementedError def construct(self): self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.set_title(self.title) self.window.set_size_request(*self.size) self.window.set_resizable(self.resizable) # set default icons of gaphor windows icon_dir = os.path.abspath(pkg_resources.resource_filename('gaphor.ui', 'pixmaps')) icons = (gtk.gdk.pixbuf_new_from_file(os.path.join(icon_dir, f)) for f in ICONS) self.window.set_icon_list(*icons) self.window.add_accel_group(self.ui_manager.get_accel_group()) if self.menubar_path or self.toolbar_path: # Create a full featured window. vbox = gtk.VBox() self.window.add(vbox) vbox.show() menubar = self.ui_manager.get_widget(self.menubar_path) if menubar: vbox.pack_start(menubar, expand=False) toolbar = self.ui_manager.get_widget(self.toolbar_path) if toolbar: vbox.pack_start(toolbar, expand=False) vbox.pack_end(self.ui_component(), expand=self.resizable) vbox.show() # TODO: add statusbar else: # Create a simple window. self.window.add(self.ui_component()) self.window.show() # vim:sw=4:et:ai gaphor-0.17.2/gaphor/ui/wrapbox.py000066400000000000000000000050551220151210700170060ustar00rootroot00000000000000 import gobject import gtk class Wrapbox(gtk.Table): """ A Wrapbox contains a set of items. A wrap box tries to optimize it's content by moving elements to a second row if the do not fit on the first. And a third and a fourth, depending on the given space. The width is given, the height is changed in order to fit all contained objects. """ def __init__(self): self.__gobject_init__() self.resize_idle_id = 0 self.rows = 1 self.cols = 1 self.resize(self.rows, self.cols) self.connect('size_allocate', self.on_size_allocate) self.children = [] def calculate_size(self, allocation): children = self.children max_width = 0 for c in children: size_request = c.size_request() #print size_request max_width = max(max_width, size_request[0]) cols = allocation.width / (max_width or 1) if cols == 0: cols = 1 rows = len(children) / cols if len(children) % cols: rows += 1 return cols, rows def set_new_size(self): #table = self.table table = self children = self.children if not children: return rows = self.rows cols = self.cols for c in children: table.remove(c) table.resize(rows, cols) x = y = 0 for c in children: table.attach(c, left_attach=x, right_attach=x+1, top_attach=y, bottom_attach=y+1) x += 1 if x == rows: x = 0 y += 1 def _idle_handler(self): try: self.set_new_size() finally: self.resize_idle_id = 0 def on_size_allocate(self, table, allocation): rows, cols = self.calculate_size(allocation) #print 'size_allocate', rows, cols if not self.resize_idle_id and (rows != self.rows or cols != self.cols): #print 'size_allocate', 'setting idle handler' self.resize_idle_id = gobject.idle_add(self._idle_handler) self.rows = rows self.cols = cols def add(self, widget): assert widget, 'No widget supplied: %s' % widget self.cols += 1 row = self.rows col = self.cols #self.table.attach(widget, left_attach=col-1, right_attach=col, self.attach(widget, left_attach=col-1, right_attach=col, top_attach=row-1, bottom_attach=row) self.children.append(widget) gobject.type_register(Wrapbox) # vim:sw=4:et gaphor-0.17.2/iconsrc/000077500000000000000000000000001220151210700145105ustar00rootroot00000000000000gaphor-0.17.2/iconsrc/activityfinalnode.xcf000066400000000000000000000054301220151210700207300ustar00rootroot00000000000000gimp xcf v002  BB*/ gimp-commentCreated with The GIMPS gimp-commentCreated with The GIMPgimp-image-grid(style intersections) (fgcolor (color-rgba 0.000000 0.000000 0.000000 1.000000)) (bgcolor (color-rgba 1.000000 1.000000 1.000000 1.000000)) (xspacing 10.000000) (yspacing 10.000000) (spacing-unit inches) (xoffset 0.000000) (yoffset 0.000000) (offset-unit inches) ‰FîàEmpty Layer#3ÿ     ½Ñá@@@€÷qãúýýúãq ý¸ýÿÿýý¸ þ¸ÿÿþ¸ ýqýÿÿýýqþãÿ ÿþãþúÿ ÿþúþýÿ ÿþýþýÿ ÿþýþúÿ ÿþúþãÿ ÿþãýqýÿÿýýq þ¸ÿÿþ¸ ý¸ýÿÿýý¸ ÷väúýýúäv~ Drop-ShadowÌ     1EUõ"(()("ó'r¶ÍÑÑͶs'ê/˜Ýöüýýüöݘ/'˜æûýþþöüç™'rÝûþþÿþöûÝs"¶öþþÿÿöþ÷·#(Îüþþÿÿ÷þüÎ))ÒýþÿÿøþÒ*)ÒýþÿÿøþÒ*(Îüþÿÿ÷ýÎ)"·öþþÿÿöþ÷¸#sÝüþÿÿôüÞs'™çüþþÿÿêþüçš(/™Þ÷ýýþý÷ßš0ó(u¸ÎÒÒθv)õ#())(#Empty Layer#1ÿ     ð@@@Nõ²õýþþýõ² ýšùÿÿýùš ý¶þÿÿýþ¶ýšþÿ ÿýþšýùÿ ÿýùþ²ÿÿþ²þõÿÿþõþýÿÿþýþþÿÿþþþþÿÿþþþýÿÿþýþõÿÿþõþ²ÿÿþ²ýùÿ ÿýùýšþÿ ÿýþšý¶þÿÿýþ¶ ýšùÿÿýùš õ#¶õýþþýõ¶#M Empty Layerÿ      ž²Â@@@6üSÛüþþüüÛS óÏãM  MãÏû;ïeûeï;üïIüIïüÏe üeÏüSã üãSýÛMýMÛýü ý üýþýþýþýþýþýþýþýþýü ý üýÛMýMÛüSã üãSüÏe üeÏüïIüIïû;ïeûeï;óÏÛI  IÛÏ ü\ÛüþþüüÛ\5Drop-Shadow#1Ì     % Š ž ®äääú (())û(!ù%]”¦¨©©ú§”]&í 4ˆ–]5+**+5]–ˆ5 ø Pf.ø.fP ù5\ò\5%ˆfôfˆ%]–. ö.–]!•^ ÷^•!(§5 ÷5§()©+ ù+©))©*ù*©))©*ø*©))©+ ø+©)(§5 ÷5§(!•] ö]•!^–. ô.–^&ˆfòfˆ&5\ù\5ø Pf-ø-fQ í 5‡“[5+**+5Z“ˆ5 ù&`•¦¨©©ú§•`'ú!(())û(!gaphor-0.17.2/iconsrc/artifact.xcf000066400000000000000000000120601220151210700170060ustar00rootroot00000000000000gimp xcf file88BB / gimp-commentCreated with The GIMP®¬‰ oÜ00 New Layer#4ÿ      V00j00z   Ïÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ'ÿÙ00 New Layer#3ÿ     T00h00xÃ¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥éÃÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂéÃÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑéà ÿ÷þåb ( ÿùîh ÿüë\ ÿüéV ÿüâS ÿüþæbÿþìÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿé00 New Layer#2ÿ     900M00]   aÿüÚ<ÿûþÞDÿüåDÿüá:ÿüÜ5ÿý×9ÿýþÝÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ‡ )Drop-Shadow#2Ì      ¿ )Ó )ã   û    û÷  $%%&&%ø$! ÷":NX\\]]\÷YSE1 ö:eˆ™Ÿ ¡¡ öœ“a>!ö NˆµÍÕÖ××ÖõÓʶ•lB"ö %X™ÍèðòóóòéñëÝÜnB" &\ŸÕðùûüüûêøñáÄ›lA  &\ ÖòûýþþýëúòáÙi=  &]¡×óüþÿÿþëýúòàÁ”_0 &]¡×óüþÿÿþìýúñܶ~D &]¡×óüþÿÿþíýùëÊ“S"  &]¡×óüþÿ ÿþîûñÓZ%  &]¡×óüþÿ ÿþîüóÖ \&  &]¡×óüþÿ ÿþîüóס]'  &]¡×óüþÿ ÿþîüóס]'  &]¡×óüþÿ ÿîýôØ¡]'  &]¡×óüþÿ ÿîýôØ¡]'  &]¡×óüþÿ ÿîýôØ¡]'  &]¡×óüþÿ ÿîýôØ¡]'  &]¡×óüþÿ ÿîýôØ¡]'  &]¡×óüþÿ ÿîýôØ¡]'  &]¡×óüþÿ ÿîýôØ¡]'  &]¡×óüþÿ ÿîýôØ¡]'  &]¡×óüþÿ ÿîýôØ¡]'  &]¡×óüþÿ ÿîýôØ¡]'  &]¡×óüþÿ ÿîýôØ¡]'  &]¡×óüþÿ ÿîýôØ¡]'  &]¡×óüþÿ ÿîýôØ¡]'  &]¡×óüþÿ ÿîýôØ¡]'  &]¡×óüþÿ ÿîýôØ¡]'  &]¡×óüþÿ ÿîýôØ¡]'  &]¡×óüþÿ ÿîýôØ¡]'  &\ Öñúüý ýîûòÖ \&  %YšÎéñóô ôîòéΚY%  Oˆ¶ÎÖר ØîÖηˆO! :eˆ™Ÿ ¡ ¡îŸšˆf;":NX\\] ]î\YN:"!%&&' 'ø&%!û    û 00 New Layerÿ      00 )00 9a+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+áaa+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+äaa+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æaa+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿa00 Backgroundÿ     ¨00¼00Ì    ÿ88 Drop-ShadowÌ      „88˜88¨ @ @ @& û  &  û÷  $%%&%&î%$  ":NX\\]%]î\YN:":eˆ™Ÿ ¡%¡îŸšˆf; NˆµÍÕÖ×%×îÕͶˆN!  %X™Íèðòó%óîñèÍšY%  &\ŸÕðùûü%üîúñÕŸ\&  &\ Öòûýþ%þîüóס]'  &]¡×óüþÿ%ÿîýôØ¡]'  &]¡×óüþÿ%ÿîýôØ¡]'  &]¡×óüþÿ%ÿîýôØ¡]'  &]¡×óüþÿ%ÿîýôØ¡]'  &]¡×óüþÿ%ÿîýôØ¡]'  &]¡×óüþÿ%ÿîýôØ¡]'  &]¡×óüþÿ%ÿîýôØ¡]'  &]¡×óüþÿ%ÿîýôØ¡]'  &]¡×óüþÿ%ÿîýôØ¡]'  &]¡×óüþÿ%ÿîýôØ¡]'  &]¡×óüþÿ%ÿîýôØ¡]'  &]¡×óüþÿ%ÿîýôØ¡]'  &]¡×óüþÿ%ÿîýôØ¡]'  &]¡×óüþÿ%ÿîýôØ¡]'  &]¡×óüþÿ%ÿîýôØ¡]'  &]¡×óüþÿ%ÿîýôØ¡]'  &]¡×óüþÿ%ÿîýôØ¡]'  &]¡×óüþÿ%ÿîýôØ¡]'  &]¡×óüþÿ%ÿîýôØ¡]'  &]¡×óüþÿ%ÿîýôØ¡]'  &]¡×óüþÿ%ÿîýôØ¡]'  &]¡×óüþÿ%ÿîýôØ¡]'  &]¡×óüþÿ%ÿîýôØ¡]'  &]¡×óüþÿ%ÿîýôØ¡]'  &]¡×óüþÿ%ÿîýôØ¡]'  &]¡×óüþÿ%ÿîýôØ¡]'  &]¡×óüþÿ%ÿîýôØ¡]'  &]¡×óüþÿ%ÿîýôØ¡]'  &]¡×óüþÿ%ÿîýôØ¡]'  &]¡×óüþÿ%ÿîýôØ¡]'  &]¡×óüþÿ%ÿîýôØ¡]'  &]¡×óüþÿ%ÿîýôØ¡]'  &]¡×óüþÿ%ÿîýôØ¡]'  &]¡×óüþÿ%ÿîýôØ¡]'  &]¡×óüþÿ%ÿîýôØ¡]'  &]¡×óüþÿ%ÿîýôØ¡]'  &]¡×óüþÿ%ÿîýôØ¡]'  &]¡×óüþÿ%ÿîýôØ¡]'  &]¡×óüþÿ%ÿîýôØ¡]'  &\ Öñúüý%ýîûòÖ \&  %YšÎéñóô%ôîòéΚY%  Oˆ¶ÎÖר%ØîÖηˆO! :eˆ™Ÿ ¡%¡îŸšˆf;":NX\\]%]î\YN:"!%&&'%'ø&%!û  &  û &gaphor-0.17.2/iconsrc/assembly-connector.svg000066400000000000000000000164371220151210700210530ustar00rootroot00000000000000 image/svg+xml gaphor-0.17.2/iconsrc/class.svg000066400000000000000000000040351220151210700163400ustar00rootroot00000000000000 gaphor-0.17.2/iconsrc/component.xcf000066400000000000000000000041131220151210700172130ustar00rootroot00000000000000gimp xcf file00ÿÿÿÿÿÿÿÿ0BB"/ gimp-commentCreated with The GIMP½IË`00 New Layer#2ÿ     "m0000‘²¥¥¥¥¥¥¥¥¥¥…²ÂÂÂÂÂÂÂÂÂÂ…²ÑÑÑÑÑÑÑÑÑÑ…²ÿÿÿÿÿÿÿÿÿÿ…00 New Layerÿ     ï0000r¥¥¥¥¥¥¥¥¥¥ÅrÂÂÂÂÂÂÂÂÂÂÅrÑÑÑÑÑÑÑÑÑÑÅrÿÿÿÿÿÿÿÿÿÿÅ,0Pasted Layer#5ÿ     v,0Š,0šþá)áþá)áþá)áþá)áþá)áþá)áþá)áþá)áþá)áþá)áþá)áþá)áþá)áþá)áþá)áþá)áþá)áþáRáþä)äþä)äþä)äþä)äþä)äþä)äþä)äþä)äþä)äþä)äþä)äþä)äþä)äþä)äþä)äþä)äþä)äþäRäþæ)æþæ)æþæ)æþæ)æþæ)æþæ)æþæ)æþæ)æþæ)æþæ)æþæ)æþæ)æþæ)æþæ)æþæ)æþæ)æþæ)æþæRæ]#ÿ#ÿ#ÿ#ÿ#ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ#ÿ#ÿ#ÿ#ÿ#ÿ#ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ#ÿ#ÿ#ÿ#ÿ#ÿY00Pasted Layer#4ÿ      0000/   'ÿ'ÿ'ÿ'ÿ'ÿ'ÿÈÿ'ÿ'ÿ'ÿ'ÿ'ÿÈÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ'ÿ00Selection Maskù00 00²ÿÿÿÿÿÿÿÿÿÿ…gaphor-0.17.2/iconsrc/connector.svg000066400000000000000000000164531220151210700172340ustar00rootroot00000000000000 image/svg+xml gaphor-0.17.2/iconsrc/device.xcf000066400000000000000000000101311220151210700164450ustar00rootroot00000000000000gimp xcf file88 !B÷šB÷šgimp-image-grid(style solid) (fgcolor (color-rgba 0.000000 0.000000 0.000000 1.000000)) (bgcolor (color-rgba 1.000000 1.000000 1.000000 1.000000)) (xspacing 10.000000) (yspacing 10.000000) (spacing-unit inches) (xoffset 0.000000) (yoffset 0.000000) (offset-unit inches) ¢‘ÜPasted Layer#1ÿ      ) Uiy###ÿÿÿÿÿ  Pasted Layerÿ     : N ^þ¥¥¥¥¥¥¥¥/þÂÂÂÂÂÂÂÂ/þ!ÑÑÑÑÑÑÑÑ/ýÿþÖÿÿ88 Backgroundÿ     ƒ88—88§øp-¸pápü…ÕááûßÅ{%ù%´‡pKü†×ááùàÈz# ú¯ppKüzÒááøàÊ%K ûppKûÒàáá÷àÍ„(S¬pü‡×ááößÊ…, P¯Ü püÕááõßÅ}) JªÛáü{ÔááôàÉ{$ F¥ÚááüwÑááöàÌ‚&K¥ÙááûÒàááöà·+ N«ÙááüÖááöߡ- I©ÛááüxÒááößÈ+ I¦Ùááü|Ôáá÷Ï…( H§Úáá+û K¦Ùáá+üZªÚáá áý¼Úáá á á á á á á á á á á á á á á á á á á á á á á á á á áþÕ á áý×… ááûàÒ† ááüÒz ááü× ááüÕ‡ááüÔááûàÑ{þ ááüÒw ááüÖ áûáÒý áüÔxü$ áý|û! áþù" áá×ør-ºrärü‡ØääûâÈ}&ù&¶ˆrLü‡ÚääùãÊ|# ú±rrLü|ÕääøãÍ&L ûrrLûÔãää÷ãІ)T®rüˆÚääöâ̇, Q±ß rü€ØääõâÈ~* K¬Þäü}×ääôãË}$ G¨ÝääüxÓääöãÏ„'L§ÛääûÔãääöãщ+ O­Üääü€ÙääöâΉ. J«ÞääüyÔääöâʃ+ J¨Üääü~×ää÷Ò‡) I©Ýää+û L¨Üää+ü[¬Ýää äý¿Ýää ä ä ä ä ä ä ä ä ä ä ä ä ä ä ä ä ä ä ä ä ä ä ä ä ä äþØ ä äýÚ‡ ääûãÕ‡ ääüÔ| ääüÚ ääü؈ääü×€ääûãÓ}þ ääüÔx ääüÙ äûäÔ€ý äü×yü% äý~û! äþù# ää×øs.¼sæsüˆÚææûäÊ~&ù&¸ŠsLü‰ÜææùåÌ}$ ú²ssLü}׿æøåÏ‚&M ûssLû‚Öåææ÷åÒ‡)U¯süŠÜææöäΈ- R³á süÚææõäÊ€* K®àæü~ÙææôåÍ~$ H©ßææüyÕææöåÑ…'L¨Ýææû‚ÖåææöåÓŠ, P®ÞææüÛææöäЊ. J­àææüzÖææöäÌ„, JªÞææüÙææ÷Óˆ) I«ßææ+û LªÞææ+ü\®ßææ æýÁßææ æ æ æ æ æ æ æ æ æ æ æ æ æ æ æ æ æ æ æ æ æ æ æ æ æ æþÚ æ æý܈ ææûå׉ ææüÖ} ææüÜ‚ ææüÚŠææüÙææûåÕ~þ ææüÖy ææüÛ‚ æûæÖý æüÙzü% æýû" æþù# ææ×N öñ        úõý&Ùÿÿú ñ&ßÿÿú>. ò Ðÿ ÿùmR/ô #Ïýÿ ÿù’m>ô (ßÿ"ÿù¤{GõÚÿ#ÿùªJ ýýÕÿ$ÿù¬J ÷Éÿ%ÿù­J ü#Ðýÿ%ÿù­J þýÚÿ'ÿù­J úÎÿ(ÿù­J üÕÿ)ÿù­J üÐÿ*ÿù­J ýÔÿ+ÿõ­J Öÿ,ÿö­J ÿ-ÿö­J ÿ-ÿö­J ÿ-ÿö­J ÿ-ÿö­J ÿ-ÿö­J ÿ-ÿö­J ÿ-ÿö­J ÿ-ÿö­J ÿ-ÿö­J ÿ-ÿö­J ÿ-ÿö­J ÿ-ÿö­J ÿ-ÿö­J ÿ-ÿö­J ÿ-ÿö­J ÿ-ÿö¬J ÿ-ÿö«€J ÿ,ÿõô©}Gÿ+ÿôô¡tAÿ*ÿóõÅ®Žb5ÿ)ÿòôű–rH$ÿ(ÿñ÷Ʊ—vN-ÿ'ÿõôȱ˜wQ. ýÿ&ÿôóű—vQ0 ýÿ%ÿôõƱ—wR0 ýÿ$ÿôöDZ—vR7 ýÿ#ÿóøÊ²˜vQ6 ýÿ"ÿñôȲ™vT6 ýÿ!ÿðôƲ™xS1 ýÿ ÿðøÉ±˜vS1 ýÿÿïöɲ˜vR1 ýÿÿîöį™{T1 ö?m’¥«¬­­¬õª¢tQ1 ö.Qmz€€õ}ucJ.  ù .>FJJ÷GB6%  ú ùû ûgaphor-0.17.2/iconsrc/flowfinalnode.svg000066400000000000000000000104231220151210700200600ustar00rootroot00000000000000 image/svg+xml gaphor-0.17.2/iconsrc/forknode.svg000066400000000000000000000054711220151210700170470ustar00rootroot00000000000000 image/svg+xml gaphor-0.17.2/iconsrc/gaphor.svg000066400000000000000000000400011220151210700165040ustar00rootroot00000000000000 G g image/svg+xml gaphor-0.17.2/iconsrc/interaction.xcf000066400000000000000000000036331220151210700175360ustar00rootroot00000000000000gimp xcf file00BB / gimp-commentCreated with The GIMP¢ 0Ä00 New Layer#3ÿ     J00^00na¥¥¥¥¥¥¥maÂÂÂÂÂÂÂmaÑÑÑÑÑÑÑmaÿÿþþÿþð ÿüþñž ÿüò ÿûþîš ÿûþéŒn00 New Layerÿ     ¸00Ì00Ü   /ÿÿÿÿÿþýÿþèÿüþí“ÿüí“ÿûþê”ÿûýä‰ 00 New Layer#2ÿ     Ø00ì00üa+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+áaa+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+äaa+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æaa+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿa00 Backgroundÿ     k0000   gaphor-0.17.2/iconsrc/lifeline.xcf000066400000000000000000000023541220151210700170050ustar00rootroot00000000000000gimp xcf file00BB / gimp-commentCreated with The GIMPžÜ¸00 New Layer#4ÿ     F00Z00j   ×ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿîÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-ÿ-----ÿ-ÿ-ÿ-ÿ00 New Layer#3ÿ     „00˜00¨iááááááááááááááááªiääääääääääääääääªiææææææææææææææææªiÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿª00 New Layer#2ÿ     h00|00Œ   ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿHgaphor-0.17.2/iconsrc/message.svg000066400000000000000000000202221220151210700166530ustar00rootroot00000000000000 image/svg+xml gaphor-0.17.2/iconsrc/node.xcf000066400000000000000000000106501220151210700161410ustar00rootroot00000000000000gimp xcf file00BB/ gimp-commentCreated with The GIMPPath 0A0A0?€?€?€?€?€?€@@€@@€@@€?€@À?€@À?€@À?€A?€A?€AA ¶cT \ Ä00 New Layer#5ÿ     ÿÿÿÿf00z00Š   *úsÔý(ùqÖþí'øsÔþî &÷t×þò¨;%önÔýî©B$öiÑýì <$öhÍýñ¤8$öqÐüó«>$ösÖýó¯C$ökÓþïªD $öeÎýñ¦=$öeËüó¬?$ölÎüò¬B%÷hÑýó­A&ø9µüðªB'ù1šË™>(ú 3K)Û00 New Layer#4ÿ     ÿÿÿÿ 0000/‚ á# á# á# á# á# á# á# á# á# á# á# á# á# á# á# á# á# á# á# á# á# á# á# á# á# á# á# á# á# á# á# á# á# á# á# á# á# á# á# á# á# á‚ ä# ä# ä# ä# ä# ä# ä# ä# ä# ä# ä# ä# ä# ä# ä# ä# ä# ä# ä# ä# ä# ä# ä# ä# ä# ä# ä# ä# ä# ä# ä# ä# ä# ä# ä# ä# ä# ä# ä# ä# ä# ä‚ æ# æ# æ# æ# æ# æ# æ# æ# æ# æ# æ# æ# æ# æ# æ# æ# æ# æ# æ# æ# æ# æ# æ# æ# æ# æ# æ# æ# æ# æ# æ# æ# æ# æ# æ# æ# æ# æ# æ# æ# æ# æÂ‚ ÿ# ÿ# ÿ# ÿ# ÿ# ÿ# ÿ# ÿ# ÿ# ÿ# ÿ# ÿ# ÿ# ÿ# ÿ# ÿ# ÿ# ÿ# ÿ# ÿ# ÿ# ÿ# ÿ# ÿ# ÿ# ÿ# ÿ# ÿ# ÿþò" ÿýô—"ÿûþï˜""ÿüî‹!#ÿüô‘$ÿüò™%ÿüñ"&ÿûþíŒþ"ÿüî‡#ÿüó‘$ûÿî ý"üñˆü "ýû"þùÁ00 New Layer#3ÿ     ÿÿÿÿ ü0000 c)á)á)á)á)á)á)á)á)á)á)á)ábc)ä)ä)ä)ä)ä)ä)ä)ä)ä)ä)ä)äbc)æ)æ)æ)æ)æ)æ)æ)æ)æ)æ)æ)æbcø ü"—òÿÿùü!˜ôÿÿúü‹ïÿÿûû‘îþÿÿü"™ôÿÿüòÿÿ üŒñÿ ÿ ü‡íÿ!ÿû ‘îþÿ!ÿüóÿ#ÿüˆîÿ$ÿüñÿ%ÿb00 New Layer#6ÿ      00 00 (2¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥p2ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂp2ÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑp2ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿp00 New Layer#2ÿ      °00 Ä00 Ô¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥oÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂoÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑoÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿo00 New Layerÿ     j00~00Ž   öò õý$Øÿÿõý#ÞÿÿøýÎÿ ÿ÷ü Íýÿ ÿøý%Þÿ"ÿôØÿ#ÿüýÓÿ$ÿûýÇÿ%ÿþü Îýÿ%ÿøÙÿ'ÿùÌÿ(ÿýÓÿ)ÿýÎÿ*ÿüÒÿ+ÿýÕÿ,ÿþÿ-ÿþÿ-ÿþÿ-ÿþÿ-ÿþÿ-ÿþÿ-ÿþÿ-ÿþÿ-ÿþÿ-ÿþÿ-ÿþÿ-ÿþÿ-ÿþÿ-ÿþÿ-ÿþÿ-ÿþÿ-ÿþÿ-ÿþÿ,ÿýÒÿ+ÿüÎÿ*ÿûÓÿ)ÿúÌÿ(ÿùÙÿ&ÿ÷ýÎ ÿ&ÿýÇþÿ%ÿûÓýÿ$ÿõØÿ#ÿýÞ%ûÿ!ÿüýÍ úÿ!ÿûÎùÿ ÿýÞ#øÿÿýØ$õÿÿðÙ gaphor-0.17.2/iconsrc/objectnode.svg000066400000000000000000000133131220151210700173460ustar00rootroot00000000000000 image/svg+xml gaphor-0.17.2/iconsrc/operations.xcf000066400000000000000000000040201220151210700173710ustar00rootroot00000000000000gimp xcf fileBB/ gimp-commentCreated with The GIMPøPath 0À€?€À@?€@ A¸@ A¸@ A¸À ?€A¸@ @ A¸ABABAB@ A°@ A°@ A°ABABAAð¢T²9 New Layer#2ÿ      Rfv¯¥¥¥¥¥¥¥¥¥È¯ÂÂÂÂÂÂÂÂÂȯÑÑÑÑÑÑÑÑÑȯþ@€€þ@ þ€ÿÿþ€ þ€ÿÿþ€ þ€ÿÿþ€ þ€ÿÿþ€ þ€ÿÿþ€ þ€ÿÿþ€ þ€ÿÿþ€ þ@€€þ@Ç New Layerÿ     ú¯þ8ppþ8 þpááþp þpááþp þpááþp þpááþp þpááþp þpááþp þpááþp þ8ppþ8ǯþ9rrþ9 þrääþr þrääþr þrääþr þrääþr þrääþr þrääþr þrääþr þ9rrþ9ǯþ:ssþ: þsææþs þsææþs þsææþs þsææþs þsææþs þsææþs þsææþs þ:ssþ:Ç–þ@€€þ@ þ€ÿÿþ€ þ€ÿÿþ€ þ€ÿÿþ€ þ€ÿÿþ€ þ€ÿÿþ€ þ€ÿÿþ€ þ€ÿÿþ€ þ€ÿÿþ€ þ€ÿÿþ€ þ@€€þ@® Drop-ShadowÌ      Zn~ÄÄÄúø0?ABB÷@00ж¼¼ö·‹0@¶ð÷øø÷ñ·@A¼÷þþöø¼BB¼øþÿÿöø¼BB¼øþÿÿöø¼BB¼øþÿÿöø¼BB¼øþÿÿöø¼BB¼øþÿÿ÷ø¼B@¸ñøø÷ò¸@0‹·¼¼÷·‹10@BBø@0ý Backgroundÿ     àô@ÿ@ÿ@ÿgaphor-0.17.2/iconsrc/package.svg000066400000000000000000000041401220151210700166230ustar00rootroot00000000000000 gaphor-0.17.2/iconsrc/partition.svg000066400000000000000000000117271220151210700172520ustar00rootroot00000000000000 image/svg+xml gaphor-0.17.2/iconsrc/pointer.xcf000066400000000000000000000026571220151210700167040ustar00rootroot00000000000000gimp xcf fileBBg+ÍColorÿ      -gÿüÿÿûÿÿþÿþÿþÿþÿþÿþÿþÿþÿþÿþÿ þÿþÿ þÿÿ øÿÿÿ÷ÿÿÿÿÿûÿÿûÿÿûÿÿÿ7gÿüÿÿûÿÿþÿþÿþÿþÿþÿþÿþÿþÿþÿþÿ þÿþÿ þÿÿ øÿÿÿ÷ÿÿÿÿÿûÿÿûÿÿûÿÿÿ7gÿüÿÿûÿÿþÿþÿþÿþÿþÿþÿþÿþÿþÿþÿ þÿþÿ þÿÿ øÿÿÿ÷ÿÿÿÿÿûÿÿûÿÿûÿÿÿ7gÿÿÿÿÿÿÿÿ ÿ ÿ ÿÿþÿÿÿÿÿÿÿ7  Drop-ShadowÌ     Ó ç ÷ØØØû""ú"°·?ù)Óö¿?ø*Ôþ÷¿?÷*Ôþþ÷¿?ö*Ôþÿþ÷¿?ð*Ôþÿÿþ÷¿?*Ôþÿÿöþ÷¿?*Ôþÿÿ÷þ÷¿?*Ôþÿÿøþ÷¸#*ÔþÿÿË÷ÛÔ±#*Ôþ÷â÷þâi1#*Ô÷¿iÛþ÷¸#"°·?*·÷þÛF""FÛþ÷¸#ù"·÷÷¸#ù?··?û"" New Layerÿ     {Ÿ@ÿ@ÿ@ÿ@ÿgaphor-0.17.2/iconsrc/state.svg000066400000000000000000000067411220151210700163610ustar00rootroot00000000000000 image/svg+xml gaphor-0.17.2/iconsrc/subsystem.svg000066400000000000000000000065531220151210700173000ustar00rootroot00000000000000 image/svg+xml gaphor-0.17.2/iconsrc/transition.svg000066400000000000000000000171361220151210700174330ustar00rootroot00000000000000 image/svg+xml gaphor-0.17.2/iconsrc/uml_icons.xcf000066400000000000000000000122041220151210700172010ustar00rootroot00000000000000gimp xcf file00BBg/ gimp-commentCreated with The GIMP«UnnamedHAÆ?@AÆ?@@ÈA²@ÈA²@ÈA²A’A°A’A°A’A°A’B8A’B8A’B8AôB9AôB9AôB9AôA²AôA²AôA²B+A°B+A°B+A°AÆ?@noteA¬ÁpA¬ÁpBhA¬BhA¬BhA¬B„ÁàB„ÁàB„ÁàS gimp-commentCreated with The GIMPgimp-image-grid(style intersections) (fgcolor (color-rgba 0.000000 0.000000 0.000000 1.000000)) (bgcolor (color-rgba 1.000000 1.000000 1.000000 1.000000)) (xspacing 10.000000) (yspacing 10.000000) (spacing-unit inches) (xoffset 0.000000) (yoffset 0.000000) (offset-unit inches) Èä¥ L ö ÌÞð°00note#3ÿ     9k0000   !þÿ-þÿ-þÿ-þÿ-þÿ-þÿ-þÿ-þÿ-þÿ-þÿ-þÿ-þÿ-þÿ- ÿ`00note#2ÿ     B‡00›00«#ýOá-ýOá-ýOá-ýKá-ýOá-ýOá-ýOá-ýOá-ýOá-ýOá-ýOá-þO¿#ýPä-ýPä-ýPä-ýLä-ýPä-ýPä-ýPä-ýPä-ýPä-ýPä-ýPä-þP¿#ýQæ-ýQæ-ýQæ-ýLæ-ýQæ-ýQæ-ýQæ-ýQæ-ýQæ-ýQæ-ýQæ-þQ¿#ÿýæ$ÿýæÿ ú<ûÿæÿ!ú<ûÿéÿ"ú<ýÿæÿ#ú<ýÿæÿ$ú<ûÿæÿ%ú<ûÿæÿ&ö<ûÿæÿÿ&÷<ûÿæÿÿ'ø<ûÿæÿÿ(ù<ûÿæÿÿ)þ<ÿÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+aÿ00note#1ÿ     @H00\00l"¥,¥+¥*¥)¥(¥'¥& ¥% ¥$ ¥# ¥" ¥" ¥"Â,Â+Â*Â)Â(Â'Â& Â% Â$ Â# Â" Â" Â"Ñ,Ñ+Ñ*Ñ)Ñ(Ñ'Ñ& Ñ% Ñ$ Ñ# Ñ" Ñ" Ñ"ÿ,ÿ+ÿ*ÿ)ÿ(ÿ'ÿ& ÿ% ÿ$ ÿ# ÿ" ÿ" ÿ00noteÿ     < í00 00 %á &á'á(á)á*á+á,á-á.áþáá%ä &ä'ä(ä)ä*ä+ä,ä-ä.äþää%æ &æ'æ(æ)æ*æ+æ,æ-æ.æþææ#ÿýÃ$ÿýÃ%ÿýÃ&ÿýÇ'ÿýÃ(ÿýÃ)ÿýÃ*ÿýÃ+ÿûÃÿ+ÿüÃÿ,ÿýÃÿ-ÿþÃÿ¿ÿ00packageÿ      š00 ®00 ¾ ¥` Â` Ñ`aÿÿÿÿÿÿÿÿÿÿÿÿr00 package#1ÿ      °00 Ä00 ÔÒ+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á’Ò+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä’Ò+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ’Ò+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ’00 package#2ÿ      r00†00–   ÿÿÿÿÿÿÿÿÿÿÿÿÿÿ`ÿ00classÿ     €00”00¤ ¥` Â` Ñ`a+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿb00class#1ÿ     œ00°00ÀÒ+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á+á’Ò+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä+ä’Ò+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ+æ’Ò+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿc+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ+ÿ’00class#2ÿ     T00h00x   gaphor-0.17.2/po/000077500000000000000000000000001220151210700134665ustar00rootroot00000000000000gaphor-0.17.2/po/ca.po000066400000000000000000000264271220151210700144240ustar00rootroot00000000000000# Catalan translation for Gaphor # Copyright © 2006, 2011 Free Software Foundation, Inc. # This file is distributed under the same licence as the gaphor package. # Jordi Vilalta Prat , 2006. # Jordi Mallach , 2011. # msgid "" msgstr "" "Project-Id-Version: Gaphor 0.15\n" "POT-Creation-Date: Thu Jun 10 15:59:38 2010\n" "PO-Revision-Date: 2011-03-05 20:26+0100\n" "Last-Translator: Jordi Vilalta Prat \n" "Language-Team: Catalan \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: gaphor/adapters/actions/partitionpage.py:30 msgid "External" msgstr "Extern" #: gaphor/adapters/profiles/metaclasseditor.py:31 #: gaphor/adapters/propertypages.py:552 msgid "Name" msgstr "Nom" #: gaphor/adapters/profiles/stereotypespage.py:140 msgid "Attribute" msgstr "Atribut" #: gaphor/adapters/profiles/stereotypespage.py:140 #: gaphor/ui/diagramtoolbox.py:72 msgid "Stereotype" msgstr "Estereotip" #: gaphor/adapters/profiles/stereotypespage.py:167 msgid "Value" msgstr "Valor" #: gaphor/adapters/profiles/stereotypespage.py:219 msgid "Show stereotypes attributes" msgstr "Mostra els atributs dels estereotips" #: gaphor/adapters/propertypages.py:505 gaphor/ui/diagramtoolbox.py:26 msgid "Comment" msgstr "Comentari" #: gaphor/adapters/propertypages.py:627 msgid "Abstract" msgstr "Resum" #: gaphor/adapters/propertypages.py:661 msgid "Folded" msgstr "Plegat" #: gaphor/adapters/propertypages.py:737 msgid "Show attributes" msgstr "Mostra els atributs" #: gaphor/adapters/propertypages.py:754 msgid "Attributes" msgstr "Atributs" #: gaphor/adapters/propertypages.py:754 gaphor/adapters/propertypages.py:827 msgid "S" msgstr "S" #: gaphor/adapters/propertypages.py:811 msgid "Show operations" msgstr "Mostra les operacions" #: gaphor/adapters/propertypages.py:827 msgid "Operation" msgstr "Operació" #: gaphor/adapters/propertypages.py:874 gaphor/ui/diagramtoolbox.py:33 msgid "Dependency" msgstr "Dependència" #: gaphor/adapters/propertypages.py:875 msgid "Usage" msgstr "Ús" #: gaphor/adapters/propertypages.py:876 msgid "Realization" msgstr "Realització" #: gaphor/adapters/propertypages.py:877 gaphor/ui/diagramtoolbox.py:35 msgid "Implementation" msgstr "Implementació" #: gaphor/adapters/propertypages.py:889 msgid "Dependency type" msgstr "Tipus de dependència" #: gaphor/adapters/propertypages.py:897 msgid "Automatic" msgstr "Automàtic" #: gaphor/adapters/propertypages.py:977 msgid "Show direction" msgstr "Mostra la direcció" #: gaphor/adapters/propertypages.py:982 msgid "Invert Direction" msgstr "Inverteix la direcció" #: gaphor/adapters/propertypages.py:988 msgid "Head" msgstr "Cap" #: gaphor/adapters/propertypages.py:992 msgid "Tail" msgstr "Cua" #: gaphor/adapters/propertypages.py:1181 msgid "Orthogonal" msgstr "Ortogonal" #: gaphor/adapters/propertypages.py:1198 msgid "Horizontal" msgstr "Horitzontal" #: gaphor/adapters/propertypages.py:1234 msgid "Upper bound" msgstr "Límit superior" #: gaphor/adapters/propertypages.py:1249 msgid "Ordering" msgstr "Ordenació" #: gaphor/adapters/propertypages.py:1297 msgid "Join specification" msgstr "Especificació de la unió" #: gaphor/adapters/propertypages.py:1329 #: gaphor/adapters/states/propertypages.py:32 msgid "Guard" msgstr "Guàrdia" #: gaphor/adapters/propertypages.py:1382 msgid "Indirectly instantiated" msgstr "Instanciat indirectament" #: gaphor/adapters/propertypages.py:1417 gaphor/adapters/propertypages.py:1438 #: gaphor/adapters/propertypages.py:1445 gaphor/ui/diagramtoolbox.py:55 msgid "Message" msgstr "Missatge" #: gaphor/adapters/propertypages.py:1440 msgid "Additional Messages" msgstr "Missatges addicionals" #: gaphor/adapters/propertypages.py:1447 msgid "Inverted Messages" msgstr "Missatges invertits" #: gaphor/adapters/propertypages.py:1451 msgid "Message sort" msgstr "Ordenació dels missatges" #: gaphor/adapters/states/propertypages.py:79 msgid "Entry" msgstr "Entrada" #: gaphor/adapters/states/propertypages.py:86 msgid "Exit" msgstr "Sortida" #: gaphor/adapters/states/propertypages.py:93 msgid "Do Activity" msgstr "Realitza l'activitat" #: gaphor/misc/errorhandler.py:21 msgid "An error occured." msgstr "S'ha produït un error." #: gaphor/misc/errorhandler.py:28 msgid "" "\n" "\n" "Do you want to debug?\n" "(Gaphor should have been started from the command line)" msgstr "" "\n" "\n" "Voleu depurar?\n" "(S'hauria d'haver inicial el Gaphor des de la línia d'ordres)" #: gaphor/plugins/xmiexport/__init__.py:39 msgid "Export to XMI" msgstr "Exporta a XMI" #: gaphor/plugins/xmiexport/__init__.py:40 msgid "Export model to XMI (XML Model Interchange) format" msgstr "Exporta el model al format XMI (XML Model Interchange)" #: gaphor/services/diagramexportmanager.py:74 msgid "" "The file %s already exists. Do you want to replace it with the file you are " "exporting to?" msgstr "El fitxer %s ja existeix. Voleu reemplaçar-lo amb el fitxer al qual esteu exportant?" #: gaphor/services/filemanager.py:135 msgid "" "Opening a new model will flush the currently loaded model.\n" "Any changes made will not be saved. Do you want to continue?" msgstr "" "L'obertura d'un nou model descartarà el model actualment carregat.\n" "Els darrers canvis no es desaran. Voleu continuar?" #: gaphor/services/filemanager.py:144 msgid "New model" msgstr "Model nou" #: gaphor/services/filemanager.py:147 msgid "main" msgstr "principal" #: gaphor/services/filemanager.py:163 msgid "Loading model from %s" msgstr "S'està carregant el model des de %s" #: gaphor/services/filemanager.py:163 msgid "Loading..." msgstr "S'està carregant…" #: gaphor/services/filemanager.py:193 msgid "" "The model contains some references to items that are not maintained. Do you " "want to clean this before saving the model?" msgstr "El model conté algunes referències a elements que no estan mantinguts. Voleu netejar això abans de desar el model?" #: gaphor/services/filemanager.py:260 msgid "New from template" msgstr "Nou des d'una plantilla" #: gaphor/services/filemanager.py:305 msgid "Save Gaphor model as" msgstr "Anomena i desa el model de Gaphor" #: gaphor/storage/storage.py:186 msgid "Loading %d elements..." msgstr "S'estan carregant %d elements…" #: gaphor/ui/diagramtab.py:76 msgid "" msgstr "" #: gaphor/ui/diagramtoolbox.py:22 msgid "Pointer" msgstr "Punter" #: gaphor/ui/diagramtoolbox.py:23 msgid "Line" msgstr "Línia" #: gaphor/ui/diagramtoolbox.py:24 msgid "Box" msgstr "Caixa" #: gaphor/ui/diagramtoolbox.py:25 msgid "Ellipse" msgstr "EÅ€lipsi" #: gaphor/ui/diagramtoolbox.py:27 msgid "Comment line" msgstr "Línia de comentari" #: gaphor/ui/diagramtoolbox.py:28 msgid "Classes" msgstr "Classes" #: gaphor/ui/diagramtoolbox.py:29 msgid "Class" msgstr "Classe" #: gaphor/ui/diagramtoolbox.py:30 msgid "Interface" msgstr "Interfície" #: gaphor/ui/diagramtoolbox.py:31 msgid "Package" msgstr "Paquet" #: gaphor/ui/diagramtoolbox.py:32 gaphor/ui/diagramtoolbox.py:66 msgid "Association" msgstr "Associació" #: gaphor/ui/diagramtoolbox.py:34 msgid "Generalization" msgstr "Generalització" #: gaphor/ui/diagramtoolbox.py:36 msgid "Components" msgstr "Components" #: gaphor/ui/diagramtoolbox.py:37 msgid "Component" msgstr "Component" #: gaphor/ui/diagramtoolbox.py:38 msgid "Artifact" msgstr "Artefacte" #: gaphor/ui/diagramtoolbox.py:39 msgid "Node" msgstr "Node" #: gaphor/ui/diagramtoolbox.py:40 msgid "Device" msgstr "Dispositiu" #: gaphor/ui/diagramtoolbox.py:41 msgid "Subsystem" msgstr "Subsistema" #: gaphor/ui/diagramtoolbox.py:42 msgid "Connector" msgstr "Connector" #: gaphor/ui/diagramtoolbox.py:43 msgid "Actions" msgstr "Accions" #: gaphor/ui/diagramtoolbox.py:44 msgid "Action" msgstr "Acció" #: gaphor/ui/diagramtoolbox.py:45 msgid "Initial node" msgstr "Node inicial" #: gaphor/ui/diagramtoolbox.py:46 msgid "Activity final node" msgstr "Node final de l'activitat" #: gaphor/ui/diagramtoolbox.py:47 msgid "Flow final node" msgstr "Node final del fluxe" #: gaphor/ui/diagramtoolbox.py:48 msgid "Decision/merge node" msgstr "Node de decisió/fusió" #: gaphor/ui/diagramtoolbox.py:49 msgid "Fork/join node" msgstr "Node de desviació/unió" #: gaphor/ui/diagramtoolbox.py:50 msgid "Object node" msgstr "Node d'objecte" #: gaphor/ui/diagramtoolbox.py:51 msgid "Partition" msgstr "Partició" #: gaphor/ui/diagramtoolbox.py:52 msgid "Control/object flow" msgstr "Fluxe de control/objecte" #: gaphor/ui/diagramtoolbox.py:53 msgid "Interactions" msgstr "Interaccions" #: gaphor/ui/diagramtoolbox.py:54 msgid "Lifeline" msgstr "Línia de vida" #: gaphor/ui/diagramtoolbox.py:56 msgid "Interaction" msgstr "Interacció" #: gaphor/ui/diagramtoolbox.py:57 msgid "States" msgstr "Estats" #: gaphor/ui/diagramtoolbox.py:58 msgid "State" msgstr "Estat" #: gaphor/ui/diagramtoolbox.py:59 msgid "Initial Pseudostate" msgstr "Pseudoestat inicial" #: gaphor/ui/diagramtoolbox.py:60 msgid "Final State" msgstr "Estat final" #: gaphor/ui/diagramtoolbox.py:61 msgid "History Pseudostate" msgstr "Pseudoestat de l'historial" #: gaphor/ui/diagramtoolbox.py:62 msgid "Transition" msgstr "Transició" #: gaphor/ui/diagramtoolbox.py:63 msgid "Use Cases" msgstr "Casos d'ús" #: gaphor/ui/diagramtoolbox.py:64 msgid "Use case" msgstr "Cas d'ús" #: gaphor/ui/diagramtoolbox.py:65 msgid "Actor" msgstr "Actor" #: gaphor/ui/diagramtoolbox.py:67 msgid "Include" msgstr "Inclou" #: gaphor/ui/diagramtoolbox.py:68 msgid "Extend" msgstr "Extèn" #: gaphor/ui/diagramtoolbox.py:69 msgid "Profiles" msgstr "Perfils" #: gaphor/ui/diagramtoolbox.py:70 msgid "Profile" msgstr "Perfil" #: gaphor/ui/diagramtoolbox.py:71 msgid "Metaclass" msgstr "Metaclasse" #: gaphor/ui/diagramtoolbox.py:73 msgid "Extension" msgstr "Extensió" #: gaphor/ui/elementeditor.py:23 msgid "Element Editor" msgstr "Editor d'elements" #: gaphor/ui/elementeditor.py:48 msgid "Editor" msgstr "Editor" #: gaphor/ui/mainwindow.py:193 msgid "Save changed to your model before closing?" msgstr "Voleu desar els canvis al model abans de tancar?" #: gaphor/ui/mainwindow.py:195 msgid "If you close without saving, your changes will be discarded." msgstr "Si tanqueu sense desar, es descartaran els vostres canvis." #: gaphor/ui/mainwindow.py:579 msgid "Rename" msgstr "Canvia el nom" #: gaphor/ui/mainwindow.py:592 msgid "_New diagram" msgstr "Diagrama _nou" #: gaphor/ui/mainwindow.py:609 msgid "_Delete diagram" msgstr "Suprimeix el _diagrama" #: gaphor/ui/mainwindow.py:630 msgid "New _package" msgstr "_Paquet nou" #: gaphor/ui/mainwindow.py:646 msgid "Delete pac_kage" msgstr "Suprimeix el pa_quet" #: gaphor/ui/mainwindow.py:654 msgid "_Refresh" msgstr "_Refresca" #: gaphor/ui/mainwindow.py:659 msgid "_Reset tool" msgstr "_Reinicia l'eina" #: gaphor/ui/propertyeditor.py:28 msgid "Properties" msgstr "Propietats" #~ msgid "Created a new model" #~ msgstr "S'ha creat un nou model" #~ msgid "Could not load model file." #~ msgstr "No s'ha pogut carregar el fitxer del model." #~ msgid "_Open..." #~ msgstr "_Obre..." #~ msgid "Save the model to a new file" #~ msgstr "Desa el model a un nou fitxer" #~ msgid "(from %s)" #~ msgstr "(des de %s)" #~ msgid "_File" #~ msgstr "_Fitxer" #~ msgid "Recent files" #~ msgstr "Fitxers recents" #~ msgid "_Import" #~ msgstr "_Importa" #~ msgid "_Diagram" #~ msgstr "_Diagrama" #~ msgid "Tools" #~ msgstr "Eines" #~ msgid "_Window" #~ msgstr "_Finestra" #~ msgid "_Help" #~ msgstr "_Ajuda" gaphor-0.17.2/po/es.po000066400000000000000000000247261220151210700144500ustar00rootroot00000000000000# Spanish Gaphor translation # Copyright (C) 2005, 2006 # David Roguin , 2005. # Jordi Vilalta Prat , 2006. # msgid "" msgstr "" "Project-Id-Version: Gaphor 0.8.0\n" "POT-Creation-Date: Thu Jun 10 15:59:38 2010\n" "PO-Revision-Date: 2006-02-11 08:02+0100\n" "Last-Translator: Jordi Vilalta Prat \n" "Language-Team: es \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: pygettext.py 1.5\n" #: gaphor/adapters/actions/partitionpage.py:30 msgid "External" msgstr "" #: gaphor/adapters/profiles/metaclasseditor.py:31 #: gaphor/adapters/propertypages.py:552 msgid "Name" msgstr "" #: gaphor/adapters/profiles/stereotypespage.py:140 msgid "Attribute" msgstr "" #: gaphor/adapters/profiles/stereotypespage.py:140 #: gaphor/ui/diagramtoolbox.py:72 msgid "Stereotype" msgstr "" #: gaphor/adapters/profiles/stereotypespage.py:167 msgid "Value" msgstr "" #: gaphor/adapters/profiles/stereotypespage.py:219 msgid "Show stereotypes attributes" msgstr "" #: gaphor/adapters/propertypages.py:505 gaphor/ui/diagramtoolbox.py:26 #, fuzzy msgid "Comment" msgstr "Componentes" #: gaphor/adapters/propertypages.py:627 msgid "Abstract" msgstr "" #: gaphor/adapters/propertypages.py:661 msgid "Folded" msgstr "" #: gaphor/adapters/propertypages.py:737 msgid "Show attributes" msgstr "" #: gaphor/adapters/propertypages.py:754 msgid "Attributes" msgstr "" #: gaphor/adapters/propertypages.py:754 gaphor/adapters/propertypages.py:827 msgid "S" msgstr "" #: gaphor/adapters/propertypages.py:811 msgid "Show operations" msgstr "" #: gaphor/adapters/propertypages.py:827 #, fuzzy msgid "Operation" msgstr "Interacciones" #: gaphor/adapters/propertypages.py:874 gaphor/ui/diagramtoolbox.py:33 msgid "Dependency" msgstr "" #: gaphor/adapters/propertypages.py:875 msgid "Usage" msgstr "" #: gaphor/adapters/propertypages.py:876 msgid "Realization" msgstr "" #: gaphor/adapters/propertypages.py:877 gaphor/ui/diagramtoolbox.py:35 #, fuzzy msgid "Implementation" msgstr "Interacciones" #: gaphor/adapters/propertypages.py:889 msgid "Dependency type" msgstr "" #: gaphor/adapters/propertypages.py:897 msgid "Automatic" msgstr "" #: gaphor/adapters/propertypages.py:977 msgid "Show direction" msgstr "" #: gaphor/adapters/propertypages.py:982 #, fuzzy msgid "Invert Direction" msgstr "Interacciones" #: gaphor/adapters/propertypages.py:988 msgid "Head" msgstr "" #: gaphor/adapters/propertypages.py:992 msgid "Tail" msgstr "" #: gaphor/adapters/propertypages.py:1181 msgid "Orthogonal" msgstr "" #: gaphor/adapters/propertypages.py:1198 msgid "Horizontal" msgstr "" #: gaphor/adapters/propertypages.py:1234 msgid "Upper bound" msgstr "" #: gaphor/adapters/propertypages.py:1249 msgid "Ordering" msgstr "" #: gaphor/adapters/propertypages.py:1297 msgid "Join specification" msgstr "" #: gaphor/adapters/propertypages.py:1329 #: gaphor/adapters/states/propertypages.py:32 msgid "Guard" msgstr "" #: gaphor/adapters/propertypages.py:1382 msgid "Indirectly instantiated" msgstr "" #: gaphor/adapters/propertypages.py:1417 gaphor/adapters/propertypages.py:1438 #: gaphor/adapters/propertypages.py:1445 gaphor/ui/diagramtoolbox.py:55 msgid "Message" msgstr "" #: gaphor/adapters/propertypages.py:1440 msgid "Additional Messages" msgstr "" #: gaphor/adapters/propertypages.py:1447 msgid "Inverted Messages" msgstr "" #: gaphor/adapters/propertypages.py:1451 msgid "Message sort" msgstr "" #: gaphor/adapters/states/propertypages.py:79 msgid "Entry" msgstr "" #: gaphor/adapters/states/propertypages.py:86 #, fuzzy msgid "Exit" msgstr "_Editar" #: gaphor/adapters/states/propertypages.py:93 msgid "Do Activity" msgstr "" #: gaphor/misc/errorhandler.py:21 msgid "An error occured." msgstr "Ha ocurrido un error." #: gaphor/misc/errorhandler.py:28 msgid "" "\n" "\n" "Do you want to debug?\n" "(Gaphor should have been started from the command line)" msgstr "" "\n" "\n" "Quiere iniciar el debug?\n" "(Debe iniciar Gaphor desde la linea de comandos)" #: gaphor/plugins/xmiexport/__init__.py:39 #, fuzzy msgid "Export to XMI" msgstr "_Exportar" #: gaphor/plugins/xmiexport/__init__.py:40 msgid "Export model to XMI (XML Model Interchange) format" msgstr "" #: gaphor/services/diagramexportmanager.py:74 msgid "" "The file %s already exists. Do you want to replace it with the file you are " "exporting to?" msgstr "" #: gaphor/services/filemanager.py:135 msgid "" "Opening a new model will flush the currently loaded model.\n" "Any changes made will not be saved. Do you want to continue?" msgstr "" "Abrir un modelo nuevo borrará el modelo actual.\n" "Cualquier modificación no será guardada. ¿Desea continuar?" #: gaphor/services/filemanager.py:144 msgid "New model" msgstr "" #: gaphor/services/filemanager.py:147 msgid "main" msgstr "" #: gaphor/services/filemanager.py:163 msgid "Loading model from %s" msgstr "Cargando modelo desde %s" #: gaphor/services/filemanager.py:163 msgid "Loading..." msgstr "Cargando..." #: gaphor/services/filemanager.py:193 msgid "" "The model contains some references to items that are not maintained. Do you " "want to clean this before saving the model?" msgstr "" #: gaphor/services/filemanager.py:260 msgid "New from template" msgstr "" #: gaphor/services/filemanager.py:305 msgid "Save Gaphor model as" msgstr "Guardar el modelo Gaphor como" #: gaphor/storage/storage.py:186 msgid "Loading %d elements..." msgstr "Cargando %d elementos..." #: gaphor/ui/diagramtab.py:76 msgid "" msgstr "" #: gaphor/ui/diagramtoolbox.py:22 msgid "Pointer" msgstr "" #: gaphor/ui/diagramtoolbox.py:23 msgid "Line" msgstr "" #: gaphor/ui/diagramtoolbox.py:24 msgid "Box" msgstr "" #: gaphor/ui/diagramtoolbox.py:25 msgid "Ellipse" msgstr "" #: gaphor/ui/diagramtoolbox.py:27 msgid "Comment line" msgstr "" #: gaphor/ui/diagramtoolbox.py:28 msgid "Classes" msgstr "Clases" #: gaphor/ui/diagramtoolbox.py:29 #, fuzzy msgid "Class" msgstr "Clases" #: gaphor/ui/diagramtoolbox.py:30 #, fuzzy msgid "Interface" msgstr "Interacciones" #: gaphor/ui/diagramtoolbox.py:31 msgid "Package" msgstr "" #: gaphor/ui/diagramtoolbox.py:32 gaphor/ui/diagramtoolbox.py:66 #, fuzzy msgid "Association" msgstr "Acciones" #: gaphor/ui/diagramtoolbox.py:34 #, fuzzy msgid "Generalization" msgstr "Interacciones" #: gaphor/ui/diagramtoolbox.py:36 msgid "Components" msgstr "Componentes" #: gaphor/ui/diagramtoolbox.py:37 #, fuzzy msgid "Component" msgstr "Componentes" #: gaphor/ui/diagramtoolbox.py:38 msgid "Artifact" msgstr "" #: gaphor/ui/diagramtoolbox.py:39 msgid "Node" msgstr "" #: gaphor/ui/diagramtoolbox.py:40 msgid "Device" msgstr "" #: gaphor/ui/diagramtoolbox.py:41 msgid "Subsystem" msgstr "" #: gaphor/ui/diagramtoolbox.py:42 msgid "Connector" msgstr "" #: gaphor/ui/diagramtoolbox.py:43 msgid "Actions" msgstr "Acciones" #: gaphor/ui/diagramtoolbox.py:44 #, fuzzy msgid "Action" msgstr "Acciones" #: gaphor/ui/diagramtoolbox.py:45 msgid "Initial node" msgstr "" #: gaphor/ui/diagramtoolbox.py:46 msgid "Activity final node" msgstr "" #: gaphor/ui/diagramtoolbox.py:47 msgid "Flow final node" msgstr "" #: gaphor/ui/diagramtoolbox.py:48 msgid "Decision/merge node" msgstr "" #: gaphor/ui/diagramtoolbox.py:49 msgid "Fork/join node" msgstr "" #: gaphor/ui/diagramtoolbox.py:50 msgid "Object node" msgstr "" #: gaphor/ui/diagramtoolbox.py:51 msgid "Partition" msgstr "" #: gaphor/ui/diagramtoolbox.py:52 msgid "Control/object flow" msgstr "" #: gaphor/ui/diagramtoolbox.py:53 msgid "Interactions" msgstr "Interacciones" #: gaphor/ui/diagramtoolbox.py:54 msgid "Lifeline" msgstr "" #: gaphor/ui/diagramtoolbox.py:56 #, fuzzy msgid "Interaction" msgstr "Interacciones" #: gaphor/ui/diagramtoolbox.py:57 msgid "States" msgstr "" #: gaphor/ui/diagramtoolbox.py:58 msgid "State" msgstr "" #: gaphor/ui/diagramtoolbox.py:59 msgid "Initial Pseudostate" msgstr "" #: gaphor/ui/diagramtoolbox.py:60 msgid "Final State" msgstr "" #: gaphor/ui/diagramtoolbox.py:61 msgid "History Pseudostate" msgstr "" #: gaphor/ui/diagramtoolbox.py:62 msgid "Transition" msgstr "" #: gaphor/ui/diagramtoolbox.py:63 msgid "Use Cases" msgstr "Casos de Uso" #: gaphor/ui/diagramtoolbox.py:64 #, fuzzy msgid "Use case" msgstr "Casos de Uso" #: gaphor/ui/diagramtoolbox.py:65 #, fuzzy msgid "Actor" msgstr "Acciones" #: gaphor/ui/diagramtoolbox.py:67 msgid "Include" msgstr "" #: gaphor/ui/diagramtoolbox.py:68 msgid "Extend" msgstr "" #: gaphor/ui/diagramtoolbox.py:69 msgid "Profiles" msgstr "Perfiles" #: gaphor/ui/diagramtoolbox.py:70 #, fuzzy msgid "Profile" msgstr "Perfiles" #: gaphor/ui/diagramtoolbox.py:71 msgid "Metaclass" msgstr "" #: gaphor/ui/diagramtoolbox.py:73 msgid "Extension" msgstr "" #: gaphor/ui/elementeditor.py:23 msgid "Element Editor" msgstr "" #: gaphor/ui/elementeditor.py:48 #, fuzzy msgid "Editor" msgstr "_Editor" #: gaphor/ui/mainwindow.py:193 msgid "Save changed to your model before closing?" msgstr "" #: gaphor/ui/mainwindow.py:195 msgid "If you close without saving, your changes will be discarded." msgstr "" #: gaphor/ui/mainwindow.py:579 #, fuzzy msgid "Rename" msgstr "_Renombrar" #: gaphor/ui/mainwindow.py:592 msgid "_New diagram" msgstr "_Nuevo diagrama" #: gaphor/ui/mainwindow.py:609 msgid "_Delete diagram" msgstr "_Borrar diagrama" #: gaphor/ui/mainwindow.py:630 msgid "New _package" msgstr "" #: gaphor/ui/mainwindow.py:646 #, fuzzy msgid "Delete pac_kage" msgstr "_Borrar diagrama" #: gaphor/ui/mainwindow.py:654 msgid "_Refresh" msgstr "_Refrescar" #: gaphor/ui/mainwindow.py:659 msgid "_Reset tool" msgstr "" #: gaphor/ui/propertyeditor.py:28 #, fuzzy msgid "Properties" msgstr "Perfiles" #~ msgid "_New" #~ msgstr "_Nuevo" #~ msgid "Created a new model" #~ msgstr "Modelo nuevo creado" #~ msgid "_Revert..." #~ msgstr "_Deshacer..." #~ msgid "Could not load model file." #~ msgstr "No se pudo cargar el modelo del archivo." #~ msgid "_Open..." #~ msgstr "_Abrir..." #~ msgid "Save the model to a new file" #~ msgstr "Guarda el modelo a un nuevo archivo" #~ msgid "_Console" #~ msgstr "_Consola" #~ msgid "_Manual" #~ msgstr "_Manual" #~ msgid "_About" #~ msgstr "_Acerca de" #~ msgid "_Open" #~ msgstr "_Abrir" #~ msgid "_Delete" #~ msgstr "_Borrar" #~ msgid "_Undo" #~ msgstr "_Deshacer" #~ msgid "(from %s)" #~ msgstr "(desde %s)" #~ msgid "_File" #~ msgstr "_Archivo" #~ msgid "Recent files" #~ msgstr "Archivos recientes" #~ msgid "_Import" #~ msgstr "_Importar" #~ msgid "_Diagram" #~ msgstr "_Diagrama" #~ msgid "Tools" #~ msgstr "Herramientas" #~ msgid "_Window" #~ msgstr "_Ventana" #~ msgid "_Help" #~ msgstr "_Ayuda" gaphor-0.17.2/po/fr.po000066400000000000000000000254501220151210700144430ustar00rootroot00000000000000# French Gaphor Translation # Copyleft # Antonin Delpeuch 2010 # Note about the translation of the UML vocabulary : I used the translations used here : # http://laurent-audibert.developpez.com/Cours-UML/html/ msgid "" msgstr "" "Project-Id-Version: Gaphor 0.13.1\n" "POT-Creation-Date: 2010-06-20 10:40+CEST\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: Antonin Delpeuch \n" "Language-Team: LANGUAGE \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=CHARSET\n" "Content-Transfer-Encoding: UTF-8\n" "Generated-By: pygettext.py 1.5\n" #: gaphor/adapters/actions/partitionpage.py:30 msgid "External" msgstr "Externe" #: gaphor/adapters/profiles/metaclasseditor.py:31 #: gaphor/adapters/propertypages.py:552 msgid "Name" msgstr "Nom" #: gaphor/adapters/profiles/stereotypespage.py:140 msgid "Attribute" msgstr "Attribut" #: gaphor/adapters/profiles/stereotypespage.py:140 #: gaphor/ui/diagramtoolbox.py:72 msgid "Stereotype" msgstr "Stéréotype" #: gaphor/adapters/profiles/stereotypespage.py:167 msgid "Value" msgstr "Valeur" #: gaphor/adapters/profiles/stereotypespage.py:219 msgid "Show stereotypes attributes" msgstr "Afficher les attributs des stéréotypes" #: gaphor/adapters/propertypages.py:505 gaphor/ui/diagramtoolbox.py:26 msgid "Comment" msgstr "Commentaire" #: gaphor/adapters/propertypages.py:627 msgid "Abstract" msgstr "Abstrait" #: gaphor/adapters/propertypages.py:661 msgid "Folded" msgstr "Plié" #: gaphor/adapters/propertypages.py:737 msgid "Show attributes" msgstr "Afficher les attributs" #: gaphor/adapters/propertypages.py:754 msgid "Attributes" msgstr "Attributs" #: gaphor/adapters/propertypages.py:754 gaphor/adapters/propertypages.py:827 msgid "S" msgstr "S" #: gaphor/adapters/propertypages.py:811 msgid "Show operations" msgstr "Afficher les opérations" #: gaphor/adapters/propertypages.py:827 msgid "Operation" msgstr "Opération" #: gaphor/adapters/propertypages.py:874 gaphor/ui/diagramtoolbox.py:33 msgid "Dependency" msgstr "Dépendance" #: gaphor/adapters/propertypages.py:875 msgid "Usage" msgstr "Utilisation" #: gaphor/adapters/propertypages.py:876 msgid "Realization" msgstr "Réalisation" #: gaphor/adapters/propertypages.py:877 gaphor/ui/diagramtoolbox.py:35 msgid "Implementation" msgstr "Implémentation" #: gaphor/adapters/propertypages.py:889 msgid "Dependency type" msgstr "Type de dépendance" #: gaphor/adapters/propertypages.py:897 msgid "Automatic" msgstr "Automatique" #: gaphor/adapters/propertypages.py:977 msgid "Show direction" msgstr "Montrer la direction" #: gaphor/adapters/propertypages.py:982 msgid "Invert Direction" msgstr "Inverser la direction" #: gaphor/adapters/propertypages.py:988 msgid "Head" msgstr "Tête" #: gaphor/adapters/propertypages.py:992 msgid "Tail" msgstr "Queue" #: gaphor/adapters/propertypages.py:1172 msgid "Orthogonal" msgstr "Orthogonal" #: gaphor/adapters/propertypages.py:1189 msgid "Horizontal" msgstr "Horizontal" #: gaphor/adapters/propertypages.py:1225 msgid "Upper bound" msgstr "Limite supérieure" #: gaphor/adapters/propertypages.py:1240 msgid "Ordering" msgstr "Classement" #: gaphor/adapters/propertypages.py:1288 msgid "Join specification" msgstr "Lier une spécification" #: gaphor/adapters/propertypages.py:1320 #: gaphor/adapters/states/propertypages.py:32 msgid "Guard" msgstr "Garde" #: gaphor/adapters/propertypages.py:1373 msgid "Indirectly instantiated" msgstr "Instancié indirectement" #: gaphor/adapters/propertypages.py:1408 gaphor/adapters/propertypages.py:1429 #: gaphor/adapters/propertypages.py:1436 gaphor/ui/diagramtoolbox.py:55 msgid "Message" msgstr "Message" #: gaphor/adapters/propertypages.py:1431 msgid "Additional Messages" msgstr "Messages additionnels" #: gaphor/adapters/propertypages.py:1438 msgid "Inverted Messages" msgstr "Messages inversés" #: gaphor/adapters/propertypages.py:1442 msgid "Message sort" msgstr "Tri des messages" #: gaphor/adapters/states/propertypages.py:79 msgid "Entry" msgstr "Entrée" #: gaphor/adapters/states/propertypages.py:86 msgid "Exit" msgstr "Sortie" #: gaphor/adapters/states/propertypages.py:93 msgid "Do Activity" msgstr "Fait une activité" #: gaphor/misc/errorhandler.py:21 msgid "An error occured." msgstr "Une erreur est survenue." #: gaphor/misc/errorhandler.py:28 msgid "" "\n" "\n" "Do you want to debug?\n" "(Gaphor should have been started from the command line)" msgstr "" "\n" "\n" "Voulez-vous débogger ?\n" "(Gaphor devrait avoir été lancé en ligne de commande)" #: gaphor/plugins/xmiexport/__init__.py:39 msgid "Export to XMI" msgstr "Exporter en XMI" #: gaphor/plugins/xmiexport/__init__.py:40 msgid "Export model to XMI (XML Model Interchange) format" msgstr "Exporter le modèle au format XMI (XML Model Interchange)" #: gaphor/services/diagramexportmanager.py:74 msgid "The file %s already exists. Do you want to replace it with the file you are exporting to?" msgstr "Le fichier %s existe déjà. Voulez-vous le remplacer par le fichier vers lequel vous exportez ?" #: gaphor/services/filemanager.py:135 msgid "" "Opening a new model will flush the currently loaded model.\n" "Any changes made will not be saved. Do you want to continue?" msgstr "Ouvrir un nouveau modèle va supprimer le modèle chargé actuellement.\n" "Les changements ne seront pas enregistrés. Voulez-vous continuer ?" #: gaphor/services/filemanager.py:144 msgid "New model" msgstr "Nouveau modèle" #: gaphor/services/filemanager.py:147 msgid "main" msgstr "principal" #: gaphor/services/filemanager.py:163 msgid "Loading model from %s" msgstr "Chargement du modèle depuis %s" #: gaphor/services/filemanager.py:163 msgid "Loading..." msgstr "Chargement..." #: gaphor/services/filemanager.py:193 msgid "The model contains some references to items that are not maintained. Do you want to clean this before saving the model?" msgstr "Le modèle contient des références à des items qui ne sont pas supportés. Voulez-vous les nettoyer avant de sauvegarder le modèle ?" #: gaphor/services/filemanager.py:260 msgid "New from template" msgstr "" #: gaphor/services/filemanager.py:305 msgid "Save Gaphor model as" msgstr "Enregistrer le modèle Gaphor sous" #: gaphor/storage/storage.py:186 msgid "Loading %d elements..." msgstr "Chargement de %d éléments..." #: gaphor/ui/diagramtab.py:76 msgid "" msgstr "" #: gaphor/ui/diagramtoolbox.py:22 msgid "Pointer" msgstr "Pointeur" #: gaphor/ui/diagramtoolbox.py:23 msgid "Line" msgstr "Ligne" #: gaphor/ui/diagramtoolbox.py:24 msgid "Box" msgstr "Boîte" #: gaphor/ui/diagramtoolbox.py:25 msgid "Ellipse" msgstr "Ellipse" #: gaphor/ui/diagramtoolbox.py:27 msgid "Comment line" msgstr "Ligne de commentaire" #: gaphor/ui/diagramtoolbox.py:28 msgid "Classes" msgstr "Classes" #: gaphor/ui/diagramtoolbox.py:29 msgid "Class" msgstr "Classe" #: gaphor/ui/diagramtoolbox.py:30 msgid "Interface" msgstr "Interface" #: gaphor/ui/diagramtoolbox.py:31 msgid "Package" msgstr "Paquetage" #: gaphor/ui/diagramtoolbox.py:32 gaphor/ui/diagramtoolbox.py:66 msgid "Association" msgstr "Association" #: gaphor/ui/diagramtoolbox.py:34 msgid "Generalization" msgstr "Généralisation" #: gaphor/ui/diagramtoolbox.py:36 msgid "Components" msgstr "Composants" #: gaphor/ui/diagramtoolbox.py:37 msgid "Component" msgstr "Composant" #: gaphor/ui/diagramtoolbox.py:38 msgid "Artifact" msgstr "Artefact" #: gaphor/ui/diagramtoolbox.py:39 msgid "Node" msgstr "NÅ“ud" #: gaphor/ui/diagramtoolbox.py:40 msgid "Device" msgstr "Appareil" #: gaphor/ui/diagramtoolbox.py:41 msgid "Subsystem" msgstr "Sous-système" #: gaphor/ui/diagramtoolbox.py:42 msgid "Connector" msgstr "Connecteur" #: gaphor/ui/diagramtoolbox.py:43 msgid "Actions" msgstr "Actions" #: gaphor/ui/diagramtoolbox.py:44 msgid "Action" msgstr "Action" #: gaphor/ui/diagramtoolbox.py:45 msgid "Initial node" msgstr "NÅ“ud initial" #: gaphor/ui/diagramtoolbox.py:46 msgid "Activity final node" msgstr "NÅ“ud de fin d'activité" #: gaphor/ui/diagramtoolbox.py:47 msgid "Flow final node" msgstr "NÅ“ud de fin de flot" #: gaphor/ui/diagramtoolbox.py:48 msgid "Decision/merge node" msgstr "NÅ“ud de décision et de fusion" #: gaphor/ui/diagramtoolbox.py:49 msgid "Fork/join node" msgstr "NÅ“ud de bifurcation et d'union" #: gaphor/ui/diagramtoolbox.py:50 msgid "Object node" msgstr "NÅ“ud d'objet" #: gaphor/ui/diagramtoolbox.py:51 msgid "Partition" msgstr "Partition" #: gaphor/ui/diagramtoolbox.py:52 msgid "Control/object flow" msgstr "Flot de contrôle et d'objet" #: gaphor/ui/diagramtoolbox.py:53 msgid "Interactions" msgstr "Interactions" #: gaphor/ui/diagramtoolbox.py:54 msgid "Lifeline" msgstr "Temps de vie" #: gaphor/ui/diagramtoolbox.py:56 msgid "Interaction" msgstr "Interaction" #: gaphor/ui/diagramtoolbox.py:57 msgid "States" msgstr "États" #: gaphor/ui/diagramtoolbox.py:58 msgid "State" msgstr "État" #: gaphor/ui/diagramtoolbox.py:59 msgid "Initial Pseudostate" msgstr "Pseudo-état initial" #: gaphor/ui/diagramtoolbox.py:60 msgid "Final State" msgstr "État Final" #: gaphor/ui/diagramtoolbox.py:61 msgid "History Pseudostate" msgstr "Pseudo-état historique" #: gaphor/ui/diagramtoolbox.py:62 msgid "Transition" msgstr "Transition" #: gaphor/ui/diagramtoolbox.py:63 msgid "Use Cases" msgstr "Cas d'Utilisation" #: gaphor/ui/diagramtoolbox.py:64 msgid "Use case" msgstr "Cas d'utilisation" #: gaphor/ui/diagramtoolbox.py:65 msgid "Actor" msgstr "Acteur" #: gaphor/ui/diagramtoolbox.py:67 msgid "Include" msgstr "Inclure" #: gaphor/ui/diagramtoolbox.py:68 msgid "Extend" msgstr "Étendre" #: gaphor/ui/diagramtoolbox.py:69 msgid "Profiles" msgstr "Profils" #: gaphor/ui/diagramtoolbox.py:70 msgid "Profile" msgstr "Profil" #: gaphor/ui/diagramtoolbox.py:71 msgid "Metaclass" msgstr "Méta-classe" #: gaphor/ui/diagramtoolbox.py:73 msgid "Extension" msgstr "Extension" #: gaphor/ui/elementeditor.py:23 msgid "Element Editor" msgstr "Editeur d'éléments" #: gaphor/ui/elementeditor.py:48 msgid "Editor" msgstr "Editeur" #: gaphor/ui/mainwindow.py:193 msgid "Save changed to your model before closing?" msgstr "Sauvegarder les changements de votre modèle avant de fermer ?" #: gaphor/ui/mainwindow.py:195 msgid "If you close without saving, your changes will be discarded." msgstr "Si vous fermez sans sauvegarder, vos changements seront perdus." #: gaphor/ui/mainwindow.py:579 msgid "Rename" msgstr "Renommer" #: gaphor/ui/mainwindow.py:592 msgid "_New diagram" msgstr "_Nouveau diagramme" #: gaphor/ui/mainwindow.py:609 msgid "_Delete diagram" msgstr "Supprimer le _diagramme" #: gaphor/ui/mainwindow.py:630 msgid "New _package" msgstr "Nouveau _paquetage" #: gaphor/ui/mainwindow.py:646 msgid "Delete pac_kage" msgstr "Supprimer le paquetage (_k)" #: gaphor/ui/mainwindow.py:654 msgid "_Refresh" msgstr "_Réactualiser" #: gaphor/ui/mainwindow.py:659 msgid "_Reset tool" msgstr "_Réinitialiser l'outil" #: gaphor/ui/propertyeditor.py:28 msgid "Properties" msgstr "Propriétés" gaphor-0.17.2/po/nl.po000066400000000000000000000252561220151210700144510ustar00rootroot00000000000000# Dutch translations for Gaphor # # Copyright (C) 2004 Arjan Molenaar # Arjan Molenaar , 2004. # msgid "" msgstr "" "Project-Id-Version: 0.6.0\n" "POT-Creation-Date: Thu Jun 10 15:59:38 2010\n" "PO-Revision-Date: 2004-06-10 08:00+001\n" "Last-Translator: Arjan Molenaar \n" "Language-Team: nl\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: pygettext.py 1.4\n" #: gaphor/adapters/actions/partitionpage.py:30 msgid "External" msgstr "" #: gaphor/adapters/profiles/metaclasseditor.py:31 #: gaphor/adapters/propertypages.py:552 msgid "Name" msgstr "" #: gaphor/adapters/profiles/stereotypespage.py:140 msgid "Attribute" msgstr "" #: gaphor/adapters/profiles/stereotypespage.py:140 #: gaphor/ui/diagramtoolbox.py:72 msgid "Stereotype" msgstr "" #: gaphor/adapters/profiles/stereotypespage.py:167 msgid "Value" msgstr "" #: gaphor/adapters/profiles/stereotypespage.py:219 msgid "Show stereotypes attributes" msgstr "" #: gaphor/adapters/propertypages.py:505 gaphor/ui/diagramtoolbox.py:26 #, fuzzy msgid "Comment" msgstr "Componenten" #: gaphor/adapters/propertypages.py:627 msgid "Abstract" msgstr "" #: gaphor/adapters/propertypages.py:661 msgid "Folded" msgstr "" #: gaphor/adapters/propertypages.py:737 msgid "Show attributes" msgstr "" #: gaphor/adapters/propertypages.py:754 msgid "Attributes" msgstr "" #: gaphor/adapters/propertypages.py:754 gaphor/adapters/propertypages.py:827 msgid "S" msgstr "" #: gaphor/adapters/propertypages.py:811 msgid "Show operations" msgstr "" #: gaphor/adapters/propertypages.py:827 #, fuzzy msgid "Operation" msgstr "Interacties" #: gaphor/adapters/propertypages.py:874 gaphor/ui/diagramtoolbox.py:33 msgid "Dependency" msgstr "" #: gaphor/adapters/propertypages.py:875 msgid "Usage" msgstr "" #: gaphor/adapters/propertypages.py:876 msgid "Realization" msgstr "" #: gaphor/adapters/propertypages.py:877 gaphor/ui/diagramtoolbox.py:35 #, fuzzy msgid "Implementation" msgstr "Interacties" #: gaphor/adapters/propertypages.py:889 msgid "Dependency type" msgstr "" #: gaphor/adapters/propertypages.py:897 msgid "Automatic" msgstr "" #: gaphor/adapters/propertypages.py:977 msgid "Show direction" msgstr "" #: gaphor/adapters/propertypages.py:982 #, fuzzy msgid "Invert Direction" msgstr "Interacties" #: gaphor/adapters/propertypages.py:988 msgid "Head" msgstr "" #: gaphor/adapters/propertypages.py:992 msgid "Tail" msgstr "" #: gaphor/adapters/propertypages.py:1181 msgid "Orthogonal" msgstr "" #: gaphor/adapters/propertypages.py:1198 msgid "Horizontal" msgstr "" #: gaphor/adapters/propertypages.py:1234 msgid "Upper bound" msgstr "" #: gaphor/adapters/propertypages.py:1249 msgid "Ordering" msgstr "" #: gaphor/adapters/propertypages.py:1297 msgid "Join specification" msgstr "" #: gaphor/adapters/propertypages.py:1329 #: gaphor/adapters/states/propertypages.py:32 msgid "Guard" msgstr "" #: gaphor/adapters/propertypages.py:1382 msgid "Indirectly instantiated" msgstr "" #: gaphor/adapters/propertypages.py:1417 gaphor/adapters/propertypages.py:1438 #: gaphor/adapters/propertypages.py:1445 gaphor/ui/diagramtoolbox.py:55 msgid "Message" msgstr "" #: gaphor/adapters/propertypages.py:1440 msgid "Additional Messages" msgstr "" #: gaphor/adapters/propertypages.py:1447 msgid "Inverted Messages" msgstr "" #: gaphor/adapters/propertypages.py:1451 msgid "Message sort" msgstr "" #: gaphor/adapters/states/propertypages.py:79 msgid "Entry" msgstr "" #: gaphor/adapters/states/propertypages.py:86 #, fuzzy msgid "Exit" msgstr "_Wijzig" #: gaphor/adapters/states/propertypages.py:93 msgid "Do Activity" msgstr "" #: gaphor/misc/errorhandler.py:21 msgid "An error occured." msgstr "Er is een fout opgetreden" #: gaphor/misc/errorhandler.py:28 msgid "" "\n" "\n" "Do you want to debug?\n" "(Gaphor should have been started from the command line)" msgstr "" "\n" "\n" "Wilt u debuggen?(Gaphor moet gestart zijn vanaf de commando regel)" #: gaphor/plugins/xmiexport/__init__.py:39 #, fuzzy msgid "Export to XMI" msgstr "_Exporteer" #: gaphor/plugins/xmiexport/__init__.py:40 msgid "Export model to XMI (XML Model Interchange) format" msgstr "" #: gaphor/services/diagramexportmanager.py:74 msgid "" "The file %s already exists. Do you want to replace it with the file you are " "exporting to?" msgstr "" #: gaphor/services/filemanager.py:135 msgid "" "Opening a new model will flush the currently loaded model.\n" "Any changes made will not be saved. Do you want to continue?" msgstr "" "Het openen van een nieuw model zal het huidige model vervangen.\n" "Veranderingen zullen niet eerst opgeslagen worden. Wilt u doorgaan?" #: gaphor/services/filemanager.py:144 msgid "New model" msgstr "Nieuw model" #: gaphor/services/filemanager.py:147 msgid "main" msgstr "" #: gaphor/services/filemanager.py:163 msgid "Loading model from %s" msgstr "Laden van model %s" #: gaphor/services/filemanager.py:163 msgid "Loading..." msgstr "Bezig met laden..." #: gaphor/services/filemanager.py:193 msgid "" "The model contains some references to items that are not maintained. Do you " "want to clean this before saving the model?" msgstr "" #: gaphor/services/filemanager.py:260 msgid "New from template" msgstr "" #: gaphor/services/filemanager.py:305 msgid "Save Gaphor model as" msgstr "Sla Gaphor model op als" #: gaphor/storage/storage.py:186 msgid "Loading %d elements..." msgstr "Laden van %s elementen..." #: gaphor/ui/diagramtab.py:76 msgid "" msgstr "" #: gaphor/ui/diagramtoolbox.py:22 msgid "Pointer" msgstr "" #: gaphor/ui/diagramtoolbox.py:23 msgid "Line" msgstr "" #: gaphor/ui/diagramtoolbox.py:24 msgid "Box" msgstr "" #: gaphor/ui/diagramtoolbox.py:25 msgid "Ellipse" msgstr "" #: gaphor/ui/diagramtoolbox.py:27 msgid "Comment line" msgstr "" #: gaphor/ui/diagramtoolbox.py:28 msgid "Classes" msgstr "Klassen" #: gaphor/ui/diagramtoolbox.py:29 #, fuzzy msgid "Class" msgstr "Klassen" #: gaphor/ui/diagramtoolbox.py:30 #, fuzzy msgid "Interface" msgstr "Interacties" #: gaphor/ui/diagramtoolbox.py:31 msgid "Package" msgstr "" #: gaphor/ui/diagramtoolbox.py:32 gaphor/ui/diagramtoolbox.py:66 #, fuzzy msgid "Association" msgstr "Acties" #: gaphor/ui/diagramtoolbox.py:34 #, fuzzy msgid "Generalization" msgstr "Interacties" #: gaphor/ui/diagramtoolbox.py:36 msgid "Components" msgstr "Componenten" #: gaphor/ui/diagramtoolbox.py:37 #, fuzzy msgid "Component" msgstr "Componenten" #: gaphor/ui/diagramtoolbox.py:38 msgid "Artifact" msgstr "" #: gaphor/ui/diagramtoolbox.py:39 #, fuzzy msgid "Node" msgstr "Nieuw model" #: gaphor/ui/diagramtoolbox.py:40 msgid "Device" msgstr "" #: gaphor/ui/diagramtoolbox.py:41 msgid "Subsystem" msgstr "" #: gaphor/ui/diagramtoolbox.py:42 msgid "Connector" msgstr "" #: gaphor/ui/diagramtoolbox.py:43 msgid "Actions" msgstr "Acties" #: gaphor/ui/diagramtoolbox.py:44 #, fuzzy msgid "Action" msgstr "Acties" #: gaphor/ui/diagramtoolbox.py:45 msgid "Initial node" msgstr "" #: gaphor/ui/diagramtoolbox.py:46 msgid "Activity final node" msgstr "" #: gaphor/ui/diagramtoolbox.py:47 msgid "Flow final node" msgstr "" #: gaphor/ui/diagramtoolbox.py:48 msgid "Decision/merge node" msgstr "" #: gaphor/ui/diagramtoolbox.py:49 msgid "Fork/join node" msgstr "" #: gaphor/ui/diagramtoolbox.py:50 msgid "Object node" msgstr "" #: gaphor/ui/diagramtoolbox.py:51 msgid "Partition" msgstr "" #: gaphor/ui/diagramtoolbox.py:52 msgid "Control/object flow" msgstr "" #: gaphor/ui/diagramtoolbox.py:53 #, fuzzy msgid "Interactions" msgstr "Interacties" #: gaphor/ui/diagramtoolbox.py:54 msgid "Lifeline" msgstr "" #: gaphor/ui/diagramtoolbox.py:56 #, fuzzy msgid "Interaction" msgstr "Interacties" #: gaphor/ui/diagramtoolbox.py:57 msgid "States" msgstr "" #: gaphor/ui/diagramtoolbox.py:58 msgid "State" msgstr "" #: gaphor/ui/diagramtoolbox.py:59 msgid "Initial Pseudostate" msgstr "" #: gaphor/ui/diagramtoolbox.py:60 msgid "Final State" msgstr "" #: gaphor/ui/diagramtoolbox.py:61 msgid "History Pseudostate" msgstr "" #: gaphor/ui/diagramtoolbox.py:62 msgid "Transition" msgstr "" #: gaphor/ui/diagramtoolbox.py:63 msgid "Use Cases" msgstr "Use cases" #: gaphor/ui/diagramtoolbox.py:64 #, fuzzy msgid "Use case" msgstr "Use cases" #: gaphor/ui/diagramtoolbox.py:65 #, fuzzy msgid "Actor" msgstr "Acties" #: gaphor/ui/diagramtoolbox.py:67 msgid "Include" msgstr "" #: gaphor/ui/diagramtoolbox.py:68 msgid "Extend" msgstr "" #: gaphor/ui/diagramtoolbox.py:69 msgid "Profiles" msgstr "Profielen" #: gaphor/ui/diagramtoolbox.py:70 #, fuzzy msgid "Profile" msgstr "Profielen" #: gaphor/ui/diagramtoolbox.py:71 msgid "Metaclass" msgstr "" #: gaphor/ui/diagramtoolbox.py:73 msgid "Extension" msgstr "" #: gaphor/ui/elementeditor.py:23 msgid "Element Editor" msgstr "" #: gaphor/ui/elementeditor.py:48 #, fuzzy msgid "Editor" msgstr "_Wijzig" #: gaphor/ui/mainwindow.py:193 msgid "Save changed to your model before closing?" msgstr "" #: gaphor/ui/mainwindow.py:195 msgid "If you close without saving, your changes will be discarded." msgstr "" #: gaphor/ui/mainwindow.py:579 #, fuzzy msgid "Rename" msgstr "_Hernoem" #: gaphor/ui/mainwindow.py:592 #, fuzzy msgid "_New diagram" msgstr "_Nieuw Diagram" #: gaphor/ui/mainwindow.py:609 #, fuzzy msgid "_Delete diagram" msgstr "Verwijder _Diagram" #: gaphor/ui/mainwindow.py:630 msgid "New _package" msgstr "" #: gaphor/ui/mainwindow.py:646 #, fuzzy msgid "Delete pac_kage" msgstr "Verwijder _Diagram" #: gaphor/ui/mainwindow.py:654 msgid "_Refresh" msgstr "Ve_rvers" #: gaphor/ui/mainwindow.py:659 msgid "_Reset tool" msgstr "" #: gaphor/ui/propertyeditor.py:28 #, fuzzy msgid "Properties" msgstr "Profielen" #~ msgid "Created a new model" #~ msgstr "Maak een nieuw model" #~ msgid "_Revert..." #~ msgstr "_Herlaad..." #~ msgid "Could not load model file." #~ msgstr "Kan model bestand niet laden." #~ msgid "_Open..." #~ msgstr "_Open..." #~ msgid "Save the model to a new file" #~ msgstr "Sla het model op in een nieuw bestand" #~ msgid "_Console" #~ msgstr "_Console" #~ msgid "_Manual" #~ msgstr "_Handleiding" #~ msgid "_About" #~ msgstr "_Over Gaphor" #~ msgid "_Open" #~ msgstr "_Open" #~ msgid "_Delete" #~ msgstr "Verwij_der" #, fuzzy #~ msgid "_Undo" #~ msgstr "_Ongedaan maken" #~ msgid "(from %s)" #~ msgstr "(van %s)" #~ msgid "_File" #~ msgstr "_Bestand" #~ msgid "Recent files" #~ msgstr "Recente bestanden" #, fuzzy #~ msgid "_Import" #~ msgstr "_Exporteer" #~ msgid "_Diagram" #~ msgstr "_Diagram" #~ msgid "Tools" #~ msgstr "Hulpmiddelen" #~ msgid "_Window" #~ msgstr "_Venster" #~ msgid "_Help" #~ msgstr "_Help" #~ msgid "Quit Gaphor?" #~ msgstr "Beeindig Gaphor?" #~ msgid "To be implemented" #~ msgstr "Not niet gemaakt" #~ msgid "Error occured while in worker thread" #~ msgstr "Er is een fout opgetreden in de worker thread" gaphor-0.17.2/po/pt_BR.po000066400000000000000000000263241220151210700150430ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: Gaphor 0.14.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2010-05-08 06:05-0300\n" "PO-Revision-Date: \n" "Last-Translator: Ygor Mutti \n" "Language-Team: pt_BR\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Poedit-SourceCharset: utf-8\n" "X-Poedit-Basepath: /home/ygor\n" "X-Poedit-Language: Portuguese\n" "X-Poedit-Country: BRAZIL\n" "X-Poedit-SearchPath-0: gaphor/gaphor\n" #: gaphor/gaphor/adapters/propertypages.py:505 msgid "Comment" msgstr "Comentário" #: gaphor/gaphor/adapters/propertypages.py:552 msgid "Name" msgstr "Nome" #: gaphor/gaphor/adapters/propertypages.py:627 #, fuzzy msgid "Abstract" msgstr "Abstrato" #: gaphor/gaphor/adapters/propertypages.py:661 msgid "Folded" msgstr "Dobrado" #: gaphor/gaphor/adapters/propertypages.py:737 msgid "Show attributes" msgstr "Mostrar atributos" #: gaphor/gaphor/adapters/propertypages.py:754 msgid "Attributes" msgstr "Atributos" #: gaphor/gaphor/adapters/propertypages.py:754 #: gaphor/gaphor/adapters/propertypages.py:827 msgid "S" msgstr "S" #: gaphor/gaphor/adapters/propertypages.py:811 msgid "Show operations" msgstr "Mostrar operações" #: gaphor/gaphor/adapters/propertypages.py:827 msgid "Operation" msgstr "Operação" #: gaphor/gaphor/adapters/propertypages.py:874 msgid "Dependency" msgstr "Dependência" #: gaphor/gaphor/adapters/propertypages.py:875 msgid "Usage" msgstr "Uso" #: gaphor/gaphor/adapters/propertypages.py:876 msgid "Realization" msgstr "Realização" #: gaphor/gaphor/adapters/propertypages.py:877 msgid "Implementation" msgstr "Implementação" #: gaphor/gaphor/adapters/propertypages.py:889 msgid "Dependency type" msgstr "Tipo de dependência" #: gaphor/gaphor/adapters/propertypages.py:897 msgid "Automatic" msgstr "Automático" #: gaphor/gaphor/adapters/propertypages.py:977 msgid "Show direction" msgstr "Mostrar direção" #: gaphor/gaphor/adapters/propertypages.py:982 msgid "Invert Direction" msgstr "Inverter direção" #: gaphor/gaphor/adapters/propertypages.py:988 #, fuzzy msgid "Head" msgstr "Cabeça" #: gaphor/gaphor/adapters/propertypages.py:992 #, fuzzy msgid "Tail" msgstr "Cauda" #: gaphor/gaphor/adapters/propertypages.py:1181 msgid "Orthogonal" msgstr "Ortogonal" #: gaphor/gaphor/adapters/propertypages.py:1198 msgid "Horizontal" msgstr "Horizontal" #: gaphor/gaphor/adapters/propertypages.py:1234 msgid "Upper bound" msgstr "Limite superior" #: gaphor/gaphor/adapters/propertypages.py:1249 msgid "Ordering" msgstr "Ordenação" #: gaphor/gaphor/adapters/propertypages.py:1297 msgid "Join specification" msgstr "Juntar especificação" #: gaphor/gaphor/adapters/propertypages.py:1329 msgid "Guard" msgstr "Guarda" #: gaphor/gaphor/adapters/propertypages.py:1382 #, fuzzy msgid "Indirectly instantiated" msgstr "Indiretamente instanciado" #: gaphor/gaphor/adapters/propertypages.py:1417 #: gaphor/gaphor/adapters/propertypages.py:1438 #: gaphor/gaphor/adapters/propertypages.py:1445 msgid "Message" msgstr "Mensagem" #: gaphor/gaphor/adapters/propertypages.py:1440 msgid "Additional Messages" msgstr "Mensagens adicionais" #: gaphor/gaphor/adapters/propertypages.py:1447 msgid "Inverted Messages" msgstr "Mensagens invertidas" #: gaphor/gaphor/adapters/propertypages.py:1451 #, fuzzy msgid "Message sort" msgstr "Tipo de mensagem" #: gaphor/gaphor/adapters/actions/partitionpage.py:30 msgid "External" msgstr "Externo" #: gaphor/gaphor/adapters/profiles/stereotypespage.py:140 msgid "Stereotype" msgstr "Estereótipo" #: gaphor/gaphor/adapters/profiles/stereotypespage.py:140 msgid "Attribute" msgstr "Atributo" #: gaphor/gaphor/adapters/profiles/stereotypespage.py:167 msgid "Value" msgstr "Valor" #: gaphor/gaphor/adapters/profiles/stereotypespage.py:219 msgid "Show stereotypes attributes" msgstr "Mostrar atributos do estereótipo" #: gaphor/gaphor/adapters/states/propertypages.py:79 msgid "Entry" msgstr "Entrada" #: gaphor/gaphor/adapters/states/propertypages.py:86 msgid "Exit" msgstr "Sair" #: gaphor/gaphor/adapters/states/propertypages.py:93 #, fuzzy msgid "Do Activity" msgstr "Realizar atividade" #: gaphor/gaphor/misc/errorhandler.py:21 msgid "An error occured." msgstr "Ocorreu um erro." #: gaphor/gaphor/misc/errorhandler.py:28 msgid "" "\n" "\n" "Do you want to debug?\n" "(Gaphor should have been started from the command line)" msgstr "" "\n" "\n" "Deseja depurar?\n" "(O Gaphor deveria ter sido iniciado pela linha de comando)" #: gaphor/gaphor/plugins/xmiexport/__init__.py:39 msgid "Export to XMI" msgstr "Exportar para XMI" #: gaphor/gaphor/plugins/xmiexport/__init__.py:40 msgid "Export model to XMI (XML Model Interchange) format" msgstr "Exportar modelo para o formato XMI (XML Model Interchange)" #: gaphor/gaphor/services/diagramexportmanager.py:74 #, python-format msgid "The file %s already exists. Do you want to replace it with the file you are exporting to?" msgstr "O arquivo %s já existe. Deseja substituí-lo pelo arquivo que você está exportando?" #: gaphor/gaphor/services/filemanager.py:135 msgid "" "Opening a new model will flush the currently loaded model.\n" "Any changes made will not be saved. Do you want to continue?" msgstr "" "Abrir um novo modelo apagará o modelo carregado atualmente.\n" "Qualquer modificação que foi feita não será salva. Deseja continuar?" #: gaphor/gaphor/services/filemanager.py:144 msgid "New model" msgstr "Novo modelo" #: gaphor/gaphor/services/filemanager.py:147 msgid "main" msgstr "principal" #: gaphor/gaphor/services/filemanager.py:163 msgid "Loading..." msgstr "Carregando..." #: gaphor/gaphor/services/filemanager.py:163 #, python-format msgid "Loading model from %s" msgstr "Carregando modelo em %s" #: gaphor/gaphor/services/filemanager.py:193 msgid "The model contains some references to items that are not maintained. Do you want to clean this before saving the model?" msgstr "O modelo contém algumas referências a elementos que não são mantidos. Deseja limpá-las antes de salvar o modelo?" #: gaphor/gaphor/services/filemanager.py:260 msgid "New from template" msgstr "Novo a partir de template" #: gaphor/gaphor/services/filemanager.py:305 msgid "Save Gaphor model as" msgstr "Salvar modelo do Gaphor como" #: gaphor/gaphor/storage/storage.py:186 #, python-format msgid "Loading %d elements..." msgstr "Carregando %d elementos..." #: gaphor/gaphor/ui/diagramtab.py:76 msgid "" msgstr "" #: gaphor/gaphor/ui/diagramtoolbox.py:22 msgid "Pointer" msgstr "Ponteiro" #: gaphor/gaphor/ui/diagramtoolbox.py:23 msgid "Line" msgstr "Linha" #: gaphor/gaphor/ui/diagramtoolbox.py:24 msgid "Box" msgstr "Caixa" #: gaphor/gaphor/ui/diagramtoolbox.py:25 msgid "Ellipse" msgstr "Elipse" #: gaphor/gaphor/ui/diagramtoolbox.py:27 msgid "Comment line" msgstr "Linha de comentário" #: gaphor/gaphor/ui/diagramtoolbox.py:28 msgid "Classes" msgstr "Classes" #: gaphor/gaphor/ui/diagramtoolbox.py:29 msgid "Class" msgstr "Classe" #: gaphor/gaphor/ui/diagramtoolbox.py:30 msgid "Interface" msgstr "Interface" #: gaphor/gaphor/ui/diagramtoolbox.py:31 msgid "Package" msgstr "Pacote" #: gaphor/gaphor/ui/diagramtoolbox.py:32 #: gaphor/gaphor/ui/diagramtoolbox.py:66 msgid "Association" msgstr "Associação" #: gaphor/gaphor/ui/diagramtoolbox.py:34 msgid "Generalization" msgstr "Generalização" #: gaphor/gaphor/ui/diagramtoolbox.py:36 msgid "Components" msgstr "Componentes" #: gaphor/gaphor/ui/diagramtoolbox.py:37 msgid "Component" msgstr "Componente" #: gaphor/gaphor/ui/diagramtoolbox.py:38 msgid "Artifact" msgstr "Artefato" #: gaphor/gaphor/ui/diagramtoolbox.py:39 msgid "Node" msgstr "Nó" #: gaphor/gaphor/ui/diagramtoolbox.py:40 msgid "Device" msgstr "Dispositivo" #: gaphor/gaphor/ui/diagramtoolbox.py:41 msgid "Subsystem" msgstr "Subsistema" #: gaphor/gaphor/ui/diagramtoolbox.py:42 msgid "Connector" msgstr "Conector" #: gaphor/gaphor/ui/diagramtoolbox.py:43 msgid "Actions" msgstr "Ações" #: gaphor/gaphor/ui/diagramtoolbox.py:44 msgid "Action" msgstr "Ação" #: gaphor/gaphor/ui/diagramtoolbox.py:45 msgid "Initial node" msgstr "Nó inicial" #: gaphor/gaphor/ui/diagramtoolbox.py:46 msgid "Activity final node" msgstr "Nó final de atividade" #: gaphor/gaphor/ui/diagramtoolbox.py:47 msgid "Flow final node" msgstr "Nó final de fluxo" #: gaphor/gaphor/ui/diagramtoolbox.py:48 msgid "Decision/merge node" msgstr "Nó de decisão/intercalação" #: gaphor/gaphor/ui/diagramtoolbox.py:49 msgid "Fork/join node" msgstr "Nó de separação/junção" #: gaphor/gaphor/ui/diagramtoolbox.py:50 msgid "Object node" msgstr "Nó de objeto" #: gaphor/gaphor/ui/diagramtoolbox.py:51 msgid "Partition" msgstr "Partição" #: gaphor/gaphor/ui/diagramtoolbox.py:52 msgid "Control/object flow" msgstr "Fluxo de controle/objeto" #: gaphor/gaphor/ui/diagramtoolbox.py:53 msgid "Interactions" msgstr "Interações" #: gaphor/gaphor/ui/diagramtoolbox.py:54 msgid "Lifeline" msgstr "Linha de vida" #: gaphor/gaphor/ui/diagramtoolbox.py:56 msgid "Interaction" msgstr "Interação" #: gaphor/gaphor/ui/diagramtoolbox.py:57 msgid "States" msgstr "Estados" #: gaphor/gaphor/ui/diagramtoolbox.py:58 msgid "State" msgstr "Estado" #: gaphor/gaphor/ui/diagramtoolbox.py:59 msgid "Initial Pseudostate" msgstr "Pseudo-estado Inicial" #: gaphor/gaphor/ui/diagramtoolbox.py:60 msgid "Final State" msgstr "Estado Final" #: gaphor/gaphor/ui/diagramtoolbox.py:61 #, fuzzy msgid "History Pseudostate" msgstr "Pseudo-estado de História" #: gaphor/gaphor/ui/diagramtoolbox.py:62 msgid "Transition" msgstr "Transição" #: gaphor/gaphor/ui/diagramtoolbox.py:63 msgid "Use Cases" msgstr "Casos de uso" #: gaphor/gaphor/ui/diagramtoolbox.py:64 msgid "Use case" msgstr "Caso de uso" #: gaphor/gaphor/ui/diagramtoolbox.py:65 msgid "Actor" msgstr "Ator" #: gaphor/gaphor/ui/diagramtoolbox.py:67 msgid "Include" msgstr "Inclui" #: gaphor/gaphor/ui/diagramtoolbox.py:68 msgid "Extend" msgstr "Extende" #: gaphor/gaphor/ui/diagramtoolbox.py:69 msgid "Profiles" msgstr "Perfis" #: gaphor/gaphor/ui/diagramtoolbox.py:70 msgid "Profile" msgstr "Perfil" #: gaphor/gaphor/ui/diagramtoolbox.py:71 msgid "Metaclass" msgstr "Metaclasse" #: gaphor/gaphor/ui/diagramtoolbox.py:73 msgid "Extension" msgstr "Extensão" #: gaphor/gaphor/ui/elementeditor.py:23 msgid "Element Editor" msgstr "Editor de Elemento" #: gaphor/gaphor/ui/elementeditor.py:48 msgid "Editor" msgstr "Editor" #: gaphor/gaphor/ui/mainwindow.py:193 msgid "Save changed to your model before closing?" msgstr "Salvar as modificações no modelo antes de fechar?" #: gaphor/gaphor/ui/mainwindow.py:195 msgid "If you close without saving, your changes will be discarded." msgstr "Se você fechar sem salvar, suas modifcações serão descartadas." #: gaphor/gaphor/ui/mainwindow.py:578 msgid "Rename" msgstr "Renomear" #: gaphor/gaphor/ui/mainwindow.py:591 msgid "_New diagram" msgstr "_Novo diagrama" #: gaphor/gaphor/ui/mainwindow.py:608 msgid "_Delete diagram" msgstr "_Excluir diagrama" #: gaphor/gaphor/ui/mainwindow.py:629 msgid "New _package" msgstr "Novo _pacote" #: gaphor/gaphor/ui/mainwindow.py:645 msgid "Delete pac_kage" msgstr "Excluir pa_cote" #: gaphor/gaphor/ui/mainwindow.py:653 msgid "_Refresh" msgstr "_Atualizar" #: gaphor/gaphor/ui/mainwindow.py:658 msgid "_Reset tool" msgstr "_Reiniciar ferramenta" #: gaphor/gaphor/ui/propertyeditor.py:28 msgid "Properties" msgstr "Propriedades" gaphor-0.17.2/po/ru.po000066400000000000000000000276771220151210700144770ustar00rootroot00000000000000# Russian Gaphor Translation # Copyleft # Евгений Лежнин , 2011 msgid "" msgstr "" "Project-Id-Version: Gaphor 0.13.1\n" "POT-Creation-Date: 2010-06-20 10:40+CEST\n" "PO-Revision-Date: 2011-08-22 22:15+0700\n" "Last-Translator: Евгений Лежнин \n" "Language-Team: Russian\n" "Language: ru\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: UTF-8\n" "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%" "10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" "X-Generator: Virtaal 0.6.1\n" "Generated-By: pygettext.py 1.5\n" #: gaphor/adapters/actions/partitionpage.py:30 msgid "External" msgstr "Внешний" #: gaphor/adapters/profiles/metaclasseditor.py:31 #: gaphor/adapters/propertypages.py:552 msgid "Name" msgstr "ИмÑ" #: gaphor/adapters/profiles/stereotypespage.py:140 msgid "Attribute" msgstr "Ðтрибут" #: gaphor/adapters/profiles/stereotypespage.py:140 #: gaphor/ui/diagramtoolbox.py:72 msgid "Stereotype" msgstr "Стереотип" #: gaphor/adapters/profiles/stereotypespage.py:167 msgid "Value" msgstr "Значение" #: gaphor/adapters/profiles/stereotypespage.py:219 msgid "Show stereotypes attributes" msgstr "Показывать атрибуты Ñтереотипов" #: gaphor/adapters/propertypages.py:505 gaphor/ui/diagramtoolbox.py:26 msgid "Comment" msgstr "Комментарий" #: gaphor/adapters/propertypages.py:627 msgid "Abstract" msgstr "ÐбÑтракциÑ" #: gaphor/adapters/propertypages.py:661 msgid "Folded" msgstr "Свёрнуто" #: gaphor/adapters/propertypages.py:737 msgid "Show attributes" msgstr "Показывать атрибуты" #: gaphor/adapters/propertypages.py:754 msgid "Attributes" msgstr "Ðтрибуты" #: gaphor/adapters/propertypages.py:754 gaphor/adapters/propertypages.py:827 msgid "S" msgstr "S" #: gaphor/adapters/propertypages.py:811 msgid "Show operations" msgstr "Показывать дейÑтвиÑ" #: gaphor/adapters/propertypages.py:827 msgid "Operation" msgstr "ДейÑтвие" #: gaphor/adapters/propertypages.py:874 gaphor/ui/diagramtoolbox.py:33 msgid "Dependency" msgstr "ЗавиÑимоÑть" #: gaphor/adapters/propertypages.py:875 msgid "Usage" msgstr "ИÑпользование" #: gaphor/adapters/propertypages.py:876 msgid "Realization" msgstr "РеализациÑ" #: gaphor/adapters/propertypages.py:877 gaphor/ui/diagramtoolbox.py:35 msgid "Implementation" msgstr "РеализациÑ" #: gaphor/adapters/propertypages.py:889 msgid "Dependency type" msgstr "Тип завиÑимоÑти" #: gaphor/adapters/propertypages.py:897 msgid "Automatic" msgstr "ÐвтоматичеÑкий" #: gaphor/adapters/propertypages.py:977 msgid "Show direction" msgstr "Показывать направление" #: gaphor/adapters/propertypages.py:982 msgid "Invert Direction" msgstr "Инвертировать направление" #: gaphor/adapters/propertypages.py:988 #, fuzzy msgid "Head" msgstr "Голова" #: gaphor/adapters/propertypages.py:992 #, fuzzy msgid "Tail" msgstr "ХвоÑÑ‚" #: gaphor/adapters/propertypages.py:1172 msgid "Orthogonal" msgstr "ПрÑмоугольный" #: gaphor/adapters/propertypages.py:1189 msgid "Horizontal" msgstr "Горизонтальный" #: gaphor/adapters/propertypages.py:1225 msgid "Upper bound" msgstr "ВерхнÑÑ Ð³Ñ€Ð°Ð½Ð¸Ñ†Ð°" #: gaphor/adapters/propertypages.py:1240 msgid "Ordering" msgstr "ПорÑдок" #: gaphor/adapters/propertypages.py:1288 msgid "Join specification" msgstr "СпоÑоб ÑоединениÑ" #: gaphor/adapters/propertypages.py:1320 #: gaphor/adapters/states/propertypages.py:32 msgid "Guard" msgstr "Защита" #: gaphor/adapters/propertypages.py:1373 msgid "Indirectly instantiated" msgstr "" #: gaphor/adapters/propertypages.py:1408 gaphor/adapters/propertypages.py:1429 #: gaphor/adapters/propertypages.py:1436 gaphor/ui/diagramtoolbox.py:55 msgid "Message" msgstr "Сообщение" #: gaphor/adapters/propertypages.py:1431 msgid "Additional Messages" msgstr "Дополнительные Ñообщение" #: gaphor/adapters/propertypages.py:1438 msgid "Inverted Messages" msgstr "" #: gaphor/adapters/propertypages.py:1442 msgid "Message sort" msgstr "" #: gaphor/adapters/states/propertypages.py:79 msgid "Entry" msgstr "Элемент" #: gaphor/adapters/states/propertypages.py:86 msgid "Exit" msgstr "Выход" #: gaphor/adapters/states/propertypages.py:93 msgid "Do Activity" msgstr "" #: gaphor/misc/errorhandler.py:21 msgid "An error occured." msgstr "Произошла ошибка." #: gaphor/misc/errorhandler.py:28 msgid "" "\n" "\n" "Do you want to debug?\n" "(Gaphor should have been started from the command line)" msgstr "" "\n" "\n" "Ð’Ñ‹ хотите войти в режим отладки?\n" "(Gaphor должен быть запущен из командной Ñтроки)" #: gaphor/plugins/xmiexport/__init__.py:39 msgid "Export to XMI" msgstr "ЭкÑпорт в XMI" #: gaphor/plugins/xmiexport/__init__.py:40 msgid "Export model to XMI (XML Model Interchange) format" msgstr "ЭкÑпорт модели в формат XMI (XML Model Interchange)" #: gaphor/services/diagramexportmanager.py:74 msgid "The file %s already exists. Do you want to replace it with the file you are exporting to?" msgstr "Файл %s ÑущеÑтвует. Хотите ли вы заменить его ÑкÑпортируемым?" #: gaphor/services/filemanager.py:135 msgid "" "Opening a new model will flush the currently loaded model.\n" "Any changes made will not be saved. Do you want to continue?" msgstr "" "Открытие новой модели вызовет удаление текущей.\n" "Любые Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð±ÑƒÐ´ÑƒÑ‚ потерÑны. Хотите продолжить?" #: gaphor/services/filemanager.py:144 msgid "New model" msgstr "ÐÐ¾Ð²Ð°Ñ Ð¼Ð¾Ð´ÐµÐ»ÑŒ" #: gaphor/services/filemanager.py:147 msgid "main" msgstr "главнаÑ" #: gaphor/services/filemanager.py:163 msgid "Loading model from %s" msgstr "Загрузка модели из %s" #: gaphor/services/filemanager.py:163 msgid "Loading..." msgstr "Загрузка..." #: gaphor/services/filemanager.py:193 msgid "The model contains some references to items that are not maintained. Do you want to clean this before saving the model?" msgstr "" #: gaphor/services/filemanager.py:260 msgid "New from template" msgstr "Ðовый из шаблона" #: gaphor/services/filemanager.py:305 msgid "Save Gaphor model as" msgstr "Сохранить модель Gaphor как" #: gaphor/storage/storage.py:186 msgid "Loading %d elements..." msgstr "ЗагружаетÑÑ %d Ñлементов..." #: gaphor/ui/diagramtab.py:76 msgid "" msgstr "<пуÑто>" #: gaphor/ui/diagramtoolbox.py:22 msgid "Pointer" msgstr "Указатель" #: gaphor/ui/diagramtoolbox.py:23 msgid "Line" msgstr "ЛиниÑ" #: gaphor/ui/diagramtoolbox.py:24 msgid "Box" msgstr "Контейнер" #: gaphor/ui/diagramtoolbox.py:25 msgid "Ellipse" msgstr "ЭллипÑ" #: gaphor/ui/diagramtoolbox.py:27 msgid "Comment line" msgstr "Ð›Ð¸Ð½Ð¸Ñ Ðº комментарию" #: gaphor/ui/diagramtoolbox.py:28 msgid "Classes" msgstr "КлаÑÑÑ‹" #: gaphor/ui/diagramtoolbox.py:29 msgid "Class" msgstr "КлаÑÑ" #: gaphor/ui/diagramtoolbox.py:30 msgid "Interface" msgstr "ИнтерфейÑ" #: gaphor/ui/diagramtoolbox.py:31 msgid "Package" msgstr "Пакет" #: gaphor/ui/diagramtoolbox.py:32 gaphor/ui/diagramtoolbox.py:66 msgid "Association" msgstr "ÐÑÑоциациÑ" #: gaphor/ui/diagramtoolbox.py:34 msgid "Generalization" msgstr "Обобщение" #: gaphor/ui/diagramtoolbox.py:36 msgid "Components" msgstr "Компоненты" #: gaphor/ui/diagramtoolbox.py:37 msgid "Component" msgstr "Компонент" #: gaphor/ui/diagramtoolbox.py:38 msgid "Artifact" msgstr "Ðртефакт" #: gaphor/ui/diagramtoolbox.py:39 msgid "Node" msgstr "Узел" #: gaphor/ui/diagramtoolbox.py:40 msgid "Device" msgstr "УÑтройÑтво" #: gaphor/ui/diagramtoolbox.py:41 msgid "Subsystem" msgstr "ПодÑиÑтема" #: gaphor/ui/diagramtoolbox.py:42 msgid "Connector" msgstr "Соединитель" #: gaphor/ui/diagramtoolbox.py:43 msgid "Actions" msgstr "ДейÑтвиÑ" #: gaphor/ui/diagramtoolbox.py:44 msgid "Action" msgstr "ДейÑтвие" #: gaphor/ui/diagramtoolbox.py:45 msgid "Initial node" msgstr "Ðачальный узел" #: gaphor/ui/diagramtoolbox.py:46 msgid "Activity final node" msgstr "Финальный узел деÑтельноÑти" #: gaphor/ui/diagramtoolbox.py:47 msgid "Flow final node" msgstr "Узел Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð¸Ñ Ð¿Ð¾Ñ‚Ð¾ÐºÐ°" #: gaphor/ui/diagramtoolbox.py:48 msgid "Decision/merge node" msgstr "Узел принÑÑ‚Ð¸Ñ Ñ€ÐµÑˆÐµÐ½Ð¸Ñ Ð¸Ð»Ð¸ узел ÑлиÑниÑ" #: gaphor/ui/diagramtoolbox.py:49 msgid "Fork/join node" msgstr "Узел раздвоениÑ/ÑлиÑниÑ" #: gaphor/ui/diagramtoolbox.py:50 msgid "Object node" msgstr "Узел данных" #: gaphor/ui/diagramtoolbox.py:51 #, fuzzy msgid "Partition" msgstr "Раздел" #: gaphor/ui/diagramtoolbox.py:52 msgid "Control/object flow" msgstr "Поток управлениÑ/объектов" #: gaphor/ui/diagramtoolbox.py:53 msgid "Interactions" msgstr "ВзаимодейÑтвиÑ" #: gaphor/ui/diagramtoolbox.py:54 msgid "Lifeline" msgstr "Ð›Ð¸Ð½Ð¸Ñ Ð¶Ð¸Ð·Ð½Ð¸" #: gaphor/ui/diagramtoolbox.py:56 msgid "Interaction" msgstr "ВзаимодейÑтвие" #: gaphor/ui/diagramtoolbox.py:57 msgid "States" msgstr "СоÑтоÑниÑ" #: gaphor/ui/diagramtoolbox.py:58 msgid "State" msgstr "СоÑтоÑние" #: gaphor/ui/diagramtoolbox.py:59 msgid "Initial Pseudostate" msgstr "Ðачальное пÑевдоÑоÑтоÑние" #: gaphor/ui/diagramtoolbox.py:60 msgid "Final State" msgstr "Финальное пÑевдоÑоÑтоÑние" #: gaphor/ui/diagramtoolbox.py:61 msgid "History Pseudostate" msgstr "" #: gaphor/ui/diagramtoolbox.py:62 msgid "Transition" msgstr "Переход" #: gaphor/ui/diagramtoolbox.py:63 msgid "Use Cases" msgstr "Варианты иÑпользованиÑ" #: gaphor/ui/diagramtoolbox.py:64 msgid "Use case" msgstr "Вариант иÑпользованиÑ" #: gaphor/ui/diagramtoolbox.py:65 msgid "Actor" msgstr "Ðктёр" #: gaphor/ui/diagramtoolbox.py:67 msgid "Include" msgstr "Включение" #: gaphor/ui/diagramtoolbox.py:68 msgid "Extend" msgstr "РаÑширение" #: gaphor/ui/diagramtoolbox.py:69 msgid "Profiles" msgstr "Профили" #: gaphor/ui/diagramtoolbox.py:70 msgid "Profile" msgstr "Профиль" #: gaphor/ui/diagramtoolbox.py:71 msgid "Metaclass" msgstr "МетаклаÑÑ" #: gaphor/ui/diagramtoolbox.py:73 msgid "Extension" msgstr "РаÑширение" #: gaphor/ui/elementeditor.py:23 msgid "Element Editor" msgstr "Редактор Ñлементов" #: gaphor/ui/elementeditor.py:48 msgid "Editor" msgstr "Редактор" #: gaphor/ui/mainwindow.py:193 msgid "Save changed to your model before closing?" msgstr "Ð’Ñ‹ ÑобираетеÑÑŒ закрыть окно. Хотите ли вы Ñохранить изменениÑ?" #: gaphor/ui/mainwindow.py:195 msgid "If you close without saving, your changes will be discarded." msgstr "ЕÑли вы закроете окно без ÑохранениÑ, вÑе Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð±ÑƒÐ´ÑƒÑ‚ потерÑны" #: gaphor/ui/mainwindow.py:579 msgid "Rename" msgstr "Переименовать" #: gaphor/ui/mainwindow.py:592 msgid "_New diagram" msgstr "_ÐÐ¾Ð²Ð°Ñ Ð´Ð¸Ð°Ð³Ñ€Ð°Ð¼Ð¼Ð°" #: gaphor/ui/mainwindow.py:609 msgid "_Delete diagram" msgstr "Удалить _диаграмму" #: gaphor/ui/mainwindow.py:630 msgid "New _package" msgstr "Ðовый _пакет" #: gaphor/ui/mainwindow.py:646 msgid "Delete pac_kage" msgstr "Удалить па_кет" #: gaphor/ui/mainwindow.py:654 msgid "_Refresh" msgstr "_Обновить" #: gaphor/ui/mainwindow.py:659 msgid "_Reset tool" msgstr "_Возврат" #: gaphor/ui/propertyeditor.py:28 msgid "Properties" msgstr "Параметры" gaphor-0.17.2/po/sv.po000066400000000000000000000252431220151210700144640ustar00rootroot00000000000000# Dutch translations for Gaphor # # Copyright (C) 2004 Arjan Molenaar # Arjan Molenaar , 2004. # msgid "" msgstr "" "Project-Id-Version: gaphor\n" "POT-Creation-Date: Thu Jun 10 15:59:38 2010\n" "PO-Revision-Date: 2006-05-11 16:34+0100\n" "Last-Translator: Daniel Nylander \n" "Language-Team: Swedish \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: pygettext.py 1.4\n" #: gaphor/adapters/actions/partitionpage.py:30 msgid "External" msgstr "" #: gaphor/adapters/profiles/metaclasseditor.py:31 #: gaphor/adapters/propertypages.py:552 msgid "Name" msgstr "" #: gaphor/adapters/profiles/stereotypespage.py:140 msgid "Attribute" msgstr "" #: gaphor/adapters/profiles/stereotypespage.py:140 #: gaphor/ui/diagramtoolbox.py:72 msgid "Stereotype" msgstr "" #: gaphor/adapters/profiles/stereotypespage.py:167 msgid "Value" msgstr "" #: gaphor/adapters/profiles/stereotypespage.py:219 msgid "Show stereotypes attributes" msgstr "" #: gaphor/adapters/propertypages.py:505 gaphor/ui/diagramtoolbox.py:26 #, fuzzy msgid "Comment" msgstr "Komponenter" #: gaphor/adapters/propertypages.py:627 msgid "Abstract" msgstr "" #: gaphor/adapters/propertypages.py:661 msgid "Folded" msgstr "" #: gaphor/adapters/propertypages.py:737 msgid "Show attributes" msgstr "" #: gaphor/adapters/propertypages.py:754 msgid "Attributes" msgstr "" #: gaphor/adapters/propertypages.py:754 gaphor/adapters/propertypages.py:827 msgid "S" msgstr "" #: gaphor/adapters/propertypages.py:811 msgid "Show operations" msgstr "" #: gaphor/adapters/propertypages.py:827 #, fuzzy msgid "Operation" msgstr "Interaktioner" #: gaphor/adapters/propertypages.py:874 gaphor/ui/diagramtoolbox.py:33 msgid "Dependency" msgstr "" #: gaphor/adapters/propertypages.py:875 msgid "Usage" msgstr "" #: gaphor/adapters/propertypages.py:876 msgid "Realization" msgstr "" #: gaphor/adapters/propertypages.py:877 gaphor/ui/diagramtoolbox.py:35 #, fuzzy msgid "Implementation" msgstr "Interaktioner" #: gaphor/adapters/propertypages.py:889 msgid "Dependency type" msgstr "" #: gaphor/adapters/propertypages.py:897 msgid "Automatic" msgstr "" #: gaphor/adapters/propertypages.py:977 msgid "Show direction" msgstr "" #: gaphor/adapters/propertypages.py:982 #, fuzzy msgid "Invert Direction" msgstr "Interaktioner" #: gaphor/adapters/propertypages.py:988 msgid "Head" msgstr "" #: gaphor/adapters/propertypages.py:992 msgid "Tail" msgstr "" #: gaphor/adapters/propertypages.py:1181 msgid "Orthogonal" msgstr "" #: gaphor/adapters/propertypages.py:1198 msgid "Horizontal" msgstr "" #: gaphor/adapters/propertypages.py:1234 msgid "Upper bound" msgstr "" #: gaphor/adapters/propertypages.py:1249 msgid "Ordering" msgstr "" #: gaphor/adapters/propertypages.py:1297 msgid "Join specification" msgstr "" #: gaphor/adapters/propertypages.py:1329 #: gaphor/adapters/states/propertypages.py:32 msgid "Guard" msgstr "" #: gaphor/adapters/propertypages.py:1382 msgid "Indirectly instantiated" msgstr "" #: gaphor/adapters/propertypages.py:1417 gaphor/adapters/propertypages.py:1438 #: gaphor/adapters/propertypages.py:1445 gaphor/ui/diagramtoolbox.py:55 msgid "Message" msgstr "" #: gaphor/adapters/propertypages.py:1440 msgid "Additional Messages" msgstr "" #: gaphor/adapters/propertypages.py:1447 msgid "Inverted Messages" msgstr "" #: gaphor/adapters/propertypages.py:1451 msgid "Message sort" msgstr "" #: gaphor/adapters/states/propertypages.py:79 msgid "Entry" msgstr "" #: gaphor/adapters/states/propertypages.py:86 #, fuzzy msgid "Exit" msgstr "R_edigera" #: gaphor/adapters/states/propertypages.py:93 msgid "Do Activity" msgstr "" #: gaphor/misc/errorhandler.py:21 msgid "An error occured." msgstr "Ett fel har inträffat." #: gaphor/misc/errorhandler.py:28 msgid "" "\n" "\n" "Do you want to debug?\n" "(Gaphor should have been started from the command line)" msgstr "" "\n" "\n" "Vill du felsöka?\n" "(Gaphor bör ha startats frÃ¥n kommandoraden)" #: gaphor/plugins/xmiexport/__init__.py:39 #, fuzzy msgid "Export to XMI" msgstr "_Exportera" #: gaphor/plugins/xmiexport/__init__.py:40 msgid "Export model to XMI (XML Model Interchange) format" msgstr "" #: gaphor/services/diagramexportmanager.py:74 msgid "" "The file %s already exists. Do you want to replace it with the file you are " "exporting to?" msgstr "" #: gaphor/services/filemanager.py:135 msgid "" "Opening a new model will flush the currently loaded model.\n" "Any changes made will not be saved. Do you want to continue?" msgstr "" "Att öppna en ny modell kommer att ta bort den för närvarande\n" "inlästa modellen. Eventuella ändringar kommer inte att sparas.\n" "Vill du fortsätta?" #: gaphor/services/filemanager.py:144 msgid "New model" msgstr "" #: gaphor/services/filemanager.py:147 msgid "main" msgstr "" #: gaphor/services/filemanager.py:163 msgid "Loading model from %s" msgstr "Läser in modell frÃ¥n %s" #: gaphor/services/filemanager.py:163 msgid "Loading..." msgstr "Läser in..." #: gaphor/services/filemanager.py:193 msgid "" "The model contains some references to items that are not maintained. Do you " "want to clean this before saving the model?" msgstr "" #: gaphor/services/filemanager.py:260 msgid "New from template" msgstr "" #: gaphor/services/filemanager.py:305 msgid "Save Gaphor model as" msgstr "Spara Gaphor-modell som" #: gaphor/storage/storage.py:186 msgid "Loading %d elements..." msgstr "Läser in %d element..." #: gaphor/ui/diagramtab.py:76 msgid "" msgstr "" #: gaphor/ui/diagramtoolbox.py:22 msgid "Pointer" msgstr "" #: gaphor/ui/diagramtoolbox.py:23 msgid "Line" msgstr "" #: gaphor/ui/diagramtoolbox.py:24 msgid "Box" msgstr "" #: gaphor/ui/diagramtoolbox.py:25 msgid "Ellipse" msgstr "" #: gaphor/ui/diagramtoolbox.py:27 msgid "Comment line" msgstr "" #: gaphor/ui/diagramtoolbox.py:28 msgid "Classes" msgstr "Klasser" #: gaphor/ui/diagramtoolbox.py:29 #, fuzzy msgid "Class" msgstr "Klasser" #: gaphor/ui/diagramtoolbox.py:30 #, fuzzy msgid "Interface" msgstr "Interaktioner" #: gaphor/ui/diagramtoolbox.py:31 msgid "Package" msgstr "" #: gaphor/ui/diagramtoolbox.py:32 gaphor/ui/diagramtoolbox.py:66 #, fuzzy msgid "Association" msgstr "Ã…tgärder" #: gaphor/ui/diagramtoolbox.py:34 #, fuzzy msgid "Generalization" msgstr "Interaktioner" #: gaphor/ui/diagramtoolbox.py:36 msgid "Components" msgstr "Komponenter" #: gaphor/ui/diagramtoolbox.py:37 #, fuzzy msgid "Component" msgstr "Komponenter" #: gaphor/ui/diagramtoolbox.py:38 msgid "Artifact" msgstr "" #: gaphor/ui/diagramtoolbox.py:39 msgid "Node" msgstr "" #: gaphor/ui/diagramtoolbox.py:40 msgid "Device" msgstr "" #: gaphor/ui/diagramtoolbox.py:41 msgid "Subsystem" msgstr "" #: gaphor/ui/diagramtoolbox.py:42 msgid "Connector" msgstr "" #: gaphor/ui/diagramtoolbox.py:43 msgid "Actions" msgstr "Ã…tgärder" #: gaphor/ui/diagramtoolbox.py:44 #, fuzzy msgid "Action" msgstr "Ã…tgärder" #: gaphor/ui/diagramtoolbox.py:45 msgid "Initial node" msgstr "" #: gaphor/ui/diagramtoolbox.py:46 msgid "Activity final node" msgstr "" #: gaphor/ui/diagramtoolbox.py:47 msgid "Flow final node" msgstr "" #: gaphor/ui/diagramtoolbox.py:48 msgid "Decision/merge node" msgstr "" #: gaphor/ui/diagramtoolbox.py:49 msgid "Fork/join node" msgstr "" #: gaphor/ui/diagramtoolbox.py:50 msgid "Object node" msgstr "" #: gaphor/ui/diagramtoolbox.py:51 msgid "Partition" msgstr "" #: gaphor/ui/diagramtoolbox.py:52 msgid "Control/object flow" msgstr "" #: gaphor/ui/diagramtoolbox.py:53 msgid "Interactions" msgstr "Interaktioner" #: gaphor/ui/diagramtoolbox.py:54 msgid "Lifeline" msgstr "" #: gaphor/ui/diagramtoolbox.py:56 #, fuzzy msgid "Interaction" msgstr "Interaktioner" #: gaphor/ui/diagramtoolbox.py:57 msgid "States" msgstr "" #: gaphor/ui/diagramtoolbox.py:58 msgid "State" msgstr "" #: gaphor/ui/diagramtoolbox.py:59 msgid "Initial Pseudostate" msgstr "" #: gaphor/ui/diagramtoolbox.py:60 msgid "Final State" msgstr "" #: gaphor/ui/diagramtoolbox.py:61 msgid "History Pseudostate" msgstr "" #: gaphor/ui/diagramtoolbox.py:62 msgid "Transition" msgstr "" # Osäker pÃ¥ denna #: gaphor/ui/diagramtoolbox.py:63 msgid "Use Cases" msgstr "Använd lÃ¥dor" # Osäker pÃ¥ denna #: gaphor/ui/diagramtoolbox.py:64 #, fuzzy msgid "Use case" msgstr "Använd lÃ¥dor" #: gaphor/ui/diagramtoolbox.py:65 #, fuzzy msgid "Actor" msgstr "Ã…tgärder" #: gaphor/ui/diagramtoolbox.py:67 msgid "Include" msgstr "" #: gaphor/ui/diagramtoolbox.py:68 msgid "Extend" msgstr "" #: gaphor/ui/diagramtoolbox.py:69 msgid "Profiles" msgstr "Profiler" #: gaphor/ui/diagramtoolbox.py:70 #, fuzzy msgid "Profile" msgstr "Profiler" #: gaphor/ui/diagramtoolbox.py:71 msgid "Metaclass" msgstr "" #: gaphor/ui/diagramtoolbox.py:73 msgid "Extension" msgstr "" #: gaphor/ui/elementeditor.py:23 msgid "Element Editor" msgstr "" #: gaphor/ui/elementeditor.py:48 #, fuzzy msgid "Editor" msgstr "_Redigerare" #: gaphor/ui/mainwindow.py:193 msgid "Save changed to your model before closing?" msgstr "" #: gaphor/ui/mainwindow.py:195 msgid "If you close without saving, your changes will be discarded." msgstr "" #: gaphor/ui/mainwindow.py:579 #, fuzzy msgid "Rename" msgstr "_Byt namn" #: gaphor/ui/mainwindow.py:592 msgid "_New diagram" msgstr "_Nytt diagram" #: gaphor/ui/mainwindow.py:609 msgid "_Delete diagram" msgstr "_Ta bort diagram" #: gaphor/ui/mainwindow.py:630 msgid "New _package" msgstr "" #: gaphor/ui/mainwindow.py:646 #, fuzzy msgid "Delete pac_kage" msgstr "_Ta bort diagram" #: gaphor/ui/mainwindow.py:654 msgid "_Refresh" msgstr "_Uppdatera" #: gaphor/ui/mainwindow.py:659 msgid "_Reset tool" msgstr "" #: gaphor/ui/propertyeditor.py:28 #, fuzzy msgid "Properties" msgstr "Profiler" #~ msgid "_New" #~ msgstr "_Ny" #~ msgid "Created a new model" #~ msgstr "Skapade en ny modell" #~ msgid "_Revert..." #~ msgstr "_GÃ¥ tillbaka..." #~ msgid "Could not load model file." #~ msgstr "Kunde inte läsa in modellfil." #~ msgid "_Open..." #~ msgstr "_Öppna..." #~ msgid "Save the model to a new file" #~ msgstr "Spara modellen till en ny fil" #~ msgid "_Console" #~ msgstr "_Konsoll" #~ msgid "_Manual" #~ msgstr "_Manual" #~ msgid "_About" #~ msgstr "_Om" #~ msgid "_Open" #~ msgstr "_Öppna" #~ msgid "_Delete" #~ msgstr "_Ta bort" #~ msgid "_Undo" #~ msgstr "_Ã…ngra" #~ msgid "(from %s)" #~ msgstr "(frÃ¥n %s)" #~ msgid "_File" #~ msgstr "_Arkiv" #~ msgid "Recent files" #~ msgstr "Tidigare filer" #~ msgid "_Import" #~ msgstr "_Importera" #~ msgid "_Diagram" #~ msgstr "_Diagram" #~ msgid "Tools" #~ msgstr "Verktyg" #~ msgid "_Window" #~ msgstr "_Fönster" #~ msgid "_Help" #~ msgstr "_Hjälp" #~ msgid "To be implemented" #~ msgstr "Not niet gemaakt" #~ msgid "Error occured while in worker thread" #~ msgstr "Er is een fout opgetreden in de worker thread" gaphor-0.17.2/pylintrc000066400000000000000000000235051220151210700146440ustar00rootroot00000000000000# lint Python modules using external checkers. # # $Revision: 866 $ # $HeadURL: https://svn.sourceforge.net/svnroot/gaphor/gaphas/trunk/pylintrc $ # # This is the main checker controling the other ones and the reports # generation. It is itself both a raw checker and an astng checker in order # to: # * handle message activation / deactivation at the module level # * handle some basic but necessary stats'data (number of classes, methods...) # # This checker also defines the following reports: # * R0001: Total errors / warnings # * R0002: % errors / warnings by module # * R0003: Messages # * R0004: Global evaluation [MASTER] # Profiled execution. profile=no # Add to the black list. It should be a base name, not a # path. You may set this option multiple times. ignore=CVS # Pickle collected data for later comparisons. persistent=yes # Set the cache size for astng objects. cache-size=500 # List of plugins (as comma separated values of python modules names) to load, # usually to register additional checkers. load-plugins= [REPORTS] # Tells wether to display a full report or only the messages reports=yes # Use HTML as output format instead of text html=no # Use a parseable text output format, so your favorite text editor will be able # to jump to the line corresponding to a message. parseable=no # Colorizes text output using ansi escape codes color=no # Put messages in a separate file for each module / package specified on the # command line instead of printing them on stdout. Reports (if any) will be # written in a file name "pylint_global.[txt|html]". files-output=no # Python expression which should return a note less than 10 (10 is the highest # note).You have access to the variables errors warning, statement which # respectivly contain the number of errors / warnings messages and the total # number of statements analyzed. This is used by the global evaluation report # (R0004). evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) # Add a comment according to your evaluation note. This is used by the global # evaluation report (R0004). comment=no # Include message's id in output include-ids=no # checks for : # * doc strings # * modules / classes / functions / methods / arguments / variables name # * number of arguments, local variables, branchs, returns and statements in # functions, methods # * required module attributes # * dangerous default values as arguments # * redefinition of function / method / class # * uses of the global statement # # This checker also defines the following reports: # * R0101: Statistics by type [BASIC] # Enable / disable this checker enable-basic=yes # Required attributes for module, separated by a comma required-attributes=__version__ # Regular expression which should only match functions or classes name which do # not require a docstring no-docstring-rgx=__.*__ # Regular expression which should only match correct module names module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ # Regular expression which should only match correct module level names const-rgx=(([A-Z_][A-Z1-9_]*)|(__.*__))$ # Regular expression which should only match correct class names class-rgx=[A-Z_][a-zA-Z0-9]+$ # Regular expression which should only match correct function names function-rgx=[a-z_][a-z0-9_]{0,30}$ # Regular expression which should only match correct method names method-rgx=[a-z_][a-z0-9_]{2,30}$ # Regular expression which should only match correct instance attribute names attr-rgx=[a-z_][a-z0-9_]{0,30}$ # Regular expression which should only match correct argument names argument-rgx=[a-z_][a-z0-9_]{0,30}$ # Regular expression which should only match correct variable names variable-rgx=[a-z_][a-z0-9_]{0,30}$ # Regular expression which should only match correct list comprehension / # generator expression variable names inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ # Good variable names which should always be accepted, separated by a comma good-names=i,j,k,ex,Run,_ # Bad variable names which should always be refused, separated by a comma bad-names=foo,bar,baz,toto,tutu,tata # List of builtins function names that should not be used, separated by a comma bad-functions=map,filter,apply,input # try to find bugs in the code using type inference # [TYPECHECK] # Enable / disable this checker enable-typecheck=yes # Tells wether missing members accessed in mixin class should be ignored. A # mixin class is detected if its name ends with "mixin" (case insensitive). ignore-mixin-members=yes # When zope mode is activated, consider the acquired-members option to ignore # access to some undefined attributes. zope=no # List of members which are usually get through zope's acquisition mecanism and # so shouldn't trigger E0201 when accessed (need zope=yes to be considered. acquired-members=REQUEST,acl_users,aq_parent # checks for # * unused variables / imports # * undefined variables # * redefinition of variable from builtins or from an outer scope # * use of variable before assigment # [VARIABLES] # Enable / disable this checker enable-variables=yes # Tells wether we should check for unused import in __init__ files. init-import=no # A regular expression matching names used for dummy variables (i.e. not used). dummy-variables-rgx=_|dummy # List of additional names supposed to be defined in builtins. Remember that # you should avoid to define new builtins when possible. additional-builtins= # checks for : # * methods without self as first argument # * overriden methods signature # * access only to existant members via self # * attributes not defined in the __init__ method # * supported interfaces implementation # * unreachable code # [CLASSES] # Enable / disable this checker enable-classes=yes # List of interface methods to ignore, separated by a comma. This is used for # instance to not check methods defines in Zope's Interface base class. ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by # List of method names used to declare (i.e. assign) instance attributes. defining-attr-methods=__init__,__new__,setUp # checks for sign of poor/misdesign: # * number of methods, attributes, local variables... # * size, complexity of functions, methods # [DESIGN] # Enable / disable this checker enable-design=yes # Maximum number of arguments for function / method max-args=5 # Maximum number of locals for function / method body max-locals=15 # Maximum number of return / yield for function / method body max-returns=6 # Maximum number of branch for function / method body max-branchs=12 # Maximum number of statements in function / method body max-statements=50 # Maximum number of parents for a class (see R0901). max-parents=7 # Maximum number of attributes for a class (see R0902). max-attributes=7 # Minimum number of public methods for a class (see R0903). min-public-methods=2 # Maximum number of public methods for a class (see R0904). max-public-methods=20 # checks for # * external modules dependencies # * relative / wildcard imports # * cyclic imports # * uses of deprecated modules # # This checker also defines the following reports: # * R0401: External dependencies # * R0402: Modules dependencies graph [IMPORTS] # Enable / disable this checker enable-imports=yes # Deprecated modules which should not be used, separated by a comma deprecated-modules=regsub,string,TERMIOS,Bastion,rexec # Create a graph of every (i.e. internal and external) dependencies in the # given file (report R0402 must not be disabled) import-graph= # Create a graph of external dependencies in the given file (report R0402 must # not be disabled) ext-import-graph= # Create a graph of internal dependencies in the given file (report R0402 must # not be disabled) int-import-graph= # checks for usage of new style capabilities on old style classes and # other new/old styles conflicts problems # * use of property, __slots__, super # * "super" usage # * raising a new style class as exception # [NEWSTYLE] # Enable / disable this checker enable-newstyle=yes # checks for # * excepts without exception filter # * string exceptions # [EXCEPTIONS] # Enable / disable this checker enable-exceptions=yes # checks for : # * unauthorized constructions # * strict indentation # * line length # * use of <> instead of != # [FORMAT] # Enable / disable this checker enable-format=yes # Maximum number of characters on a single line. max-line-length=80 # Maximum number of lines in a module max-module-lines=1000 # String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 # tab). indent-string=' ' # checks for: # * warning notes in the code like FIXME, XXX # * PEP 263: source code with non ascii character but no encoding declaration # [MISCELLANEOUS] # Enable / disable this checker enable-miscellaneous=yes # List of note tags to take in consideration, separated by a comma. Default to # FIXME, XXX, TODO notes=FIXME,XXX,TODO # does not check anything but gives some raw metrics : # * total number of lines # * total number of code lines # * total number of docstring lines # * total number of comments lines # * total number of empty lines # # This checker also defines the following reports: # * R0701: Raw metrics [METRICS] # Enable / disable this checker enable-metrics=yes # checks for similarities and duplicated code. This computation may be # memory / CPU intensive, so you should disable it if you experiments some # problems. # # This checker also defines the following reports: # * R0801: Duplication [SIMILARITIES] # Enable / disable this checker enable-similarities=yes # Minimum lines number of a similarity. min-similarity-lines=4 # Ignore comments when computing similarities. ignore-comments=yes # Ignore docstrings when computing similarities. ignore-docstrings=yes gaphor-0.17.2/run-gaphor.bat000077500000000000000000000010731220151210700156260ustar00rootroot00000000000000rem Startup script for Gaphor on Windows rem rem You should check if the directory where the win32 libraries are rem found is correct, as well as the place where Python is installed. rem rem rem This is where you installed the Gaphor-win32-libs package. set GAPHOR_WIN32_LIBS=gaphor-win32-libs rem set GAPHOR_WIN32_LIBS=c:\msys\1.0\target rem Where is Python installed? set PYTHONHOME=c:\Python24 rem Should have to change these: set PATH=%GAPHOR_WIN32_LIBS%\bin;%PYTHONHOME%;%PATH% set PYTHONPATH=%GAPHOR_WIN32_LIBS%\lib\python24 python setup.py run %1 %2 %3 %4 gaphor-0.17.2/run-gaphor.sh000077500000000000000000000000421220151210700154650ustar00rootroot00000000000000 python setup.py develop run "$@" gaphor-0.17.2/setup.cfg000066400000000000000000000003341220151210700146710ustar00rootroot00000000000000[egg_info] #tag_build=.dev #tag_date=1 #tag_svn_revision=0 [nosetests] with-doctest=1 doctest-extension=.txt tests=gaphor,tests,doc verbosity=2 rednose=1 detailed-errors=1 #with-coverage=1 cover-package=gaphor/diagram gaphor-0.17.2/setup.py000066400000000000000000000127351220151210700145720ustar00rootroot00000000000000""" Setup script for Gaphor. Run 'python setup.py develop' to set up a development environment, including dependencies. """ VERSION = '0.17.2' import os import sys sys.path.insert(0, '.') from ez_setup import use_setuptools use_setuptools() from setuptools import setup, find_packages from distutils.cmd import Command #try: #from sphinx.setup_command import BuildDoc #except ImportError, e: # print 'No Sphynx found' from utils.command.build_mo import build_mo from utils.command.build_pot import build_pot from utils.command.build_uml import build_uml from utils.command.install_lib import install_lib from utils.command.run import run LINGUAS = [ 'ca', 'es', 'fr', 'nl', 'sv' ] # Wrap setuptools' build_py command, so we're sure build_uml is performed # before the build_py code. from setuptools.command.build_py import build_py class build_py_with_sub_commands(build_py): def run(self): for cmd_name in self.get_sub_commands(): self.run_command(cmd_name) build_py.run(self) build_py_with_sub_commands.sub_commands.append(('build_uml', None)) setup( name='gaphor', version=VERSION, url='http://gaphor.sourceforge.net', author='Arjan J. Molenaar', author_email='arjanmol@users.sourceforge.net', license='GNU General Public License', description='Gaphor is a UML modeling tool', long_description=""" Gaphor is a UML modeling tool written in Python. It uses the GTK+ environment for user interaction. """, classifiers = [ 'Development Status :: 5 - Production/Stable', 'Environment :: X11 Applications :: GTK', 'Intended Audience :: Developers', 'Intended Audience :: End Users/Desktop', 'Intended Audience :: Information Technology', 'License :: OSI Approved :: GNU General Public License (GPL)', 'Operating System :: MacOS :: MacOS X', 'Operating System :: Microsoft :: Windows', 'Operating System :: POSIX', 'Operating System :: Unix', 'Programming Language :: Python', 'Topic :: Multimedia :: Graphics :: Editors :: Vector-Based', 'Topic :: Software Development :: Documentation', ], keywords = 'model modeling modelling uml diagram python tool', packages = find_packages(exclude=['ez_setup', 'utils*']), include_package_data = True, install_requires = [ # 'PyGTK >= 2.8.0', - Exclude, since it will not build anyway 'gaphas >= 0.7.2', 'etk.docking >= 0.2', 'zope.component >= 3.4.0', # - won't compile on windows. ], zip_safe = False, #test_suite = 'nose.collector', entry_points = { 'console_scripts': [ 'gaphor = gaphor:main', 'gaphorconvert = gaphor.tools.gaphorconvert:main', ], 'gaphor.services': [ 'component_registry = gaphor.services.componentregistry:ZopeComponentRegistry', #'event_dispatcher = gaphor.services.eventdispatcher:EventDispatcher', 'adapter_loader = gaphor.services.adapterloader:AdapterLoader', 'properties = gaphor.services.properties:Properties', 'undo_manager = gaphor.services.undomanager:UndoManager', 'element_factory = gaphor.UML.elementfactory:ElementFactoryService', 'file_manager = gaphor.services.filemanager:FileManager', #'backup_service = gaphor.services.backupservice:BackupService', 'diagram_export_manager = gaphor.services.diagramexportmanager:DiagramExportManager', 'action_manager = gaphor.services.actionmanager:ActionManager', 'ui_manager = gaphor.services.actionmanager:UIManager', 'main_window = gaphor.ui.mainwindow:MainWindow', 'copy = gaphor.services.copyservice:CopyService', 'sanitizer = gaphor.services.sanitizerservice:SanitizerService', 'element_dispatcher = gaphor.services.elementdispatcher:ElementDispatcher', #'property_dispatcher = gaphor.services.propertydispatcher:PropertyDispatcher', 'xmi_export = gaphor.plugins.xmiexport:XMIExport', 'diagram_layout = gaphor.plugins.diagramlayout:DiagramLayout', 'pynsource = gaphor.plugins.pynsource:PyNSource', #'check_metamodel = gaphor.plugins.checkmetamodel:CheckModelWindow', #'live_object_browser = gaphor.plugins.liveobjectbrowser:LiveObjectBrowser', 'alignment = gaphor.plugins.alignment:Alignment', 'help = gaphor.services.helpservice:HelpService', ], 'gaphor.uicomponents': [ #'mainwindow = gaphor.ui.mainwindow:MainWindow', 'namespace = gaphor.ui.mainwindow:Namespace', 'toolbox = gaphor.ui.mainwindow:Toolbox', 'consolewindow = gaphor.ui.consolewindow:ConsoleWindow', 'elementeditor = gaphor.ui.elementeditor:ElementEditor', ], }, cmdclass = { 'build_py': build_py_with_sub_commands, 'build_uml': build_uml, #'build_doc': BuildDoc, 'build_mo': build_mo, 'build_pot': build_pot, 'install_lib': install_lib, 'run': run, }, setup_requires = [ #'Sphinx >= 1.0.6', 'nose >= 0.10.4', 'setuptools-git >= 0.3.4' ], test_suite = 'nose.collector', options = dict( build_pot = dict( all_linguas = ','.join(LINGUAS), ), build_mo = dict( all_linguas = ','.join(LINGUAS), ), ), ) # vim:sw=4:et:ai gaphor-0.17.2/test-diagrams/000077500000000000000000000000001220151210700156145ustar00rootroot00000000000000gaphor-0.17.2/test-diagrams/action-issue.gaphor000066400000000000000000000172101220151210700214220ustar00rootroot00000000000000 (1.0, 0.0, 0.0, 1.0, 180.0, 386.0) 429.0 300.0 0 (1.0, 0.0, 0.0, 1.0, 0.0, 30.0) 170.0 300.0 0 (1.0, 0.0, 0.0, 1.0, 50.0, 53.0) 75.0 30.0 0 (1.0, 0.0, 0.0, 1.0, 82.0, 206.0) 75.0 30.0 0 (1.0, 0.0, 0.0, 1.0, 170.0, 30.0) 259.0 270.0 0 (1.0, 0.0, 0.0, 1.0, 141.0, 156.0) 75.0 30.0 0 0 (1.0, 0.0, 0.0, 1.0, 269.0, 499.0) 0 0 [(0.0, 0.0), (20.0, 123.0)] 0 (1.0, 0.0, 0.0, 1.0, 305.0, 482.0) 0 0 [(0.0, 0.0), (186.0, 93.0)] 0 (1.0, 0.0, 0.0, 1.0, 491.0, 595.0) 0 0 [(0.0, 0.0), (-154.0, 44.0)] gaphor-0.17.2/test-diagrams/assembly.gaphor000066400000000000000000000135371220151210700206460ustar00rootroot00000000000000 2(1.0, 0.0, 0.0, 1.0, 201.0, 39.0)148.050.02(1.0, 0.0, 0.0, 1.0, 202.0, 295.0)148.050.0111(1.0, 0.0, 0.0, 1.0, 44.0, 39.0)100.058.0113(1.0, 0.0, 0.0, 1.0, 262.0, 158.0)20.020.0111(1.0, 0.0, 0.0, 1.0, 428.0, 296.0)100.058.0(1.0, 0.0, 0.0, 1.0, 144.0, 66.0)00[(0.0, 0.0), (57.0, -1.0)](1.0, 0.0, 0.0, 1.0, 428.0, 328.0)00[(0.0, 0.0), (-78.0, -5.0)]0gaphor-0.17.2/test-diagrams/association.gaphor000066400000000000000000000112121220151210700213270ustar00rootroot00000000000000 111(1.0, 0.0, 0.0, 1.0, 70.0, 159.0)106.098.0111(1.0, 0.0, 0.0, 1.0, 424.0, 186.0)100.050.0(1.0, 0.0, 0.0, 1.0, 176.0, 206.0)00[(0.0, 0.0), (248.0, 0.0)]1gaphor-0.17.2/test-diagrams/associations-pre015.gaphor000066400000000000000000001071201220151210700225300ustar00rootroot00000000000000 111(1.0, 0.0, 0.0, 1.0, 104.0, 87.0)100.058.0111(1.0, 0.0, 0.0, 1.0, 407.0, 81.0)100.058.0(1.0, 0.0, 0.0, 1.0, 407.0, 98.0)00[(0.0, 0.0), (-203.0, 4.0799999999999983)]0-1(1.0, 0.0, 0.0, 1.0, 108.0, 188.0)91.030.0(1.0, 0.0, 0.0, 1.0, 199.0, 203.0)00[(0.0, 0.0), (210.0, 0.0)]03(1.0, 0.0, 0.0, 1.0, 409.0, 170.0)38.060.0(1.0, 0.0, 0.0, 1.0, 601.0, 95.0)121.086.0-1(1.0, 0.0, 0.0, 1.0, 116.0, 287.0)91.030.03(1.0, 0.0, 0.0, 1.0, 416.0, 262.00000000000006)38.060.0(1.0, 0.0, 0.0, 1.0, 207.0, 303.0)00[(0.0, 0.0), (209.0, 0.0)]0111(1.0, 0.0, 0.0, 1.0, 134.0, 366.0)100.050.0111(1.0, 0.0, 0.0, 1.0, 400.0, 356.0)100.058.0(1.0, 0.0, 0.0, 1.0, 234.0, 383.0)00[(0.0, 0.0), (166.0, -5.0)]0111(1.0, 0.0, 0.0, 1.0, 137.0, 475.98617511520752)100.050.0111(1.0, 0.0, 0.0, 1.0, 413.0, 463.47926267281105)100.058.0(1.0, 0.0, 0.0, 1.0, 237.0, 491.47926267281105)00[(0.0, 0.0), (176.0, -2.0)]0-1(1.0, 0.0, 0.0, 1.0, 105.0, 565.47926267281105)137.048.47926267283(1.0, 0.0, 0.0, 1.0, 430.0, 562.47926267281105)38.060.0(1.0, 0.0, 0.0, 1.0, 242.0, 588.47926267281105)00[(0.0, 0.0), (188.0, 2.0)]0111(1.0, 0.0, 0.0, 1.0, 127.0, 692.84792626728108)100.050.0111(1.0, 0.0, 0.0, 1.0, 422.0, 692.84792626728108)100.058.0(1.0, 0.0, 0.0, 1.0, 227.0, 712.84792626728108)00[(0.0, 0.0), (195.0, 4.0)]0111(1.0, 0.0, 0.0, 1.0, 619.0, 533.84792626728108)100.050.0111(1.0, 0.0, 0.0, 1.0, 627.0, 660.84792626728108)100.050.0(1.0, 0.0, 0.0, 1.0, 670.0, 583.84792626728108)00[(0.0, 0.0), (-1.0, 77.0)]111(1.0, 0.0, 0.0, 1.0, 127.0, 788.0)100.050.0111(1.0, 0.0, 0.0, 1.0, 418.0, 788.0)100.050.0(1.0, 0.0, 0.0, 1.0, 227.0, 803.0)00[(0.0, 0.0), (191.0, 2.0)]0gaphor-0.17.2/test-diagrams/components.gaphor000066400000000000000000000024621220151210700212070ustar00rootroot00000000000000 2(1.0, 0.0, 0.0, 1.0, 85.0, 216.0)178.050.00gaphor-0.17.2/test-diagrams/deployment.gaphor000066400000000000000000000270701220151210700212040ustar00rootroot00000000000000 1 (1.0, 0.0, 0.0, 1.0, 118.0, 76.0) 529.0 373.698630137 0 2 (1.0, 0.0, 0.0, 1.0, 73.0, 53.0) 127.0 50.0 0 1 (1.0, 0.0, 0.0, 1.0, 267.0, 42.0) 204.0 169.0 0 2 (1.0, 0.0, 0.0, 1.0, 25.0, 30.0) 148.0 50.0 0 2 (1.0, 0.0, 0.0, 1.0, 27.0, 97.0) 148.0 50.0 0 2 (1.0, 0.0, 0.0, 1.0, 75.0, 141.0) 127.0 50.0 0 1 (1.0, 0.0, 0.0, 1.0, 98.0, 246.0) 319.0 99.698630137 0 2 (1.0, 0.0, 0.0, 1.0, 27.0, 33.0) 127.0 50.0 0 2 (1.0, 0.0, 0.0, 1.0, 173.0, 34.397260273973416) 127.0 50.0 0 gaphor-0.17.2/test-diagrams/diagram-#4.gaphor000066400000000000000000001721331220151210700206350ustar00rootroot00000000000000 ClientAppeared()]]> ClientDisappeared()]]> PortAppeared()]]> PortDisappeared()]]> PortsConnected()]]> PortsDisconnected()]]> StudioAppeared()]]> StudioDisappeared()]]> RoomAppeared()]]> RoomDisappeared()]]> 0 1 1 (1.0, 0.0, 0.0, 1.0, 705.0, 13.0) 129.0 96.0 0 0 (1.0, 0.0, 0.0, 1.0, 765.0, 109.0) 0 0 [(0.0, 0.0), (0.0, 0.0), (2.0, 148.0)] 0 0 1 (1.0, 0.0, 0.0, 1.0, 718.0, 257.0) 100.0 50.0 0 0 1 1 (1.0, 0.0, 0.0, 1.0, 18.0, 10.0) 140.0 149.0 1 0 1 1 (1.0, 0.0, 0.0, 1.0, 176.0, 15.0) 129.0 84.0 0 0 1 1 (1.0, 0.0, 0.0, 1.0, 320.0, 13.0) 138.0 123.0 0 0 1 1 (1.0, 0.0, 0.0, 1.0, 491.0, 11.0) 185.0 193.0 0 0 (1.0, 0.0, 0.0, 1.0, 676.0, 204.0) 0 0 [(0.0, 0.0), (42.0, 54.0)] 0 (1.0, 0.0, 0.0, 1.0, 414.0, 136.0) 0 0 [(0.0, 0.0), (77.439999999999998, 119.0)] 0 0 1 (1.0, 0.0, 0.0, 1.0, 363.0, 255.0) 247.0 51.0 0 0 (1.0, 0.0, 0.0, 1.0, 548.0, 204.0) 0 0 [(0.0, 0.0), (1.0, 51.0)] 0 0 1 (1.0, 0.0, 0.0, 1.0, 184.0, 279.0) 110.0 50.0 0 0 (1.0, 0.0, 0.0, 1.0, 241.0, 99.0) 1 0 [(0.0, 0.0), (0.0, 0.0), (-0.90000000000000568, 180.0)] 0 0 1 (1.0, 0.0, 0.0, 1.0, 23.0, 218.0) 100.0 50.0 0 0 (1.0, 0.0, 0.0, 1.0, 72.090909090909093, 159.0) 0 0 [(0.0, 0.0), (-1.0909090909090935, 59.0)] 0 0 1 (1.0, 0.0, 0.0, 1.0, 885.0, 192.0) 100.0 51.0 0 0 1 1 (1.0, 0.0, 0.0, 1.0, 888.0, 19.0) 100.0 63.0 0 0 (1.0, 0.0, 0.0, 1.0, 937.0, 82.0) 0 0 [(0.0, 0.0), (0.0, 110.0)] (1.0, 0.0, 0.0, 1.0, 262.0, 178.0) 114.0 45.0 0 0 (1.0, 0.0, 0.0, 1.0, 335.0, 223.0) 0 0 [(0.0, 0.0), (5.8770614692654135, 142.0)] 0 (1.0, 0.0, 0.0, 1.0, 925.0, 243.0) 0 0 [(0.0, 0.0), (0.0, 122.0), (-666.0, 122.0), (-666.0, 86.0)] 1 0 0 1 (1.0, 0.0, 0.0, 1.0, 173.0, 677.0) 129.0 50.0 0 0 0 1 (1.0, 0.0, 0.0, 1.0, 344.0, 674.0) 133.0 50.0 0 0 (1.0, 0.0, 0.0, 1.0, 252.0, 782.0) 0 0 [(0.0, 0.0), (0.0, -55.0)] 0 (1.0, 0.0, 0.0, 1.0, 392.0, 782.0) 0 0 [(0.0, 0.0), (0.0, -58.0)] 0 0 1 (1.0, 0.0, 0.0, 1.0, 171.0, 782.0) 293.0 50.0 0 0 (1.0, 0.0, 0.0, 1.0, 71.0, 268.0) 0 0 [(0.0, 0.0), (-1.0, 270.0)] 1 0 (1.0, 0.0, 0.0, 1.0, 229.0, 329.0) 0 0 [(0.0, 0.0), (-4.0, 209.0)] 1 0 (1.0, 0.0, 0.0, 1.0, 395.11000000000001, 306.0) 0 0 [(0.0, 0.0), (-154.11000000000001, 371.0)] 1 0 (1.0, 0.0, 0.0, 1.0, 439.57000000000005, 306.0) 0 0 [(0.0, 0.0), (-52.57000000000005, 368.0)] 1 0 (1.0, 0.0, 0.0, 1.0, 294.0, 305.0) 0 0 [(0.0, 0.0), (69.0, -20.420000000000016)] 1 0 (1.0, 0.0, 0.0, 1.0, 318.52909844150719, 297.74080883803515) 0 0 [(0.0, 0.0), (5.4709015584928125, -74.74080883803515)] 0 0 1 (1.0, 0.0, 0.0, 1.0, 13.0, 538.0) 1008.0 63.0 0 0 (1.0, 0.0, 0.0, 1.0, 205.0, 677.0) 0 0 [(0.0, 0.0), (0.0, -76.0)] 0 (1.0, 0.0, 0.0, 1.0, 419.0, 674.0) 0 0 [(0.0, 0.0), (0.0, -73.0)] 0 (1.0, 0.0, 0.0, 1.0, 773.0, 307.0) 0 0 [(0.0, 0.0), (0.0, 231.0)] 1 0 (1.0, 0.0, 0.0, 1.0, 957.0, 243.0) 0 0 [(0.0, 0.0), (0.0, 295.0)] 1 0 0 1 (1.0, 0.0, 0.0, 1.0, 462.0, 442.0) 100.0 50.0 0 0 0 1 (1.0, 0.0, 0.0, 1.0, 600.0, 440.0) 100.0 50.0 0 0 (1.0, 0.0, 0.0, 1.0, 649.0, 490.0) 0 0 [(0.0, 0.0), (-198.0, 184.0)] 1 0 (1.0, 0.0, 0.0, 1.0, 512.0, 492.0) 0 0 [(0.0, 0.0), (-235.0, 185.0)] 1 0 (1.0, 0.0, 0.0, 1.0, 718.0, 307.0) 0 0 [(0.0, 0.0), (-174.0, 135.0)] 0 (1.0, 0.0, 0.0, 1.0, 743.0, 307.0) 0 0 [(0.0, 0.0), (-56.0, 133.0)] 0 (1.0, 0.0, 0.0, 1.0, 507.0, 442.0) 0 0 [(0.0, 0.0), (28.0, -136.0)] 0 0 (1.0, 0.0, 0.0, 1.0, 586.0, 306.0) 0 0 [(0.0, 0.0), (29.0, 134.0)] 0 0 0 1 (1.0, 0.0, 0.0, 1.0, 800.0, 417.0) 105.0 51.0 0 0 (1.0, 0.0, 0.0, 1.0, 817.0, 307.0) 0 0 [(0.0, 0.0), (30.25, 110.0)] 0 Appeared()]]> gaphor-0.17.2/test-diagrams/interactions.gaphor000066400000000000000000000210001220151210700215110ustar00rootroot00000000000000 (1.0, 0.0, 0.0, 1.0, 45.0, 56.0)623.0442.0(1.0, 0.0, 0.0, 1.0, 32.0, 55.0)114.050.0261.0(1.0, 0.0, 0.0, 1.0, 177.0, 55.0)114.050.0261.0(1.0, 0.0, 0.0, 1.0, 307.0, 164.0)114.050.010.0(1.0, 0.0, 0.0, 1.0, 489.0, 167.0)114.050.010.0(1.0, 0.0, 0.0, 1.0, 134.0, 211.0)00[(0.0, 0.0), (145.0, 3.0)](1.0, 0.0, 0.0, 1.0, 279.0, 271.0)00[(0.0, 0.0), (-145.0, 0.0)](1.0, 0.0, 0.0, 1.0, 534.0, 247.0)00[(0.0, 0.0), (-68.0, 0.0)]gaphor-0.17.2/test-diagrams/lifelines-pre015.gaphor000066400000000000000000000327421220151210700220120ustar00rootroot00000000000000 (1.0, 0.0, 0.0, 1.0, 203.0, 188.0) 100.0 50.0 10.0 (1.0, 0.0, 0.0, 1.0, 521.0, 185.0) 100.0 50.0 10.0 (1.0, 0.0, 0.0, 1.0, 303.0, 203.0) 0 0 [(0.0, 0.0), (218.0, 2.0)] (1.0, 0.0, 0.0, 1.0, 66.0, 203.0) 0 0 [(0.0, 0.0), (137.0, 3.0)] (1.0, 0.0, 0.0, 1.0, 251.0, 188.0) 0 0 [(0.0, 0.0), (-3.0, -91.0)] (1.0, 0.0, 0.0, 1.0, 289.0, 238.0) 0 0 [(0.0, 0.0), (118.0, 73.0), (270.0, -3.0)] (1.0, 0.0, 0.0, 1.0, 763.0, 116.0) 233.0 50.0 gaphor-0.17.2/test-diagrams/lifelines.gaphor000066400000000000000000000406331220151210700207760ustar00rootroot00000000000000 (1.0, 0.0, 0.0, 1.0, 66.0, 93.0)100.050.0243.0(1.0, 0.0, 0.0, 1.0, 289.0, 102.0)100.050.0305.0(1.0, 0.0, 0.0, 1.0, 116.0, 211.0)00[(0.0, 0.0), (223.0, 1.0)](1.0, 0.0, 0.0, 1.0, 499.0, 116.0)100.050.0245.0(1.0, 0.0, 0.0, 1.0, 116.0, 160.0)00[(0.0, 0.0), (223.0, 3.0)](1.0, 0.0, 0.0, 1.0, 339.0, 205.0)00[(0.0, 0.0), (210.0, -1.0)](1.0, 0.0, 0.0, 1.0, 339.0, 236.0)00[(0.0, 0.0), (210.0, -1.0)](1.0, 0.0, 0.0, 1.0, 549.0, 271.0)00[(0.0, 0.0), (-210.0, 1.0)](1.0, 0.0, 0.0, 1.0, 116.0, 331.0)00[(0.0, 0.0), (223.0, -2.0)](1.0, 0.0, 0.0, 1.0, 116.0, 185.0)00[(0.0, 0.0), (433.0, 2.0)](1.0, 0.0, 0.0, 1.0, 10.0, 153.0)00[(0.0, 0.0), (106.0, -1.0)](1.0, 0.0, 0.0, 1.0, 549.0, 303.0)00[(0.0, 0.0), (136.0, -1.0)]gaphor-0.17.2/test-diagrams/line-align.gaphor000066400000000000000000000054351220151210700210440ustar00rootroot00000000000000 (1.0, 0.0, 0.0, 1.0, 15.0, 182.0)79.030.0(1.0, 0.0, 0.0, 1.0, 467.0, 180.0)79.030.0(1.0, 0.0, 0.0, 1.0, 112.0, 208.0)00[(-18.0, -12.0), (355.0, -11.0)]gaphor-0.17.2/test-diagrams/line-stereotype.gaphor000066400000000000000000000240161220151210700221510ustar00rootroot00000000000000 111(1.0, 0.0, 0.0, 1.0, 58.0, 164.0)100.050.0111(1.0, 0.0, 0.0, 1.0, 389.0, 180.0)100.050.0(1.0, 0.0, 0.0, 1.0, 373.0, 195.0)00[(16.0, 8.0), (-215.0, -11.0)]0111(1.0, 0.0, 0.0, 1.0, 71.0, 297.0)100.060.0111(1.0, 0.0, 0.0, 1.0, 381.0, 288.0)106.061.0(1.0, 0.0, 0.0, 1.0, 167.0, 305.0)00[(4.0, 1.5999999999999659), (214.0, 1.3000000000000114)]111(1.0, 0.0, 0.0, 1.0, 436.0, 60.0)100.050.0111(1.0, 0.0, 0.0, 1.0, 244.0, 352.0)106.060.0(1.0, 0.0, 0.0, 1.0, 147.0, 353.0)00[(24.0, 0.0), (97.0, 24.0)]gaphor-0.17.2/test-diagrams/namespace.gaphor000066400000000000000000000065431220151210700207620ustar00rootroot00000000000000 (1.0, 0.0, 0.0, 1.0, 60.0, 169.0)120.060.0(1.0, 0.0, 0.0, 1.0, 320.0, 167.0)121.060.0111(1.0, 0.0, 0.0, 1.0, 195.0, 72.0)100.050.0111(1.0, 0.0, 0.0, 1.0, 139.0, 53.0)100.050.0gaphor-0.17.2/test-diagrams/simple-items.gaphor000066400000000000000000000023541220151210700214320ustar00rootroot00000000000000 New model main (1.0, 0.0, 0.0, 1.0, 336.0, 113.0) 0 0 [(0.0, 0.0), (93.0, -2.0), (87.0, 83.0)] (1.0, 0.0, 0.0, 1.0, 215.0, 121.0) 97.0 74.0 (1.0, 0.0, 0.0, 1.0, 332.0, 137.0) 60.0 60.0 gaphor-0.17.2/test-diagrams/stereotype.gaphor000066400000000000000000000512431220151210700212260ustar00rootroot00000000000000 0(1.0, 0.0, 0.0, 1.0, 73.0, 179.0)120.060.0111(1.0, 0.0, 0.0, 1.0, 142.0, 73.0)100.050.0111(1.0, 0.0, 0.0, 1.0, 373.0, 78.0)100.058.0(1.0, 0.0, 0.0, 1.0, 268.0, 115.0)00[(-26.0, -2.0), (105.0, -2.2000000000000028)]111(1.0, 0.0, 0.0, 1.0, 345.0, 194.0)100.058.0(1.0, 0.0, 0.0, 1.0, 258.0, 128.0)00[(196.1650485436893, 165.0), (331.0, 85.0)]111(1.0, 0.0, 0.0, 1.0, 393.0, 293.0)100.058.0(1.0, 0.0, 0.0, 1.0, 328.0, 240.0)00[(-86.0, -145.0), (201.0, -66.199999999999989)](1.0, 0.0, 0.0, 1.0, 86.0, 350.0)20.030.0(1.0, 0.0, 0.0, 1.0, 251.0, 307.0)20.020.0111(1.0, 0.0, 0.0, 1.0, 529.0, 157.0)100.056.0(1.0, 0.0, 0.0, 1.0, 470.0, 215.0)00[(-25.0, 8.0), (59.0, -17.680000000000007)](1.0, 0.0, 0.0, 1.0, 289.0, 368.0)20.020.0111(1.0, 0.0, 0.0, 1.0, 613.0, 292.0)100.058.0(1.0, 0.0, 0.0, 1.0, 638.0, 290.0)00[(0.0, 2.0), (-35.0, -77.0)](1.0, 0.0, 0.0, 1.0, 191.0, 355.0)00[(-85.0, 7.0), (109.0, 13.0)]111(1.0, 0.0, 0.0, 1.0, 592.0, 53.0)100.060.0(1.0, 0.0, 0.0, 1.0, 606.0, 119.0)00[(-2.0, -6.0), (-19.0, 38.0)](1.0, 0.0, 0.0, 1.0, 343.0, 68.0)00[(-59.0, 100.0), (-150.0, 132.0)]gaphor-0.17.2/test-diagrams/taggedvalues-pre015.gaphor000066400000000000000000000061221220151210700225040ustar00rootroot00000000000000 111(1.0, 0.0, 0.0, 1.0, 100.0, 89.0)100.050.0111(1.0, 0.0, 0.0, 1.0, 288.0, 90.0)100.050.0 gaphor-0.17.2/tests/000077500000000000000000000000001220151210700142125ustar00rootroot00000000000000gaphor-0.17.2/tests/issue_53.gaphor000066400000000000000000000206021220151210700170530ustar00rootroot00000000000000 (1.0, 0.0, 0.0, 1.0, 20.0, 142.0) 100.0 70.0 0 (1.0, 0.0, 0.0, 1.0, 20.0, 24.0) 257.0 54.0 0 (1.0, 0.0, 0.0, 1.0, 177.0, 142.0) 100.0 70.0 0 1 1 1 (1.0, 0.0, 0.0, 1.0, 238.0, 146.0) 100.0 70.0 0 1 1 1 (1.0, 0.0, 0.0, 1.0, 229.5, 305.0) 117.0 72.0 0 0 (1.0, 0.0, 0.0, 1.0, 287.0, 216.0) 0 0 [(0.0, 0.0), (1.0, 89.0)] (1.0, 0.0, 0.0, 1.0, 46.0, 49.0) 100.0 70.0 0 1 1 1 (1.0, 0.0, 0.0, 1.0, 152.0, 162.0) 113.0 61.0 0 gaphor-0.17.2/tests/test-model.gaphor000066400000000000000000001026541220151210700175010ustar00rootroot00000000000000 1 1 1 (1.0, 0.0, 0.0, 1.0, 237.0, 90.0) 100.0 50.0 0 1 1 1 (1.0, 0.0, 0.0, 1.0, 228.0, 238.0) 100.0 50.0 0 0 (1.0, 0.0, 0.0, 1.0, 276.0, 140.0) 0 0 [(0.0, 0.0), (3.0, 98.0)] 1 1 1 (1.0, 0.0, 0.0, 1.0, 439.0, 240.0) 100.0 60.0 0 0 (1.0, 0.0, 0.0, 1.0, 328.0, 252.0) 0 0 [(0.0, 0.0), (111.0, -1.1999999999999886)] 0 (1.0, 0.0, 0.0, 1.0, 364.0, 15.0) 318.0 146.0 0 1 1 1 (1.0, 0.0, 0.0, 1.0, 433.0, 349.0) 100.0 50.0 0 0 (1.0, 0.0, 0.0, 1.0, 484.0, 300.0) 0 0 [(0.0, 0.0), (-1.0, 49.0)] 0 (1.0, 0.0, 0.0, 1.0, 328.0, 274.0) 0 0 [(0.0, 0.0), (111.0, 0.91726005415705458)] 0 0 (1.0, 0.0, 0.0, 1.0, 313.0, 288.0) 1 0 [(0.0, 0.0), (0.0, 75.0), (120.0, 75.0)] 0 0 (1.0, 0.0, 0.0, 1.0, 243.0, 288.0) 1 0 [(0.0, 0.0), (0.0, 102.0), (190.0, 102.0)] 0 (1.0, 0.0, 0.0, 1.0, 21.0, 24.0) 100.0 70.0 0 1 1 1 (1.0, 0.0, 0.0, 1.0, 33.4140625, 237.0) 114.0 62.0 0 0 (1.0, 0.0, 0.0, 1.0, 228.0, 271.0) 0 0 [(0.0, 0.0), (-80.5859375, 0.68007647446302144)] 0 1 1 1 (1.0, 0.0, 0.0, 1.0, 27.0, 392.0) 134.0 51.0 0 0 (1.0, 0.0, 0.0, 1.0, 80.0, 299.0) 0 0 [(0.0, 0.0), (9.9799999999999898, 93.0)] 0 1 1 1 1 (1.0, 0.0, 0.0, 1.0, 111.0, 113.0) 100.0 58.0 0 1 1 1 (1.0, 0.0, 0.0, 1.0, 109.0, 269.0) 119.0 62.0 0 0 (1.0, 0.0, 0.0, 1.0, 159.0, 171.0) 0 0 [(0.0, 0.0), (9.5, 98.0)] 1 1 1 (1.0, 0.0, 0.0, 1.0, 344.0, 106.0) 147.0 69.0 0 1 1 1 (1.0, 0.0, 0.0, 1.0, 340.0, 235.0) 147.0 93.0 0 0 (1.0, 0.0, 0.0, 1.0, 413.0, 175.0) 0 0 [(0.0, 0.0), (0.0, 60.0)] gaphor-0.17.2/tests/test_action_issue.py000066400000000000000000000061641220151210700203170ustar00rootroot00000000000000 from gaphor import UML from gaphor.tests import TestCase from gaphor.storage import storage from gaphor.diagram.items import FlowItem, ActionItem class ActionIssueTestCase(TestCase): def test_it(self): """ Test an issue when loading a freshly created action diagram. """ ef = self.element_factory storage.load('test-diagrams/action-issue.gaphor', ef) actions = ef.lselect(lambda e: e.isKindOf(UML.Action)) flows = ef.lselect(lambda e: e.isKindOf(UML.ControlFlow)) self.assertEqual(3, len(actions)) self.assertEqual(3, len(flows)) # Actions live in partitions: partitions = ef.lselect(lambda e: e.isKindOf(UML.ActivityPartition)) self.assertEquals(2, len(partitions)) # Okay, so far the data model is saved correctly. Now, how do the # handles behave? diagrams = ef.lselect(lambda e: e.isKindOf(UML.Diagram)) self.assertEquals(1, len(diagrams)) canvas = diagrams[0].canvas assert 9 == len(canvas.get_all_items()) # Part, Part, Act, Act, Part, Act, Flow, Flow, Flow for e in actions + flows: self.assertEquals(1, len(e.presentation), e) for i in canvas.select(lambda e: isinstance(e, (FlowItem, ActionItem))): self.assertTrue(i.subject, i) # Loaded as: # # actions[1] --> flows[0, 1] # flows[0, 2] --> actions[0] # flows[1] --> actions[2] --> flows[2] # start element: self.assertSame(actions[1].outgoing[0], flows[0]) self.assertSame(actions[1].outgoing[1], flows[1]) self.assertFalse(actions[1].incoming) cinfo, = canvas.get_connections(handle=flows[0].presentation[0].head) self.assertSame(cinfo.connected, actions[1].presentation[0]) cinfo, = canvas.get_connections(handle=flows[1].presentation[0].head) self.assertSame(cinfo.connected, actions[1].presentation[0]) # Intermediate element: self.assertSame(actions[2].incoming[0], flows[1]) self.assertSame(actions[2].outgoing[0], flows[2]) cinfo, = canvas.get_connections(handle=flows[1].presentation[0].tail) self.assertSame(cinfo.connected, actions[2].presentation[0]) cinfo, = canvas.get_connections(handle=flows[2].presentation[0].head) self.assertSame(cinfo.connected, actions[2].presentation[0]) # Final element: self.assertSame(actions[0].incoming[0], flows[0]) self.assertSame(actions[0].incoming[1], flows[2]) cinfo, = canvas.get_connections(handle=flows[0].presentation[0].tail) self.assertSame(cinfo.connected, actions[0].presentation[0]) cinfo, = canvas.get_connections(handle=flows[2].presentation[0].tail) self.assertSame(cinfo.connected, actions[0].presentation[0]) # Test the parent-child connectivity for a in actions: p, = a.inPartition self.assertTrue(p) self.assertTrue(canvas.get_parent(a.presentation[0])) self.assertSame(canvas.get_parent(a.presentation[0]), p.presentation[0]) # vim:sw=4:et:ai gaphor-0.17.2/tests/test_gen_uml.py000066400000000000000000000030751220151210700172560ustar00rootroot00000000000000""" Test case that checks the working of the utils/command/gen_uml.py module. """ import unittest from utils.command.gen_uml import generate class PseudoFile(object): def __init__(self): self.data = '' def write(self, data): self.data += data def close(self): pass class GenUmlTestCase(unittest.TestCase): def test_loading(self): model_file = 'tests/test-model.gaphor' outfile = PseudoFile() generate(model_file, outfile) assert outfile.data == GENERATED, '"""%s"""' % outfile.data GENERATED = """# This file is generated by build_uml.py. DO NOT EDIT! from properties import association, attribute, enumeration, derived, derivedunion, redefine # class 'ValSpec' has been stereotyped as 'SimpleAttribute' # class 'ShouldNotShowUp' has been stereotyped as 'SimpleAttribute' too class C(object): pass class D(C): pass class Element(object): pass class SubClass(Element): pass C.attr = attribute('attr', str) # 'SubClass.value' is a simple attribute SubClass.value = attribute('value', str) C.name1 = association('name1', SubClass, opposite='name2') SubClass.name2 = association('name2', C, opposite='name1') C.base = association('base', SubClass, opposite='abstract') D.name3 = association('name3', SubClass, opposite='name4') D.subbase = association('subbase', SubClass, opposite='concrete') SubClass.concrete = association('concrete', D, opposite='subbase') SubClass.abstract = derivedunion('abstract', C, 0, '*', SubClass.concrete) SubClass.name4 = redefine(SubClass, 'name4', D, name2) """ # vim:sw=4:et:ai gaphor-0.17.2/tests/test_issue_132.py000066400000000000000000000026061220151210700173440ustar00rootroot00000000000000 from gaphor.tests import TestCase from gaphor.ui.namespace import NamespaceModel from gaphor import UML from gaphor.diagram import items from gaphor.core import transactional class UndoRedoBugTestCase(TestCase): services = TestCase.services + ['undo_manager'] def setUp(self): super(UndoRedoBugTestCase, self).setUp() self.undo_manager = self.get_service('undo_manager') self.namespace = NamespaceModel(self.element_factory) @transactional def create_with_attribute(self): self.class_ = self.element_factory.create(UML.Class) self.attribute = self.element_factory.create(UML.Property) self.class_.ownedAttribute = self.attribute # Fix: Remove operation should be transactional ;) @transactional def remove_attribute(self): self.attribute.unlink() def test_bug_with_attribute(self): """ Does not trigger the error. """ self.create_with_attribute() assert len(self.class_.ownedAttribute) == 1 assert self.attribute.namespace is self.class_, self.attribute.namespace self.remove_attribute() assert len(self.class_.ownedAttribute) == 0 assert self.attribute.namespace is None self.undo_manager.undo_transaction() assert self.attribute in self.class_.ownedAttribute self.undo_manager.redo_transaction() # vi:sw=4:et:ai gaphor-0.17.2/tests/test_issue_4.py000066400000000000000000000022611220151210700171770ustar00rootroot00000000000000""" Test GitHub issue #4. Diagram could not be loaded due to JuggleError (presumed cyclic resolving of diagram items). """ from gaphor.tests import TestCase from gaphor import UML from gaphor.storage.storage import load class CyclicDiagramTestCase(TestCase): #services = TestCase.services + ['undo_manager'] def setUp(self): super(CyclicDiagramTestCase, self).setUp() def test_bug(self): """ Load file. This does not nearly resemble the error, since the model should be loaded from within the mainloop (which will delay all updates). """ load('test-diagrams/diagram-#4.gaphor', self.element_factory) def test_bug_idle(self): """ Load file in gtk main loop. This does not nearly resemble the error, since the model should be loaded from within the mainloop (which will delay all updates). """ import gobject, gtk def handler(): try: load('test-diagrams/diagram-#4.gaphor', self.element_factory) finally: gtk.main_quit() assert gobject.timeout_add(1, handler) > 0 gtk.main() # vi:sw=4:et:ai gaphor-0.17.2/tests/test_issue_53.py000066400000000000000000000044751220151210700172740ustar00rootroot00000000000000 import unittest from gaphor import UML from gaphor.application import Application import pkg_resources class PackageWithStereotypesRemovalTestCase(unittest.TestCase): def setUp(self): Application.init() element_factory = Application.get_service('element_factory') from gaphor.storage.storage import load load('tests/issue_53.gaphor', element_factory) def tearDown(self): Application.get_service('element_factory').shutdown() Application.shutdown() def testPackageRemoval(self): # Load the application element_factory = Application.get_service('element_factory') # Find all profile instances profiles = element_factory.lselect(lambda e: e.isKindOf(UML.Profile)) # Check there is 1 profile self.assertEquals(1, len(profiles)) # Check the profile has 1 presentation self.assertEquals(1, len(profiles[0].presentation)) # Unlink the presentation profiles[0].presentation[0].unlink() self.assertFalse(element_factory.lselect(lambda e: e.isKindOf(UML.Profile))) classes = element_factory.lselect(lambda e: e.isKindOf(UML.Class)) self.assertEquals(1, len(classes)) # Check if the link is really removed: self.assertFalse(classes[0].appliedStereotype) self.assertFalse(element_factory.lselect(lambda e: e.isKindOf(UML.InstanceSpecification))) self.assertEquals(3, len(element_factory.lselect(lambda e: e.isKindOf(UML.Diagram)))) def testPackageRemovalByRemovingTheDiagram(self): element_factory = Application.get_service('element_factory') diagram = element_factory.lselect(lambda e: e.isKindOf(UML.Diagram) and e.name == 'Stereotypes diagram')[0] self.assertTrue(diagram) diagram.unlink() self.assertFalse(element_factory.lselect(lambda e: e.isKindOf(UML.Profile))) classes = element_factory.lselect(lambda e: e.isKindOf(UML.Class)) self.assertEquals(1, len(classes)) # Check if the link is really removed: self.assertFalse(classes[0].appliedStereotype) self.assertFalse(element_factory.lselect(lambda e: e.isKindOf(UML.InstanceSpecification))) self.assertEquals(2, len(element_factory.lselect(lambda e: e.isKindOf(UML.Diagram)))) # vim:sw=4:et:ai gaphor-0.17.2/tests/test_issue_gaphas.py000066400000000000000000000017131220151210700203000ustar00rootroot00000000000000 from gaphor.tests import TestCase from gaphor import UML from gaphor.diagram import items from gaphor.diagram.interfaces import IConnect from zope import component from gaphas.aspect import ConnectionSink import gaphor.adapters.classes.classconnect class GaphasTest(TestCase): services = TestCase.services + [ 'sanitizer_service', 'undo_manager' ] def test_remove_class_with_association(self): c1 = self.create(items.ClassItem, UML.Class) c1.name = 'klassitem1' c2 = self.create(items.ClassItem, UML.Class) c2.name = 'klassitem2' a = self.create(items.AssociationItem) assert 3 == len(self.diagram.canvas.get_all_items()) self.connect(a, a.head, c1) self.connect(a, a.tail, c2) assert a.subject assert self.element_factory.lselect(lambda e: e.isKindOf(UML.Association))[0] is a.subject c1.unlink() self.diagram.canvas.update_now() # vim:sw=4:et:ai gaphor-0.17.2/tests/test_undo.py000066400000000000000000000034541220151210700165760ustar00rootroot00000000000000 from gaphor.tests import TestCase from gaphor import UML from gaphor.diagram import items from gaphor.core import transactional class UndoTest(TestCase): services = TestCase.services + [ 'undo_manager' ] def test_class_association_undo_redo(self): factory = self.element_factory undo_manager = self.get_service('undo_manager') ci1 = self.create(items.ClassItem, UML.Class) self.assertEquals(6, len(self.diagram.canvas.solver.constraints)) ci2 = self.create(items.ClassItem, UML.Class) self.assertEquals(12, len(self.diagram.canvas.solver.constraints)) a = self.create(items.AssociationItem) self.connect(a, a.head, ci1) self.connect(a, a.tail, ci2) # Diagram, Association, 2x Class, Property, LiteralSpecification self.assertEquals(8, len(factory.lselect())) self.assertEquals(14, len(self.diagram.canvas.solver.constraints)) @transactional def delete_class(): ci2.unlink() undo_manager.clear_undo_stack() self.assertFalse(undo_manager.can_undo()) delete_class() self.assertTrue(undo_manager.can_undo()) self.assertEquals(ci1, self.get_connected(a.head)) self.assertEquals(None, self.get_connected(a.tail)) for i in range(3): # Diagram, Class #self.assertEquals(2, len(factory.lselect()), factory.lselect()) self.assertEquals(7, len(self.diagram.canvas.solver.constraints)) undo_manager.undo_transaction() self.assertEquals(14, len(self.diagram.canvas.solver.constraints)) self.assertEquals(ci1, self.get_connected(a.head)) self.assertEquals(ci2, self.get_connected(a.tail)) undo_manager.redo_transaction() # vim:sw=4:et:ai gaphor-0.17.2/utils/000077500000000000000000000000001220151210700142105ustar00rootroot00000000000000gaphor-0.17.2/utils/__init__.py000066400000000000000000000000001220151210700163070ustar00rootroot00000000000000gaphor-0.17.2/utils/browseUML.py000077500000000000000000000012041220151210700164410ustar00rootroot00000000000000#!/usr/bin/env python # # Print all attributes of a specific class. # # Usage: browseUML.py # E.g. browseUML.py Class # # Arjan Molenaar. import sys sys.path.append("..") from gaphor.UML import * done = [ object ] def print_vars(cls): global done done.append(cls) print cls.__name__ + ":" dict = cls.__dict__ for key in dict.keys(): print "\t" + key + ":", str(dict[key]) for base in cls.__bases__: if base not in done: print_vars(base) args = sys.argv[1:] if args: cls = eval(args[0]) print_vars(cls) else: print "Usage: " + sys.argv[0] + " " sys.exit(1) gaphor-0.17.2/utils/command/000077500000000000000000000000001220151210700156265ustar00rootroot00000000000000gaphor-0.17.2/utils/command/__init__.py000066400000000000000000000000621220151210700177350ustar00rootroot00000000000000""" Additional distutils commands for Gaphor. """ gaphor-0.17.2/utils/command/build_mo.py000066400000000000000000000034261220151210700177770ustar00rootroot00000000000000# vim:sw=4:et """build_mo Generate .mo files from po files. """ from distutils.core import Command from distutils.dep_util import newer import os.path import msgfmt class build_mo(Command): description = 'Create .mo files from .po files' # List of option tuples: long name, short name (None if no short # name), and help string. user_options = [('build-dir=', None, 'Directory to build locale files'), ('force', 'f', 'Force creation of .mo files'), ('all-linguas', None, ''), ] boolean_options = ['force'] def initialize_options (self): self.build_dir = None self.force = None self.all_linguas = None def finalize_options (self): self.set_undefined_options('build', ('force', 'force')) if self.build_dir is None: self.set_undefined_options('build', ('build_lib', 'build_dir')) self.build_dir = os.path.join(self.build_dir, 'gaphor', 'data', 'locale') self.all_linguas = self.all_linguas.split(',') def run (self): """Run msgfmt.make() on all_linguas.""" if not self.all_linguas: return for lingua in self.all_linguas: pofile = os.path.join('po', lingua + '.po') outdir = os.path.join(self.build_dir, lingua, 'LC_MESSAGES') self.mkpath(outdir) outfile = os.path.join(outdir, 'gaphor.mo') if self.force or newer(pofile, outfile): print 'converting %s -> %s' % (pofile, outfile) msgfmt.make(pofile, outfile) else: print 'not converting %s (output up-to-date)' % pofile from distutils.command.build import build build.sub_commands.append(('build_mo', None)) gaphor-0.17.2/utils/command/build_pot.py000066400000000000000000000117121220151210700201630ustar00rootroot00000000000000# vim:sw=4:et """build_pot Build a PO template (for i18n) and update the .po files to reflect the last changes. """ from distutils.core import Command from commands import getstatus import sys, os.path import pygettext # from pygettext.main(): class Options: # constants GNU = 1 SOLARIS = 2 # defaults extractall = 0 # FIXME: currently this option has no effect at all. keywords = [] writelocations = 1 locationstyle = GNU verbose = 0 width = 78 excludefilename = '' docstrings = 0 nodocstrings = {} toexclude = [] class build_pot(Command): description="Generate a .po template file (.pot) from python source files" user_options = [('msgmerge=', None, 'location of the msgmerge program'), ('extract-all', 'a', ''), ('default-domain=', 'd', ''), ('escape', 'E', ''), ('docstrings', 'D', ''), ('keyword=', 'k', 'Comma separated list of keywords'), ('no-default-keywords', 'K', ''), ('add-location', 'n', ''), ('no-location', None, ''), ('style=', 'S', 'POT file style "gnu" or "solaris"'), ('output=', 'o', ''), ('output-dir=', 'p', ''), ('width=', 'w', ''), ('exclude-file=', 'x', ''), ('all-linguas=', None, ''), #('no-docstrings=', 'X', ''), ] boolean_options = [ 'extract-all', 'escape', 'docstrings', 'no-default-keywords', 'add-location', 'no-location', 'no-docstrings' ] # constants GNU = 1 SOLARIS = 2 def initialize_options(self): self.podir = 'po' self.msgmerge = 'msgmerge' self.options = Options() # defaults for variable parsing: self.escape = 0 self.width = 78 self.extract_all = 0 # doesn't do anything yet self.default_domain = None self.keyword = None self.no_default_keywords = 0 self.no_location = 0 self.style = None self.output = None self.output_dir = None self.docstrings = 0 self.exclude_file = None #self.no_docstrings = None self.all_linguas = [] def finalize_options(self): options = self.options self.name = self.distribution.get_name() # Build default options for the TokenEater if self.default_domain: self.output = self.default_domain + '.pot' if self.keyword: options.keywords.extend(self.keyword.split(',')) if self.no_default_keywords: options.keywords = [ ] if self.no_location: options.writelocations = 0 if self.style: if self.style == 'gnu': options.locationstyle = self.GNU elif self.style == 'solaris': options.locationstyle = self.SOLARIS else: raise SystemExit, 'Invalid value for --style: %s' % self.style if not self.output: self.output = self.distribution.get_name() + '.pot' if not self.output_dir: self.output_dir = self.podir if self.docstrings: options.docstrings = 1 options.width = int(self.width) if self.exclude_file: try: fp = open(self.exclude_file) options.toexclude = fp.readlines() fp.close() except IOError: raise SystemExit, "Can't read --exclude-file: %s" % self.exclude_file # skip: self.no_docstrings if self.all_linguas: self.all_linguas = self.all_linguas.split(',') # calculate escapes pygettext.make_escapes(self.escape) # calculate all keywords options.keywords.append('_') if self.output_dir: self.output = os.path.join(self.output_dir, self.output) self.packages = self.distribution.packages #self.all_linguas = self.distribution.get_all_linguas() #self.all_linguas = self.distribution.options['po']['all_linguas'] def run(self): self.create_pot_file() self.merge_files() def create_pot_file(self): """ Create a new .pot file. This is basically a rework of the main function of pygettext. """ import glob import tokenize source_files = [] for p in self.packages: pathlist = p.split('.') path = apply(os.path.join, pathlist) source_files.extend(glob.glob(os.path.join(path, '*.py'))) # slurp through all the files eater = pygettext.TokenEater(self.options) for filename in source_files: if self.verbose: print 'Working on %s' % filename fp = open(filename) try: eater.set_filename(filename) try: tokenize.tokenize(fp.readline, eater) except tokenize.TokenError, e: print '%s: %s, line %d, column %d' % ( e[0], filename, e[1][0], e[1][1]) finally: fp.close() # write the output if self.output == '-': fp = sys.stdout else: fp = open(self.output, 'w') try: eater.write(fp) finally: if fp is not sys.stdout: fp.close() def merge_files(self): if not self.all_linguas: return for lingua in self.all_linguas: d = { 'msgmerge': self.msgmerge, 'po': os.path.join(self.output_dir, lingua + '.po'), 'pot': self.output } if self.verbose: sys.stdout.write('Merging %(pot)s and %(po)s ' % d) sys.stdout.flush() res = os.system('%(msgmerge)s %(po)s %(pot)s -o %(po)s' % d) if res: SystemExit, 'error while running msgmerge.' gaphor-0.17.2/utils/command/build_uml.py000066400000000000000000000036741220151210700201660ustar00rootroot00000000000000#!/usr/bin/env python """ This file provides the code generator which transforms gaphor/UML/uml2.gaphor into gaphor/UML/uml2.py. Also a distutils tool, build_uml, is provided. """ import os.path from distutils.core import Command from distutils.util import byte_compile from distutils.dep_util import newer class build_uml(Command): description = "Generate gaphor/UML/uml2.py." user_options = [ ('build-lib=','b', "build directory (where to install from)"), ('force', 'f', "force installation (overwrite existing files)"), ] boolean_options = [ 'force' ] def initialize_options(self): #self.build_lib = None self.force = 0 self.data_dir = None def finalize_options(self): self.set_undefined_options('build', #('build_lib', 'build_lib'), ('force', 'force')) def run(self): import sys #sys.path.insert(0, self.build_lib) self.generate_uml2() def generate_uml2(self): """ Generate gaphor/UML/uml2.py in the build directory. """ gen = os.path.join('utils', 'command', 'gen_uml.py') overrides = os.path.join('gaphor', 'UML', 'uml2.override') model = os.path.join('gaphor', 'UML', 'uml2.gaphor') py_model = os.path.join('gaphor', 'UML', 'uml2.py') outfile = py_model #os.path.join(self.build_lib, py_model) self.mkpath(os.path.dirname(outfile)) if self.force or newer(model, outfile) \ or newer(overrides, outfile) \ or newer(gen, outfile): print 'generating %s from %s...' % (py_model, model) print ' (warnings can be ignored)' import gen_uml gen_uml.generate(model, outfile, overrides) else: print 'not generating %s (up-to-date)' % py_model byte_compile([outfile]) # vim:sw=4:et gaphor-0.17.2/utils/command/gen_uml.py000066400000000000000000000465711220151210700176430ustar00rootroot00000000000000#!/usr/bin/env python """ This file provides the code generator which transforms gaphor/UML/uml2.gaphor into gaphor/UML/uml2.py. Also a distutils tool, build_uml, is provided. """ # # Create a UML 2.0 datamodel from the Gaphor 0.2.0 model file. # # To do this we do the following: # 1. read the model file with the gaphor parser # 2. Create a object herarcy by ordering elements based on generalizations # Recreate the model using some very dynamic class, so we can set all # attributes and traverse them to generate the data model. from gaphor.storage.parser import parse, base, element, canvas, canvasitem import sys, string, operator import override header = """# This file is generated by build_uml.py. DO NOT EDIT! from properties import association, attribute, enumeration, derived, derivedunion, redefine """ # Make getitem behave more politely base.__real_getitem__ = base.__getitem__ def base__getitem__(self, key): try: return self.__real_getitem__(key) except KeyError: return None base.__getitem__ = base__getitem__ import re pattern = r'([A-Z])' sub = r'_\1' def camelCase_to_underscore(str): """ >>> camelCase_to_underscore('camelcase') 'camelcase' >>> camelCase_to_underscore('camelCase') 'camel_case' >>> camelCase_to_underscore('camelCamelCase') 'camel_camel_case' """ return re.sub(pattern, sub, str).lower() _ = camelCase_to_underscore def msg(s): sys.stderr.write(' ') sys.stderr.write(s) sys.stderr.write('\n') sys.stderr.flush() class Writer: def __init__(self, filename, overrides=None): self.overrides = overrides if filename: self.out = hasattr(filename, 'write') and filename or open(filename, 'w') else: self.out = sys.stdout def write(self, data): self.out.write(data) def close(self): self.out.close() def write_classdef(self, clazz): """ Write a class definition (class xx(x): pass). First the parent classes are examined. After that its own definition is written. It is ensured that class definitions are only written once. """ if not clazz.written: s = '' for g in clazz.generalization: self.write_classdef(g) if s: s += ', ' s = s + g['name'] if not s: s = 'object' if not self.overrides.write_override(self, clazz['name']): self.write('class %s(%s): pass\n' % (clazz['name'], s)) clazz.written = True def write_property(self, full_name, value): """ Write a property to the file. If the property is overridden, use the overridden value. full_name should be like Class.attribute. value is free format text. """ if not self.overrides.write_override(self, full_name): self.write('%s = %s\n' % (full_name, value)) def write_attribute(self, a, enumerations={}): """ Write a definition for attribute a. Enumerations may be a dict of enumerations, indexed by ID. These are used to identify enums. """ params = { } type = a.typeValue if type is None: raise ValueError('ERROR! type is not specified for property %s.%s' % (a.class_name, a.name)) print a.class_name, a.name, 'type is', type if type.lower() == 'boolean': # FixMe: Should this be a boolean or an integer? # Integer is save and compattable with python2.2. type = 'int' elif type.lower() in ('integer', 'unlimitednatural'): type = 'int' elif type.lower() == 'string': # Change to basestr for Python 2.3 type = 'str' #type = '(str, unicode)' default = a.defaultValue # Make sure types are represented the Python way: if default and default.lower() in ('true', 'false'): default = default.title() # True or False... if default is not None: params['default'] = str(default) lower = a.lowerValue if lower and lower != '0': params['lower'] = lower upper = a.upperValue if upper == '*': params['upper'] = "'*'" elif upper and upper != '1': params['upper'] = upper #kind, derived, a.name, type, default, lower, upper = parse_attribute(a) full_name = "%s.%s" % (a.class_name, a.name) if self.overrides.has_override(full_name): self.overrides.write_override(self, full_name) elif eval(a.isDerived or '0'): msg('ignoring derived attribute %s.%s: no definition' % (a.class_name, a.name)) elif type.endswith('Kind') or type.endswith('Sort'): e = filter(lambda e: e['name'] == type, enumerations.values())[0] self.write_property("%s.%s" % (a.class_name, a.name), "enumeration('%s', %s, '%s')" % (a.name, e.enumerates, default or e.enumerates[0])) else: if params: attribute = "attribute('%s', %s, %s)" % (a.name, type, ', '.join(map('='.join, params.items()))) else: attribute = "attribute('%s', %s)" % (a.name, type) self.write_property("%s.%s" % (a.class_name, a.name), attribute) def write_operation(self, o): full_name = "%s.%s" % (o.class_name, o.name) if self.overrides.has_override(full_name): self.overrides.write_override(self, full_name) else: msg("No override for operation %s" % full_name) def write_association(self, head, tail): """ Write an association for head. The association should not be a redefine or derived association. """ if head.written: return assert head.navigable # Derived unions and redefines are handled separately assert not head.derived assert not head.redefines a = "association('%s', %s" % (head.name, head.opposite_class_name) if head.lower not in ('0', 0): a += ', lower=%s' % head.lower if head.upper != '*': a += ', upper=%s' % head.upper if head.composite: a += ', composite=True' # Add the opposite property if the head itself is navigable: if tail.navigable: try: #o_derived, o_name = parse_association_name(tail['name']) o_name = tail.name o_derived = tail.derived except KeyError: msg('ERROR! no name, but navigable: %s (%s.%s)' % (tail.id, tail.class_name, tail.name)) else: assert not (head.derived and not o_derived), 'One end is derived, the other end not ???' a += ", opposite='%s'" % o_name self.write_property("%s.%s" % (head.class_name, head.name), a + ')') def write_derivedunion(self, d): """ Write a derived union. If there are no subsets a warning is issued. The derivedunion is still created though. Derived unions may be created for associations that were returned False by write_association(). """ subs = '' for u in d.union: if u.derived and not u.written: self.write_derivedunion(u) if subs: subs += ', ' subs += '%s.%s' % (u.class_name, u.name) if subs: self.write_property("%s.%s" % (d.class_name, d.name), "derivedunion('%s', %s, %s, %s, %s)" % (d.name, d.opposite_class_name, d.lower, d.upper == '*' and "'*'" or d.upper, subs)) else: if not self.overrides.has_override('%s.%s' % (d.class_name, d.name)): msg('no subsets for derived union: %s.%s[%s..%s]' % (d.class_name, d.name, d.lower, d.upper)) self.write_property("%s.%s" % (d.class_name, d.name), "derivedunion('%s', %s, %s, %s)" % (d.name, d.opposite_class_name, d.lower, d.upper == '*' and "'*'" or d.upper)) d.written = True def write_redefine(self, r): """ Redefines may be created for associations that were returned False by write_association(). """ self.write_property("%s.%s" % (r.class_name, r.name), "redefine(%s, '%s', %s, %s)" % (r.class_name, r.name, r.opposite_class_name, r.redefines)) def parse_association_name(name): # First remove spaces name = name.replace(' ','') derived = False # Check if this is a derived union while name and not name[0].isalpha(): if name[0] == '/': derived = True name = name[1:] return derived, name def parse_association_tags(appliedStereotypes): subsets = [] redefines = None for stereotype in appliedStereotypes or []: for slot in stereotype.slot or []: #msg('scanning %s = %s' % (slot.definingFeature.name, slot.value.value)) if slot.definingFeature.name == 'subsets': value = slot.value # remove all whitespaces and stuff value = value.replace(' ', '').replace('\n', '').replace('\r', '') subsets = value.split(',') if slot.definingFeature.name == 'redefines': value = slot.value # remove all whitespaces and stuff redefines = value.replace(' ', '').replace('\n', '').replace('\r', '') #print 'found', subsets, redefines return subsets, redefines def parse_association_end(head, tail): """ The head association end is enriched with the following attributes: derived - association is a derived union or not name - name of the association end (name of head is found on tail) class_name - name of the class this association belongs to opposite_class_name - name of the class at the other end of the assoc. lower - lower multiplicity upper - upper multiplicity composite - if the association has a composite relation to the other end subsets - derived unions that use the association redefines - redefines existing associations """ head.navigable = head.get('class_') if not head.navigable: # from this side, the association is not navigable return name = head.name if name is None: raise ValueError('ERROR! no name, but navigable: %s (%s.%s)' % (head.id, head.class_name, head.name)) #print head.id, head.lowerValue upper = head.upperValue or '*' lower = head.lowerValue or upper if lower == '*': lower = 0 subsets, redefines = parse_association_tags(head.appliedStereotype) # Add the values found. These are used later to generate derived unions. head.class_name = head.class_['name'] head.opposite_class_name = head.type['name'] head.lower = lower head.upper = upper head.subsets = subsets head.composite = head.get('aggregation') == 'composite' head.derived = int(head.isDerived or 0) head.redefines = redefines def generate(filename, outfile=None, overridesfile=None): # parse the file all_elements = parse(filename) def resolve(val, attr): """Resolve references. """ try: refs = val.references[attr] except KeyError: val.references[attr] = None return if type(refs) is type([]): unrefs = [] for r in refs: unrefs.append(all_elements[r]) val.references[attr] = unrefs else: val.references[attr] = all_elements[refs] overrides = override.Overrides(overridesfile) writer = Writer(outfile, overrides) # extract usable elements from all_elements. Some elements are given # some extra attributes. classes = { } enumerations = { } generalizations = { } associations = { } properties = { } operations = { } extensions = { } # for identifying metaclasses for key, val in all_elements.items(): # Find classes, *Kind (enumerations) are given special treatment if isinstance(val, element): if val.type == 'Class' and val.get('name'): if val['name'].endswith('Kind') or val['name'].endswith('Sort'): enumerations[key] = val else: # Metaclasses are removed later on (need to be checked # via the Extension instances) classes[key] = val # Add extra properties for easy code generation: val.specialization = [] val.generalization = [] val.stereotypeName = None val.written = False elif val.type == 'Generalization': generalizations[key] = val elif val.type == 'Association': val.asAttribute = None associations[key] = val elif val.type == 'Property': properties[key] = val #resolve(val, 'typeValue') #resolve(val, 'defaultValue') #resolve(val, 'lowerValue') #resolve(val, 'upperValue') resolve(val, 'appliedStereotype') for st in val.appliedStereotype or []: resolve(st, 'slot') for slot in st.slot or []: resolve(slot, 'value') resolve(slot, 'definingFeature') val.written = False elif val.type == 'Operation': operations[key] = val elif val.type == 'Extension': extensions[key] = val # find inheritance relationships for g in generalizations.values(): #assert g.specific and g.general specific = g['specific'] general = g['general'] classes[specific].generalization.append(classes[general]) classes[general].specialization.append(classes[specific]) # add values to enumerations: for e in enumerations.values(): values = [] for key in e['ownedAttribute']: values.append(str(properties[key]['name'])) e.enumerates = tuple(values) # Remove metaclasses from classes dict # should check for Extension.memberEnd.type for e in extensions.values(): ends = [] for end in e.memberEnd: end = all_elements[end] if not end['type']: continue end.type = all_elements[end['type']] ends.append(end) e.memberEnd = ends if ends: del classes[e.memberEnd[0].type.id] # create file header writer.write(header) # Tag classes with appliedStereotype for c in classes.values(): if c.get('appliedStereotype'): # Figure out stereotype name through # Class.appliedStereotype.classifier.name instSpec = all_elements[c.appliedStereotype[0]] sType = all_elements[instSpec.classifier[0]] c.stereotypeName = sType.name print " class '%s' has been stereotyped as '%s'" % (c.name, c.stereotypeName) writer.write("# class '%s' has been stereotyped as '%s'\n" % (c.name, c.stereotypeName)) #c.written = True def tag_children(me): for child in me.specialization: child.stereotypeName = sType.name print " class '%s' has been stereotyped as '%s' too" % (child.name, child.stereotypeName) writer.write("# class '%s' has been stereotyped as '%s' too\n" % (child.name, child.stereotypeName)) #child.written = True tag_children(child) tag_children(c) ignored_classes = set() # create class definitions, not for SimpleAttribute for c in classes.values(): if c.stereotypeName == 'SimpleAttribute': ignored_classes.add(c) else: writer.write_classdef(c) # create attributes and enumerations derivedattributes = { } for c in filter(lambda c: c not in ignored_classes, classes.values()): for p in c.get('ownedAttribute') or []: a = properties.get(p) # set class_name, since write_attribute depends on it a.class_name = c['name'] if not a.get('association'): if overrides.derives('%s.%s' % (a.class_name, a.name)): derivedattributes[a.name] = a else: writer.write_attribute(a, enumerations) # create associations, derivedunions are held back derivedunions = { } # indexed by name in stead of id redefines = [ ] for a in associations.values(): ends = [] # Resolve some properties: for end in a.memberEnd: end = properties[end] end.type = classes[end['type']] end.class_ = end.get('class_') and classes[end['class_']] or None end.is_simple_attribute = False if end.type and end.type.stereotypeName == 'SimpleAttribute': end.is_simple_attribute = True a.asAttribute = end ends.append(end) for e1, e2 in ((ends[0], ends[1]), (ends[1], ends[0])): parse_association_end(e1, e2) for e1, e2 in ((ends[0], ends[1]), (ends[1], ends[0])): if a.asAttribute: if a.asAttribute is e1 and e1.navigable: writer.write("# '%s.%s' is a simple attribute\n" % (e2.type.name, e1.name)) e1.class_name = e2.type.name e1.typeValue = 'str' writer.write_attribute(e1, enumerations) e1.written = True e2.written = True elif e1.redefines: redefines.append(e1) elif e1.derived or overrides.derives('%s.%s' % (e1.class_name, e1.name)): assert not derivedunions.get(e1.name), "%s.%s is already in derived union set in class %s" % (e1.class_name, e1.name, derivedunions.get(e1.name).class_name) derivedunions[e1.name] = e1 e1.union = [ ] e1.written = False elif e1.navigable: writer.write_association(e1, e2) # create derived unions, first link the association ends to the d for a in (v for v in properties.values() if v.subsets): for s in a.subsets or (): try: if a.type not in ignored_classes: derivedunions[s].union.append(a) except KeyError: msg('not a derived union: %s.%s' % (a.class_name, s)) # TODO: We should do something smart here, since derived attributes (mostly) # may depend on other derived attributes or associations. for d in derivedattributes.values(): writer.write_attribute(d) for d in derivedunions.values(): writer.write_derivedunion(d) for r in redefines or (): msg('redefining %s -> %s.%s' % (r.redefines, r.class_name, r.name)) writer.write_redefine(r) # create operations for c in filter(lambda c: c not in ignored_classes, classes.values()): for p in c.get('ownedOperation') or (): o = operations.get(p) o.class_name = c['name'] writer.write_operation(o) writer.close() if __name__ == '__main__': import doctest doctest.testmod() generate('uml2.gaphor') # vim:sw=4:et:ai gaphor-0.17.2/utils/command/install_lib.py000066400000000000000000000003771220151210700205030ustar00rootroot00000000000000 from setuptools.command.install_lib import install_lib as _install_lib class install_lib(_install_lib): def build(self): _install_lib.build(self) self.run_command('build_uml') self.run_command('build_mo') # vim:sw=4:et:ai gaphor-0.17.2/utils/command/msgfmt.py000077500000000000000000000125771220151210700175140ustar00rootroot00000000000000#! /usr/bin/env python # Written by Martin v. Lo"wis """Generate binary message catalog from textual translation description. This program converts a textual Uniforum-style message catalog (.po file) into a binary GNU catalog (.mo file). This is essentially the same function as the GNU msgfmt program, however, it is a simpler implementation. Usage: msgfmt.py [OPTIONS] filename.po Options: -o file --output-file=file Specify the output file to write to. If omitted, output will go to a file named filename.mo (based off the input file name). -h --help Print this message and exit. -V --version Display version information and exit. """ import sys import os import getopt import struct import array __version__ = "1.1" MESSAGES = {} def usage(code, msg=''): print >> sys.stderr, __doc__ if msg: print >> sys.stderr, msg sys.exit(code) def add(id, str, fuzzy): "Add a non-fuzzy translation to the dictionary." global MESSAGES if not fuzzy and str: MESSAGES[id] = str def generate(): "Return the generated output." global MESSAGES keys = MESSAGES.keys() # the keys are sorted in the .mo file keys.sort() offsets = [] ids = strs = '' for id in keys: # For each string, we need size and file offset. Each string is NUL # terminated; the NUL does not count into the size. offsets.append((len(ids), len(id), len(strs), len(MESSAGES[id]))) ids += id + '\0' strs += MESSAGES[id] + '\0' output = '' # The header is 7 32-bit unsigned integers. We don't use hash tables, so # the keys start right after the index tables. # translated string. keystart = 7*4+16*len(keys) # and the values start after the keys valuestart = keystart + len(ids) koffsets = [] voffsets = [] # The string table first has the list of keys, then the list of values. # Each entry has first the size of the string, then the file offset. for o1, l1, o2, l2 in offsets: koffsets += [l1, o1+keystart] voffsets += [l2, o2+valuestart] offsets = koffsets + voffsets output = struct.pack("Iiiiiii", 0x950412deL, # Magic 0, # Version len(keys), # # of entries 7*4, # start of key index 7*4+len(keys)*8, # start of value index 0, 0) # size and offset of hash table output += array.array("i", offsets).tostring() output += ids output += strs return output def make(filename, outfile): ID = 1 STR = 2 # Compute .mo name from .po name and arguments if filename.endswith('.po'): infile = filename else: infile = filename + '.po' if outfile is None: outfile = os.path.splitext(infile)[0] + '.mo' try: lines = open(infile).readlines() except IOError, msg: print >> sys.stderr, msg sys.exit(1) section = None fuzzy = 0 # Parse the catalog lno = 0 for l in lines: lno += 1 # If we get a comment line after a msgstr, this is a new entry if l[0] == '#' and section == STR: add(msgid, msgstr, fuzzy) section = None fuzzy = 0 # Record a fuzzy mark if l[:2] == '#,' and l.find('fuzzy'): fuzzy = 1 # Skip comments if l[0] == '#': continue # Now we are in a msgid section, output previous section if l.startswith('msgid'): if section == STR: add(msgid, msgstr, fuzzy) section = ID l = l[5:] msgid = msgstr = '' # Now we are in a msgstr section elif l.startswith('msgstr'): section = STR l = l[6:] # Skip empty lines l = l.strip() if not l: continue # XXX: Does this always follow Python escape semantics? l = eval(l) if section == ID: msgid += l elif section == STR: msgstr += l else: print >> sys.stderr, 'Syntax error on %s:%d' % (infile, lno), \ 'before:' print >> sys.stderr, l sys.exit(1) # Add last entry if section == STR: add(msgid, msgstr, fuzzy) # Compute output output = generate() try: open(outfile,"wb").write(output) except IOError,msg: print >> sys.stderr, msg def main(): try: opts, args = getopt.getopt(sys.argv[1:], 'hVo:', ['help', 'version', 'output-file=']) except getopt.error, msg: usage(1, msg) outfile = None # parse options for opt, arg in opts: if opt in ('-h', '--help'): usage(0) elif opt in ('-V', '--version'): print >> sys.stderr, "msgfmt.py", __version__ sys.exit(0) elif opt in ('-o', '--output-file'): outfile = arg # do it if not args: print >> sys.stderr, 'No input file given' print >> sys.stderr, "Try `msgfmt --help' for more information." return for filename in args: make(filename, outfile) if __name__ == '__main__': main() gaphor-0.17.2/utils/command/override.py000066400000000000000000000050611220151210700200210ustar00rootroot00000000000000""" This file contains code for loading up an override file. The override file provides implementations of functions where the code generator could not do its job correctly. This is a simple rip-off of the override script used in PyGTK. """ import sys, string class Overrides: def __init__(self, filename=None): self.overrides = {} if filename: self.read_overrides(filename) def read_overrides(self, filename): """Read a file and return a dictionary of overriden properties and their implementation. An override file ahs the form: override %% """ fp = open(filename, 'r') # read all the components of the file ... # bufs contains a list of (lines, startline) pairs. bufs = [] startline = 1 lines = [] line = fp.readline() linenum = 1 while line: if line == '%%\n' or line == '%%': if lines: bufs.append((list(lines), startline)) startline = linenum + 1 lines = [] else: lines.append(line) line = fp.readline() linenum = linenum + 1 if lines: bufs.append((list(lines), startline)) if not bufs: return # Parse the parts of the file for lines, startline in bufs: line = lines[0] rest = lines[1:] words = string.split(line) # TODO: Create a mech to define dependencies if words[0] == 'override': func = words[1] deps = () if len(words) > 3 and words[2] == 'derives': deps = tuple(words[3:]) self.overrides[func] = (deps, string.join(rest, ''), '%d: %s' % (startline, line)) elif words[0] == 'comment': pass # ignore comments else: print "Unknown word: '%s', line %d" (words[0], startline) raise SystemExit def has_override(self, key): return bool(self.overrides.get(key)) def derives(self, key): return self.overrides.get(key, ((), None))[0] def write_override(self, fp, key): """Write override data for 'key' to a file refered to by 'fp'.""" deps, data, line = self.overrides.get(key, ((), None, None)) if not data: return False fp.write('# ') fp.write(line) fp.write(data) return True # vim:sw=4:et:ai gaphor-0.17.2/utils/command/pygettext.py000077500000000000000000000530451220151210700202470ustar00rootroot00000000000000#! /usr/bin/env python # -*- coding: iso-8859-1 -*- # Originally written by Barry Warsaw # # Minimally patched to make it even more xgettext compatible # by Peter Funk # # 2002-11-22 Jürgen Hermann # Added checks that _() only contains string literals, and # command line args are resolved to module lists, i.e. you # can now pass a filename, a module or package name, or a # directory (including globbing chars, important for Win32). # Made docstring fit in 80 chars wide displays using pydoc. # # for selftesting try: import fintl _ = fintl.gettext except ImportError: _ = lambda s: s __doc__ = _("""pygettext -- Python equivalent of xgettext(1) Many systems (Solaris, Linux, Gnu) provide extensive tools that ease the internationalization of C programs. Most of these tools are independent of the programming language and can be used from within Python programs. Martin von Loewis' work[1] helps considerably in this regard. There's one problem though; xgettext is the program that scans source code looking for message strings, but it groks only C (or C++). Python introduces a few wrinkles, such as dual quoting characters, triple quoted strings, and raw strings. xgettext understands none of this. Enter pygettext, which uses Python's standard tokenize module to scan Python source code, generating .pot files identical to what GNU xgettext[2] generates for C and C++ code. From there, the standard GNU tools can be used. A word about marking Python strings as candidates for translation. GNU xgettext recognizes the following keywords: gettext, dgettext, dcgettext, and gettext_noop. But those can be a lot of text to include all over your code. C and C++ have a trick: they use the C preprocessor. Most internationalized C source includes a #define for gettext() to _() so that what has to be written in the source is much less. Thus these are both translatable strings: gettext("Translatable String") _("Translatable String") Python of course has no preprocessor so this doesn't work so well. Thus, pygettext searches only for _() by default, but see the -k/--keyword flag below for how to augment this. [1] http://www.python.org/workshops/1997-10/proceedings/loewis.html [2] http://www.gnu.org/software/gettext/gettext.html NOTE: pygettext attempts to be option and feature compatible with GNU xgettext where ever possible. However some options are still missing or are not fully implemented. Also, xgettext's use of command line switches with option arguments is broken, and in these cases, pygettext just defines additional switches. Usage: pygettext [options] inputfile ... Options: -a --extract-all Extract all strings. -d name --default-domain=name Rename the default output file from messages.pot to name.pot. -E --escape Replace non-ASCII characters with octal escape sequences. -D --docstrings Extract module, class, method, and function docstrings. These do not need to be wrapped in _() markers, and in fact cannot be for Python to consider them docstrings. (See also the -X option). -h --help Print this help message and exit. -k word --keyword=word Keywords to look for in addition to the default set, which are: %(DEFAULTKEYWORDS)s You can have multiple -k flags on the command line. -K --no-default-keywords Disable the default set of keywords (see above). Any keywords explicitly added with the -k/--keyword option are still recognized. --no-location Do not write filename/lineno location comments. -n --add-location Write filename/lineno location comments indicating where each extracted string is found in the source. These lines appear before each msgid. The style of comments is controlled by the -S/--style option. This is the default. -o filename --output=filename Rename the default output file from messages.pot to filename. If filename is `-' then the output is sent to standard out. -p dir --output-dir=dir Output files will be placed in directory dir. -S stylename --style stylename Specify which style to use for location comments. Two styles are supported: Solaris # File: filename, line: line-number GNU #: filename:line The style name is case insensitive. GNU style is the default. -v --verbose Print the names of the files being processed. -V --version Print the version of pygettext and exit. -w columns --width=columns Set width of output to columns. -x filename --exclude-file=filename Specify a file that contains a list of strings that are not be extracted from the input files. Each string to be excluded must appear on a line by itself in the file. -X filename --no-docstrings=filename Specify a file that contains a list of files (one per line) that should not have their docstrings extracted. This is only useful in conjunction with the -D option above. If `inputfile' is -, standard input is read. """) import os import sys import time import getopt import token import tokenize import operator __version__ = '1.5' default_keywords = ['_'] DEFAULTKEYWORDS = ', '.join(default_keywords) EMPTYSTRING = '' # The normal pot-file header. msgmerge and Emacs's po-mode work better if it's # there. pot_header = _('''\ # SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR ORGANIZATION # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\\n" "POT-Creation-Date: %(time)s\\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\\n" "Last-Translator: FULL NAME \\n" "Language-Team: LANGUAGE \\n" "MIME-Version: 1.0\\n" "Content-Type: text/plain; charset=CHARSET\\n" "Content-Transfer-Encoding: ENCODING\\n" "Generated-By: pygettext.py %(version)s\\n" ''') def usage(code, msg=''): print >> sys.stderr, __doc__ % globals() if msg: print >> sys.stderr, msg sys.exit(code) escapes = [] def make_escapes(pass_iso8859): global escapes if pass_iso8859: # Allow iso-8859 characters to pass through so that e.g. 'msgid # "Höhe"' would result not result in 'msgid "H\366he"'. Otherwise we # escape any character outside the 32..126 range. mod = 128 else: mod = 256 for i in range(256): if 32 <= (i % mod) <= 126: escapes.append(chr(i)) else: escapes.append("\\%03o" % i) escapes[ord('\\')] = '\\\\' escapes[ord('\t')] = '\\t' escapes[ord('\r')] = '\\r' escapes[ord('\n')] = '\\n' escapes[ord('\"')] = '\\"' def escape(s): global escapes s = list(s) for i in range(len(s)): s[i] = escapes[ord(s[i])] return EMPTYSTRING.join(s) def safe_eval(s): # unwrap quotes, safely return eval(s, {'__builtins__':{}}, {}) def normalize(s): # This converts the various Python string types into a format that is # appropriate for .po files, namely much closer to C style. lines = s.split('\n') if len(lines) == 1: s = '"' + escape(s) + '"' else: if not lines[-1]: del lines[-1] lines[-1] = lines[-1] + '\n' for i in range(len(lines)): lines[i] = escape(lines[i]) lineterm = '\\n"\n"' s = '""\n"' + lineterm.join(lines) + '"' return s def containsAny(str, set): """ Check whether 'str' contains ANY of the chars in 'set' """ return 1 in [c in str for c in set] def _visit_pyfiles(list, dirname, names): """ Helper for getFilesForName(). """ # get extension for python source files if not globals().has_key('_py_ext'): import imp global _py_ext _py_ext = [triple[0] for triple in imp.get_suffixes() if triple[2] == imp.PY_SOURCE][0] # don't recurse into CVS directories if 'CVS' in names: names.remove('CVS') # add all *.py files to list list.extend( [os.path.join(dirname, file) for file in names if os.path.splitext(file)[1] == _py_ext]) def _get_modpkg_path(dotted_name, pathlist=None): """ Get the filesystem path for a module or a package. Return the file system path to a file for a module, and to a directory for a package. Return None if the name is not found, or is a builtin or extension module. """ import imp # split off top-most name parts = dotted_name.split('.', 1) if len(parts) > 1: # we have a dotted path, import top-level package try: file, pathname, description = imp.find_module(parts[0], pathlist) if file: file.close() except ImportError: return None # check if it's indeed a package if description[2] == imp.PKG_DIRECTORY: # recursively handle the remaining name parts pathname = _get_modpkg_path(parts[1], [pathname]) else: pathname = None else: # plain name try: file, pathname, description = imp.find_module(dotted_name, pathlist) if file: file.close() if description[2] not in [imp.PY_SOURCE, imp.PKG_DIRECTORY]: pathname = None except ImportError: pathname = None return pathname def getFilesForName(name): """ Get a list of module files for a filename, a module or package name, or a directory. """ import imp if not os.path.exists(name): # check for glob chars if containsAny(name, "*?[]"): import glob files = glob.glob(name) list = [] for file in files: list.extend(getFilesForName(file)) return list # try to find module or package name = _get_modpkg_path(name) if not name: return [] if os.path.isdir(name): # find all python files in directory list = [] os.path.walk(name, _visit_pyfiles, list) return list elif os.path.exists(name): # a single file return [name] return [] class TokenEater: def __init__(self, options): self.__options = options self.__messages = {} self.__state = self.__waiting self.__data = [] self.__lineno = -1 self.__freshmodule = 1 self.__curfile = None def __call__(self, ttype, tstring, stup, etup, line): # dispatch ## import token ## print >> sys.stderr, 'ttype:', token.tok_name[ttype], \ ## 'tstring:', tstring self.__state(ttype, tstring, stup[0]) def __waiting(self, ttype, tstring, lineno): opts = self.__options # Do docstring extractions, if enabled if opts.docstrings and not opts.nodocstrings.get(self.__curfile): # module docstring? if self.__freshmodule: if ttype == tokenize.STRING: self.__addentry(safe_eval(tstring), lineno, isdocstring=1) self.__freshmodule = 0 elif ttype not in (tokenize.COMMENT, tokenize.NL): self.__freshmodule = 0 return # class docstring? if ttype == tokenize.NAME and tstring in ('class', 'def'): self.__state = self.__suiteseen return if ttype == tokenize.NAME and tstring in opts.keywords: self.__state = self.__keywordseen def __suiteseen(self, ttype, tstring, lineno): # ignore anything until we see the colon if ttype == tokenize.OP and tstring == ':': self.__state = self.__suitedocstring def __suitedocstring(self, ttype, tstring, lineno): # ignore any intervening noise if ttype == tokenize.STRING: self.__addentry(safe_eval(tstring), lineno, isdocstring=1) self.__state = self.__waiting elif ttype not in (tokenize.NEWLINE, tokenize.INDENT, tokenize.COMMENT): # there was no class docstring self.__state = self.__waiting def __keywordseen(self, ttype, tstring, lineno): if ttype == tokenize.OP and tstring == '(': self.__data = [] self.__lineno = lineno self.__state = self.__openseen else: self.__state = self.__waiting def __openseen(self, ttype, tstring, lineno): if ttype == tokenize.OP and tstring == ')': # We've seen the last of the translatable strings. Record the # line number of the first line of the strings and update the list # of messages seen. Reset state for the next batch. If there # were no strings inside _(), then just ignore this entry. if self.__data: self.__addentry(EMPTYSTRING.join(self.__data)) self.__state = self.__waiting elif ttype == tokenize.STRING: self.__data.append(safe_eval(tstring)) elif ttype not in [tokenize.COMMENT, token.INDENT, token.DEDENT, token.NEWLINE, tokenize.NL]: # warn if we see anything else than STRING or whitespace print >>sys.stderr, _('*** %(file)s:%(lineno)s: Seen unexpected token "%(token)s"') % { 'token': tstring, 'file': self.__curfile, 'lineno': self.__lineno} self.__state = self.__waiting def __addentry(self, msg, lineno=None, isdocstring=0): if lineno is None: lineno = self.__lineno if not msg in self.__options.toexclude: entry = (self.__curfile, lineno) self.__messages.setdefault(msg, {})[entry] = isdocstring def set_filename(self, filename): self.__curfile = filename self.__freshmodule = 1 def write(self, fp): options = self.__options timestamp = time.ctime(time.time()) # The time stamp in the header doesn't have the same format as that # generated by xgettext... print >> fp, pot_header % {'time': timestamp, 'version': __version__} # Sort the entries. First sort each particular entry's keys, then # sort all the entries by their first item. reverse = {} for k, v in self.__messages.items(): keys = v.keys() keys.sort() reverse.setdefault(tuple(keys), []).append((k, v)) rkeys = reverse.keys() rkeys.sort() for rkey in rkeys: rentries = reverse[rkey] rentries.sort() for k, v in rentries: isdocstring = 0 # If the entry was gleaned out of a docstring, then add a # comment stating so. This is to aid translators who may wish # to skip translating some unimportant docstrings. if reduce(operator.__add__, v.values()): isdocstring = 1 # k is the message string, v is a dictionary-set of (filename, # lineno) tuples. We want to sort the entries in v first by # file name and then by line number. v = v.keys() v.sort() if not options.writelocations: pass # location comments are different b/w Solaris and GNU: elif options.locationstyle == options.SOLARIS: for filename, lineno in v: d = {'filename': filename, 'lineno': lineno} print >>fp, _( '# File: %(filename)s, line: %(lineno)d') % d elif options.locationstyle == options.GNU: # fit as many locations on one line, as long as the # resulting line length doesn't exceeds 'options.width' locline = '#:' for filename, lineno in v: d = {'filename': filename, 'lineno': lineno} s = _(' %(filename)s:%(lineno)d') % d if len(locline) + len(s) <= options.width: locline = locline + s else: print >> fp, locline locline = "#:" + s if len(locline) > 2: print >> fp, locline if isdocstring: print >> fp, '#, docstring' print >> fp, 'msgid', normalize(k) print >> fp, 'msgstr ""\n' def main(): global default_keywords try: opts, args = getopt.getopt( sys.argv[1:], 'ad:DEhk:Kno:p:S:Vvw:x:X:', ['extract-all', 'default-domain=', 'escape', 'help', 'keyword=', 'no-default-keywords', 'add-location', 'no-location', 'output=', 'output-dir=', 'style=', 'verbose', 'version', 'width=', 'exclude-file=', 'docstrings', 'no-docstrings', ]) except getopt.error, msg: usage(1, msg) # for holding option values class Options: # constants GNU = 1 SOLARIS = 2 # defaults extractall = 0 # FIXME: currently this option has no effect at all. escape = 0 keywords = [] outpath = '' outfile = 'messages.pot' writelocations = 1 locationstyle = GNU verbose = 0 width = 78 excludefilename = '' docstrings = 0 nodocstrings = {} options = Options() locations = {'gnu' : options.GNU, 'solaris' : options.SOLARIS, } # parse options for opt, arg in opts: if opt in ('-h', '--help'): usage(0) elif opt in ('-a', '--extract-all'): options.extractall = 1 elif opt in ('-d', '--default-domain'): options.outfile = arg + '.pot' elif opt in ('-E', '--escape'): options.escape = 1 elif opt in ('-D', '--docstrings'): options.docstrings = 1 elif opt in ('-k', '--keyword'): options.keywords.append(arg) elif opt in ('-K', '--no-default-keywords'): default_keywords = [] elif opt in ('-n', '--add-location'): options.writelocations = 1 elif opt in ('--no-location',): options.writelocations = 0 elif opt in ('-S', '--style'): options.locationstyle = locations.get(arg.lower()) if options.locationstyle is None: usage(1, _('Invalid value for --style: %s') % arg) elif opt in ('-o', '--output'): options.outfile = arg elif opt in ('-p', '--output-dir'): options.outpath = arg elif opt in ('-v', '--verbose'): options.verbose = 1 elif opt in ('-V', '--version'): print _('pygettext.py (xgettext for Python) %s') % __version__ sys.exit(0) elif opt in ('-w', '--width'): try: options.width = int(arg) except ValueError: usage(1, _('--width argument must be an integer: %s') % arg) elif opt in ('-x', '--exclude-file'): options.excludefilename = arg elif opt in ('-X', '--no-docstrings'): fp = open(arg) try: while 1: line = fp.readline() if not line: break options.nodocstrings[line[:-1]] = 1 finally: fp.close() # calculate escapes make_escapes(options.escape) # calculate all keywords options.keywords.extend(default_keywords) # initialize list of strings to exclude if options.excludefilename: try: fp = open(options.excludefilename) options.toexclude = fp.readlines() fp.close() except IOError: print >> sys.stderr, _( "Can't read --exclude-file: %s") % options.excludefilename sys.exit(1) else: options.toexclude = [] # resolve args to module lists expanded = [] for arg in args: if arg == '-': expanded.append(arg) else: expanded.extend(getFilesForName(arg)) args = expanded # slurp through all the files eater = TokenEater(options) for filename in args: if filename == '-': if options.verbose: print _('Reading standard input') fp = sys.stdin closep = 0 else: if options.verbose: print _('Working on %s') % filename fp = open(filename) closep = 1 try: eater.set_filename(filename) try: tokenize.tokenize(fp.readline, eater) except tokenize.TokenError, e: print >> sys.stderr, '%s: %s, line %d, column %d' % ( e[0], filename, e[1][0], e[1][1]) finally: if closep: fp.close() # write the output if options.outfile == '-': fp = sys.stdout closep = 0 else: if options.outpath: options.outfile = os.path.join(options.outpath, options.outfile) fp = open(options.outfile, 'w') closep = 1 try: eater.write(fp) finally: if closep: fp.close() if __name__ == '__main__': main() # some more test strings _(u'a unicode string') _('*** Seen unexpected token "%(token)s"' % {'token': 'test'}) # this one creates a warning _('more' 'than' 'one' 'string') gaphor-0.17.2/utils/command/run.py000066400000000000000000000117451220151210700170140ustar00rootroot00000000000000""" Command for running gaphor and tests directly from setup.py. """ import sys, os.path from distutils.core import Command from pkg_resources import load_entry_point class run(Command): description = 'Launch Gaphor from the local directory' user_options = [ ('build-dir=', None, ''), ('command=', 'c', 'execute command'), ('file=', 'f', 'execute file'), ('doctest=', 'd', 'execute doctests in module (e.g. gaphor.geometry)'), ('unittest=', 'u', 'execute unittest file (e.g. tests/test-ns.py)'), ('model=', 'm', 'load a model file'), ('coverage', None, 'Calculate coverage (requires coverage.py)'), ('profile', 'p', 'Run with profiling enabled'), ] def initialize_options(self): self.build_lib = None self.command = None self.file = None self.doctest = None self.unittest = None self.model = None self.coverage = None self.verbosity = 2 self.profile = None def finalize_options(self): self.set_undefined_options('build', ('build_lib', 'build_lib')) def run(self): print 'Starting Gaphor...' if self.model: print 'Starting with model file', self.model for cmd_name in self.get_sub_commands(): self.run_command(cmd_name) #if self.build_lib not in sys.path: #sys.path.insert(0, self.build_lib) import gaphor #os.environ['GAPHOR_DATADIR'] = os.path.abspath('data') if self.coverage: import coverage coverage.start() if self.command: print 'Executing command: %s...' % self.command exec self.command elif self.doctest: print 'Running doctest cases in module: %s...' % self.doctest import imp # use zope's one since it handles coverage right from zope.testing import doctest # Figure out the file: f = os.path.join(*self.doctest.split('.')) + '.py' fp = open(f) # Prepend module's package path to sys.path pkg = os.path.join(self.build_lib, *self.doctest.split('.')[:-1]) #if pkg: # sys.path.insert(0, pkg) # print 'Added', pkg, 'to sys.path' # Load the module as local module (without package) test_module = imp.load_source(self.doctest.split('.')[-1], f, fp) failure, tests = doctest.testmod(test_module, name=self.doctest, optionflags=doctest.ELLIPSIS + doctest.NORMALIZE_WHITESPACE) if self.coverage: print print 'Coverage report:' coverage.report(f) sys.exit(failure != 0) elif self.unittest: # Running a unit test is done by opening the unit test file # as a module and running the tests within that module. print 'Running test cases in unittest file: %s...' % self.unittest import imp, unittest fp = open(self.unittest) test_module = imp.load_source('gaphor_test', self.unittest, fp) test_suite = unittest.TestLoader().loadTestsFromModule(test_module) #test_suite = unittest.TestLoader().loadTestsFromName(self.unittest) test_runner = unittest.TextTestRunner(verbosity=self.verbosity) result = test_runner.run(test_suite) if self.coverage: print print 'Coverage report:' coverage.report(self.unittest) sys.exit(not result.wasSuccessful()) elif self.file: print 'Executing file: %s...' % self.file dir, f = os.path.split(self.file) print 'Extending PYTHONPATH with %s' % dir #sys.path.append(dir) execfile(self.file, {}) else: print 'Launching Gaphor...' del sys.argv[1:] starter = load_entry_point('gaphor==%s' % (self.distribution.get_version(),), 'console_scripts', 'gaphor') if self.profile: print 'Enabling profiling...' try: import cProfile import pstats prof = cProfile.Profile() prof.runcall(starter) prof.dump_stats('gaphor.prof') p = pstats.Stats('gaphor.prof') p.strip_dirs().sort_stats('time').print_stats(20) except ImportError, ex: import hotshot, hotshot.stats prof = hotshot.Profile('gaphor.prof') prof.runcall(starter) prof.close() stats = hotshot.stats.load('gaphor.prof') stats.strip_dirs() stats.sort_stats('time', 'calls') stats.print_stats(20) else: starter() sub_commands = [('build', None)] # vim:sw=4:et gaphor-0.17.2/utils/compare.py000066400000000000000000000153461220151210700162210ustar00rootroot00000000000000#!/usr/bin/env python # vim:sw=4:et: """Report differences in two Gaphor models. This can be called as: python compare.py model1.gaphor model2.gaphor This file is part of Gaphor. """ __all__ = ['Compare'] try: import env except ImportError: pass import gaphor.storage import gaphor.storage.parser import gaphor.UML class Compare(object): """This class makes it possible to compare two files. By default reports are printed to stdout in a diff-like syntax. """ def __init__(self, filename1, filename2): self.filename1 = filename1 self.filename2 = filename2 self.elements1, self.factory1 = self.load(self.filename1) self.elements2, self.factory2 = self.load(self.filename2) self.show_id = True def out(self, msg): """Print a message generated by report(). """ print msg def report(self, factory, element, name=None, value=None, isref=False): """Report an element that has differences. The attribute show_id can be set to False to suppress element ids. A fancy diff message is send to method out(msg). """ if factory is self.factory1: msg = '-' else: msg = '+' if isinstance(element, gaphor.storage.parser.canvas): msg += ' :' else: if self.show_id: msg += ' %s' % element.id n = element.get('name') if n: msg += ' (%s)' % n if self.show_id or n: msg += ':' msg += ' %s' % (isinstance(element, gaphor.storage.parser.canvas) and 'Canvas' or element.type) if name: msg += '.%s' % name if value: if isref: if self.show_id: msg += ' = %s' % value obj = factory.lookup(value) if hasattr(obj, 'name'): msg += ' (%s)' % obj.name else: msg += ' = %s' % value self.out(msg) def load(self, filename): """Load the model file and create a factory. A tuple (elements, factory) is returned. """ elements = gaphor.storage.parser.parse(filename) factory = gaphor.UML.ElementFactory() try: gaphor.storage.load_elements(elements, factory) except Exception, e: self.out('! File %s could not be loaded completely.' % filename) self.out('! Trying to diff on parsed elements only.') self.out(e) return elements, factory def elements_in_both_files(self): """Generator function that returns tuples (element1, element2) of elements that exist in both files (they have the same id). """ vals = [] for key1, val1 in self.elements1.iteritems(): val2 = self.elements2.get(key1) if val2: yield (val1, val2) def check_missing_elements(self): """Report elements that exist in one factory, but not in the other. """ keys1 = self.elements1.keys() keys2 = self.elements2.keys() for key in keys1: if key not in keys2: self.report(self.factory1, self.elements1[key]) for key in keys2: if key not in keys1: self.report(self.factory2, self.elements2[key]) def check_missing_references(self, element1, element2): """Report references to other elements that are present in one element and not in the other one. """ keys1 = element1.references.keys() keys2 = element2.references.keys() for key in keys1: if key not in keys2: self.report(self.factory1, element1, key) for key in keys2: if key not in keys1: self.report(self.factory2, element2, key) def check_differences_references(self, element1, element2): keys1 = element1.references.keys() keys2 = element2.references.keys() for key in keys1: if key in keys2: val1 = element1.references.get(key) val2 = element2.references.get(key) try: for val in val1: if val not in val2: self.report(self.factory1, element1, key, val, True) for val in val2: if val not in val1: self.report(self.factory2, element2, key, val, True) except TypeError: if val1 != val2: self.report(self.factory1, element1, key, val1, True) self.report(self.factory2, element2, key, val2, True) def check_missing_values(self, element1, element2): keys1 = element1.values.keys() keys2 = element2.values.keys() for key in keys1: if key not in keys2: self.report(self.factory1, element1, key) for key in keys2: if key not in keys1: self.report(self.factory2, element2, key) def check_differences_values(self, element1, element2): keys1 = element1.values.keys() keys2 = element2.values.keys() for key in keys1: if key in keys2: val1 = element1.values.get(key) val2 = element2.values.get(key) if val1 != val2: self.report(self.factory1, element1, key, val1) self.report(self.factory2, element2, key, val2) def compare(self): """Start the comparison of the files provided to the constructor. """ self.check_missing_elements() for element1, element2 in self.elements_in_both_files(): self.check_missing_references(element1, element2) self.check_differences_references(element1, element2) self.check_missing_values(element1, element2) self.check_differences_values(element1, element2) if __name__ == '__main__': import sys usage = "usage: %s [-v][-h|--help] old_model new_model" % sys.argv[0] files = [] show_id = False # Parse command line arguments: for arg in sys.argv[1:]: if arg.startswith('-'): if arg == '-v': show_id = True elif arg in ('-h', '--help'): print usage sys.exit(0) else: print '%s: invalid option "%s".' % (sys.argv[0], arg) print usage sys.exit(1) else: files.append(arg) if len(files) != 2: print usage sys.exit(1) c = Compare(files[0], files[1]) c.show_id = show_id c.compare() gaphor-0.17.2/utils/format.py000066400000000000000000000006301220151210700160510ustar00rootroot00000000000000 import re pattern = r'([A-Z])' sub = r'_\1' def camelCase_to_underscore(str): """ >>> camelCase_to_underscore('camelcase') 'camelcase' >>> camelCase_to_underscore('camelCase') 'camel_case' >>> camelCase_to_underscore('camelCamelCase') 'camel_camel_case' """ return re.sub(pattern, sub, str).lower() if __name__ == '__main__': import doctest doctest.testmod()