././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1665611335.9639218 agordejo-0.4.2/.gitignore0000644000175000017500000000257214321633110014033 0ustar00nilsnils# ---> Python # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ *.egg-info/ .installed.cfg *.egg MANIFEST # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .nox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover .hypothesis/ .pytest_cache/ # Translations *.mo *.pot # Django stuff: *.log local_settings.py db.sqlite3 # Flask stuff: instance/ .webassets-cache # Scrapy stuff: .scrapy # Sphinx documentation docs/_build/ # PyBuilder target/ # Jupyter Notebook .ipynb_checkpoints # IPython profile_default/ ipython_config.py # pyenv .python-version # celery beat schedule file celerybeat-schedule # SageMath parsed files *.sage.py # Environments .env .venv env/ venv/ ENV/ env.bak/ venv.bak/ # Spyder project settings .spyderproject .spyproject # Rope project settings .ropeproject # mkdocs documentation /site # mypy .mypy_cache/ .dmypy.json dmypy.json # Pyre type checker .pyre/ #build and makefile *.bin *.build build buildnsmdata Makefile *compiledprefix.py ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1665611813.3026767 agordejo-0.4.2/CHANGELOG0000644000175000017500000000533014321634045013260 0ustar00nilsnils#Changelog Format: Double ## for a version number followed by a space, ISO-Date, Semantic Versioning: ## YYYY-MM-DD major.minor.patch Two empty lines before the next entry. External contributors notice at the end of the line: (LastName, FirstName / nick) ## 2022-10-15 0.4.2 Enable lss debug environment in verbose mode Remove leftover mention of quick mode in manpage and other typos in documentation and logs. Better configure output Fix one crash from a type mistake. ## 2022-07-15 0.4.1 Handle xdg-lib exceptions and log them as error, instead of crashing ## 2022-04-15 0.4.0 Initial startup-check for old $HOME session root vs new XDG root. Offers to move the session files to the new location. Support nsmd 1.6.0 lockfiles. Older nsmd versions still work. On startup give a choice to connect to an already running session (nsmd 1.6.0) Remove internal program- and icon-database and replace with dynamic XDG based lookup. pyxdg >=0.27 is now a dependency https://freedesktop.org/wiki/Software/pyxdg/ Add a nsm-data desktop file with nsm tags and "no-display", so it can be found by Agordejo. ## 2022-01-15 0.3.1 Option in Control menu to split the session view between horizontally and vertically The session list now dynamically expands to the needed width. Better log messages Fix regression and workarounds for crashes introduced by a recent PyQt update. Fix a bug where symlink targets are not available or have no permission but the session list tries to count the symlink and filesizes and crashes. This CHANGELOG is now available through the programs help menu directly. ## 2021-07-08 0.3.0 Remove "Quick" mode. As it turns out "Full" mode is quick enough. Port convenience features to full mode. Add button in session chooser for alternative access to context menu options Add a timeline above running session to show global jack transport position. Also add controls to set the position. Saving the timeline settings per session is done via nsm-data, which increases version from 1.0 to 1.1 Add normal "Save" to tray icon. Add file integrity check after copying a session Add progress updates to copy-session. Submenu in tray icon to toggle visibility of individual clients (if supported) Double click on a crashed clients opens it again. More programs and icons added to the internal database. Fix a rare crash where the hostname must be case sensitive. ## 2021-01-15 0.2.1 Remove Nuitka as dependency. Build commands stay the same. ## 2020-12-05 0.2.0 Fix crash from Qt/PyQt regression with Qt 5.15.2 Install nsm-data manpage ## 2020-10-15 0.1.1 Correct typo in the name of the project :) Sorry. ## 2020-07-15 0.1.0 A lot is working already, single-computer session are working fine. Network sessions of any form are not usable yet. ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1665611335.9639218 agordejo-0.4.2/COPYING0000644000175000017500000010451514321633110013076 0ustar00nilsnils GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, 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 them 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 prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If 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 convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU 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 Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "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 PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM 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 PROGRAM (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 PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state 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 program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1665611335.9639218 agordejo-0.4.2/LICENSE0000644000175000017500000010665014321633110013052 0ustar00nilsnilsNo file of this program is without a specific license. But some files miss a direct header: This program is licensed under the GPLv3, as seen in the original text below. This software release (or source repository) contains files that were either autogenerated from files not contained here or are "save files" of programs such as the Qt Designer. For technical reasons they do not have a license header. They are all licensed GPLv3 as well. All documentation is licensed under the Creative Commons Attribution-ShareAlike 4.0 International License. To view a copy of this license, visit http://creativecommons.org/licenses/by-sa/4.0/ or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. Again, if a file with documentation is not licensed through a header or notice in the file itself it is licensed under CC-By-Sa 4.0 as well. The reasons for not including a header are purely technical. All files and content considered "branding" are licensed as CC-By-ND. Such as image files, logos, icons. These and the name of the program itself cannot be reused when forking and re-releasing. GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, 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 them 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 prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If 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 convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU 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 Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "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 PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM 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 PROGRAM (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 PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state 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 program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1665611335.9639218 agordejo-0.4.2/Makefile.in0000644000175000017500000000671414321633110014112 0ustar00nilsnils #This is a makefile and needs tabs, not spaces. .PHONY: install uninstall clean gitclean resources all: #Our Program mkdir -p build cd build && printf "prefix = \"$(PREFIX)\"" > compiledprefix.py #Only copy the needed files cp -r "$(PROGRAM)" __main__.py engine qtgui build #Clean all pycache in build cd build && find . -type d -name "__pycache__" -exec rm -r {} + python3 -m zipapp "build" --output="$(PROGRAM).bin" --python="/usr/bin/env python3" rm build/compiledprefix.py #nsm-data mkdir -p buildnsmdata cp tools/nsm-data buildnsmdata/__main__.py cp tools/nsmclient.py buildnsmdata/nsmclient.py cd buildnsmdata && find . -type d -name "__pycache__" -exec rm -r {} + python3 -m zipapp "buildnsmdata" --output="nsm-data.bin" --python="/usr/bin/env python3" clean: rm -rf build/ rm -rf buildnsmdata/ rm -f "$(PROGRAM).bin" rm -f "nsm-data.bin" rm Makefile find . -type d -name "__pycache__" -exec rm -r {} + #Convenience function for developing, not used for the build or install process gitclean: git clean -f -X -d #Convenience function for developing, not used for the build or install process resources: cd documentation && python3 build.py cd documentation && sh build-documentation.sh cd qtgui/resources && sh buildresources.sh install: #nsm-data install -D -m 755 "nsm-data.bin" $(DESTDIR)$(PREFIX)/bin/nsm-data #Agordejo install -D -m 755 $(PROGRAM).bin $(DESTDIR)$(PREFIX)/bin/$(PROGRAM) install -D -m 644 documentation/out/* -t $(DESTDIR)$(PREFIX)/share/doc/$(PROGRAM) install -D -m 644 README.md $(DESTDIR)$(PREFIX)/share/doc/$(PROGRAM)/README.md install -D -m 644 LICENSE $(DESTDIR)$(PREFIX)/share/doc/$(PROGRAM)/LICENSE install -D -m 644 CHANGELOG $(DESTDIR)$(PREFIX)/share/doc/$(PROGRAM)/CHANGELOG install -D -m 644 desktop/desktop.desktop $(DESTDIR)$(PREFIX)/share/applications/org.laborejo.agordejo.desktop install -D -m 644 desktop/agordejo-continue.desktop $(DESTDIR)$(PREFIX)/share/applications/org.laborejo.agordejo-continue.desktop install -D -m 644 desktop/nsm-data.desktop $(DESTDIR)$(PREFIX)/share/applications/org.laborejo.nsm-data.desktop install -d $(DESTDIR)$(PREFIX)/share/man/man1 gzip -c documentation/$(PROGRAM).1 > $(DESTDIR)$(PREFIX)/share/man/man1/$(PROGRAM).1.gz gzip -c documentation/nsm-data.1 > $(DESTDIR)$(PREFIX)/share/man/man1/nsm-data.1.gz #Icons for size in 16 32 64 128 256 512 ; do \ install -D -m 644 desktop/images/"$$size"x"$$size".png $(DESTDIR)$(PREFIX)/share/icons/hicolor/"$$size"x"$$size"/apps/$(PROGRAM).png ; \ done install -D -m 644 desktop/images/256x256.png $(DESTDIR)$(PREFIX)/share/pixmaps/$(PROGRAM).png #Engine resources #install -d $(DESTDIR)$(PREFIX)/share/$(PROGRAM) #cp -r engine/resources/* $(DESTDIR)$(PREFIX)/share/$(PROGRAM)/ uninstall: #Directories rm -rf $(DESTDIR)$(PREFIX)/share/doc/$(PROGRAM) rm -rf $(DESTDIR)$(PREFIX)/share/$(PROGRAM) #Files rm -f $(DESTDIR)$(PREFIX)/bin/$(PROGRAM) rm -f $(DESTDIR)$(PREFIX)/bin/nsm-data rm -f $(DESTDIR)$(PREFIX)/share/applications/org.laborejo.agordejo.desktop rm -f $(DESTDIR)$(PREFIX)/share/applications/org.laborejo.agordejo-continue.desktop rm -f $(DESTDIR)$(PREFIX)/share/applications/org.laborejo.nsm-data.desktop rm -f $(DESTDIR)$(PREFIX)/share/man/man1/$(PROGRAM).1.gz rm -f $(DESTDIR)$(PREFIX)/share/man/man1/nsm-data.1.gz #Icons for size in 16 32 64 128 256 512 ; do \ rm -f $(DESTDIR)$(PREFIX)/share/icons/hicolor/"$$size"x"$$size"/apps/$(PROGRAM).png ; \ done rm -f $(DESTDIR)$(PREFIX)/share/pixmaps/$(PROGRAM).png ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1665611396.701277 agordejo-0.4.2/README.md0000644000175000017500000000500714321633205013323 0ustar00nilsnils [//]: # (Generated 2022-10-12T23:49:56.705597. Changes belong into template/documentation/readme.template) #Agordejo Program version 0.4.2 ![Screenshot](https://git.laborejo.org/lss/Agordejo/raw/branch/master/documentation/screenshot.png "Screenshot") Agordejo (Esperanto: 'place to set things up') is a music production session manager. It is used to start your programs, remember their (JACK) interconnections and make your life easier in general. You can create a session, or project, add programs to it and then use commands to save, start/stop, hide/show all programs at once, or individually. At a later date you can then re-open the session and continue where you left off. Agordejo does not re-invent the wheel but instead uses the New-Session-Manager daemon and enhances it with some tricks of its own, that always remain 100% compatible with the original sessions. This README is just a short introduction. Consult the manual (see below) for more information. # Contact and Information * Website https://www.laborejo.org * Bugs and Issues: https://www.laborejo.org/bugs * Git Repositories for all programs: https://git.laborejo.org * Documentation and Manual https://www.laborejo.org/documentation/agordejo # Installation and Starting ## Download ### Release Version If the latest release is not available through your package manger you can build it yourself: Download the latest code release on https://www.laborejo.org/downloads and extract it. ### Git Version It is possible to clone a git repository. `git clone https://git.laborejo.org/lss/agordejo.git` ## Dependencies * Glibc * Python 3.6 (maybe earlier) * PyQt5 for Python 3 * DejaVu Sans Sarif TTF (Font) (recommended, but not technically necessary) * nsmd: New Session Manager * pyxdg: python-xdg #### Build Dependencies * Bash * GCC (development is done on 8.2, but most likely you can use a much earlier version) ### Environment: * Jack Audio Connection Kit must be running ## Build and Install ./configure --prefix=/usr/local make sudo make install ## Starting If you installed Agordejo through a package manager or yourself simply use your application launcher or terminal to start the executable `agordejo` You can also run Agordejo after extracting the release archive or cloning from git, without make or installation. If you did so, for additional features please link tools/nsm-data to your executable PATH. Use the manpage `man agordejo` or run `agordejo --help` (or local variant `./agordejo --help` ) to see available command line parameters. ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1665611335.9639218 agordejo-0.4.2/__main__.py0000644000175000017500000000065314321633110014133 0ustar00nilsnils#!/usr/bin/env python3 # encoding: utf-8 # file: __main__.py """ This file is an alternativ to ./agordejo when using zipapp. Any code changes in ./agordejo must be manually recplicated here. """ if __name__ == '__main__': from engine import start from qtgui import mainwindow #which in turn imports the engine and starts the engine mainwindow.MainWindow() #Program is over. Code here does not get executed. ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1665611335.9639218 agordejo-0.4.2/agordejo0000755000175000017500000000035414321633110013557 0ustar00nilsnils#! /usr/bin/env python3 # -*- coding: utf-8 -*- from engine import start from qtgui import mainwindow #which in turn imports the engine and starts the engine mainwindow.MainWindow() #Program is over. Code here does not get executed. ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1665611335.993922 agordejo-0.4.2/configure0000755000175000017500000000501014321633110013740 0ustar00nilsnils#!/bin/bash program=agordejo version=0.4.2 #debugsym=true prefix=/usr/local required_version_python=3.8 required_version_pyqt=5.0 required_version_pyxdg=0.27 for arg in "$@"; do case "$arg" in --prefix=*) prefix=`echo $arg | sed 's/--prefix=//'` ;; #--enable-debug) # debugsym=true;; #--disable-debug) # debugsym=false;; --help) echo 'usage: ./configure [options]' echo 'options:' echo ' --prefix=: installation prefix' #echo ' --enable-debug: include debug symbols' #echo ' --disable-debug: do not include debug symbols' echo 'all invalid options are silently ignored' exit 0 ;; esac done echo "PREFIX=$prefix" echo echo "Checking Dependencies" function version_gt() { test "$(printf '%s\n' "$@" | sort -V | head -n 1)" != "$1"; } command -v python3 >/dev/null 2>&1 || { echo >&2 "Python3 >= $required_version_python is required but it's not installed. Aborting."; exit 1; } PY3VERSION=$(python3 -c 'import platform; print(platform.python_version())') echo "Python version is: $PY3VERSION, required >= $required_version_python" if version_gt $required_version_python $PY3VERSION; then echo "Python3 must be version >= $required_version_python but is $PY3VERSION. Aborting."; exit 1; fi python3 -c 'import PyQt5' >/dev/null 2>&1 || { echo >&2 "PyQt for Python3 >= $required_version_pyqt is required but it's not installed. Aborting."; exit 1; } PYQTVERSION=$(python3 -c 'from PyQt5.QtCore import QT_VERSION_STR; print(QT_VERSION_STR)') echo "Py-Qt version is: $PYQTVERSION, required >= $required_version_pyqt" if version_gt $required_version_pyqt $PYQTVERSION; then echo "PyQt must be version >= $required_version_pyqt but is $PYQTVERSION. Aborting."; exit 1; fi python3 -c 'import xdg' >/dev/null 2>&1 || { echo >&2 "PyXDG for Python3 >= $required_version_pyxdg is required but it's not installed. https://freedesktop.org/wiki/Software/pyxdg/ . Aborting."; exit 1; } PYXDGVERSION=$(python3 -c 'from xdg import __version__; print(__version__)') echo "PyXDG version is: $PYXDGVERSION, required >= $required_version_pyxdg" if version_gt $required_version_pyxdg $PYXDGVERSION; then echo "PyXDG must be version >= $required_version_pyxdg but is $PYXDGVERSION. Aborting."; exit 1; fi echo echo "Generating Makefile" printf "PREFIX=$prefix\nPROGRAM=$program\nVERSION=$version\n" >Makefile #if $debugsym; then # echo 'dbg = -g' >>Makefile #fi cat Makefile.in >>Makefile echo echo 'Configuration complete. Type make to build.' ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1665611335.957255 agordejo-0.4.2/desktop/agordejo-continue.desktop0000644000175000017500000000047514321633110020523 0ustar00nilsnils[Desktop Entry] Type=Application Name=Agordejo Continue Comment=Music and audio production session manager based on NSM. Continue last session, start as TrayIcon. Exec=agordejo --continue --hide Icon=agordejo Terminal=false StartupNotify=false Version=1.0 Categories=AudioVideo;Audio;X-Recorders;X-Multitrack;X-Jack; ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1665611335.9739218 agordejo-0.4.2/desktop/desktop.desktop0000644000175000017500000000043714321633110016556 0ustar00nilsnils[Desktop Entry] Type=Application Name=Agordejo Comment=Music and audio production session manager based on NSM. Exec=agordejo Icon=agordejo Terminal=false StartupNotify=false Version=1.0 Categories=AudioVideo;Audio;X-Recorders;X-Multitrack;X-Jack; X-NSM-Capable=true X-NSM-Exec=agordejo ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1665611335.957255 agordejo-0.4.2/desktop/images/1248-transparent.png0000644000175000017500000010615414321633110020425 0ustar00nilsnils‰PNG  IHDRྠïaÍ pHYsÃÃÇo¨dtEXtSoftwarewww.inkscape.org›î< IDATxœìÝw¸%E™øñï`È$©$e  ‚iUÌyU\3ê0§u]ÝU×UYuÍ‚Š ¢¨¨¸"Ö (ŠdÉ 90 0sÔ=?/×Î9]]oõ9ßÏóÔ s»ßê¾çt÷ÛUo$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’4‚D IRm8í¿¯‰BóY XcÊß , ŠE’$å·XwÚŸÝLÄ"ÍÉœ$•µØ¸pwÒ ÃúÀB`u`íÉ¿w)ð‰ˆÇÜ–ÀÎÀ½€{ò·sµÉdÛtžŸ¿¸¸|Z»88¸©…¸ÇÍ:ÀNÀSÚæ“mÓɶÚ?¿ ¸fJ»ŠtŽÎÎl¶yYK€û·“~/oV~WÆ5—ÕHçw®Ÿú°5Û¿ß,'õõöÉ.®®¤;fë‘ÎÙŠè@ÔØBÒõþfàŽàXFÍÀš¤¤K?Öet_Æ,$½|º9: “îÅvvœüçV¤küæÀfüí¾y6ËH×ø«I×ûK¹ëµþlü<ª p’TƇKIÅó¹ØŽ”¼Q;ÖöÜØØ¢À~/%%âþü ø é¦P³ÛØxàdÛXÔò>¯Nžl¿~N÷’§[Ïòÿ&H«wÔfJÎ-#œª÷÷§ëýÝ5IßqÓÿ9ýÏfK®•p;pé!ìLàtàÀ)Ô—è:”Dí™z~z#oW2ûïæ¤„óL–“•sýýÞ¶‘’ð·—Fk“^­AzàïéãÙôF¦ôúÒKHÝ:ùÿ®® =4_FJ†Âwä–À_§ýÙÔs0õ³µŠt.z¦ž—~xÓ¾_óš>ú»óý>ô¬KJ¸@ú>è½D™:’¹÷2Óvïdî/SÝLúýí½œè‡Þ÷Ø ¤ßÍÙŽaïûr.½mO·ó?ƒ¯ÇÌ׸Þg­wzÇ©÷÷{çç›À3çÙG6ö <€to6_‚­©Û?‘®õ'‘®õ´¼O1p’Ô¾û’’-ƒz/ðî̱Œ»í§ÿ<„þ J8ø5ð3à8Òq¶:éí<¸Gl8@z(ûðcàR²¦v’‰êÏí¤ïêŸ?"%ǧ' K;Ø#8†ÜLz(>‡4šøôÉv&õŽæœn3ünoÛúK¢_ÇpÉÄ®øð¼è út_à‰ÀcII·¶_®õã\Òµþ‡¤{²Ú^ÌH’¤9|ŠôFyÐvw­_¥ál¼•ôvs˜óPº­"=txý¿ÑïºÀCO׿kg“¦ÂÖj5âS—Û5¤ïï‡÷Òú¤yb÷¶ 8øðdÒè¢ZmDüñõÖoR­7 }TÛ¡}‡(ÛÿBJ¢G«ùÚuÀ€G“FþJ8NƒZøþîÔæE¤imªÏ:¤)4Ó‹Ãöë…À—²E3>’Þ¦¾x&£Üvìó\œVA¬m¶÷yJXHš;JÇüÒK·­2'IšÑÄéÙþ¾½m®“¦P½·ïMÚC‹GÝ=k‘Þ|GKµó€w’ÞøwÉÚ¤©'× Ûj‘’ï5L¾€øã1Šíài?þ­ ú:*íDÒ šRÉÓ™,›!.[¾Öo¢õçÄÚf«å¹àÉÀŸ‰?mµåÀÇ€»å:`’4݉ÿ²³ý}ûê\'Maö$Ïù=ªtà²x)ý9Œjw’V<Û»á±,Ṥ•}£Y©viÊM¤S‰?£Ü޶íûl î‹ôqÔÚå¤ip‹ðŒÓK¢ˆö>ÏÃ7+ˆµÍö²>C[vþøãPªÝ¼™:^ºI1? þKÎö÷íOs4…ùyÎïÀ6…cï‚¥¤úHÑŸ¿šÚoHoœk³ieÉèãÕŽ$nªÊûŒÑ6|»”\nÃG*èߨ¶ËHõ'KNç¥ix5¶Çôy>YA¬m¶¨û€µI ¢ÜÞGŒ£ØÎÞø(JÒÿåfûû¶‚ºj Ön&ß9þ`Ùð«¶ð.Òï}ôg¯Æö³ámv HSƒ—\¢Ûµ¤Ñš¥}iÈxmƒ·’ê|åôÆ ú5êíg”[æG…ú4®í9}ž‡×Wk›m×>CNdtëïÒV‡3ÂVÐÅÕGg}F{Å .[Ø!:ÝŤ7¹¼”غ5µØ 8 x&gs^t“6'šþ®Â iщ#¯ô¥\^p_ãîu¤šm9ß/ɸ-Íl?àà•öuq}Œ³Múü{i5ŠX«({°ˆT×õ—À= î·V H«ºŸ ì‹*dNƒXJ÷WÝeK£Ð]˜y{Ïϼͮy)ù¶{t •«!÷ àÀ㢩ÐÀïHÉä|à/ëI¤Äs®$œç¯Œ%Àÿ’äm&È=ŸíڸϿwv«Qĺ˜4꼄 ï#ÿèß®»7©LÊó¢Q]LÀiÙÕ?ÏO=D;I¢×0žIðE¤©]GëÇÒçïÿy¤)]Q«‚­þJZyíDR"ð² Xf³3) ·}Q`º«GGçôtÒˆ•ñ,àW´7ãã´–¶«¤ßpç×´H ßÚÏŽ“ûzt¡ýÍäZR2õDà÷¤wÆ3ÝšÀáÀ‡I÷²’™j ÄVuóüÔãå-m÷>À£HEÕÇÅšÀ×€§ư 8‹4à4à:RÑõe¤©ù«“ÉØ–”x½/°FH¤Iä¸wO¶Ò‰â«HµÎ~Lz X6ÃßYØ x,©NPÔ¢=ëǯ>ßâ~NoqÛ¥ÝIwî]ŸL*Fþ††ÛYFúLB™‰®œ¿]I#Wö'Âì”ÌÛÓ]õ›€› %Zk\´¨©ÿ+°‡®_Ø×Twߎ&¿™¦è¯ìüð4àÁÅ¢›Ý{î=VÇ¢`ã8’BÃ;xXtšÕùX{¡Fß´U¯íûÀ[Úvm6¾ì´ÿ“HõºŽ®àçÖ ­‚õLàÙä­Ø I ÂÒþxSá}^¼ ø:iÕµ~-&%ußCš&ix3ð_-îc]Rÿþ¤ÑY¢lºAý™T¬þ·À™ÀümJÕ"` Ò(½Çˆ±¤Ußl¸G’ê”-%݇•~èÔm¤kÕqÀH£nœüKH¿›‘~'÷ ¿}©/9w=é»<çJó €“ú~?ÒˆùÈ6óùýýþ®FºÿÙŒôùÜ‘”Èì7!–ËOHßoýØ}òïî@:Kéæì°+HɨÓH ©cìžeP˜ÜGÉ{›;O®ðgwÞ <øÜÇñ¤{[‚ãÔ׿²ŒmîUwœžï5´{žW÷*Ö›8‘F D|–þ <&S?6&»¥Pì¥ê¾Lµøøñ6i_ "kb©pþ­ñOoÿÚ°/ƒX xidUt¿§·w ÑŸÝ€¯’®ƒÑñOo×$ÉeiDǧȻÒv®v:éû{P›:¹‚>LmWÐn½ÆµHäߣÎßßa>SÝxÙdÿî(ïĺ1ð*Ò”Æèã>_[EšÚXzt×c)½<•ôßÔ>¤Å7¢Ïݯ€u2ôGÒˆÛŠø/,Ûüí³@ógÚ?χëMŒ ˆy»“4šª:Û‘Þ|¶Ý‡‹Zˆ}>ÿÑ ÞaÏÓA™ûpê¸1uæ~Íg )‘Ýï©íÍ ú³i´\t¦·#ôi.[ß­ SÛ/öiðR=§è¾ôÚÅ´Wnª½©/ùÓ47ÕæÀ¿‘FC¶ï¥â\}2΢¤¤þ¾ú9¨}(Ÿ|ûy¬M*k}Dú=“¤Y=–ø/+Ûüí¥³@±eÎóÔ=}¬‰µHÓÎJv®"M«hÓ"Ò4Ã6ûqbË}˜®íŸÓÛ*Ò*¢mؘôfºôïÞÔ¶xnKý›Ë§‡Œ·Ö$izæÏ*èÇôöІýšÍÒÔ¬èþõZÓ\Ï"à_HSË£û4Aún͵²í\Ö§®Q€9p=›Gµïä›ÊüŽ–blÒVó²ýÞ¤Jöõ[´óBt)Á}.l©’FÄ›ˆÿ¢²Íß>6Û T_¦Ü¹Î=¨ I7\¥?7×Pvá6oü¾_°O#%ŒJž«¦É™ù¬ ü¦pŸ¦·”¯{¸&i¥ØÈ~ç<Çk’êE÷ejûM†~Íf5R­µè>N/×sR]Õè~M‡eîÛl¶'•ˆîïí$àz¢kH®zÈ ©k$æðÁL}ÄÝH£@KöóÚOx\¸O3µÿh¹’:ìKÄIÙæo?™íªuQö†ù/t³Xð\JOeœn"­ŒYÚ熌w¾öÅBñïL:v%ÏÕQEz–FPU¨O³µ¿’ ™—ôöL±7m¹’¬ëQ¦$À ­ÍQ¶O© äOÀAJHÕ0E|‚TϬ„ÿ)ا¹Z› 8H37rÇüã{N ñ ÛV¹(i1)V²ŸW’·næ\¢G¯žÑz/%uR-oVms·6W=ÒÜ^Oùó=J«¡FÝä>»Dçf°iJSîþ”x›º©ÐzÉóôWÒÑRv®k¡ƒ´_’F6•²SKý´åå¸ õŒ$š Ýª‹(?El¦ÖFÒ(œ YÜ£¥>Nõ  þMom'à>”9æWfŒmmÒª¾Ñça‚Tï±´¶ËfÌÔžT¤gÉbàÇ-õ£ß¶ŒøÕØ%Ufu¬g믕zk¤¿YœIùsýã+à^´[”y¶vh‰ÎÍa7ò¯ ÷¶q%sÌý´§è×tÏho®öžÖ{yW—dŒ}Ø–{šñÛ*èS¯ÝI»Åü¬ m%à • (=òv¦ví@_H*Ý× ¸ÕÈûBêÙãûYÆØš´7dî×|žLù…(Ž(Ò³»Úœ´Úqä¹=e4E-oÅmýµGÎ|Õ¢}‰9׫H«7vÙêÄœþ+yWÖÖ!äí׫ZŽ÷©™ãí§ýš”äŽphŸ1¶Õî ìéc[êÇ -wn åëÍÕÞ’¹S½¥‚þµ™€ƒ˜¤ÀLí-÷ʬœ=_+‘€XJJPçˆùÛ™cûïLq5mm/5Õ&”OJ­ ÌèÒ™ìßgŒm¶÷·ÞKUaÔê©K£Ð@<_彟i[¹aè©å{ñÒ‚ûúågÔ| 8¿ð>{޾´ïž·’¦žkÄ™€S?J®¨æ<_emBZ 2ʤ ºhwàû=r«ÙÍçRàë·×fîÊßÿ‚T -ÊÕ”Ÿ:ÝΤ•ÈK¸¬Ð~Jû iêb D{‰›‹[ÚnmÞH*Ôi3ÚŸòIËۯͿ“Fý6uòŽš.™øšK©ïç§P¾>îäŸ:<¨7‘^ØDYDZ|eQ` *Àœúሪnñ|•õBRAý(kQnU¶œŸ%ð)í½ÀÊ€ýÎæË·ÕÖ¨¾}H e”öÑ€}N÷IâG½غÀ~./°Ë€oF1i!°GKÛÕó7Ýu¤Ñ"Ñ^G»«Ëù치<5ÀÖ&­œ›Kt²RÜ6_°õ,!-¼PÚ7ˆOtžGü=Ç}éæ=½`Ný0¡Ó-÷ÁÏv) €£ƒþ™˜DV¯°ß‹£ö;—Ÿ“jÒåÐÆ úBÒè·ÒuØ.Ž)¼Ï™ÜAþºdƒZ‹2£"§Û¶-zzÑTmM»¿†TmœÚ´[wsW¶ÏõB*çýE ç!×=Â|^OL¶Cö9“'>áú>º;³E}ð!]óY“¸‚˜Nî7šÝ#€¢ƒ¶!ÇïŠõ€wíûã¤BÏ5Yü(Ó¶ÚHÀý1uú¾F=#œÃ3IoÇÛ9ý¦mÇ“gz[÷ki»½Bæã`%éA5ÚËI ò6,oi»5û©øS9¯©á<”˜B7ÒhëÒÎN ØïLn&¦ÖíTSžE…˜€Ó|îsÑ»È:peÔ0ú­§K‹1¼Ø4`¿·_ Øo?ŽÏ´Üß«—,Í1)— ÒJx‘Ðþ¹åÜrà”è &µ¹zõ(ŸÃé¾\Ã&Àó[Úö8Ëž•¤éˆMåLÀÕÔιXÓlÞJÌêð9ëàæðiâÏùkH‰8 pšÓO»ÉóÖ¾ÍI…jkñÚU‘Ó¤º9Ž%ÕªQ®…r{!°]æmöã4âGœMw8pmp O¢ÝQpÑm;1:€Im>XSÒæNàÐè €·´Ýq:—Såxùr?ò•æ¨á<´€Û‚4š3‘Aû͕Ŀ\xCp j‰ 8ÍÇ‘TÝd®}/Vbšƒ¢èÃëHÓ¤#|5h¿ý¸„µ&àŽŽ€vWYVpšËF¤šêžÅÀÎÑAŒ°¨:ýxõM…ôÀùšÀý×8¥qº 2l#×[úÝHu#œHÝž5Ü”H;÷pÑSùÚvUtÔ²ÒkIß'-”i7òß3ã¹ì™+·Š´øÆqÀÁ¤dÛÀCI‹å\ÞB<Ñ ¸¶~÷² àÿ÷=Ÿ®õK}¢ƒP^¹ŠSj4í€YJüHQ´ðÄè æp7Ò›ø¯D2ÍÞÀ½÷ÿÃÀ}÷ë ÛÈ•€‹L2Ÿ¸ï~ ¼'8†»“6OŽ£k®`Šš“Ì]s)p*°{` €G’kQs¿"%×–7·’>¿ç‘^V•NˆE¿œhkÿû;µ´íù\DžÚ·m9“TŸ.z@ÃsHŸGÀi.Öë6Ï_;^L*N_³×F0ƒÈ:’9¨]“Ñ9+€Ã€[2ı˜Tÿ-Êï÷ÝÓÈ3]¸©6¦¼º•Ô3ª(zDͨ©á%Ë#¢!7^t|ˆ´øÍáÀH ?;ù<'pßµ_롎QpO§½š¡ `Ns±þ[·yþò«uñ…éö$8«ÅêÀ³÷?h-™(× ø÷¯%=t¾’4"êE䩳°i†í « 7å5,èáMùpjyvyë5ªŽ"&àÔ%‹§îßk}îì„òq ªæâªnóüå÷X`Ûè útðëè &=Ø8pÿ]IÀ]üX6åÏnl7’FÜ@Ñwi*N"“¥·çî¿_'¯Ža3àÁÔ»`E­jHZÖ<íª«~Kª· 0†mIËWÆ õë‘À&û?=pßý:™4³`íà8žü48ebN³YÜ;:5²5©(í ÑŒš_˜îi¤ß¦^>6xÿ'ï¿_× ŽaðäÀýŸG|­~ü‚gôL‚Gcn €%ÑA`® ×’÷;DZð¿Á1HýxJðþk(å0Ÿ;H/qÇ£ƒ÷¯Œ¢oU¯mHÉu×à>ÑAŒ»ûð÷¯m+>-þ98†žÇî{8#pÿ]³idU”.Ü\Goï½)Ì:Ôqï{et#êwÑ»D­Ö!­äøjàà(Ò‹ƒóHߥבÄ'PÇÉ#÷½’öFñçö‹èHƒb¶ŠBy8N³±~ØhXJuQFÁKá• IDATì;ó]Àlj}Ø{ð^Ò*bQ¶v ÜÿÅÜuJ§æyCpNðþqñ×ʽ€õIS”5¿»G0©+ž]SCR|è*±”T§ò)“ÿÞï½ÐÍ­E¤©¶!6Ùy1ÝYL£–ÕÆ|):5WÃ[@ÕÉúa£Áó˜Ç"R®_+€¯l'œ¾m CtáØȺ$:WÔé~ÕðV|1pÿè :d›è&Àˆ:+:¼ï¸/p*ðnÒˆÀAž7MÀ•}­¿8xÿƒ8‘:’…Œ@y˜€Ól¢ßê+Ïcû“ê©õë'¤QW?n'œ¼&xÿÞW¦4Ö`ñ«ç^¼ÿAœÀ¤=£èZF'Õ(E5×í¢¨À°…ý'HïÕ¾‡ï¿K •,§Ž—&¾l&à4ßàpy ºøÂw&ÿYCn)ðˆÀýG'à. Þ—Ü“øÚŸ]JÀOZµ5š7åýÛ+:R’¡–äí¨9TK,Ò:À¦Á1D6w+ÝX„gÜ/xÿ]ºÖC³)vV‹BÍ™€ÓL;E¡,6Ä¢MmÃ`«x®Ž™ü÷_’n(£´ß5H7 ‘º4¥1Zô 9të­øJêx+~ßè:b1±/#zÎ!~‘žQuu|ço@°m‡ü9§Ÿ–±„øEÚº–€«a1¯%¤ÅÔq&à4“HÎ Žfl楤pýú [ánuŒ‚{itSi»ÿ]RÃÃXWÔ0’êªèTÃMùöøV¼%½”ŠöÛèFÜ_£ }&ÇÙ°#ßMÀ•±”økÆÕÁûT ×zp•à‘`N3—i‹_‹ èH]¶˜Á_øö´ÿ>:S,M,^°ß{ìsº®%t"E¿Y]I÷êÿÔpS¾˜áGœŒ“A¿ËÛr\t#®†ÜѶX¼ ¸2¢G¿ÜÀ€j¸ÖC÷ÕjÈœf2#¦NžOÉ‘¶Õp¡íª'[ø3ßößß'¾& À‹€u ﳆ…ë£èˆQ’S-#ÕÇê’3£˜TÃg­f[ψ‚t-86:ˆWCn£è=”Áï›zLÀ•qèè^î\긗÷Z?LÀi&£>nðªÉ¾ŽÑ¿àúùlÓ ‹/ü™t‘žê:àçY¢if}à……÷}£0ÜCW, ~ÚÔ²àý£–E>¢Ï]í&~:<À À ÑAŒ8p±^ÕàgGý~¼&àw'uÔ¨õZ?LÀi&£ž°ù4pâä¿_¼?0–îÍ`5Ì”l¼nÊ£?kµY |‘´N –ÿʨaÜêÑ´h `à9Àç€ËCÉóÀkvûÖ¥Žz˜]¼Þ×p­_Lªé¬3§éFu¤ÔJRý«¹$¯£Ž·6˜€Ì ‹/@šæ4—£©cJäàµö³1ñµ½™ïŸ ¸áÕpS¾ftY´õó£™â3¤D…ÚWÃ}Ü(%à^œAå©NÛ)À×€—fÜWW¯]Rõºyvet“¢ëõª!pšnT5Ÿþ8Ïß¹ø·±DÕÄjîjx©!†®¨á†®«÷$5“®áüEÚø8ð[ê[tè­Ô±2縨adÍjÑtT“2]SËè©.^ïk¸Öƒ×ûÎëâ/¿Ú³9£¹¼ñé¿HöÀ«½Q¦îW×õŠ âNàˆ>ÿîOKÜ~[^M»o²k¸A¨!†®¨áXÕ°Rå0j¸)×dóž¤ÄÛ…´ÿ6ŒãIõ±TN ¸®~—E«áܺZ®]LR×p­‡zΡ†äBSâè·_êT â—¤‚úä'ÔRÒÔÍî© ^Hø8àª>ÿî*à À»ÜG¶%-6ÑÖ´Ønjˆ¡+j8V]½'Y£5ÝúuÀ^¤Ñèg“ÊCÜJúÝØT~_`› ûqiºÿ¨½Ð«] Iœbè"GÀµ¯†k=tóz_õüœt^ùÕžQ«ÿv'ó/¼0›7O6ÈQ¬QL°æÖÆâ Ó}øêøþ=ˆöp5¼Ý¬aTWWÔpS^ÃïÌ0jx+^˃A[Ïœl]uð\êX cÜÔüªá;¡‹L,´¯†k=tóz_ËçúöèÔŒSP5Õ¨%h>ÆðõÜ®¤ŽQJ9Z‚5·H#*±Œ´ÚÞ .~0àÏ´åa¤‘$m¨áFÚ\ÿj¥SCRz5Ü”{C^ Òê?‹dLÕpí¥„xI5\‡F]-Ǹ‹×ûZ>׵ġ!uñ—_í¥MŽM? ¼ˆö¥Z‚5·a_8šáV\û4iúg "ýžçVCB íÜRà#¤non$ý>,Ÿòï½¥Ìþ»2×ÿ›n æ^PeuÒ"Fð.Ö$^^¬Oz¹0}dN ç«–âЃò_S½ørtc¬†:€~‡ãsiûj¸ÖC7¯÷5Œ®…zΡ†äz÷Ž"£ƒ€›nc%iA†_2xb¦F[ãjl3Y¼`ˆŸtúiÏqÀÀöCþ|NÿHz`ì·Ž]¿j¸AX<ÙÚJ,ÕÒ¶Ûôþ¬†óµ~tCªá^ê–èÄéÞããÑŒ¹>&à†Sùuµ$‘Ö‹`µL›õzßqNAUÏöÀ:ÑAdr<ðLÛú5pX¦mÕÀQp3{)99ˆË~ŠÑ*à³CþlnK®öÝ|jyô¼by‹ÛnÓLS&k¸)ïâ 9ÔqSî‹•X·’j¾™|‹WC§«×†h5œ»QWÃË6èæ ·~?—3ÜÌUÄœzFeúéíÀk2oóÍŒÎÃͨœçÜâg¾F³•¿H=IªW¿0oÓ¨¹lÖâ¶k¨ý5Œ™k¸¡[›:npUCÌ£rê¢ó}€#¢`B¼Ëjø.uµ$‡»øÂÍïeaN=£22ê¿€³2oóZà_3o3ʨœçœv2ÄÏ5­ñsùFj6µ%i`NדFúE3÷÷fº¿®x3[7:€!ÔpS~Mtch‚4’yàÁ±èojHâ\@GÕP¿oÔÕ’À17¯õ#ÀœzFadÔ%Àû[Úög€ßµ´í’Fá<çö ¯ñ÷àÏö]Ót¥ƒ2oo%pCæmcÓ·]Ë›äA¬bæ)(µÜ”oÀrF-ço\üØ›4}Yp,º«pWGÐQ5$8F]-׊ £B ×zp#ÀœzFadÔA´W˜riA†Fô4±”ÑXP"—5çñs¹V¸;‰z»”y›5Üè9î®n#Ü™®†sp·è†PÛü‹¢?ž< øml(šÅšÑ0Z ¸ÿ6î ì<xiôç™÷UCòtÔÝLåOºx­¯a„¾×ú`Nk;DÑÐqÀ·[ÞÇïÏ·¼¶­ lDEžÅàoá1†OdÜVS¹GÁ]‘y{Ãh3·œfu#Ì6j¯–Æ-¢B 7幄õ7%½¨|8ðƒØp4>¹Wt©?ç§’î·!þ¼°pT¦}™€+£†QT^ë‡satjΜv¦ÛþWá…Ù¼zT‡5 £sfõÏÿ%ïÍõ7¨§^ÌÓ­2n¯†¤@›7y«èÞƒÖlñ^\4ŠÙuñ¦…æ£ÂMÀ•QÃ(*¯õéá¾Z ™€t¿.Øs íë:à…öÕ–®Ÿï\v<ÄÏåš~Ús;i*G V^™q{fÜÖ°ÚÝ[Ã(¿AÌö`xuLKñ¦|p—ÓÍéÐ]p_Rî{¤{T¿èQ*À_‚cˆð]àŸ˜¹ÄA¿j¨±5LÀ 'úZi$ª:Μ Û#¢."%àJú"ð›ÂûÌ©Ëç;§‡ø™ëIb¹}š4µµK2m«†7u;¶¼ý®t˜-ÞUÔqS~÷è†]LÚ‘Yí{iúÝÁ8J§vÑ ¸Kh¯q펎iðókç Dsº0:Òl‹®Õ¤Ž^$j838e`NÐíQ¯n-¼ÏÞ‚ ]«ýÔÓåóËÚÀCüÜ‘´3Jè2àè¶;ŒMçfÚV £66hqû綸í6Ìï…¥‚˜C×ê‘.¢Ý•vûñ§àý‹ÅÀ»ã‰OºjvÑ£TÎÞ´÷5øÙèä鸨áåèZä-yRBôÂ7Ç  LÀ º;"êàûAû>…4j©‹vÆaþÿ¬?ÄÏåž~:UM‹1¼6ÓvþL³é(¹´9 ®k[gÍñÿN+Å캖€ÛŒ”„‹4j ¸¿¿"-|ô'Ò”›óI«÷Õ`_àÿˆO¼jfÑçe®ïØqp2ÃpeÔp­‡î]͎ڵ~l™€ÓúÀÖÑA a9iôHï ÕÞéšÕéÞE/·a¦ŸžC»SIJìÖ`w`¿ Û¹‰:êUìÔâ¶Þ|øé åª÷7ŒH#,ÿƒô{6›nî6&~šÇ ¢oÈ¡Žó–Ó瀇{{÷œl[ÓN €aì ü˜øÑVú{Ñ£TÆ=éº8Œu²F¡Ùü™:îSºö,ýÝ2j×ú±eN»Ò½9øÿNüê›H«¢vQWG=æp_àCü\›£ßzjwP¦íü1Óvšh3wðNRñé‡Û›V„û:±µýn^F¥õtÒKƒ¹jÖZ"¨>té¦<:wpFp ¥Ü@Z‘ôCÑLÚøݼ‡eџɃ÷_ƒŸùs&àʸ™tï­K×úÄ·ü6xÿÊÄœºXì\àÃÑALú2ðóè †ÐÅóžËˇø™UÀá¹™Á×k ì§O¶Ï°ßgØFS÷/¼¿ëI+Â=—ôY‹Ùø<àóôŸ<ƒ´*o´.}?åøŒ4ñKº[tÀ[iVg*§§ïe…òˆ¥r ŽRá Å;µœ^Žvi0ÀÝ€5÷'©4ƒF€ 8ué˯ç à¶è &M‚¨eË~ué7§un(³Bärà öÓE¤ÅFš:!Ã6šzq×»³‡SþmóYÀü™ÛIõ{¢Ý7:€Ü+xÿ5|¾"ü+pXt“ÞOû«-«‘ ¸éÞý`. % µ:Ö(.å×Ñ÷‹`Ñ×úSH#Þ5\J]]KÄ| 86:ˆiN>I|MºAt1ñšÃsî ëÀ£2Ç2›s í§/¦Yñó“H£ÖÎÐ6v!}V#ÜHú~(Y¿êÿüÜÞ9‚7åý×À+Iײ=ƒãX øoà±Áq(½h¹{àþkHjÔàà'À£üå׺Ô3`”ý":RyŒ»—FÒ‡{õÒȹ†4Š« íR}¥­ ü•øcÔo[Åxõÿ=ñǾk퟇:Òwu\ýxY†~4±4вT‡·Ágk·Ò„gwœ®"ÿ ¬[ö§×Þ<@¼÷VTóð„ânӉćÈûíf‰©T{\æþlÜŸ à] âß x>pp‘×ú©c¤¢2ªýæVíêBfð¦è t0u<Ìg·è zyt·€4º³‰¯å¤¡gï¿äò·4øÙóIKÞGÚúk6F>4|1pß5ºøht“ÞÁx.rm5â¾3~HÙï÷q6Ûb'K€Gï'½ ?x+©Þµf÷­èHç­f‘%LŽÀ©í#ÇÜøZD7ÞLZ]´Kn¥ 2lBZ¡jÔ=ˆñJ6¶å…ÀF ~þtàwyBÚý{îÿ6àöBûZÕðç¿”%Šfj^ms⮡? Úw « ùsŸgøÅGrÚŒæÓö»¬é¢=ÃÚTà?§‚ö;Žú]mx)i–‹€£éN±ÿÒ§ùýBS5_ë!¶$Æ¡ûVKLÀ¯{w£Ò¯ÓG1¤£HoDk×…QM9ú-µi>µª†Q;ÑÓP»2JâkÄ׳|jðþç²q‰†Ci渚ûÂap·PÏÃÊ뉽kÔÂ$ ÚïÙÀ[Úv—?mØ´‚ê ‘®%'G[æªã.!MÝtobP™ËZÀƒö}ñ/®Õ‚¾Ø£öúoÀ«‰lâµÔ¿pĨ'à6 -o¯<^M³BÇGЬ6Yï¿+u»®&¾¦Ñ®¤—E5Šš2sðé·_˹& œ’üF ÿsо—í·'*aõ™ü$í²ïúç1·~G¿ÍæYÀŸ€}3Ä2J‹€z_¸=„T§.Â!AûUËLÀ¯Ú/‡?¢¡sÿŠbµ'b›z!õ¯®Ô%[Ïmðó7‘¦‰EÚØz#Ñ+Ž â“Ñ?bq& 'íûpÚ]Ì£†þ&;g¿ÉHCo æú}#pK€ØïM´;]?ú\B]#àr¬f¿ ðÌ Û%߮ޡÆk=À“ƒö{%éz¯dn|Õœx¹‰T8u¼¸ :ˆ9Ôžˆmb!iÄV­V×ÏÐþJ*‚?µ]ãLÞ@³©w%~dëëƒ÷_BŽQ Ç“F Dz!qS=g³)]Úð‘–÷1 ü_ÈEs›S!úFìÿ‘Ĭtþ!Ú-+}.¡Ž M1ÎõìÒÖ”á®ZAü ·=©ï™d!q#ó>Iñ®TÓ°b•UÛ—ÜTï."“处ŷ£™Å}H˜è¬mxpÏÌÛ¼¸Š”(»´<øÔ6õÏo'ݘ¯œåŸƒXJJ„ÔˆØx ð¿CþüÅÀ7€çe‹hpû;çÆÐ¶\S&>Bì‚ ;“^¿ Œaº¨7õßÎly5Ÿ°Ï¿ÒþÊ»ÑçbÎçL^ši;w0Ú‹Ø ëSÀÛ‰5òàuûŸnob^¶ÝH|BTRfkw’Þ¦×ÖNe4Ãß#þØÎÖr'©jñ†;'ï ¼yÍÕJÕ^H*%P²o+(÷¢îÙ…ú4W»¬õ^Îm1p6ùúóà!㇤g‚È~þ…:fB½œ˜þG­ž-©e&öËu¾vt{]=¤{¾vD{]qORQ<QÃágj+iþõú úñð†}ÔUcŸ«=)sÜ*÷lí`óÌ}Ô}Hõ2K÷½äK’臯 ò¸y{}鵋(3µjiPÿ¦·'´ÝÑIÉo/Ò³ä-ö£ß¶Š´Êl”œ£ßnføäθ$à֢܋ÂÙÚ‹ZïåܧQ¾ß'Sfd­¤?"ö‹µŸöøÖz_Ö® Ÿ *ÕNo­÷1>ÍðÇ¢–Q6³yñ¿/S[Ó„å"ÒÃvdJ×,5¥áÅ-Ä>ìÈÒ\íc-ôiGQ¾ÏgRnú"ÀgZêÇ íüL}Ù†ô¢ º?½VbUîýû7µ•(¤¾1åG…ÿ’²Èk©ƒ¶õ'g²„´pS®~üªA,Ñ ¸ÃÄ>¨§´Ô‡~ÛyÄ&}#FžÞ ìU¢s’bÔ<²×Î%öË7‡Àω?–óµ;¨«èiÛV›öX¼µ|ÈYH]ÓPWÒüÆü>4;g9Ú[ö¡_«S.!ÐÆ(M‰}¹ƒTÃ4”ý¶Š¼µüúñ³ q7m7gìÏO+èO¯]Fû+³¾®‚~N’õm;¬pŸ®¦|‰”c3Ä£½ íŽÎâ ÄØOûxƒX¢pŸiû0¾™1öaÚûÚïâŒgôc-¿›’*·±_¨ƒ´a ¥ÖâEÄÃ~Ûî-ƒÒ¾D³ãð¯åCX-7ä½–ãA+zªØ `Ï ý˜Ï6ûôÑ–ú½ Ã)¿"ñ†_U¹I+=âop]¦Ø›¶\E¨k»·½ÐÔaôq‚ôµM/(ÜŸÛ}[îÓL"V\ž©ýOÛÁÖä¯Ûdjct®t‚f b¯·ólòÊ!ãmÒÎÂ…¤‘¶/±AÚ­À=Ú9 ­Û¸’øcØo{^;‡¡¨Ýi>ºèSÅ£Üÿÿû2µÝIóäÕBâ§ÆŸOáÕ¦‡ìÏ‘-öãkû1S;¸Å¾ÍäÅ™â¤ý‘ò#“wÎ{Ž–«ˆÿz¤úÑýéµÛ€2õm&gUÐÇ^Û¥¥>îF%Y²/¯h©/sÙ¶A¼¹ÛE”] j!p\ ýh’ЉNÀE”H‰^û÷” µåW]ܯDç$Åy5±_¦ƒ¶¶sZW[’d¾övC1¹¦ûž\8îaü7ñ¿/ÓÛ‰¤æ&6#~zü/h÷f¯ä¢¶ØõH+•E§UÀÓ[ìßTw§ü(€[ˆ©¹TËôÅ ò®0žÞ~Nóï˙ܣ‚¾Mm·ÐÇ­(_ þúѨUgk{·ÛÝ»ø·â_N³ÑÓÑ ¸÷7ˆ½‰O cí«”Iþ.¾п7蛤`M ÔGµÜ«ùµí~¤QAÑÇmöƒVŽD9¯ ÏqXEý£.ÿ—øß—™Ú+3ôí!Ä׃û!í%á¾S¸/Û´ÔHßsË ÷gj»…öëÁ­ œT¸_«€g¶Ü¯™, º‹üìMmGeìÛã*èÏôÖF½Ñ÷VЯ©í*òÖ¼ÛøSá>|…v’¥ýˆ^ hzûn»Ýýÿ^F;õ6OlWtîÝ ãÖÒH´È¾—X üà€~}²#K%ù±_¢Ã´‹èÎÜø…Àoˆ?fÃã®Ú•¼+K~¢løÙ’Ø=¾\Å IDAT¤Ç\í:Òh¡¦žCùb÷ÓÛOHÓÈsÚ„4Õ d?È܇éžIì*“×û´Ô·E¤i¼¥ûôî–ú3Ÿ§c‰¶X?Sß—WЧ©íà ™úiñµôkzËUóînÀi…c?†òõ&{jYÍvj[<¼ÍN“]hëúÿÙ†±E'àÞÖ0þ&¶.%®R¿{m&áþ‘ò÷'’^òIq €ë‰½€ Ûþ­…ãц‰?VÃ^Ü6háx´mò.Q?Az0zhÉNôi!é tôïÊ\ídòÜPD/Ê0œCª5”ˇúpXÆøgóÖ€~Mm·OÎܧÕoô%êmøöÄ?\ÎÔÎØÇTП™~wŸ‘¡o«SïÈè•Àöïä¿ÎÏ×~DÜÃñÖÔ—0îµË'ãËmcÚŸ*þæ†1FG¾¾aüMíAÞ—ÝôO‘^ŽåôBÒ}É~\Lz¡.i ”\/w[ì”ÿdµñè&­­‘$mÙ¸‚vŽÅõÀ#Êue^›Pò­×Ž Ï¨ÿª /+H«17}{iE¯Òñ_Gz8o[t˜U¤óõ2ôe+bV<ýeÎÕt¤Þ‡ýå¤ié9Ü¿‚þÌÖgø ¶~ZAæj·“êú༄4"´tY‚oQ~”ž‡;Ò¨ŸöWò½¤\ 8ˆ4]¹í¸ŸÖ0Öèûûˆ…@¦{,ñeBN$ÏËÑÕH/yJ|» Ø1Cü’:âñÄ~i6mÇå?$Y}ŽøcÔ¤ÕpqïÇÀ—iêÛJà3À½ÊtkFÛ’êúÜ@»}ÍÝNžJ󇘈Qc3µKIÃL‰{åWì›Ú7D̃Z@J€EŸ§K€1\ ¿ÅÀK‰%þ#Ê>ðo<—vVÌÝnÞlž¡ßçUПÙÚJàד}}"pÒïäL–û‘¦ÔÝZAìý¶³HÉ–ùê¬nDísA@Œ‡’„Í|6ž Û0öÒíG¤’ƒ–kX—”Èù4pMÁx›¬€ ñ ¸£esx<ñI¸Ûÿdø²'{S×î*bX’èmÄ~aæh¥V¾Ô^ÄÖBÊÑj®}ð`à”?.«HÅÉ›N£Ä#H7ѵК¶[I£‰vmp,>XA?zífÒÔÄ—nægJš¬AmóÒutÌ_èï07VKn‚t“û¤ÑSó%¶¶'Õº0(Öc)3ÕíäFÎ-Ü¿\m%©ÿ¤Àa|¨‚~ Òöšûs€o“®£is·+Hçó3À'I«þ˜{©U¤ÑÎ¥¦€ÿ;é³¹št®¶8Ÿ4 úpÒùüÀ”ö)Rbó‡¤$lÔ"eM‰NÀíÛ0þœjHÂM¦ŽIzá»é<1¯G1Â}‚4òmé<1JA‡ÿeÙ´]BÞUµrXüŽøcÓ´ûÀdößÄŸ¯´ßÅÿ/zJ_îÖtÅ¿×PçÊ«HÓûÀ4ô*âIŽv]î“YéЦ·SÛïâÿ×Å•tçjßÎpLžHìTήµ¤QÏ¥§SAš: £tÚlï§Üh›GêS©vùÇaå‹ù7i½ò‹€eÄ3ŠíTÒtß’’)v[ÿíW}™¹E'à6ÉЇÜv!~Œ>¿5·_“‘4†SÇpámÃ,Îm3êzÔ´Õº*ÏÄฃ2£U·ô¯Ívn¦c³i Ktjo§‘V,‹ôpÚ[$¥Ëí&à™ Žë0Þ”)öšÚÚC‹U{¿m‹É˜w¨ –Qk+I£a#[•—¶]j_îëÌÌ-¢>h¯ÝIšmS£Í©!˜¨öIâtQÅjý0+¿/ÕIÅ[K˜Ë‡¼mÍj­O° ñç{1iʶmNZl”lÃìÅq*°'©VþÞm¤‘U{’êFú) øÓà8jr:©®×Q…÷;_Ñû.ÚvÈŸûfÖ(Ú³Š4âFóüE:TOë-¤º¥y>Ë;/Ã6"V©î¹†ôP£+I£¬ßK½1–v p)Ùñ£Ê™€MŠ ×èa¤U£"íüSp ¹Õš€Û9:€IÛØÇööQÚjÀÖ™¶u3鯿¤7ÒJŽ"%ªßIJÄÕà à€e¼oBW‡ÎØÿ(~§ û}òk†ŸÂZÒU¤Q/0šç/ÂuÀkI/~‡ç³¼Ÿù3 fsõü%ÔJàݤëýEÁ±Dû5p?|Q¬9˜€µ&VšøíÝy¸$Uyøñï¬0Ì ›*Œ Š( .¸"Df“¨¨q‰QƒÆ-jbˆ‰Qã¾Å}‰Æ5*.1î5 H”MdUff¹¿?ÞÛ¿[·oÕ©S½UwßïçyÎ3wî­Suª»ªºë­÷œó`·–¶½ŒH-n;+kÐÆ5P{Ƕ0ký¶ÑkfǸ»Õ€×÷!bPâIÉh–Ÿƒ!GL*0n¶¯"¾~¯å¶´á"Ûæ¹Ä¬ÀmEæî¨­ë±Þ3&å°ÓøþÒuĬÈwÞÂ\`³-¾Ÿ£weŸõ—Ðnî’·ÝDgÖû7³ø²á6ÏŽ~Ùr[4æ À-ãXéÇ-‰iãÛðà°–¶=L㨽eÛ ˜U7Íù Œã@»ƒ0Œ®ÚWãi=8wëggîÁøÏ` ñþ ü‘Ý3í6™‡Ñn¶ LçÐý<|ûôÀZ1<~žÆ÷o.#&¢Ùx)ð»v›óÿù~Ž^¿Ÿ9mß Ìé^ÝœLô:£å¶ŒÂ ñ øbæÕÅxT À-ãXé×_3ú@Ø-SF¼ÍQ9˜vfM¬s‹¶0k_œ÷Á6Ú0Ìý:˜ á©Ào†¸qðßÄ÷>O|ù›;€w·'ž\ßns†bð.bàüWOÅÛ6Mã”vìÚGÝï0þ]ºŠ¦õ3a6Nd“¿†ñ»ÎLãù8îúÍ€Ûy ­èÝy-o¿g÷þ˜é}@ú}"ÐøX3Π p‹ÃjFÓu® Ë·2Ú® ¯§½®¯Ã¶3p‡¶Qb\p£¸šÖ›­aßtlÞK>žübÈÛ¥ ÀÛÃc€oµÚšþݼ’ŒüåD&ã¤ÛD KpGà/éÿ†oPÖÐîàáÃÒë,¨AÒÏ ª!CrNág6is=†ÈR?ø ñ™0n–Ó_ðX½é7®×.ïƒ2‰8ˆ„Ÿ%>øa»Í˜¯OÜÂIÒ÷¦ý©˜‡]ž4°W+íH"‹£íýfùÓ½Zƒs>í¿.3À'†½£À{ZÜ¿a–¿ä‹”a ðà‹ÀÖ>ÛÞFÙAÌ z<°jÀ¯Í¸Ù 8øí¿îMËEDqÝÓ{q Ú†Q^ÕçërÌìCª„}o Ú3Ne31ÖÔ €»0YÖÒþë·ØÊ YïLÚa-ïÃ>؇qqàãÄyÜö±Ñô8ú1ž­Ô—åm7@#1ã¿u{-Ñ m÷±œéœx¡Û!Œß9ã’Á1Šv´=ÖȰŒz¿f€/Í–}ˆ§¯O$ÆLW[€ï_&Š¿°ÝæŒÌâ‹í€;AÇ']UÇÑF"ƒêýD6â8ù2­×“~¿¿~ƒ˜Ey³Ë.þ¯ðÿi}sì ²™DdÏüøñPe-æ÷²-ƒs´ÍëĵŒOFõ œ>[ÖF|/;’ñì•·ƒÃõƒÄLóÛmަ…¸ÅaZÇ+Úx1&ܰ<ÅñZŽcÀv§¶0Ë\ïÚܯ+7Ζ[ÇΖ‡ mú?"›ã˳ÿâiý$;ŸÈ({9‘ÝòðÙr$í^~MdS~˜ôbK‹miÂëI¹­Dר¿@[­{ÒŽi};fˆ .,”_Ï–Ÿ2]7½Óþ^Ž£A¯Ú ÀMj÷Ó:ˆï!2ÈF|Ö?Œv‡¹žk÷ ÄÜq/TÈÜâ°‚F³ë½øÉÖ}kÚ›quÔÆñxùÙl¹ŽøÐî”ëˆñ|:6±ðÆøzbl»²àÙ~!Þeö÷kˆ'tk å¢>ö!×%DÔ¢íÛ‰' Ûˆ‘›ˆÔýHglž]¶ÎµÄÄuãÒ¬£:ûsWâõÝ•è.¹sÉïÆå îo€wÏ–eD碫þ݉1¼Ö a»;ˆãç¬Ùræì¿ÃÌÚtçÌ–×ÇÔ݈÷êˆÙŸïÀp‚rWƒFŸItÿ;øí¶3 Û‰ãì:âì\S6×”ÎuäFàfʯ¡¾žtêÖݤ®$=vÛ®Ì]‹–Áñå…;×ë5D–X¿>I~n;ñÚm ^³îתóºî ^kfÿ¶)±Îâ²Eݸ_2÷9×y;:Ÿ EeïUN½¢ D`¬Nç8ªÓy­:¯ã ³íÜØ`Ó`†8aîµ(ê|¾uŽ­ŽÜzuïu±m¹ŸGÅãi7“½Ô9ï»-¡| ¶²í–}§D«ÍÙéÏlqÛ£r5ðï³e ñ=ì^Ägý=ƒNtq}=›ËíûÄgÿ8Ž©)2í]é®d|±¶÷að]‚>AÌr³l'¾¥n¤i¶ñð¶Dví-ˆ'´{3?0°Œ8_:ˆ D0ãZ"@p1P½„Èð¸y”;±,ö'&ÞXO¼W{7K{ßq:_Ú;ïUçæu;Ñ5éj"¸v5p‘÷ûQí€Zµø*qÞ^Jœ³—çjç!ÏF"Xäç¡´x½ø»–¶}ñ°`±Û‹ø¬?ø|¿%sŸ÷;wV2÷Yób_A|Î_A|7ûåì¿9$©‘½iàÊQ—§ ä•›ó1اQ—q'K’$I…ÓÞ÷ñ}G°’Fh<Ô`ÚvZð/ .]|%ð–­k’Œã8p’$IÒ(­oi»ç3˜îö’ƈ¸é7Žãy ÛžDºø ¼{`±YŒÇ$I’Tt@KÛíRÒ07ýk&ÓÓ‰AÕû±?ð’´e€“$IÒb¶+1[¾ÓÒv% ‘¸é·X)K·Q>£R®7‘ž±mš-ÖÀ­$I’p8íLZ8|½…íJ2pÓm pç¶Ñ¢{Oí±î1À °-“f?¢+¯$I’´ݳ¥íþ€˜‘YÒ”17Ýn ¬m»-;•˜ ¶‰€7¡-“Æ,8I’$-V‡·´Ýϵ´]ICfnº-Öî§E»A¸&^Üqm™4?’$IZ¬îßÂ6g€O¶°]I#`nº@ Oìí±-“Ä 8I’$-FwÖ·°Ý¯¿ja»’F`yÛ ÐP@ K€ww¶Õ,ûF`ç¡·h2ÀíßÞÄyxâ‹ÜÊv›#I’´h\¼¨ÇºdCxoáçg÷/oo~Úv#4 ÀM7(sž ¼%±ÌMs&Â!Dðr¦í†L€Ý‰ Û]ˆ€ÛÁÄë×tüAI’$ Ƨú¨{ìÀZ‘ïbà3…ÿÿ!ðÈÚ¡|3À?·ÝMpÓk%p`Û3¯">ˆ//ùÛ*à_GÛœ±·¸ pIÛ 3{³buÊ=€[·Ú"I’$uûRõöŽdC2½ØZøÿšÚ f>\Øv#49 ÀM¯ƒ€m7bÌìJ<¡8¡äo/#fÕ|‡°¸pëXl[ßfƒ$I’”å¿z¬÷xFu󻟂¸q÷àÙm7B“ÅÜô²ûi¹'ï¾UøÝ€ç·ÒšñwðŶ1BûGŽ"º“:Y$IÒd¹ò^/9N`;r½ØÜõ»}Zh‡ò|†H긱í†h²€›^àÊ-Þ Î\Š÷›qâ…*Ó>‘Ç­€1t;¨ÝæH’$iÎê±Þ±Ä}Â(K$-ÅÜ8ºx)ð–Û¡ enzM{à¤wžCŒùöXÚduRLc ÷.ÄdÜÊJ’$izœÙc½—´õfˆnŒÛº~¿'4N6“ù½¸¾å¶h‚€›^Ó8¤S€Ó€×¶ÜŽqw1¡ÇÍm7¤;é>Š˜Ijßv›#I’¤!;»‡:î3è†Ôø7à›%¿?`ÄíP¹‹€wï®j·)šà¦ÓnÄ앪¶8أ톌¹Îlºç´ÝÜ 8 8ؽå¶H’$itþ¯áòk· £! —/ªø›½™Ú³ø2ðÎÙw´ÛMpÓ鮨­.‡Á·ø4°SÛ ‘$IÒXø}ærûŸV±-Ý~<‰˜ý´Ê}m›¦Ñ6àÀYĸßßÎkµEক8 Òm‰±Õ6¶ÝoÇà›$I’æäàn |ƒÑŽûökàÑÀ¦šå2‚¶L“mÄxÕg?žý÷§Àæ6%•17}öÃ1°4XK€ƒ´Ý7·Ý€Ed;Õ÷kpÝÖ=lÒºS¶Pÿ»M72ÞçÏuŒï¬cÃ<^‡ézâ<Öàm$n5xã~­šd›‰ç§Ñ¥5¿5ðMàö#hKÇ¥D`íÊŒe?IdlM²MÄw¡aÛœÏôËš2à¦Ï8wÔäº+ã€;¸åÖ{3ñ¥¶2¼A^·1ž™Š’$Iãàà‹ÀmF¸Í+‡f.ÿ³!¶ER‹ ÀM»ŸjÆ5°{Ùl‘$I’RFd—í:Âmþx8pÁ·)iLr¶ƸJ4Ù<®$I’4‰–'_`´Á·*|“¤)u1æ‘Å2Èr5’$IÒd¹ðFÿÝù8“©$MµeÄ€—mk,ÓYn$I’49¾Ëh¿/o^@dÝIÒ>„õK’ÆÜù´?P¿ezË ´—$IÒdù1ƒýN|&pߑàÍôôØ™˜„A–ÕÀú¶!I’$5ðÑ­ç"àxà^À÷´NI‹ˆ¸éq0°¬íFhêÙ U’$I“äýÀ¦>ê_¼€ènúQ" N’37= Œh<Î$I’4I~¼©‡zWÿ¼Ø2ÈFIZ| ÀM'`Ð(xœI’$iÒ¼¸4sÙ_'§×©M’¤ õ_´?H¿eúË9H’$I“çAÀVª¿çžü)ë#Iªqíg,Ó_¶;!I’$Mž“€Ì}·½xpD›’$MŽ=h?0cY<å0$I’¤ÉôdàÇÀ³u-·E’4aŽ¢ý Œeñ”ã‘$I’$IÙœ„a:80¾FÉ™P%I’$IjÀÜt0×Ü%À sÛnÈ2'I’$I’ÿ¡ýn‰“R6ÿ ¬™}í–O®ƒ¶MJ¹I’$I’¤Ed p-íe&¡œ¬¯x÷ÞDzjrK”8`­$I’$IZDö§ý€Ì¸—Gf¾ž_ƒ6{¹æë)I’$IÒ¢çp“Ïñ¸ªýx.p/à»™uÎŽ üzH횎;(I’$IR&p“Ï@ÈB[7·'º•nïa§w&x×®iSÃÀ¯$I’$I™–·ÝõÍ@È|_#‚fç `]7¼Oÿ<ƒÖwÒð-nGŒ¹¸+1‰ÌuÀ¥L΃ÄÃÝ€µÀfb®®n¡=ˈ±@÷˜mS§=¿6´Ðža¸5°qÜl#öïrâ5¤[ûÎng†8&¯.ðv†i`obvûpqŽiúíI\."ÞÿÅl5p âø¿±…í¯!>ï̶ֻábœëˈkÌb° XI|6IšBKÚn€úv6pXÛ¿þøâ·qO" w¿!ncR\KÜÀJ>ÂÜöë™Ë:]FŽ ¾T¿xÕ¶¹œ¸A^Cd¼î ‚ ;_à!n®v"ožÁÜÜMÀ–®m¬!‚G»ÿ¼¤¤=/^Dí·»ê-VÇÙµ)»O&^×»ÏÖë6\œ ü7qÝ»"±Îs€Û7êw›–0r•5À[€ç×´·Ì}€?Ü…¸™(sð3à;Ä>ü„áÜdí <xpæfÁîv11fèW€/‘œN|÷Žˆã²Jñø,*ž/œmCŽÀÇÍý½*–»†x¿>»îŸg®¿c)ðPà/ˆqU÷«XnðSà›Ä˜ª?L¬ó¹Àë™þÞHœKE«™;ŽVçün ÚÞ±øSâõ:œ¹×»ÛuÄkõ%à?ÿk¸“S‰s®ûú´–¸†­^¼,s÷¾ÅÜõ¥ø:ÏßµÄùþ›†møàcÔ_ÏW¯þ±‡mt[Fœ{»‘w=_ÅÂ㣩=ˆ÷t÷Ùÿÿ!ð…>×yð?Äù± Þ*`gb_¾C^Àoñšt®×[ˆÏ«Ž]g—YEœÿŸì±í_!®×ožÓãzšXC¼îÇד¨¾/½øñyñ âaûUÛ¸8V¯-ù[ñý)³ ñ~u[9»N€ÛP~žO<é|¯ØÎ‡fë˜ÛßÝYhÛl ³ˆsÿ?™û—$µ`9q!n{@þ6˵À‹©¾Á´%ÄñÅCܧI)U7_Z\Ž!ÿ˜¹ŒÁœ«j°Í²L±W6¨_,/­hÏ_eÖ?*±OK‰›ž=´k ð^âF¬Ì÷{Üß&7×Kˆ×Y=nk¸ø{"³j–O¡·™Â·ÿNy  "ð9Ìëë“3÷ñ(à‚·ñM";1Ç݈àd/Û9‹|–9©Çuþ6³Ý‡'n„›nk;ð9b<Ù\'6X÷C3×yHƒ6Wë<¤Á6.§<@ÑÔ± ¶9¨Ì¬çu­÷XçÈßÜ +ä&Óc»îZÏ ”„e?âaöò_¯²óæ{Äõc5Õ®ìc9¥*âGCÚÞï‰~Ňƒ’¤êþÐ\Le;ð!"]¾ «Sˆ š¶_‹¶J¯_ö4]šÎÄ|ü¶ù©Û+˾9¡a›;åÏ+ÚóØÌú·©¨¿ øDE›ˆÉaÎ#‚B©õWev½¯Çý}lÅúºÝ™˜è¦j=€AŒÓˆŒ¼Ôv·™JýX¼;±þ €s‰LšT[ö¯Xÿ;jêõ[þ&cŸNdpt×ÝN3N û}M¼feõKd»Õ9«öç~5õªÊW2Ú @}‘ET¶žÄët:‘É’Úæv";¨ê<+ºwƒ}¹‚¼ ójÊßïîÒO×î[5h÷ ùâ”7ØÞYØÞâzÚ½îÃû\ï òŠo%²¾rü4söØî7—¬ëo{\WʲÙõ¦ŠWïñW‰Ï”+ËvJªçË÷2ê÷S\±Ý4\Ï5À¯ˆ‡”uŸI3ÄÃ}û-I’?g¸,ãZ¾Åøt»½ lû5i£¼`¯Ÿ¦Ã®D&NÎBª;ZŽ=©ˇˆÀPYÆÝRâÆÛ3ÖsÑ óv‰6-%®I­XÇ¿]ª¼©¤ÎD»8îäž³ëªjkUÖŠÙö} c7}UÁÂn¡:Èóc¢+jÙ{p'âÆ©ª9AŽ”SJÖyÉl{Š™‚»]£«2£ª2Ä–I9ÇО]u×Çæ‰T._]³,ióÀ³™Ÿµ·’8W½GO¨ÙÎ=YTØFd*»,/#^ÛË+¶óâÄ6öžF^FÌG‰lºœLÚý¨Îȼ€øÕ½žƒ‰Àm*ØýSòνˆlݳ3öë«ä1»ít×.–ÍDÆçºÊÚyÖ¬ØFwé7 ¶;y2?B¼7ƒÈ þƒŠm|xë^<€è*X·O—²ðÚPf5Ñýøsë9•Þ{$¬¦ü¼û ƒíY²'4¯z->LìcYÔ»ÿ‘¨ûîÄvw&‚ü§%êwÞ‹nëˆa N&º¿–Õ«z(ñÙñºšíÎ]o‹V×Ò3kêýœÞºáK’úÐk7ªI-—O[Çqì‘ÿ”rZÊñÂiªÜ‘¼c§ŸqŸ“±þ’øBͺþ¡AÛ–Šõ?QSçÞì_OŒSåÕmÍy"þ?u;åYëèøsªWo¥þn)å7V×4hC™õ,¼©ßBýª¼°¤3D ¬NÝÍ]ÝMö Jê¼?±üJÊ»>.QçhÊß«u–P L=|9˜£ª»Îß%êtœPR¯XÎ&ÿ¼ÞX¿l=_¥>Hõ`àw‰¶\Jú-ZGyÆUwiÒ-qv¯{eƒú9n_ÒÆ²’›ÉU&§ûþY ö{_U¶ñÍDà ,>S±b9ü}[ÊÂÏ—¯öÙΧ&Úö¤>×ݱ'Õß7ãÀåxvÅ:^žQw1–Zê|NYI óÐ]· IDATï¹Ûþzb»3Tw¡]IŒÇ˜ªûÖŒíK’è³ä}9šôr#‘ÍP6ù8YJ¯¢ý×le]A4}꺼ÍP”JÉcì ÖWÐ{`ÃöÐUÿÏj–/Ë ú÷š:;QÞ5'' ð²’zÅ’ REu&âçÈ¿©Ü…]’Îά[åU%mª›Ð`1({w½C3¶WDÈÉré¾¹ûQbÙ?,ÙÆ5ÔgP•ÝÌ=-±üá%Ëo£~|±×–Ô{EMˆrêu|]Æ: ²ª²·~I~ÖÈᤳò~J~¦æß&ÖÓ)[‰ì©\oíªDƒº¹rÆOütëÿaÆúßÜÇú»íKº‹ß?p[G'¶S,9ÝÍ;ŽïªÛïd ©,«÷¹nˆÏªT¬ðð†ë+ëÚù”̺ïªhÇ y³/Îèª÷žŒzuŸ¹©1ìv~¨»Á™*iDrÒÝ5¾i»C6CÌèt0sã­³D··;¯¡ÿYºÆÝÁTú®Å+gü¡?¦zl­”C‰.!uš Ð~YŸïö³®ÿŸ_³ü±%¿»¼¦Î¢+^·œ³/®ù{Îk·q­+ËpÛÀÜô9®eá ü e 6ðˆ’ßÕ½¦Û–ü>ç5½(c™:/cþÌ©©ëkÙþ]Iý¬Šï/ù]jÿÊŽÍ)ŸØ¤Ÿít\Iús>÷¼þg¢ÛZ™§1†Í”ŸPJ}Wá&–AÒœ€-,œÅö‚¶¥#çzþz³ñ.äMlÑt”“H`ÿtúïþÞÔ©äOðÑýž7·è^TOŽ€~Pë‡ø.\~ ð_=¬¯[îÄ5ÜV·m,œˆ)ç>,'¸We1y•eÄ÷)IÄÜäZMÿƒT³™'ÇQÃ8n®%žvJ}ÖÅ$Û™üô´xTÍY´œf];NÌ\®É auý›¬ ìRÝ)WQ~? c;ß-ù]Τ4uÁ­œý}5ÕÔ%®M|§‡6¤Tò»a¾¦ƒ˜™ñ àk…ÿïBõõµlÿnC}Öá÷‰›È¢Ôþ•mgWêƒi¿`aà&w¤Ôk™óî>À3+þößÀ·3ÛÑñI¢›z•gÑlvÔ:·&†wÈÉ -^k¶Ðü¼Ë‘s=_FtlêÄÌå5ãã2ÒUˆcûÄm/×J¢+~Nff÷çË•}l÷/3–i’×í¾À_Wüí*zË6ü9 Z¹Ÿƒ¸N“¯ã.ÔßK÷»Ýÿ®ùûÁ}®_Òˆ€›\9ýIt 1¦ÂÄXE“ì|b ì‡7$ÓhÚ³0Õ\N¦ ÄPªëE·äÏ š›A MÿÞ­;H°%±ì^¿8õã¹Îü,³›È»ißÞçß×ãUÕ}gFºÓõÿ~pk(®àHÒi@t•*f.o¥>« ê_³\§³ö}’Èԩʾܻäwk©Ï„ØÈÂã³l;P?óå q|æn§(u¾å¼Oõw£÷f¶¡[jüº¥5ïÅ£ÈSáç~ƒÖUr¯ç'Ñ,sl9ðÄÌe›\ÏSÁ܃ƒ‰åžËè³ûoGz2ŽM]ÿïõ}_Çܘ‘©×âäKÐퟨþLz{ÍvSÎìúîk0ˆët§gÎw‰ŒéœY€ûÝn]–cîà IRŸN"ol‰I)73Ö-3©V3)]Gû¯õ Ë)|4ùÖ1ÿøø éã§*S¥Ìc õ6Q=Ûâ åÝ3«<´¦9­ìªŸº¦Ý6±Ý/Q?‰Á“‰›¨˜eã‡K݃Ô,¬ßÌlC·â`·^í–hßw©ú<xùc…= ±Í@è¨ÐÿRÒ³íB+O$ÕÍœøÕŠí\K}Wð{Çç£3ÚT”:¯ŸQS÷ÎD®¬îMô÷ýâ;‰víîPS¿8Üw‰¬˜Ô1³…ú̺û–¿ªéeXÛÕ¦ºëy“,¸G1ÿ½ùmb½ïW€¹Y—·S?nã ºõ]X߯‰‡²©mÎPŸ•¶”ùÇxÝqWåäÂ:NfáLÇÅÒËC•²ñ#‹åŽ=¶¢Kæ ñ€ätò„ÏJ´§Ÿn¢uþ(±ÝòDnMÔïõÁ‚$©¡7PÿA>)å«äÍ47 ö%‚ížõpR˧ûòhÂ݉ùÇG݃‚_?Xÿê}ˆ…³ÁË·´¹Í\*X4CÌž6è–û À-!†¨ªÛdÇ¢•DÖóäuyKYFõä3Ä Úô¹nýàVY9u³ru²ËÊ¥Àý›5¿ÒÇÛùù³6ÑO.5ØyjR‹u›ÕÔ/àþ¹qSåW¤ƒÀ÷.,{Eó]ªu‡®öÔ]ÏÏ'¿gƧ õþôL‘e]ÛZÏÜ,Àß&λîÉ_½Í£ ëû9p ggî.›©Ÿø¥8›q]Vo™%À¹³õo"ޱNp²¬l¢:[»Ê?'ÖW7.j=ˆìÜå ëõ€[CqpLÆz÷&®ï"=9B]fϸ¸Žôÿ? ºg¾Œü ·a:”ôäÝ„纙˜ ñv±jj;éÏ“{í|%q#×–ÕÄ ÿ‰ f“Œ›ÿMüíÖDúýô?Fgêý܃˜íö3D`a<2ñ·înÎM¥Æƒò‰1R>0[RnGëÚR¼žßL´7<¹#åwtÛ“ùÁÛQ\ÏŸÎÜ=ÏLj@×Ë?€ÁÏ*ûSê»ïL¾› ÏÐÔQÌ=ôþ2ñ9”z˜ºŠ¼ñâŠRçâO®«Ûï‰1&›Ñ‹Cˆc~#‘ÅØF²@ÝqxÆHZ!i` ÀM®I|,–I RÎbr'™(º=ýg¬hz³Å.'‚!o¬©srÆzgn îÿ%2€R3ãMJê¯{¯"fpk;wxÍßÏI+ê}¹æïk‰q».¤½@܈™:;cM5™å»nÿ–VçÑ_ ®n;KˆÀáOh?·Œê™O!²Éúq ó_ïvùÙ¼Ϧ>0xõ™ÃR¼ž_Áà®çO`®{ý/ˆî½©ëù¾ëLYIdïAm:³.×¹ö¼>·[æ]D0å àmCØvG1˜ÖiË'HÏü,`§Ìõ¯¢|—Ž~ƒá£tOæw—mr”ª1W!ÆÍþƨ"I‹Ù^Ôw]Dzè:怡óíBŒ¥¶‰öߣ^Jj{-.ÅnZAØWÝÕªŽŸÔ?U>»°üsf÷ÜÄ:gÈ¿«Í.¨SŠ]ŠêÊ5ôˆë§ êëjêŽËµ}=én¨Ýå:ú ÄõÒõ]Ë<¦ÁöV¥ÜýÛJï¸TwײsùÓôˆëµ ê5mË Õ9­f·JÔíî‚Úq013qj½›(ï–8ì.¨Ï(¬¿“e³ŠÈ>Jw©YïY…å;aÏI¬s†þ‚ä+¬§T^BõxŠó&•ñ[ç躊٤kÉ®j€ÿ~º îÃܵq#ó ¾¹¦=‘¹»×¬§—С—.¨ŸíZn}Ûí§ êq5u_ÞC{$µÌ ¸ÉT7>Ä8ú&‘=ñd†3Xð$ÛDàî|˜øP$wm»Å`U'£áFÒƒ8/þ:ñ÷Ù»¡ßDœ#“÷sàõ –ߓȈ»*¬H/>Pu¶^gµ´‹h6AÌ®ÌeĽ„è6Hëˆsã®DÀ÷ÀK»–i’Y±•ÈbÉ]¯˜÷Nªg7-óWäw ^ü ‘÷ô¼hªn¬ÄA›Õü½ÉëÚq.õ‰U ¿[b™²ëùfà‰:KH;enÍÄCYîõ¼,ã â»Vê³i9éϦ^m$+uçüÛè}Ò*'1—}øyæŸÛï$ýýóyäeyÖM:“Ê´kËRbÆßýˆï&ιî#£Ê€[¼øHb™Ÿ¯Ms$IÅًƽ\BÞ4Ýšsó3~ƽ¼n8/ƒ&Pç ü 1«qǾ¤3’n :ÃáM…åÞWø}qÀ²òàÌ6·ñeû5í¨*çR?[bQ?pÝÙÝeÔ‚”%D·È^^Ó_Ù+¹ê2àrÊ‘=ìãKzÜÖµ¤»5u{bÛÙDd~5éšÙkÜ#kÚrRuÕlW³£u«2à:ÞW³îŽ7ì ¸·ÖÿÖÂï÷!=kæ&ªƒ0ÅY”?Xø}q_ÊÊÃz܇â̸ױðµŽtâzŸ=÷èÂzÊÆS|zb»r6 ôš·”xÈЩ[ö™š #÷}ø³šuœÐ Íƒ”Ê€Ë-½ up&­‡'°¥&;š!ºn÷Û5[RKÌ€›L“q´ øG"«ëC5Ëj¾oOˆO`2²'áxÔh3¤.ïú95îÍjÊ+‰ñ‚:Š7®©1ƒ`r2à Æ%z4‘‘ÐÔ‰ûšTzµ²æï½Þ¨à ðxb†Å¦nGÌÎýü¶(mkuN^LšXGŒõêßSˆ,Œˆ z«ˆ6žÆðºñ©vÀ6ê2s3Ë<›ôäïÁ(hV]ϯ$}^­"‚KÝVãyv4¹ž÷løKæÀ%2²‹6þŽºƒ Þ–ù·Ù6¥F-áXæºPþŠò±ÃÞR³Žœqñêî-sÏň!NîD\“oG Á³ûlÙ«ðûÛÑ[÷Ц†1ñÃ׈ìäŸ]¤_Fuöð q¼Þ›ùç¤$iÈΠÿ§8Ã*;ˆÁ\GÙõdš­#¦so2žÑ¨K]×-Åñ¢ºoe.¡¬\Ld‚ýIáïgwýmeÍú^”ÙæqÈ€ëXJd5ÝXÓ¦ªkïc3¶ÑOÜ'jêº»Ô tº8o ·ëÛÓ2¶Q—wqÛjÃÑ}ìã#˜ŸÙÒ¤¼§ÁvîKdòô²/17áDJ¯p«Ùþ«³÷²ÚßÔl#5©ÿ])uðášöÍoìs;· ºú¦Æñš¡>“©ŸÜ'k¶}xÃ}ê–Ê>iMÝ&8ˆ®äuAÍbðxиý ëÞBõL¶'Ô´±3ùNqf÷û,\ Y}U빸aûßSÓ®^ʧ¶áèBݜܢrî{>CÞ÷ðãjÖÙK¹Št·ùÕÔ? £ÝEK©ž´.øÝÑKîÄû~1®a/×&8ˆ®Ú©å?×C$I}øÿAÚ¤ü‚ôÍFïîÌ,¹Rì:¨Åé~Ì%–Û‰ôMöÍÄŒ ÿWÛ1æUÕz6S}ÓXÕî²ÒtÐã;êÖuK»%qcþAbŒÇ¯Sð[A¼SmNMÐO¢;fªþ ºRöjw¢«ìûˆ ɯQÿ.^Cz¿R“zô€ëÇ3wŸ%fk}\FÇ“ž–Ž'‚jŸ™ÝÎs2¶óÒ£ÔÔï'÷‰º3Äd½ZË\ʲR—Ó4ðÄÄöºË pÅî­UÁ ˆ.‘—%Úu3ó¯9Ý“éý]b=[Èædõ“YôR¶ÑìÁóÑ…º98ˆñ~75hSN{¾Þ`}MJêsæI5uëÎå2ÅI$Šå³™õ{ À BÓÄ÷TcKêHšƒè£Ñê%ýy®%RÎ߯p¦áVï~ <ø’ûfÚý®D°c¦…mk<¬/üœ„y qyeÅßWctu¼+±®Ô ¼;{Q?0úõ5_O³(ü¼¡fÙÈIŠnCÌÄVe+1Pû~Ä9_¦{öÁAú0é1ŸCý8tÃt ð/]¿»-é÷p;Ñ-k_ª3цùšv{qÜ~‹…•%À[™Œ8“x8—ò1"ø–Š¿¯,ùÝ«™?£yNçkDà¨ê¦¸l;ƒòYbÿÊnh!•oîqÝÇP} œüoMýâ~çK!‚8'e.?Hë ?§®ç7ÇcÕ,³+ˆkFG¯×ó•D6RÎ1ødæŽMÀÿd6×ûÍÌM‚2Ãüëô=ˆ r™eÄð'g´¡ÓæŽÜ÷üçÄùßdv┃€ÍþÅîñ߬Yv/ÒãœtÊõ¤'1xtE½Ny@F»w­YÇK2ÖQôôBÝÿªY¶lLÏ2·sÇD›SQýfÀíLz¬­â}iËKÚóÜ̺·¤:K,5ˆ{]Ü^ Ú¿s™V[‰àFÑÎ%ë¯ ¾u,~YÑÆ”,U×2¹ã*Bu¦dj¶Iè/"¸_U½wCMu©Ë™!óÔÂò«Y¶h<®»V:®˜±÷še÷ /ãl#é1ƒYSÿ¨Œv/a~Wá—eÔ)ZOzXëɟųx}¿°a;rºÏPŸ÷†Â²uïc·îײ¬<2Qÿµ‰z34?»'DH]»Ê¤2à~Ó°-ÅÏ™Hg÷’õݨŸØ°Í’Æ„³VN–ƒîÓãnß º7>ƒú,‡MDWªƒ˜û7*‡Œp[?‡~ÞT³ì5ÌÏr«òQ⦭J]σ2¶q=é/ßÇd¬£èÈÂÏçÕ,[6†ÎÝ3·óKàwKeãÔ}†Ôýý&à”šeÞ@³ Ó õóš^Aõrê5­÷.5VR·ã™ËzXNÌŽW·­ÜÙíTw-Û¿îmJñrT šß$S¬[NÑ©T_3–¯ÈXG·õÀŸUüíÛäu+mšÌŒ¼™Ãë†u¡Éõü÷äAþƒt¶Ò ®ç!&XfÁ™Ž‹ˆ®ëUÖÏÎ\W1û«édeϤþó£ÎZ¢[vÇûÖŸaá€n©TÿBú=­Êšl*÷¾(un: ô‰…ŸW™“UêÚ·SÅïO%®ÙU^EóvK’:ž¼'bý– €ÇŽhŸ4\Gg0šãæoG´OO—0w,œ‘±ü©°½.°pךú¯ËlûGëØFtḵšŠuêžX³|Ù˜U¿ÌÜÖâæ¦»þŤ®XR§XrÆ+[JXRëùÍOEý Q–mpù£_•Ôÿ雨?.©S,wËÜöNÌ?fX89¾”oãö™Û¨:Öˆ·(Ë<:s;ÿR±Ô²KHgùç~Æž~˜Q¿®ëß73ÛQ×EäóäeŒ¿²Pçf`Ÿšå_\±½‡fl«¬«å õ³1ÛXVªf(ìvpeͺ΢ٸ¥kˆ×ärªg¾­Sì\,ÇeÔ=¤¢îkkê½°¢^§œ˜Ùö7–ÔíîÞxXÅ6^Ÿ±þÕ”¿ge³îQ±Odlgå]×~I:¸z@Å6;å½Û†ä}:±žëÉÏŠ|\b=OÈ\Ç*bŒ¨N½›hž1yCU[€»g׺¯$oB›Ï$Ú—šL§ã¾‰ú3Ôw¡<‚ù³„~_.îR³IR.¢þC°iù!q¥Åã@â‹Æ ¥-Œv¦@µk-ð§T±¾ž£¤îéöJên :`± qãü6ò&¥¹†ø‚›£↩n}—ÿJtaû" p ƒa×·ªÙ¤3¯'ºñ=Œør}0‘w*å7¿#Æè*³ÿl›ËžÜw—À;ˆîÝ2öápÒçw¿¯çã‹þ0,ë†| Õ3Öù@bû7YfÇ™yw&ÞÇS(ϾžêÏÇÛYu_.©WVÎþèrõX¢ËÒ‹ˆ,ßUÔ¹…7¥ÏLlc;1ÀÿqD·×‰ Š“‰±×ÊÞë§Vì_U ¯S¾HÌdx¢ûë}ˆñb«†<8¥b;;Çõ?‘÷pèL"£0g<°U¤Ï±bRˆ'¯Õî³ûòGÄûÚ@™!r'fl{'âzå]y·3²>‚f]µïÄü@u§ô€[C\Ï«ŽçÄ{tTÍzÊÞÿÔd:«ˆÉ ÞJÞûÿ;¢kg1hüPâzR¶üÈ{q®Ïüî©r>16`1hs$q¬W=˜øÌl[d¶ â³°êQwîNTÞ>qÎæv…½q½¬ëZܹ¶žLõ+©Ï@;{v÷$‚]·'^Ï¿'Æ¡®ªw Õcß™¸Ö–=\)+ß#‚ëO$®ÓOþŽxȸ)Q§ÛDöì×2¶¹™Kï°’õ@: ·Sn&>ûžÊhÇ —¤©¶–ò/ƒ½–ˈ/9Ý 4BùMY?%w<M¾Ü1)7“ÎBx|I·%–Dæv»KÕàóEBÞl~©r%p¿ŒmAta+v굜Gz”Ôw©Rœé¶7ðŸØíÄMÄ:z÷ÊÇqkZ.$=Y5#ß KYVñÝ€Ó°îëIw¡¼å™­MËÒ³Ð>¨ÇõæœÏßqžEÞ¬Ëuå,ò3M†Ã ÉIDATÚ`½u™ÝžP²Ž~p97ù3D,Õø¸’:ïL,Læv»Kg|·eÔÏú¾ƒ¼™KS3K§ÊgëïJý¸¦Òt‚ªñàºp9×áÜ1/ÌÜ—bùëšužH}Ötn¹‰x •Ê.>k@ÛJ•² ¥êºí–•‹+öaõ™ÅÒt¬AIR…û1˜Š-D£&³piz­ ž8^Ë`ޝ¦ƒ[krBþq‘êÞ¾œøâY\¾*“ â ~/Çfî¸t§ÑüÇvâKwÓ®ü;Šó{ا ÀË©Ÿ ­»kVn©ëÓíÁDv[/ŸKï#ÝE´‰D6V/67uuÙ9™ ý–O'¶ÿ(âµnzœn%2QÖ×ì_Ç‘Äù“S,;fëÕ¬žÖp½ryfû;ö%ºåÇbË-?'T9ã@v<£ÁúOn¸/™uÅuô€«ëÎ_,©ìÞå, ܤÆÖzVƒíË•³õÈ\¾.ê'_¨*qmPç?2ÚÓ­lÒžî\ÎçHÝ8¡Í^ú¿#cÝ{YŒU™¿uåâ33g TæÜ ÊËK¶››qW,;¨~Xù¢ÌulÄÄ iìõ3Ó˜F+•Ýë ÄÍë…X—¦ÃV" ûa¢{Ô³èmÌ”ŽCè}àcM– Dw£fÿqÌŸHéfâýÿ91)ɧi¼úîl¹‘ÝtÑ5m?æŸÛˆlÎsˆÁÒ?EÞwÎù|ãl[¡üuÛ•¹ch5sך\—]ËþžÈN{8qlÌü쨢‡ÀωÀõgèmÖÒ«©ók9sA;³L6õ\bò›Ãˆ×"wöä*×Y…gÿŸºž§¾#l#®eo ΋Ï?I,¿™Þ®çk× â}êÔ¹¾ðûN&PnFíÍ,<·0ÿ³£øÞu®™Ûfÿ¿•8¿v”¬»óÚ1»/MÈãdMô^™!ίîëÎ…ÀofÛpÝì¿Åm¯&ïš´3qM,îgEë˜ øìFL^Qç"hõJ"öQ̋řZ·Î®ïjâ3ïtâ³öÜŒmtœÏ\fYñ˜.ê~Ë_Ãne³¾_4»Þι½¹s«£xÝYMtÝ™8þº½›xÍVçʵÄ{QgAMA± üa cHRMz&€„ú€èu0ê`:˜pœºQ<bKGDÿ‡Ì¿ pHYs € €UäktIMEä ‰J®nIDATxÚí[[lTEžÝ+K—b‹”FbkÅK¥Ö#^ƒ!LH4±_$ÃÅ1˜Ø„HR«&h„@‚%<¨/š«F$@ÕX"A)mITjªåÒ›ÛtËîö÷¡éî93ó_fÏ©íƒÿ˦çÌ÷ýßÎü³3ç;S¥f=€ŒÊ ôU4¿Š( àcE“DG ©Û‘( ïš_Mö0éÛ]A󳌀³Á0s½f[ÀåKÁ д€3ÁósE@ a8Z@“`Î÷ùS<¹0‚€ÂQâ[Ò?Å¿…‘_ýNÝ%„2 ) ”dhævü¨Y0± Š€üxžŸêÞpò«Ô9â&% ¤¤‰(!•Mô¿bŒÄÈ»K £ù‰è -¿êÆoQ$Ô¯‘´"¨òƒ (x?ïH"€€=°£ºòµ`=@<œ.â‰+ÇÕ|»þpŠ è|³Ã_ þ™ƒ€vžö©©–ëø–_ ðàKà†w§>ß[À6ÅÉ‚xcùÔgÅëàCPË‘V'¦›NÜÁµ­s¯äõé7ÙÆÇ#LÛ‚¤³€.ÿ3ÞÖOs­{|Ì0Þè›Y-dšâ< ¸ÜYîýkIÓœ C¤­¥ ᆭ•ÌJºý:ç!  ÂÈ1½ý·tV¹ ˆÓÍLÄs$ wÐIÒÅLÄ•rÒ±éÜUj^+ÙIBpB»²-Ù½)$}?…ÙêØä³ÄyÖË{r{ʱ++%¸6"  P™[RÞ^ÉeLÀàMlÀ.é5Ê lYŒÝ‰5»"€˜õð{Ï?ˆßC(xÉäí§<¯}ø6?¬ØT§ˆ¨ití{¦Ñ]^Ù1²C¦fÁ¯è÷8LB¡ç|Š‘¬šäÀ#ö3‡iˆŽ·÷SJ]çVvè$8ÁíÔÔG_bw0R[ÇàÛì%ÃôŒ–£Ð;å5 ڷѶáÈü„XÀi¢‹ó¢òŸG@mH{ P»‘Ô&¹—’ÔJk@:”ßÄï HŒ5‹¶«PÅôš @D_ƒ Ž½v¡O€À ¬;Lt²–µÏ.ô øœ–ìNqùÒ­eO;"€1çmæÓÄ›Ÿ«ð,‰z¬[–à—õ$U"€2o?,Op¤† «µ ÂXó„[~€ä~ü@‰Ç.ô @Ï_G\Ó\Åê«Ì œZts t¡ÎÚ…Þ ‰};TÑzTjı¢Y¨}©2bl¡oÑÍ%ì uÖ.ô ¸Õhg,º¹„m¡®´ 0 BÛ¢›K˜ uÖ.ôР¥­üyi´Ý¢‘gìBÿËHbÑÍ%ô…úC‹ß BzÑÍ%ü uæt¡G€çø ¿èæÞ…úQ€Yކd`B™%tEXtdate:create2020-07-24T11:10:15+00:00Y*ùó%tEXtdate:modify2020-07-24T11:10:15+00:00(wAOtEXtSoftwarewww.inkscape.org›î<IEND®B`‚././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1665611335.957255 agordejo-0.4.2/desktop/images/16x16.png0000644000175000017500000000070314321633110016246 0ustar00nilsnils‰PNG  IHDR:˜ ½gAMA± üa cHRMz&€„ú€èu0ê`:˜pœºQ<bKGDÿ‡Ì¿ pHYs00–ìFytIMEä wŽz8‘IDATÓeÏ=jBa…á‡+"ˆ±p–ºíMs]B¶Ä}hã*¬ÔBÄpv‹ÜÏœj8ðžy‡·I~ʺHTØ•`Ç+±ýE}¬Öøx9ã3™ádI÷šÜz¬¢*$¥$õ22RJƒ{K9«uÇæ¿öFÕxke‹ö×áôÝi¤'L÷Ir¬™¾?ÿh6ý¡”U¦%tEXtdate:create2020-07-24T11:10:14+00:00ÿ]òG%tEXtdate:modify2020-07-24T11:10:14+00:00ŽJûtEXtSoftwarewww.inkscape.org›î<IEND®B`‚././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1665611335.957255 agordejo-0.4.2/desktop/images/256x256.png0000644000175000017500000000763214321633110016432 0ustar00nilsnils‰PNG  IHDRy÷ºgAMA± üa cHRMz&€„ú€èu0ê`:˜pœºQ<bKGDÿ‡Ì¿ pHYspto:tIMEä G[hIDATxÚí]kpUÕ^7‰Á"`ÅHyˆ õQ_µ£ÔZ°:SÚmœ¶¶>ªFVêèëÔuÔD¡3üh5Ö¶êP)bÁ·"VC€^"È yÝÝÁx“œsözísN.÷û•Ü{ö^ßúîÚkï³Ï>{ó0"|?júðÈš¯ŽÚ)ƒIñÖ¢ö¨Èk ñ_"÷:6ŠŠ ø$j÷Å„DŸ¤²ÈŒ÷ÿ& ë õ!:H(–E@u üS#)- jçå,²ˆLÇ! YÈ’à°ú¨*`!K‚ŸÅÂ8°[PX$@øI°ä@ÔN§c“ ? ÿÐð³ [€xµ>Ÿ¬\ƒñêø|Žy¸½@gQKÔ.÷DA2—å?76ÇÌhÙÂ+Ç n9͈+@ÜR›QV¦¹ŒiÌ^ ¥¨3j‡{#7YÀñŸ5±ó:׳Š1ˆ_ àrÊ À3¿Èåµ9•Ò·–Äœ8ïZéM†üÌܬVW=ç­1žohq²×ì¦UÙ›á½6§Ö~w"”<¤U‹O­NàüŸÀ­)ÕÆbiä,È€œJyÉŠ“Šußz´º[tªœ + n×!pÜ0¿\¤`Ó#Qf'pý4F¡k§óŒÑ8†O²Š1†Î1”¸×œGÎa£q¤Ý ìg-è;£šÙ¥µN®åÛK`I¼àM‰Wr»ôO³Š‘B€&«ðupõ 1€“ ž`ûðå‚ÅÒ}<0J Àˆû…H,IIÐ7’ÙŒ©Mò¶M¦"St(¾––ëèþÃS"ÿ!Ÿ‘“Û“`´€‚é=˜ú=z O’ô(šàír*™ËÓq”Ÿ.à4ÛãT)O½ãÙT&cï‚û¢ó8©&9ºn'Ló5^‡Êͧ–ØÐ†¿–Âp#¡Þ.TÿAA€§×PK´¦Æ)0Æs¹[|}‰=¿¡—!0¥Àèyw´é˜}^†ÀÔqÀó+„þ¿ýWF!S|Æd=­™Ø&êÚYcG;éšê8\Ö=Å)Õ ÖÄÖ$áb´Xïñ\(Ú)€ÝƒyFßwÌ)ñä/yåàžC¼rx®¸ÏE_XÎöÿÍEÌ‚x®!·s×´ÝÆÝñ5^ÔV2 >ÉÛ (³·ø¡=ùl °†uS¸sBÛ䞯 ýÇG€``ó¯XÅfòýdzŠY÷âRF¡×^XD³ G¸›žÛî’t¢5²›èk~㜄Ÿ Sƒ-  f­ÀŽ ¢“Ì6â~Z|Ü*òZf ”ÉNrC¯åA }Wìå%¤ËÿóŠÐ–/Zñû¢wSV¿¶Š2 …oh[(a- àñEÞ7ž%e[ÐwÁÛ8…{b"òv+@«ø½f€ëÑ0—G¥#÷H-yjÚ ÿDúÏ7öÁZœØ ²wÒͨËZÄ/‘cPÙ;iîÝèy‚3TŒqQÉ_ç“Ô’iö2鞘¦›äOyàšÐ´†Qª4âã«©—•,%i ðŽ+û‹3‚—ezá]”È$¨¶ìöy– Þªe Ç)€Þ^â¯úÚ,{yœÎ9t,=e;ÕÑ8£r€Ò®§ðR€í SL‚òei‘ôµsx”¦¡ÝzIPu7ýþ{È>T§iÅ'€îy Oø=ðÙT¡jÅ: |ó`ÿÅdkL¸@•˜ßû½ü÷’½q¡Zì”OÐôÄp¯aj£l¾(ìÔJ‚[dSÔ}ñùo=>|h—²•fÜm5"þ¡Ì oM#ë&Ýzáe­Ð?S¦ãÎ>¤îlW·‚áÀÁ©Boý­×‘.(dóF4ñúÜzo«(ÞJÑ ”z# [÷ÅÌ6îqa15Ž`µ r÷qš‰jý ð±Õ7Tps®Vz42 ’9FG'«½ý|÷Ÿ~Ý s{¾øå‡î ¶6ÕöÆu:9`„#zPvÔ€øY¸FªpPkJ¼r?2ƳÖI/h°9‡à-Wô.McRG+øàm«ˆ$èðtÉwŸ€gÞtgÃÝ¿pÇJö›ÈE­,Üó€ú¹p¿Âñ2"îÖê ä.T9bÉC­`_(¹ë4— ]c—å& ±P2Ž'ìâaeo Ž'ìâaeŸkÖ$ØYÔµ$ƒ{{ÜÒ¯ý‡ÛÔ¸U€þÝìü³Ø*è߀ÿ1/€­82¨3jdÈMúo‹€š~î?tZ^>³ Ðßs Õ›ý=X=È `)žñMÀÒ 48 õA>Øzþ뿵ÐÿS€Í‡¬Á…3¡ û`I‚CFM_Žâ7-IpGø× )ÀâE°™,^d# °è1½@?ŸÿÇ7ùO÷›2Â8²9àË@2£û(@fäÀ`?²T0øFRÅ·»7ÉþËGš®^È)ýÜÔ—97“ëÒY «\»¨kó×Õ váCžÏ¸eujU÷1Ù‹Õ6 ðƳ<¨ûŸ‘P0'ýâæyôs$˜Íàj‡”¦míelg©³%ÉA[— àj;À×Þð0·òbgöNa PïŠNIE‡§ÁΪ“\™ÜÏàu7\Ž+;èk2Y.;•ÈoøY €wÔ› SªMj9gSÚ1Ÿ#À­ˆœ¹Èذl¢»·q¸TÆÀrÌwmÌ=õpC€T±2‰Déç÷1f¿úèxpÊÇT€”Ë0¸ð=¤ûÆóÑåÊÖ·ÓxU•@Ú¸‡Å£Tí/ñÀÿvXs2 `Îú›ˆ½éëTGÇÞøý?Ô³>íSÚ¯;GÇ?¢7s´lOø7Ë}cŒy]ĹdÚ•ÆdC}ƽ8tV)½S6 *€ÎtXÞ-ûîcLÃòasž¨¡  ²©Ï·p{ÛcãuT¼·. è¦ÄÇ,úï$êc—,“okàî7H-,oQøù»ÐVq‚”ÎÔ&p†Ì^¢³™û¤£ã1Dšd‡e^ðŽªûÆó¿ËDŒr¼wsõ`¥ÄØ) »x‘‘Z$ÚÜøšä[Ê/ktà¾1Æ.l:ý'š3Ù†¦á c{)›×,šS˜fÆ/uè¾1Ƭ`A Siðæg‡T´×è¬âmq;œ$À>މœÒ½ÎÝ7Ƙ¼Ñ±'9?8g$^¹Æ„… œ­Vx àÓÝÓgCFT-ç¶N:Æýk1} æí“Ô;ÂòÚ›Bs`úzòèØÏ'Ï£=¦KÌØZô‰Ïn¡V/ñlÞ¤HêžoÝ®ÈV‘]yMËú@Ùãÿd'ã^R‹F˜ÖáXŒ®ô¸2ÔIÎÐD{sä“Ñ9pÊšJñº¬EŽ=½òÙ Ž[²lB¤îŒx;ïäã•W`¡j,ž×iô‰ö…¨Ññd¯&à)@b¨™Sº'j¿ÓP_†Ø“0ßãóÑ®°oØ.Ö_k'í±6Ã;XsàiUËÕžÙ(aüÒÅ£­x|æ)€% Ëk©:ÃÀôõEÁWxûåN×V3­ÎQaWðãÔïz4O‚®ž÷VÔ^âƒK¸F ô—±Dô¨3 ¤ª†û²Oô­õà}¿ ‚–øÅMþ‹ WöÀ+ úåÀ)Wê¯ßÒÇÀ«gø|åá™—Þ½àØW5žQ†‚1‹^;Ûó ϰP#úJòˆ]@ÔŒÏ@I±ÈŒ¾o [€Ìèû&&@PDÄ% 3ú¾ÑßDÍølh+ÉvQ3>ê%@?b uïè'@l²+@£îõ&0jÆgE[I^¿ ŒšñYQW’WO€¸dGÛ?1#ï n²£íŸXCÛ?Ú»€¨Ÿe%yõ]@Ԍ϶’¼vÄ ÊÒN€ØäBÙC1B#Ï .¹Pöò. jÆçDWI^{5ãs¢¬$¯ºÃ%])'@lr£ë£X‚C×GÊM`ԌϪ’¼r5ã ÐU’×M€¸˜ ê¥˜á‘Ç 7&¨z)V€ðPõ’ê. ¥,j† ©$¯» ˆšñF4mWL5b `†¦ŸT ¶fhú)V€‰ ÀÑô“æ. jÆ¢¨$¯º ˆšñ†h*Ék&@\LQô”f@¤QsPô ¾T[n»@Íñx¨™·ÏBO! Š@³ ŽÐÅËNYÝYo=ˆžE7†ïÒI³ <^üéþ³èÌï 'ÑÝÔ†RL€{À _'¢ë¦ §Ñ½v©  haO"*~£  =_)ÚÞ௾ˆˆ.ý*z"ÝÐóU!W€òßù‡£§Ò•|¬Ê¯<ðÀɃ™ËïGO¥+z¯Ðémwž£e“!“VŸzð¦õ²ѳéÂÎ:ã(nCkŠŸè‘ö¿Ã@Í[z Z píÔÿ3ñZôtº æ­‚­ƒîíô¿÷‰žPgbæ‘ÎÿÀ‡Ðꌚ·ÔšÀÀ4ã'¿Ø%õSW-GÏ©#ZJòzM`Xšñ=v5¼hA/ô¤:¢¦$¯–aµ7\ÒíŸ.¼=©NhùK-‚j†Ü“æï==­Žhù«0à»ÒücÙÃèiu$ï ¤%àò/¦ýç/^…žX´ü¥µ I3¾ç«¥ÿæ÷¼žÛ)””äÕv!iÆWfˆ?¯DO­-%y­¨zwÆ?ÝÐVJÓJ€€Z€ªþÿÔ' >PÉc…W>üù,ü·£§w %i5CßÔ±''%ëÆeûó– ¡Ü±úwQ´šÀ}¡ÄŸ¾™5þ4æfôO²Wç¼H¥楠wæøÀÝïBOñ$:M€RÓT÷ÍñÒï¡§xŸ)%@(›€~:çG>ýIô$ß!V~zßàCÕ§¡§ÙF>U€”Òm­\Ì9ßàC£ç §ÙÆF•ý™Î6pûh [r2²¶ÉÇŽ_Tž)mWèG•¶´ Œâo¶P( â5£øÔ Ã~ì_ÐS%"%¯P(}Ìø£ßWù)6±ðrç»?:òvôd‰”¼¦Ò¡î‹í݉‹”E¼Ó¡¡$¯Ó¡oµ½/y=]RR’WI€V€Ï|Âêãù,z¤ã7• ìó]Ë/<–ëG4üV(àÛ£,¿0ünË/ á7•&pÔë –då¼õ½m¿Ò<ž·£^B¥ lÜ©0Hv~`ê¹~ˆìë‡åÇÐH¼fü>äð¥+¾€ž¶Æ±J*  0FVúÙv€m<ÚßékŒ(xN#à›€yܾ6t.zâ ž+„ pÁŽ_¼ñbðÌcàÀý臞O€û@…giàýòcdcæ•Î_½l&vêäß PHp(›ïñåGb'/ï;…·÷Ÿåñå3ïõø2ò¾Ëû 0áë^_¿+&+€/m’î/€Š äCh©" ïH¸3*&P#.µ(ÿcP½É³øR Ü|†ï%œ¿h@ýÙë+ümôŽ?•߇4@Ü{ò €l&ýÃE¾ö^ âÞËëà‘(^ȃ.×Lõ¿Mºg‚¸÷Ä›@ fü :&1¨ƒcßBÙ ­$/ß5ãæès7Ùq%yñÀµ“¿Ìv©/_³BÚâ kºK¸S´@G¾! ÒþËß FÀ 01iÿ‰7ÃÞ¶ Cêp^îð8Ãv‹^^¼ <ò[zIwÊP¢R{„•䥵dpçK(1áGÃó4Þê@‰J {P:@›€JþçyÇW`Lö`~V€¡ovÎu{»À—dWf|•Ä;=ý0‡È ¿X'¼ ÄhÆðy™çù?þÂQ%yém ¤(ùÐûß·ǘY '¤¸yœÿ5Ò‚õa&Àˆ»Ä.ý­Q{ˆ% JîtŸ>ˆ>PÖ‡²M B3þ£OK^}ú“ê‰*É 7ÍøÙ“žb²Jò² hŒ$Ü}›¾I¢^”Mý`¤´ÚÃíæ's!êÅ|« ¤Ïù.}BݦX̹ÚTÀcˆOi%êEÑ]€ºf|éF…½süQ]«$•äewêšñwh,Ðêb¢Jò¢  Ýœ{«Ê0·i¿ï,éGÑÐn”6éêb’~̧ `) àÎG>£k˜¤E›À!ª¯Ôõ©¥5ÔîqG4-²WìÒ¢Mà>ÝW*­%Üþ-UËÞT’—L]Íø17)ö ©GÒ#ØH&€n à àNÉU‘ô¤d¨n¾ðaÍÑèŠÏkŽ+@N%ÜyLSL ™@U3ÞQÀ¡ßVLPI^p¨©ÁZõ·š/ý›Þ`bJò’Û@ÅÀ]ÀUQ)9_ &€bø¥+õÆ:ÅåŠbr¾Ì‹ zyÿájCÅ •û|$Ü¢'& çK¹&PO3~ÂÐN-“×*$¦$/تiÆûJ¸ÃyYvä”äå,Pk¼%Ü™ü­‘ļ)—Z-ÀÀ•JÇC\‡‘æB̛ɯ ’î Òó¦\xŽŽfø¤ÕÀÃ܉Zß¿ZeœsvÈ\·H,û«h†¯â9Þ™W§ªœ†]ÔP&s]±%@I3žIÀI_SFì°%¹ºp'}Ge˜l< ÓƒHùS,t6l’î(‰ Hù3Ù€QÀ¯¨ˆ HùS¬ ¬ ÞãeÎ#áÙ0±Y~rŠ5*šñךp RJòR  Ñ(þ—ûÎVDÈ£R  Ñ<2@aÊ4ú@!&8.ÿ’ü†hˆ $,‰.S}7#+E}äÇZ„v-eÇ$ÑÆoÿU~ 3~÷iù1J%~ôú-@E3~ä&é¡ iºà5…QD”䥶*·v"ŸèÈýñò©PèÜ~DZWÕŒ­:'Ëø4É€Nܨ2L.*þ©2L¬ÝyöW:ãdåWÔGƧ2M šfüðZ¹³á 96þuD”ä…š@5ÍøÝø»Áó”â/¤$/“zo…=†“§ocS•ÚP"¦Ê$€Þ[aÍ7è05[¯$’ñ«H(Ÿ¾r±îx§X¼\w< ¿æA ºí ò€m¾CyÀ¤TuÍø·îV°;…Ô͈ÄëvÛ@}Íøâ¿NÑ’hÍ­Þ§àW’Ùêßm½A[ˆ¨u¶zü%<+‘ÅèW~¬>äOþªo¦€gó£ݾOyÀw¬Œ #êñ¸CPÅ##žhšñDÅ+ß«9Ü+Sõ» %y‰& OD­7hödˆ®“D”äôˆÆš…Šƒýð%Œ‘ü¾HD @Dt‡Þ}™ýšjá÷mþT:¬×Þ¢qúA:ø}+ÐŽÞ®â‹4¶<ÿAV]ŽúýqôVæ ¼¨®ßÎ…kT„CZ.]‡²]I^` ®ßÎÆÿTfÁ:˜…üJòü €|Nûn'´ Ï ±{—?P›"¢ÆÛùfÐBvïæW ÿ‘Jû/?GÈî]þ&PW3¾+ã× ÷Í“t5±»À­$ÏßþÚT-<@4þô&·{Ù뢹²ïjîEŸNÍݰ'²$":&ÛVÛ| ßÖ¥%’ç5<»m·ó®UÊýÀ™4¡WUÍøôl‘;³é{øS©¸•ä¹·ššñ™(­[ß5î(Ú6n%yöm ¼ ¢¦›….\@ü¹=Ìø€ˆ~÷¤Èeÿï·hÈØ=œ€¨Râä¾ãø(ø Fl{Hà¢óëÑf»‡™›À}uߘÏDïõçq_òõñ  À«$ÏÝÖ…:^Á~ÉÙaÄŸ[Iž9ÂXˆèéÿe¾àÒߣM: ¯™ ˆMUðnÙšnBt ^çkàx@EÀ^37Jšñ&”¬gT-Ù:AçHxX•ä™›ÀûT]‘ÖnDn,8²óçÕx€û— /žù5Û¥~ý´1àý½7Âiˆˆ¾q„éBÇnE›Ò V/çsìæz|ëžhS:p„³ $"¢Gy\U/ý ©%¬^Îë`¸râEfXŸ»eMÍxVü’á"¿P”0‚UIž5Âjˆˆnöß25†Õñú™5[ˆhï=Þ—˜»mD78ýœç€ÿ›çj MèN¬æøöÚ’F„ZZjµ=a€§˜ÀOÿŒ6 5Œ'âqþ¤¢oÍ™uÝ¿|x¬ö‘ðFð)ɳþ` @žbwNOçÐÜuÜðCôä3Ì‹ïRœ `HDÔ:ÛõتԵÍèɧ‡ÑÓPèåŸ8~! `£§›@5ÍxkÊ7vùÚ±ˆ#áMàS’çlÕ4ã­9p—Ó×î 5þœJòŒ ê @D?zÑáK¯þ=íÌðùš1í‰ÜÄt,áóuaTZó_Ö_Y¸=é,ðùš± ÔÔŒ·f`Ý™v_Ø?V[†Ê6%yÆ&ð@Èñ§ƒ·[~áÖãO{ؽáK€W"úo;¡ç—¡'œ6u^¾¸$"JͶ¹«×r-îÐ{#ؼ]0ÀNLà‰µè鿀ÑÛ)&.Cû$e{ŒmÙ;=Ù\\Î5¾  ®oÍá9ƽ¥=Ù\0*É3¥’ìÍ<I¬—¿ ªн&ÏøÃ%Œ®$"M‘½‰õ)¸üÍõ[F3Þž?^ë˯J@À¦$Ïö[F3ÞžŠ\óðhˆo·¤ƒÉã\ „M}gGÖ?ïº=AS˜<^h —˜ÀM\ç ‰Ãæqž;JïFûÜßg1ãiôäÌÍs'iÔŒ·÷ÜÆÓ2ýéÄE¼'1K£$ϵ jÆ[³íáŒz(9ñçR’gJ€Ä´DDó·gøÃN 1x|^ˆ Ðt}†?ÌAʘ  1»@""zziÚ~jzbVðøœ© ÄjÆ[3rSšªéÂíöW¢$ÏÔ‚5ã­Ù9?Í?>˜¬ø³)Ésì&ŸE;Ö’ºn6l9Íÿ²ºü‰ã>OHV @iÅB’0ƒÅë< ¨M¥øMH’f°x½@+@71c· 'dO8 Íxkvß×éïÝž=Lú, Ä6´+\è¹¾ƒõ½ÑÓqa{(M`òZêòðwh’fpø½p€^høåŸÐ“q"˜H`HÔAL 1 ß ¸´‹ Ì OÀ¿süŠf¼5=_¹˜ˆ¨æ’¤ÎÿˆwïÊò[@(šñÖ´õ©;ÿzÿkp$@BW"úËO‰ègÏ£§á ƒç9 ¡= Ñ­ 6Çƒç »Ð[wӷ”0‚Åóþw“F¢ýàAñY^±qŽÿ@†]À¡Éx›.):0À÷ K@Pšñ…ïp €vCãï{†Hp˜xü}+@¢ñ÷=C884Íð¢Ü×÷ M`pšñ…„¿’¼Ä‰·÷c$› ö€H¼½+@²ñö¾÷. ¥_Ú …Lic¯ïûï¶Åø#iò}£Õ;â €Å×ÿ1<â&‹¯ÿcH8¾þ÷Ý4õ Xa·ðT’÷Þ„«_ ´xní›±@ã߈-ÏÄ tb(p<#๠80mdŸO |w±àñÓìõL€Øàñ‹A¬‰Ç/1_ üšÀTyÚüHÿƒbçžMà®´õ:´ÛçÛ~ W€ðŠBL€äL€¸ ¯(Ä |¼¢àµ HŠf|žã£$ï· HŠf|žã¥$ï•qŸ8ÄÈ` 7aà‡XòŸ8øì’¤Ÿ×êëúU¯]ÀÆÿ0ðQ’÷J´á‘wðˆ„OÄ <"+@>+@ƒ©IÓŒÏc<”ä=`=ÚìÈ)Ü׈+@8¸ÇÂ#b±8î±p¿œê߈6;r’¾‡ ÷¸üZŒ8ÙáúM÷ˆ+@H8G#&@~H€¸  çhÄ 8GÃyp¼_R5·óW%y÷]@b5ãó“æÍŽ_tN€Ø„…k<œ ¶aáXò„X ×x¸î¢f|`8*É;ï¢f|`¸*É;'ÚàH#Rð P‚žŽM€käɧÏÝ_Õ= œ»ò”yqHtÑÌ¿§R©}~Ú‹PîGrL€=hs9˜¼êkÖ|=ÞÐL€§ÐÖú3lQk»=KG¡§ãÏÓN àØ$þ6PéœÚY£›±i¾óÖ¡à§ ðïhk=™¾½›I»gz¹_Ö\&¢­õâ’åiZý^ôļ˜¤˜Í^b…`Ê«š3˜Õ²hzr”6[ÅÐ+\Ÿ>€^³Ö8×íÁš ¨×K€_£mufÚÆ¦Õ=Eg~ã’n»€¤nÆ,{æ‚\Yòì…èi:â— ð´­NôûO#ëÞ^8=U'>ëRÜà<´­ÏÜklßþDÞ>_-Ž%Ð=S_´2qÓÇж§Ç1­xmª5Ã;Þ÷5cé»Ð“¶æU‡pj“ö0@ŸÎ÷}͘QWÕ=qK\ââ” ÛL¯qºÑ_RY—°»ÃnqqXµ>N|ÁÁ“¼ô>ôôm¸Úa pJ€³Ñ–š3¨Êåi;­‹†¢M0g˜RìGjL¯Š¯ð§R©Ô‘¹§¡Í0fŸNüm§)Ój¼ÃŸJ¥R[swx¹µmNM`BzÀóž|f<Ë…Î]ò§ hcÌpˆŒK$b8`þ†O°]ìCkž6È§ÈØ—Ä÷£íÌMñÌ7Yª;*ÜÅùÔ¸Ì~ pH€Öh;srå:æð§R©TíÇÑf夿õýNrH€×ÑfæbÄ"ð§R©ÔÒw£MËÅNëpèïûÌ­Ÿ%téµUehó²c›|K€¢ÏÕΓ۷—TÖ]ã'·-ŒJ„¼ ˜´bÉHÑÎZøÒeh#³àë•ð"´•9ka‹Ðêß‘VáóábëÀ>Þõ¹Ù^‡ŸJ¥RGƒ½;Üû„|„ºLߪþT*•Ú9mnlo~;ôa&ÀØ?,­8܈Åχ¹ZGÇ>BÜ ¬Ú }—æÊµ‹ÎD›ëèäCèyÍæJýû´Å³êæ„w¾ŒCtl—¿àî†}h½ââß™:¾ß›˜mÛX'À‘Àn„Œ^ *•J1ýâÌFq£eX‡3,ÍøÓçnÄ>­1m]X‡LY+É['@H= ì}_3zUn ê5"ÛøØW´…íL^¹dzD4¨ú¥ËÑshÇ6>É­g/|1”g¶'®Xzz'±e×Èæ·¤â0´ùëÊÑP™bÙÚ&@ ’ñiyB³+׈입³^‚8"vÜSËÂ{ssøâ¿NEÏȺ °M€Z€òª a¾›6uU‡LYFÈ6à›€ž×l® iÛÕ‘âYusà?–[GÈn¥›6ïÃÐk}꧃=4Õ®°L€l¯;{ß׌œçPÉrºÕSQ¶KÀkG¦ÍÝ„·ô¦­…Þ>ºÃêã– lŠgn›_`ß¶‹‘eà6SV. Å6dPõËWÀ·‹QB*À°E/&ë$çK–ÙÊà P:§Îþ'43jæc™²‹‘p$F3~ú㣣2°çŽŸä­”ä-…#šñ—,_6JT†-F,\vJòv  ß ª¶SþLY8dÊ*Jv  ÝôªØì}_3ŠfmU— °ŠRÐ 0mmuPÜ9qú<íÇ@u 8/÷áþÉà\e «(Yí45ãÜ~S2îûÑü“»ö© f£$o· ÐÓŒ/ž¹ÿÃ*#=¯Ù¬wwØJIÞ*ÔZ€¾º8gÙ(¯Ö{ŒÅ&Nv@gþÃ=ÿ‘T÷”Ö!S6q ¯ô™[Ÿ¼û¾f̨Ց °‰“U8Xþ˜è¢Ï>Ì#ö¼qÏäß­+7S‘M¼1L|ê«zÉF†W*W‰ñÆY¦Ÿ´Úˆ¯ƒ«BzÉJˆKÿ"È”E¤J€äß÷5£ès›¤™JÙMÀ´¿U~ '§Ï«—=dÊ"R¡T€óŸ|fœàåCcÄâç$™²ˆ”EØÒ¯IjÂç|#¼ãv„iýÙ-ÿºvi£éZjÓn•ŠñÌÍ·$Mñ¬ÍbMÛ̧a~U©વ‹!ÇÁÎÀêR‡Û™ÇÊ"dZ€‹DWð9ÿKeŽ·4¸îŸ „$RB8ä M/ óX™ïšúµ0ÏòÒêÈOiðjåJæ+öh,5û Å.`süÏ^¸:Æ¿I+–0ÿÖRkúIóà]J*jÃ_Q…ÿî°q´ÌƒÀÚN¯-˜û¾fô™·…õ)ãhA*ÀØ?. îÈi8Ã?w1ßÕB®åU®æ³4¸r Ÿq´Œwñ̬çWï+Ìû~F4ÌìÏ•ö™ÅË|À´$E†Ä€ù>És¥ÃÏé&@r„Øaœ÷{ Óx'C ?Ü?ðH˜ÆK¯ÍÜšCžÐ°2e/Ó&0UÞà7¡ÉÕ¡îžÖÜ´ÂïýÝV0nw6xMgØ¢Õ1þ6L|aé(¯ Úeö9ÓðjJçÔæëË>rÌØä'A`1Óði¦£ÎËJ6¥sê|îFL¾¼gy€‡û'¯C¦©åU¯$ù'4SV:KFÌpÐÜ÷¸Ë$z}åþÁ"Ž) Ž|w¾“ï{7ö2ø”é.`³Ó¦­]ãïKßyng¤ßbô1ÃpiÆ,É“CžÐŒYòŒË!Sf13Lû o2÷OÓÖ8”R³˜ %@q¼ïËJ/‡C¦XÀr ˜º*A‡û'ûC¦Ìbf¶ 8Zfs®É°ÑPÌ7–U¾fñéâCw w6šñ}’x¸2°“ 0S’7K‹`zM(*ºyˆÝÝa£¨VÓ1'¾ØÃý“ÁÙ‹ÍV5ŠkTõÒN),&¯4• @æ7<ÿ‘¯óÂÔv%tEXtdate:create2020-07-24T11:10:16+00:00hÂãn%tEXtdate:modify2020-07-24T11:10:16+00:00Ÿ[ÒtEXtSoftwarewww.inkscape.org›î<IEND®B`‚././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1665611335.957255 agordejo-0.4.2/desktop/images/64x64.png0000644000175000017500000000202314321633110016251 0ustar00nilsnils‰PNG  IHDR@@.gAMA± üa cHRMz&€„ú€èu0ê`:˜pœºQ<bKGDÿ‡Ì¿ pHYsÀÀÃ1S‘tIMEä ‰J®áIDATXíVMHTQ>3 Š ÙÐ/D"4ôc-j´q!ÕÂ"ȵE´´6¶*\-’Ä$já®ÚÑ&0#RúqL²2t”#‹f²ñ´˜÷sϹç¼÷dæ¬fÎ÷½ïžûûî;%’˜…ñç â Å0 ýk „V<, (wÜÄT¨@j‰˜È¦CŸÈu!|œSnð&pNÉP+#T.ê&Nä-ú+“ŸÐ·`ï öÅæ`â:½kn†¸H<8¹MZR9‰¼ÜªIDœJ²lZøÅ_­+ˆˆx™eã šÀ0ï@óÛY~Dk#÷°§x,*n¸HX[Z(@yÆö`åøºŠ û5ÖZ—\#È:E`–°vþ5_’]›“LNì)¹ªž‘ïŹ Ä›¶²fý ÕE•ÓF¶z†^¶8›2Ð3*à‹iLO¬@Îü)VÁIL$?ÒƒL„O’À}ZsPáä³`¬×øÓó†‚&×[¥™Ù¦e€i uÜøãÔÊ¡‚@.aµnÀ­±%‘³^Ù½w/”:zm›(£× k܆ ¶[A‡Í‚Ÿq:) ì-hp JÀ![`“ăGøXÌ×XÊ€˜þ±UÌ»ã¢/0$ó`›’â]ÐÞå÷JÞ½Úã<5,(³‰>ßñ µLg\ô¢ ˆ4²ÔÄåîÀ{¢lc¯J2Tà­bÕÕo}k+pL”ÄxÛ"~oOH`q\tÄ4¸·Æx£¿3îØxM¿q­?´§E¸k \äh²óù°ä»«9å’)ÀfÁXóò˜9Ëf¸&S€V¸ç9Jñ²ž°Ò†7öPŽ¥{µf|c@¬hÿ‰züî4Öˆ/pËKžÄàȶywßm_àœ“Ú=ˆá1¼×aŸ÷ŠâêîžG,ôo€â¸%ÇG[aEž¬O%tEXtdate:create2020-07-24T11:10:15+00:00Y*ùó%tEXtdate:modify2020-07-24T11:10:15+00:00(wAOtEXtSoftwarewww.inkscape.org›î<IEND®B`‚././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1665611335.957255 agordejo-0.4.2/desktop/images/aboutlogo.png0000644000175000017500000001405014321633110017454 0ustar00nilsnils‰PNG  IHDR,©;~È!gAMA± üa cHRMz&€„ú€èu0ê`:˜pœºQ<bKGDÿ‡Ì¿ pHYsŒŒ'±„tIMEä ~86öIDATxÚíiœʇŸYAAÑ`À(q‹‚KŒFD q‰à\‚h4?¼.Qp ¸{5*qÜ@\nTÀˆŠ àŠ,²ïÌ0ÃÀÌ03ÿûáô4ÝÕ}Μ9‡†Ü›zΗSõÖ[]Uó?ÕÝÕoõd‹eÏ“¹¯`ùÿ‰–%¬°,‘`…e‰+,K$XaY"Á Ë VX–H°Â²D‚–%¬°,‘`…e‰+,K$XaY"Á Ë VX–H°Â²D‚–%¬°,‘`…e‰+,K$XaY"Á Ë VX–H°Â²D‚–%¬°,‘`…e‰+,K$XaY"Á Ë VX–H°Â²D‚–%¬°,‘`…e‰+,K$XaY"Á Ë VX–H°Â²D‚–%¬°,‘`…e‰+,K$XaY"Á Ë VX–H°Â²D‚–%¬°,‘`…e‰+,K$XaY"Á Ë VX–H°Â²D‚–%¬°,‘½¯$}9‡¦Ì䱤Jgq,½9‚v4¢ŠbV1Ÿ¹Ì¡:O3ÎäFK²)g3 ùœ( ”Èñˆb ’2 €,’4$J6±‰ÅÌ`£Ïk½Øæ|/qÚј ŒbŠØÊB~ iÕmè–‡jJ\K6MÜïù4`,ŸÒ‘[(FnÛpZMy‘ÙÚ[ðŽæPZ’E9Yħ|ÈŽ=ñËPúutbb==®á‹z•Ïaû[i_g·›3„!´ ±la ãøˆ`—»q+ýÉ ä—ó £XìË{‚!I´¸ŠI çk7ý4WÕ鳑wy|y³ùe’c4”'èÆ·qíðª/ÝÛè2±ìà`Y’GÒÿœ¯úò§zá<×óª:J^¬ u{ªá‘«û´+Aù P¶§üI÷²R×»^KÚk¶ómrÒ~· µL`?ÙSkžRU‚²;u³2ÓSÅžÖð¤;_Ë“õ<Âû®ç·Êˆ[*Sù޲B#õ' ÓÛªöäNóùèC­Lë\õ×Xcا¨±ëqœ¦«&УõßzQ[ùƒ¯ÓômÈ8L×ãzIËÜí:Å=ÚUú)àU¦ÑzFõ¹ïq¿š âãìÐ{jæÖÙB³<¶RR_ ÐËF¯ÞPÃ}-¬×ë-¬™õª¿“O§Æ-÷¸ïc”ãZNP‘›?Îã‘£<%êåZNÕ_mïúf­ßzTèüÉþI ×ëê8^™¤2C:Gz~.O^ë\[;½è×~ZhCOÛó4ÛcÛª£\ËoUaH+YkOëûz «8Á¼üøO#ïÄ)u¹¯Ô§Æ üÁµ<àÉ}ÈçsƒÏãz£Íwyl¹©Ð±´Ì7y$RÇ ýƘ/¾óü,:Åj ¯Ü/ܼ‘Fù·|ýzÚgì³Ýfxþ×¾V^³u<Hºþ\­÷yVëRmµÍWêxÞ­µüÜ×òÍÊóydê3_•êšàÇ´["£ ‹÷Ô»8®gØ.w-ù „….rr·»?¥ò£=¥÷ xoFÙ®LcìÐA©ê"ýu¬ÃÉJÁëȤKö¥Ð—ÎäÚR7QàI}Ç'†½Šη 7o„¯å¯Sîó¨áI_:‡[<©­qÛkù ¤¼àa#=Ôý¶ƒ üÞu#*iåä%J<ßï$Óš@•1JOûÒ FФ/¬ä%’šWð6ý2Ï NŒF ö¥'‡Ô[4(so¤ælŸ}fÀã J}é há~®oÕ²ÖH{Ûº=A?·øÝiŸ”_Wq'Òš NN©Qbw[¤Ï2#PÛÏ`H‰ô…uD¤^‡ÐŒ¹¤€ËŒR¿&ß—žRÓ³ô¦û1ÞIŸkô}AÀ£ÌXRÌáÌ$Zœa¤‹““éF='ÔQ¾ §s ½xޱÌ2æžpúé`Ÿ·2×—ÎçÔ¤Ûïãß]XW’Ügä^k´ûþ8¤¦•Lç+ÏïѰ!>st]jðŸú~HzLéÃê(¿’÷y¾I×oö¹ŠÕ{¨Ï!D)¬Õ Ö»&õ0©—e<Ì_~gcî0O¬«’¨»›/U8|•Ð'œóô¬$|b,7Ò–îB3§åÉãoqè,—JŸCHWXÍ9 ®íF®!Þ£<I¢ö~´&SÆ? Ëu¾TG_ª&áÅnŒ\£Ýáëtêâ"N÷¥w¹§ÞºÙl¤›Ä-Ùœ^<—°åa4ó\#îÉ>‡’®°â_„ÈkÌâ¥'$ÝSaøŒµÊs÷wæ×Ãs7y àE'=ž->{#®p¿› ÍâZbla»qáš:¯šC¿&éqù‰PÏ?Šy´¢8å6Ò™³Ý‹ì<’¥ØH7 J§Ï>¢Öõž_G5CB¯y®céí|öÖ2ÕIïäy£Äwåܼ¥ß}ýµ)d)S¬¢*pÖ!¤ æ,ô}ÜÖN7Ö„:ò -ë9–ôÂ8åjXÂd8©ä…Uf,Þfz–`SésBÒVûÐßùü/=›±¡GîJ"bîSé@'çóžQ¢£»†c.ôå~+¢)G±Ôg=‰g!°rhhÿü|·µÒÓ¸Q9– ÅÒºFÓÏü„¥—8½J^X{ºÏ IOXaóU…ç)W-ÃŒÓX|ïZºrƒXê~¦JÕ.:|`ä÷ô|¯fï Xµó4ïCÃ',RÓ?ð5ãx©æjÖùrúp^½FÓ?gV”‚­;œSrhRIsëîó.#¢5iÒVØø¨uï-Ü‘¤w-W&uü_ñs>3®N£¹/í½å/wNÌï÷;§«7~ª'ŽÝœ‹²€2ã. 7Æ×ôò§ö¥&Æ]H¨õªfóð_|Ï:gÿ:Æë-㢤î>¿ä-D´Bf^„ìP£Ð’™ú_ç³NN]éÈ*[;äÜв¿ð…×Ò*´lc­vìÁ¨ëu<çXúC6Êã5ØgÙÏÍÔiOO¾Vxleêæ±åøfIêãXÎ ´ï¯í†íJ×v€oÈdàk?×ê¨ÑŽínÃr—ÞûO_~_·¶\ãä´Þoàó©ÑÅu´âQ·F3h¹D'9¶¡¯…j.„ÑBOîbíïÑ¿InÏÚxöñÔ|›ÂºÆ×ö†šë³®t¥ÕгiEªŠ3Eìa·ĸ'Prhœ’×JæúÂw«}§„xÛ6*ÜÉþ.ò\Oéa߆ IºÎ÷õo¨Ò›ú«FúvÎTé2GÏ áe¶óƒz6ÔÏ«DSõ¡oÃ,ß<>8d—Ž$­×d½¦Y>ÏoSXc¼Ÿq½»Ku›îw¯Ô$©R¿OGVé ë.…SáLɵŸBÏ?£uö0J,öÍ{åqêùÒ-sž6)Ët¦qÄz ô4]ËO:ÑWþî¸%c³Ýi!–¯xyÙ¢|×dèí¤übLq|.6òÏ Œr¾Ù¶›uLz²J/æ=Þ:T.ûÒ÷Çy VƒyÃÜÊó½IÜçb»W‹ß¤3#¡±Ûß™   ïù ã(ƇÞܯä/n-ïGŒšN‰óf„¶ÑŒ ‡Æà,VóDZ…-ÎèbÄlÚPÂî7*Ĩ¢ØÊV0Õò*3=}ŽQÁ÷˜^jT›ùr‹âÔý%ýiÆéô  MiÄ6Öñ 3êùòƒ¸¤þî†|JÌw«éÊv ›¹tOPKÇzÄ+Y’ã¾­%(ö©Ÿ Oè{€˜24¡¬R˜·$Â&ygß4"õSa]ñT70Ž"†×YËäÖLºÐ£èΉßbrO…ãÉÛΔ„»è¡o`wd½I]XuÍ5¹<ΚÖQ*ž<Ûs§Ñ§^¯ÿéÌàjß™äA²ç¦uñ7¤/«(g,’:·‡É³'#émß5Xo–]}{—çp_`×PV–ÐI—:±T;Ý0èL × =£—U¤«R+ôŒ¾Ö&¡!ªÖ.=©ZÚ ŸÔÜÎŽ+’íaDddž``‚÷¯_‘f'^”ô•ÊÕ&Õòmtbê[i†P?U+OYZ­ÞBwJ:G¨¯jœ£¿én¿X¦›…†i’PbQü¹Z¨¦ºH;•­\mV/µ”Ô[è5 :U±—æ¤çUã–4Lè0I±·Ó n‹‘$'Îëy£ÎÆhw(q$éN¡ýàäýEÒ‹Î÷;Uã)Õ]нåý¡<ý |¡E**P¹~ü¿"¬[BR£‰:0®Os4^÷\Ë#iv⟚£X˜s?}!éV!´J?9b›"t¡¤W´¿ •!t«¤_ }¢]Φ‹ÃÕÚ©mÖé7ÊW¡ްbâ»\ÒµV¡P;IsÕ]Í•«X$þ=B£$ýÒ9j‰Ö ª´M;t¤¹x¨æHz](SŸhûºÚæzEµÚ ÷ІÉ|à¾?«3/p?o²KYÊÉ` ÏÓ“¯xÒ÷¢€®Ô¾åª’EÒX•4äU±ûr¶1,ä\:Ї©¬¢•sw>ˆWy™vîöù\ ¼DkÇ·sK×ÐĹÿkJåÎ_²+/q{œl.ä5r¹=ÍNÔ„eJaWqlÈ?2ù³8ÇØð?K³b* ø-½ØÅ¯Nžl¿~N÷’§[Ïòÿ&H«wÔfJÎ-#œª÷÷§ëýÝ5IßqÓÿ9ýÏfK®•p;pé!ìLàtàÀ)Ô—è:”Dí™z~z#oW2ûïæ¤„óL–“•sýýÞ¶‘’ð·—Fk“^­AzàïéãÙôF¦ôúÒKHÝ:ùÿ®® =4_FJ†Âwä–À_§ýÙÔs0õ³µŠt.z¦ž—~xÓ¾_óš>ú»óý>ô¬KJ¸@ú>è½D™:’¹÷2Óvïdî/SÝLúýí½œè‡Þ÷Ø ¤ßÍÙŽaïûr.½mO·ó?ƒ¯ÇÌ׸Þg­wzÇ©÷÷{çç›À3çÙG6ö <€to6_‚­©Û?‘®õ'‘®õ´¼O1p’Ô¾û’’-ƒz/ðî̱Œ»í§ÿ<„þ J8ø5ð3à8Òq¶:éí<¸Gl8@z(ûðcàR²¦v’‰êÏí¤ïêŸ?"%ǧ' K;Ø#8†ÜLz(>‡4šøôÉv&õŽæœn3ünoÛúK¢_ÇpÉÄ®øð¼è út_à‰ÀcII·¶_®õã\Òµþ‡¤{²Ú^ÌH’¤9|ŠôFyÐvw­_¥ál¼•ôvs˜óPº­"=txý¿ÑïºÀCO׿kg“¦ÂÖj5âS—Û5¤ïï‡÷Òú¤yb÷¶ 8øðdÒè¢ZmDüñõÖoR­7 }TÛ¡}‡(ÛÿBJ¢G«ùÚuÀ€G“FþJ8NƒZøþîÔæE¤imªÏ:¤)4Ó‹Ãöë…À—²E3>’Þ¦¾x&£Üvìó\œVA¬m¶÷yJXHš;JÇüÒK·­2'IšÑÄéÙþ¾½m®“¦P½·ïMÚC‹GÝ=k‘Þ|GKµó€w’ÞøwÉÚ¤©'× Ûj‘’ï5L¾€øã1Šíài?þ­ ú:*íDÒ šRÉÓ™,›!.[¾Öo¢õçÄÚf«å¹àÉÀŸ‰?mµåÀÇ€»å:`’4݉ÿ²³ý}ûê\'Maö$Ïù=ªtà²x)ý9Œjw’V<Û»á±,Ṥ•}£Y©viÊM¤S‰?£Ü޶íûl î‹ôqÔÚå¤ip‹ðŒÓK¢ˆö>ÏÃ7+ˆµÍö²>C[vþøãPªÝ¼™:^ºI1? þKÎö÷íOs4…ùyÎïÀ6…cï‚¥¤úHÑŸ¿šÚoHoœk³ieÉèãÕŽ$nªÊûŒÑ6|»”\nÃG*èߨ¶ËHõ'KNç¥ix5¶Çôy>YA¬m¶¨û€µI ¢ÜÞGŒ£ØÎÞø(JÒÿåfûû¶‚ºj Ön&ß9þ`Ùð«¶ð.Òï}ôg¯Æö³ámv HSƒ—\¢Ûµ¤Ñš¥}iÈxmƒ·’ê|åôÆ ú5êíg”[æG…ú4®í9}ž‡×Wk›m×>CNdtëïÒV‡3ÂVÐÅÕGg}F{Å .[Ø!:ÝŤ7¹¼”غ5µØ 8 x&gs^t“6'šþ®Â iщ#¯ô¥\^p_ãîu¤šm9ß/ɸ-Íl?àà•öuq}Œ³Múü{i5ŠX«({°ˆT×õ—À= î·V H«ºŸ ì‹*dNƒXJ÷WÝeK£Ð]˜y{Ïϼͮy)ù¶{t •«!÷ àÀ㢩ÐÀïHÉä|à/ëI¤Äs®$œç¯Œ%Àÿ’äm&È=ŸíڸϿwv«Qĺ˜4꼄 ï#ÿèß®»7©LÊó¢Q]LÀiÙÕ?ÏO=D;I¢×0žIðE¤©]GëÇÒçïÿy¤)]Q«‚­þJZyíDR"ð² Xf³3) ·}Q`º«GGçôtÒˆ•ñ,àW´7ãã´–¶«¤ßpç×´H ßÚÏŽ“ûzt¡ýÍäZR2õDà÷¤wÆ3ÝšÀáÀ‡I÷²’™j ÄVuóüÔãå-m÷>À£HEÕÇÅšÀ×€§ư 8‹4à4à:RÑõe¤©ù«“ÉØ–”x½/°FH¤Iä¸wO¶Ò‰â«HµÎ~Lz X6ÃßYØ x,©NPÔ¢=ëǯ>ßâ~NoqÛ¥ÝIwî]ŸL*Fþ††ÛYFúLB™‰®œ¿]I#Wö'Âì”ÌÛÓ]õ›€› %Zk\´¨©ÿ+°‡®_Ø×Twߎ&¿™¦è¯ìüð4àÁÅ¢›Ý{î=VÇ¢`ã8’BÃ;xXtšÕùX{¡Fß´U¯íûÀ[Úvm6¾ì´ÿ“HõºŽ®àçÖ ­‚õLàÙä­Ø I ÂÒþxSá}^¼ ø:iÕµ~-&%ußCš&ix3ð_-îc]Rÿþ¤ÑY¢lºAý™T¬þ·À™ÀümJÕ"` Ò(½Çˆ±¤Ußl¸G’ê”-%݇•~èÔm¤kÕqÀH£nœüKH¿›‘~'÷ ¿}©/9w=é»<çJó €“ú~?ÒˆùÈ6óùýýþ®FºÿÙŒôùÜ‘”Èì7!–ËOHßoýØ}òïî@:Kéæì°+HɨÓH ©cìžeP˜ÜGÉ{›;O®ðgwÞ <øÜÇñ¤{[‚ãÔ׿²ŒmîUwœžï5´{žW÷*Ö›8‘F D|–þ <&S?6&»¥Pì¥ê¾Lµøøñ6i_ "kb©pþ­ñOoÿÚ°/ƒX xidUt¿§·w ÑŸÝ€¯’®ƒÑñOo×$ÉeiDǧȻÒv®v:éû{P›:¹‚>LmWÐn½ÆµHäߣÎßßa>SÝxÙdÿî(ïĺ1ð*Ò”Æèã>_[EšÚXzt×c)½<•ôßÔ>¤Å7¢Ïݯ€u2ôGÒˆÛŠø/,Ûüí³@ógÚ?χëMŒ ˆy»“4šª:Û‘Þ|¶Ý‡‹Zˆ}>ÿÑ ÞaÏÓA™ûpê¸1uæ~Íg )‘Ýï©íÍ ú³i´\t¦·#ôi.[ß­ SÛ/öiðR=§è¾ôÚÅ´Wnª½©/ùÓ47ÕæÀ¿‘FC¶ï¥â\}2΢¤¤þ¾ú9¨}(Ÿ|ûy¬M*k}Dú=“¤Y=–ø/+Ûüí¥³@±eÎóÔ=}¬‰µHÓÎJv®"M«hÓ"Ò4Ã6ûqbË}˜®íŸÓÛ*Ò*¢mؘôfºôïÞÔ¶xnKý›Ë§‡Œ·Ö$izæÏ*èÇôöІýšÍÒÔ¬èþõZÓ\Ï"à_HSË£û4Aún͵²í\Ö§®Q€9p=›Gµïä›ÊüŽ–blÒVó²ýÞ¤Jöõ[´óBt)Á}.l©’FÄ›ˆÿ¢²Íß>6Û T_¦Ü¹Î=¨ I7\¥?7×Pvá6oü¾_°O#%ŒJž«¦É™ù¬ ü¦pŸ¦·”¯{¸&i¥ØÈ~ç<Çk’êE÷ejûM†~Íf5R­µè>N/×sR]Õè~M‡eîÛl¶'•ˆîïí$àz¢kH®zÈ ©k$æðÁL}ÄÝH£@KöóÚOx\¸O3µÿh¹’:ìKÄIÙæo?™íªuQö†ù/t³Xð\JOeœn"­ŒYÚ熌w¾öÅBñïL:v%ÏÕQEz–FPU¨O³µ¿’ ™—ôöL±7m¹’¬ëQ¦$À ­ÍQ¶O© äOÀAJHÕ0E|‚TϬ„ÿ)ا¹Z› 8H37rÇüã{N ñ ÛV¹(i1)V²ŸW’·næ\¢G¯žÑz/%uR-oVms·6W=ÒÜ^Oùó=J«¡FÝä>»Dçf°iJSîþ”x›º©ÐzÉóôWÒÑRv®k¡ƒ´_’F6•²SKý´åå¸ õŒ$š Ýª‹(?El¦ÖFÒ(œ YÜ£¥>Nõ  þMom'à>”9æWfŒmmÒª¾Ñça‚Tï±´¶ËfÌÔžT¤gÉbàÇ-õ£ß¶ŒøÕØ%Ufu¬g믕zk¤¿YœIùsýã+à^´[”y¶vh‰ÎÍa7ò¯ ÷¶q%sÌý´§è×tÏho®öžÖ{yW—dŒ}Ø–{šñÛ*èS¯ÝI»Åü¬ m%à • (=òv¦ví@_H*Ý× ¸ÕÈûBêÙãûYÆØš´7dî×|žLù…(Ž(Ò³»Úœ´Úqä¹=e4E-oÅmýµGÎ|Õ¢}‰9׫H«7vÙêÄœþ+yWÖÖ!äí׫ZŽ÷©™ãí§ýš”äŽphŸ1¶Õî ìéc[êÇ -wn åëÍÕÞ’¹S½¥‚þµ™€ƒ˜¤ÀLí-÷ʬœ=_+‘€XJJPçˆùÛ™cûïLq5mm/5Õ&”OJ­ ÌèÒ™ìßgŒm¶÷·ÞKUaÔê©K£Ð@<_彟i[¹aè©å{ñÒ‚ûúågÔ| 8¿ð>{޾´ïž·’¦žkÄ™€S?J®¨æ<_emBZ 2ʤ ºhwàû=r«ÙÍçRàë·×fîÊßÿ‚T -ÊÕ”Ÿ:ÝΤ•ÈK¸¬Ð~Jû iêb D{‰›‹[ÚnmÞH*Ôi3ÚŸòIËۯͿ“Fý6uòŽš.™øšK©ïç§P¾>îäŸ:<¨7‘^ØDYDZ|eQ` *Àœúሪnñ|•õBRAý(kQnU¶œŸ%ð)í½ÀÊ€ýÎæË·ÕÖ¨¾}H e”öÑ€}N÷IâG½غÀ~./°Ë€oF1i!°GKÛÕó7Ýu¤Ñ"Ñ^G»«Ëù치<5ÀÖ&­œ›Kt²RÜ6_°õ,!-¼PÚ7ˆOtžGü=Ç}éæ=½`Ný0¡Ó-÷ÁÏv) €£ƒþ™˜DV¯°ß‹£ö;—Ÿ“jÒåÐÆ úBÒè·ÒuØ.Ž)¼Ï™ÜAþºdƒZ‹2£"§Û¶-zzÑTmM»¿†TmœÚ´[wsW¶ÏõB*çýE ç!×=Â|^OL¶Cö9“'>áú>º;³E}ð!]óY“¸‚˜Nî7šÝ#€¢ƒ¶!ÇïŠõ€wíûã¤BÏ5Yü(Ó¶ÚHÀý1uú¾F=#œÃ3IoÇÛ9ý¦mÇ“gz[÷ki»½Bæã`%éA5ÚËI ò6,oi»5û©øS9¯©á<”˜B7ÒhëÒÎN ØïLn&¦ÖíTSžE…˜€Ó|îsÑ»È:peÔ0ú­§K‹1¼Ø4`¿·_ Øo?ŽÏ´Üß«—,Í1)— ÒJx‘Ðþ¹åÜrà”è &µ¹zõ(ŸÃé¾\Ã&Àó[Úö8Ëž•¤éˆMåLÀÕÔιXÓlÞJÌêð9ëàæðiâÏùkH‰8 pšÓO»ÉóÖ¾ÍI…jkñÚU‘Ó¤º9Ž%ÕªQ®…r{!°]æmöã4âGœMw8pmp O¢ÝQpÑm;1:€Im>XSÒæNàÐè €·´Ýq:—Såxùr?ò•æ¨á<´€Û‚4š3‘Aû͕Ŀ\xCp j‰ 8ÍÇ‘TÝd®}/Vbšƒ¢èÃëHÓ¤#|5h¿ý¸„µ&àŽŽ€vWYVpšËF¤šêžÅÀÎÑAŒ°¨:ýxõM…ôÀùšÀý×8¥qº 2l#×[úÝHu#œHÝž5Ü”H;÷pÑSùÚvUtÔ²ÒkIß'-”i7òß3ã¹ì™+·Š´øÆqÀÁ¤dÛÀCI‹å\ÞB<Ñ ¸¶~÷² àÿ÷=Ÿ®õK}¢ƒP^¹ŠSj4í€YJüHQ´ðÄè æp7Ò›ø¯D2ÍÞÀ½÷ÿÃÀ}÷ë ÛÈ•€‹L2Ÿ¸ï~ ¼'8†»“6OŽ£k®`Šš“Ì]s)p*°{` €G’kQs¿"%×–7·’>¿ç‘^V•NˆE¿œhkÿû;µ´íù\DžÚ·m9“TŸ.z@ÃsHŸGÀi.Öë6Ï_;^L*N_³×F0ƒÈ:’9¨]“Ñ9+€Ã€[2ı˜Tÿ-Êï÷ÝÓÈ3]¸©6¦¼º•Ô3ª(zDͨ©á%Ë#¢!7^t|ˆ´øÍáÀH ?;ù<'pßµ_롎QpO§½š¡ `Ns±þ[·yþò«uñ…éö$8«ÅêÀ³÷?h-™(× ø÷¯%=t¾’4"êE䩳°i†í « 7å5,èáMùpjyvyë5ªŽ"&àÔ%‹§îßk}îì„òq ªæâªnóüå÷X`Ûè útðëè &=Ø8pÿ]IÀ]üX6åÏnl7’FÜ@Ñwi*N"“¥·çî¿_'¯Ža3àÁÔ»`E­jHZÖ<íª«~Kª· 0†mIËWÆ õë‘À&û?=pßý:™4³`íà8žü48ebN³YÜ;:5²5©(í ÑŒš_˜îi¤ß¦^>6xÿ'ï¿_× ŽaðäÀýŸG|­~ü‚gôL‚Gcn €%ÑA`® ×’÷;DZð¿Á1HýxJðþk(å0Ÿ;H/qÇ£ƒ÷¯Œ¢oU¯mHÉu×à>ÑAŒ»ûð÷¯m+>-þ98†žÇî{8#pÿ]³idU”.Ü\Goï½)Ì:Ôqï{et#êwÑ»D­Ö!­äøjàà(Ò‹ƒóHߥבÄ'PÇÉ#÷½’öFñçö‹èHƒb¶ŠBy8N³±~ØhXJuQFÁKá• IDATì;ó]Àlj}Ø{ð^Ò*bQ¶v ÜÿÅÜuJ§æyCpNðþqñ×ʽ€õIS”5¿»G0©+ž]SCR|è*±”T§ò)“ÿÞï½ÐÍ­E¤©¶!6Ùy1ÝYL£–ÕÆ|):5WÃ[@ÕÉúa£Áó˜Ç"R®_+€¯l'œ¾m CtáØȺ$:WÔé~ÕðV|1pÿè :d›è&Àˆ:+:¼ï¸/p*ðnÒˆÀAž7MÀ•}­¿8xÿƒ8‘:’…Œ@y˜€Ól¢ßê+Ïcû“ê©õë'¤QW?n'œ¼&xÿÞW¦4Ö`ñ«ç^¼ÿAœÀ¤=£èZF'Õ(E5×í¢¨À°…ý'HïÕ¾‡ï¿K •,§Ž—&¾l&à4ßàpy ºøÂw&ÿYCn)ðˆÀýG'à. Þ—Ü“øÚŸ]JÀOZµ5š7åýÛ+:R’¡–äí¨9TK,Ò:À¦Á1D6w+ÝX„gÜ/xÿ]ºÖC³)vV‹BÍ™€ÓL;E¡,6Ä¢MmÃ`«x®Ž™ü÷_’n(£´ß5H7 ‘º4¥1Zô 9të­øJêx+~ßè:b1±/#zÎ!~‘žQuu|ço@°m‡ü9§Ÿ–±„øEÚº–€«a1¯%¤ÅÔq&à4“HÎ Žfl楤pýú [ánuŒ‚{itSi»ÿ]RÃÃXWÔ0’êªèTÃMùöøV¼%½”ŠöÛèFÜ_£ }&ÇÙ°#ßMÀ•±”økÆÕÁûT ×zp•à‘`N3—i‹_‹ èH]¶˜Á_øö´ÿ>:S,M,^°ß{ìsº®%t"E¿Y]I÷êÿÔpS¾˜áGœŒ“A¿ËÛr\t#®†ÜѶX¼ ¸2¢G¿ÜÀ€j¸ÖC÷ÕjÈœf2#¦NžOÉ‘¶Õp¡íª'[ø3ßößß'¾& À‹€u ﳆ…ë£èˆQ’S-#ÕÇê’3£˜TÃg­f[ψ‚t-86:ˆWCn£è=”Áï›zLÀ•qèè^î\긗÷Z?LÀi&£>nðªÉ¾ŽÑ¿àúùlÓ ‹/ü™t‘žê:àçY¢if}à……÷}£0ÜCW, ~ÚÔ²àý£–E>¢Ï]í&~:<À À ÑAŒ8p±^ÕàgGý~¼&àw'uÔ¨õZ?LÀi&£ž°ù4pâä¿_¼?0–îÍ`5Ì”l¼nÊ£?kµY |‘´N –ÿʨaÜêÑ´h `à9Àç€ËCÉóÀkvûÖ¥Žz˜]¼Þ×p­_Lªé¬3§éFu¤ÔJRý«¹$¯£Ž·6˜€Ì ‹/@šæ4—£©cJäàµö³1ñµ½™ïŸ ¸áÕpS¾ftY´õó£™â3¤D…ÚWÃ}Ü(%à^œAå©NÛ)À×€—fÜWW¯]Rõºyvet“¢ëõª!pšnT5Ÿþ8Ïß¹ø·±DÕÄjîjx©!†®¨á†®«÷$5“®áüEÚø8ð[ê[tè­Ô±2縨adÍjÑtT“2]SËè©.^ïk¸Öƒ×ûÎëâ/¿Ú³9£¹¼ñé¿HöÀ«½Q¦îW×õŠ âNàˆ>ÿîOKÜ~[^M»o²k¸A¨!†®¨áXÕ°Rå0j¸)×dóž¤ÄÛ…´ÿ6ŒãIõ±TN ¸®~—E«áܺZ®]LR×p­‡zΡ†äBSâè·_êT â—¤‚úä'ÔRÒÔÍî© ^Hø8àª>ÿî*à À»ÜG¶%-6ÑÖ´Ønjˆ¡+j8V]½'Y£5ÝúuÀ^¤Ñèg“ÊCÜJúÝØT~_`› ûqiºÿ¨½Ð«] Iœbè"GÀµ¯†k=tóz_õüœt^ùÕžQ«ÿv'ó/¼0›7O6ÈQ¬QL°æÖÆâ Ó}øêøþ=ˆöp5¼Ý¬aTWWÔpS^ÃïÌ0jx+^˃A[Ïœl]uð\êX cÜÔüªá;¡‹L,´¯†k=tóz_ËçúöèÔŒSP5Õ¨%h>ÆðõÜ®¤ŽQJ9Z‚5·H#*±Œ´ÚÞ .~0àÏ´åa¤‘$m¨áFÚ\ÿj¥SCRz5Ü”{C^ Òê?‹dLÕpí¥„xI5\‡F]-Ǹ‹×ûZ>׵ġ!uñ—_í¥MŽM? ¼ˆö¥Z‚5·a_8šáV\û4iúg "ýžçVCB íÜRà#¤non$ý>,Ÿòï½¥Ìþ»2×ÿ›n æ^PeuÒ"Fð.Ö$^^¬Oz¹0}dN ç«–âЃò_S½ørtc¬†:€~‡ãsiûj¸ÖC7¯÷5Œ®…zΡ†äz÷Ž"£ƒ€›nc%iA†_2xb¦F[ãjl3Y¼`ˆŸtúiÏqÀÀöCþ|NÿHz`ì·Ž]¿j¸AX<ÙÚJ,ÕÒ¶Ûôþ¬†óµ~tCªá^ê–èÄéÞããÑŒ¹>&à†Sùuµ$‘Ö‹`µL›õzßqNAUÏöÀ:ÑAdr<ðLÛú5pX¦mÕÀQp3{)99ˆË~ŠÑ*à³CþlnK®öÝ|jyô¼by‹ÛnÓLS&k¸)ïâ 9ÔqSî‹•X·’j¾™|‹WC§«×†h5œ»QWÃË6èæ ·~?—3ÜÌUÄœzFeúéíÀk2oóÍŒÎÃͨœçÜâg¾F³•¿H=IªW¿0oÓ¨¹lÖâ¶k¨ý5Œ™k¸¡[›:npUCÌ£rê¢ó}€#¢`B¼Ëjø.uµ$‡»øÂÍïeaN=£22ê¿€³2oóZà_3o3ʨœçœv2ÄÏ5­ñsùFj6µ%i`NדFúE3÷÷fº¿®x3[7:€!ÔpS~Mtch‚4’yàÁ±èojHâ\@GÕP¿oÔÕ’À17¯õ#ÀœzFadÔ%Àû[Úög€ßµ´í’Fá<çö ¯ñ÷àÏö]Ót¥ƒ2oo%pCæmcÓ·]Ë›äA¬bæ)(µÜ”oÀrF-ço\üØ›4}Yp,º«pWGÐQ5$8F]-׊ £B ×zp#ÀœzFadÔA´W˜riA†Fô4±”ÑXP"—5çñs¹V¸;‰z»”y›5Üè9î®n#Ü™®†sp·è†PÛü‹¢?ž< øml(šÅšÑ0Z ¸ÿ6î ì<xiôç™÷UCòtÔÝLåOºx­¯a„¾×ú`Nk;DÑÐqÀ·[ÞÇïÏ·¼¶­ lDEžÅàoá1†OdÜVS¹GÁ]‘y{Ãh3·œfu#Ì6j¯–Æ-¢B 7幄õ7%½¨|8ðƒØp4>¹Wt©?ç§’î·!þ¼°pT¦}™€+£†QT^ë‡satjΜv¦ÛþWá…Ù¼zT‡5 £sfõÏÿ%ïÍõ7¨§^ÌÓ­2n¯†¤@›7y«èÞƒÖlñ^\4ŠÙuñ¦…æ£ÂMÀ•QÃ(*¯õéá¾Z ™€t¿.Øs íë:à…öÕ–®Ÿï\v<ÄÏåš~Ús;i*G V^™q{fÜÖ°ÚÝ[Ã(¿AÌö`xuLKñ¦|p—ÓÍéÐ]p_Rî{¤{T¿èQ*À_‚cˆð]àŸ˜¹ÄA¿j¨±5LÀ 'úZi$ª:Μ Û#¢."%àJú"ð›ÂûÌ©Ëç;§‡ø™ëIb¹}š4µµK2m«†7u;¶¼ý®t˜-ÞUÔqS~÷è†]LÚ‘Yí{iúÝÁ8J§vÑ ¸Kh¯q펎iðókç Dsº0:Òl‹®Õ¤Ž^$j838e`NÐíQ¯n-¼ÏÞ‚ ]«ýÔÓåóËÚÀCüÜ‘´3Jè2àè¶;ŒMçfÚV £66hqû綸í6Ìï…¥‚˜C×ê‘.¢Ý•vûñ§àý‹ÅÀ»ã‰OºjvÑ£TÎÞ´÷5øÙèä鸨áåèZä-yRBôÂ7Ç  LÀ º;"êàûAû>…4j©‹vÆaþÿ¬?ÄÏåž~:UM‹1¼6ÓvþL³é(¹´9 ®k[gÍñÿN+Å캖€ÛŒ”„‹4j ¸¿¿"-|ô'Ò”›óI«÷Õ`_àÿˆO¼jfÑçe®ïØqp2ÃpeÔp­‡î]͎ڵ~l™€ÓúÀÖÑA a9iôHï ÕÞéšÕéÞE/·a¦ŸžC»SIJìÖ`w`¿ Û¹‰:êUìÔâ¶Þ|øé åª÷7ŒH#,ÿƒô{6›nî6&~šÇ ¢oÈ¡Žó–Ó瀇{{÷œl[ÓN €aì ü˜øÑVú{Ñ£TÆ=éº8Œu²F¡Ùü™:îSºö,ýÝ2j×ú±eN»Ò½9øÿNüê›H«¢vQWG=æp_àCü\›£ßzjwP¦íü1Óvšh3wðNRñé‡Û›V„û:±µýn^F¥õtÒKƒ¹jÖZ"¨>té¦<:wpFp ¥Ü@Z‘ôCÑLÚøݼ‡eџɃ÷_ƒŸùs&àʸ™tï­K×úÄ·ü6xÿÊÄœºXì\àÃÑALú2ðóè †ÐÅóžËˇø™UÀá¹™Á×k ì§O¶Ï°ßgØFS÷/¼¿ëI+Â=—ôY‹Ùø<àóôŸ<ƒ´*o´.}?åøŒ4ñKº[tÀ[iVg*§§ïe…òˆ¥r ŽRá Å;µœ^Žvi0ÀÝ€5÷'©4ƒF€ 8ué˯ç à¶è &M‚¨eË~ué7§un(³Bärà öÓE¤ÅFš:!Ã6šzq×»³‡SþmóYÀü™ÛIõ{¢Ý7:€Ü+xÿ5|¾"ü+pXt“ÞOû«-«‘ ¸éÞý`. % µ:Ö(.å×Ñ÷‹`Ñ×úSH#Þ5\J]]KÄ| 86:ˆiN>I|MºAt1ñšÃsî ëÀ£2Ç2›s í§/¦Yñó“H£ÖÎÐ6v!}V#ÜHú~(Y¿êÿüÜÞ9‚7åý×À+Iײ=ƒãX øoà±Áq(½h¹{àþkHjÔàà'À£üå׺Ô3`”ý":RyŒ»—FÒ‡{õÒȹ†4Š« íR}¥­ ü•øcÔo[Åxõÿ=ñǾk퟇:Òwu\ýxY†~4±4вT‡·Ágk·Ò„gwœ®"ÿ ¬[ö§×Þ<@¼÷VTóð„ânӉćÈûíf‰©T{\æþlÜŸ à] âß x>pp‘×ú©c¤¢2ªýæVíêBfð¦è t0u<Ìg·è zyt·€4º³‰¯å¤¡gï¿äò·4øÙóIKÞGÚúk6F>4|1pß5ºøht“ÞÁx.rm5â¾3~HÙï÷q6Ûb'K€Gï'½ ?x+©Þµf÷­èHç­f‘%LŽÀ©í#ÇÜøZD7ÞLZ]´Kn¥ 2lBZ¡jÔ=ˆñJ6¶å…ÀF ~þtàwyBÚý{îÿ6àöBûZÕðç¿”%Šfj^ms⮡? Úw « ùsŸgøÅGrÚŒæÓö»¬é¢=ÃÚTà?§‚ö;Žú]mx)i–‹€£éN±ÿÒ§ùýBS5_ë!¶$Æ¡ûVKLÀ¯{w£Ò¯ÓG1¤£HoDk×…QM9ú-µi>µª†Q;ÑÓP»2JâkÄ׳|jðþç²q‰†Ci渚ûÂap·PÏÃÊ뉽kÔÂ$ ÚïÙÀ[Úv—?mØ´‚ê ‘®%'G[æªã.!MÝtobP™ËZÀƒö}ñ/®Õ‚¾Ø£öúoÀ«‰lâµÔ¿pĨ'à6 -o¯<^M³BÇGЬ6Yï¿+u»®&¾¦Ñ®¤—E5Šš2sðé·_˹& œ’üF ÿsо—í·'*aõ™ü$í²ïúç1·~G¿ÍæYÀŸ€}3Ä2J‹€z_¸=„T§.Â!AûUËLÀ¯Ú/‡?¢¡sÿŠbµ'b›z!õ¯®Ô%[Ïmðó7‘¦‰EÚØz#Ñ+Ž â“Ñ?bq& 'íûpÚ]Ì£†þ&;g¿ÉHCo æú}#pK€ØïM´;]?ú\B]#àr¬f¿ ðÌ Û%߮ޡÆk=À“ƒö{%éz¯dn|Õœx¹‰T8u¼¸ :ˆ9Ôžˆmb!iÄV­V×ÏÐþJ*‚?µ]ãLÞ@³©w%~dëëƒ÷_BŽQ Ç“F Dz!qS=g³)]Úð‘–÷1 ü_ÈEs›S!úFìÿ‘Ĭtþ!Ú-+}.¡Ž M1ÎõìÒÖ”á®ZAü ·=©ï™d!q#ó>Iñ®TÓ°b•UÛ—ÜTï."“处ŷ£™Å}H˜è¬mxpÏÌÛ¼¸Š”(»´<øÔ6õÏo'ݘ¯œåŸƒXJJ„ÔˆØx ð¿CþüÅÀ7€çe‹hpû;çÆÐ¶\S&>Bì‚ ;“^¿ Œaº¨7õßÎly5Ÿ°Ï¿ÒþÊ»ÑçbÎçL^ši;w0Ú‹Ø ëSÀÛ‰5òàuûŸnob^¶ÝH|BTRfkw’Þ¦×ÖNe4Ãß#þØÎÖr'©jñ†;'ï ¼yÍÕJÕ^H*%P²o+(÷¢îÙ…ú4W»¬õ^Îm1p6ùúóà!㇤g‚È~þ…:fB½œ˜þG­ž-©e&öËu¾vt{]=¤{¾vD{]qORQ<QÃágj+iþõú úñð†}ÔUcŸ«=)sÜ*÷lí`óÌ}Ô}Hõ2K÷½äK’臯 ò¸y{}鵋(3µjiPÿ¦·'´ÝÑIÉo/Ò³ä-ö£ß¶Š´Êl”œ£ßnføäθ$à֢܋ÂÙÚ‹ZïåܧQ¾ß'Sfd­¤?"ö‹µŸöøÖz_Ö® Ÿ *ÕNo­÷1>ÍðÇ¢–Q6³yñ¿/S[Ó„å"ÒÃvdJ×,5¥áÅ-Ä>ìÈÒ\íc-ôiGQ¾ÏgRnú"ÀgZêÇ íüL}Ù†ô¢ º?½VbUîýû7µ•(¤¾1åG…ÿ’²Èk©ƒ¶õ'g²„´pS®~üªA,Ñ ¸ÃÄ>¨§´Ô‡~ÛyÄ&}#FžÞ ìU¢s’bÔ<²×Î%öË7‡Àω?–óµ;¨«èiÛV›öX¼µ|ÈYH]ÓPWÒüÆü>4;g9Ú[ö¡_«S.!ÐÆ(M‰}¹ƒTÃ4”ý¶Š¼µüúñ³ q7m7gìÏO+èO¯]Fû+³¾®‚~N’õm;¬pŸ®¦|‰”c3Ä£½ íŽÎâ ÄØOûxƒX¢pŸiû0¾™1öaÚûÚïâŒgôc-¿›’*·±_¨ƒ´a ¥ÖâEÄÃ~Ûî-ƒÒ¾D³ãð¯åCX-7ä½–ãA+zªØ `Ï ý˜Ï6ûôÑ–ú½ Ã)¿"ñ†_U¹I+=âop]¦Ø›¶\E¨k»·½ÐÔaôq‚ôµM/(ÜŸÛ}[îÓL"V\ž©ýOÛÁÖä¯Ûdjct®t‚f b¯·ólòÊ!ãmÒÎÂ…¤‘¶/±AÚ­À=Ú9 ­Û¸’øcØo{^;‡¡¨Ýi>ºèSÅ£Üÿÿû2µÝIóäÕBâ§ÆŸOáÕ¦‡ìÏ‘-öãkû1S;¸Å¾ÍäÅ™â¤ý‘ò#“wÎ{Ž–«ˆÿz¤úÑýéµÛ€2õm&gUÐÇ^Û¥¥>îF%Y²/¯h©/sÙ¶A¼¹ÛE”] j!p\ ýh’ЉNÀE”H‰^û÷” µåW]ܯDç$Åy5±_¦ƒ¶¶sZW[’d¾övC1¹¦ûž\8îaü7ñ¿/ÓÛ‰¤æ&6#~zü/h÷f¯ä¢¶ØõH+•E§UÀÓ[ìßTw§ü(€[ˆ©¹TËôÅ ò®0žÞ~Nóï˙ܣ‚¾Mm·ÐÇ­(_ þúѨUgk{·ÛÝ»ø·â_N³ÑÓÑ ¸÷7ˆ½‰O cí«”Iþ.¾п7蛤`M ÔGµÜ«ùµí~¤QAÑÇmöƒVŽD9¯ ÏqXEý£.ÿ—øß—™Ú+3ôí!Ä׃û!í%á¾S¸/Û´ÔHßsË ÷gj»…öëÁ­ œT¸_«€g¶Ü¯™, º‹üìMmGeìÛã*èÏôÖF½Ñ÷VЯ©í*òÖ¼ÛøSá>|…v’¥ýˆ^ hzûn»Ýýÿ^F;õ6OlWtîÝ ãÖÒH´È¾—X üà€~}²#K%ù±_¢Ã´‹èÎÜø…Àoˆ?fÃã®Ú•¼+K~¢løÙ’Ø=¾\Å IDAT¤Ç\í:Òh¡¦žCùb÷ÓÛOHÓÈsÚ„4Õ d?È܇éžIì*“×û´Ô·E¤i¼¥ûôî–ú3Ÿ§c‰¶X?Sß—WЧ©íà ™úiñµôkzËUóînÀi…c?†òõ&{jYÍvj[<¼ÍN“]hëúÿÙ†±E'àÞÖ0þ&¶.%®R¿{m&áþ‘ò÷'’^òIq €ë‰½€ Ûþ­…ãц‰?VÃ^Ü6háx´mò.Q?Az0zhÉNôi!é tôïÊ\ídòÜPD/Ê0œCª5”ˇúpXÆøgóÖ€~Mm·OÎܧÕoô%êmøöÄ?\ÎÔÎØÇTП™~wŸ‘¡o«SïÈè•Àöïä¿ÎÏ×~DÜÃñÖÔ—0îµË'ãËmcÚŸ*þæ†1FG¾¾aüMíAÞ—ÝôO‘^ŽåôBÒ}É~\Lz¡.i ”\/w[ì”ÿdµñè&­­‘$mÙ¸‚vŽÅõÀ#Êue^›Pò­×Ž Ï¨ÿª /+H«17}{iE¯Òñ_Gz8o[t˜U¤óõ2ôe+bV<ýeÎÕt¤Þ‡ýå¤ié9Ü¿‚þÌÖgø ¶~ZAæj·“êú༄4"´tY‚oQ~”ž‡;Ò¨ŸöWò½¤\ 8ˆ4]¹í¸ŸÖ0Öèûûˆ…@¦{,ñeBN$ÏËÑÕH/yJ|» Ø1Cü’:âñÄ~i6mÇå?$Y}ŽøcÔ¤ÕpqïÇÀ—iêÛJà3À½ÊtkFÛ’êúÜ@»}ÍÝNžJ󇘈Qc3µKIÃL‰{åWì›Ú7D̃Z@J€EŸ§K€1\ ¿ÅÀK‰%þ#Ê>ðo<—vVÌÝnÞlž¡ßçUПÙÚJàד}}"pÒïäL–û‘¦ÔÝZAìý¶³HÉ–ùê¬nDísA@Œ‡’„Í|6ž Û0öÒíG¤’ƒ–kX—”Èù4pMÁx›¬€ ñ ¸£esx<ñI¸Ûÿdø²'{S×î*bX’èmÄ~aæh¥V¾Ô^ÄÖBÊÑj®}ð`à”?.«HÅÉ›N£Ä#H7ѵК¶[I£‰vmp,>XA?zífÒÔÄ—nægJš¬AmóÒutÌ_èï07VKn‚t“û¤ÑSó%¶¶'Õº0(Öc)3ÕíäFÎ-Ü¿\m%©ÿ¤Àa|¨‚~ Òöšûs€o“®£is·+Hçó3À'I«þ˜{©U¤ÑÎ¥¦€ÿ;é³¹št®¶8Ÿ4 úpÒùüÀ”ö)Rbó‡¤$lÔ"eM‰NÀíÛ0þœjHÂM¦ŽIzá»é<1¯G1Â}‚4òmé<1JA‡ÿeÙ´]BÞUµrXüŽøcÓ´ûÀdößÄŸ¯´ßÅÿ/zJ_îÖtÅ¿×PçÊ«HÓûÀ4ô*âIŽv]î“YéЦ·SÛïâÿ×Å•tçjßÎpLžHìTήµ¤QÏ¥§SAš: £tÚlï§Üh›GêS©vùÇaå‹ù7i½ò‹€eÄ3ŠíTÒtß’’)v[ÿíW}™¹E'à6ÉЇÜv!~Œ>¿5·_“‘4†SÇpámÃ,Îm3êzÔ´Õº*ÏÄฃ2£U·ô¯Ívn¦c³i Ktjo§‘V,‹ôpÚ[$¥Ëí&à™ Žë0Þ”)öšÚÚC‹U{¿m‹É˜w¨ –Qk+I£a#[•—¶]j_îëÌÌ-¢>h¯ÝIšmS£Í©!˜¨öIâtQÅjý0+¿/ÕIÅ[K˜Ë‡¼mÍj­O° ñç{1iʶmNZl”lÃìÅq*°'©VþÞm¤‘U{’êFú) øÓà8jr:©®×Q…÷;_Ñû.ÚvÈŸûfÖ(Ú³Š4âFóüE:TOë-¤º¥y>Ë;/Ã6"V©î¹†ôP£+I£¬ßK½1–v p)Ùñ£Ê™€MŠ ×èa¤U£"íüSp ¹Õš€Û9:€IÛØÇööQÚjÀÖ™¶u3鯿¤7ÒJŽ"%ªßIJÄÕà à€e¼oBW‡ÎØÿ(~§ û}òk†ŸÂZÒU¤Q/0šç/ÂuÀkI/~‡ç³¼Ÿù3 fsõü%ÔJàݤëýEÁ±Dû5p?|Q¬9˜€µ&VšøíÝy¸$Uyøñï¬0Ì ›*Œ Š( .¸"Df“¨¨q‰QƒÆ-jbˆ‰Qã¾Å}‰Æ5*.1î5 H”MdUff¹¿?ÞÛ¿[·oÕ©S½UwßïçyÎ3wî­Suª»ªºë­÷œó`·–¶½ŒH-n;+kÐÆ5P{Ƕ0ký¶ÑkfǸ»Õ€×÷!bPâIÉh–Ÿƒ!GL*0n¶¯"¾~¯å¶´á"Ûæ¹Ä¬ÀmEæî¨­ë±Þ3&å°ÓøþÒuĬÈwÞÂ\`³-¾Ÿ£weŸõ—Ðnî’·ÝDgÖû7³ø²á6ÏŽ~Ùr[4æ À-ãXéÇ-‰iãÛðà°–¶=L㨽eÛ ˜U7Íù Œã@»ƒ0Œ®ÚWãi=8wëggîÁøÏ` ñþ ü‘Ý3í6™‡Ñn¶ LçÐý<|ûôÀZ1<~žÆ÷o.#&¢Ùx)ð»v›óÿù~Ž^¿Ÿ9mß Ìé^ÝœLô:£å¶ŒÂ ñ øbæÕÅxT À-ãXé×_3ú@Ø-SF¼ÍQ9˜vfM¬s‹¶0k_œ÷Á6Ú0Ìý:˜ á©Ào†¸qðßÄ÷>O|ù›;€w·'ž\ßns†bð.bàüWOÅÛ6Mã”vìÚGÝï0þ]ºŠ¦õ3a6Nd“¿†ñ»ÎLãù8îúÍ€Ûy ­èÝy-o¿g÷þ˜é}@ú}"ÐøX3Π p‹ÃjFÓu® Ë·2Ú® ¯§½®¯Ã¶3p‡¶Qb\p£¸šÖ›­aßtlÞK>žübÈÛ¥ ÀÛÃc€oµÚšþݼ’ŒüåD&ã¤ÛD KpGà/éÿ†oPÖÐîàáÃÒë,¨AÒÏ ª!CrNág6is=†ÈR?ø ñ™0n–Ó_ðX½é7®×.ïƒ2‰8ˆ„Ÿ%>øa»Í˜¯OÜÂIÒ÷¦ý©˜‡]ž4°W+íH"‹£íýfùÓ½Zƒs>í¿.3À'†½£À{ZÜ¿a–¿ä‹”a ðà‹ÀÖ>ÛÞFÙAÌ z<°jÀ¯Í¸Ù 8øí¿îMËEDqÝÓ{q Ú†Q^ÕçërÌìCª„}o Ú3Ne31ÖÔ €»0YÖÒþë·ØÊ YïLÚa-ïÃ>؇qqàãÄyÜö±Ñô8ú1ž­Ô—åm7@#1ã¿u{-Ñ m÷±œéœx¡Û!Œß9ã’Á1Šv´=ÖȰŒz¿f€/Í–}ˆ§¯O$ÆLW[€ï_&Š¿°ÝæŒÌâ‹í€;AÇ']UÇÑF"ƒêýD6â8ù2­×“~¿¿~ƒ˜Ey³Ë.þ¯ðÿi}sì ²™DdÏüøñPe-æ÷²-ƒs´ÍëĵŒOFõ œ>[ÖF|/;’ñì•·ƒÃõƒÄLóÛmަ…¸ÅaZÇ+Úx1&ܰ<ÅñZŽcÀv§¶0Ë\ïÚܯ+7Ζ[ÇΖ‡ mú?"›ã˳ÿâiý$;ŸÈ({9‘ÝòðÙr$í^~MdS~˜ôbK‹miÂëI¹­Dר¿@[­{ÒŽi};fˆ .,”_Ï–Ÿ2]7½Óþ^Ž£A¯Ú ÀMj÷Ó:ˆï!2ÈF|Ö?Œv‡¹žk÷ ÄÜq/TÈÜâ°‚F³ë½øÉÖ}kÚ›quÔÆñxùÙl¹ŽøÐî”ëˆñ|:6±ðÆøzbl»²àÙ~!Þeö÷kˆ'tk å¢>ö!×%DÔ¢íÛ‰' Ûˆ‘›ˆÔýHglž]¶ÎµÄÄuãÒ¬£:ûsWâõÝ•è.¹sÉïÆå îo€wÏ–eD碫þ݉1¼Ö a»;ˆãç¬Ùræì¿ÃÌÚtçÌ–×ÇÔ݈÷êˆÙŸïÀp‚rWƒFŸItÿ;øí¶3 Û‰ãì:âì\S6×”ÎuäFàfʯ¡¾žtêÖݤ®$=vÛ®Ì]‹–Áñå…;×ë5D–X¿>I~n;ñÚm ^³îתóºî ^kfÿ¶)±Îâ²Eݸ_2÷9×y;:Ÿ EeïUN½¢ D`¬Nç8ªÓy­:¯ã ³íÜØ`Ó`†8aîµ(ê|¾uŽ­ŽÜzuïu±m¹ŸGÅãi7“½Ô9ï»-¡| ¶²í–}§D«ÍÙéÏlqÛ£r5ðï³e ñ=ì^Ägý=ƒNtq}=›ËíûÄgÿ8Ž©)2í]é®d|±¶÷að]‚>AÌr³l'¾¥n¤i¶ñð¶Dví-ˆ'´{3?0°Œ8_:ˆ D0ãZ"@p1P½„Èð¸y”;±,ö'&ÞXO¼W{7K{ßq:_Ú;ïUçæu;Ñ5éj"¸v5p‘÷ûQí€Zµø*qÞ^Jœ³—çjç!ÏF"Xäç¡´x½ø»–¶}ñ°`±Û‹ø¬?ø|¿%sŸ÷;wV2÷Yób_A|Î_A|7ûåì¿9$©‘½iàÊQ—§ ä•›ó1اQ—q'K’$I…ÓÞ÷ñ}G°’Fh<Ô`ÚvZð/ .]|%ð–­k’Œã8p’$IÒ(­oi»ç3˜îö’ƈ¸é7Žãy ÛžDºø ¼{`±YŒÇ$I’Tt@KÛíRÒ07ýk&ÓÓ‰AÕû±?ð’´e€“$IÒb¶+1[¾ÓÒv% ‘¸é·X)K·Q>£R®7‘ž±mš-ÖÀ­$I’p8íLZ8|½…íJ2pÓm pç¶Ñ¢{Oí±î1À °-“f?¢+¯$I’´ݳ¥íþ€˜‘YÒ”17Ýn ¬m»-;•˜ ¶‰€7¡-“Æ,8I’$-V‡·´Ýϵ´]ICfnº-Öî§E»A¸&^Üqm™4?’$IZ¬îßÂ6g€O¶°]I#`nº@ Oìí±-“Ä 8I’$-FwÖ·°Ý¯¿ja»’F`yÛ ÐP@ K€ww¶Õ,ûF`ç¡·h2ÀíßÞÄyxâ‹ÜÊv›#I’´h\¼¨ÇºdCxoáçg÷/oo~Úv#4 ÀM7(sž ¼%±ÌMs&Â!Dðr¦í†L€Ý‰ Û]ˆ€ÛÁÄë×tüAI’$ Ƨú¨{ìÀZ‘ïbà3…ÿÿ!ðÈÚ¡|3À?·ÝMpÓk%p`Û3¯">ˆ//ùÛ*à_GÛœ±·¸ pIÛ 3{³buÊ=€[·Ú"I’$uûRõöŽdC2½ØZøÿšÚ f>\Øv#49 ÀM¯ƒ€m7bÌìJ<¡8¡äo/#fÕ|‡°¸pëXl[ßfƒ$I’”å¿z¬÷xFu󻟂¸q÷àÙm7B“ÅÜô²ûi¹'ï¾UøÝ€ç·ÒšñwðŶ1BûGŽ"º“:Y$IÒd¹ò^/9N`;r½ØÜõ»}Zh‡ò|†H긱í†h²€›^àÊ-Þ Î\Š÷›qâ…*Ó>‘Ç­€1t;¨ÝæH’$iÎê±Þ±Ä}Â(K$-ÅÜ8ºx)ð–Û¡ enzM{à¤wžCŒùöXÚduRLc ÷.ÄdÜÊJ’$izœÙc½—´õfˆnŒÛº~¿'4N6“ù½¸¾å¶h‚€›^Ó8¤S€Ó€×¶ÜŽqw1¡ÇÍm7¤;é>Š˜Ijßv›#I’¤!;»‡:î3è†Ôø7à›%¿?`ÄíP¹‹€wï®j·)šà¦ÓnÄ앪¶8أ톌¹Îlºç´ÝÜ 8 8ؽå¶H’$itþ¯áòk· £! —/ªø›½™Ú³ø2ðÎÙw´ÛMpÓ鮨­.‡Á·ø4°SÛ ‘$IÒXø}ærûŸV±-Ý~<‰˜ý´Ê}m›¦Ñ6àÀYĸßßÎkµEক8 Òm‰±Õ6¶ÝoÇà›$I’æäàn |ƒÑŽûökàÑÀ¦šå2‚¶L“mÄxÕg?žý÷§Àæ6%•17}öÃ1°4XK€ƒ´Ý7·Ý€Ed;Õ÷kpÝÖ=lÒºS¶Pÿ»M72ÞçÏuŒï¬cÃ<^‡ézâ<Öàm$n5xã~­šd›‰ç§Ñ¥5¿5ðMàö#hKÇ¥D`íÊŒe?IdlM²MÄw¡aÛœÏôËš2à¦Ï8wÔäº+ã€;¸åÖ{3ñ¥¶2¼A^·1ž™Š’$Iãàà‹ÀmF¸Í+‡f.ÿ³!¶ER‹ ÀM»ŸjÆ5°{Ùl‘$I’RFd—í:Âmþx8pÁ·)iLr¶ƸJ4Ù<®$I’4‰–'_`´Á·*|“¤)u1æ‘Å2Èr5’$IÒd¹ðFÿÝù8“©$MµeÄ€—mk,ÓYn$I’49¾Ëh¿/o^@dÝIÒ>„õK’ÆÜù´?P¿ezË ´—$IÒdù1ƒýN|&pߑàÍôôØ™˜„A–ÕÀú¶!I’$5ðÑ­ç"àxà^À÷´NI‹ˆ¸éq0°¬íFhêÙ U’$I“äýÀ¦>ê_¼€ènúQ" N’37= Œh<Î$I’4I~¼©‡zWÿ¼Ø2ÈFIZ| ÀM'`Ð(xœI’$iÒ¼¸4sÙ_'§×©M’¤ õ_´?H¿eúË9H’$I“çAÀVª¿çžü)ë#Iªqíg,Ó_¶;!I’$Mž“€Ì}·½xpD›’$MŽ=h?0cY<å0$I’¤ÉôdàÇÀ³u-·E’4aŽ¢ý Œeñ”ã‘$I’$IÙœ„a:80¾FÉ™P%I’$IjÀÜt0×Ü%À sÛnÈ2'I’$I’ÿ¡ýn‰“R6ÿ ¬™}í–O®ƒ¶MJ¹I’$I’¤Ed p-íe&¡œ¬¯x÷ÞDzjrK”8`­$I’$IZDö§ý€Ì¸—Gf¾ž_ƒ6{¹æë)I’$IÒ¢çp“Ïñ¸ªýx.p/à»™uÎŽ üzH횎;(I’$IR&p“Ï@ÈB[7·'º•nïa§w&x×®iSÃÀ¯$I’$I™–·ÝõÍ@È|_#‚fç `]7¼Oÿ<ƒÖwÒð-nGŒ¹¸+1‰ÌuÀ¥L΃ÄÃÝ€µÀfb®®n¡=ˈ±@÷˜mS§=¿6´Ðža¸5°qÜl#öïrâ5¤[ûÎng†8&¯.ðv†i`obvûpqŽiúíI\."ÞÿÅl5p âø¿±…í¯!>ï̶ֻábœëˈkÌb° XI|6IšBKÚn€úv6pXÛ¿þøâ·qO" w¿!ncR\KÜÀJ>ÂÜöë™Ë:]FŽ ¾T¿xÕ¶¹œ¸A^Cd¼î ‚ ;_à!n®v"ožÁÜÜMÀ–®m¬!‚G»ÿ¼¤¤=/^Dí·»ê-VÇÙµ)»O&^×»ÏÖë6\œ ü7qÝ»"±Îs€Û7êw›–0r•5À[€ç×´·Ì}€?Ü…¸™(sð3à;Ä>ü„áÜdí <xpæfÁîv11fèW€/‘œN|÷Žˆã²Jñø,*ž/œmCŽÀÇÍý½*–»†x¿>»îŸg®¿c)ðPà/ˆqU÷«XnðSà›Ä˜ª?L¬ó¹Àë™þÞHœKE«™;ŽVçün ÚÞ±øSâõ:œ¹×»ÛuÄkõ%à?ÿk¸“S‰s®ûú´–¸†­^¼,s÷¾ÅÜõ¥ø:ÏßµÄùþ›†møàcÔ_ÏW¯þ±‡mt[Fœ{»‘w=_ÅÂ㣩=ˆ÷t÷Ùÿÿ!ð…>×yð?Äù± Þ*`gb_¾C^Àoñšt®×[ˆÏ«Ž]g—YEœÿŸì±í_!®×ožÓãzšXC¼îÇד¨¾/½øñyñ âaûUÛ¸8V¯-ù[ñý)³ ñ~u[9»N€ÛP~žO<é|¯ØÎ‡fë˜ÛßÝYhÛl ³ˆsÿ?™û—$µ`9q!n{@þ6˵À‹©¾Á´%ÄñÅCܧI)U7_Z\Ž!ÿ˜¹ŒÁœ«j°Í²L±W6¨_,/­hÏ_eÖ?*±OK‰›ž=´k ð^âF¬Ì÷{Üß&7×Kˆ×Y=nk¸ø{"³j–O¡·™Â·ÿNy  "ð9Ìëë“3÷ñ(à‚·ñM";1Ç݈àd/Û9‹|–9©Çuþ6³Ý‡'n„›nk;ð9b<Ù\'6X÷C3×yHƒ6Wë<¤Á6.§<@ÑÔ± ¶9¨Ì¬çu­÷XçÈßÜ +ä&Óc»îZÏ ”„e?âaöò_¯²óæ{Äõc5Õ®ìc9¥*âGCÚÞï‰~Ňƒ’¤êþÐ\Le;ð!"]¾ «Sˆ š¶_‹¶J¯_ö4]šÎÄ|ü¶ù©Û+˾9¡a›;åÏ+ÚóØÌú·©¨¿ øDE›ˆÉaÎ#‚B©õWev½¯Çý}lÅúºÝ™˜è¦j=€AŒÓˆŒ¼Ôv·™JýX¼;±þ €s‰LšT[ö¯Xÿ;jêõ[þ&cŸNdpt×ÝN3N û}M¼feõKd»Õ9«öç~5õªÊW2Ú @}‘ET¶žÄët:‘É’Úæv";¨ê<+ºwƒ}¹‚¼ ójÊßïîÒO×î[5h÷ ùâ”7ØÞYØÞâzÚ½îÃû\ï òŠo%²¾rü4söØî7—¬ëo{\WʲÙõ¦ŠWïñW‰Ï”+ËvJªçË÷2ê÷S\±Ý4\Ï5À¯ˆ‡”uŸI3ÄÃ}û-I’?g¸,ãZ¾Åøt»½ lû5i£¼`¯Ÿ¦Ã®D&NÎBª;ZŽ=©ˇˆÀPYÆÝRâÆÛ3ÖsÑ óv‰6-%®I­XÇ¿]ª¼©¤ÎD»8îäž³ëªjkUÖŠÙö} c7}UÁÂn¡:Èóc¢+jÙ{p'âÆ©ª9AŽ”SJÖyÉl{Š™‚»]£«2£ª2Ä–I9ÇО]u×Çæ‰T._]³,ióÀ³™Ÿµ·’8W½GO¨ÙÎ=YTØFd*»,/#^ÛË+¶óâÄ6öžF^FÌG‰lºœLÚý¨Îȼ€øÕ½žƒ‰Àm*ØýSòνˆlݳ3öë«ä1»ít×.–ÍDÆçºÊÚyÖ¬ØFwé7 ¶;y2?B¼7ƒÈ þƒŠm|xë^<€è*X·O—²ðÚPf5Ñýøsë9•Þ{$¬¦ü¼û ƒíY²'4¯z->LìcYÔ»ÿ‘¨ûîÄvw&‚ü§%êwÞ‹nëˆa N&º¿–Õ«z(ñÙñºšíÎ]o‹V×Ò3kêýœÞºáK’úÐk7ªI-—O[Çqì‘ÿ”rZÊñÂiªÜ‘¼c§ŸqŸ“±þ’øBͺþ¡AÛ–Šõ?QSçÞì_OŒSåÕmÍy"þ?u;åYëèøsªWo¥þn)å7V×4hC™õ,¼©ßBýª¼°¤3D ¬NÝÍ]ÝMö Jê¼?±üJÊ»>.QçhÊß«u–P L=|9˜£ª»Îß%êtœPR¯XÎ&ÿ¼ÞX¿l=_¥>Hõ`àw‰¶\Jú-ZGyÆUwiÒ-qv¯{eƒú9n_ÒÆ²’›ÉU&§ûþY ö{_U¶ñÍDà ,>S±b9ü}[ÊÂÏ—¯öÙΧ&Úö¤>×ݱ'Õß7ãÀåxvÅ:^žQw1–Zê|NYI óÐ]· IDATï¹Ûþzb»3Tw¡]IŒÇ˜ªûÖŒíK’è³ä}9šôr#‘ÍP6ù8YJ¯¢ý×le]A4}꺼ÍP”JÉcì ÖWÐ{`ÃöÐUÿÏj–/Ë ú÷š:;QÞ5'' ð²’zÅ’ REu&âçÈ¿©Ü…]’Îά[åU%mª›Ð`1({w½C3¶WDÈÉré¾¹ûQbÙ?,ÙÆ5ÔgP•ÝÌ=-±üá%Ëo£~|±×–Ô{EMˆrêu|]Æ: ²ª²·~I~ÖÈᤳò~J~¦æß&ÖÓ)[‰ì©\oíªDƒº¹rÆOütëÿaÆúßÜÇú»íKº‹ß?p[G'¶S,9ÝÍ;ŽïªÛïd ©,«÷¹nˆÏªT¬ðð†ë+ëÚù”̺ïªhÇ y³/Îèª÷žŒzuŸ¹©1ìv~¨»Á™*iDrÒÝ5¾i»C6CÌèt0sã­³D··;¯¡ÿYºÆÝÁTú®Å+gü¡?¦zl­”C‰.!uš Ð~YŸïö³®ÿŸ_³ü±%¿»¼¦Î¢+^·œ³/®ù{Îk·q­+ËpÛÀÜô9®eá ü e 6ðˆ’ßÕ½¦Û–ü>ç5½(c™:/cþÌ©©ëkÙþ]Iý¬Šï/ù]jÿÊŽÍ)ŸØ¤Ÿít\Iús>÷¼þg¢ÛZ™§1†Í”ŸPJ}Wá&–AÒœ€-,œÅö‚¶¥#çzþz³ñ.äMlÑt”“H`ÿtúïþÞÔ©äOðÑýž7·è^TOŽ€~Pë‡ø.\~ ð_=¬¯[îÄ5ÜV·m,œˆ)ç>,'¸We1y•eÄ÷)IÄÜäZMÿƒT³™'ÇQÃ8n®%žvJ}ÖÅ$Û™üô´xTÍY´œf];NÌ\®É auý›¬ ìRÝ)WQ~? c;ß-ù]Τ4uÁ­œý}5ÕÔ%®M|§‡6¤Tò»a¾¦ƒ˜™ñ àk…ÿïBõõµlÿnC}Öá÷‰›È¢Ôþ•mgWêƒi¿`aà&w¤Ôk™óî>À3+þößÀ·3ÛÑñI¢›z•gÑlvÔ:·&†wÈÉ -^k¶Ðü¼Ë‘s=_FtlêÄÌå5ãã2ÒUˆcûÄm/×J¢+~Nff÷çË•}l÷/3–i’×í¾À_Wüí*zË6ü9 Z¹Ÿƒ¸N“¯ã.ÔßK÷»Ýÿ®ùûÁ}®_Òˆ€›\9ýIt 1¦ÂÄXE“ì|b ì‡7$ÓhÚ³0Õ\N¦ ÄPªëE·äÏ š›A MÿÞ­;H°%±ì^¿8õã¹Îü,³›È»ißÞçß×ãUÕ}gFºÓõÿ~pk(®àHÒi@t•*f.o¥>« ê_³\§³ö}’Èԩʾܻäwk©Ï„ØÈÂã³l;P?óå q|æn§(u¾å¼Oõw£÷f¶¡[jüº¥5ïÅ£ÈSáç~ƒÖUr¯ç'Ñ,sl9ðÄÌe›\ÏSÁ܃ƒ‰åžËè³ûoGz2ŽM]ÿïõ}_Çܘ‘©×âäKÐퟨþLz{ÍvSÎìúîk0ˆët§gÎw‰ŒéœY€ûÝn]–cîà IRŸN"ol‰I)73Ö-3©V3)]Gû¯õ Ë)|4ùÖ1ÿøø éã§*S¥Ìc õ6Q=Ûâ åÝ3«<´¦9­ìªŸº¦Ý6±Ý/Q?‰Á“‰›¨˜eã‡K݃Ô,¬ßÌlC·â`·^í–hßw©ú<xùc…= ±Í@è¨ÐÿRÒ³íB+O$ÕÍœøÕŠí\K}Wð{Çç£3ÚT”:¯ŸQS÷ÎD®¬îMô÷ýâ;‰víîPS¿8Üw‰¬˜Ô1³…ú̺û–¿ªéeXÛÕ¦ºëy“,¸G1ÿ½ùmb½ïW€¹Y—·S?nã ºõ]X߯‰‡²©mÎPŸ•¶”ùÇxÝqWåäÂ:NfáLÇÅÒËC•²ñ#‹åŽ=¶¢Kæ ñ€ätò„ÏJ´§Ÿn¢uþ(±ÝòDnMÔïõÁ‚$©¡7PÿA>)å«äÍ47 ö%‚ížõpR˧ûòhÂ݉ùÇG݃‚_?Xÿê}ˆ…³ÁË·´¹Í\*X4CÌž6è–û À-!†¨ªÛdÇ¢•DÖóäuyKYFõä3Ä Úô¹nýàVY9u³ru²ËÊ¥Àý›5¿ÒÇÛùù³6ÑO.5ØyjR‹u›ÕÔ/àþ¹qSåW¤ƒÀ÷.,{Eó]ªu‡®öÔ]ÏÏ'¿gƧ õþôL‘e]ÛZÏÜ,Àß&λîÉ_½Í£ ëû9p ggî.›©Ÿø¥8›q]Vo™%À¹³õo"ޱNp²¬l¢:[»Ê?'ÖW7.j=ˆìÜå ëõ€[CqpLÆz÷&®ï"=9B]fϸ¸Žôÿ? ºg¾Œü ·a:”ôäÝ„纙˜ ñv±jj;éÏ“{í|%q#×–ÕÄ ÿ‰ f“Œ›ÿMüíÖDúýô?Fgêý܃˜íö3D`a<2ñ·înÎM¥Æƒò‰1R>0[RnGëÚR¼žßL´7<¹#åwtÛ“ùÁÛQ\ÏŸÎÜ=ÏLj@×Ë?€ÁÏ*ûSê»ïL¾› ÏÐÔQÌ=ôþ2ñ9”z˜ºŠ¼ñâŠRçâO®«Ûï‰1&›Ñ‹Cˆc~#‘ÅØF²@ÝqxÆHZ!i` ÀM®I|,–I RÎbr'™(º=ýg¬hz³Å.'‚!o¬©srÆzgn îÿ%2€R3ãMJê¯{¯"fpk;wxÍßÏI+ê}¹æïk‰q».¤½@܈™:;cM5™å»nÿ–VçÑ_ ®n;KˆÀáOh?·Œê™O!²Éúq ó_ïvùÙ¼Ϧ>0xõ™ÃR¼ž_Áà®çO`®{ý/ˆî½©ëù¾ëLYIdïAm:³.×¹ö¼>·[æ]D0å àmCØvG1˜ÖiË'HÏü,`§Ìõ¯¢|—Ž~ƒá£tOæw—mr”ª1W!ÆÍþƨ"I‹Ù^Ôw]Dzè:怡óíBŒ¥¶‰öߣ^Jj{-.ÅnZAØWÝÕªŽŸÔ?U>»°üsf÷ÜÄ:gÈ¿«Í.¨SŠ]ŠêÊ5ôˆë§ êëjêŽËµ}=én¨Ýå:ú ÄõÒõ]Ë<¦ÁöV¥ÜýÛJï¸TwײsùÓôˆëµ ê5mË Õ9­f·JÔíî‚Úq013qj½›(ï–8ì.¨Ï(¬¿“e³ŠÈ>Jw©YïY…å;aÏI¬s†þ‚ä+¬§T^BõxŠó&•ñ[ç躊٤kÉ®j€ÿ~º îÃܵq#ó ¾¹¦=‘¹»×¬§—С—.¨ŸíZn}Ûí§ êq5u_ÞC{$µÌ ¸ÉT7>Ä8ú&‘=ñd†3Xð$ÛDàî|˜øP$wm»Å`U'£áFÒƒ8/þ:ñ÷Ù»¡ßDœ#“÷sàõ –ߓȈ»*¬H/>Pu¶^gµ´‹h6AÌ®ÌeĽ„è6Hëˆsã®DÀ÷ÀK»–i’Y±•ÈbÉ]¯˜÷Nªg7-óWäw ^ü ‘÷ô¼hªn¬ÄA›Õü½ÉëÚq.õ‰U ¿[b™²ëùfà‰:KH;enÍÄCYîõ¼,ã â»Vê³i9éϦ^m$+uçüÛè}Ò*'1—}øyæŸÛï$ýýóyäeyÖM:“Ê´kËRbÆßýˆï&ιî#£Ê€[¼øHb™Ÿ¯Ms$IÅًƽ\BÞ4Ýšsó3~ƽ¼n8/ƒ&Pç ü 1«qǾ¤3’n :ÃáM…åÞWø}qÀ²òàÌ6·ñeû5í¨*çR?[bQ?pÝÙÝeÔ‚”%D·È^^Ó_Ù+¹ê2àrÊ‘=ìãKzÜÖµ¤»5u{bÛÙDd~5éšÙkÜ#kÚrRuÕlW³£u«2à:ÞW³îŽ7ì ¸·ÖÿÖÂï÷!=kæ&ªƒ0ÅY”?Xø}q_ÊÊÃz܇â̸ױðµŽtâzŸ=÷èÂzÊÆS|zb»r6 ôš·”xÈЩ[ö™š #÷}ø³šuœÐ Íƒ”Ê€Ë-½ up&­‡'°¥&;š!ºn÷Û5[RKÌ€›L“q´ øG"«ëC5Ëj¾oOˆO`2²'áxÔh3¤.ïú95îÍjÊ+‰ñ‚:Š7®©1ƒ`r2à Æ%z4‘‘ÐÔ‰ûšTzµ²æï½Þ¨à ðxb†Å¦nGÌÎýü¶(mkuN^LšXGŒõêßSˆ,Œˆ z«ˆ6žÆðºñ©vÀ6ê2s3Ë<›ôäïÁ(hV]ϯ$}^­"‚KÝVãyv4¹ž÷løKæÀ%2²‹6þŽºƒ Þ–ù·Ù6¥F-áXæºPþŠò±ÃÞR³Žœqñêî-sÏň!NîD\“oG Á³ûlÙ«ðûÛÑ[÷Ц†1ñÃ׈ìäŸ]¤_Fuöð q¼Þ›ùç¤$iÈΠÿ§8Ã*;ˆÁ\GÙõdš­#¦so2žÑ¨K]×-Åñ¢ºoe.¡¬\Ld‚ýIáïgwýmeÍú^”ÙæqÈ€ëXJd5ÝXÓ¦ªkïc3¶ÑOÜ'jêº»Ô tº8o ·ëÛÓ2¶Q—wqÛjÃÑ}ìã#˜ŸÙÒ¤¼§ÁvîKdòô²/17áDJ¯p«Ùþ«³÷²ÚßÔl#5©ÿ])uðášöÍoìs;· ºú¦Æñš¡>“©ŸÜ'k¶}xÃ}ê–Ê>iMÝ&8ˆ®äuAÍbðxиý ëÞBõL¶'Ô´±3ùNqf÷û,\ Y}U빸aûßSÓ®^ʧ¶áèBݜܢrî{>CÞ÷ðãjÖÙK¹Št·ùÕÔ? £ÝEK©ž´.øÝÑKîÄû~1®a/×&8ˆ®Ú©å?×C$I}øÿAÚ¤ü‚ôÍFïîÌ,¹Rì:¨Åé~Ì%–Û‰ôMöÍÄŒ ÿWÛ1æUÕz6S}ÓXÕî²ÒtÐã;êÖuK»%qcþAbŒÇ¯Sð[A¼SmNMÐO¢;fªþ ºRöjw¢«ìûˆ ɯQÿ.^Cz¿R“zô€ëÇ3wŸ%fk}\FÇ“ž–Ž'‚jŸ™ÝÎs2¶óÒ£ÔÔï'÷‰º3Äd½ZË\ʲR—Ó4ðÄÄöºË pÅî­UÁ ˆ.‘—%Úu3ó¯9Ý“éý]b=[Èædõ“YôR¶ÑìÁóÑ…º98ˆñ~75hSN{¾Þ`}MJêsæI5uëÎå2ÅI$Šå³™õ{ À BÓÄ÷TcKêHšƒè£Ñê%ýy®%RÎ߯p¦áVï~ <ø’ûfÚý®D°c¦…mk<¬/üœ„y qyeÅßWctu¼+±®Ô ¼;{Q?0úõ5_O³(ü¼¡fÙÈIŠnCÌÄVe+1Pû~Ä9_¦{öÁAú0é1ŸCý8tÃt ð/]¿»-é÷p;Ñ-k_ª3цùšv{qÜ~‹…•%À[™Œ8“x8—ò1"ø–Š¿¯,ùÝ«™?£yNçkDà¨ê¦¸l;ƒòYbÿÊnh!•oîqÝÇP} œüoMýâ~çK!‚8'e.?Hë ?§®ç7ÇcÕ,³+ˆkFG¯×ó•D6RÎ1ødæŽMÀÿd6×ûÍÌM‚2Ãüëô=ˆ r™eÄð'g´¡ÓæŽÜ÷üçÄùßdv┃€ÍþÅîñ߬Yv/ÒãœtÊõ¤'1xtE½Ny@F»w­YÇK2ÖQôôBÝÿªY¶lLÏ2·sÇD›SQýfÀíLz¬­â}iËKÚóÜ̺·¤:K,5ˆ{]Ü^ Ú¿s™V[‰àFÑÎ%ë¯ ¾u,~YÑÆ”,U×2¹ã*Bu¦dj¶Iè/"¸_U½wCMu©Ë™!óÔÂò«Y¶h<®»V:®˜±÷še÷ /ãl#é1ƒYSÿ¨Œv/a~Wá—eÔ)ZOzXëɟųx}¿°a;rºÏPŸ÷†Â²uïc·îײ¬<2Qÿµ‰z34?»'DH]»Ê¤2à~Ó°-ÅÏ™Hg÷’õݨŸØ°Í’Æ„³VN–ƒîÓãnß º7>ƒú,‡MDWªƒ˜û7*‡Œp[?‡~ÞT³ì5ÌÏr«òQ⦭J]σ2¶q=é/ßÇd¬£èÈÂÏçÕ,[6†ÎÝ3·óKàwKeãÔ}†Ôýý&à”šeÞ@³ Ó õóš^Aõrê5­÷.5VR·ã™ËzXNÌŽW·­ÜÙíTw-Û¿îmJñrT šß$S¬[NÑ©T_3–¯ÈXG·õÀŸUüíÛäu+mšÌŒ¼™Ãë†u¡Éõü÷äAþƒt¶Ò ®ç!&XfÁ™Ž‹ˆ®ëUÖÏÎ\W1û«édeϤþó£ÎZ¢[vÇûÖŸaá€n©TÿBú=­Êšl*÷¾(un: ô‰…ŸW™“UêÚ·SÅïO%®ÙU^EóvK’:ž¼'bý– €ÇŽhŸ4\Gg0šãæoG´OO—0w,œ‘±ü©°½.°pךú¯ËlûGëØFtḵšŠuêžX³|Ù˜U¿ÌÜÖâæ¦»þŤ®XR§XrÆ+[JXRëùÍOEý Q–mpù£_•Ôÿ雨?.©S,wËÜöNÌ?fX89¾”oãö™Û¨:Öˆ·(Ë<:s;ÿR±Ô²KHgùç~Æž~˜Q¿®ëß73ÛQ×EäóäeŒ¿²Pçf`Ÿšå_\±½‡fl«¬«å õ³1ÛXVªf(ìvpeͺ΢ٸ¥kˆ×ärªg¾­Sì\,ÇeÔ=¤¢îkkê½°¢^§œ˜Ùö7–ÔíîÞxXÅ6^Ÿ±þÕ”¿ge³îQ±Odlgå]×~I:¸z@Å6;å½Û†ä}:±žëÉÏŠ|\b=OÈ\Ç*bŒ¨N½›hž1yCU[€»g׺¯$oB›Ï$Ú—šL§ã¾‰ú3Ôw¡<‚ù³„~_.îR³IR.¢þC°iù!q¥Åã@â‹Æ ¥-Œv¦@µk-ð§T±¾ž£¤îéöJên :`± qãü6ò&¥¹†ø‚›£↩n}—ÿJtaû" p ƒa×·ªÙ¤3¯'ºñ=Œør}0‘w*å7¿#Æè*³ÿl›ËžÜw—À;ˆîÝ2öápÒçw¿¯çã‹þ0,ë†| Õ3Öù@bû7YfÇ™yw&ÞÇS(ϾžêÏÇÛYu_.©WVÎþèrõX¢ËÒ‹ˆ,ßUÔ¹…7¥ÏLlc;1ÀÿqD·×‰ Š“‰±×ÊÞë§Vì_U ¯S¾HÌdx¢ûë}ˆñb«†<8¥b;;Çõ?‘÷pèL"£0g<°U¤Ï±bRˆ'¯Õî³ûòGÄûÚ@™!r'fl{'âzå]y·3²>‚f]µïÄü@u§ô€[C\Ï«ŽçÄ{tTÍzÊÞÿÔd:«ˆÉ ÞJÞûÿ;¢kg1hüPâzR¶üÈ{q®Ïüî©r>16`1hs$q¬W=˜øÌl[d¶ â³°êQwîNTÞ>qÎæv…½q½¬ëZܹ¶žLõ+©Ï@;{v÷$‚]·'^Ï¿'Æ¡®ªw Õcß™¸Ö–=\)+ß#‚ëO$®ÓOþŽxȸ)Q§ÛDöì×2¶¹™Kï°’õ@: ·Sn&>ûžÊhÇ —¤©¶–ò/ƒ½–ˈ/9Ý 4BùMY?%w<M¾Ü1)7“ÎBx|I·%–Dæv»KÕàóEBÞl~©r%p¿ŒmAta+v굜Gz”Ôw©Rœé¶7ðŸØíÄMÄ:z÷ÊÇqkZ.$=Y5#ß KYVñÝ€Ó°îëIw¡¼å™­MËÒ³Ð>¨ÇõæœÏßqžEÞ¬Ëuå,ò3M†Ã ÉIDATÚ`½u™ÝžP²Ž~p97ù3D,Õø¸’:ïL,Læv»Kg|·eÔÏú¾ƒ¼™KS3K§ÊgëïJý¸¦Òt‚ªñàºp9×áÜ1/ÌÜ—bùëšužH}Ötn¹‰x •Ê.>k@ÛJ•² ¥êºí–•‹+öaõ™ÅÒt¬AIR…û1˜Š-D£&³piz­ ž8^Ë`ޝ¦ƒ[krBþq‘êÞ¾œøâY\¾*“ â ~/Çfî¸t§ÑüÇvâKwÓ®ü;Šó{ا ÀË©Ÿ ­»kVn©ëÓíÁDv[/ŸKï#ÝE´‰D6V/67uuÙ9™ ý–O'¶ÿ(âµnzœn%2QÖ×ì_Ç‘Äù“S,;fëÕ¬žÖp½ryfû;ö%ºåÇbË-?'T9ã@v<£ÁúOn¸/™uÅuô€«ëÎ_,©ìÞå, ܤÆÖzVƒíË•³õÈ\¾.ê'_¨*qmPç?2ÚÓ­lÒžî\ÎçHÝ8¡Í^ú¿#cÝ{YŒU™¿uåâ33g TæÜ ÊËK¶››qW,;¨~Xù¢ÌulÄÄ iìõ3Ó˜F+•Ýë ÄÍë…X—¦ÃV" ûa¢{Ô³èmÌ”ŽCè}àcM– Dw£fÿqÌŸHéfâýÿ91)ɧi¼úîl¹‘ÝtÑ5m?æŸÛˆlÎsˆÁÒ?EÞwÎù|ãl[¡üuÛ•¹ch5sך\—]ËþžÈN{8qlÌü쨢‡ÀωÀõgèmÖÒ«©ók9sA;³L6õ\bò›Ãˆ×"wöä*×Y…gÿŸºž§¾#l#®eo ΋Ï?I,¿™Þ®çk× â}êÔ¹¾ðûN&PnFíÍ,<·0ÿ³£øÞu®™Ûfÿ¿•8¿v”¬»óÚ1»/MÈãdMô^™!ίîëÎ…ÀofÛpÝì¿Åm¯&ïš´3qM,îgEë˜ øìFL^Qç"hõJ"öQ̋řZ·Î®ïjâ3ïtâ³öÜŒmtœÏ\fYñ˜.ê~Ë_Ãne³¾_4»Þι½¹s«£xÝYMtÝ™8þº½›xÍVçʵÄ{Q>>ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ>>>ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1665611335.957255 agordejo-0.4.2/desktop/images/favicon.png0000644000175000017500000000122314321633110017104 0ustar00nilsnils‰PNG  IHDR V%(gAMA± üa cHRMz&€„ú€èu0ê`:˜pœºQ<bKGDÿ‡Ì¿ pHYs``w·tIMEä ú†wÍaIDAT8Ë’?K‚QÆŸ÷•ˆÒijÈ-irj ¬%šrúñÒÐ5eßÀ¯¸DP[HK6½B" BiQö4ÜsßûGÂÎrÏyøqþÝL4*+yrIɘ÷€œôÆ2÷J¢ühk}®âgÈ\gýœ$ÉQF¢ÄKì):¢.}ðsYüŽ 4D<'y)~ÃS V6¬o¹M$«j‚.Iò) ¨Ú%Š€CYû h•ø~’T½÷_¦„,Å-¶É ý ÷»ïV—¡³èÓ Wgî²I²¢×j/ 3E!ùÃcÖ¿ÃT"ÎÝf?5Ð@Ë:ƒYËoé)ìkx7:×âY[ñ@¬ÇÌ-·“3A^šìë?@°ýÌ—(™)xU@S +7$É»U-4PWÑB}$ßýs²¨¤º"˜Šú4ö¶? ‘ÊÖbºö¸  LL´_³ÉhÙ%tEXtdate:create2020-07-24T11:10:17+00:00εèÚ%tEXtdate:modify2020-07-24T11:10:17+00:00¿èPftEXtSoftwarewww.inkscape.org›î<IEND®B`‚././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1665611335.957255 agordejo-0.4.2/desktop/nsm-data.desktop0000644000175000017500000000035714321633110016612 0ustar00nilsnils[Desktop Entry] Type=Application Name=NSM-Data Comment=In-session data storage for Agordejo Exec=nsm-data Icon=agordejo Terminal=false StartupNotify=false Version=1.0 Categories=Audio; X-NSM-Capable=true X-NSM-Exec=nsm-data NoDisplay=true ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1665611335.957255 agordejo-0.4.2/documentation/LICENSE0000644000175000017500000004735614321633110015732 0ustar00nilsnilsfrom: https://creativecommons.org/licenses/by-sa/4.0/legalcode.txt Attribution-ShareAlike 4.0 International ======================================================================= Creative Commons Corporation ("Creative Commons") is not a law firm and does not provide legal services or legal advice. Distribution of Creative Commons public licenses does not create a lawyer-client or other relationship. Creative Commons makes its licenses and related information available on an "as-is" basis. Creative Commons gives no warranties regarding its licenses, any material licensed under their terms and conditions, or any related information. Creative Commons disclaims all liability for damages resulting from their use to the fullest extent possible. Using Creative Commons Public Licenses Creative Commons public licenses provide a standard set of terms and conditions that creators and other rights holders may use to share original works of authorship and other material subject to copyright and certain other rights specified in the public license below. The following considerations are for informational purposes only, are not exhaustive, and do not form part of our licenses. Considerations for licensors: Our public licenses are intended for use by those authorized to give the public permission to use material in ways otherwise restricted by copyright and certain other rights. Our licenses are irrevocable. Licensors should read and understand the terms and conditions of the license they choose before applying it. Licensors should also secure all rights necessary before applying our licenses so that the public can reuse the material as expected. Licensors should clearly mark any material not subject to the license. This includes other CC- licensed material, or material used under an exception or limitation to copyright. More considerations for licensors: wiki.creativecommons.org/Considerations_for_licensors Considerations for the public: By using one of our public licenses, a licensor grants the public permission to use the licensed material under specified terms and conditions. If the licensor's permission is not necessary for any reason--for example, because of any applicable exception or limitation to copyright--then that use is not regulated by the license. Our licenses grant only permissions under copyright and certain other rights that a licensor has authority to grant. Use of the licensed material may still be restricted for other reasons, including because others have copyright or other rights in the material. A licensor may make special requests, such as asking that all changes be marked or described. Although not required by our licenses, you are encouraged to respect those requests where reasonable. More considerations for the public: wiki.creativecommons.org/Considerations_for_licensees ======================================================================= Creative Commons Attribution-ShareAlike 4.0 International Public License By exercising the Licensed Rights (defined below), You accept and agree to be bound by the terms and conditions of this Creative Commons Attribution-ShareAlike 4.0 International Public License ("Public License"). To the extent this Public License may be interpreted as a contract, You are granted the Licensed Rights in consideration of Your acceptance of these terms and conditions, and the Licensor grants You such rights in consideration of benefits the Licensor receives from making the Licensed Material available under these terms and conditions. Section 1 -- Definitions. a. Adapted Material means material subject to Copyright and Similar Rights that is derived from or based upon the Licensed Material and in which the Licensed Material is translated, altered, arranged, transformed, or otherwise modified in a manner requiring permission under the Copyright and Similar Rights held by the Licensor. For purposes of this Public License, where the Licensed Material is a musical work, performance, or sound recording, Adapted Material is always produced where the Licensed Material is synched in timed relation with a moving image. b. Adapter's License means the license You apply to Your Copyright and Similar Rights in Your contributions to Adapted Material in accordance with the terms and conditions of this Public License. c. BY-SA Compatible License means a license listed at creativecommons.org/compatiblelicenses, approved by Creative Commons as essentially the equivalent of this Public License. d. Copyright and Similar Rights means copyright and/or similar rights closely related to copyright including, without limitation, performance, broadcast, sound recording, and Sui Generis Database Rights, without regard to how the rights are labeled or categorized. For purposes of this Public License, the rights specified in Section 2(b)(1)-(2) are not Copyright and Similar Rights. e. Effective Technological Measures means those measures that, in the absence of proper authority, may not be circumvented under laws fulfilling obligations under Article 11 of the WIPO Copyright Treaty adopted on December 20, 1996, and/or similar international agreements. f. Exceptions and Limitations means fair use, fair dealing, and/or any other exception or limitation to Copyright and Similar Rights that applies to Your use of the Licensed Material. g. License Elements means the license attributes listed in the name of a Creative Commons Public License. The License Elements of this Public License are Attribution and ShareAlike. h. Licensed Material means the artistic or literary work, database, or other material to which the Licensor applied this Public License. i. Licensed Rights means the rights granted to You subject to the terms and conditions of this Public License, which are limited to all Copyright and Similar Rights that apply to Your use of the Licensed Material and that the Licensor has authority to license. j. Licensor means the individual(s) or entity(ies) granting rights under this Public License. k. Share means to provide material to the public by any means or process that requires permission under the Licensed Rights, such as reproduction, public display, public performance, distribution, dissemination, communication, or importation, and to make material available to the public including in ways that members of the public may access the material from a place and at a time individually chosen by them. l. Sui Generis Database Rights means rights other than copyright resulting from Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, as amended and/or succeeded, as well as other essentially equivalent rights anywhere in the world. m. You means the individual or entity exercising the Licensed Rights under this Public License. Your has a corresponding meaning. Section 2 -- Scope. a. License grant. 1. Subject to the terms and conditions of this Public License, the Licensor hereby grants You a worldwide, royalty-free, non-sublicensable, non-exclusive, irrevocable license to exercise the Licensed Rights in the Licensed Material to: a. reproduce and Share the Licensed Material, in whole or in part; and b. produce, reproduce, and Share Adapted Material. 2. Exceptions and Limitations. For the avoidance of doubt, where Exceptions and Limitations apply to Your use, this Public License does not apply, and You do not need to comply with its terms and conditions. 3. Term. The term of this Public License is specified in Section 6(a). 4. Media and formats; technical modifications allowed. The Licensor authorizes You to exercise the Licensed Rights in all media and formats whether now known or hereafter created, and to make technical modifications necessary to do so. The Licensor waives and/or agrees not to assert any right or authority to forbid You from making technical modifications necessary to exercise the Licensed Rights, including technical modifications necessary to circumvent Effective Technological Measures. For purposes of this Public License, simply making modifications authorized by this Section 2(a) (4) never produces Adapted Material. 5. Downstream recipients. a. Offer from the Licensor -- Licensed Material. Every recipient of the Licensed Material automatically receives an offer from the Licensor to exercise the Licensed Rights under the terms and conditions of this Public License. b. Additional offer from the Licensor -- Adapted Material. Every recipient of Adapted Material from You automatically receives an offer from the Licensor to exercise the Licensed Rights in the Adapted Material under the conditions of the Adapter's License You apply. c. No downstream restrictions. You may not offer or impose any additional or different terms or conditions on, or apply any Effective Technological Measures to, the Licensed Material if doing so restricts exercise of the Licensed Rights by any recipient of the Licensed Material. 6. No endorsement. Nothing in this Public License constitutes or may be construed as permission to assert or imply that You are, or that Your use of the Licensed Material is, connected with, or sponsored, endorsed, or granted official status by, the Licensor or others designated to receive attribution as provided in Section 3(a)(1)(A)(i). b. Other rights. 1. Moral rights, such as the right of integrity, are not licensed under this Public License, nor are publicity, privacy, and/or other similar personality rights; however, to the extent possible, the Licensor waives and/or agrees not to assert any such rights held by the Licensor to the limited extent necessary to allow You to exercise the Licensed Rights, but not otherwise. 2. Patent and trademark rights are not licensed under this Public License. 3. To the extent possible, the Licensor waives any right to collect royalties from You for the exercise of the Licensed Rights, whether directly or through a collecting society under any voluntary or waivable statutory or compulsory licensing scheme. In all other cases the Licensor expressly reserves any right to collect such royalties. Section 3 -- License Conditions. Your exercise of the Licensed Rights is expressly made subject to the following conditions. a. Attribution. 1. If You Share the Licensed Material (including in modified form), You must: a. retain the following if it is supplied by the Licensor with the Licensed Material: i. identification of the creator(s) of the Licensed Material and any others designated to receive attribution, in any reasonable manner requested by the Licensor (including by pseudonym if designated); ii. a copyright notice; iii. a notice that refers to this Public License; iv. a notice that refers to the disclaimer of warranties; v. a URI or hyperlink to the Licensed Material to the extent reasonably practicable; b. indicate if You modified the Licensed Material and retain an indication of any previous modifications; and c. indicate the Licensed Material is licensed under this Public License, and include the text of, or the URI or hyperlink to, this Public License. 2. You may satisfy the conditions in Section 3(a)(1) in any reasonable manner based on the medium, means, and context in which You Share the Licensed Material. For example, it may be reasonable to satisfy the conditions by providing a URI or hyperlink to a resource that includes the required information. 3. If requested by the Licensor, You must remove any of the information required by Section 3(a)(1)(A) to the extent reasonably practicable. b. ShareAlike. In addition to the conditions in Section 3(a), if You Share Adapted Material You produce, the following conditions also apply. 1. The Adapter's License You apply must be a Creative Commons license with the same License Elements, this version or later, or a BY-SA Compatible License. 2. You must include the text of, or the URI or hyperlink to, the Adapter's License You apply. You may satisfy this condition in any reasonable manner based on the medium, means, and context in which You Share Adapted Material. 3. You may not offer or impose any additional or different terms or conditions on, or apply any Effective Technological Measures to, Adapted Material that restrict exercise of the rights granted under the Adapter's License You apply. Section 4 -- Sui Generis Database Rights. Where the Licensed Rights include Sui Generis Database Rights that apply to Your use of the Licensed Material: a. for the avoidance of doubt, Section 2(a)(1) grants You the right to extract, reuse, reproduce, and Share all or a substantial portion of the contents of the database; b. if You include all or a substantial portion of the database contents in a database in which You have Sui Generis Database Rights, then the database in which You have Sui Generis Database Rights (but not its individual contents) is Adapted Material, including for purposes of Section 3(b); and c. You must comply with the conditions in Section 3(a) if You Share all or a substantial portion of the contents of the database. For the avoidance of doubt, this Section 4 supplements and does not replace Your obligations under this Public License where the Licensed Rights include other Copyright and Similar Rights. Section 5 -- Disclaimer of Warranties and Limitation of Liability. a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. c. The disclaimer of warranties and limitation of liability provided above shall be interpreted in a manner that, to the extent possible, most closely approximates an absolute disclaimer and waiver of all liability. Section 6 -- Term and Termination. a. This Public License applies for the term of the Copyright and Similar Rights licensed here. However, if You fail to comply with this Public License, then Your rights under this Public License terminate automatically. b. Where Your right to use the Licensed Material has terminated under Section 6(a), it reinstates: 1. automatically as of the date the violation is cured, provided it is cured within 30 days of Your discovery of the violation; or 2. upon express reinstatement by the Licensor. For the avoidance of doubt, this Section 6(b) does not affect any right the Licensor may have to seek remedies for Your violations of this Public License. c. For the avoidance of doubt, the Licensor may also offer the Licensed Material under separate terms or conditions or stop distributing the Licensed Material at any time; however, doing so will not terminate this Public License. d. Sections 1, 5, 6, 7, and 8 survive termination of this Public License. Section 7 -- Other Terms and Conditions. a. The Licensor shall not be bound by any additional or different terms or conditions communicated by You unless expressly agreed. b. Any arrangements, understandings, or agreements regarding the Licensed Material not stated herein are separate from and independent of the terms and conditions of this Public License. Section 8 -- Interpretation. a. For the avoidance of doubt, this Public License does not, and shall not be interpreted to, reduce, limit, restrict, or impose conditions on any use of the Licensed Material that could lawfully be made without permission under this Public License. b. To the extent possible, if any provision of this Public License is deemed unenforceable, it shall be automatically reformed to the minimum extent necessary to make it enforceable. If the provision cannot be reformed, it shall be severed from this Public License without affecting the enforceability of the remaining terms and conditions. c. No term or condition of this Public License will be waived and no failure to comply consented to unless expressly agreed to by the Licensor. d. Nothing in this Public License constitutes or may be interpreted as a limitation upon, or waiver of, any privileges and immunities that apply to the Licensor or You, including from the legal processes of any jurisdiction or authority. ======================================================================= Creative Commons is not a party to its public licenses. Notwithstanding, Creative Commons may elect to apply one of its public licenses to material it publishes and in those instances will be considered the “Licensor.†The text of the Creative Commons public licenses is dedicated to the public domain under the CC0 Public Domain Dedication. Except for the limited purpose of indicating that material is shared under a Creative Commons public license or as otherwise permitted by the Creative Commons policies published at creativecommons.org/policies, Creative Commons does not authorize the use of the trademark "Creative Commons" or any other trademark or logo of Creative Commons without its prior written consent including, without limitation, in connection with any unauthorized modifications to any of its public licenses or any other arrangements, understandings, or agreements concerning use of licensed material. For the avoidance of doubt, this paragraph does not form part of the public licenses. Creative Commons may be contacted at creativecommons.org. ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1665611396.8046114 agordejo-0.4.2/documentation/agordejo.10000644000175000017500000000535114321633205016573 0ustar00nilsnils.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.2. .TH AGORDEJO "1" "October 2022" "Agordejo 0.4.2" "User Commands" .SH NAME Agordejo - Music and audio production session manager based on NSM. .SH DESCRIPTION usage: agordejo [\-h] [\-v] [\-V] [\-u URL] [\-\-nsm\-url URL] [\-l SESSION] [\-c] [\-i] .IP [\-\-session\-root SESSIONROOT] .PP Agordejo \- Version 0.4.2 \- Copyright 2022 by Laborejo Software Suite \- https://www.laborejo.org/agordejo .SS "options:" .TP \fB\-h\fR, \fB\-\-help\fR show this help message and exit .TP \fB\-v\fR, \fB\-\-version\fR show program's version number and exit .TP \fB\-V\fR, \fB\-\-verbose\fR (Development) Switch the logger to INFO and print out all kinds of information to get a high\-level idea of what the program is doing. You can also set the environment variable LSS_DEBUG=1 .TP \fB\-u\fR URL, \fB\-\-url\fR URL Force URL for the session. If there is already a running session we will connect to it. Otherwise we will start one there. Default is local host with random port. Example: osc.udp://myhost.localdomain:14294/ .TP \fB\-\-nsm\-url\fR URL Same as \fB\-\-url\fR. .TP \fB\-l\fR SESSION, \fB\-\-load\-session\fR SESSION Session to open on startup, must exist. Overrides \fB\-\-continue\fR .TP \fB\-c\fR, \fB\-\-continue\fR Autostart last active session. .TP \fB\-i\fR, \fB\-\-hide\fR Start GUI hidden in tray, only if tray available on system. .TP \fB\-\-session\-root\fR SESSIONROOT Root directory of all sessions. Defaults to $XDG_DATA_HOME/nsm/ .SH USAGE Agordejo (Esperanto: 'place to set things up') is a music production session manager. It is used to start your programs, remember their (JACK) interconnections and make your life easier in general. You can create a session, or project, add programs to it and then use commands to save, start/stop, hide/show all programs at once, or individually. At a later date you can then re-open the session and continue where you left off. Agordejo does not re-invent the wheel but instead uses the New-Session-Manager daemon and enhances it with some tricks of its own, that always remain 100% compatible with the original sessions. .SH EXAMPLES Run agordejo. You are now in the session overview. Press the "Quick New" button and add programs by a double clicking on the list. Try right-clicking on many things to get more options in context menus. .SH "REPORTING BUGS" https://www.laborejo.org/bugs .SH COPYRIGHT Agordejo 0.4.2 - Copyright 2022 Laborejo Software Suite https://www.laborejo.org/ .SH "SEE ALSO" The full documentation for Agordejo is maintained as a multi-lingual html site to your systems doc-dir. For example: xdg-open file:///usr/share/doc/agordejo/index.html The documentation can also be found online https://www.laborejo.org/documentation/agordejo ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1665611335.957255 agordejo-0.4.2/documentation/build-documentation.sh0000644000175000017500000000073414321633110021214 0ustar00nilsnils#!/bin/sh #The documentation is built statically and does not belong to the normal build process with configure and make #Its updating is part of the development process, not packaging and running. #The correct out/ dir is already part of git. set -e asciidoctor index.adoc -o out/index.html asciidoctor german.adoc -o out/german.html asciidoctor english.adoc -o out/english.html cp ../desktop/images/favicon.* out/ cp ../desktop/images/1248-transparent.png out/logo.png ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1665611335.957255 agordejo-0.4.2/documentation/build.py0000644000175000017500000001007014321633110016355 0ustar00nilsnils#! /usr/bin/env python3 # -*- coding: utf-8 -*- """ This documentation is licensed under Creative Commons-BY-SA-4.0. Please read the provided documentation/LICENSE file or visit https://creativecommons.org/licenses/by-sa/4.0/legalcode The documentation is built statically and does not belong to the normal build process with configure and make Its updating is part of the development process, not packaging and running. The correct out/ dir is already part of git. .adoc is asciidoctor, not simple asciidoc. """ #Make the readme import sys sys.path.append("../engine") from config import METADATA import subprocess from os import getcwd import os.path assert os.path.exists(os.path.join(getcwd(), __file__)), (getcwd(), __file__) import datetime #Readme with open("readme.template", "r") as r: template_readme = r.read() template_readme = template_readme.replace("", datetime.datetime.now().isoformat()) template_readme = template_readme.replace("", METADATA["name"]) template_readme = template_readme.replace("", METADATA["version"]) template_readme = template_readme.replace("", METADATA["shortName"]) template_readme = template_readme.replace("", METADATA["description"]) template_readme = template_readme.replace("", METADATA["dependencies"]) template_readme = template_readme.replace("", METADATA["author"]) with open ("../README.md", "w") as w: w.write(template_readme) print ("Built /README.md") #Documentation index with open("index.adoc.template", "r") as r: template_index = r.read() template_index = template_index.replace("", METADATA["name"]) template_index = template_index.replace("", METADATA["shortName"]) template_index = template_index.replace("", METADATA["version"]) template_index = template_index.replace("", METADATA["author"]) with open ("index.adoc", "w") as w: w.write(template_index) #Documentation METADATA["supportedLanguages"].update({"English":""}) for language in METADATA["supportedLanguages"].keys(): language = language.lower() try: with open(f"{language}.adoc.template", "r") as r: template = r.read() except: continue #language not yet supported as manual for key, value in METADATA.items(): #all strings if type(value) is str: template = template.replace(f"<{key}>", value) if language == "english": template = template.replace("", "== Introduction\n\n" + METADATA["description"]) with open (f"{language}.part.adoc", "r") as clientPart: template = template.replace("", clientPart.read()) with open (f"{language}.adoc", "w") as w: w.write(template) #Create manpage #Needs help2man manpage_template = f""" [name] {METADATA["name"]} - {METADATA["tagline"]} [usage] {METADATA["description"]} [Reporting bugs] https://www.laborejo.org/bugs [copyright] {METADATA["name"]} {METADATA["version"]} - Copyright {METADATA["year"]} {METADATA["author"]} https://www.laborejo.org/ [examples] Run agordejo. You are now in the session overview. Press the "Quick New" button and add programs by a double clicking on the list. Try right-clicking on many things to get more options in context menus. [see also] The full documentation for {METADATA["name"]} is maintained as a multi-lingual html site to your systems doc-dir. For example: xdg-open file:///usr/share/doc/{METADATA["shortName"]}/index.html The documentation can also be found online https://www.laborejo.org/documentation/{METADATA["shortName"]} """ with open ("manpageinclude.h2m", "w") as w: w.write(manpage_template) command = f"help2man ../{METADATA['shortName']} --no-info --include manpageinclude.h2m > {METADATA['shortName']}.1" subprocess.run(command, capture_output=True, text=True, shell=True) #help2man for the much simpler nsm-data commandNsmDataMNan = "help2man ../tools/nsm-data --no-info > nsm-data.1" subprocess.run(commandNsmDataMNan, capture_output=True, text=True, shell=True) print ("Built. You still need to run") print ("sh build-documentation.sh") ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1665611335.9539216 agordejo-0.4.2/documentation/css/normalize.css0000644000175000017500000001632514321633110020217 0ustar00nilsnils/*! normalize.css v6.0.0 | MIT License | github.com/necolas/normalize.css */ /* Document ========================================================================== */ /** * 1. Correct the line height in all browsers. * 2. Prevent adjustments of font size after orientation changes in * IE on Windows Phone and in iOS. */ html { line-height: 1.15; /* 1 */ -ms-text-size-adjust: 100%; /* 2 */ -webkit-text-size-adjust: 100%; /* 2 */ } /* Sections ========================================================================== */ /** * Add the correct display in IE 9-. */ article, aside, footer, header, nav, section { display: block; } /** * Correct the font size and margin on `h1` elements within `section` and * `article` contexts in Chrome, Firefox, and Safari. */ h1 { font-size: 2em; margin: 0.67em 0; } /* Grouping content ========================================================================== */ /** * Add the correct display in IE 9-. * 1. Add the correct display in IE. */ figcaption, figure, main { /* 1 */ display: block; } /** * Add the correct margin in IE 8. */ figure { margin: 1em 40px; } /** * 1. Add the correct box sizing in Firefox. * 2. Show the overflow in Edge and IE. */ hr { box-sizing: content-box; /* 1 */ height: 0; /* 1 */ overflow: visible; /* 2 */ } /** * 1. Correct the inheritance and scaling of font size in all browsers. * 2. Correct the odd `em` font sizing in all browsers. */ pre { font-family: monospace, monospace; /* 1 */ font-size: 1em; /* 2 */ } /* Text-level semantics ========================================================================== */ /** * 1. Remove the gray background on active links in IE 10. * 2. Remove gaps in links underline in iOS 8+ and Safari 8+. */ a { background-color: transparent; /* 1 */ -webkit-text-decoration-skip: objects; /* 2 */ } /** * 1. Remove the bottom border in Chrome 57- and Firefox 39-. * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari. */ abbr[title] { border-bottom: none; /* 1 */ text-decoration: underline; /* 2 */ text-decoration: underline dotted; /* 2 */ } /** * Prevent the duplicate application of `bolder` by the next rule in Safari 6. */ b, strong { font-weight: inherit; } /** * Add the correct font weight in Chrome, Edge, and Safari. */ b, strong { font-weight: bolder; } /** * 1. Correct the inheritance and scaling of font size in all browsers. * 2. Correct the odd `em` font sizing in all browsers. */ code, kbd, samp { font-family: monospace, monospace; /* 1 */ font-size: 1em; /* 2 */ } /** * Add the correct font style in Android 4.3-. */ dfn { font-style: italic; } /** * Add the correct background and color in IE 9-. */ mark { background-color: #ff0; color: #000; } /** * Add the correct font size in all browsers. */ small { font-size: 80%; } /** * Prevent `sub` and `sup` elements from affecting the line height in * all browsers. */ sub, sup { font-size: 75%; line-height: 0; position: relative; vertical-align: baseline; } sub { bottom: -0.25em; } sup { top: -0.5em; } /* Embedded content ========================================================================== */ /** * Add the correct display in IE 9-. */ audio, video { display: inline-block; } /** * Add the correct display in iOS 4-7. */ audio:not([controls]) { display: none; height: 0; } /** * Remove the border on images inside links in IE 10-. */ img { border-style: none; } /** * Hide the overflow in IE. */ svg:not(:root) { overflow: hidden; } /* Forms ========================================================================== */ /** * Remove the margin in Firefox and Safari. */ button, input, optgroup, select, textarea { margin: 0; } /** * Show the overflow in IE. * 1. Show the overflow in Edge. */ button, input { /* 1 */ overflow: visible; } /** * Remove the inheritance of text transform in Edge, Firefox, and IE. * 1. Remove the inheritance of text transform in Firefox. */ button, select { /* 1 */ text-transform: none; } /** * 1. Prevent a WebKit bug where (2) destroys native `audio` and `video` * controls in Android 4. * 2. Correct the inability to style clickable types in iOS and Safari. */ button, html [type="button"], /* 1 */ [type="reset"], [type="submit"] { -webkit-appearance: button; /* 2 */ } /** * Remove the inner border and padding in Firefox. */ button::-moz-focus-inner, [type="button"]::-moz-focus-inner, [type="reset"]::-moz-focus-inner, [type="submit"]::-moz-focus-inner { border-style: none; padding: 0; } /** * Restore the focus styles unset by the previous rule. */ button:-moz-focusring, [type="button"]:-moz-focusring, [type="reset"]:-moz-focusring, [type="submit"]:-moz-focusring { outline: 1px dotted ButtonText; } /** * 1. Correct the text wrapping in Edge and IE. * 2. Correct the color inheritance from `fieldset` elements in IE. * 3. Remove the padding so developers are not caught out when they zero out * `fieldset` elements in all browsers. */ legend { box-sizing: border-box; /* 1 */ color: inherit; /* 2 */ display: table; /* 1 */ max-width: 100%; /* 1 */ padding: 0; /* 3 */ white-space: normal; /* 1 */ } /** * 1. Add the correct display in IE 9-. * 2. Add the correct vertical alignment in Chrome, Firefox, and Opera. */ progress { display: inline-block; /* 1 */ vertical-align: baseline; /* 2 */ } /** * Remove the default vertical scrollbar in IE. */ textarea { overflow: auto; } /** * 1. Add the correct box sizing in IE 10-. * 2. Remove the padding in IE 10-. */ [type="checkbox"], [type="radio"] { box-sizing: border-box; /* 1 */ padding: 0; /* 2 */ } /** * Correct the cursor style of increment and decrement buttons in Chrome. */ [type="number"]::-webkit-inner-spin-button, [type="number"]::-webkit-outer-spin-button { height: auto; } /** * 1. Correct the odd appearance in Chrome and Safari. * 2. Correct the outline style in Safari. */ [type="search"] { -webkit-appearance: textfield; /* 1 */ outline-offset: -2px; /* 2 */ } /** * Remove the inner padding and cancel buttons in Chrome and Safari on macOS. */ [type="search"]::-webkit-search-cancel-button, [type="search"]::-webkit-search-decoration { -webkit-appearance: none; } /** * 1. Correct the inability to style clickable types in iOS and Safari. * 2. Change font properties to `inherit` in Safari. */ ::-webkit-file-upload-button { -webkit-appearance: button; /* 1 */ font: inherit; /* 2 */ } /* Interactive ========================================================================== */ /* * Add the correct display in IE 9-. * 1. Add the correct display in Edge, IE, and Firefox. */ details, /* 1 */ menu { display: block; } /* * Add the correct display in all browsers. */ summary { display: list-item; } /* Scripting ========================================================================== */ /** * Add the correct display in IE 9-. */ canvas { display: inline-block; } /** * Add the correct display in IE. */ template { display: none; } /* Hidden ========================================================================== */ /** * Add the correct display in IE 10-. */ [hidden] { display: none; } ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1665611335.9539216 agordejo-0.4.2/documentation/css/style.css0000644000175000017500000000441614321633110017355 0ustar00nilsnils/* https://github.com/markdowncss/retro */ pre, code { font-family: Menlo, Monaco, "Courier New", monospace; background-color: rgb(20,20,20); } pre { padding: .5rem; line-height: 1.25; overflow-x: scroll; } @media print { *, *:before, *:after { background: transparent !important; color: #000 !important; box-shadow: none !important; text-shadow: none !important; } a, a:visited { text-decoration: underline; } a[href]:after { content: " (" attr(href) ")"; } abbr[title]:after { content: " (" attr(title) ")"; } a[href^="#"]:after, a[href^="javascript:"]:after { content: ""; } pre, blockquote { border: 1px solid #999; page-break-inside: avoid; } thead { display: table-header-group; } tr, img { page-break-inside: avoid; } img { max-width: 100% !important; } p, h2, h3 { orphans: 3; widows: 3; } h2, h3 { page-break-after: avoid; } } a, a:visited { color: #01ff70; } a:hover, a:focus, a:active { color: #2ecc40; } .retro-no-decoration { text-decoration: none; } html { font-size: 12px; } @media screen and (min-width: 32rem) and (max-width: 48rem) { html { font-size: 15px; } } @media screen and (min-width: 48rem) { html { font-size: 16px; } } body { line-height: 1.85; } p, .retro-p { font-size: 1rem; margin-bottom: 1.3rem; } h1, .retro-h1, h2, .retro-h2, h3, .retro-h3, h4, .retro-h4 { margin: 1.414rem 0 .5rem; font-weight: inherit; line-height: 1.42; } h1, .retro-h1 { margin-top: 0; font-size: 2.698rem; } h2, .retro-h2 { font-size: 1.827rem; } h3, .retro-h3 { font-size: 1.0rem; } h4, .retro-h4 { font-size: 0.9em; } h5, .retro-h5 { font-size: 0.7rem; } h6, .retro-h6 { font-size: .88rem; } small, .retro-small { font-size: .707em; } /* https://github.com/mrmrs/fluidity */ img, canvas, iframe, video, svg, select, textarea { max-width: 100%; } html, body { background-color: #222; min-height: 100%; } html { font-size: 18px; } body { color: #fafafa; font-family: "Courier New"; line-height: 1.45; margin: 6rem auto 1rem; max-width: 48rem; padding: .25rem; } pre { background-color: #333; } blockquote { border-left: 3px solid #01ff70; padding-left: 1rem; } ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1665611396.701277 agordejo-0.4.2/documentation/english.adoc0000644000175000017500000005146214321633205017204 0ustar00nilsnils:Author: Laborejo Software Suite :Version: 0.4.2 :iconfont-remote!: :!webfonts: //// This documentation is licensed under the Creative Commons Attribution-ShareAlike 4.0 International License. To view a copy of this license, visit http://creativecommons.org/licenses/by-sa/4.0/ or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. A copy of the license has been provided in the file documentation/LICENSE. //// //// https://powerman.name/doc/asciidoc https://asciidoctor.org/docs/user-manual/ //// :sectnums: :toc: left :toc-title: Table of Contents :toclevels: 3 = Agordejo // Don't write in the empty line above line. It will be interpreted as author html tag For program version 0.4.2 == Introduction Agordejo (Esperanto: 'place to set things up') is a music production session manager. It is used to start your programs, remember their (JACK) interconnections and make your life easier in general. You can create a session, or project, add programs to it and then use commands to save, start/stop, hide/show all programs at once, or individually. At a later date you can then re-open the session and continue where you left off. Agordejo does not re-invent the wheel but instead uses the New-Session-Manager daemon and enhances it with some tricks of its own, that always remain 100% compatible with the original sessions. == Preamble Session Management leads to simplification of workflows, overview and control over programs and data and a good portion of convenience :) No program exists on its own, because no program can do everything that is necessary for today's music production. This is obvious in a JACK environment, which is fundamentally modular: Different programs fulfill different functions and "talk" to each other by sending data to each other. A sequencer sends MIDI to a sampler or synthesizer, which is connected to a plug-in host for effects etc. Even the most monolithic all-in-one DAWs have to, or want to, eventually connect to the outside world. For example, to connect to a screen recorder or streaming program, include a word processor for recording order or lyrics, or to use a function that is simply not available in the DAW. Much of the work is already done by the JACK subsystem. All programs can share their music in real time, have synchronized timelines and play in the same tempo. What remains is the tedious work of always starting all programs, loading project files, connecting audio channels etc. Session management in general (e.g. specifically written starter script files) and Agordejo in particular do this work for you, or at the very least, greatly simplify it. In contrast to the self-written script mentioned above, you don't have to decide in advance on a setup, but everything is saved automatically as long as you manage everything through Agordejo. === Example * Start Agordejo (Start menu, terminal etc.) * Press the button "Quick New" on the left * Now you get a choice of programs: * A double single click with the mouse starts a program * The program appears as entry in the other column and indicates that it is running * To show it's GUI you probably need to double click on this new entry. * The programs "jackpatch" and "nsm-data" were already added automatically. * Audio and midi ports can now be connected together in a patchbay. The connections are stored in Agordejo. * The name of the session so far is simply a date. Through the menu you can enter a real name. Like "My song" * If you are finished you can return to the session selection by using the menus "Save and Close" * Now Agordejo could be closed itself. * All stored data is in a single directory on the hard disk (`~/.local/share/nms/My Song`) * The session can be resumed: After double clicking on the name, all programs start automatically and connect their JACK ports to among themselves. == Full Explanation === Selecting a Session Sessions are displayed as a table, which you sort by clicking on a column header. Here is shown how the session is called and when it was saved the last time, probably the two most important pieces of information. Also shown is how many programs/clients are in the session and whether it contains symbolic links. The latter is probably set to "Yes" if use a sampler, or similar, that contains large audio files. These are initially only linked into the session, and not copied, to save disk space. The displayed disk space usage is not the actual one, but includes the sizes of symlink-targets. Only when you archive the session or replace the links with real files the number becomes correct. Finally, the directory in which the files are actually stored is also given. Sessions represent the directory tree. A session is always a "leaf" and cannot include subsessions. When creating or renaming sessions, you can also arrange them in the tree by using the usual slash notation: `song123` -> `New album/song 123` or `Test/asdf` -> `Romantic pop ballads/My heart will keep beating`. How to organize your sessions and how many subdirectories you create is up to you. It is, however, not allowed to start the name with `/` or use the special characters `..`. The tree view can be deactivated by a checkbox on the left side, e.g. to be able to sort them. This is just a view, your data remains untouched. Each session has a context menu (e.g. right mouse button) with further options: You can choose to rename a session, delete it (including all associated files on the hard disk!) and more. These functions are equivalent to your file manager. If you like, you can also use your file manager to rename, move, or delete the session directories themselves (unless they are is currently open). You don't need to restart Agordejo to do this, it will respond to the changes while running. Click on "New session" to create one. In contrast to the quick view, you must enter the name directly . As mentioned above, you can use the directory tree. In addition, there are several (almost obligatory) programs suggested to start with. Normally one should accept all suggestions. There is always the option to remove them later. A double click on an existing session (or the "Load Selected" button) does just that. === In a Session The view is divided into three areas: Program starter, programs in the current session and the session notes. There is also a dynamic menu. On the left side you see the program starter. A double-click starts a program instance in this session. You can also start a program more than once. For available programs please refer to the chapter "Program Database". ==== Running Programs Started programs are located on the right side. A double-click switches the visibility to hide its window if the program supports it. If not, nothing happens. The following information is available per program: * The name (possibly with icon) * A "label" that programs can use freely (e.g. Fluajho shows the loaded .sf2 here) * The program status * Stopped , not running * Ready, running * Launch, If the status halts here but the programs works, it is one that does not specifically support session mode. Agordejo cannot know if it is already running or not. Everything is fine! :) * Other states are only transitions and usually only visible for a very short time, e.g. Open / Loading * Visibility (A cross for visible, blank for invisible) * Changes - Are there currently unsaved changes? * ID - A unique identifier that can be used to distinguish between multiple instances of the same program All other functions are accessible via the menu or context menu. One click on a program selects it, and the client menu in the menu bar will now apply to the client. Alternatively, you can right-click on an entry for a context menu, which is identical to the menu. In addition to self-explanatory functions there are also: * Rename gives the program a self-chosen name, mostly to make its purpose clear and to distinguish it better from others. This feature is only available when 'nsm-data' is running in the session. * Save only tells this program to save * Remove takes the program out of the session. However, no files are deleted in the process. At the moment you have to "clean up" in your file manager by hand. If the client "nsm-data" is in the session (this is the default setting) the lower area provides a large text field for notes. It is the same as in the quick view. Write what you want: TODO lists, lyrics, credits and sources from external Samples etc. ==== The Session Menu In contrast to the quick view, full mode offers menus, which can also be accessed via the usual keyboard shortcuts (Ctrl+S for saving etc.). * Save instructs all programs to save, the session continues to run * Save and Close ends the session, after all programs saved * Abort ends the session without saving the programs * Save As saves the session under a different name and closes the current session without saving. From now on you work under the new name. * Add Client offers the option to add any program, whether it is in the program database or not. * Any installed programs are suggested. Agordejo doesn't check them for usefulness for a music session, or even for runnability. You will find `ls` here as well as `agordejo` itself. == Program-Database Agordejos launcher is based on a program database, which is partly self-generated, partly maintained by hand. As in a start menu Agordejo will offer you only programs that are actually installed on your system. The database is created at the first start. Depending on your system, this can take some moments to a few minutes. If you are reinstalling or uninstalling audio programs, you will need to update the database via the command in the control menu. Program installations and system changes are even possible while Agordejo is running (even in a session). After a DB update you can immediately access all new programs. If you do not see an installed program in our launcher, but you are sure that it supports session management please report it to info@laborejo.org or under https://laborejo.org/bugs . In addition, you can add (in full view) programs that are not in the database. === For advanced users The strict rule is that only programs in the $PATH are included in the database. Absolutel paths are not allowed, even if you enter the program name yourself through the menu. However sometimes you just want to try out software, or you are a developer yourself and want to test without system-wide installation. In the control menu / settings is a tab "Program-PATH", where you can define and add your own search paths. One absolute directory path per line, no wildcards, trailing slash don't matter. For example: `/home/myuser/sources/newsequencer/bin/` These search paths are not stored in the session, but locally in your `~/.config` directory. == Tray Agordejo has a tray icon, if your window manager supports it. A click on the trayicon shows or hides Agordejo. If you close Agordejo using the normal window manager function, such as a click on the [X], the program and the session is not terminated, but minimized to the tray. A right click on the icon gives you access to common functions: You can directly start the most recently used sessions. If a session is already running you can save, cancel etc. Agordejo can also be completely exited here. == Program parameters As an advanced user, you can start Agordejo in the terminal and add some parameters.. For a complete list please use the --help parameter. For example: * `--session newAlbum/mySong` starts the given session. * `--continue` starts the last active session. * `--hide` starts Agordejo as TrayIcon. * `--url osc.udp://myhost.localdomain:14294/` connects to this server, if available, or starts the internal session server at this address. This is a very technical option and probably not needed. * `--session-root /home/user/production2030` sets the root directory. Only sessions in this directory are displayed, everything is stored here. The combination of `--continue` and `--hide` is essentially what many people expect from Session Management: Resuming at the previous state, without any extra windows in their way. If your system uses a start menu you will find not only the normal Agordejo starter but also "Agordejo Continue" to start this mode directly. == Miscellaneous / Explanations / FAQ *Session Save and Exit responds slowly*: Agordejo is not a standalone program like an word processor. The participating programs in the session are not plugins either. When you end the session a signal is sent to all participating client to save. This may take a few moments where you are able to see "live" how individual programs terminate and disappear from the session. Everything is fine. *I have added a program but it does not save with the session*: Does the program support session management? If not, Agordejo cannot do much. But you can ask the program developers to contact us (info@laborejo.org) and we can work together on support. *The programs hang on exit*: Sorry about that. Actually, the programs themselves are to blame, but we are also interested in improving the situation by offering at least an emergency solution in the future. *Agordejo won't start! I start the program but I can't see anything*: Most likely Agordejo is running, but invisible, because you exited it from the tray last time. Is it in the tray? A message should have popped up, maybe you missed it. If there is no tray in your window manager, the program should always be visible. With all these special window managers in Linux it may be that the tray detection did not work properly. Contingency plan is to delete `~/.config/LaborejoSoftwareSuite/agordejo`. This will NOT remove any sessions, but only local settings such as the visibility of the program window. At next start Agordejo will behave like the very first start. *JACK crashed. A lot of programs hang. What can I do to prevent data loss?* Probably already many programs in the session are not running properly and are not reacting anymore. The best thing to do is to use the 'Abort Session' function and restart everything. If the data has actually been unsaved for a long time, you can also dare to save/exit. It may be necessary to re-draw some jack, or all, jack connections by hand at the next start. If you want to be on the safe side, you can manually make a copy of the session directory in your file manager before ending the session (with inevitable crashes). *A program update broke my session because it can no longer load its files.* Unfortunately, this is a problem that even Agordejo can't solve. It also happens with LV2 plugins and with all other software, such as office programs. If you fear that a program becomes incompatible in the future, write down its version number in the session notes, so that you can at least, in an emergency, reinstall the old program version (even if this is very is cumbersome). *What's better? Monolithic DAW or session management?* Why not both? There is no conflict. Session management is worthwhile with two or more participating programs, which one needs almost always. You should not feel compelled to suddenly make everything modular with individual programs, only because you use a session manager. Agordejo is designed to make your music production easier. If it is faster and more comfortable to manage all plugins and effects e.g. in a single "Carla" instance then you should do exactly that. If you basically want to do everything in Ardour, do that, but start Ardour anyway in session management, because no program can do everything alone and the time will come where you add a second one. Session management is another level of hierarchy. Sequencers or DAWs are not plug-ins themselves. Patroneo does not belong "in" Ardour and Ardour does not belong "in" Laborejo. Already in this example each of the programs fulfils a different role because the others follow a different design philosophy and cannot ever offer the same workflow. And more: Some programs can't host plugins, some can't export audio files. They are not bad programs, but programs that concentrate on one task. Furthermore, there is a lot of software that does not directly do music production, but still is connected in the grander scheme: Open Broadcast Studio (OBS), music player, word processors and graphic programs etc. *Agordejo contains functionality which is not within its scope*: Music production is very complex and complexity is inevitable. It's like a waterbed: if you press down on one side, something bounces up in another place. When you create a "clean and lean" program, which therefore implements only a part of the complete workflow, then the missing part pops up somewhere else. A minimalistic session manager provokes plug-ins (not LV2), helper-scripts, workarounds and hacks. E.g. not to include file management provokes user errors like deleting the wrong files. If the SM knows what to do and it can do it, then let it do it. Or crashes: Technically, crashing programs are not the "problem" of the session managers, but they are part of the software reality. Crashes happen every day and need to be handled. Can Agordejo simplify the work and help to restore good conditions again? Then that should be done. Session management is also an opportunity to simplify even complex technical scenarios, e.g. distributing sessions over the network. == Installation and Start Agordejo is exclusive for Linux. The best way to install is to use your package manager. If it is not there, or only in an outdated version, please ask your Linux distribution to provide a recent version. If not available in the package repository you can build Agordejo yourself. .Build and Install * Please check the supplied README.md for dependencies. * You can download a release or clone the git version ** Download the latest version from https://www.laborejo.org/downloads and extract it. ** git clone https://git.laborejo.org/lss/agordejo.git * Change into the new directory and use these commands: * `./configure --prefix=/usr` ** The default prefix is /usr/local * `make` * `sudo make install` Now the program is available to run via your menu/launcher or `agordejo` in a terminal. Please read README.md for other ways of starting agordejo, which are impractical for actual use but can be helpful for testing and development. == Help and Development You can help Agordejo in several ways: Testing and reporting errors, translating, marketing, support, programming and more. === Testing and Reporting Errors If you find a bug in the program (or it runs too slow) please contact us in a way that suits you best. We are thankful for any help. .How to contact us * Report bugs and issues: https://www.laborejo.org/bugs * Website: https://www.laborejo.org * E-Mail: info@laborejo.org * If you see the opportunity and know that a developer will read it also forums, social media etc.. === Programming If you want to do some programming and don't know where to start please get in contact with us directly. The short version is: clone the git, change the code, create a git patch or point me to your public git. === Translations Agordejo is very easy to translate with the help of the Qt-Toolchain, without any need for programming. The easiest way is to contact the developers and they will setup the new language. However, here are the complete instructions for doing a translation completely on your own and integrating it into the program. You can add a new language like this: * Open a terminal and navigate to qtgui/resources/translations * Edit the file `config.pro` with a text editor ** Append the name of your language in the last line, in the form `XY.ts`, where XY is the language code. ** Make sure to leave a space between the individual languages entries. * Run `sh update.sh` in the same directory ** The program has now generated a new `.ts` file in the same directory. * Start Qt Linguist with `linguist-qt5` (may be named differently) and open your newly generated file * Select your "Target Language" and use the program to create a translation * Send us the `.ts` file, such as by e-mail to info@laborejo.org You can also incorporate the translation into Agordejo for testing purposes. This requires rudimentary Python knowledge. * Run the "Release" option in QtLinguists "File" menu. It creates a `.qm` file in the same directory as your `.ts` file. * Edit `qtgui/resources/resources.qrc` and duplicate the line `translations/de.qm` but change it to your new .qm file. * run `sh buildresources.sh` * Edit `engine/config.py`: add your language to the line that begins with "supportedLanguages" like this: `{"German": "de.qm", "Esperanto: "eo.qm"}` ** To find out your language string (German, Esperanto etc.) open the `python3` interpreter in a terminal and run the following command: ** `from PyQt5 import QtCore;QtCore.QLocale().languageToString(QtCore.QLocale().language())` To test the new translation you can either run the program normally, if your system is set to that language. Alternatively start agordejo via the terminal: * `LANGUAGE=de_DE.UTF-8 ./agordejo -V --save /dev/null` ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1665611335.957255 agordejo-0.4.2/documentation/english.adoc.template0000644000175000017500000001071014321633110021000 0ustar00nilsnils:Author: :Version: :iconfont-remote!: :!webfonts: //// This documentation is licensed under the Creative Commons Attribution-ShareAlike 4.0 International License. To view a copy of this license, visit http://creativecommons.org/licenses/by-sa/4.0/ or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. A copy of the license has been provided in the file documentation/LICENSE. //// //// https://powerman.name/doc/asciidoc https://asciidoctor.org/docs/user-manual/ //// :sectnums: :toc: left :toc-title: Table of Contents :toclevels: 3 = // Don't write in the empty line above line. It will be interpreted as author html tag For program version == Installation and Start is exclusive for Linux. The best way to install is to use your package manager. If it is not there, or only in an outdated version, please ask your Linux distribution to provide a recent version. If not available in the package repository you can build yourself. .Build and Install * Please check the supplied README.md for dependencies. * You can download a release or clone the git version ** Download the latest version from https://www.laborejo.org/downloads and extract it. ** git clone https://git.laborejo.org/lss/.git * Change into the new directory and use these commands: * `./configure --prefix=/usr` ** The default prefix is /usr/local * `make` * `sudo make install` Now the program is available to run via your menu/launcher or `` in a terminal. Please read README.md for other ways of starting , which are impractical for actual use but can be helpful for testing and development. == Help and Development You can help in several ways: Testing and reporting errors, translating, marketing, support, programming and more. === Testing and Reporting Errors If you find a bug in the program (or it runs too slow) please contact us in a way that suits you best. We are thankful for any help. .How to contact us * Report bugs and issues: https://www.laborejo.org/bugs * Website: https://www.laborejo.org * E-Mail: info@laborejo.org * If you see the opportunity and know that a developer will read it also forums, social media etc.. === Programming If you want to do some programming and don't know where to start please get in contact with us directly. The short version is: clone the git, change the code, create a git patch or point me to your public git. === Translations is very easy to translate with the help of the Qt-Toolchain, without any need for programming. The easiest way is to contact the developers and they will setup the new language. However, here are the complete instructions for doing a translation completely on your own and integrating it into the program. You can add a new language like this: * Open a terminal and navigate to qtgui/resources/translations * Edit the file `config.pro` with a text editor ** Append the name of your language in the last line, in the form `XY.ts`, where XY is the language code. ** Make sure to leave a space between the individual languages entries. * Run `sh update.sh` in the same directory ** The program has now generated a new `.ts` file in the same directory. * Start Qt Linguist with `linguist-qt5` (may be named differently) and open your newly generated file * Select your "Target Language" and use the program to create a translation * Send us the `.ts` file, such as by e-mail to info@laborejo.org You can also incorporate the translation into for testing purposes. This requires rudimentary Python knowledge. * Run the "Release" option in QtLinguists "File" menu. It creates a `.qm` file in the same directory as your `.ts` file. * Edit `qtgui/resources/resources.qrc` and duplicate the line `translations/de.qm` but change it to your new .qm file. * run `sh buildresources.sh` * Edit `engine/config.py`: add your language to the line that begins with "supportedLanguages" like this: `{"German": "de.qm", "Esperanto: "eo.qm"}` ** To find out your language string (German, Esperanto etc.) open the `python3` interpreter in a terminal and run the following command: ** `from PyQt5 import QtCore;QtCore.QLocale().languageToString(QtCore.QLocale().language())` To test the new translation you can either run the program normally, if your system is set to that language. Alternatively start via the terminal: * `LANGUAGE=de_DE.UTF-8 ./ -V --save /dev/null` ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1665611335.957255 agordejo-0.4.2/documentation/english.part.adoc0000644000175000017500000003740314321633110020143 0ustar00nilsnils== Preamble Session Management leads to simplification of workflows, overview and control over programs and data and a good portion of convenience :) No program exists on its own, because no program can do everything that is necessary for today's music production. This is obvious in a JACK environment, which is fundamentally modular: Different programs fulfill different functions and "talk" to each other by sending data to each other. A sequencer sends MIDI to a sampler or synthesizer, which is connected to a plug-in host for effects etc. Even the most monolithic all-in-one DAWs have to, or want to, eventually connect to the outside world. For example, to connect to a screen recorder or streaming program, include a word processor for recording order or lyrics, or to use a function that is simply not available in the DAW. Much of the work is already done by the JACK subsystem. All programs can share their music in real time, have synchronized timelines and play in the same tempo. What remains is the tedious work of always starting all programs, loading project files, connecting audio channels etc. Session management in general (e.g. specifically written starter script files) and Agordejo in particular do this work for you, or at the very least, greatly simplify it. In contrast to the self-written script mentioned above, you don't have to decide in advance on a setup, but everything is saved automatically as long as you manage everything through Agordejo. === Example * Start Agordejo (Start menu, terminal etc.) * Press the button "Quick New" on the left * Now you get a choice of programs: * A double single click with the mouse starts a program * The program appears as entry in the other column and indicates that it is running * To show it's GUI you probably need to double click on this new entry. * The programs "jackpatch" and "nsm-data" were already added automatically. * Audio and midi ports can now be connected together in a patchbay. The connections are stored in Agordejo. * The name of the session so far is simply a date. Through the menu you can enter a real name. Like "My song" * If you are finished you can return to the session selection by using the menus "Save and Close" * Now Agordejo could be closed itself. * All stored data is in a single directory on the hard disk (`~/.local/share/nms/My Song`) * The session can be resumed: After double clicking on the name, all programs start automatically and connect their JACK ports to among themselves. == Full Explanation === Selecting a Session Sessions are displayed as a table, which you sort by clicking on a column header. Here is shown how the session is called and when it was saved the last time, probably the two most important pieces of information. Also shown is how many programs/clients are in the session and whether it contains symbolic links. The latter is probably set to "Yes" if use a sampler, or similar, that contains large audio files. These are initially only linked into the session, and not copied, to save disk space. The displayed disk space usage is not the actual one, but includes the sizes of symlink-targets. Only when you archive the session or replace the links with real files the number becomes correct. Finally, the directory in which the files are actually stored is also given. Sessions represent the directory tree. A session is always a "leaf" and cannot include subsessions. When creating or renaming sessions, you can also arrange them in the tree by using the usual slash notation: `song123` -> `New album/song 123` or `Test/asdf` -> `Romantic pop ballads/My heart will keep beating`. How to organize your sessions and how many subdirectories you create is up to you. It is, however, not allowed to start the name with `/` or use the special characters `..`. The tree view can be deactivated by a checkbox on the left side, e.g. to be able to sort them. This is just a view, your data remains untouched. Each session has a context menu (e.g. right mouse button) with further options: You can choose to rename a session, delete it (including all associated files on the hard disk!) and more. These functions are equivalent to your file manager. If you like, you can also use your file manager to rename, move, or delete the session directories themselves (unless they are is currently open). You don't need to restart Agordejo to do this, it will respond to the changes while running. Click on "New session" to create one. In contrast to the quick view, you must enter the name directly . As mentioned above, you can use the directory tree. In addition, there are several (almost obligatory) programs suggested to start with. Normally one should accept all suggestions. There is always the option to remove them later. A double click on an existing session (or the "Load Selected" button) does just that. === In a Session The view is divided into three areas: Program starter, programs in the current session and the session notes. There is also a dynamic menu. On the left side you see the program starter. A double-click starts a program instance in this session. You can also start a program more than once. For available programs please refer to the chapter "Program Database". ==== Running Programs Started programs are located on the right side. A double-click switches the visibility to hide its window if the program supports it. If not, nothing happens. The following information is available per program: * The name (possibly with icon) * A "label" that programs can use freely (e.g. Fluajho shows the loaded .sf2 here) * The program status * Stopped , not running * Ready, running * Launch, If the status halts here but the programs works, it is one that does not specifically support session mode. Agordejo cannot know if it is already running or not. Everything is fine! :) * Other states are only transitions and usually only visible for a very short time, e.g. Open / Loading * Visibility (A cross for visible, blank for invisible) * Changes - Are there currently unsaved changes? * ID - A unique identifier that can be used to distinguish between multiple instances of the same program All other functions are accessible via the menu or context menu. One click on a program selects it, and the client menu in the menu bar will now apply to the client. Alternatively, you can right-click on an entry for a context menu, which is identical to the menu. In addition to self-explanatory functions there are also: * Rename gives the program a self-chosen name, mostly to make its purpose clear and to distinguish it better from others. This feature is only available when 'nsm-data' is running in the session. * Save only tells this program to save * Remove takes the program out of the session. However, no files are deleted in the process. At the moment you have to "clean up" in your file manager by hand. If the client "nsm-data" is in the session (this is the default setting) the lower area provides a large text field for notes. It is the same as in the quick view. Write what you want: TODO lists, lyrics, credits and sources from external Samples etc. ==== The Session Menu In contrast to the quick view, full mode offers menus, which can also be accessed via the usual keyboard shortcuts (Ctrl+S for saving etc.). * Save instructs all programs to save, the session continues to run * Save and Close ends the session, after all programs saved * Abort ends the session without saving the programs * Save As saves the session under a different name and closes the current session without saving. From now on you work under the new name. * Add Client offers the option to add any program, whether it is in the program database or not. * Any installed programs are suggested. Agordejo doesn't check them for usefulness for a music session, or even for runnability. You will find `ls` here as well as `agordejo` itself. == Program-Database Agordejos launcher is based on a program database, which is partly self-generated, partly maintained by hand. As in a start menu Agordejo will offer you only programs that are actually installed on your system. The database is created at the first start. Depending on your system, this can take some moments to a few minutes. If you are reinstalling or uninstalling audio programs, you will need to update the database via the command in the control menu. Program installations and system changes are even possible while Agordejo is running (even in a session). After a DB update you can immediately access all new programs. If you do not see an installed program in our launcher, but you are sure that it supports session management please report it to info@laborejo.org or under https://laborejo.org/bugs . In addition, you can add (in full view) programs that are not in the database. === For advanced users The strict rule is that only programs in the $PATH are included in the database. Absolutel paths are not allowed, even if you enter the program name yourself through the menu. However sometimes you just want to try out software, or you are a developer yourself and want to test without system-wide installation. In the control menu / settings is a tab "Program-PATH", where you can define and add your own search paths. One absolute directory path per line, no wildcards, trailing slash don't matter. For example: `/home/myuser/sources/newsequencer/bin/` These search paths are not stored in the session, but locally in your `~/.config` directory. == Tray Agordejo has a tray icon, if your window manager supports it. A click on the trayicon shows or hides Agordejo. If you close Agordejo using the normal window manager function, such as a click on the [X], the program and the session is not terminated, but minimized to the tray. A right click on the icon gives you access to common functions: You can directly start the most recently used sessions. If a session is already running you can save, cancel etc. Agordejo can also be completely exited here. == Program parameters As an advanced user, you can start Agordejo in the terminal and add some parameters.. For a complete list please use the --help parameter. For example: * `--session newAlbum/mySong` starts the given session. * `--continue` starts the last active session. * `--hide` starts Agordejo as TrayIcon. * `--url osc.udp://myhost.localdomain:14294/` connects to this server, if available, or starts the internal session server at this address. This is a very technical option and probably not needed. * `--session-root /home/user/production2030` sets the root directory. Only sessions in this directory are displayed, everything is stored here. The combination of `--continue` and `--hide` is essentially what many people expect from Session Management: Resuming at the previous state, without any extra windows in their way. If your system uses a start menu you will find not only the normal Agordejo starter but also "Agordejo Continue" to start this mode directly. == Miscellaneous / Explanations / FAQ *Session Save and Exit responds slowly*: Agordejo is not a standalone program like an word processor. The participating programs in the session are not plugins either. When you end the session a signal is sent to all participating client to save. This may take a few moments where you are able to see "live" how individual programs terminate and disappear from the session. Everything is fine. *I have added a program but it does not save with the session*: Does the program support session management? If not, Agordejo cannot do much. But you can ask the program developers to contact us (info@laborejo.org) and we can work together on support. *The programs hang on exit*: Sorry about that. Actually, the programs themselves are to blame, but we are also interested in improving the situation by offering at least an emergency solution in the future. *Agordejo won't start! I start the program but I can't see anything*: Most likely Agordejo is running, but invisible, because you exited it from the tray last time. Is it in the tray? A message should have popped up, maybe you missed it. If there is no tray in your window manager, the program should always be visible. With all these special window managers in Linux it may be that the tray detection did not work properly. Contingency plan is to delete `~/.config/LaborejoSoftwareSuite/agordejo`. This will NOT remove any sessions, but only local settings such as the visibility of the program window. At next start Agordejo will behave like the very first start. *JACK crashed. A lot of programs hang. What can I do to prevent data loss?* Probably already many programs in the session are not running properly and are not reacting anymore. The best thing to do is to use the 'Abort Session' function and restart everything. If the data has actually been unsaved for a long time, you can also dare to save/exit. It may be necessary to re-draw some jack, or all, jack connections by hand at the next start. If you want to be on the safe side, you can manually make a copy of the session directory in your file manager before ending the session (with inevitable crashes). *A program update broke my session because it can no longer load its files.* Unfortunately, this is a problem that even Agordejo can't solve. It also happens with LV2 plugins and with all other software, such as office programs. If you fear that a program becomes incompatible in the future, write down its version number in the session notes, so that you can at least, in an emergency, reinstall the old program version (even if this is very is cumbersome). *What's better? Monolithic DAW or session management?* Why not both? There is no conflict. Session management is worthwhile with two or more participating programs, which one needs almost always. You should not feel compelled to suddenly make everything modular with individual programs, only because you use a session manager. Agordejo is designed to make your music production easier. If it is faster and more comfortable to manage all plugins and effects e.g. in a single "Carla" instance then you should do exactly that. If you basically want to do everything in Ardour, do that, but start Ardour anyway in session management, because no program can do everything alone and the time will come where you add a second one. Session management is another level of hierarchy. Sequencers or DAWs are not plug-ins themselves. Patroneo does not belong "in" Ardour and Ardour does not belong "in" Laborejo. Already in this example each of the programs fulfils a different role because the others follow a different design philosophy and cannot ever offer the same workflow. And more: Some programs can't host plugins, some can't export audio files. They are not bad programs, but programs that concentrate on one task. Furthermore, there is a lot of software that does not directly do music production, but still is connected in the grander scheme: Open Broadcast Studio (OBS), music player, word processors and graphic programs etc. *Agordejo contains functionality which is not within its scope*: Music production is very complex and complexity is inevitable. It's like a waterbed: if you press down on one side, something bounces up in another place. When you create a "clean and lean" program, which therefore implements only a part of the complete workflow, then the missing part pops up somewhere else. A minimalistic session manager provokes plug-ins (not LV2), helper-scripts, workarounds and hacks. E.g. not to include file management provokes user errors like deleting the wrong files. If the SM knows what to do and it can do it, then let it do it. Or crashes: Technically, crashing programs are not the "problem" of the session managers, but they are part of the software reality. Crashes happen every day and need to be handled. Can Agordejo simplify the work and help to restore good conditions again? Then that should be done. Session management is also an opportunity to simplify even complex technical scenarios, e.g. distributing sessions over the network. ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1665611396.701277 agordejo-0.4.2/documentation/german.adoc0000644000175000017500000005573014321633205017026 0ustar00nilsnils:Author: Laborejo Software Suite :Version: 0.4.2 :iconfont-remote!: :!webfonts: //// This documentation is licensed under the Creative Commons Attribution-ShareAlike 4.0 International License. To view a copy of this license, visit http://creativecommons.org/licenses/by-sa/4.0/ or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. A copy of the license has been provided in the file documentation/LICENSE. //// //// https://powerman.name/doc/asciidoc https://asciidoctor.org/docs/user-manual/ //// :sectnums: :toc: left :toc-title: Inhaltsverzeichnis :toclevels: 3 = Agordejo // Don't write in the empty line above line. It will be interpreted as author html tag Für Programmversion 0.4.2 == Präambel Session Management führt zur Vereinfachung von Arbeitsabläufen, Übersicht und Kontrolle über Programme und Daten und eine gehörige Portion positive Bequemlichkeit :) Kein Programm existiert für sich alleine, denn kein Programm kann alles leisten, was für heutige Musikproduktion nötig ist. Direkt ersichtlich ist das in einer JACK Umgebung, die prinzipiell modular ist: Verschiedene Programme erfüllen verschiedene Zwecke und "sprechen" miteinander, indem sie sich Daten schicken. Ein Sequencer schickt Midi an einen Sampler oder Synthesizer, der mit einem Plugin-Host für Effekte verbunden ist usw. Selbst die monolithischsten All-In-One DAWs müssen, oder möchten, irgendwann mit der Außenwelt kommunizieren. z.B. um sich mit einem Screenrecorder oder Streamingprogramm zu verbinden, einen Wordprozessor mit Aufnahmereihenfolge oder Liedtexten zu starten, um Musik zu visualisieren oder (um ehrlich zu bleiben) eine Funktion zu nutzen, die es in diesem Programm eben nicht gibt. Einen Großteil der Arbeit erledigt bereits das JACK-Subsystem. Alle Programme können ihre Musik in Echtzeit teilen, verfügen über synchronisierte Timelines und spielen im gleichen Tempo. Was übrig bleibt ist die leidige Arbeit jedesmal alle Programme zu starten, die Projektdateien zu laden, alle Audiokanäle zu verbinden usw. Session Management im allgemeinen (z.B. extra geschriebene Starter-Scriptdateien) und Agordejo im speziellen nehmen Ihnen diese Arbeit ab, oder vereinfachen sie zumindest stark. Im Gegensatz zum erwähnten selbstgeschriebenen Script müssen Sie sich nicht im Vorraus für ein Setup entscheiden, sondern alles wird automatisch verwaltet, solange Sie alles durch den Session Manager Agordejo starten. === Anwendungsbeispiel * Agordejo starten (Startmenü, Terminal etc.) * Den Knopf auf der linken Seite drücken: "Schnellstart Neu" * Nun bekommt man eine Auswahl an Programmen: * Ein Doppelklick mit der Maus startet ein Programm * Das Programm erscheint in der anderen Spalte und zeigt an, dass es läuft. * Die GUI des Programms erscheint evtl. erst wenn man auf den neuen Eintrag doppelklickt. * Die Programme "jackpatch" und "nsm-data" wurden schon automatisch hinzugefügt * Audio- und Midiports können nun in einer Patchbay miteinander verbunden werden. Die Verbindungen werden in Agordejo gespeichert. * Der Name der Session ist bisher einfach ein Datum. Im Menü kann man richtigen Namen eingeben. Etwa "Mein Lied" * Ist man soweit fertig kommt man durch den Menüeintrag "Speichern und Schließen" wieder in die Sessionauswahl zurück * Nun könnte Agordejo geschlossen werden. * Alle gespeicherten Daten liegen in einem einzigen Verzeichnis auf der Festplatte (`~/.local/share/nms/Mein Lied`) * Die Session kann weitergeführt werden: Nach dem Doppelklick auf den Namen starten alle Programme automatisch und verbinden ihre JACK-Ports untereinander. == Vollständige Erklärung === Session Auswählen Sessions stellen sich als Tabelle dar, die Sie durch den Klick auf eine Spaltenüberschrift ordnen können. So wird hier gezeigt wie die Session heißt und wann das letzte mal gespeichert wurde, wohl die beiden wichtigsten Informationen. Dann wird gezeigt wie viele Programme/Clients die Session verwaltet und ob sie Symbolische Links enthält. Letzteres steht wahrscheinlich auf "Ja", wenn Sie einen Sampler o.ä. benutzen, der große Audiodateien geladen hat. Diese werden zunächst nur in die Session gelinkt, und nicht kopiert, um Speicherplatz zu sparen. Der angezeigte Speicherverbrauch ist nicht der tatsächliche Verbrauch, sondern beinhaltet die Größen der Symlinks. Erst wenn Sie die Session archivieren oder die Links durch reale Dateien ersetzen stimmt die angezeigte Zahl. Schließlich wird auch das Verzeichnis angegeben, in dem die Dateien tatsächlich gespeichert sind. Sessions stellen den Verzeichnisbaum dar. Eine Session ist immer ein "Blatt" und kann keine Subsessions enthalten. Sie können beim Anlegen oder Umbenennen die Sessions auch im Baum anordnen indem Sie die gewohnte Schrägstrichschreibweise benutzen: `song123` -> `Neues Album/Song 123` oder `Versuche/asdf` -> `Romantische Pop-Balladen/Mein Herz wird weiter schlagen`. Wie Sie ihre Sessions organisieren, und wie viele Unterverzeichnisse Sie anlegen, steht Ihnen frei. Es ist allerdings nicht erlaubt den Namen mit `/` anfangen zu lassen oder die speziellen Zeichen `..` zu benutzen. Die Baumansicht kann durch eine Checkbox an der linken Seite deaktiviert werden, um etwa besser sortieren zu können. Das ist nur eine Ansicht, ihre Daten bleiben unangetastet. Jede Session verfügt über ein Kontextmenü (z.B. rechte Maustaste) mit weiteren Optionen: Sie können eine Session umbennen, löschen (inkl. aller dazugehörigen Dateien auf der Festplatte!) und mehr. Diese Funktionen sind gleichwertig zu ihrem Dateimanager. Falls Sie möchten, können Sie auch einfach die Sessionverzeichnisse selbst umbennen, verschieben oder löschen (sofern diese nicht gerade geöffnet ist). Dazu muss Agordejo nicht neugestartet werden, es reagiert selbst auf die Änderungen. Klicken Sie auf "Neue Session" um diese anzulegen. Im Gegensatz zur schnellen Ansicht müssen Sie hier den Namen direkt eingeben. Wie oben erwäht können Sie dazu den Verzeichnisbaum benutzen. Darüberhinaus wird angeboten verschiedene (fast schon obligatorische) Programme direkt mitzustarten. Im Normalfall sollte man alle Vorschläge akzeptieren. Es gibt jederzeit die Möglichkeit diese wieder zu entfernen. Ein Doppelklick auf eine existierende Session (oder der Knopf "Lade Ausgewählte") machen genau das. === In einer Session Die Ansicht ist in drei Bereiche eingeteilt: Programmstarter, Programme in der laufenden Session und die Session-Notizen. Dazu gibt es ein dynamisches Menü. Auf der linken Seite sehen die den Programmstarter. Ein Doppelklick startet eine Programminstanz in der Session. Sie können ein Programm auch mehrmals starten. Welche Programme zur Verfügung stehen entnehmen Sie bitte dem Kapitel "Programm-Datenbank". ==== Laufende Programm Gestartete Programme befinden sich auf der rechten Seite. Ein Doppelklick schaltet die Sichtbarkeit um, sofern das Programm unterstüzt sein Fenster zu verstecken. Falls nicht passiert nichts. Pro Programm gibt es folgende Informationen: * Der Name (evtl. mit Icon) * Ein "Label", das Programme frei benutzen können (z.B. zeigt Fluajho hier das geladene .sf2 an) * Den Programmstatus (auf Englisch, da es sich um definierte Schlüsselworte handelt). * Stopped / Gestoppt, läuft nicht * Ready / Läuft und Bereit * Launch / Startet. Wenn das Programm hier stehen bleibt, aber funktioniert, handelt es sich um eins, dass nicht speziell den Sessionmodus unterstützt. Agordejo kann nicht wissen, ob es bereits läuft oder nicht. Alles ist in Ordnung! :) * Weitere Zustände sind nur Übergänge und meist nur sehr kurz zu sehen, z.B. Open / Läd gerade * Sichtbarkeit (Ein Kreuz für sichtbar, Leer für unsichtbar) * Änderungen - Gibt es momentan ungespeicherte Änderungen? * ID - Ein eindeutiges Kürzel mit dem man auch mehrere Instanzen des gleichen Programmes auseinander halten kann Alle weiteren Funktionen sind durch das Menü oder Kontextmenü zugänglich. Ein Klick auf ein Programm wählt es aus, und das Clientmenü in der Menüleiste gilt nun dafür. Alternativ kann mit Rechtsklick auf den Eintrag das Kontextmenü geöffnet werden, das identisch ist. Neben den selbsterklärenden Funktionen gibt es noch: * Umbenennen gibt dem Programm einen selbstgewählten Namen, besonders um seinen Zweck deutlich zu machen und es besser von anderen zu unterscheiden. Diese Funktion steht nur zur Verfügung, wenn `nsm-data` in der Session läuft. * Speichern weist nur dieses Programm an abzuspeichern * Entfernen nimmt das Programm aus der Session. Dabei werden jedoch keine Dateien gelöscht. Zur Zeit muss leider noch von Hand im Dateimanager "aufgeräumt" werden. Befindet sich der Client "nsm-data" in der Session (das ist Voreinstellung) steht im unteren Bereich ein großes Textfeld für Notizen zur Verfügung. Es ist dasselbe wie in der schnellen Ansicht. Schreiben Sie was Sie möchten: TODO-Listen, Liedtexte, Credits und Quellen von externen Samples etc. ==== Das Session Menü Im Gegensatz zur schnellen Ansicht stehen hier weitere Menüs zur Verfügung, die auch über die üblichen Tastaturkürzel zu erreichen sind (Strg+S für Speichern etc.). * Speichern weist alle Programme an zu speichern, die Session läuft weiter * Speichern und Schließen beendet die Session, vorher speichern alle Programme noch einmal ab * Abbrechen beendet die Session, ohne dass die Programme abspeichern * Speichern Unter speichert die Session unter einem anderen Namen und schließt die laufende ohne abzuspeichern. Ab nun arbeitet man in der neuen Session. * Client Hinzufügen bietet die Option ein beliebiges Programm hinzuzufügen, egal ob es in der Programmdatenbank ist, oder nicht. * Es werden alle installierten Programme vorgeschlagen. Agordejo überprüft diese nicht auf Sinnhaftigkeit für eine Musik-Session, oder auch nur auf Lauffähigkeit. Sie finden hier `ls` wie auch `agordejo` selbst. == Programm-Datenbank Agordejos Programmstarter basiert auf einer Programmdatenbank, die sich teilweise selbst erstellt, teilweise von Hand eingepflegt wurde. Das bedeutet nichts anderes, als das alle installieren Programme in ihrem System überprüft werden (wie ein Startmenü) und Ihnen im Agordejo-Starter nur das angeboten wird, was Sie auch tatsächlich installiert haben. Beim ersten Start wird daher die Programmdatenbank erstellt. Je nach System kann dies einige Augenblicke bis einige Minuten dauern. Wenn Sie Audio-Programme neu installieren, oder deinstallieren müssen Sie die Datenbank selbst aktualisieren. Im Steuerungsmenü gibt es den Befehl. Programminstallationen sind sogar möglich während Agordejo läuft (auch in einer Session). Nach einem DB-Update stehen Ihnen sofort alle neuen Programme zur Verfügung. Wenn Sie ein installiertes Programm nicht in unserer Liste sehen, aber von dem Sie sicher sind, dass es Session Management unterstützt melden Sie es bitte an info@laborejo.org oder unter https://laborejo.org/bugs . Darüberhinaus können Sie (in der vollen Ansicht) Programme hinzufügen, die nicht in der Datenbank sind. Siehe dort. === Für Fortgeschrittene Die eiserne Regel ist, dass nur Programme im $PATH in die Datenbank aufgenommen werden. Absolute Pfade sind unzulässig, selbst wenn man den Programmnamen selbst durch das Menü eingibt. Allerdings möchte man manchmal Software nur ausprobieren, oder ist selbst Entwickler und möchte ohne systemweite Installation testen. In den Einstellungen im Steuerungsmenü gibt es einen Tab "Programm-PATH", wo Sie eigene Suchpfade hinzufügen können. Ein absoluter Pfad pro Zeile, keine Wildcards, Trailing Slash spielt keine Rolle. Zum Beispiel: `/home/myuser/sources/newsequencer/bin/` Diese Suchpfade werden nicht in der Session gespeichert sondern lokal in ihrem `~/.config` Verzeichnis. == Tray Agordejo verfügt über ein Tray-Icon, sofern ihr Windowmanager das unterstüzt. Ein Klick auf das Trayicon zeigt oder versteckt Agordejo. Schließt man Agordejo über die normale Windowmanagerfunktion, etwa ein Klick auf das [X], wird das Programm und die Session nicht beendet, sondern in den Tray minimiert. Ein Rechtsklick auf das Icon bietet Schnellzugriff auf häufige Funktionen: Sie können hier die zuletzt benutzen Sessions direkt starten. Läuft bereits eine Session können Sie speichern, abbrechen etc. Agordejo kann hier auch komplett beendet werden. == Programmparameter Als fortgeschrittener Benutzer können Sie Agordejo im Terminal starten und dort einige Parameter angeben. Für eine vollständige Liste benutzen Sie bitte den --help Parameter. Eine Auswahl: * `--session neuesAlbum/meinLied` startet direkt die angegebene Session. * `--continue` startet die zuletzt benutzte Session * `--hide` startet Agordejo als Trayicon. * `--url osc.udp://myhost.localdomain:14294/` verbindet sich zu diesem Server, falls vorhanden, oder startet den internen Session-Server unter dieser Adresse. Dies ist eine sehr technische Option und wird wahrscheinlich nicht benötigt. * `--session-root /home/benutzer/produktion2020` setzt das Wurzelverzeichnis. Nur Sessions in diesem Verzeichnis werden angezeigt, alles wird dort gespeichert. Die Kombination von `--continue` und `--hide` ergibt einen Modus, den viele Leute vom Session Management erwarten: dort weiter machen wo man aufgehört hat, ohne dass Extrafenster angezeigt werden. Falls Ihr System über ein Startmenü verfügt finden Sie daher neben dem normalen Agordejo-Starter auch eine "Agordejo Continue"-Verknüpfung für genau diesen Modus. == Verschiedenes / Erklärungen / FAQ *Session Speichern und Beenden reagiert langsam*: Agordejo ist kein Einzelprogramm wie ein Office-Writer. Die teilnehmenden Programme in der Session sind auch keine Plugins. Wenn Sie die Session beenden wird ein Signal an alle teilnehmenden Programme gesendet, dass sie speichern sollen. Das kann ein paar Momente dauern, in denen Sie "live" mitverfolgen, wie die einzelnen Programme sich beenden und aus der Session verschwinden. Es ist alles in Ordnung. *Ich habe ein Programm hinzugefügt aber es speichert nicht mit der Session*: Unterstützt das Programm Session Management? Wenn nicht, kann Agordejo nichts tun. Aber Sie können die Programmentwickler bitten mit uns Kontakt aufzunehmen (info@laborejo.org) und wir können zusammen an der Unterstützung arbeiten. *Die Programme hängen beim Beenden*: Das tut uns leid. Eigentlich sind die Programme selbst schuld, aber auch wir sind daran interessiert die Situation zu verbessen, indem wir in Zukunft zumindest eine Notlösung anbieten. *Agordejo startet nicht mehr! Ich starte das Programm aber ich sehe nichts.*: Wahrscheinlich ist Agordejo unsichtbar, weil Sie es aus dem Tray heraus beendet hatten. Ist es im Tray? Eigentlich hätte ein kleines Nachrichtenfenster aufpoppen sollen. Falls in ihrem Window-Manager kein Tray vorhanden ist sollte das Programm immer sichtbar sein. Bei besonderen Window-Managern (bei der großen Auswahl in Linux) kann es sein, dass die Tray-Erkennung nicht richtig funktioniert hat. Notfallplan ist es `~/.config/LaborejoSoftwareSuite/agordejo` zu löschen. Dabei werden KEINE Sessions gelöscht, sondern nur lokale Einstellungen wie die Sichtbarkeit des Programmfensters. Beim nächsten Start wird Agordejo sich verhalten wie beim allerersten. *JACK ist abgestürzt. Viele Programme hängen. Was tun um Datenverlust zu vermeiden?*: Vermutlich sind bereits viele Programme der Session nicht mehr richtig lauffähig und reagieren nicht mehr. Am besten benutzen Sie die `Session Abbrechen` Funktion und starten alles neu. Wenn die Daten tatsächlich schon lange ungespeichert waren kann man auch ein Speichern/Beenden wagen. Dann kann es aber sein, dass man beim nächsten Start einige Jack connections von Hand neu ziehen muss. Wer extrem sicher gehen möchte kann vor dem Beenden der Session (mit unweigerlichen abstürzen) manuell im Dateimanager eine Kopie des Session-Verzeichnisses machen. *Ein Programmupdate hat meine Session kaputtgemacht, weil es seine Dateien nicht mehr laden kann.*: Das ist leider ein Problem, dass auch Agordejo nicht lösen kann. Es passiert mit LV2-Plugins ebenso wie mit alle anderen Software, etwa Officeprogramme. Falls Sie befürchten, dass ein Programm in Zukunft inkompatibel wird notieren Sie sich dessen Versionsnummer in den Session-Notizen, damit Sie zumindest zur Not die alte Programmversion wieder installieren können (auch wenn das sehr umständlich ist). *Was ist besser? Monolithische DAW oder Session Management?*: Warum nicht beides? Es gibt keinen Konflikt. Session Management lohnt sich ab zwei teilnehmenden Programmen, und auf die kommt man so gut wie immer. Sie sollten sich nicht genötigt fühlen plötzlich alles modular mit Einzelprogrammen zu machen, nur weil Sie einen Session Manager benutzen. Agordejo ist dafür da, ihre Musikproduktion einfacher zu machen. Wenn es schneller und bequemer ist alle Plugins und Effekte z.B. in einer einzelnen Carla-Instanz zu verwalten dann sollten Sie genau das machen. Wenn Sie prinzipiell alles in Ardour machen wollen, machen Sie das, aber starten Sie Ardour trotzdem im Session Management, denn kein Programm kann alles alleine, und der Zeitpunkt wird kommen, an dem Sie ein weiteres hinzufügen. Session Management ist andere Hirarchieebene. So sind Sequencer oder DAWs selbst keine Plugins. Patroneo gehört nicht "in" Ardour und Ardour gehört nicht "in" Laborejo. Und schon in diesem Beispiel erfüllt jedes der Programme eine Rolle, die die anderen beiden nicht leisten können, da sie einer anderen Design-Philosophie folgen. Und mehr: Manche Programme können keine Plugins hosten, manche können keine Audiodateien exportieren. Das sind deswegen keine schlechten Programme, sondern welche, die sich auf eine Aufgabe konzentrieren. Darüberhinaus gibt es eine Menge Software, die nicht direkt Musikproduktion ausübt, aber trotzdem inhaltlich dazu gehört: Open Broadcast Studio (OBS), Musikplayer, Schreib- und Grafikprogramme etc. *Agordejo beinhaltet Funktionalität, die nicht seine Aufgabe ist*: Musikproduktion ist sehr komplex und Komplexität ist unvermeidlich. Sie ist wie ein Wasserbett: Drückt man die auf der einen Seite runter, muss etwas an einen anderen Stelle hochdrücken. Macht man ein programm "clean and lean", und implementiert damit nur einen Teil des kompletten Arbeitsablaufs, dann kommt der fehlende Teil woanders wieder hoch. Ein minimalistischer Session Manager provoziert Plugins (nicht LV2), Helper-Scripts, Workarounds und Hacks. z.B. Dateiverwaltung nicht mit einzuschließen provoziert Anwender-Fehler im Dateimanager (wie das Löschen der falschen Dateien). Wenn der SM weiß was zu tun ist, und er es tun kann, dann soll er es machen. Oder Abstürze: Technisch gesehen sind abstürzende Programme nicht das "Problem" des Session Managers, aber sie sind Teil der Softwarewirklichkeit. Abstürze passieren jeden Tag und nun muss man damit umgehen. Kann Agordejo die Arbeit vereinfachen und helfen den guten Zustand wieder herzustellen? Dann sollte das geschehen. Session Management ist außerdem eine Chance auch komplexe technische Szenarios zu vereinfachen, z.B. Sessions über das Netzwerk zu verteilen. == Installation und Start Agordejo ist exklusiv für Linux. Am besten installieren Sie Agordejo über deinen Paketmanager. Falls es dort nicht vorhanden ist, oder nur in einer veralteten Version, bitten sie ihre Linuxdistribution Agordejo bereitzustellen. Falls nicht in den Paketquellen vorhanden kann man Agordejo auch selbst "bauen". .Abhängigkeiten* * Eine Liste der Abhängigkeit befindet sich in der README.md * Kompilieren und Installieren geht entweder mit einem Releasedownload oder mit der Git-Version: ** Laden Sie von https://www.laborejo.org/downloads die aktuelle Version herunter und entpacken Sie sie. ** `git clone https://git.laborejo.org/lss/agordejo.git` * Wechseln Sie in das neue Verzeichnis und benutzen diese Befehle: *`./configure --prefix=/usr` ** Das Standardprefix is /usr/local * `make` * `sudo make install` Nun ist das Programm durch `agordejo` in ihrem Terminal oder Programmstarter vorhanden. In der Datei README.md befinden sich weitere Möglichkeiten agordejo zu starten. Diese sind zum Musikmachen nicht praktikabel, aber nützlich für Tests und Entwicklung. == Helfen und Entwicklung Sie können Agordejo auf viele Arten und Weisen helfen: Testen und Fehler melden, übersetzen, marketing, anderen Nutzern helfen und schließlich programmieren. === Testen und Programmfehler Falls Sie einen Fehler im Programm entdecken (oder es zu langsam läuft) melden Sie diese bitte auf eine Art und Weise, die ihnen am besten passt. .Kontaktmöglichkeiten * Bugs und Probleme melden: https://www.laborejo.org/bugs * Webseite: https://www.laborejo.org * E-Mail: info@laborejo.org * Wenn Sie die Gelegenheiten sehen und wissen, dass ein Entwickler es lesen wird sind auch Foren, Socialmedia etc. möglich. == Entwicklung Falls Sie an der Entwicklung interessiert sind, melden Sie sich am besten direkt bei uns (s.o.) Kurzversion: clone git, programmieren, einen git-patch erstellen oder uns eine git URL zukommen lassen. === Übersetzungen Agordejo ist mit Hilfe der Qt-Toolchain sehr einfach zu übersetzen, ohne, dass man dafür Programmieren muss. Die einfachste Variante ist es einfach die Entwickler anzusprechen und sie werden die neue Sprache einrichten. Hier ist dennoch die komplette Anleitung, um eine Übersetzung komplett alleine anzufertigen und in das Programm einzubinden. So fügt man eine neue Sprache hinzu: * Öffnen Sie ein Terminal und navigieren zu qtgui/resources/translations * Bearbeiten Sie die Datei `config.pro` in einem Texteditor. ** Hängem Sie in der letzten Zeile den Namen der neuen Sprache an, in der Form `XY.ts`, wobei XY der Sprachcode ist. ** Achten Sie bitte darauf ein Leerzeichen zwischen den einzelnen Sprachen zu lassen * Führen Sie `sh update.sh` im selben Verzeichnis aus. ** Das Programm hat nun eine neue `.ts`-Datei im Verzeichnis erstellt. * Starten Sie Qt Linguist mit `linguist-qt5` (kann evtl. anders heißen) und öffnen von dort die neu generierte Datei. * Wählen Sie die "Target Language", also Zielsprache, aus und benutzen das Programm um eine Übersetzung anzufertigen. * Senden Sie uns bitte die .ts Datei, z.B. per E-Mail an info@laborejo.org (s.u bei Bugs und Programmfehler für mehr Kontaktmöglichkeiten) Die Übersetzung können Sie auch selbst, zum Testen, einbinden. Dafür sind rudimentäre Python Kentnisse nötig. * Im Qt Linguist "Datei" Menü ist eine "Release" Option. Das erstellt eine `.qm` Datei im gleichen Verzeichnis wie die `.ts` Datei.* Bearbeiten Sie `qtgui/resources/resources.qrc` und kopieren die Zeile `translations/de.qm` . Dabei das Länderkürzel zum Neuen ändern. * Führen Sie `sh buildresources.sh` aus * Bearbeiten Sie `engine/config.py`: Die neue Sprache hinzufügen. z.B. `{"German":"de.qm", "Esperanto:"eo.qm"}` ** Um den Sprachstring herauszufinden öffnen Sie den `python3`-Interpreter im Terminal und führen aus: ** `from PyQt5 import QtCore;QtCore.QLocale().languageToString(QtCore.QLocale().language())` Um die neue Übersetzung zu testen starten Sie das Programm, falls ihr System bereits auf diese Sprache eingstellt ist. Ansonsten starten Sie agordejo mit diesem Befehl, Sprachcode ändern, vom Terminal aus: * `LANGUAGE=de_DE.UTF-8 ./agordejo -V` ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1665611335.957255 agordejo-0.4.2/documentation/german.adoc.template0000644000175000017500000001154714321633110020631 0ustar00nilsnils:Author: :Version: :iconfont-remote!: :!webfonts: //// This documentation is licensed under the Creative Commons Attribution-ShareAlike 4.0 International License. To view a copy of this license, visit http://creativecommons.org/licenses/by-sa/4.0/ or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. A copy of the license has been provided in the file documentation/LICENSE. //// //// https://powerman.name/doc/asciidoc https://asciidoctor.org/docs/user-manual/ //// :sectnums: :toc: left :toc-title: Inhaltsverzeichnis :toclevels: 3 = // Don't write in the empty line above line. It will be interpreted as author html tag Für Programmversion == Installation und Start ist exklusiv für Linux. Am besten installieren Sie über deinen Paketmanager. Falls es dort nicht vorhanden ist, oder nur in einer veralteten Version, bitten sie ihre Linuxdistribution bereitzustellen. Falls nicht in den Paketquellen vorhanden kann man auch selbst "bauen". .Abhängigkeiten* * Eine Liste der Abhängigkeit befindet sich in der README.md * Kompilieren und Installieren geht entweder mit einem Releasedownload oder mit der Git-Version: ** Laden Sie von https://www.laborejo.org/downloads die aktuelle Version herunter und entpacken Sie sie. ** `git clone https://git.laborejo.org/lss/.git` * Wechseln Sie in das neue Verzeichnis und benutzen diese Befehle: *`./configure --prefix=/usr` ** Das Standardprefix is /usr/local * `make` * `sudo make install` Nun ist das Programm durch `` in ihrem Terminal oder Programmstarter vorhanden. In der Datei README.md befinden sich weitere Möglichkeiten zu starten. Diese sind zum Musikmachen nicht praktikabel, aber nützlich für Tests und Entwicklung. == Helfen und Entwicklung Sie können auf viele Arten und Weisen helfen: Testen und Fehler melden, übersetzen, marketing, anderen Nutzern helfen und schließlich programmieren. === Testen und Programmfehler Falls Sie einen Fehler im Programm entdecken (oder es zu langsam läuft) melden Sie diese bitte auf eine Art und Weise, die ihnen am besten passt. .Kontaktmöglichkeiten * Bugs und Probleme melden: https://www.laborejo.org/bugs * Webseite: https://www.laborejo.org * E-Mail: info@laborejo.org * Wenn Sie die Gelegenheiten sehen und wissen, dass ein Entwickler es lesen wird sind auch Foren, Socialmedia etc. möglich. == Entwicklung Falls Sie an der Entwicklung interessiert sind, melden Sie sich am besten direkt bei uns (s.o.) Kurzversion: clone git, programmieren, einen git-patch erstellen oder uns eine git URL zukommen lassen. === Übersetzungen ist mit Hilfe der Qt-Toolchain sehr einfach zu übersetzen, ohne, dass man dafür Programmieren muss. Die einfachste Variante ist es einfach die Entwickler anzusprechen und sie werden die neue Sprache einrichten. Hier ist dennoch die komplette Anleitung, um eine Übersetzung komplett alleine anzufertigen und in das Programm einzubinden. So fügt man eine neue Sprache hinzu: * Öffnen Sie ein Terminal und navigieren zu qtgui/resources/translations * Bearbeiten Sie die Datei `config.pro` in einem Texteditor. ** Hängem Sie in der letzten Zeile den Namen der neuen Sprache an, in der Form `XY.ts`, wobei XY der Sprachcode ist. ** Achten Sie bitte darauf ein Leerzeichen zwischen den einzelnen Sprachen zu lassen * Führen Sie `sh update.sh` im selben Verzeichnis aus. ** Das Programm hat nun eine neue `.ts`-Datei im Verzeichnis erstellt. * Starten Sie Qt Linguist mit `linguist-qt5` (kann evtl. anders heißen) und öffnen von dort die neu generierte Datei. * Wählen Sie die "Target Language", also Zielsprache, aus und benutzen das Programm um eine Übersetzung anzufertigen. * Senden Sie uns bitte die .ts Datei, z.B. per E-Mail an info@laborejo.org (s.u bei Bugs und Programmfehler für mehr Kontaktmöglichkeiten) Die Übersetzung können Sie auch selbst, zum Testen, einbinden. Dafür sind rudimentäre Python Kentnisse nötig. * Im Qt Linguist "Datei" Menü ist eine "Release" Option. Das erstellt eine `.qm` Datei im gleichen Verzeichnis wie die `.ts` Datei.* Bearbeiten Sie `qtgui/resources/resources.qrc` und kopieren die Zeile `translations/de.qm` . Dabei das Länderkürzel zum Neuen ändern. * Führen Sie `sh buildresources.sh` aus * Bearbeiten Sie `engine/config.py`: Die neue Sprache hinzufügen. z.B. `{"German":"de.qm", "Esperanto:"eo.qm"}` ** Um den Sprachstring herauszufinden öffnen Sie den `python3`-Interpreter im Terminal und führen aus: ** `from PyQt5 import QtCore;QtCore.QLocale().languageToString(QtCore.QLocale().language())` Um die neue Übersetzung zu testen starten Sie das Programm, falls ihr System bereits auf diese Sprache eingstellt ist. Ansonsten starten Sie mit diesem Befehl, Sprachcode ändern, vom Terminal aus: * `LANGUAGE=de_DE.UTF-8 ./ -V` ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1665611335.957255 agordejo-0.4.2/documentation/german.part.adoc0000644000175000017500000004416314321633110017764 0ustar00nilsnils== Präambel Session Management führt zur Vereinfachung von Arbeitsabläufen, Übersicht und Kontrolle über Programme und Daten und eine gehörige Portion positive Bequemlichkeit :) Kein Programm existiert für sich alleine, denn kein Programm kann alles leisten, was für heutige Musikproduktion nötig ist. Direkt ersichtlich ist das in einer JACK Umgebung, die prinzipiell modular ist: Verschiedene Programme erfüllen verschiedene Zwecke und "sprechen" miteinander, indem sie sich Daten schicken. Ein Sequencer schickt Midi an einen Sampler oder Synthesizer, der mit einem Plugin-Host für Effekte verbunden ist usw. Selbst die monolithischsten All-In-One DAWs müssen, oder möchten, irgendwann mit der Außenwelt kommunizieren. z.B. um sich mit einem Screenrecorder oder Streamingprogramm zu verbinden, einen Wordprozessor mit Aufnahmereihenfolge oder Liedtexten zu starten, um Musik zu visualisieren oder (um ehrlich zu bleiben) eine Funktion zu nutzen, die es in diesem Programm eben nicht gibt. Einen Großteil der Arbeit erledigt bereits das JACK-Subsystem. Alle Programme können ihre Musik in Echtzeit teilen, verfügen über synchronisierte Timelines und spielen im gleichen Tempo. Was übrig bleibt ist die leidige Arbeit jedesmal alle Programme zu starten, die Projektdateien zu laden, alle Audiokanäle zu verbinden usw. Session Management im allgemeinen (z.B. extra geschriebene Starter-Scriptdateien) und Agordejo im speziellen nehmen Ihnen diese Arbeit ab, oder vereinfachen sie zumindest stark. Im Gegensatz zum erwähnten selbstgeschriebenen Script müssen Sie sich nicht im Vorraus für ein Setup entscheiden, sondern alles wird automatisch verwaltet, solange Sie alles durch den Session Manager Agordejo starten. === Anwendungsbeispiel * Agordejo starten (Startmenü, Terminal etc.) * Den Knopf auf der linken Seite drücken: "Schnellstart Neu" * Nun bekommt man eine Auswahl an Programmen: * Ein Doppelklick mit der Maus startet ein Programm * Das Programm erscheint in der anderen Spalte und zeigt an, dass es läuft. * Die GUI des Programms erscheint evtl. erst wenn man auf den neuen Eintrag doppelklickt. * Die Programme "jackpatch" und "nsm-data" wurden schon automatisch hinzugefügt * Audio- und Midiports können nun in einer Patchbay miteinander verbunden werden. Die Verbindungen werden in Agordejo gespeichert. * Der Name der Session ist bisher einfach ein Datum. Im Menü kann man richtigen Namen eingeben. Etwa "Mein Lied" * Ist man soweit fertig kommt man durch den Menüeintrag "Speichern und Schließen" wieder in die Sessionauswahl zurück * Nun könnte Agordejo geschlossen werden. * Alle gespeicherten Daten liegen in einem einzigen Verzeichnis auf der Festplatte (`~/.local/share/nms/Mein Lied`) * Die Session kann weitergeführt werden: Nach dem Doppelklick auf den Namen starten alle Programme automatisch und verbinden ihre JACK-Ports untereinander. == Vollständige Erklärung === Session Auswählen Sessions stellen sich als Tabelle dar, die Sie durch den Klick auf eine Spaltenüberschrift ordnen können. So wird hier gezeigt wie die Session heißt und wann das letzte mal gespeichert wurde, wohl die beiden wichtigsten Informationen. Dann wird gezeigt wie viele Programme/Clients die Session verwaltet und ob sie Symbolische Links enthält. Letzteres steht wahrscheinlich auf "Ja", wenn Sie einen Sampler o.ä. benutzen, der große Audiodateien geladen hat. Diese werden zunächst nur in die Session gelinkt, und nicht kopiert, um Speicherplatz zu sparen. Der angezeigte Speicherverbrauch ist nicht der tatsächliche Verbrauch, sondern beinhaltet die Größen der Symlinks. Erst wenn Sie die Session archivieren oder die Links durch reale Dateien ersetzen stimmt die angezeigte Zahl. Schließlich wird auch das Verzeichnis angegeben, in dem die Dateien tatsächlich gespeichert sind. Sessions stellen den Verzeichnisbaum dar. Eine Session ist immer ein "Blatt" und kann keine Subsessions enthalten. Sie können beim Anlegen oder Umbenennen die Sessions auch im Baum anordnen indem Sie die gewohnte Schrägstrichschreibweise benutzen: `song123` -> `Neues Album/Song 123` oder `Versuche/asdf` -> `Romantische Pop-Balladen/Mein Herz wird weiter schlagen`. Wie Sie ihre Sessions organisieren, und wie viele Unterverzeichnisse Sie anlegen, steht Ihnen frei. Es ist allerdings nicht erlaubt den Namen mit `/` anfangen zu lassen oder die speziellen Zeichen `..` zu benutzen. Die Baumansicht kann durch eine Checkbox an der linken Seite deaktiviert werden, um etwa besser sortieren zu können. Das ist nur eine Ansicht, ihre Daten bleiben unangetastet. Jede Session verfügt über ein Kontextmenü (z.B. rechte Maustaste) mit weiteren Optionen: Sie können eine Session umbennen, löschen (inkl. aller dazugehörigen Dateien auf der Festplatte!) und mehr. Diese Funktionen sind gleichwertig zu ihrem Dateimanager. Falls Sie möchten, können Sie auch einfach die Sessionverzeichnisse selbst umbennen, verschieben oder löschen (sofern diese nicht gerade geöffnet ist). Dazu muss Agordejo nicht neugestartet werden, es reagiert selbst auf die Änderungen. Klicken Sie auf "Neue Session" um diese anzulegen. Im Gegensatz zur schnellen Ansicht müssen Sie hier den Namen direkt eingeben. Wie oben erwäht können Sie dazu den Verzeichnisbaum benutzen. Darüberhinaus wird angeboten verschiedene (fast schon obligatorische) Programme direkt mitzustarten. Im Normalfall sollte man alle Vorschläge akzeptieren. Es gibt jederzeit die Möglichkeit diese wieder zu entfernen. Ein Doppelklick auf eine existierende Session (oder der Knopf "Lade Ausgewählte") machen genau das. === In einer Session Die Ansicht ist in drei Bereiche eingeteilt: Programmstarter, Programme in der laufenden Session und die Session-Notizen. Dazu gibt es ein dynamisches Menü. Auf der linken Seite sehen die den Programmstarter. Ein Doppelklick startet eine Programminstanz in der Session. Sie können ein Programm auch mehrmals starten. Welche Programme zur Verfügung stehen entnehmen Sie bitte dem Kapitel "Programm-Datenbank". ==== Laufende Programm Gestartete Programme befinden sich auf der rechten Seite. Ein Doppelklick schaltet die Sichtbarkeit um, sofern das Programm unterstüzt sein Fenster zu verstecken. Falls nicht passiert nichts. Pro Programm gibt es folgende Informationen: * Der Name (evtl. mit Icon) * Ein "Label", das Programme frei benutzen können (z.B. zeigt Fluajho hier das geladene .sf2 an) * Den Programmstatus (auf Englisch, da es sich um definierte Schlüsselworte handelt). * Stopped / Gestoppt, läuft nicht * Ready / Läuft und Bereit * Launch / Startet. Wenn das Programm hier stehen bleibt, aber funktioniert, handelt es sich um eins, dass nicht speziell den Sessionmodus unterstützt. Agordejo kann nicht wissen, ob es bereits läuft oder nicht. Alles ist in Ordnung! :) * Weitere Zustände sind nur Übergänge und meist nur sehr kurz zu sehen, z.B. Open / Läd gerade * Sichtbarkeit (Ein Kreuz für sichtbar, Leer für unsichtbar) * Änderungen - Gibt es momentan ungespeicherte Änderungen? * ID - Ein eindeutiges Kürzel mit dem man auch mehrere Instanzen des gleichen Programmes auseinander halten kann Alle weiteren Funktionen sind durch das Menü oder Kontextmenü zugänglich. Ein Klick auf ein Programm wählt es aus, und das Clientmenü in der Menüleiste gilt nun dafür. Alternativ kann mit Rechtsklick auf den Eintrag das Kontextmenü geöffnet werden, das identisch ist. Neben den selbsterklärenden Funktionen gibt es noch: * Umbenennen gibt dem Programm einen selbstgewählten Namen, besonders um seinen Zweck deutlich zu machen und es besser von anderen zu unterscheiden. Diese Funktion steht nur zur Verfügung, wenn `nsm-data` in der Session läuft. * Speichern weist nur dieses Programm an abzuspeichern * Entfernen nimmt das Programm aus der Session. Dabei werden jedoch keine Dateien gelöscht. Zur Zeit muss leider noch von Hand im Dateimanager "aufgeräumt" werden. Befindet sich der Client "nsm-data" in der Session (das ist Voreinstellung) steht im unteren Bereich ein großes Textfeld für Notizen zur Verfügung. Es ist dasselbe wie in der schnellen Ansicht. Schreiben Sie was Sie möchten: TODO-Listen, Liedtexte, Credits und Quellen von externen Samples etc. ==== Das Session Menü Im Gegensatz zur schnellen Ansicht stehen hier weitere Menüs zur Verfügung, die auch über die üblichen Tastaturkürzel zu erreichen sind (Strg+S für Speichern etc.). * Speichern weist alle Programme an zu speichern, die Session läuft weiter * Speichern und Schließen beendet die Session, vorher speichern alle Programme noch einmal ab * Abbrechen beendet die Session, ohne dass die Programme abspeichern * Speichern Unter speichert die Session unter einem anderen Namen und schließt die laufende ohne abzuspeichern. Ab nun arbeitet man in der neuen Session. * Client Hinzufügen bietet die Option ein beliebiges Programm hinzuzufügen, egal ob es in der Programmdatenbank ist, oder nicht. * Es werden alle installierten Programme vorgeschlagen. Agordejo überprüft diese nicht auf Sinnhaftigkeit für eine Musik-Session, oder auch nur auf Lauffähigkeit. Sie finden hier `ls` wie auch `agordejo` selbst. == Programm-Datenbank Agordejos Programmstarter basiert auf einer Programmdatenbank, die sich teilweise selbst erstellt, teilweise von Hand eingepflegt wurde. Das bedeutet nichts anderes, als das alle installieren Programme in ihrem System überprüft werden (wie ein Startmenü) und Ihnen im Agordejo-Starter nur das angeboten wird, was Sie auch tatsächlich installiert haben. Beim ersten Start wird daher die Programmdatenbank erstellt. Je nach System kann dies einige Augenblicke bis einige Minuten dauern. Wenn Sie Audio-Programme neu installieren, oder deinstallieren müssen Sie die Datenbank selbst aktualisieren. Im Steuerungsmenü gibt es den Befehl. Programminstallationen sind sogar möglich während Agordejo läuft (auch in einer Session). Nach einem DB-Update stehen Ihnen sofort alle neuen Programme zur Verfügung. Wenn Sie ein installiertes Programm nicht in unserer Liste sehen, aber von dem Sie sicher sind, dass es Session Management unterstützt melden Sie es bitte an info@laborejo.org oder unter https://laborejo.org/bugs . Darüberhinaus können Sie (in der vollen Ansicht) Programme hinzufügen, die nicht in der Datenbank sind. Siehe dort. === Für Fortgeschrittene Die eiserne Regel ist, dass nur Programme im $PATH in die Datenbank aufgenommen werden. Absolute Pfade sind unzulässig, selbst wenn man den Programmnamen selbst durch das Menü eingibt. Allerdings möchte man manchmal Software nur ausprobieren, oder ist selbst Entwickler und möchte ohne systemweite Installation testen. In den Einstellungen im Steuerungsmenü gibt es einen Tab "Programm-PATH", wo Sie eigene Suchpfade hinzufügen können. Ein absoluter Pfad pro Zeile, keine Wildcards, Trailing Slash spielt keine Rolle. Zum Beispiel: `/home/myuser/sources/newsequencer/bin/` Diese Suchpfade werden nicht in der Session gespeichert sondern lokal in ihrem `~/.config` Verzeichnis. == Tray Agordejo verfügt über ein Tray-Icon, sofern ihr Windowmanager das unterstüzt. Ein Klick auf das Trayicon zeigt oder versteckt Agordejo. Schließt man Agordejo über die normale Windowmanagerfunktion, etwa ein Klick auf das [X], wird das Programm und die Session nicht beendet, sondern in den Tray minimiert. Ein Rechtsklick auf das Icon bietet Schnellzugriff auf häufige Funktionen: Sie können hier die zuletzt benutzen Sessions direkt starten. Läuft bereits eine Session können Sie speichern, abbrechen etc. Agordejo kann hier auch komplett beendet werden. == Programmparameter Als fortgeschrittener Benutzer können Sie Agordejo im Terminal starten und dort einige Parameter angeben. Für eine vollständige Liste benutzen Sie bitte den --help Parameter. Eine Auswahl: * `--session neuesAlbum/meinLied` startet direkt die angegebene Session. * `--continue` startet die zuletzt benutzte Session * `--hide` startet Agordejo als Trayicon. * `--url osc.udp://myhost.localdomain:14294/` verbindet sich zu diesem Server, falls vorhanden, oder startet den internen Session-Server unter dieser Adresse. Dies ist eine sehr technische Option und wird wahrscheinlich nicht benötigt. * `--session-root /home/benutzer/produktion2020` setzt das Wurzelverzeichnis. Nur Sessions in diesem Verzeichnis werden angezeigt, alles wird dort gespeichert. Die Kombination von `--continue` und `--hide` ergibt einen Modus, den viele Leute vom Session Management erwarten: dort weiter machen wo man aufgehört hat, ohne dass Extrafenster angezeigt werden. Falls Ihr System über ein Startmenü verfügt finden Sie daher neben dem normalen Agordejo-Starter auch eine "Agordejo Continue"-Verknüpfung für genau diesen Modus. == Verschiedenes / Erklärungen / FAQ *Session Speichern und Beenden reagiert langsam*: Agordejo ist kein Einzelprogramm wie ein Office-Writer. Die teilnehmenden Programme in der Session sind auch keine Plugins. Wenn Sie die Session beenden wird ein Signal an alle teilnehmenden Programme gesendet, dass sie speichern sollen. Das kann ein paar Momente dauern, in denen Sie "live" mitverfolgen, wie die einzelnen Programme sich beenden und aus der Session verschwinden. Es ist alles in Ordnung. *Ich habe ein Programm hinzugefügt aber es speichert nicht mit der Session*: Unterstützt das Programm Session Management? Wenn nicht, kann Agordejo nichts tun. Aber Sie können die Programmentwickler bitten mit uns Kontakt aufzunehmen (info@laborejo.org) und wir können zusammen an der Unterstützung arbeiten. *Die Programme hängen beim Beenden*: Das tut uns leid. Eigentlich sind die Programme selbst schuld, aber auch wir sind daran interessiert die Situation zu verbessen, indem wir in Zukunft zumindest eine Notlösung anbieten. *Agordejo startet nicht mehr! Ich starte das Programm aber ich sehe nichts.*: Wahrscheinlich ist Agordejo unsichtbar, weil Sie es aus dem Tray heraus beendet hatten. Ist es im Tray? Eigentlich hätte ein kleines Nachrichtenfenster aufpoppen sollen. Falls in ihrem Window-Manager kein Tray vorhanden ist sollte das Programm immer sichtbar sein. Bei besonderen Window-Managern (bei der großen Auswahl in Linux) kann es sein, dass die Tray-Erkennung nicht richtig funktioniert hat. Notfallplan ist es `~/.config/LaborejoSoftwareSuite/agordejo` zu löschen. Dabei werden KEINE Sessions gelöscht, sondern nur lokale Einstellungen wie die Sichtbarkeit des Programmfensters. Beim nächsten Start wird Agordejo sich verhalten wie beim allerersten. *JACK ist abgestürzt. Viele Programme hängen. Was tun um Datenverlust zu vermeiden?*: Vermutlich sind bereits viele Programme der Session nicht mehr richtig lauffähig und reagieren nicht mehr. Am besten benutzen Sie die `Session Abbrechen` Funktion und starten alles neu. Wenn die Daten tatsächlich schon lange ungespeichert waren kann man auch ein Speichern/Beenden wagen. Dann kann es aber sein, dass man beim nächsten Start einige Jack connections von Hand neu ziehen muss. Wer extrem sicher gehen möchte kann vor dem Beenden der Session (mit unweigerlichen abstürzen) manuell im Dateimanager eine Kopie des Session-Verzeichnisses machen. *Ein Programmupdate hat meine Session kaputtgemacht, weil es seine Dateien nicht mehr laden kann.*: Das ist leider ein Problem, dass auch Agordejo nicht lösen kann. Es passiert mit LV2-Plugins ebenso wie mit alle anderen Software, etwa Officeprogramme. Falls Sie befürchten, dass ein Programm in Zukunft inkompatibel wird notieren Sie sich dessen Versionsnummer in den Session-Notizen, damit Sie zumindest zur Not die alte Programmversion wieder installieren können (auch wenn das sehr umständlich ist). *Was ist besser? Monolithische DAW oder Session Management?*: Warum nicht beides? Es gibt keinen Konflikt. Session Management lohnt sich ab zwei teilnehmenden Programmen, und auf die kommt man so gut wie immer. Sie sollten sich nicht genötigt fühlen plötzlich alles modular mit Einzelprogrammen zu machen, nur weil Sie einen Session Manager benutzen. Agordejo ist dafür da, ihre Musikproduktion einfacher zu machen. Wenn es schneller und bequemer ist alle Plugins und Effekte z.B. in einer einzelnen Carla-Instanz zu verwalten dann sollten Sie genau das machen. Wenn Sie prinzipiell alles in Ardour machen wollen, machen Sie das, aber starten Sie Ardour trotzdem im Session Management, denn kein Programm kann alles alleine, und der Zeitpunkt wird kommen, an dem Sie ein weiteres hinzufügen. Session Management ist andere Hirarchieebene. So sind Sequencer oder DAWs selbst keine Plugins. Patroneo gehört nicht "in" Ardour und Ardour gehört nicht "in" Laborejo. Und schon in diesem Beispiel erfüllt jedes der Programme eine Rolle, die die anderen beiden nicht leisten können, da sie einer anderen Design-Philosophie folgen. Und mehr: Manche Programme können keine Plugins hosten, manche können keine Audiodateien exportieren. Das sind deswegen keine schlechten Programme, sondern welche, die sich auf eine Aufgabe konzentrieren. Darüberhinaus gibt es eine Menge Software, die nicht direkt Musikproduktion ausübt, aber trotzdem inhaltlich dazu gehört: Open Broadcast Studio (OBS), Musikplayer, Schreib- und Grafikprogramme etc. *Agordejo beinhaltet Funktionalität, die nicht seine Aufgabe ist*: Musikproduktion ist sehr komplex und Komplexität ist unvermeidlich. Sie ist wie ein Wasserbett: Drückt man die auf der einen Seite runter, muss etwas an einen anderen Stelle hochdrücken. Macht man ein programm "clean and lean", und implementiert damit nur einen Teil des kompletten Arbeitsablaufs, dann kommt der fehlende Teil woanders wieder hoch. Ein minimalistischer Session Manager provoziert Plugins (nicht LV2), Helper-Scripts, Workarounds und Hacks. z.B. Dateiverwaltung nicht mit einzuschließen provoziert Anwender-Fehler im Dateimanager (wie das Löschen der falschen Dateien). Wenn der SM weiß was zu tun ist, und er es tun kann, dann soll er es machen. Oder Abstürze: Technisch gesehen sind abstürzende Programme nicht das "Problem" des Session Managers, aber sie sind Teil der Softwarewirklichkeit. Abstürze passieren jeden Tag und nun muss man damit umgehen. Kann Agordejo die Arbeit vereinfachen und helfen den guten Zustand wieder herzustellen? Dann sollte das geschehen. Session Management ist außerdem eine Chance auch komplexe technische Szenarios zu vereinfachen, z.B. Sessions über das Netzwerk zu verteilen. ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1665611396.701277 agordejo-0.4.2/documentation/index.adoc0000644000175000017500000000221014321633205016645 0ustar00nilsnils:Author: Laborejo Software Suite :Version: 0.4.2 :iconfont-remote!: :!webfonts: //// This documentation is licensed under the Creative Commons Attribution-ShareAlike 4.0 International License. To view a copy of this license, visit http://creativecommons.org/licenses/by-sa/4.0/ or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. A copy of the license has been provided in the file documentation/LICENSE. //// //// https://powerman.name/doc/asciidoc https://asciidoctor.org/docs/user-manual/ //// :nofooter: == Agordejo Multi-Language Documentation image::logo.png["logo", 320, 180] For program version 0.4.2 This site is part of the https://www.laborejo.org[Laborejo Software Suite] Please choose a language * link:english.html[English] * link:german.html[Deutsch (German)] Further Links * https://laborejo.org/bugs/[Bug and Issues, and other Feedback] * Write to info@laborejo.org for any comment or question. New Session Manager * https://linuxaudio.github.io/new-session-manager/api/index.html[API Document] - How to write your own client * https://github.com/linuxaudio/new-session-manager[Sourcecode] and Readme ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1665611335.957255 agordejo-0.4.2/documentation/index.adoc.template0000644000175000017500000000217714321633110020466 0ustar00nilsnils:Author: :Version: :iconfont-remote!: :!webfonts: //// This documentation is licensed under the Creative Commons Attribution-ShareAlike 4.0 International License. To view a copy of this license, visit http://creativecommons.org/licenses/by-sa/4.0/ or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. A copy of the license has been provided in the file documentation/LICENSE. //// //// https://powerman.name/doc/asciidoc https://asciidoctor.org/docs/user-manual/ //// :nofooter: == Multi-Language Documentation image::logo.png["logo", 320, 180] For program version This site is part of the https://www.laborejo.org[Laborejo Software Suite] Please choose a language * link:english.html[English] * link:german.html[Deutsch (German)] Further Links * https://laborejo.org/bugs/[Bug and Issues, and other Feedback] * Write to info@laborejo.org for any comment or question. New Session Manager * https://linuxaudio.github.io/new-session-manager/api/index.html[API Document] - How to write your own client * https://github.com/linuxaudio/new-session-manager[Sourcecode] and Readme ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1665611396.701277 agordejo-0.4.2/documentation/manpageinclude.h2m0000644000175000017500000000250614321633205020302 0ustar00nilsnils [name] Agordejo - Music and audio production session manager based on NSM. [usage] Agordejo (Esperanto: 'place to set things up') is a music production session manager. It is used to start your programs, remember their (JACK) interconnections and make your life easier in general. You can create a session, or project, add programs to it and then use commands to save, start/stop, hide/show all programs at once, or individually. At a later date you can then re-open the session and continue where you left off. Agordejo does not re-invent the wheel but instead uses the New-Session-Manager daemon and enhances it with some tricks of its own, that always remain 100% compatible with the original sessions. [Reporting bugs] https://www.laborejo.org/bugs [copyright] Agordejo 0.4.2 - Copyright 2022 Laborejo Software Suite https://www.laborejo.org/ [examples] Run agordejo. You are now in the session overview. Press the "Quick New" button and add programs by a double clicking on the list. Try right-clicking on many things to get more options in context menus. [see also] The full documentation for Agordejo is maintained as a multi-lingual html site to your systems doc-dir. For example: xdg-open file:///usr/share/doc/agordejo/index.html The documentation can also be found online https://www.laborejo.org/documentation/agordejo ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1665611396.8679454 agordejo-0.4.2/documentation/nsm-data.10000644000175000017500000000076214321633205016506 0ustar00nilsnils.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.2. .TH NSM-DATA "1" "October 2022" "nsm-data 1.1" "User Commands" .SH NAME nsm-data \- manual page for nsm-data 1.1 .SH DESCRIPTION usage: nsm\-data [\-h] [\-v] .PP nsm\-data is a module for Agordejo. It only communicates over OSC in an NSMSession and has no standalone functionality. .SS "options:" .TP \fB\-h\fR, \fB\-\-help\fR show this help message and exit .TP \fB\-v\fR, \fB\-\-version\fR show program's version number and exit ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1639688410.2178724 agordejo-0.4.2/documentation/out/CHANGELOG0000777000175000017500000000000014156724332020601 2../../CHANGELOGustar00nilsnils././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1665611397.1212819 agordejo-0.4.2/documentation/out/english.html0000644000175000017500000016162714321633205020056 0ustar00nilsnils Agordejo

For program version 0.4.2

1. Introduction

Agordejo (Esperanto: 'place to set things up') is a music production session manager. It is used to start your programs, remember their (JACK) interconnections and make your life easier in general.

You can create a session, or project, add programs to it and then use commands to save, start/stop, hide/show all programs at once, or individually. At a later date you can then re-open the session and continue where you left off.

Agordejo does not re-invent the wheel but instead uses the New-Session-Manager daemon and enhances it with some tricks of its own, that always remain 100% compatible with the original sessions.

2. Preamble

Session Management leads to simplification of workflows, overview and control over programs and data and a good portion of convenience :)

No program exists on its own, because no program can do everything that is necessary for today’s music production.

This is obvious in a JACK environment, which is fundamentally modular: Different programs fulfill different functions and "talk" to each other by sending data to each other. A sequencer sends MIDI to a sampler or synthesizer, which is connected to a plug-in host for effects etc.

Even the most monolithic all-in-one DAWs have to, or want to, eventually connect to the outside world. For example, to connect to a screen recorder or streaming program, include a word processor for recording order or lyrics, or to use a function that is simply not available in the DAW.

Much of the work is already done by the JACK subsystem. All programs can share their music in real time, have synchronized timelines and play in the same tempo.

What remains is the tedious work of always starting all programs, loading project files, connecting audio channels etc. Session management in general (e.g. specifically written starter script files) and Agordejo in particular do this work for you, or at the very least, greatly simplify it.

In contrast to the self-written script mentioned above, you don’t have to decide in advance on a setup, but everything is saved automatically as long as you manage everything through Agordejo.

2.1. Example

  • Start Agordejo (Start menu, terminal etc.)

  • Press the button "Quick New" on the left

  • Now you get a choice of programs:

  • A double single click with the mouse starts a program

  • The program appears as entry in the other column and indicates that it is running

  • To show it’s GUI you probably need to double click on this new entry.

  • The programs "jackpatch" and "nsm-data" were already added automatically.

  • Audio and midi ports can now be connected together in a patchbay. The connections are stored in Agordejo.

  • The name of the session so far is simply a date. Through the menu you can enter a real name. Like "My song"

  • If you are finished you can return to the session selection by using the menus "Save and Close"

  • Now Agordejo could be closed itself.

  • All stored data is in a single directory on the hard disk (~/.local/share/nms/My Song)

  • The session can be resumed: After double clicking on the name, all programs start automatically and connect their JACK ports to among themselves.

3. Full Explanation

3.1. Selecting a Session

Sessions are displayed as a table, which you sort by clicking on a column header. Here is shown how the session is called and when it was saved the last time, probably the two most important pieces of information. Also shown is how many programs/clients are in the session and whether it contains symbolic links. The latter is probably set to "Yes" if use a sampler, or similar, that contains large audio files. These are initially only linked into the session, and not copied, to save disk space. The displayed disk space usage is not the actual one, but includes the sizes of symlink-targets. Only when you archive the session or replace the links with real files the number becomes correct.

Finally, the directory in which the files are actually stored is also given.

Sessions represent the directory tree. A session is always a "leaf" and cannot include subsessions. When creating or renaming sessions, you can also arrange them in the tree by using the usual slash notation: song123New album/song 123 or Test/asdfRomantic pop ballads/My heart will keep beating. How to organize your sessions and how many subdirectories you create is up to you. It is, however, not allowed to start the name with / or use the special characters ... The tree view can be deactivated by a checkbox on the left side, e.g. to be able to sort them. This is just a view, your data remains untouched.

Each session has a context menu (e.g. right mouse button) with further options: You can choose to rename a session, delete it (including all associated files on the hard disk!) and more. These functions are equivalent to your file manager. If you like, you can also use your file manager to rename, move, or delete the session directories themselves (unless they are is currently open). You don’t need to restart Agordejo to do this, it will respond to the changes while running.

Click on "New session" to create one. In contrast to the quick view, you must enter the name directly . As mentioned above, you can use the directory tree. In addition, there are several (almost obligatory) programs suggested to start with. Normally one should accept all suggestions. There is always the option to remove them later.

A double click on an existing session (or the "Load Selected" button) does just that.

3.2. In a Session

The view is divided into three areas: Program starter, programs in the current session and the session notes. There is also a dynamic menu.

On the left side you see the program starter. A double-click starts a program instance in this session. You can also start a program more than once. For available programs please refer to the chapter "Program Database".

3.2.1. Running Programs

Started programs are located on the right side. A double-click switches the visibility to hide its window if the program supports it. If not, nothing happens.

The following information is available per program:

  • The name (possibly with icon)

  • A "label" that programs can use freely (e.g. Fluajho shows the loaded .sf2 here)

  • The program status

  • Stopped , not running

  • Ready, running

  • Launch, If the status halts here but the programs works, it is one that does not specifically support session mode. Agordejo cannot know if it is already running or not. Everything is fine! :)

  • Other states are only transitions and usually only visible for a very short time, e.g. Open / Loading

  • Visibility (A cross for visible, blank for invisible)

  • Changes - Are there currently unsaved changes?

  • ID - A unique identifier that can be used to distinguish between multiple instances of the same program

All other functions are accessible via the menu or context menu. One click on a program selects it, and the client menu in the menu bar will now apply to the client. Alternatively, you can right-click on an entry for a context menu, which is identical to the menu.

In addition to self-explanatory functions there are also:

  • Rename gives the program a self-chosen name, mostly to make its purpose clear and to distinguish it better from others. This feature is only available when 'nsm-data' is running in the session.

  • Save only tells this program to save

  • Remove takes the program out of the session. However, no files are deleted in the process. At the moment you have to "clean up" in your file manager by hand.

If the client "nsm-data" is in the session (this is the default setting) the lower area provides a large text field for notes. It is the same as in the quick view. Write what you want: TODO lists, lyrics, credits and sources from external Samples etc.

3.2.2. The Session Menu

In contrast to the quick view, full mode offers menus, which can also be accessed via the usual keyboard shortcuts (Ctrl+S for saving etc.).

  • Save instructs all programs to save, the session continues to run

  • Save and Close ends the session, after all programs saved

  • Abort ends the session without saving the programs

  • Save As saves the session under a different name and closes the current session without saving. From now on you work under the new name.

  • Add Client offers the option to add any program, whether it is in the program database or not.

  • Any installed programs are suggested. Agordejo doesn’t check them for usefulness for a music session, or even for runnability. You will find ls here as well as agordejo itself.

4. Program-Database

Agordejos launcher is based on a program database, which is partly self-generated, partly maintained by hand. As in a start menu Agordejo will offer you only programs that are actually installed on your system.

The database is created at the first start. Depending on your system, this can take some moments to a few minutes.

If you are reinstalling or uninstalling audio programs, you will need to update the database via the command in the control menu. Program installations and system changes are even possible while Agordejo is running (even in a session). After a DB update you can immediately access all new programs.

If you do not see an installed program in our launcher, but you are sure that it supports session management please report it to info@laborejo.org or under https://laborejo.org/bugs .

In addition, you can add (in full view) programs that are not in the database.

4.1. For advanced users

The strict rule is that only programs in the $PATH are included in the database. Absolutel paths are not allowed, even if you enter the program name yourself through the menu. However sometimes you just want to try out software, or you are a developer yourself and want to test without system-wide installation.

In the control menu / settings is a tab "Program-PATH", where you can define and add your own search paths. One absolute directory path per line, no wildcards, trailing slash don’t matter.

For example: /home/myuser/sources/newsequencer/bin/

These search paths are not stored in the session, but locally in your ~/.config directory.

5. Tray

Agordejo has a tray icon, if your window manager supports it. A click on the trayicon shows or hides Agordejo.

If you close Agordejo using the normal window manager function, such as a click on the [X], the program and the session is not terminated, but minimized to the tray.

A right click on the icon gives you access to common functions:

You can directly start the most recently used sessions.

If a session is already running you can save, cancel etc.

Agordejo can also be completely exited here.

6. Program parameters

As an advanced user, you can start Agordejo in the terminal and add some parameters.. For a complete list please use the --help parameter.

For example:

  • --session newAlbum/mySong starts the given session.

  • --continue starts the last active session.

  • --hide starts Agordejo as TrayIcon.

  • --url osc.udp://myhost.localdomain:14294/ connects to this server, if available, or starts the internal session server at this address. This is a very technical option and probably not needed.

  • --session-root /home/user/production2030 sets the root directory. Only sessions in this directory are displayed, everything is stored here.

The combination of --continue and --hide is essentially what many people expect from Session Management: Resuming at the previous state, without any extra windows in their way. If your system uses a start menu you will find not only the normal Agordejo starter but also "Agordejo Continue" to start this mode directly.

7. Miscellaneous / Explanations / FAQ

Session Save and Exit responds slowly: Agordejo is not a standalone program like an word processor. The participating programs in the session are not plugins either. When you end the session a signal is sent to all participating client to save. This may take a few moments where you are able to see "live" how individual programs terminate and disappear from the session. Everything is fine.

I have added a program but it does not save with the session: Does the program support session management? If not, Agordejo cannot do much. But you can ask the program developers to contact us (info@laborejo.org) and we can work together on support.

The programs hang on exit: Sorry about that. Actually, the programs themselves are to blame, but we are also interested in improving the situation by offering at least an emergency solution in the future.

Agordejo won’t start! I start the program but I can’t see anything: Most likely Agordejo is running, but invisible, because you exited it from the tray last time. Is it in the tray? A message should have popped up, maybe you missed it. If there is no tray in your window manager, the program should always be visible. With all these special window managers in Linux it may be that the tray detection did not work properly. Contingency plan is to delete ~/.config/LaborejoSoftwareSuite/agordejo. This will NOT remove any sessions, but only local settings such as the visibility of the program window. At next start Agordejo will behave like the very first start.

JACK crashed. A lot of programs hang. What can I do to prevent data loss? Probably already many programs in the session are not running properly and are not reacting anymore. The best thing to do is to use the 'Abort Session' function and restart everything. If the data has actually been unsaved for a long time, you can also dare to save/exit. It may be necessary to re-draw some jack, or all, jack connections by hand at the next start. If you want to be on the safe side, you can manually make a copy of the session directory in your file manager before ending the session (with inevitable crashes).

A program update broke my session because it can no longer load its files. Unfortunately, this is a problem that even Agordejo can’t solve. It also happens with LV2 plugins and with all other software, such as office programs. If you fear that a program becomes incompatible in the future, write down its version number in the session notes, so that you can at least, in an emergency, reinstall the old program version (even if this is very is cumbersome).

What’s better? Monolithic DAW or session management? Why not both? There is no conflict. Session management is worthwhile with two or more participating programs, which one needs almost always.

You should not feel compelled to suddenly make everything modular with individual programs, only because you use a session manager. Agordejo is designed to make your music production easier. If it is faster and more comfortable to manage all plugins and effects e.g. in a single "Carla" instance then you should do exactly that. If you basically want to do everything in Ardour, do that, but start Ardour anyway in session management, because no program can do everything alone and the time will come where you add a second one.

Session management is another level of hierarchy. Sequencers or DAWs are not plug-ins themselves. Patroneo does not belong "in" Ardour and Ardour does not belong "in" Laborejo. Already in this example each of the programs fulfils a different role because the others follow a different design philosophy and cannot ever offer the same workflow. And more:

Some programs can’t host plugins, some can’t export audio files. They are not bad programs, but programs that concentrate on one task. Furthermore, there is a lot of software that does not directly do music production, but still is connected in the grander scheme: Open Broadcast Studio (OBS), music player, word processors and graphic programs etc.

Agordejo contains functionality which is not within its scope: Music production is very complex and complexity is inevitable. It’s like a waterbed: if you press down on one side, something bounces up in another place. When you create a "clean and lean" program, which therefore implements only a part of the complete workflow, then the missing part pops up somewhere else. A minimalistic session manager provokes plug-ins (not LV2), helper-scripts, workarounds and hacks.

E.g. not to include file management provokes user errors like deleting the wrong files. If the SM knows what to do and it can do it, then let it do it. Or crashes: Technically, crashing programs are not the "problem" of the session managers, but they are part of the software reality. Crashes happen every day and need to be handled. Can Agordejo simplify the work and help to restore good conditions again? Then that should be done. Session management is also an opportunity to simplify even complex technical scenarios, e.g. distributing sessions over the network.

8. Installation and Start

Agordejo is exclusive for Linux. The best way to install is to use your package manager. If it is not there, or only in an outdated version, please ask your Linux distribution to provide a recent version.

If not available in the package repository you can build Agordejo yourself.

Build and Install
  • Please check the supplied README.md for dependencies.

  • You can download a release or clone the git version

  • Change into the new directory and use these commands:

  • ./configure --prefix=/usr

    • The default prefix is /usr/local

  • make

  • sudo make install

Now the program is available to run via your menu/launcher or agordejo in a terminal.

Please read README.md for other ways of starting agordejo, which are impractical for actual use but can be helpful for testing and development.

9. Help and Development

You can help Agordejo in several ways: Testing and reporting errors, translating, marketing, support, programming and more.

9.1. Testing and Reporting Errors

If you find a bug in the program (or it runs too slow) please contact us in a way that suits you best. We are thankful for any help.

How to contact us

9.2. Programming

If you want to do some programming and don’t know where to start please get in contact with us directly. The short version is: clone the git, change the code, create a git patch or point me to your public git.

9.3. Translations

Agordejo is very easy to translate with the help of the Qt-Toolchain, without any need for programming. The easiest way is to contact the developers and they will setup the new language.

However, here are the complete instructions for doing a translation completely on your own and integrating it into the program.

You can add a new language like this:

  • Open a terminal and navigate to qtgui/resources/translations

  • Edit the file config.pro with a text editor

    • Append the name of your language in the last line, in the form XY.ts, where XY is the language code.

    • Make sure to leave a space between the individual languages entries.

  • Run sh update.sh in the same directory

    • The program has now generated a new .ts file in the same directory.

  • Start Qt Linguist with linguist-qt5 (may be named differently) and open your newly generated file

  • Select your "Target Language" and use the program to create a translation

  • Send us the .ts file, such as by e-mail to info@laborejo.org

You can also incorporate the translation into Agordejo for testing purposes. This requires rudimentary Python knowledge.

  • Run the "Release" option in QtLinguists "File" menu. It creates a .qm file in the same directory as your .ts file.

  • Edit qtgui/resources/resources.qrc and duplicate the line <file>translations/de.qm</file> but change it to your new .qm file.

  • run sh buildresources.sh

  • Edit engine/config.py: add your language to the line that begins with "supportedLanguages" like this: {"German": "de.qm", "Esperanto: "eo.qm"}

    • To find out your language string (German, Esperanto etc.) open the python3 interpreter in a terminal and run the following command:

    • from PyQt5 import QtCore;QtCore.QLocale().languageToString(QtCore.QLocale().language())

To test the new translation you can either run the program normally, if your system is set to that language. Alternatively start agordejo via the terminal:

  • LANGUAGE=de_DE.UTF-8 ./agordejo -V --save /dev/null

././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1665611397.1246152 agordejo-0.4.2/documentation/out/favicon.ico0000644000175000017500000001027614321633205017651 0ustar00nilsnils  ¨( @ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿBBBÿÿÿÿCCCÿÔÔÔÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÚÚÚÿKKKÿÿÿÿBBBÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿnnnÿÿÿÿÿÿnnnÿñññÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿôôôÿÿÿÿÿÿÿnnnÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ™™™ÿÿÿÿÿÿÿÿ™™™ÿýýýÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþþþÿªªªÿÿÿÿÿÿÿÿ™™™ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÅÅÅÿÿÿÿÿÿÿÿÿ444ÿÇÇÇÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÎÎÎÿ;;;ÿÿÿÿÿÿÿÿÿÅÅÅÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿñññÿÿÿÿÿÿÿÿÿÿÿ]]]ÿéééÿÿÿÿÿíííÿnnnÿÿÿÿÿÿÿÿÿÿÿïïïÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿlllÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿJJJÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿJJJÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿvvvÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿvvvÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ¡¡¡ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ¡¡¡ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÍÍÍÿÿÿÿÿÿÿÿÿÿÿÿ ÿÿÿÿÿÿÿÿÿÿÿÿÍÍÍÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ÷÷÷ÿÿÿÿÿÿÿÿÿÿÿ ÿÌÌÌÿÿÿÿÿÿÿÿÿÿÿÿöööÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ&&&ÿÿÿÿÿÿÿÿÿÿŠŠŠÿÿÿÿÿ‰‰‰ÿÿÿÿÿÿÿÿÿÿ&&&ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿRRRÿÿÿÿÿÿÿÿÿ%%%ÿøøøÿÿÿÿÿùùùÿ(((ÿÿÿÿÿÿÿÿÿRRRÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿÿÿÿÿÿÿÿÿ···ÿÿÿÿÿÿÿÿÿÿÿÿÿ»»»ÿÿÿÿÿÿÿÿÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ©©©ÿÿÿÿÿÿÿÿSSSÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿRRRÿÿÿÿÿÿÿÿ©©©ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÕÕÕÿÿÿÿÿÿÿ ÿßßßÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿâââÿ ÿÿÿÿÿÿÿÕÕÕÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿûûûÿÿÿÿÿÿÿƒƒƒÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ‚‚‚ÿÿÿÿÿÿÿúúúÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ...ÿÿÿÿÿÿ‚‚‚ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ...ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿZZZÿÿÿÿÿÿ ÿßßßÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿâââÿ ÿÿÿÿÿÿZZZÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ………ÿÿÿÿÿÿÿRRRÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿQQQÿÿÿÿÿÿÿ………ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ±±±ÿÿÿÿÿÿÿÿ¶¶¶ÿÿÿÿÿÿÿÿÿÿÿÿÿ»»»ÿÿÿÿÿÿÿÿ±±±ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÝÝÝÿÿÿÿÿÿÿÿ%%%ÿøøøÿÿÿÿÿùùùÿ(((ÿÿÿÿÿÿÿÿÝÝÝÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿýýýÿ ÿÿÿÿÿÿÿÿ‰‰‰ÿÿÿÿÿˆˆˆÿÿÿÿÿÿÿÿ ÿýýýÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ666ÿÿÿÿÿÿÿÿ ÿÌÌÌÿÿÿÿÿÿÿÿÿ666ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿbbbÿÿÿÿÿÿÿÿÿ ÿÿÿÿÿÿÿÿÿbbbÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ¹¹¹ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ¹¹¹ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåååÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåååÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ>>>ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ>>>ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1665611397.1246152 agordejo-0.4.2/documentation/out/favicon.png0000644000175000017500000000122314321633205017653 0ustar00nilsnils‰PNG  IHDR V%(gAMA± üa cHRMz&€„ú€èu0ê`:˜pœºQ<bKGDÿ‡Ì¿ pHYs``w·tIMEä ú†wÍaIDAT8Ë’?K‚QÆŸ÷•ˆÒijÈ-irj ¬%šrúñÒÐ5eßÀ¯¸DP[HK6½B" BiQö4ÜsßûGÂÎrÏyøqþÝL4*+yrIɘ÷€œôÆ2÷J¢ühk}®âgÈ\gýœ$ÉQF¢ÄKì):¢.}ðsYüŽ 4D<'y)~ÃS V6¬o¹M$«j‚.Iò) ¨Ú%Š€CYû h•ø~’T½÷_¦„,Å-¶É ý ÷»ïV—¡³èÓ Wgî²I²¢×j/ 3E!ùÃcÖ¿ÃT"ÎÝf?5Ð@Ë:ƒYËoé)ìkx7:×âY[ñ@¬ÇÌ-·“3A^šìë?@°ýÌ—(™)xU@S +7$É»U-4PWÑB}$ßýs²¨¤º"˜Šú4ö¶? ‘ÊÖbºö¸  LL´_³ÉhÙ%tEXtdate:create2020-07-24T11:10:17+00:00εèÚ%tEXtdate:modify2020-07-24T11:10:17+00:00¿èPftEXtSoftwarewww.inkscape.org›î<IEND®B`‚././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1665611397.0379474 agordejo-0.4.2/documentation/out/german.html0000644000175000017500000016564214321633205017677 0ustar00nilsnils Agordejo

Für Programmversion 0.4.2

1. Präambel

Session Management führt zur Vereinfachung von Arbeitsabläufen, Übersicht und Kontrolle über Programme und Daten und eine gehörige Portion positive Bequemlichkeit :)

Kein Programm existiert für sich alleine, denn kein Programm kann alles leisten, was für heutige Musikproduktion nötig ist.

Direkt ersichtlich ist das in einer JACK Umgebung, die prinzipiell modular ist: Verschiedene Programme erfüllen verschiedene Zwecke und "sprechen" miteinander, indem sie sich Daten schicken. Ein Sequencer schickt Midi an einen Sampler oder Synthesizer, der mit einem Plugin-Host für Effekte verbunden ist usw.

Selbst die monolithischsten All-In-One DAWs müssen, oder möchten, irgendwann mit der Außenwelt kommunizieren. z.B. um sich mit einem Screenrecorder oder Streamingprogramm zu verbinden, einen Wordprozessor mit Aufnahmereihenfolge oder Liedtexten zu starten, um Musik zu visualisieren oder (um ehrlich zu bleiben) eine Funktion zu nutzen, die es in diesem Programm eben nicht gibt.

Einen Großteil der Arbeit erledigt bereits das JACK-Subsystem. Alle Programme können ihre Musik in Echtzeit teilen, verfügen über synchronisierte Timelines und spielen im gleichen Tempo.

Was übrig bleibt ist die leidige Arbeit jedesmal alle Programme zu starten, die Projektdateien zu laden, alle Audiokanäle zu verbinden usw. Session Management im allgemeinen (z.B. extra geschriebene Starter-Scriptdateien) und Agordejo im speziellen nehmen Ihnen diese Arbeit ab, oder vereinfachen sie zumindest stark.

Im Gegensatz zum erwähnten selbstgeschriebenen Script müssen Sie sich nicht im Vorraus für ein Setup entscheiden, sondern alles wird automatisch verwaltet, solange Sie alles durch den Session Manager Agordejo starten.

1.1. Anwendungsbeispiel

  • Agordejo starten (Startmenü, Terminal etc.)

  • Den Knopf auf der linken Seite drücken: "Schnellstart Neu"

  • Nun bekommt man eine Auswahl an Programmen:

  • Ein Doppelklick mit der Maus startet ein Programm

  • Das Programm erscheint in der anderen Spalte und zeigt an, dass es läuft.

  • Die GUI des Programms erscheint evtl. erst wenn man auf den neuen Eintrag doppelklickt.

  • Die Programme "jackpatch" und "nsm-data" wurden schon automatisch hinzugefügt

  • Audio- und Midiports können nun in einer Patchbay miteinander verbunden werden. Die Verbindungen werden in Agordejo gespeichert.

  • Der Name der Session ist bisher einfach ein Datum. Im Menü kann man richtigen Namen eingeben. Etwa "Mein Lied"

  • Ist man soweit fertig kommt man durch den Menüeintrag "Speichern und Schließen" wieder in die Sessionauswahl zurück

  • Nun könnte Agordejo geschlossen werden.

  • Alle gespeicherten Daten liegen in einem einzigen Verzeichnis auf der Festplatte (~/.local/share/nms/Mein Lied)

  • Die Session kann weitergeführt werden: Nach dem Doppelklick auf den Namen starten alle Programme automatisch und verbinden ihre JACK-Ports untereinander.

2. Vollständige Erklärung

2.1. Session Auswählen

Sessions stellen sich als Tabelle dar, die Sie durch den Klick auf eine Spaltenüberschrift ordnen können. So wird hier gezeigt wie die Session heißt und wann das letzte mal gespeichert wurde, wohl die beiden wichtigsten Informationen. Dann wird gezeigt wie viele Programme/Clients die Session verwaltet und ob sie Symbolische Links enthält. Letzteres steht wahrscheinlich auf "Ja", wenn Sie einen Sampler o.ä. benutzen, der große Audiodateien geladen hat. Diese werden zunächst nur in die Session gelinkt, und nicht kopiert, um Speicherplatz zu sparen. Der angezeigte Speicherverbrauch ist nicht der tatsächliche Verbrauch, sondern beinhaltet die Größen der Symlinks. Erst wenn Sie die Session archivieren oder die Links durch reale Dateien ersetzen stimmt die angezeigte Zahl.

Schließlich wird auch das Verzeichnis angegeben, in dem die Dateien tatsächlich gespeichert sind.

Sessions stellen den Verzeichnisbaum dar. Eine Session ist immer ein "Blatt" und kann keine Subsessions enthalten. Sie können beim Anlegen oder Umbenennen die Sessions auch im Baum anordnen indem Sie die gewohnte Schrägstrichschreibweise benutzen: song123Neues Album/Song 123 oder Versuche/asdfRomantische Pop-Balladen/Mein Herz wird weiter schlagen. Wie Sie ihre Sessions organisieren, und wie viele Unterverzeichnisse Sie anlegen, steht Ihnen frei. Es ist allerdings nicht erlaubt den Namen mit / anfangen zu lassen oder die speziellen Zeichen .. zu benutzen. Die Baumansicht kann durch eine Checkbox an der linken Seite deaktiviert werden, um etwa besser sortieren zu können. Das ist nur eine Ansicht, ihre Daten bleiben unangetastet.

Jede Session verfügt über ein Kontextmenü (z.B. rechte Maustaste) mit weiteren Optionen: Sie können eine Session umbennen, löschen (inkl. aller dazugehörigen Dateien auf der Festplatte!) und mehr. Diese Funktionen sind gleichwertig zu ihrem Dateimanager. Falls Sie möchten, können Sie auch einfach die Sessionverzeichnisse selbst umbennen, verschieben oder löschen (sofern diese nicht gerade geöffnet ist). Dazu muss Agordejo nicht neugestartet werden, es reagiert selbst auf die Änderungen.

Klicken Sie auf "Neue Session" um diese anzulegen. Im Gegensatz zur schnellen Ansicht müssen Sie hier den Namen direkt eingeben. Wie oben erwäht können Sie dazu den Verzeichnisbaum benutzen. Darüberhinaus wird angeboten verschiedene (fast schon obligatorische) Programme direkt mitzustarten. Im Normalfall sollte man alle Vorschläge akzeptieren. Es gibt jederzeit die Möglichkeit diese wieder zu entfernen.

Ein Doppelklick auf eine existierende Session (oder der Knopf "Lade Ausgewählte") machen genau das.

2.2. In einer Session

Die Ansicht ist in drei Bereiche eingeteilt: Programmstarter, Programme in der laufenden Session und die Session-Notizen. Dazu gibt es ein dynamisches Menü.

Auf der linken Seite sehen die den Programmstarter. Ein Doppelklick startet eine Programminstanz in der Session. Sie können ein Programm auch mehrmals starten. Welche Programme zur Verfügung stehen entnehmen Sie bitte dem Kapitel "Programm-Datenbank".

2.2.1. Laufende Programm

Gestartete Programme befinden sich auf der rechten Seite. Ein Doppelklick schaltet die Sichtbarkeit um, sofern das Programm unterstüzt sein Fenster zu verstecken. Falls nicht passiert nichts.

Pro Programm gibt es folgende Informationen:

  • Der Name (evtl. mit Icon)

  • Ein "Label", das Programme frei benutzen können (z.B. zeigt Fluajho hier das geladene .sf2 an)

  • Den Programmstatus (auf Englisch, da es sich um definierte Schlüsselworte handelt).

  • Stopped / Gestoppt, läuft nicht

  • Ready / Läuft und Bereit

  • Launch / Startet. Wenn das Programm hier stehen bleibt, aber funktioniert, handelt es sich um eins, dass nicht speziell den Sessionmodus unterstützt. Agordejo kann nicht wissen, ob es bereits läuft oder nicht. Alles ist in Ordnung! :)

  • Weitere Zustände sind nur Übergänge und meist nur sehr kurz zu sehen, z.B. Open / Läd gerade

  • Sichtbarkeit (Ein Kreuz für sichtbar, Leer für unsichtbar)

  • Änderungen - Gibt es momentan ungespeicherte Änderungen?

  • ID - Ein eindeutiges Kürzel mit dem man auch mehrere Instanzen des gleichen Programmes auseinander halten kann

Alle weiteren Funktionen sind durch das Menü oder Kontextmenü zugänglich. Ein Klick auf ein Programm wählt es aus, und das Clientmenü in der Menüleiste gilt nun dafür. Alternativ kann mit Rechtsklick auf den Eintrag das Kontextmenü geöffnet werden, das identisch ist.

Neben den selbsterklärenden Funktionen gibt es noch:

  • Umbenennen gibt dem Programm einen selbstgewählten Namen, besonders um seinen Zweck deutlich zu machen und es besser von anderen zu unterscheiden. Diese Funktion steht nur zur Verfügung, wenn nsm-data in der Session läuft.

  • Speichern weist nur dieses Programm an abzuspeichern

  • Entfernen nimmt das Programm aus der Session. Dabei werden jedoch keine Dateien gelöscht. Zur Zeit muss leider noch von Hand im Dateimanager "aufgeräumt" werden.

Befindet sich der Client "nsm-data" in der Session (das ist Voreinstellung) steht im unteren Bereich ein großes Textfeld für Notizen zur Verfügung. Es ist dasselbe wie in der schnellen Ansicht. Schreiben Sie was Sie möchten: TODO-Listen, Liedtexte, Credits und Quellen von externen Samples etc.

2.2.2. Das Session Menü

Im Gegensatz zur schnellen Ansicht stehen hier weitere Menüs zur Verfügung, die auch über die üblichen Tastaturkürzel zu erreichen sind (Strg+S für Speichern etc.).

  • Speichern weist alle Programme an zu speichern, die Session läuft weiter

  • Speichern und Schließen beendet die Session, vorher speichern alle Programme noch einmal ab

  • Abbrechen beendet die Session, ohne dass die Programme abspeichern

  • Speichern Unter speichert die Session unter einem anderen Namen und schließt die laufende ohne abzuspeichern. Ab nun arbeitet man in der neuen Session.

  • Client Hinzufügen bietet die Option ein beliebiges Programm hinzuzufügen, egal ob es in der Programmdatenbank ist, oder nicht.

  • Es werden alle installierten Programme vorgeschlagen. Agordejo überprüft diese nicht auf Sinnhaftigkeit für eine Musik-Session, oder auch nur auf Lauffähigkeit. Sie finden hier ls wie auch agordejo selbst.

3. Programm-Datenbank

Agordejos Programmstarter basiert auf einer Programmdatenbank, die sich teilweise selbst erstellt, teilweise von Hand eingepflegt wurde. Das bedeutet nichts anderes, als das alle installieren Programme in ihrem System überprüft werden (wie ein Startmenü) und Ihnen im Agordejo-Starter nur das angeboten wird, was Sie auch tatsächlich installiert haben.

Beim ersten Start wird daher die Programmdatenbank erstellt. Je nach System kann dies einige Augenblicke bis einige Minuten dauern.

Wenn Sie Audio-Programme neu installieren, oder deinstallieren müssen Sie die Datenbank selbst aktualisieren. Im Steuerungsmenü gibt es den Befehl. Programminstallationen sind sogar möglich während Agordejo läuft (auch in einer Session). Nach einem DB-Update stehen Ihnen sofort alle neuen Programme zur Verfügung.

Wenn Sie ein installiertes Programm nicht in unserer Liste sehen, aber von dem Sie sicher sind, dass es Session Management unterstützt melden Sie es bitte an info@laborejo.org oder unter https://laborejo.org/bugs .

Darüberhinaus können Sie (in der vollen Ansicht) Programme hinzufügen, die nicht in der Datenbank sind. Siehe dort.

3.1. Für Fortgeschrittene

Die eiserne Regel ist, dass nur Programme im $PATH in die Datenbank aufgenommen werden. Absolute Pfade sind unzulässig, selbst wenn man den Programmnamen selbst durch das Menü eingibt. Allerdings möchte man manchmal Software nur ausprobieren, oder ist selbst Entwickler und möchte ohne systemweite Installation testen.

In den Einstellungen im Steuerungsmenü gibt es einen Tab "Programm-PATH", wo Sie eigene Suchpfade hinzufügen können. Ein absoluter Pfad pro Zeile, keine Wildcards, Trailing Slash spielt keine Rolle.

Zum Beispiel: /home/myuser/sources/newsequencer/bin/

Diese Suchpfade werden nicht in der Session gespeichert sondern lokal in ihrem ~/.config Verzeichnis.

4. Tray

Agordejo verfügt über ein Tray-Icon, sofern ihr Windowmanager das unterstüzt. Ein Klick auf das Trayicon zeigt oder versteckt Agordejo.

Schließt man Agordejo über die normale Windowmanagerfunktion, etwa ein Klick auf das [X], wird das Programm und die Session nicht beendet, sondern in den Tray minimiert.

Ein Rechtsklick auf das Icon bietet Schnellzugriff auf häufige Funktionen:

Sie können hier die zuletzt benutzen Sessions direkt starten.

Läuft bereits eine Session können Sie speichern, abbrechen etc.

Agordejo kann hier auch komplett beendet werden.

5. Programmparameter

Als fortgeschrittener Benutzer können Sie Agordejo im Terminal starten und dort einige Parameter angeben. Für eine vollständige Liste benutzen Sie bitte den --help Parameter.

Eine Auswahl:

  • --session neuesAlbum/meinLied startet direkt die angegebene Session.

  • --continue startet die zuletzt benutzte Session

  • --hide startet Agordejo als Trayicon.

  • --url osc.udp://myhost.localdomain:14294/ verbindet sich zu diesem Server, falls vorhanden, oder startet den internen Session-Server unter dieser Adresse. Dies ist eine sehr technische Option und wird wahrscheinlich nicht benötigt.

  • --session-root /home/benutzer/produktion2020 setzt das Wurzelverzeichnis. Nur Sessions in diesem Verzeichnis werden angezeigt, alles wird dort gespeichert.

Die Kombination von --continue und --hide ergibt einen Modus, den viele Leute vom Session Management erwarten: dort weiter machen wo man aufgehört hat, ohne dass Extrafenster angezeigt werden. Falls Ihr System über ein Startmenü verfügt finden Sie daher neben dem normalen Agordejo-Starter auch eine "Agordejo Continue"-Verknüpfung für genau diesen Modus.

6. Verschiedenes / Erklärungen / FAQ

Session Speichern und Beenden reagiert langsam: Agordejo ist kein Einzelprogramm wie ein Office-Writer. Die teilnehmenden Programme in der Session sind auch keine Plugins. Wenn Sie die Session beenden wird ein Signal an alle teilnehmenden Programme gesendet, dass sie speichern sollen. Das kann ein paar Momente dauern, in denen Sie "live" mitverfolgen, wie die einzelnen Programme sich beenden und aus der Session verschwinden. Es ist alles in Ordnung.

Ich habe ein Programm hinzugefügt aber es speichert nicht mit der Session: Unterstützt das Programm Session Management? Wenn nicht, kann Agordejo nichts tun. Aber Sie können die Programmentwickler bitten mit uns Kontakt aufzunehmen (info@laborejo.org) und wir können zusammen an der Unterstützung arbeiten.

Die Programme hängen beim Beenden: Das tut uns leid. Eigentlich sind die Programme selbst schuld, aber auch wir sind daran interessiert die Situation zu verbessen, indem wir in Zukunft zumindest eine Notlösung anbieten.

Agordejo startet nicht mehr! Ich starte das Programm aber ich sehe nichts.: Wahrscheinlich ist Agordejo unsichtbar, weil Sie es aus dem Tray heraus beendet hatten. Ist es im Tray? Eigentlich hätte ein kleines Nachrichtenfenster aufpoppen sollen. Falls in ihrem Window-Manager kein Tray vorhanden ist sollte das Programm immer sichtbar sein. Bei besonderen Window-Managern (bei der großen Auswahl in Linux) kann es sein, dass die Tray-Erkennung nicht richtig funktioniert hat. Notfallplan ist es ~/.config/LaborejoSoftwareSuite/agordejo zu löschen. Dabei werden KEINE Sessions gelöscht, sondern nur lokale Einstellungen wie die Sichtbarkeit des Programmfensters. Beim nächsten Start wird Agordejo sich verhalten wie beim allerersten.

JACK ist abgestürzt. Viele Programme hängen. Was tun um Datenverlust zu vermeiden?: Vermutlich sind bereits viele Programme der Session nicht mehr richtig lauffähig und reagieren nicht mehr. Am besten benutzen Sie die Session Abbrechen Funktion und starten alles neu. Wenn die Daten tatsächlich schon lange ungespeichert waren kann man auch ein Speichern/Beenden wagen. Dann kann es aber sein, dass man beim nächsten Start einige Jack connections von Hand neu ziehen muss. Wer extrem sicher gehen möchte kann vor dem Beenden der Session (mit unweigerlichen abstürzen) manuell im Dateimanager eine Kopie des Session-Verzeichnisses machen.

Ein Programmupdate hat meine Session kaputtgemacht, weil es seine Dateien nicht mehr laden kann.: Das ist leider ein Problem, dass auch Agordejo nicht lösen kann. Es passiert mit LV2-Plugins ebenso wie mit alle anderen Software, etwa Officeprogramme. Falls Sie befürchten, dass ein Programm in Zukunft inkompatibel wird notieren Sie sich dessen Versionsnummer in den Session-Notizen, damit Sie zumindest zur Not die alte Programmversion wieder installieren können (auch wenn das sehr umständlich ist).

Was ist besser? Monolithische DAW oder Session Management?: Warum nicht beides? Es gibt keinen Konflikt. Session Management lohnt sich ab zwei teilnehmenden Programmen, und auf die kommt man so gut wie immer.

Sie sollten sich nicht genötigt fühlen plötzlich alles modular mit Einzelprogrammen zu machen, nur weil Sie einen Session Manager benutzen. Agordejo ist dafür da, ihre Musikproduktion einfacher zu machen. Wenn es schneller und bequemer ist alle Plugins und Effekte z.B. in einer einzelnen Carla-Instanz zu verwalten dann sollten Sie genau das machen. Wenn Sie prinzipiell alles in Ardour machen wollen, machen Sie das, aber starten Sie Ardour trotzdem im Session Management, denn kein Programm kann alles alleine, und der Zeitpunkt wird kommen, an dem Sie ein weiteres hinzufügen.

Session Management ist andere Hirarchieebene. So sind Sequencer oder DAWs selbst keine Plugins. Patroneo gehört nicht "in" Ardour und Ardour gehört nicht "in" Laborejo. Und schon in diesem Beispiel erfüllt jedes der Programme eine Rolle, die die anderen beiden nicht leisten können, da sie einer anderen Design-Philosophie folgen. Und mehr:

Manche Programme können keine Plugins hosten, manche können keine Audiodateien exportieren. Das sind deswegen keine schlechten Programme, sondern welche, die sich auf eine Aufgabe konzentrieren. Darüberhinaus gibt es eine Menge Software, die nicht direkt Musikproduktion ausübt, aber trotzdem inhaltlich dazu gehört: Open Broadcast Studio (OBS), Musikplayer, Schreib- und Grafikprogramme etc.

Agordejo beinhaltet Funktionalität, die nicht seine Aufgabe ist: Musikproduktion ist sehr komplex und Komplexität ist unvermeidlich. Sie ist wie ein Wasserbett: Drückt man die auf der einen Seite runter, muss etwas an einen anderen Stelle hochdrücken. Macht man ein programm "clean and lean", und implementiert damit nur einen Teil des kompletten Arbeitsablaufs, dann kommt der fehlende Teil woanders wieder hoch. Ein minimalistischer Session Manager provoziert Plugins (nicht LV2), Helper-Scripts, Workarounds und Hacks. z.B. Dateiverwaltung nicht mit einzuschließen provoziert Anwender-Fehler im Dateimanager (wie das Löschen der falschen Dateien). Wenn der SM weiß was zu tun ist, und er es tun kann, dann soll er es machen.

Oder Abstürze: Technisch gesehen sind abstürzende Programme nicht das "Problem" des Session Managers, aber sie sind Teil der Softwarewirklichkeit. Abstürze passieren jeden Tag und nun muss man damit umgehen. Kann Agordejo die Arbeit vereinfachen und helfen den guten Zustand wieder herzustellen? Dann sollte das geschehen.

Session Management ist außerdem eine Chance auch komplexe technische Szenarios zu vereinfachen, z.B. Sessions über das Netzwerk zu verteilen.

7. Installation und Start

Agordejo ist exklusiv für Linux. Am besten installieren Sie Agordejo über deinen Paketmanager. Falls es dort nicht vorhanden ist, oder nur in einer veralteten Version, bitten sie ihre Linuxdistribution Agordejo bereitzustellen.

Falls nicht in den Paketquellen vorhanden kann man Agordejo auch selbst "bauen".

Abhängigkeiten*
  • Eine Liste der Abhängigkeit befindet sich in der README.md

  • Kompilieren und Installieren geht entweder mit einem Releasedownload oder mit der Git-Version:

  • Wechseln Sie in das neue Verzeichnis und benutzen diese Befehle: *./configure --prefix=/usr

    • Das Standardprefix is /usr/local

  • make

  • sudo make install

Nun ist das Programm durch agordejo in ihrem Terminal oder Programmstarter vorhanden.

In der Datei README.md befinden sich weitere Möglichkeiten agordejo zu starten. Diese sind zum Musikmachen nicht praktikabel, aber nützlich für Tests und Entwicklung.

8. Helfen und Entwicklung

Sie können Agordejo auf viele Arten und Weisen helfen: Testen und Fehler melden, übersetzen, marketing, anderen Nutzern helfen und schließlich programmieren.

8.1. Testen und Programmfehler

Falls Sie einen Fehler im Programm entdecken (oder es zu langsam läuft) melden Sie diese bitte auf eine Art und Weise, die ihnen am besten passt.

Kontaktmöglichkeiten

9. Entwicklung

Falls Sie an der Entwicklung interessiert sind, melden Sie sich am besten direkt bei uns (s.o.) Kurzversion: clone git, programmieren, einen git-patch erstellen oder uns eine git URL zukommen lassen.

9.1. Übersetzungen

Agordejo ist mit Hilfe der Qt-Toolchain sehr einfach zu übersetzen, ohne, dass man dafür Programmieren muss. Die einfachste Variante ist es einfach die Entwickler anzusprechen und sie werden die neue Sprache einrichten.

Hier ist dennoch die komplette Anleitung, um eine Übersetzung komplett alleine anzufertigen und in das Programm einzubinden.

So fügt man eine neue Sprache hinzu:

  • Öffnen Sie ein Terminal und navigieren zu qtgui/resources/translations

  • Bearbeiten Sie die Datei config.pro in einem Texteditor.

    • Hängem Sie in der letzten Zeile den Namen der neuen Sprache an, in der Form XY.ts, wobei XY der Sprachcode ist.

    • Achten Sie bitte darauf ein Leerzeichen zwischen den einzelnen Sprachen zu lassen

  • Führen Sie sh update.sh im selben Verzeichnis aus.

    • Das Programm hat nun eine neue .ts-Datei im Verzeichnis erstellt.

  • Starten Sie Qt Linguist mit linguist-qt5 (kann evtl. anders heißen) und öffnen von dort die neu generierte Datei.

  • Wählen Sie die "Target Language", also Zielsprache, aus und benutzen das Programm um eine Übersetzung anzufertigen.

  • Senden Sie uns bitte die .ts Datei, z.B. per E-Mail an info@laborejo.org (s.u bei Bugs und Programmfehler für mehr Kontaktmöglichkeiten)

Die Übersetzung können Sie auch selbst, zum Testen, einbinden. Dafür sind rudimentäre Python Kentnisse nötig.

  • Im Qt Linguist "Datei" Menü ist eine "Release" Option. Das erstellt eine .qm Datei im gleichen Verzeichnis wie die .ts Datei.* Bearbeiten Sie qtgui/resources/resources.qrc und kopieren die Zeile <file>translations/de.qm</file> . Dabei das Länderkürzel zum Neuen ändern.

  • Führen Sie sh buildresources.sh aus

  • Bearbeiten Sie engine/config.py: Die neue Sprache hinzufügen. z.B. {"German":"de.qm", "Esperanto:"eo.qm"}

    • Um den Sprachstring herauszufinden öffnen Sie den python3-Interpreter im Terminal und führen aus:

    • from PyQt5 import QtCore;QtCore.QLocale().languageToString(QtCore.QLocale().language())

Um die neue Übersetzung zu testen starten Sie das Programm, falls ihr System bereits auf diese Sprache eingstellt ist. Ansonsten starten Sie agordejo mit diesem Befehl, Sprachcode ändern, vom Terminal aus:

  • LANGUAGE=de_DE.UTF-8 ./agordejo -V

././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1665611396.9546132 agordejo-0.4.2/documentation/out/index.html0000644000175000017500000007366414321633205017537 0ustar00nilsnils Agordejo Multi-Language Documentation

Agordejo Multi-Language Documentation

logo

For program version 0.4.2

This site is part of the Laborejo Software Suite

Please choose a language

Further Links * Bug and Issues, and other Feedback * Write to info@laborejo.org for any comment or question.

New Session Manager * API Document - How to write your own client * Sourcecode and Readme

././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1665611397.1246152 agordejo-0.4.2/documentation/out/logo.png0000644000175000017500000010615414321633205017177 0ustar00nilsnils‰PNG  IHDRྠïaÍ pHYsÃÃÇo¨dtEXtSoftwarewww.inkscape.org›î< IDATxœìÝw¸%E™øñï`È$©$e  ‚iUÌyU\3ê0§u]ÝU×UYuÍ‚Š ¢¨¨¸"Ö (ŠdÉ 90 0sÔ=?/×Î9]]oõ9ßÏóÔ s»ßê¾çt÷ÛUo$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’4‚D IRm8í¿¯‰BóY XcÊß , ŠE’$å·XwÚŸÝLÄ"ÍÉœ$•µØ¸pwÒ ÃúÀB`u`íÉ¿w)ð‰ˆÇÜ–ÀÎÀ½€{ò·sµÉdÛtžŸ¿¸¸|Z»88¸©…¸ÇÍ:ÀNÀSÚæ“mÓɶÚ?¿ ¸fJ»ŠtŽÎÎl¶yYK€û·“~/oV~WÆ5—ÕHçw®Ÿú°5Û¿ß,'õõöÉ.®®¤;fë‘ÎÙŠè@ÔØBÒõþfàŽàXFÍÀš¤¤K?Öet_Æ,$½|º9: “îÅvvœüçV¤küæÀfüí¾y6ËH×ø«I×ûK¹ëµþlü<ª p’TƇKIÅó¹ØŽ”¼Q;ÖöÜØØ¢À~/%%âþü ø é¦P³ÛØxàdÛXÔò>¯Nžl¿~N÷’§[Ïòÿ&H«wÔfJÎ-#œª÷÷§ëýÝ5IßqÓÿ9ýÏfK®•p;pé!ìLàtàÀ)Ô—è:”Dí™z~z#oW2ûïæ¤„óL–“•sýýÞ¶‘’ð·—Fk“^­AzàïéãÙôF¦ôúÒKHÝ:ùÿ®® =4_FJ†Âwä–À_§ýÙÔs0õ³µŠt.z¦ž—~xÓ¾_óš>ú»óý>ô¬KJ¸@ú>è½D™:’¹÷2Óvïdî/SÝLúýí½œè‡Þ÷Ø ¤ßÍÙŽaïûr.½mO·ó?ƒ¯ÇÌ׸Þg­wzÇ©÷÷{çç›À3çÙG6ö <€to6_‚­©Û?‘®õ'‘®õ´¼O1p’Ô¾û’’-ƒz/ðî̱Œ»í§ÿ<„þ J8ø5ð3à8Òq¶:éí<¸Gl8@z(ûðcàR²¦v’‰êÏí¤ïêŸ?"%ǧ' K;Ø#8†ÜLz(>‡4šøôÉv&õŽæœn3ünoÛúK¢_ÇpÉÄ®øð¼è út_à‰ÀcII·¶_®õã\Òµþ‡¤{²Ú^ÌH’¤9|ŠôFyÐvw­_¥ál¼•ôvs˜óPº­"=txý¿ÑïºÀCO׿kg“¦ÂÖj5âS—Û5¤ïï‡÷Òú¤yb÷¶ 8øðdÒè¢ZmDüñõÖoR­7 }TÛ¡}‡(ÛÿBJ¢G«ùÚuÀ€G“FþJ8NƒZøþîÔæE¤imªÏ:¤)4Ó‹Ãöë…À—²E3>’Þ¦¾x&£Üvìó\œVA¬m¶÷yJXHš;JÇüÒK·­2'IšÑÄéÙþ¾½m®“¦P½·ïMÚC‹GÝ=k‘Þ|GKµó€w’ÞøwÉÚ¤©'× Ûj‘’ï5L¾€øã1Šíài?þ­ ú:*íDÒ šRÉÓ™,›!.[¾Öo¢õçÄÚf«å¹àÉÀŸ‰?mµåÀÇ€»å:`’4݉ÿ²³ý}ûê\'Maö$Ïù=ªtà²x)ý9Œjw’V<Û»á±,Ṥ•}£Y©viÊM¤S‰?£Ü޶íûl î‹ôqÔÚå¤ip‹ðŒÓK¢ˆö>ÏÃ7+ˆµÍö²>C[vþøãPªÝ¼™:^ºI1? þKÎö÷íOs4…ùyÎïÀ6…cï‚¥¤úHÑŸ¿šÚoHoœk³ieÉèãÕŽ$nªÊûŒÑ6|»”\nÃG*èߨ¶ËHõ'KNç¥ix5¶Çôy>YA¬m¶¨û€µI ¢ÜÞGŒ£ØÎÞø(JÒÿåfûû¶‚ºj Ön&ß9þ`Ùð«¶ð.Òï}ôg¯Æö³ámv HSƒ—\¢Ûµ¤Ñš¥}iÈxmƒ·’ê|åôÆ ú5êíg”[æG…ú4®í9}ž‡×Wk›m×>CNdtëïÒV‡3ÂVÐÅÕGg}F{Å .[Ø!:ÝŤ7¹¼”غ5µØ 8 x&gs^t“6'šþ®Â iщ#¯ô¥\^p_ãîu¤šm9ß/ɸ-Íl?àà•öuq}Œ³Múü{i5ŠX«({°ˆT×õ—À= î·V H«ºŸ ì‹*dNƒXJ÷WÝeK£Ð]˜y{Ïϼͮy)ù¶{t •«!÷ àÀ㢩ÐÀïHÉä|à/ëI¤Äs®$œç¯Œ%Àÿ’äm&È=ŸíڸϿwv«Qĺ˜4꼄 ï#ÿèß®»7©LÊó¢Q]LÀiÙÕ?ÏO=D;I¢×0žIðE¤©]GëÇÒçïÿy¤)]Q«‚­þJZyíDR"ð² Xf³3) ·}Q`º«GGçôtÒˆ•ñ,àW´7ãã´–¶«¤ßpç×´H ßÚÏŽ“ûzt¡ýÍäZR2õDà÷¤wÆ3ÝšÀáÀ‡I÷²’™j ÄVuóüÔãå-m÷>À£HEÕÇÅšÀ×€§ư 8‹4à4à:RÑõe¤©ù«“ÉØ–”x½/°FH¤Iä¸wO¶Ò‰â«HµÎ~Lz X6ÃßYØ x,©NPÔ¢=ëǯ>ßâ~NoqÛ¥ÝIwî]ŸL*Fþ††ÛYFúLB™‰®œ¿]I#Wö'Âì”ÌÛÓ]õ›€› %Zk\´¨©ÿ+°‡®_Ø×Twߎ&¿™¦è¯ìüð4àÁÅ¢›Ý{î=VÇ¢`ã8’BÃ;xXtšÕùX{¡Fß´U¯íûÀ[Úvm6¾ì´ÿ“HõºŽ®àçÖ ­‚õLàÙä­Ø I ÂÒþxSá}^¼ ø:iÕµ~-&%ußCš&ix3ð_-îc]Rÿþ¤ÑY¢lºAý™T¬þ·À™ÀümJÕ"` Ò(½Çˆ±¤Ußl¸G’ê”-%݇•~èÔm¤kÕqÀH£nœüKH¿›‘~'÷ ¿}©/9w=é»<çJó €“ú~?ÒˆùÈ6óùýýþ®FºÿÙŒôùÜ‘”Èì7!–ËOHßoýØ}òïî@:Kéæì°+HɨÓH ©cìžeP˜ÜGÉ{›;O®ðgwÞ <øÜÇñ¤{[‚ãÔ׿²ŒmîUwœžï5´{žW÷*Ö›8‘F D|–þ <&S?6&»¥Pì¥ê¾Lµøøñ6i_ "kb©pþ­ñOoÿÚ°/ƒX xidUt¿§·w ÑŸÝ€¯’®ƒÑñOo×$ÉeiDǧȻÒv®v:éû{P›:¹‚>LmWÐn½ÆµHäߣÎßßa>SÝxÙdÿî(ïĺ1ð*Ò”Æèã>_[EšÚXzt×c)½<•ôßÔ>¤Å7¢Ïݯ€u2ôGÒˆÛŠø/,Ûüí³@ógÚ?χëMŒ ˆy»“4šª:Û‘Þ|¶Ý‡‹Zˆ}>ÿÑ ÞaÏÓA™ûpê¸1uæ~Íg )‘Ýï©íÍ ú³i´\t¦·#ôi.[ß­ SÛ/öiðR=§è¾ôÚÅ´Wnª½©/ùÓ47ÕæÀ¿‘FC¶ï¥â\}2΢¤¤þ¾ú9¨}(Ÿ|ûy¬M*k}Dú=“¤Y=–ø/+Ûüí¥³@±eÎóÔ=}¬‰µHÓÎJv®"M«hÓ"Ò4Ã6ûqbË}˜®íŸÓÛ*Ò*¢mؘôfºôïÞÔ¶xnKý›Ë§‡Œ·Ö$izæÏ*èÇôöІýšÍÒÔ¬èþõZÓ\Ï"à_HSË£û4Aún͵²í\Ö§®Q€9p=›Gµïä›ÊüŽ–blÒVó²ýÞ¤Jöõ[´óBt)Á}.l©’FÄ›ˆÿ¢²Íß>6Û T_¦Ü¹Î=¨ I7\¥?7×Pvá6oü¾_°O#%ŒJž«¦É™ù¬ ü¦pŸ¦·”¯{¸&i¥ØÈ~ç<Çk’êE÷ejûM†~Íf5R­µè>N/×sR]Õè~M‡eîÛl¶'•ˆîïí$àz¢kH®zÈ ©k$æðÁL}ÄÝH£@KöóÚOx\¸O3µÿh¹’:ìKÄIÙæo?™íªuQö†ù/t³Xð\JOeœn"­ŒYÚ熌w¾öÅBñïL:v%ÏÕQEz–FPU¨O³µ¿’ ™—ôöL±7m¹’¬ëQ¦$À ­ÍQ¶O© äOÀAJHÕ0E|‚TϬ„ÿ)ا¹Z› 8H37rÇüã{N ñ ÛV¹(i1)V²ŸW’·næ\¢G¯žÑz/%uR-oVms·6W=ÒÜ^Oùó=J«¡FÝä>»Dçf°iJSîþ”x›º©ÐzÉóôWÒÑRv®k¡ƒ´_’F6•²SKý´åå¸ õŒ$š Ýª‹(?El¦ÖFÒ(œ YÜ£¥>Nõ  þMom'à>”9æWfŒmmÒª¾Ñça‚Tï±´¶ËfÌÔžT¤gÉbàÇ-õ£ß¶ŒøÕØ%Ufu¬g믕zk¤¿YœIùsýã+à^´[”y¶vh‰ÎÍa7ò¯ ÷¶q%sÌý´§è×tÏho®öžÖ{yW—dŒ}Ø–{šñÛ*èS¯ÝI»Åü¬ m%à • (=òv¦ví@_H*Ý× ¸ÕÈûBêÙãûYÆØš´7dî×|žLù…(Ž(Ò³»Úœ´Úqä¹=e4E-oÅmýµGÎ|Õ¢}‰9׫H«7vÙêÄœþ+yWÖÖ!äí׫ZŽ÷©™ãí§ýš”äŽphŸ1¶Õî ìéc[êÇ -wn åëÍÕÞ’¹S½¥‚þµ™€ƒ˜¤ÀLí-÷ʬœ=_+‘€XJJPçˆùÛ™cûïLq5mm/5Õ&”OJ­ ÌèÒ™ìßgŒm¶÷·ÞKUaÔê©K£Ð@<_彟i[¹aè©å{ñÒ‚ûúågÔ| 8¿ð>{޾´ïž·’¦žkÄ™€S?J®¨æ<_emBZ 2ʤ ºhwàû=r«ÙÍçRàë·×fîÊßÿ‚T -ÊÕ”Ÿ:ÝΤ•ÈK¸¬Ð~Jû iêb D{‰›‹[ÚnmÞH*Ôi3ÚŸòIËۯͿ“Fý6uòŽš.™øšK©ïç§P¾>îäŸ:<¨7‘^ØDYDZ|eQ` *Àœúሪnñ|•õBRAý(kQnU¶œŸ%ð)í½ÀÊ€ýÎæË·ÕÖ¨¾}H e”öÑ€}N÷IâG½غÀ~./°Ë€oF1i!°GKÛÕó7Ýu¤Ñ"Ñ^G»«Ëù치<5ÀÖ&­œ›Kt²RÜ6_°õ,!-¼PÚ7ˆOtžGü=Ç}éæ=½`Ný0¡Ó-÷ÁÏv) €£ƒþ™˜DV¯°ß‹£ö;—Ÿ“jÒåÐÆ úBÒè·ÒuØ.Ž)¼Ï™ÜAþºdƒZ‹2£"§Û¶-zzÑTmM»¿†TmœÚ´[wsW¶ÏõB*çýE ç!×=Â|^OL¶Cö9“'>áú>º;³E}ð!]óY“¸‚˜Nî7šÝ#€¢ƒ¶!ÇïŠõ€wíûã¤BÏ5Yü(Ó¶ÚHÀý1uú¾F=#œÃ3IoÇÛ9ý¦mÇ“gz[÷ki»½Bæã`%éA5ÚËI ò6,oi»5û©øS9¯©á<”˜B7ÒhëÒÎN ØïLn&¦ÖíTSžE…˜€Ó|îsÑ»È:peÔ0ú­§K‹1¼Ø4`¿·_ Øo?ŽÏ´Üß«—,Í1)— ÒJx‘Ðþ¹åÜrà”è &µ¹zõ(ŸÃé¾\Ã&Àó[Úö8Ëž•¤éˆMåLÀÕÔιXÓlÞJÌêð9ëàæðiâÏùkH‰8 pšÓO»ÉóÖ¾ÍI…jkñÚU‘Ó¤º9Ž%ÕªQ®…r{!°]æmöã4âGœMw8pmp O¢ÝQpÑm;1:€Im>XSÒæNàÐè €·´Ýq:—Såxùr?ò•æ¨á<´€Û‚4š3‘Aû͕Ŀ\xCp j‰ 8ÍÇ‘TÝd®}/Vbšƒ¢èÃëHÓ¤#|5h¿ý¸„µ&àŽŽ€vWYVpšËF¤šêžÅÀÎÑAŒ°¨:ýxõM…ôÀùšÀý×8¥qº 2l#×[úÝHu#œHÝž5Ü”H;÷pÑSùÚvUtÔ²ÒkIß'-”i7òß3ã¹ì™+·Š´øÆqÀÁ¤dÛÀCI‹å\ÞB<Ñ ¸¶~÷² àÿ÷=Ÿ®õK}¢ƒP^¹ŠSj4í€YJüHQ´ðÄè æp7Ò›ø¯D2ÍÞÀ½÷ÿÃÀ}÷ë ÛÈ•€‹L2Ÿ¸ï~ ¼'8†»“6OŽ£k®`Šš“Ì]s)p*°{` €G’kQs¿"%×–7·’>¿ç‘^V•NˆE¿œhkÿû;µ´íù\DžÚ·m9“TŸ.z@ÃsHŸGÀi.Öë6Ï_;^L*N_³×F0ƒÈ:’9¨]“Ñ9+€Ã€[2ı˜Tÿ-Êï÷ÝÓÈ3]¸©6¦¼º•Ô3ª(zDͨ©á%Ë#¢!7^t|ˆ´øÍáÀH ?;ù<'pßµ_롎QpO§½š¡ `Ns±þ[·yþò«uñ…éö$8«ÅêÀ³÷?h-™(× ø÷¯%=t¾’4"êE䩳°i†í « 7å5,èáMùpjyvyë5ªŽ"&àÔ%‹§îßk}îì„òq ªæâªnóüå÷X`Ûè útðëè &=Ø8pÿ]IÀ]üX6åÏnl7’FÜ@Ñwi*N"“¥·çî¿_'¯Ža3àÁÔ»`E­jHZÖ<íª«~Kª· 0†mIËWÆ õë‘À&û?=pßý:™4³`íà8žü48ebN³YÜ;:5²5©(í ÑŒš_˜îi¤ß¦^>6xÿ'ï¿_× ŽaðäÀýŸG|­~ü‚gôL‚Gcn €%ÑA`® ×’÷;DZð¿Á1HýxJðþk(å0Ÿ;H/qÇ£ƒ÷¯Œ¢oU¯mHÉu×à>ÑAŒ»ûð÷¯m+>-þ98†žÇî{8#pÿ]³idU”.Ü\Goï½)Ì:Ôqï{et#êwÑ»D­Ö!­äøjàà(Ò‹ƒóHߥבÄ'PÇÉ#÷½’öFñçö‹èHƒb¶ŠBy8N³±~ØhXJuQFÁKá• IDATì;ó]Àlj}Ø{ð^Ò*bQ¶v ÜÿÅÜuJ§æyCpNðþqñ×ʽ€õIS”5¿»G0©+ž]SCR|è*±”T§ò)“ÿÞï½ÐÍ­E¤©¶!6Ùy1ÝYL£–ÕÆ|):5WÃ[@ÕÉúa£Áó˜Ç"R®_+€¯l'œ¾m CtáØȺ$:WÔé~ÕðV|1pÿè :d›è&Àˆ:+:¼ï¸/p*ðnÒˆÀAž7MÀ•}­¿8xÿƒ8‘:’…Œ@y˜€Ól¢ßê+Ïcû“ê©õë'¤QW?n'œ¼&xÿÞW¦4Ö`ñ«ç^¼ÿAœÀ¤=£èZF'Õ(E5×í¢¨À°…ý'HïÕ¾‡ï¿K •,§Ž—&¾l&à4ßàpy ºøÂw&ÿYCn)ðˆÀýG'à. Þ—Ü“øÚŸ]JÀOZµ5š7åýÛ+:R’¡–äí¨9TK,Ò:À¦Á1D6w+ÝX„gÜ/xÿ]ºÖC³)vV‹BÍ™€ÓL;E¡,6Ä¢MmÃ`«x®Ž™ü÷_’n(£´ß5H7 ‘º4¥1Zô 9të­øJêx+~ßè:b1±/#zÎ!~‘žQuu|ço@°m‡ü9§Ÿ–±„øEÚº–€«a1¯%¤ÅÔq&à4“HÎ Žfl楤pýú [ánuŒ‚{itSi»ÿ]RÃÃXWÔ0’êªèTÃMùöøV¼%½”ŠöÛèFÜ_£ }&ÇÙ°#ßMÀ•±”økÆÕÁûT ×zp•à‘`N3—i‹_‹ èH]¶˜Á_øö´ÿ>:S,M,^°ß{ìsº®%t"E¿Y]I÷êÿÔpS¾˜áGœŒ“A¿ËÛr\t#®†ÜѶX¼ ¸2¢G¿ÜÀ€j¸ÖC÷ÕjÈœf2#¦NžOÉ‘¶Õp¡íª'[ø3ßößß'¾& À‹€u ﳆ…ë£èˆQ’S-#ÕÇê’3£˜TÃg­f[ψ‚t-86:ˆWCn£è=”Áï›zLÀ•qèè^î\긗÷Z?LÀi&£>nðªÉ¾ŽÑ¿àúùlÓ ‹/ü™t‘žê:àçY¢if}à……÷}£0ÜCW, ~ÚÔ²àý£–E>¢Ï]í&~:<À À ÑAŒ8p±^ÕàgGý~¼&àw'uÔ¨õZ?LÀi&£ž°ù4pâä¿_¼?0–îÍ`5Ì”l¼nÊ£?kµY |‘´N –ÿʨaÜêÑ´h `à9Àç€ËCÉóÀkvûÖ¥Žz˜]¼Þ×p­_Lªé¬3§éFu¤ÔJRý«¹$¯£Ž·6˜€Ì ‹/@šæ4—£©cJäàµö³1ñµ½™ïŸ ¸áÕpS¾ftY´õó£™â3¤D…ÚWÃ}Ü(%à^œAå©NÛ)À×€—fÜWW¯]Rõºyvet“¢ëõª!pšnT5Ÿþ8Ïß¹ø·±DÕÄjîjx©!†®¨á†®«÷$5“®áüEÚø8ð[ê[tè­Ô±2縨adÍjÑtT“2]SËè©.^ïk¸Öƒ×ûÎëâ/¿Ú³9£¹¼ñé¿HöÀ«½Q¦îW×õŠ âNàˆ>ÿîOKÜ~[^M»o²k¸A¨!†®¨áXÕ°Rå0j¸)×dóž¤ÄÛ…´ÿ6ŒãIõ±TN ¸®~—E«áܺZ®]LR×p­‡zΡ†äBSâè·_êT â—¤‚úä'ÔRÒÔÍî© ^Hø8àª>ÿî*à À»ÜG¶%-6ÑÖ´Ønjˆ¡+j8V]½'Y£5ÝúuÀ^¤Ñèg“ÊCÜJúÝØT~_`› ûqiºÿ¨½Ð«] Iœbè"GÀµ¯†k=tóz_õüœt^ùÕžQ«ÿv'ó/¼0›7O6ÈQ¬QL°æÖÆâ Ó}øêøþ=ˆöp5¼Ý¬aTWWÔpS^ÃïÌ0jx+^˃A[Ïœl]uð\êX cÜÔüªá;¡‹L,´¯†k=tóz_ËçúöèÔŒSP5Õ¨%h>ÆðõÜ®¤ŽQJ9Z‚5·H#*±Œ´ÚÞ .~0àÏ´åa¤‘$m¨áFÚ\ÿj¥SCRz5Ü”{C^ Òê?‹dLÕpí¥„xI5\‡F]-Ǹ‹×ûZ>׵ġ!uñ—_í¥MŽM? ¼ˆö¥Z‚5·a_8šáV\û4iúg "ýžçVCB íÜRà#¤non$ý>,Ÿòï½¥Ìþ»2×ÿ›n æ^PeuÒ"Fð.Ö$^^¬Oz¹0}dN ç«–âЃò_S½ørtc¬†:€~‡ãsiûj¸ÖC7¯÷5Œ®…zΡ†äz÷Ž"£ƒ€›nc%iA†_2xb¦F[ãjl3Y¼`ˆŸtúiÏqÀÀöCþ|NÿHz`ì·Ž]¿j¸AX<ÙÚJ,ÕÒ¶Ûôþ¬†óµ~tCªá^ê–èÄéÞããÑŒ¹>&à†Sùuµ$‘Ö‹`µL›õzßqNAUÏöÀ:ÑAdr<ðLÛú5pX¦mÕÀQp3{)99ˆË~ŠÑ*à³CþlnK®öÝ|jyô¼by‹ÛnÓLS&k¸)ïâ 9ÔqSî‹•X·’j¾™|‹WC§«×†h5œ»QWÃË6èæ ·~?—3ÜÌUÄœzFeúéíÀk2oóÍŒÎÃͨœçÜâg¾F³•¿H=IªW¿0oÓ¨¹lÖâ¶k¨ý5Œ™k¸¡[›:npUCÌ£rê¢ó}€#¢`B¼Ëjø.uµ$‡»øÂÍïeaN=£22ê¿€³2oóZà_3o3ʨœçœv2ÄÏ5­ñsùFj6µ%i`NדFúE3÷÷fº¿®x3[7:€!ÔpS~Mtch‚4’yàÁ±èojHâ\@GÕP¿oÔÕ’À17¯õ#ÀœzFadÔ%Àû[Úög€ßµ´í’Fá<çö ¯ñ÷àÏö]Ót¥ƒ2oo%pCæmcÓ·]Ë›äA¬bæ)(µÜ”oÀrF-ço\üØ›4}Yp,º«pWGÐQ5$8F]-׊ £B ×zp#ÀœzFadÔA´W˜riA†Fô4±”ÑXP"—5çñs¹V¸;‰z»”y›5Üè9î®n#Ü™®†sp·è†PÛü‹¢?ž< øml(šÅšÑ0Z ¸ÿ6î ì<xiôç™÷UCòtÔÝLåOºx­¯a„¾×ú`Nk;DÑÐqÀ·[ÞÇïÏ·¼¶­ lDEžÅàoá1†OdÜVS¹GÁ]‘y{Ãh3·œfu#Ì6j¯–Æ-¢B 7幄õ7%½¨|8ðƒØp4>¹Wt©?ç§’î·!þ¼°pT¦}™€+£†QT^ë‡satjΜv¦ÛþWá…Ù¼zT‡5 £sfõÏÿ%ïÍõ7¨§^ÌÓ­2n¯†¤@›7y«èÞƒÖlñ^\4ŠÙuñ¦…æ£ÂMÀ•QÃ(*¯õéá¾Z ™€t¿.Øs íë:à…öÕ–®Ÿï\v<ÄÏåš~Ús;i*G V^™q{fÜÖ°ÚÝ[Ã(¿AÌö`xuLKñ¦|p—ÓÍéÐ]p_Rî{¤{T¿èQ*À_‚cˆð]àŸ˜¹ÄA¿j¨±5LÀ 'úZi$ª:Μ Û#¢."%àJú"ð›ÂûÌ©Ëç;§‡ø™ëIb¹}š4µµK2m«†7u;¶¼ý®t˜-ÞUÔqS~÷è†]LÚ‘Yí{iúÝÁ8J§vÑ ¸Kh¯q펎iðókç Dsº0:Òl‹®Õ¤Ž^$j838e`NÐíQ¯n-¼ÏÞ‚ ]«ýÔÓåóËÚÀCüÜ‘´3Jè2àè¶;ŒMçfÚV £66hqû綸í6Ìï…¥‚˜C×ê‘.¢Ý•vûñ§àý‹ÅÀ»ã‰OºjvÑ£TÎÞ´÷5øÙèä鸨áåèZä-yRBôÂ7Ç  LÀ º;"êàûAû>…4j©‹vÆaþÿ¬?ÄÏåž~:UM‹1¼6ÓvþL³é(¹´9 ®k[gÍñÿN+Å캖€ÛŒ”„‹4j ¸¿¿"-|ô'Ò”›óI«÷Õ`_àÿˆO¼jfÑçe®ïØqp2ÃpeÔp­‡î]͎ڵ~l™€ÓúÀÖÑA a9iôHï ÕÞéšÕéÞE/·a¦ŸžC»SIJìÖ`w`¿ Û¹‰:êUìÔâ¶Þ|øé åª÷7ŒH#,ÿƒô{6›nî6&~šÇ ¢oÈ¡Žó–Ó瀇{{÷œl[ÓN €aì ü˜øÑVú{Ñ£TÆ=éº8Œu²F¡Ùü™:îSºö,ýÝ2j×ú±eN»Ò½9øÿNüê›H«¢vQWG=æp_àCü\›£ßzjwP¦íü1Óvšh3wðNRñé‡Û›V„û:±µýn^F¥õtÒKƒ¹jÖZ"¨>té¦<:wpFp ¥Ü@Z‘ôCÑLÚøݼ‡eџɃ÷_ƒŸùs&àʸ™tï­K×úÄ·ü6xÿÊÄœºXì\àÃÑALú2ðóè †ÐÅóžËˇø™UÀá¹™Á×k ì§O¶Ï°ßgØFS÷/¼¿ëI+Â=—ôY‹Ùø<àóôŸ<ƒ´*o´.}?åøŒ4ñKº[tÀ[iVg*§§ïe…òˆ¥r ŽRá Å;µœ^Žvi0ÀÝ€5÷'©4ƒF€ 8ué˯ç à¶è &M‚¨eË~ué7§un(³Bärà öÓE¤ÅFš:!Ã6šzq×»³‡SþmóYÀü™ÛIõ{¢Ý7:€Ü+xÿ5|¾"ü+pXt“ÞOû«-«‘ ¸éÞý`. % µ:Ö(.å×Ñ÷‹`Ñ×úSH#Þ5\J]]KÄ| 86:ˆiN>I|MºAt1ñšÃsî ëÀ£2Ç2›s í§/¦Yñó“H£ÖÎÐ6v!}V#ÜHú~(Y¿êÿüÜÞ9‚7åý×À+Iײ=ƒãX øoà±Áq(½h¹{àþkHjÔàà'À£üå׺Ô3`”ý":RyŒ»—FÒ‡{õÒȹ†4Š« íR}¥­ ü•øcÔo[Åxõÿ=ñǾk퟇:Òwu\ýxY†~4±4вT‡·Ágk·Ò„gwœ®"ÿ ¬[ö§×Þ<@¼÷VTóð„ânӉćÈûíf‰©T{\æþlÜŸ à] âß x>pp‘×ú©c¤¢2ªýæVíêBfð¦è t0u<Ìg·è zyt·€4º³‰¯å¤¡gï¿äò·4øÙóIKÞGÚúk6F>4|1pß5ºøht“ÞÁx.rm5â¾3~HÙï÷q6Ûb'K€Gï'½ ?x+©Þµf÷­èHç­f‘%LŽÀ©í#ÇÜøZD7ÞLZ]´Kn¥ 2lBZ¡jÔ=ˆñJ6¶å…ÀF ~þtàwyBÚý{îÿ6àöBûZÕðç¿”%Šfj^ms⮡? Úw « ùsŸgøÅGrÚŒæÓö»¬é¢=ÃÚTà?§‚ö;Žú]mx)i–‹€£éN±ÿÒ§ùýBS5_ë!¶$Æ¡ûVKLÀ¯{w£Ò¯ÓG1¤£HoDk×…QM9ú-µi>µª†Q;ÑÓP»2JâkÄ׳|jðþç²q‰†Ci渚ûÂap·PÏÃÊ뉽kÔÂ$ ÚïÙÀ[Úv—?mØ´‚ê ‘®%'G[æªã.!MÝtobP™ËZÀƒö}ñ/®Õ‚¾Ø£öúoÀ«‰lâµÔ¿pĨ'à6 -o¯<^M³BÇGЬ6Yï¿+u»®&¾¦Ñ®¤—E5Šš2sðé·_˹& œ’üF ÿsо—í·'*aõ™ü$í²ïúç1·~G¿ÍæYÀŸ€}3Ä2J‹€z_¸=„T§.Â!AûUËLÀ¯Ú/‡?¢¡sÿŠbµ'b›z!õ¯®Ô%[Ïmðó7‘¦‰EÚØz#Ñ+Ž â“Ñ?bq& 'íûpÚ]Ì£†þ&;g¿ÉHCo æú}#pK€ØïM´;]?ú\B]#àr¬f¿ ðÌ Û%߮ޡÆk=À“ƒö{%éz¯dn|Õœx¹‰T8u¼¸ :ˆ9Ôžˆmb!iÄV­V×ÏÐþJ*‚?µ]ãLÞ@³©w%~dëëƒ÷_BŽQ Ç“F Dz!qS=g³)]Úð‘–÷1 ü_ÈEs›S!úFìÿ‘Ĭtþ!Ú-+}.¡Ž M1ÎõìÒÖ”á®ZAü ·=©ï™d!q#ó>Iñ®TÓ°b•UÛ—ÜTï."“处ŷ£™Å}H˜è¬mxpÏÌÛ¼¸Š”(»´<øÔ6õÏo'ݘ¯œåŸƒXJJ„ÔˆØx ð¿CþüÅÀ7€çe‹hpû;çÆÐ¶\S&>Bì‚ ;“^¿ Œaº¨7õßÎly5Ÿ°Ï¿ÒþÊ»ÑçbÎçL^ši;w0Ú‹Ø ëSÀÛ‰5òàuûŸnob^¶ÝH|BTRfkw’Þ¦×ÖNe4Ãß#þØÎÖr'©jñ†;'ï ¼yÍÕJÕ^H*%P²o+(÷¢îÙ…ú4W»¬õ^Îm1p6ùúóà!㇤g‚È~þ…:fB½œ˜þG­ž-©e&öËu¾vt{]=¤{¾vD{]qORQ<QÃágj+iþõú úñð†}ÔUcŸ«=)sÜ*÷lí`óÌ}Ô}Hõ2K÷½äK’臯 ò¸y{}鵋(3µjiPÿ¦·'´ÝÑIÉo/Ò³ä-ö£ß¶Š´Êl”œ£ßnføäθ$à֢܋ÂÙÚ‹ZïåܧQ¾ß'Sfd­¤?"ö‹µŸöøÖz_Ö® Ÿ *ÕNo­÷1>ÍðÇ¢–Q6³yñ¿/S[Ó„å"ÒÃvdJ×,5¥áÅ-Ä>ìÈÒ\íc-ôiGQ¾ÏgRnú"ÀgZêÇ íüL}Ù†ô¢ º?½VbUîýû7µ•(¤¾1åG…ÿ’²Èk©ƒ¶õ'g²„´pS®~üªA,Ñ ¸ÃÄ>¨§´Ô‡~ÛyÄ&}#FžÞ ìU¢s’bÔ<²×Î%öË7‡Àω?–óµ;¨«èiÛV›öX¼µ|ÈYH]ÓPWÒüÆü>4;g9Ú[ö¡_«S.!ÐÆ(M‰}¹ƒTÃ4”ý¶Š¼µüúñ³ q7m7gìÏO+èO¯]Fû+³¾®‚~N’õm;¬pŸ®¦|‰”c3Ä£½ íŽÎâ ÄØOûxƒX¢pŸiû0¾™1öaÚûÚïâŒgôc-¿›’*·±_¨ƒ´a ¥ÖâEÄÃ~Ûî-ƒÒ¾D³ãð¯åCX-7ä½–ãA+zªØ `Ï ý˜Ï6ûôÑ–ú½ Ã)¿"ñ†_U¹I+=âop]¦Ø›¶\E¨k»·½ÐÔaôq‚ôµM/(ÜŸÛ}[îÓL"V\ž©ýOÛÁÖä¯Ûdjct®t‚f b¯·ólòÊ!ãmÒÎÂ…¤‘¶/±AÚ­À=Ú9 ­Û¸’øcØo{^;‡¡¨Ýi>ºèSÅ£Üÿÿû2µÝIóäÕBâ§ÆŸOáÕ¦‡ìÏ‘-öãkû1S;¸Å¾ÍäÅ™â¤ý‘ò#“wÎ{Ž–«ˆÿz¤úÑýéµÛ€2õm&gUÐÇ^Û¥¥>îF%Y²/¯h©/sÙ¶A¼¹ÛE”] j!p\ ýh’ЉNÀE”H‰^û÷” µåW]ܯDç$Åy5±_¦ƒ¶¶sZW[’d¾övC1¹¦ûž\8îaü7ñ¿/ÓÛ‰¤æ&6#~zü/h÷f¯ä¢¶ØõH+•E§UÀÓ[ìßTw§ü(€[ˆ©¹TËôÅ ò®0žÞ~Nóï˙ܣ‚¾Mm·ÐÇ­(_ þúѨUgk{·ÛÝ»ø·â_N³ÑÓÑ ¸÷7ˆ½‰O cí«”Iþ.¾п7蛤`M ÔGµÜ«ùµí~¤QAÑÇmöƒVŽD9¯ ÏqXEý£.ÿ—øß—™Ú+3ôí!Ä׃û!í%á¾S¸/Û´ÔHßsË ÷gj»…öëÁ­ œT¸_«€g¶Ü¯™, º‹üìMmGeìÛã*èÏôÖF½Ñ÷VЯ©í*òÖ¼ÛøSá>|…v’¥ýˆ^ hzûn»Ýýÿ^F;õ6OlWtîÝ ãÖÒH´È¾—X üà€~}²#K%ù±_¢Ã´‹èÎÜø…Àoˆ?fÃã®Ú•¼+K~¢løÙ’Ø=¾\Å IDAT¤Ç\í:Òh¡¦žCùb÷ÓÛOHÓÈsÚ„4Õ d?È܇éžIì*“×û´Ô·E¤i¼¥ûôî–ú3Ÿ§c‰¶X?Sß—WЧ©íà ™úiñµôkzËUóînÀi…c?†òõ&{jYÍvj[<¼ÍN“]hëúÿÙ†±E'àÞÖ0þ&¶.%®R¿{m&áþ‘ò÷'’^òIq €ë‰½€ Ûþ­…ãц‰?VÃ^Ü6háx´mò.Q?Az0zhÉNôi!é tôïÊ\ídòÜPD/Ê0œCª5”ˇúpXÆøgóÖ€~Mm·OÎܧÕoô%êmøöÄ?\ÎÔÎØÇTП™~wŸ‘¡o«SïÈè•Àöïä¿ÎÏ×~DÜÃñÖÔ—0îµË'ãËmcÚŸ*þæ†1FG¾¾aüMíAÞ—ÝôO‘^ŽåôBÒ}É~\Lz¡.i ”\/w[ì”ÿdµñè&­­‘$mÙ¸‚vŽÅõÀ#Êue^›Pò­×Ž Ï¨ÿª /+H«17}{iE¯Òñ_Gz8o[t˜U¤óõ2ôe+bV<ýeÎÕt¤Þ‡ýå¤ié9Ü¿‚þÌÖgø ¶~ZAæj·“êú༄4"´tY‚oQ~”ž‡;Ò¨ŸöWò½¤\ 8ˆ4]¹í¸ŸÖ0Öèûûˆ…@¦{,ñeBN$ÏËÑÕH/yJ|» Ø1Cü’:âñÄ~i6mÇå?$Y}ŽøcÔ¤ÕpqïÇÀ—iêÛJà3À½ÊtkFÛ’êúÜ@»}ÍÝNžJ󇘈Qc3µKIÃL‰{åWì›Ú7D̃Z@J€EŸ§K€1\ ¿ÅÀK‰%þ#Ê>ðo<—vVÌÝnÞlž¡ßçUПÙÚJàד}}"pÒïäL–û‘¦ÔÝZAìý¶³HÉ–ùê¬nDísA@Œ‡’„Í|6ž Û0öÒíG¤’ƒ–kX—”Èù4pMÁx›¬€ ñ ¸£esx<ñI¸Ûÿdø²'{S×î*bX’èmÄ~aæh¥V¾Ô^ÄÖBÊÑj®}ð`à”?.«HÅÉ›N£Ä#H7ѵК¶[I£‰vmp,>XA?zífÒÔÄ—nægJš¬AmóÒutÌ_èï07VKn‚t“û¤ÑSó%¶¶'Õº0(Öc)3ÕíäFÎ-Ü¿\m%©ÿ¤Àa|¨‚~ Òöšûs€o“®£is·+Hçó3À'I«þ˜{©U¤ÑÎ¥¦€ÿ;é³¹št®¶8Ÿ4 úpÒùüÀ”ö)Rbó‡¤$lÔ"eM‰NÀíÛ0þœjHÂM¦ŽIzá»é<1¯G1Â}‚4òmé<1JA‡ÿeÙ´]BÞUµrXüŽøcÓ´ûÀdößÄŸ¯´ßÅÿ/zJ_îÖtÅ¿×PçÊ«HÓûÀ4ô*âIŽv]î“YéЦ·SÛïâÿ×Å•tçjßÎpLžHìTήµ¤QÏ¥§SAš: £tÚlï§Üh›GêS©vùÇaå‹ù7i½ò‹€eÄ3ŠíTÒtß’’)v[ÿíW}™¹E'à6ÉЇÜv!~Œ>¿5·_“‘4†SÇpámÃ,Îm3êzÔ´Õº*ÏÄฃ2£U·ô¯Ívn¦c³i Ktjo§‘V,‹ôpÚ[$¥Ëí&à™ Žë0Þ”)öšÚÚC‹U{¿m‹É˜w¨ –Qk+I£a#[•—¶]j_îëÌÌ-¢>h¯ÝIšmS£Í©!˜¨öIâtQÅjý0+¿/ÕIÅ[K˜Ë‡¼mÍj­O° ñç{1iʶmNZl”lÃìÅq*°'©VþÞm¤‘U{’êFú) øÓà8jr:©®×Q…÷;_Ñû.ÚvÈŸûfÖ(Ú³Š4âFóüE:TOë-¤º¥y>Ë;/Ã6"V©î¹†ôP£+I£¬ßK½1–v p)Ùñ£Ê™€MŠ ×èa¤U£"íüSp ¹Õš€Û9:€IÛØÇööQÚjÀÖ™¶u3鯿¤7ÒJŽ"%ªßIJÄÕà à€e¼oBW‡ÎØÿ(~§ û}òk†ŸÂZÒU¤Q/0šç/ÂuÀkI/~‡ç³¼Ÿù3 fsõü%ÔJàݤëýEÁ±Dû5p?|Q¬9˜€µ&VšøíÝy¸$Uyøñï¬0Ì ›*Œ Š( .¸"Df“¨¨q‰QƒÆ-jbˆ‰Qã¾Å}‰Æ5*.1î5 H”MdUff¹¿?ÞÛ¿[·oÕ©S½UwßïçyÎ3wî­Suª»ªºë­÷œó`·–¶½ŒH-n;+kÐÆ5P{Ƕ0ký¶ÑkfǸ»Õ€×÷!bPâIÉh–Ÿƒ!GL*0n¶¯"¾~¯å¶´á"Ûæ¹Ä¬ÀmEæî¨­ë±Þ3&å°ÓøþÒuĬÈwÞÂ\`³-¾Ÿ£weŸõ—Ðnî’·ÝDgÖû7³ø²á6ÏŽ~Ùr[4æ À-ãXéÇ-‰iãÛðà°–¶=L㨽eÛ ˜U7Íù Œã@»ƒ0Œ®ÚWãi=8wëggîÁøÏ` ñþ ü‘Ý3í6™‡Ñn¶ LçÐý<|ûôÀZ1<~žÆ÷o.#&¢Ùx)ð»v›óÿù~Ž^¿Ÿ9mß Ìé^ÝœLô:£å¶ŒÂ ñ øbæÕÅxT À-ãXé×_3ú@Ø-SF¼ÍQ9˜vfM¬s‹¶0k_œ÷Á6Ú0Ìý:˜ á©Ào†¸qðßÄ÷>O|ù›;€w·'ž\ßns†bð.bàüWOÅÛ6Mã”vìÚGÝï0þ]ºŠ¦õ3a6Nd“¿†ñ»ÎLãù8îúÍ€Ûy ­èÝy-o¿g÷þ˜é}@ú}"ÐøX3Π p‹ÃjFÓu® Ë·2Ú® ¯§½®¯Ã¶3p‡¶Qb\p£¸šÖ›­aßtlÞK>žübÈÛ¥ ÀÛÃc€oµÚšþݼ’ŒüåD&ã¤ÛD KpGà/éÿ†oPÖÐîàáÃÒë,¨AÒÏ ª!CrNág6is=†ÈR?ø ñ™0n–Ó_ðX½é7®×.ïƒ2‰8ˆ„Ÿ%>øa»Í˜¯OÜÂIÒ÷¦ý©˜‡]ž4°W+íH"‹£íýfùÓ½Zƒs>í¿.3À'†½£À{ZÜ¿a–¿ä‹”a ðà‹ÀÖ>ÛÞFÙAÌ z<°jÀ¯Í¸Ù 8øí¿îMËEDqÝÓ{q Ú†Q^ÕçërÌìCª„}o Ú3Ne31ÖÔ €»0YÖÒþë·ØÊ YïLÚa-ïÃ>؇qqàãÄyÜö±Ñô8ú1ž­Ô—åm7@#1ã¿u{-Ñ m÷±œéœx¡Û!Œß9ã’Á1Šv´=ÖȰŒz¿f€/Í–}ˆ§¯O$ÆLW[€ï_&Š¿°ÝæŒÌâ‹í€;AÇ']UÇÑF"ƒêýD6â8ù2­×“~¿¿~ƒ˜Ey³Ë.þ¯ðÿi}sì ²™DdÏüøñPe-æ÷²-ƒs´ÍëĵŒOFõ œ>[ÖF|/;’ñì•·ƒÃõƒÄLóÛmަ…¸ÅaZÇ+Úx1&ܰ<ÅñZŽcÀv§¶0Ë\ïÚܯ+7Ζ[ÇΖ‡ mú?"›ã˳ÿâiý$;ŸÈ({9‘ÝòðÙr$í^~MdS~˜ôbK‹miÂëI¹­Dר¿@[­{ÒŽi};fˆ .,”_Ï–Ÿ2]7½Óþ^Ž£A¯Ú ÀMj÷Ó:ˆï!2ÈF|Ö?Œv‡¹žk÷ ÄÜq/TÈÜâ°‚F³ë½øÉÖ}kÚ›quÔÆñxùÙl¹ŽøÐî”ëˆñ|:6±ðÆøzbl»²àÙ~!Þeö÷kˆ'tk å¢>ö!×%DÔ¢íÛ‰' Ûˆ‘›ˆÔýHglž]¶ÎµÄÄuãÒ¬£:ûsWâõÝ•è.¹sÉïÆå îo€wÏ–eD碫þ݉1¼Ö a»;ˆãç¬Ùræì¿ÃÌÚtçÌ–×ÇÔ݈÷êˆÙŸïÀp‚rWƒFŸItÿ;øí¶3 Û‰ãì:âì\S6×”ÎuäFàfʯ¡¾žtêÖݤ®$=vÛ®Ì]‹–Áñå…;×ë5D–X¿>I~n;ñÚm ^³îתóºî ^kfÿ¶)±Îâ²Eݸ_2÷9×y;:Ÿ EeïUN½¢ D`¬Nç8ªÓy­:¯ã ³íÜØ`Ó`†8aîµ(ê|¾uŽ­ŽÜzuïu±m¹ŸGÅãi7“½Ô9ï»-¡| ¶²í–}§D«ÍÙéÏlqÛ£r5ðï³e ñ=ì^Ägý=ƒNtq}=›ËíûÄgÿ8Ž©)2í]é®d|±¶÷að]‚>AÌr³l'¾¥n¤i¶ñð¶Dví-ˆ'´{3?0°Œ8_:ˆ D0ãZ"@p1P½„Èð¸y”;±,ö'&ÞXO¼W{7K{ßq:_Ú;ïUçæu;Ñ5éj"¸v5p‘÷ûQí€Zµø*qÞ^Jœ³—çjç!ÏF"Xäç¡´x½ø»–¶}ñ°`±Û‹ø¬?ø|¿%sŸ÷;wV2÷Yób_A|Î_A|7ûåì¿9$©‘½iàÊQ—§ ä•›ó1اQ—q'K’$I…ÓÞ÷ñ}G°’Fh<Ô`ÚvZð/ .]|%ð–­k’Œã8p’$IÒ(­oi»ç3˜îö’ƈ¸é7Žãy ÛžDºø ¼{`±YŒÇ$I’Tt@KÛíRÒ07ýk&ÓÓ‰AÕû±?ð’´e€“$IÒb¶+1[¾ÓÒv% ‘¸é·X)K·Q>£R®7‘ž±mš-ÖÀ­$I’p8íLZ8|½…íJ2pÓm pç¶Ñ¢{Oí±î1À °-“f?¢+¯$I’´ݳ¥íþ€˜‘YÒ”17Ýn ¬m»-;•˜ ¶‰€7¡-“Æ,8I’$-V‡·´Ýϵ´]ICfnº-Öî§E»A¸&^Üqm™4?’$IZ¬îßÂ6g€O¶°]I#`nº@ Oìí±-“Ä 8I’$-FwÖ·°Ý¯¿ja»’F`yÛ ÐP@ K€ww¶Õ,ûF`ç¡·h2ÀíßÞÄyxâ‹ÜÊv›#I’´h\¼¨ÇºdCxoáçg÷/oo~Úv#4 ÀM7(sž ¼%±ÌMs&Â!Dðr¦í†L€Ý‰ Û]ˆ€ÛÁÄë×tüAI’$ Ƨú¨{ìÀZ‘ïbà3…ÿÿ!ðÈÚ¡|3À?·ÝMpÓk%p`Û3¯">ˆ//ùÛ*à_GÛœ±·¸ pIÛ 3{³buÊ=€[·Ú"I’$uûRõöŽdC2½ØZøÿšÚ f>\Øv#49 ÀM¯ƒ€m7bÌìJ<¡8¡äo/#fÕ|‡°¸pëXl[ßfƒ$I’”å¿z¬÷xFu󻟂¸q÷àÙm7B“ÅÜô²ûi¹'ï¾UøÝ€ç·ÒšñwðŶ1BûGŽ"º“:Y$IÒd¹ò^/9N`;r½ØÜõ»}Zh‡ò|†H긱í†h²€›^àÊ-Þ Î\Š÷›qâ…*Ó>‘Ç­€1t;¨ÝæH’$iÎê±Þ±Ä}Â(K$-ÅÜ8ºx)ð–Û¡ enzM{à¤wžCŒùöXÚduRLc ÷.ÄdÜÊJ’$izœÙc½—´õfˆnŒÛº~¿'4N6“ù½¸¾å¶h‚€›^Ó8¤S€Ó€×¶ÜŽqw1¡ÇÍm7¤;é>Š˜Ijßv›#I’¤!;»‡:î3è†Ôø7à›%¿?`ÄíP¹‹€wï®j·)šà¦ÓnÄ앪¶8أ톌¹Îlºç´ÝÜ 8 8ؽå¶H’$itþ¯áòk· £! —/ªø›½™Ú³ø2ðÎÙw´ÛMpÓ鮨­.‡Á·ø4°SÛ ‘$IÒXø}ærûŸV±-Ý~<‰˜ý´Ê}m›¦Ñ6àÀYĸßßÎkµEক8 Òm‰±Õ6¶ÝoÇà›$I’æäàn |ƒÑŽûökàÑÀ¦šå2‚¶L“mÄxÕg?žý÷§Àæ6%•17}öÃ1°4XK€ƒ´Ý7·Ý€Ed;Õ÷kpÝÖ=lÒºS¶Pÿ»M72ÞçÏuŒï¬cÃ<^‡ézâ<Öàm$n5xã~­šd›‰ç§Ñ¥5¿5ðMàö#hKÇ¥D`íÊŒe?IdlM²MÄw¡aÛœÏôËš2à¦Ï8wÔäº+ã€;¸åÖ{3ñ¥¶2¼A^·1ž™Š’$Iãàà‹ÀmF¸Í+‡f.ÿ³!¶ER‹ ÀM»ŸjÆ5°{Ùl‘$I’RFd—í:Âmþx8pÁ·)iLr¶ƸJ4Ù<®$I’4‰–'_`´Á·*|“¤)u1æ‘Å2Èr5’$IÒd¹ðFÿÝù8“©$MµeÄ€—mk,ÓYn$I’49¾Ëh¿/o^@dÝIÒ>„õK’ÆÜù´?P¿ezË ´—$IÒdù1ƒýN|&pߑàÍôôØ™˜„A–ÕÀú¶!I’$5ðÑ­ç"àxà^À÷´NI‹ˆ¸éq0°¬íFhêÙ U’$I“äýÀ¦>ê_¼€ènúQ" N’37= Œh<Î$I’4I~¼©‡zWÿ¼Ø2ÈFIZ| ÀM'`Ð(xœI’$iÒ¼¸4sÙ_'§×©M’¤ õ_´?H¿eúË9H’$I“çAÀVª¿çžü)ë#Iªqíg,Ó_¶;!I’$Mž“€Ì}·½xpD›’$MŽ=h?0cY<å0$I’¤ÉôdàÇÀ³u-·E’4aŽ¢ý Œeñ”ã‘$I’$IÙœ„a:80¾FÉ™P%I’$IjÀÜt0×Ü%À sÛnÈ2'I’$I’ÿ¡ýn‰“R6ÿ ¬™}í–O®ƒ¶MJ¹I’$I’¤Ed p-íe&¡œ¬¯x÷ÞDzjrK”8`­$I’$IZDö§ý€Ì¸—Gf¾ž_ƒ6{¹æë)I’$IÒ¢çp“Ïñ¸ªýx.p/à»™uÎŽ üzH횎;(I’$IR&p“Ï@ÈB[7·'º•nïa§w&x×®iSÃÀ¯$I’$I™–·ÝõÍ@È|_#‚fç `]7¼Oÿ<ƒÖwÒð-nGŒ¹¸+1‰ÌuÀ¥L΃ÄÃÝ€µÀfb®®n¡=ˈ±@÷˜mS§=¿6´Ðža¸5°qÜl#öïrâ5¤[ûÎng†8&¯.ðv†i`obvûpqŽiúíI\."ÞÿÅl5p âø¿±…í¯!>ï̶ֻábœëˈkÌb° XI|6IšBKÚn€úv6pXÛ¿þøâ·qO" w¿!ncR\KÜÀJ>ÂÜöë™Ë:]FŽ ¾T¿xÕ¶¹œ¸A^Cd¼î ‚ ;_à!n®v"ožÁÜÜMÀ–®m¬!‚G»ÿ¼¤¤=/^Dí·»ê-VÇÙµ)»O&^×»ÏÖë6\œ ü7qÝ»"±Îs€Û7êw›–0r•5À[€ç×´·Ì}€?Ü…¸™(sð3à;Ä>ü„áÜdí <xpæfÁîv11fèW€/‘œN|÷Žˆã²Jñø,*ž/œmCŽÀÇÍý½*–»†x¿>»îŸg®¿c)ðPà/ˆqU÷«XnðSà›Ä˜ª?L¬ó¹Àë™þÞHœKE«™;ŽVçün ÚÞ±øSâõ:œ¹×»ÛuÄkõ%à?ÿk¸“S‰s®ûú´–¸†­^¼,s÷¾ÅÜõ¥ø:ÏßµÄùþ›†møàcÔ_ÏW¯þ±‡mt[Fœ{»‘w=_ÅÂ㣩=ˆ÷t÷Ùÿÿ!ð…>×yð?Äù± Þ*`gb_¾C^Àoñšt®×[ˆÏ«Ž]g—YEœÿŸì±í_!®×ožÓãzšXC¼îÇד¨¾/½øñyñ âaûUÛ¸8V¯-ù[ñý)³ ñ~u[9»N€ÛP~žO<é|¯ØÎ‡fë˜ÛßÝYhÛl ³ˆsÿ?™û—$µ`9q!n{@þ6˵À‹©¾Á´%ÄñÅCܧI)U7_Z\Ž!ÿ˜¹ŒÁœ«j°Í²L±W6¨_,/­hÏ_eÖ?*±OK‰›ž=´k ð^âF¬Ì÷{Üß&7×Kˆ×Y=nk¸ø{"³j–O¡·™Â·ÿNy  "ð9Ìëë“3÷ñ(à‚·ñM";1Ç݈àd/Û9‹|–9©Çuþ6³Ý‡'n„›nk;ð9b<Ù\'6X÷C3×yHƒ6Wë<¤Á6.§<@ÑÔ± ¶9¨Ì¬çu­÷XçÈßÜ +ä&Óc»îZÏ ”„e?âaöò_¯²óæ{Äõc5Õ®ìc9¥*âGCÚÞï‰~Ňƒ’¤êþÐ\Le;ð!"]¾ «Sˆ š¶_‹¶J¯_ö4]šÎÄ|ü¶ù©Û+˾9¡a›;åÏ+ÚóØÌú·©¨¿ øDE›ˆÉaÎ#‚B©õWev½¯Çý}lÅúºÝ™˜è¦j=€AŒÓˆŒ¼Ôv·™JýX¼;±þ €s‰LšT[ö¯Xÿ;jêõ[þ&cŸNdpt×ÝN3N û}M¼feõKd»Õ9«öç~5õªÊW2Ú @}‘ET¶žÄët:‘É’Úæv";¨ê<+ºwƒ}¹‚¼ ójÊßïîÒO×î[5h÷ ùâ”7ØÞYØÞâzÚ½îÃû\ï òŠo%²¾rü4söØî7—¬ëo{\WʲÙõ¦ŠWïñW‰Ï”+ËvJªçË÷2ê÷S\±Ý4\Ï5À¯ˆ‡”uŸI3ÄÃ}û-I’?g¸,ãZ¾Åøt»½ lû5i£¼`¯Ÿ¦Ã®D&NÎBª;ZŽ=©ˇˆÀPYÆÝRâÆÛ3ÖsÑ óv‰6-%®I­XÇ¿]ª¼©¤ÎD»8îäž³ëªjkUÖŠÙö} c7}UÁÂn¡:Èóc¢+jÙ{p'âÆ©ª9AŽ”SJÖyÉl{Š™‚»]£«2£ª2Ä–I9ÇО]u×Çæ‰T._]³,ióÀ³™Ÿµ·’8W½GO¨ÙÎ=YTØFd*»,/#^ÛË+¶óâÄ6öžF^FÌG‰lºœLÚý¨Îȼ€øÕ½žƒ‰Àm*ØýSòνˆlݳ3öë«ä1»ít×.–ÍDÆçºÊÚyÖ¬ØFwé7 ¶;y2?B¼7ƒÈ þƒŠm|xë^<€è*X·O—²ðÚPf5Ñýøsë9•Þ{$¬¦ü¼û ƒíY²'4¯z->LìcYÔ»ÿ‘¨ûîÄvw&‚ü§%êwÞ‹nëˆa N&º¿–Õ«z(ñÙñºšíÎ]o‹V×Ò3kêýœÞºáK’úÐk7ªI-—O[Çqì‘ÿ”rZÊñÂiªÜ‘¼c§ŸqŸ“±þ’øBͺþ¡AÛ–Šõ?QSçÞì_OŒSåÕmÍy"þ?u;åYëèøsªWo¥þn)å7V×4hC™õ,¼©ßBýª¼°¤3D ¬NÝÍ]ÝMö Jê¼?±üJÊ»>.QçhÊß«u–P L=|9˜£ª»Îß%êtœPR¯XÎ&ÿ¼ÞX¿l=_¥>Hõ`àw‰¶\Jú-ZGyÆUwiÒ-qv¯{eƒú9n_ÒÆ²’›ÉU&§ûþY ö{_U¶ñÍDà ,>S±b9ü}[ÊÂÏ—¯öÙΧ&Úö¤>×ݱ'Õß7ãÀåxvÅ:^žQw1–Zê|NYI óÐ]· IDATï¹Ûþzb»3Tw¡]IŒÇ˜ªûÖŒíK’è³ä}9šôr#‘ÍP6ù8YJ¯¢ý×le]A4}꺼ÍP”JÉcì ÖWÐ{`ÃöÐUÿÏj–/Ë ú÷š:;QÞ5'' ð²’zÅ’ REu&âçÈ¿©Ü…]’Îά[åU%mª›Ð`1({w½C3¶WDÈÉré¾¹ûQbÙ?,ÙÆ5ÔgP•ÝÌ=-±üá%Ëo£~|±×–Ô{EMˆrêu|]Æ: ²ª²·~I~ÖÈᤳò~J~¦æß&ÖÓ)[‰ì©\oíªDƒº¹rÆOütëÿaÆúßÜÇú»íKº‹ß?p[G'¶S,9ÝÍ;ŽïªÛïd ©,«÷¹nˆÏªT¬ðð†ë+ëÚù”̺ïªhÇ y³/Îèª÷žŒzuŸ¹©1ìv~¨»Á™*iDrÒÝ5¾i»C6CÌèt0sã­³D··;¯¡ÿYºÆÝÁTú®Å+gü¡?¦zl­”C‰.!uš Ð~YŸïö³®ÿŸ_³ü±%¿»¼¦Î¢+^·œ³/®ù{Îk·q­+ËpÛÀÜô9®eá ü e 6ðˆ’ßÕ½¦Û–ü>ç5½(c™:/cþÌ©©ëkÙþ]Iý¬Šï/ù]jÿÊŽÍ)ŸØ¤Ÿít\Iús>÷¼þg¢ÛZ™§1†Í”ŸPJ}Wá&–AÒœ€-,œÅö‚¶¥#çzþz³ñ.äMlÑt”“H`ÿtúïþÞÔ©äOðÑýž7·è^TOŽ€~Pë‡ø.\~ ð_=¬¯[îÄ5ÜV·m,œˆ)ç>,'¸We1y•eÄ÷)IÄÜäZMÿƒT³™'ÇQÃ8n®%žvJ}ÖÅ$Û™üô´xTÍY´œf];NÌ\®É auý›¬ ìRÝ)WQ~? c;ß-ù]Τ4uÁ­œý}5ÕÔ%®M|§‡6¤Tò»a¾¦ƒ˜™ñ àk…ÿïBõõµlÿnC}Öá÷‰›È¢Ôþ•mgWêƒi¿`aà&w¤Ôk™óî>À3+þößÀ·3ÛÑñI¢›z•gÑlvÔ:·&†wÈÉ -^k¶Ðü¼Ë‘s=_FtlêÄÌå5ãã2ÒUˆcûÄm/×J¢+~Nff÷çË•}l÷/3–i’×í¾À_Wüí*zË6ü9 Z¹Ÿƒ¸N“¯ã.ÔßK÷»Ýÿ®ùûÁ}®_Òˆ€›\9ýIt 1¦ÂÄXE“ì|b ì‡7$ÓhÚ³0Õ\N¦ ÄPªëE·äÏ š›A MÿÞ­;H°%±ì^¿8õã¹Îü,³›È»ißÞçß×ãUÕ}gFºÓõÿ~pk(®àHÒi@t•*f.o¥>« ê_³\§³ö}’Èԩʾܻäwk©Ï„ØÈÂã³l;P?óå q|æn§(u¾å¼Oõw£÷f¶¡[jüº¥5ïÅ£ÈSáç~ƒÖUr¯ç'Ñ,sl9ðÄÌe›\ÏSÁ܃ƒ‰åžËè³ûoGz2ŽM]ÿïõ}_Çܘ‘©×âäKÐퟨþLz{ÍvSÎìúîk0ˆët§gÎw‰ŒéœY€ûÝn]–cîà IRŸN"ol‰I)73Ö-3©V3)]Gû¯õ Ë)|4ùÖ1ÿøø éã§*S¥Ìc õ6Q=Ûâ åÝ3«<´¦9­ìªŸº¦Ý6±Ý/Q?‰Á“‰›¨˜eã‡K݃Ô,¬ßÌlC·â`·^í–hßw©ú<xùc…= ±Í@è¨ÐÿRÒ³íB+O$ÕÍœøÕŠí\K}Wð{Çç£3ÚT”:¯ŸQS÷ÎD®¬îMô÷ýâ;‰víîPS¿8Üw‰¬˜Ô1³…ú̺û–¿ªéeXÛÕ¦ºëy“,¸G1ÿ½ùmb½ïW€¹Y—·S?nã ºõ]X߯‰‡²©mÎPŸ•¶”ùÇxÝqWåäÂ:NfáLÇÅÒËC•²ñ#‹åŽ=¶¢Kæ ñ€ätò„ÏJ´§Ÿn¢uþ(±ÝòDnMÔïõÁ‚$©¡7PÿA>)å«äÍ47 ö%‚ížõpR˧ûòhÂ݉ùÇG݃‚_?Xÿê}ˆ…³ÁË·´¹Í\*X4CÌž6è–û À-!†¨ªÛdÇ¢•DÖóäuyKYFõä3Ä Úô¹nýàVY9u³ru²ËÊ¥Àý›5¿ÒÇÛùù³6ÑO.5ØyjR‹u›ÕÔ/àþ¹qSåW¤ƒÀ÷.,{Eó]ªu‡®öÔ]ÏÏ'¿gƧ õþôL‘e]ÛZÏÜ,Àß&λîÉ_½Í£ ëû9p ggî.›©Ÿø¥8›q]Vo™%À¹³õo"ޱNp²¬l¢:[»Ê?'ÖW7.j=ˆìÜå ëõ€[CqpLÆz÷&®ï"=9B]fϸ¸Žôÿ? ºg¾Œü ·a:”ôäÝ„纙˜ ñv±jj;éÏ“{í|%q#×–ÕÄ ÿ‰ f“Œ›ÿMüíÖDúýô?Fgêý܃˜íö3D`a<2ñ·înÎM¥Æƒò‰1R>0[RnGëÚR¼žßL´7<¹#åwtÛ“ùÁÛQ\ÏŸÎÜ=ÏLj@×Ë?€ÁÏ*ûSê»ïL¾› ÏÐÔQÌ=ôþ2ñ9”z˜ºŠ¼ñâŠRçâO®«Ûï‰1&›Ñ‹Cˆc~#‘ÅØF²@ÝqxÆHZ!i` ÀM®I|,–I RÎbr'™(º=ýg¬hz³Å.'‚!o¬©srÆzgn îÿ%2€R3ãMJê¯{¯"fpk;wxÍßÏI+ê}¹æïk‰q».¤½@܈™:;cM5™å»nÿ–VçÑ_ ®n;KˆÀáOh?·Œê™O!²Éúq ó_ïvùÙ¼Ϧ>0xõ™ÃR¼ž_Áà®çO`®{ý/ˆî½©ëù¾ëLYIdïAm:³.×¹ö¼>·[æ]D0å àmCØvG1˜ÖiË'HÏü,`§Ìõ¯¢|—Ž~ƒá£tOæw—mr”ª1W!ÆÍþƨ"I‹Ù^Ôw]Dzè:怡óíBŒ¥¶‰öߣ^Jj{-.ÅnZAØWÝÕªŽŸÔ?U>»°üsf÷ÜÄ:gÈ¿«Í.¨SŠ]ŠêÊ5ôˆë§ êëjêŽËµ}=én¨Ýå:ú ÄõÒõ]Ë<¦ÁöV¥ÜýÛJï¸TwײsùÓôˆëµ ê5mË Õ9­f·JÔíî‚Úq013qj½›(ï–8ì.¨Ï(¬¿“e³ŠÈ>Jw©YïY…å;aÏI¬s†þ‚ä+¬§T^BõxŠó&•ñ[ç躊٤kÉ®j€ÿ~º îÃܵq#ó ¾¹¦=‘¹»×¬§—С—.¨ŸíZn}Ûí§ êq5u_ÞC{$µÌ ¸ÉT7>Ä8ú&‘=ñd†3Xð$ÛDàî|˜øP$wm»Å`U'£áFÒƒ8/þ:ñ÷Ù»¡ßDœ#“÷sàõ –ߓȈ»*¬H/>Pu¶^gµ´‹h6AÌ®ÌeĽ„è6Hëˆsã®DÀ÷ÀK»–i’Y±•ÈbÉ]¯˜÷Nªg7-óWäw ^ü ‘÷ô¼hªn¬ÄA›Õü½ÉëÚq.õ‰U ¿[b™²ëùfà‰:KH;enÍÄCYîõ¼,ã â»Vê³i9éϦ^m$+uçüÛè}Ò*'1—}øyæŸÛï$ýýóyäeyÖM:“Ê´kËRbÆßýˆï&ιî#£Ê€[¼øHb™Ÿ¯Ms$IÅًƽ\BÞ4Ýšsó3~ƽ¼n8/ƒ&Pç ü 1«qǾ¤3’n :ÃáM…åÞWø}qÀ²òàÌ6·ñeû5í¨*çR?[bQ?pÝÙÝeÔ‚”%D·È^^Ó_Ù+¹ê2àrÊ‘=ìãKzÜÖµ¤»5u{bÛÙDd~5éšÙkÜ#kÚrRuÕlW³£u«2à:ÞW³îŽ7ì ¸·ÖÿÖÂï÷!=kæ&ªƒ0ÅY”?Xø}q_ÊÊÃz܇â̸ױðµŽtâzŸ=÷èÂzÊÆS|zb»r6 ôš·”xÈЩ[ö™š #÷}ø³šuœÐ Íƒ”Ê€Ë-½ up&­‡'°¥&;š!ºn÷Û5[RKÌ€›L“q´ øG"«ëC5Ëj¾oOˆO`2²'áxÔh3¤.ïú95îÍjÊ+‰ñ‚:Š7®©1ƒ`r2à Æ%z4‘‘ÐÔ‰ûšTzµ²æï½Þ¨à ðxb†Å¦nGÌÎýü¶(mkuN^LšXGŒõêßSˆ,Œˆ z«ˆ6žÆðºñ©vÀ6ê2s3Ë<›ôäïÁ(hV]ϯ$}^­"‚KÝVãyv4¹ž÷løKæÀ%2²‹6þŽºƒ Þ–ù·Ù6¥F-áXæºPþŠò±ÃÞR³Žœqñêî-sÏň!NîD\“oG Á³ûlÙ«ðûÛÑ[÷Ц†1ñÃ׈ìäŸ]¤_Fuöð q¼Þ›ùç¤$iÈΠÿ§8Ã*;ˆÁ\GÙõdš­#¦so2žÑ¨K]×-Åñ¢ºoe.¡¬\Ld‚ýIáïgwýmeÍú^”ÙæqÈ€ëXJd5ÝXÓ¦ªkïc3¶ÑOÜ'jêº»Ô tº8o ·ëÛÓ2¶Q—wqÛjÃÑ}ìã#˜ŸÙÒ¤¼§ÁvîKdòô²/17áDJ¯p«Ùþ«³÷²ÚßÔl#5©ÿ])uðášöÍoìs;· ºú¦Æñš¡>“©ŸÜ'k¶}xÃ}ê–Ê>iMÝ&8ˆ®äuAÍbðxиý ëÞBõL¶'Ô´±3ùNqf÷û,\ Y}U빸aûßSÓ®^ʧ¶áèBݜܢrî{>CÞ÷ðãjÖÙK¹Št·ùÕÔ? £ÝEK©ž´.øÝÑKîÄû~1®a/×&8ˆ®Ú©å?×C$I}øÿAÚ¤ü‚ôÍFïîÌ,¹Rì:¨Åé~Ì%–Û‰ôMöÍÄŒ ÿWÛ1æUÕz6S}ÓXÕî²ÒtÐã;êÖuK»%qcþAbŒÇ¯Sð[A¼SmNMÐO¢;fªþ ºRöjw¢«ìûˆ ɯQÿ.^Cz¿R“zô€ëÇ3wŸ%fk}\FÇ“ž–Ž'‚jŸ™ÝÎs2¶óÒ£ÔÔï'÷‰º3Äd½ZË\ʲR—Ó4ðÄÄöºË pÅî­UÁ ˆ.‘—%Úu3ó¯9Ý“éý]b=[Èædõ“YôR¶ÑìÁóÑ…º98ˆñ~75hSN{¾Þ`}MJêsæI5uëÎå2ÅI$Šå³™õ{ À BÓÄ÷TcKêHšƒè£Ñê%ýy®%RÎ߯p¦áVï~ <ø’ûfÚý®D°c¦…mk<¬/üœ„y qyeÅßWctu¼+±®Ô ¼;{Q?0úõ5_O³(ü¼¡fÙÈIŠnCÌÄVe+1Pû~Ä9_¦{öÁAú0é1ŸCý8tÃt ð/]¿»-é÷p;Ñ-k_ª3цùšv{qÜ~‹…•%À[™Œ8“x8—ò1"ø–Š¿¯,ùÝ«™?£yNçkDà¨ê¦¸l;ƒòYbÿÊnh!•oîqÝÇP} œüoMýâ~çK!‚8'e.?Hë ?§®ç7ÇcÕ,³+ˆkFG¯×ó•D6RÎ1ødæŽMÀÿd6×ûÍÌM‚2Ãüëô=ˆ r™eÄð'g´¡ÓæŽÜ÷üçÄùßdv┃€ÍþÅîñ߬Yv/ÒãœtÊõ¤'1xtE½Ny@F»w­YÇK2ÖQôôBÝÿªY¶lLÏ2·sÇD›SQýfÀíLz¬­â}iËKÚóÜ̺·¤:K,5ˆ{]Ü^ Ú¿s™V[‰àFÑÎ%ë¯ ¾u,~YÑÆ”,U×2¹ã*Bu¦dj¶Iè/"¸_U½wCMu©Ë™!óÔÂò«Y¶h<®»V:®˜±÷še÷ /ãl#é1ƒYSÿ¨Œv/a~Wá—eÔ)ZOzXëɟųx}¿°a;rºÏPŸ÷†Â²uïc·îײ¬<2Qÿµ‰z34?»'DH]»Ê¤2à~Ó°-ÅÏ™Hg÷’õݨŸØ°Í’Æ„³VN–ƒîÓãnß º7>ƒú,‡MDWªƒ˜û7*‡Œp[?‡~ÞT³ì5ÌÏr«òQ⦭J]σ2¶q=é/ßÇd¬£èÈÂÏçÕ,[6†ÎÝ3·óKàwKeãÔ}†Ôýý&à”šeÞ@³ Ó õóš^Aõrê5­÷.5VR·ã™ËzXNÌŽW·­ÜÙíTw-Û¿îmJñrT šß$S¬[NÑ©T_3–¯ÈXG·õÀŸUüíÛäu+mšÌŒ¼™Ãë†u¡Éõü÷äAþƒt¶Ò ®ç!&XfÁ™Ž‹ˆ®ëUÖÏÎ\W1û«édeϤþó£ÎZ¢[vÇûÖŸaá€n©TÿBú=­Êšl*÷¾(un: ô‰…ŸW™“UêÚ·SÅïO%®ÙU^EóvK’:ž¼'bý– €ÇŽhŸ4\Gg0šãæoG´OO—0w,œ‘±ü©°½.°pךú¯ËlûGëØFtḵšŠuêžX³|Ù˜U¿ÌÜÖâæ¦»þŤ®XR§XrÆ+[JXRëùÍOEý Q–mpù£_•Ôÿ雨?.©S,wËÜöNÌ?fX89¾”oãö™Û¨:Öˆ·(Ë<:s;ÿR±Ô²KHgùç~Æž~˜Q¿®ëß73ÛQ×EäóäeŒ¿²Pçf`Ÿšå_\±½‡fl«¬«å õ³1ÛXVªf(ìvpeͺ΢ٸ¥kˆ×ärªg¾­Sì\,ÇeÔ=¤¢îkkê½°¢^§œ˜Ùö7–ÔíîÞxXÅ6^Ÿ±þÕ”¿ge³îQ±Odlgå]×~I:¸z@Å6;å½Û†ä}:±žëÉÏŠ|\b=OÈ\Ç*bŒ¨N½›hž1yCU[€»g׺¯$oB›Ï$Ú—šL§ã¾‰ú3Ôw¡<‚ù³„~_.îR³IR.¢þC°iù!q¥Åã@â‹Æ ¥-Œv¦@µk-ð§T±¾ž£¤îéöJên :`± qãü6ò&¥¹†ø‚›£↩n}—ÿJtaû" p ƒa×·ªÙ¤3¯'ºñ=Œør}0‘w*å7¿#Æè*³ÿl›ËžÜw—À;ˆîÝ2öápÒçw¿¯çã‹þ0,ë†| Õ3Öù@bû7YfÇ™yw&ÞÇS(ϾžêÏÇÛYu_.©WVÎþèrõX¢ËÒ‹ˆ,ßUÔ¹…7¥ÏLlc;1ÀÿqD·×‰ Š“‰±×ÊÞë§Vì_U ¯S¾HÌdx¢ûë}ˆñb«†<8¥b;;Çõ?‘÷pèL"£0g<°U¤Ï±bRˆ'¯Õî³ûòGÄûÚ@™!r'fl{'âzå]y·3²>‚f]µïÄü@u§ô€[C\Ï«ŽçÄ{tTÍzÊÞÿÔd:«ˆÉ ÞJÞûÿ;¢kg1hüPâzR¶üÈ{q®Ïüî©r>16`1hs$q¬W=˜øÌl[d¶ â³°êQwîNTÞ>qÎæv…½q½¬ëZܹ¶žLõ+©Ï@;{v÷$‚]·'^Ï¿'Æ¡®ªw Õcß™¸Ö–=\)+ß#‚ëO$®ÓOþŽxȸ)Q§ÛDöì×2¶¹™Kï°’õ@: ·Sn&>ûžÊhÇ —¤©¶–ò/ƒ½–ˈ/9Ý 4BùMY?%w<M¾Ü1)7“ÎBx|I·%–Dæv»KÕàóEBÞl~©r%p¿ŒmAta+v굜Gz”Ôw©Rœé¶7ðŸØíÄMÄ:z÷ÊÇqkZ.$=Y5#ß KYVñÝ€Ó°îëIw¡¼å™­MËÒ³Ð>¨ÇõæœÏßqžEÞ¬Ëuå,ò3M†Ã ÉIDATÚ`½u™ÝžP²Ž~p97ù3D,Õø¸’:ïL,Læv»Kg|·eÔÏú¾ƒ¼™KS3K§ÊgëïJý¸¦Òt‚ªñàºp9×áÜ1/ÌÜ—bùëšužH}Ötn¹‰x •Ê.>k@ÛJ•² ¥êºí–•‹+öaõ™ÅÒt¬AIR…û1˜Š-D£&³piz­ ž8^Ë`ޝ¦ƒ[krBþq‘êÞ¾œøâY\¾*“ â ~/Çfî¸t§ÑüÇvâKwÓ®ü;Šó{ا ÀË©Ÿ ­»kVn©ëÓíÁDv[/ŸKï#ÝE´‰D6V/67uuÙ9™ ý–O'¶ÿ(âµnzœn%2QÖ×ì_Ç‘Äù“S,;fëÕ¬žÖp½ryfû;ö%ºåÇbË-?'T9ã@v<£ÁúOn¸/™uÅuô€«ëÎ_,©ìÞå, ܤÆÖzVƒíË•³õÈ\¾.ê'_¨*qmPç?2ÚÓ­lÒžî\ÎçHÝ8¡Í^ú¿#cÝ{YŒU™¿uåâ33g TæÜ ÊËK¶››qW,;¨~Xù¢ÌulÄÄ iìõ3Ó˜F+•Ýë ÄÍë…X—¦ÃV" ûa¢{Ô³èmÌ”ŽCè}àcM– Dw£fÿqÌŸHéfâýÿ91)ɧi¼úîl¹‘ÝtÑ5m?æŸÛˆlÎsˆÁÒ?EÞwÎù|ãl[¡üuÛ•¹ch5sך\—]ËþžÈN{8qlÌü쨢‡ÀωÀõgèmÖÒ«©ók9sA;³L6õ\bò›Ãˆ×"wöä*×Y…gÿŸºž§¾#l#®eo ΋Ï?I,¿™Þ®çk× â}êÔ¹¾ðûN&PnFíÍ,<·0ÿ³£øÞu®™Ûfÿ¿•8¿v”¬»óÚ1»/MÈãdMô^™!ίîëÎ…ÀofÛpÝì¿Åm¯&ïš´3qM,îgEë˜ øìFL^Qç"hõJ"öQ̋řZ·Î®ïjâ3ïtâ³öÜŒmtœÏ\fYñ˜.ê~Ë_Ãne³¾_4»Þι½¹s«£xÝYMtÝ™8þº½›xÍVçʵÄ{Q. Changes belong into template/documentation/readme.template) # Program version ![Screenshot](https://git.laborejo.org/lss//raw/branch/master/documentation/screenshot.png "Screenshot") This README is just a short introduction. Consult the manual (see below) for more information. # Contact and Information * Website https://www.laborejo.org * Bugs and Issues: https://www.laborejo.org/bugs * Git Repositories for all programs: https://git.laborejo.org * Documentation and Manual https://www.laborejo.org/documentation/ # Installation and Starting ## Download ### Release Version If the latest release is not available through your package manger you can build it yourself: Download the latest code release on https://www.laborejo.org/downloads and extract it. ### Git Version It is possible to clone a git repository. `git clone https://git.laborejo.org/lss/.git` ## Dependencies * Glibc * Python 3.6 (maybe earlier) * PyQt5 for Python 3 * DejaVu Sans Sarif TTF (Font) (recommended, but not technically necessary) #### Build Dependencies * Bash * GCC (development is done on 8.2, but most likely you can use a much earlier version) ### Environment: * Jack Audio Connection Kit must be running ## Build and Install ./configure --prefix=/usr/local make sudo make install ## Starting If you installed through a package manager or yourself simply use your application launcher or terminal to start the executable `` You can also run after extracting the release archive or cloning from git, without make or installation. If you did so, for additional features please link tools/nsm-data to your executable PATH. Use the manpage `man ` or run ` --help` (or local variant `./ --help` ) to see available command line parameters. ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1665611335.957255 agordejo-0.4.2/documentation/screenshot.png0000644000175000017500000047623614321633110017613 0ustar00nilsnils‰PNG  IHDR5Ø‚ZsBITÛáOà IDATx^ìxI†ã@.ÁÝÝ5¸».‡ë».‡»»»»…BÐwOþo3aoY‹-!Ù|{ûp³=-UoOzªº«g4 —Ðà‡H€H€H€H€H kÐÊjRK     :¼H€H€H€H€H  …:›ª’ @Æu߯¥k–í¡šuN™µ8êomc»çèÅ?"Þ†í‡óåw’j:GŽÜ;öŸù#ò°Q   È\t’#nÞ|Žºõ+R¼¤ŽŽÎgOÓ']¹2c«výÆÕjÔ›5etršKKžÅ«¶8ä/öõ“çõ«çO;›–:“S¶@Á"=úÉ_ `ll,íÞ¾áùÓGÉ)¨(ëÛW¡¡ÁŠÎª*½Båê­]º8äsŠŒ w{ûúÀÞo^=SUåâzŒM¶ï?Ÿqqqþ~¾W/ÙµmúH¶!ÛlÙW®ßÙ±e]ÙSL!   P ¤{‡üs¯½xæäöÍk|}~äËçäÒ¥gJE²j⣥§(C*Ò×,›wùÂK+«‚…‹uï=¸hqç³'¥¢žäÑÑÑ}¼ÀSš4cÁOÏ+—RæC¦¢E!   %´е׀G÷ïl^¿\(üæõóÙSÆÇÍ[whÒ¼­¡‘Ñ‹gÿYµ(8(éˆÐ8}ü`Å*5ŒMMß¾y±fé|[»l=ú Ñ70X»uDXØÈÁ=†œÇ>Ÿ]¶ þ7)<,¬ÿÐÑ0 ýýöîÜrãêYA“Ÿ‰æX_ïÛ7®|ýòiÑŠÍ…Šûú…¶¶NÇ®½«×i ¯§ïîÍë–GFDÔmجBåj~~ÕjÕóóñ^FäqÈy¶®_ùôñ}œjß¹Wƒ&-ôõ ýW.þdƒ ðUÌ-¬0`÷ö3'#¨FíšIö¹dù såÉkie ˜KçÍðñù‘|Jººz]{ܱiíÙ“G„Rwn]½{ûŽQç aãàøÅDGß»}}ó?+¢¢"c³`ÅÆCûvÔ¨Óð㷛׊ÛêÐ¥w­úÍÌÌ¿ý²eÃÊ –>bãbcb¢á`¼zù$o~Ç\ïä´2~ʆë•Ï™:î“çphݾ«­¿¯Ïšåó_¿-P,Rlø¸)Ö6v÷î\Ç›èn5mÕ®µK×øØø#‡vž=æ$   ÌK ‰=˜ /Q²ÜÍ«¿D{ ÁUkÔð¿éãôh5tÔ1…‚EŠO3xø€n°«ÕªûýÛ—m›V¿zþtPOXÿB¶Š•k®X<{PïöžïÇOëîú¦w§«—Í8l¬£SaUýøÁýÛ×OEŠ•D…ºô*T´Ä„ölgh`Ô¹{¡•Òe+>{ò°Gû¦÷ïÜ„$yP/—u+ :SûÈÓ½Ï`XÃûwý{Tÿr«5kå"%ž··Œï!#'”.WÑÄÔL|ÆåÔÙKN8سcÓV-9~š¥µ5"”ê6j6zhïn.`†úz‹¬ö!#'®\2)€öêÅÉúµ´´ñ)W¡êò³F ìþöõËÝú¤Z>G'ˆwãšœžE§ïýwS¯ŽÍ!$˜&-Ú 5ëéëÔ}ùÂY’m}þüqâÈæõ˜ 3õõõ•Hbg—ž3¹­ÌŸ5×®|aý—._©g¿¡ëV,ìîÒxæ”ÑðÓ„š«Õ¬tXRp,P˜h`h˜'O¾A=ÛÏš2Ê¥cÏâ%K#¾å’yÓ»µk´èS:tííT¨(¼…ëWÎïJU­Q÷å³'´þ•tO‘ ¨$|zúz~~>²jW¯ÕàÔ±ˆwŒŒÜ¾q ŒQ„z Ù`ÂÌÂÞŽŽò­ya˜G~Ì4c~wÏ¿›…éáë—Ï׬hœÉ6šŠc-4i¹së?0õ ð¾][ªT«-ÔöÞÍõ测hýê峘Ëß¹ýŸ¨È¨woihjâ'òT¯Uç¶õ!!ÁøÀžmbÛQ, ÊN5(0À¯ÏÀ[vÃæ]˜¹8 ãÒÝí-Ü'Ä8½yùnF™r•1 ¦ù À»ðöú.LÛÇÅÆÂ1ÀÐÐÏ$ÕÄ E|®^: ²‰?¸“ß±`Šà˜ššÃÔ ’-…>ÅtŸ¿ŸÏÉ£°÷CœçÈ]8– ߇‚  /?ùsçq­) —oܲûøòõÿbAéâÙ“JZ¯×°«°h€5°úöí³pêð¾þ¾¾Awo^_`ð”ÐMP /;'™Ÿ=yGÅÝß½¹}óŠà "€­ZÍºÈ µê6F¹3‘H€H€H€Ô’@!@Q‘‘ÿ°²²‘UÞÊÚÚû‡—û†—•µ ì?¤ˆÍÊèÈ(}Ù²Hq)¤[YÙ"ºCÅ;/¿S!¹ER—ˆȃiiLxÿ5fR|Â~L?ëèê& ÿÏy娈HL#.Hh?1©ŒÈ%Ì|ûx%j ñðd@BtpppÏö]Û7ÀŽß¸v~ZZÙ >«Ó& ‡€)gìj*Ô70üàîúÉÓcëÆU»÷Ë™ÇþÑýÛ[þY #~ÞÌ .zb©ÁýÝÛmWÃZ+«„Op°(æ ¸4U\$9(‹`¬WÈúVÝûFܔޮ®®Ž®×÷¯B…aa¡b8’M€CÓ–í 8‚|¬¬¬MÍÍq‘`V6¢ËfÆÄ‘‚:³§Žýáõ ÍÁGº¢V$k¶±±¢°¤4_<ÇÂÚZ8‹ UBÃÏ?¾—.W éÔ­oÎÜöèqøWˆ­B"¼²ìyøñý[n{‡{ Oü @!„€¹ÞçOV©Qâb"°¥îçë‹à~!ƬI?_9 BÁæ“d¯‘ø??oKkDf >žã磰ž”ö –räÌóúÅ3˜È0=ΞŒ  U†¯M¶lÂ\;Ä.# #T ÛôüÙ㈡ÇOxˆ—Ý‚Œh|á þ7vToX½ÛfO†-ÚvükôDDõˆ…üM| ˆãï@‹BÏvë=ZÜаúã>Q9íÑ@pT¿!£¦ŒêñÞ ÙVmÜJpЯ{k± ÂÒ6<`Î^œ(¿•¸_Ú@ìQ~ §'òñW&–ô„™gºvù(Ð)š"Ùð¹|áVr¼¼¾b j*¯™gI€H€H€H@$Uwn]_¶Be„bçÈ™Óá…‹”˜Â3OÅů]:]ݵë6¾|Qô|R~H€H€H€H ëc‘K))ó‰£aËì‚åµ´µº-ÄQÀºElú”Ù‹ EOZ½tžjÏŸ<ÂlëæÝÇðxþ=~Î('€q6æ<åâ£ß¸f)H¯¤ª$O ñ7¾¢÷|ö^=‚ÍRúJþÄÄ?V3¯ÜòÃë;.a@I~ñ)¹­Àë8´w^æ7`ÒØÁÜÅÃgoc— ]¶zù<ñ6Ù&ð$Ó/_>þ³ýž„J„‡­_µxÄØ©Ø‚(5Ô&.……šwo^åÉ› ÊVÅ   PcšN…K¨±zT@DPppп[Ö)ÊÀt   PKI‡©¥ÚT*‹@$Xåêµ/ž9‘Å9P}   ,H é ,…*«7¼¹¬eÛNGîVP¤Þ¨ @V&À ¬ÜûÔH€H€H€H Ë`P–ër*L$@$@$@$• èDGGgeý©; d)¢—½f)…©, de ÊʽOÝI€H€H€H€²:Y®Ë©0 @V&@ +÷>u'   Èrèd¹.§Â$@$@$@$@Y™€¬ÜûÔH€H€H€H Ë 府 “ det²rïSw    ,G€@–ër*L$@$@$@$• ÐÈʽOÝI€H€H€H€²:Y®Ë©0 @V&@ +÷>u'   ÈrR0½c·>Š€åÌeøì-Eg¥Òmì²¼ü ™™•·«¨’]G.8:Vt6ÉôFÍÛLŸ·<ÉléŸ!u4Ò_N¶H$@$@$@$Aè$)G•êµ]:÷t,X86&ö³§Çé‡Îœ8›dÁ´gX·u~§B¨'( àñÃ;+—ÌÁAÚ«ýM5ä+P°g¿¡ÅK•ÑÕÑñôxôàžó§¥¨­MZÖª×xâ¨)*ÅÌ$@$@$@$@$|I¬4nÖfüÔ9—ÏŸî×¥u§VõÖ­ZX¦\%++ëä7ÆœKæNkT£ô°]rä¶ï;hdkû}Å–¯Ûþýë—áý»¶oV{íòu4QUsšššÚÚIô”ªÚb=$@$@$@$@êM@Ù €®ž^ß!#ׯ^ròÈ~Â˧ñ•%Ò¶c÷–m;?}tù¢YâyúÖí»tèÖ'.6nÿ®-‡÷íDÁ_Èùòù“‰£ Çrá $éØÁÝUkÔ513{ýòéâ9Ó²eÏÑèhCÃÏ„‡†öïÞvüÔ¹¡!AöŽÙsäBsaa¡ÃÆL.X¤x€ŸïŽÍká•IÑõ÷ô¼ù ÄÇÅݾqeÑÿ¦Hžå1 ”Í+p*ljf~íâYå°jÖmضC·Éc‡tmÝ **jìÄYB~C#£¼ŽÝÚ6BLK—J•.tLfo߸ƥiÍ=\œ iåÒYyåÂYÃò•«}ñü(™YnUçO«Ó¨™Í!Ÿc¶¹îܼ*ü„ÿ0b`·žšÙçsv/È­‰Óæ. AÎÍkŸ:vP܍ޮîÄ ŒŒ§þ=LÒúG粯\8#)^||<~*‚ƒSEŠ—1¨{ß.­°_¢v½ÆprÖ¯ZüìñCƒõ/TU¥Fݳ'uwiüÁýÝô¹Ëß¾~Ù¾Y­Ås§7Õ©p1Éæú ~÷Öµ¶ªuhQçøá½’§xL$@$@$@$@bÊ3 KL“ ¹g/Z½÷Äe|+V­)I°Nƒ¦GìBÔ{DdĆU‹qÖÄÄ´´´6¯_<Þ»=y¤vÂü=²=yt/6&ÆÏ×ûÐþ%J•QÞƒGŒG‹O_×ÑÖÙ¸f©df¹Uݹu5[¶öù‘³n£æ×/ƒB©ý»¶úûîÚº¾N}Q|ŽÜ0‰ž/,M„…†`™âÅÓGBqƒ™óW†††Ì™6òKJ¢¯o ¯¯ïëóCVEpóȾ¨ßG÷o;**[)—Ïòùá…ƒ|ŽNvÙ²oß´"½|öøÒ¹Sõ&ú9BÁ˜è˜œ¹òØÚeG†·¯^È­‰$@$@$@$@$ ,(80Q@¦¦f‚0æD]Ý%ë¶êééI‚³±±õúþMHñóóÁmck…ÿGŠc¼¾-_©*òXZZ#Гߺzºººzß¾~VÞÛ6®¾rþLpHìZ©œr«B¶+ÏÔmØlëú•ˆÂŸ;ýoq)o¯ïÂñï_­ml cg—ÝÇû‡l`R§"zúý»µAP’”$‘‘Èomc'«‹\8!!ÁÈ(䊌´¶•Sgá& yP¹¯·ØñO)ŸaÝŠ½ [³y¯€ïž›.ž9!+ SH€H€H€H€H@Ù À;××0ý«×i `ÂÜ9ì{ôKQóññFüº£> hüÔÓÓ7³°Òí²ç€ýŠcl*ˆˆؽm—Ö `³"xFy ÇßßWÖúWRÕ9DÕoRªLy<¶“åâúm³eŽm• óãÇw80ÐBJ°Ïo\½dÁò˜e—:…hŸÇî"ÚG2]PM¹Z˺B2cyN ö$³eÏ)@׃Ÿ gOviVsÍÒùc&Ì´²²‘ÛI€H€H€H€²8eæð7­]ÖȨf­ÛÃê542.Z¼”‰©™FBt»øsùü©m;æ¶w0Ð7è÷×(D¢ 3Ü0g{öЏyÄâ7lÒ ó(blbòùÓG !f¦q‹ÄH÷Ôõ¢ª\_¿À”üàáã/ž;!6 ÑD»N=Ì--± Ñ¹{?%Â||ïöá½Û aã /„Çc=Åâ;´÷Èþ Wm„‡ %óæVT¬R}À_crå¶ÇŽ…b%œç,Y‹<ŠàÈU90À®”ØÊ—ÌóÁÝõǯ®½@$ôâ©.?)™¡zíú–VÐ7 ÀäZ$· &’ dqÊB€€æÔш›o×¹Gÿ¿Æhijzz|زnÅÍë—%©]½x“ës—®ÃîØ'î/ž3U8öÉóÃÎÃç°h°gÇÆ'ï!}ë†Õc'Ï®V³.‚éŸ=z€­½©î%U]8s¼÷Àá3&’¬»™—­Û!z еK»·mT$ lèFù÷ާ1‹=Äâm(rh߿ں: Wn=¸ÖCÄõps>°{¯~­Ú´O Âîx 8«Ž\­?¼‡˜¨}'®„‡…vmóËzBllÜŒ¿‡;yÿ‰«þ¾xŠ‘T Iç²CFMÐ×3ÀÙÅs§fÜ&ÈÕ‰$@$@$@$@éC@Ó©p‰ôi)=[Aô+—.ÃúuIÏFÙ d|ÊB€2¾ôr%DpQó6±v!÷,I€H€H€H€H +P7 tÙŠûO_ :æxVîWêN$@$@$@$@r ¨g\U™H$@$@$@$@$ n+ìQ     %è(ÃS$@$@$@$@$ n’x (Ô566ÒÆƒ-“zc—º¡>$@$@$@$@$ÙÄá…]ññAÁJÞ·›„`fj¢A ³u?å%   Èrâã LLŒCCÃéž„»oíÕÔÒTTžé$@$@$@$@$@…@‚õnn`®Dž$ö$DþÐúW§H€H€H€H€H CÀì½2>  C©BaH€H€H€H€H€ÒH€@²8 d&t2SoQV    H#:iÈâ$@$@$@$@$™ÐÈL½EYI€H€H€H€H è¤ ‹“ @f"@ 3õe%    4HêE`ɨ>.>N3^3,<4y•e124ÆiE/KŸVB‚ƒcñ‰‰Q&(Ï‘ ¨}CµÐƒJ @–# ««§¥¥¥££;6˱KP8­à`—››Y”r.k`h ì}IÑ×ЈxòäApp ¦†ôºDú´ëßÐÀ°hÑ¢‡ µ% ©©éîþÁËë[ŽÚiT-­€FœF©Re´µµ|}|Ó(Š™™©s©²×®^Ò”U*]ZÁÔ±bEŸ?™FEXœH ã(^¬m]ÝŒ/-%$ "`“-ç÷/e±Àú×Ó705·”=•ÁS¾}öhÝÚE‰?§Ë‡¥q²:­@xD˜¾¾¯¯Ÿq“y*00ÈÚÚ ›HIŸVùϹÿdv³‘@&' É?öLÞƒŸH ËˆŽŽÒÓӓƯaim'˜I~{'5ŸŽér̼_¿~E3-7ix¦Ýœ­À"ˆO£K•I®0ŠIYœÍÿ,~P} u%©#’¼7cº<,,Dvºº|Ñÿ*T®1dÄø £‡„……ZXXÚ÷ï'ÏÙsä@†ß×.KÕÙ®c7 Këñ£ÁO>fÒׯŸÞ¾~imcsëú•­Vëêé÷ì;¨c—^›×¯’+ I€H #2|œs™ò‚„‘‘‘CûwM…´Çï÷ùñ#STD¼µ¸sÙâ%EÓÿøà÷¬O ?U²[U€Ü¹yc“¶{^¿tîú•óññqRÊKú.ŠŽe‹Èõx„Öaî—,S¡aÓÖ?·DhU¬R³¤s9=}ýDX1UrëIlŽ€wþ$u%<]]]CC#¹=` /ýú̼`õõè¡=¸½]½t¶^ƒ&ÎeËÂ?|pPCPPÀÃû·ó;€ÒeËëéܽ}”®T¥ÆêeóC‚ƒÜ‚ƒR¥Zm8’~Â…³§Z·ë(W&’ @Æ'°sÛ†›×¯@NeF R5îܺ¦ô¼ŠN&Ø«°þKü´þ…zE?ã±à$cŸ@’Ò¨À­LÈ»•êéé×mÔü_EÒʵºeV”.ýÎ]Eù”¤ sðâ¥r.V²{ŸÁÙsäÇàˆä–0Á•— òKFqº¹¹UÇný$­¹¢&³•ŸáL‚ˆü—H@½ È-2k"^i.ñô…ð°0ƒ__rÜ¢uûx ÍëW.îÚ¾iýš¥8Æü.¤‡É,V¼Tåª5ïß•YÑPn ,Oà‚eÿŒþ{:v7É…Q©rõ}»·ŽÚÏ’™4mî«—ÏF îõâéã6í»Èæ/å\n㺕Ó&ŽrwsmѦƒl†T§HÙ«rÊŸwOa“*pdŒf¹"HκÿꈼɯÜâHT”M²fEe•ÿ¥ˆz;ÔŽH 9c’q$#ŸŠŒ—Œ242ŠˆH´é!vƒFÍËW¬²bñÙ9°ÈÈd04JŒ)2úµ`ÁBE0ݵjùüïß¾fdõ) ("põÊyD9.™?óųGÃFO´Ïë ›>ž=°ˆŠPÉà ÀûwoÅÄÄ 1¯}>¹™T‰t<ŸÇ>o~Ù ©N‘kñË&¦º~qA„aF’)åõ‹§Çí ”œö—Ì-YVq5"oGn+~W/©ß¸%‚þÅ!@²Â$³•D/C¶ý}½,­³eFV66ÖÊņÕýæõK-MMåÙ’<«éTXþ+‘…’0Êý•´‡÷G*\ÔÈÈ0-²`AP õ×Ò–ž™KŸV 2¢¬ð€X­¤E~–%    ?N I@¾ç”"¹a³#tÞXGô Þß÷IŸV~Ÿü¬™H€H€H€H€2é`›Œ e     øMèü&°¬–H€H€H€H€2":±W( ü&t~XVK$@$@$@$@‘€ŒØ+”‰H€H€H€H€~:¿ ,«%    ŒH€@FìÊD$@$@$@$@¿‰€ß–Õ’ @F$„‡7‰iàË @¦ Ÿ`Ã+ü$ñ&àØØX}ƒˆˆ MM…uð @F o`` ‘ $$ÔÜÌTß@_‹@FèQÊ@$@$@$@$@Š `î3øAÁ!гh$± ©©©¼¼’ªyŠH€H€H€H€H £Hb@F—ò ¤…€´ÐcY    Èdèd²£¸$@$@$@$@$tÒBeI€H€H€H€H “ É:Œâ’ @Z$ñ Ø¸Xô磔k:nêÜ\yìÅy–ÎæñÞM¶\ |Äÿ ž…8[†ÍJ–.·lÞtqŠ‘±ñìEkÏúàÎ͈ð0¡ lµiIùu¦E–%    ßM  ™ÍïÙ±ñÁ­Bæ˜Ø˜d–’ÌöàÎf­ÛÛØfóñöÒK—¯äõýëÏ©¨EH€H€H€H€H€äH*HT(é0 ¸˜˜è˜(á7hÄør•ª ‹•r5q†ÄZ‚xêÿ—jƒýß¾z^¡Juqså+U¿wë~vìѯ^ãæBzÙŠU&Ì\0ņÁ£'ØØÚ"±|¥j‡ÎNš½¨ÏàÂñ´yËóäÍ'®MÁ\&L$    µ% ”¿wû:¬yMMÑÓùmì²Ù;äx÷–dÛ… oåÒåßk&Œèÿêé㞇#³›ëëü iik›˜šééê98:!ÑÒÊÆØØäË'tÍ @&!  ]çžs–®Çwä„™©VüÅã†FÆù ¡† •«cA ((@²¶ªµê^»xö“燸¸¸+N[XXYÛØùûù„†ç¶wp,Xäíë¾ÞÞÙsæ.P°ˆû»7È–jaXH€H€H€H€Ô’€jöœ @åjµsçu¸sýRòÛeN    È"’ JzðOP9¿xz*Z›‡õtõ«Ô¨óËC÷ÅÙPN¦òn®ÁAzôtïNtt´TÁ›W.ÔkØÌ>¯£¦†¦a©2q€<îo_#þÇÐÐÈÏÛûý;ׂEŠ™šYxz|­_NJégªI$@$@$@$@ ’X?²G9.©lW/íÞoèß3æ¾s}]¨p1d>âü”<×ïÖõ¦­\îݾ&u?ß¼z~hß¿{ôÅÓBÃ#ÂÜ^¿zúèÒ½½„‡…¾÷Ç‘‘Þ^ßñol2‚‘ä  \Sž%    LM@Ó©p E „†7jÔÌÛÇGQ†ÌžnkcsæÌ cã)ev•)? @'TPÇCõI€H€H€H€H@½ÐP¯þ¤6$@$@$@$@$ ”¥xx’H€H€H€H€Ô‹õêOjC$@$@$@$@J ÐPЇ'I€H€H€H€H@½$ñP‘²|X¦zu9µ!   ÈÊ”9zzzññqêêèêêÆÅÇZ[[ûúúfå+€º“ ¨%OºWæDtL´º.@¯°°ðRÎetuõԯ˩ @–%pöÌI%º+s¢¢¢ÔØ8ŽNøÀPB‡§H€H€H€H€H s°µµQ.07+çó$@$@$@$@$ Vè¨UwR    PN€€r>uîr‡üN©((.Ò¢mçVí»¦¥–% PB@ù}AIAž" ÌN@' ü=}~®qqq©ÐKK[;zn©P„EH€~7ôöµutøûò…ÓëW.ÔÔÒÄkl\Zgš²È}áw_ ¬ŸH 3PÃ1ÀßïɃ»OÇO—ϱàwW[»lºõµwÈxêØÁ‡worlí²ïÚúŽõô ¯Ù:j@·è˜hüt*T´÷ FÆ&ÏÞÛ·kKL´(Qü115séÜ«`‘bÑQQW.¹tæ„,n̵éÐ-gî**2ù_="&™Ç>_»Î=räʃÊ1*¾}õ§5o[­v}=== ˜;6®Á- •·jßÅÔÌ<**êì‰C×/“¼/*ZBv؆VŒÌæ–aa¡KçNSî£$@$ @â×OžÞ?¾;,ìñþ]¿¡cž=y¸vùü¼ŽƒGþííõÍÓã½âe*TY2g*¼‰ÃÇ5hÒòÔÑ’™ûåùñýÔ±Cq2j¢÷÷ïÏŸ<Ì€a}è˜I‡÷ì¸w纞ž>lt¹ma‚¼ïÐÑ0vgLí¿€8f˜zöû ÷$Ì0á&$N¿çF¡b%Ä!@¨!ùª•._¹vƒ¦«ÿÏÏ×®E·^ƒÖ­X ©¡ñüñƒíWCY—Î=;tí³vÙ²÷ßb¥ÊàÞƒú±WóXPçÔ± F+¹ó8`iâü飘÷ ֛Ïd›FÊçO¬?\,¯¯Ÿ‹–p²aÖ?ŽK•-ïîúúÅ“‡håÚåsaa…Š–Dz™ò•N;1S_’•_8} ?±‘C®`B΋§¡w°Ráöö5–2>{zÀ©À7—ÌÖm¹b3‘H@ þ’úÓÇîÝÆ‹ K+ë‡ö`ôÃ0…•LÜ)@¦\¥ªÇí Å8†[€ìýeÏ C¼]—^ÿ[úœ+kQÁ²å*£ò‡÷nÁÊÿî ¿¢%KcUZWWûÓ0­ƒQ ƒr")qƒøöå“dw8—­ wØDžûw®£Q¼|þX{ª‘ d5*^>,•ˆ·0·Ä‚¯Ø’öõõ¶Ï›_9\aŒF?_o KÉ̘ÕÖÑÖž4s¡¨­­ƒáá+Óæ.R†÷ï‚›ŠÏ/åMà,²á#If†ý­£§7wÚXØÁÊ+I‘j u„ ƒ‚×dai‰˜™&­Ú—*SÞÀÀ‹XûÖÑÕ5³´DØxÕÛÇ[¾.Á¢[Qb…bP -$Z˜[‰[DН&šÐ70DýBñ~‚)/¤c@V0!+(áˆ\ˆà €Äãè(á˜ÿ’ dY°/w ˜ü7hDF!¸Q<æ`<Á¢¯¯¥HñÀ•0j‰fèƒ9œ=y[~qÀL?~b®¤s¯]zZ¹p&øóæsœò¿%BCë>y~øöåóá}ÿ6oÓ![ö\/Ÿ=B\Œøõ+5jѶuû.˜ÂYÉI"¹Ã¦PahHâR‡²lÿRq õ# b gn{„øcZ=&6‘ôˆq|Ħˆæ§£"#ut511‘Šør÷nHÁ„P@@¢9+dâgO-eÿÕ§£¸ä)á\6ÉB6,þÂà–ÚcðÞýíë—Ïþ=yÙ‚bWD\[|Ü^A@ ¿\Õä6‰1Ü…SRB»þþ˜Ü*R¬äòù30§ŽçìÅk5ã5‚üý‘A\ 6UKANYÛÚ‰óXÛÙ=yt/ñçOý çMù ÜQVàuÀù F:Äò$>–+˜ø,H€H@–€x؇5ã^v –-"•3*…¹vkѨ%º_lß° _Ùâ·®]ìÔ½?NaHw}óJöÉXeÅרĴK¯›·Ån(ÿ¬]:ƒpÝͺõü¿)ÿ=úYî°)Û.SH€H@m¨ H ¡ãÚÚ°ªËTè;dôó'‚¨PŒË؆…Xì FH¢€@ ,Øþ…ùתßD’c݆͸idlܰYk,KžÂ|¿×÷¯­ÛwÃ46BÕ³çÈ%»/ä|tÿ¶}>ÇŠUjÂëÀ"¯¢Tõõ˧¶»£*Ȇí Ↄpác§H…‡"CpP!§"Õ$#2§FívÙs"Ä;Ì^<{ŒÕgÌuøÃúG¶šu ™A›Ì°g?mlí93£rµÚ˜ªRÛlˆõ‘j÷éÃ{ )^²4:¥zíúÇz“°%îу;[´…p´ª×i WZ¹‚ÉÍÉD ¬L@î°ŸœZ4 •M[¹Ñù š´B¤TN„é7mÕƒ¿®Ž.FÈêµ|ø š0ÂÈ*`<ÄÈãC:Bƒ-Bሢ=ãb1ô9—«ˆ1õ"ç©hØT$-ÓI€H ³PÁ @ç^ðÅŽ×÷o×/Ÿ½š°uá˜V-êЭÏÜeë1©ƒÙ— ƒ50i=~úü@?ìý’ćq|ÔÄ™¢§=¾îÄaÉS˜øß°j1žð3uîRDvþøþõÔ‘ýRèÑÊš%sZwèÞ®sÏȨÈsÇaÓ°l÷ ª«·ëÜkÆüššx x2ã¸gü5vÊòy3±#.~çÆe<¡hÁÊM/Ÿ=Ù¶a¥\ÕdÛB ”‚ÁM·úï޼ܹy-o߸R¨XÉñÓæ!®E‚­^Ò¹gÿZõ£éG÷EOÆý¼zñw¸6»c† Š dV*voZ½¤¥K—ý‡}ÿöyݲùÂC3ïý•Ï^¼P¸×)ž¸y@²¸\Áde` @' wØOÎ@­ˆÛáý;Ûvê>uÎR˜æ÷o_¿zþ´TNa O503· }ûòù‘};‘#ÿê¥s0=Ô¾KoDT~üø~ÿ¿›ð¸<5Î6[¶˜è˜wï^ŸÙ³]SS>Cçýã‰WÖà1¦’õ+6IËt Èì4 —P¤ChhpÃFM½½}e`z:ÀÓôJ”*ƒHÖ4¶[³n#Ì™áa|i¬‡ÅI€H€H€H€2[[›³gNâ¹ðŠSA¢ª™ž¡`ÏtÎ<ö AD¾zúè~† ¤„¥ l% ú alj†ƒ"Ô±Ii¬ÅI€H€H€H€2#†eÆ^£Ì$@$@$@$@$ ŸC€äsa* dMÜ5ûZ“ dQiÝ€Ç}êéâùËÚššô%²è5DµI€Ô’@<Þ€…^RAûjÙÝTŠH ³P48§Hþ´:úzºxl3ž»¯‰fñ¤e~H€H€Ô€@Â{×uuuôtub%^…Í8ì«A÷R ÌJ@ñàœ"ÒêhiiFDÆâ·)j•™I€H€28’Îe££cŒ õc£b$Eå°ŸÁ;Žâ‘ ¨7EƒsŠ´N«€ÈaÚ¿^ƒF)j˜™I@.k+Ë÷ïß›[XÊ=ËD ´¸uãzõ5Esû ³HñøïçñÏ$щËÏ#ûlx'‡ý´tAú”ͼiæ•\nϪ™:‚Žj©”Üî“JÌ Š_8wFÑàœ-ÄyÒêˆ*Jˆüá ƒSÄ&¨ç IDAT™•ðóóŠŽU’§H€ÒBÀÛGô~÷D@p~ñ âmlmëWØÉa?-.e3ï@šy%—Û±j¦Ž £Z*%·û¤3âŠç䨑G@BEÂ4R²ÛeFOfy9ɧÃTÈHøwš‘zãY2ï@šy%—{1¨™:‚Žj©”Üî“JT3ÅUæ$,#óCi& \H¼œÒ ’Ào'À¿Óߎ8µ dÞ4óJ.·¯ÔLAGµTJn÷I%ª—â|vgrúœyÔœ@›ŽÝkÕmœ|%ÇLþŸ½CþäçgNȰJ–)o†‚¥3 ÓäÌ•'™r$L&(åÙÌÍ-g-\­(O鲕z޳Ö6vÓæ.—Í–Òû—l ¿)¥C×>UkÖýM•³Ú´PÙ €x-xøØiV6¶ fN †|%J•­Ó Ùò…3Ò.+kÈ –ØD_AY\N9ræF°rdd¤ÏoÝ»}ýr\œŠw¯cllÜ—*ÕÀkÚ²ý¹SG„ý÷¨§t¹JÞ ¿sóª¢j}¼½ðUt6#¤—¯X­Y›Ž§عuÞ UÀ©p™òUàdÙÒMß…³'éêèXZÛT®V{ÈÈIÿ¬ZøÙóCº ІTç$ÎØŠÜ½W.Ô¬×èê¥3Á‰^k¼†––v—žòÀK%?}üphß?_HÜ¡[Ÿ €€€Èúß/aý Ó—^FàÿüÉCŸ^ÃÆL±wpôüà.÷ª°Í–£mÇîÙ³çD©×ÏŸîß½Zc©i«öÙsäŠŠŽºráÌ­kqÕ…‡…eËžÓÊÊfÇ–µøsõõþ þò•ª—*S>4$ØÖ6»†–æ‰Ã{Þ»¹b¶x©²qq1«Ô|÷öÕ¡½ÛÇN™³wÇFO÷úzúM[w(Z¼T\lÜ÷Ο:†Õ TR¹lHPf­´µ´ŽØ™R##ôePo%K'Îý‹ÕLðâŸ>LÉ:‡ý?t•¤b I*î/ ÑPÙ¡FŘؘWÏž?¼·WA›| ué1ÀÐØäå³GG쎉¥;—©X»A< ù³§nß~>Þ‰ª #¶ 9HÜ,-¬ŒŒMn^>³-¼}õÿZ[Û6m᢫§?~ʼÈȈeó§+Yº~ã–0¼ÂBBn\9óÚEÙ<}ŽxôàÎ㢹›"ÅJÕiÐdõÒ¹zúzm;öt*XO¶õóó^¿r*T¡*¢ª$ÔÁ-©K·üŽ…Üß½ÁƒbÅ׬˜gnf9vÊÿ&ŒÄ¢%œ›´hgbj†©ôKçNݹqNB™r•6­[&t_Õu°Š—t_½|ææÕ‹RMà¦Ó¢u§ì9r`7,«#T'¡”ŽŽnãæmO;xçæáä‹§^âÝP fîú é¯À—/ž{¶­ðWb â¦l—-œÛðð0!3j(^ªLãíŒ Þ¿•×!?˜¼~ùé²d:÷¦H=áêç¿0T"cc¿ýrxß¿¸n4n¹½–?*sÄËR8ðññzöø~ÝÍïÿWbÅ$þå‹Ç{ÿÝתmçVíºl^ŸÍkˆå(Õ{ÀÈ!#'ž9qððþ ›¶nÔ¼ÍÖ +A¯Kï_F,G¢8FÒ>ƒG8¼£¿®žžp­/QfÕÒÿúc áï71ôÈÑ©ðúÕ a÷ÃÍèÕØüY=¼] Po¯o—†‰r‹¾ñ[¶³´¶^4w²ž®>n0a¡!×.ŸC:î(kW,øèáV¤hIÜ`æÏú[ž#t'ðìñƒ§ 1?’MX±OÁGøSUÂa?ØT“5-i‚ñçNõpglbÚ£ßÐÊ5ê\½(z¸8>Υ˯^>/>.®g¿a˜Ë»pæX‚Eš´j‡Ûñ×/ŸªÖ¨÷`å’Ù?ß#!ê}Õ¨$ˆ%q ðó÷…ÉØ¶sÏûwo|þø!,,Y||œ<¶¯B€~ZW‘˜.D€P®Üyû õñãûOßKå„”ú·\¥jººzs¦‰‰É™Ë¾juQ–P':&êéã{eÊWv{÷§0O„PÒ/Ÿ>bÀÏœ.zÁúøÁÝÀÐî–X`૯o`—-ç¼™[ÛÚ :EînoÅ÷/°¶ÜõìÉØÊÝû ]¶`ZæaUý‘T*GîêP  8ÒïуÛ_?{FED¾xò 4$èé£{±1ÑïßÊ•+/òdË–'îCÁ?¸¼%K•ûYV¸øo&#pdßNt:þű²ÞLø#“Ì`hh¤èªˆ‹ÅN)ss \B¸ `)çòI¼ç:R"ÂBEKr þÂxüpŒû\B ­hÄCª÷ˆŠ÷üàöãû·Â…‹'¤'|þ»È;—­xöÄ¡ðÐPTuñ܉Òå* •|ôxÿñÃ;¿yýC˜áeÅ•ð€ÒŸ@⥬¢ÿý÷!úëà°ŸÎæiH|ÿêîú÷å @ÿ›W.äËï”(¼†ÆÕKgCƒƒ0q cZ™ H¯T¥æ­«—¾xzàF~ãòys k+a¬û­*cÐ^»tæ‰Zµé4eö’>FXZZ%´øË}áÝÛ—^ß¾`$ÇðþìéÃ|ù Èæ‘ºãy\L¬‰‰‰­]6äÿòÉ#&:ê·ªƒÊTܹ¬®®.Ž'óðÞÍDŒ?ï/XCƶ7Ü2p·òúúù§<‰wLWa†râÔý;7J%ôŽøþ…E<¸OÞE7ÁµCÿ*RâwkdlhŒ5"\-òÒx|ÿ:?¸s.ò(3ïß A$p|üÛWÏ„ÌÅK–qs}…»vé¬ÈLsä^éß› WÑOÃ@ÒBˆÇŸ•Ž®®Ž¶Ž<,i6ØT4~«l@J„÷<ºw«^£o^=Niji5lÒªX‰Òú†0º;•#amA B,ó ÇÑQQzú8¶´²ÑÖÑ5a¦Ž @ü©«H}VóÇÀŽ?ºÿßT4ojfªèª8~xw£¦m±5'88èÊùSð-1‰âëýsµZ¢= ·uL?ˆÓ‘ÇÌÜBn6$bÁô¿ŸŸ×¼ÅÏ‹FÄîIïôõõ#ÂÃÕÃtPöÓ¿S=b¶i+—¼ùqKÅç¿ L´ Šøûù˜&ŒiVÖyó([±ŠXA33Ÿé o@€vˆ¡!33‹vzºtîµ~Õ"©vBÜ Ik[»ì°1q~ûÆ¥d ']{Æîƒ{7±=7a>è7~<=܃üK”,ûá½+àcáBª1ìv¨×°y“–íõpòÈÞÏŸ<$3 LK|sAÐy¡ìÅ%ÏbÊÕ>o¾1“þ'$êééùüñ7*“PuhXl9#cã°PÑúŒÔëTBJTT”~‚E§ÄVxj ™qÿ ÅŠã»qM屆 2ý{SFî¿fæ–0bWn†Œø»è†Éƒ1“çˆ7¯”-_Ù©p±V. B§Nš¹8™o1 ð÷ˆˆX"xæwëˆE€2*ƒ­ÛÛ×AXÿõƒà„6Á¤®Q§aû®}–Ì"yKˆ·|,†HÇÍÎÍõÍŽÍ Ÿ(ú;TÚ9Lÿ’Îîܼ,®_X+ZšóI‘)ˆ½.ù µ N,= ÇŠ.ÈôïM9&$aßàwWEg3Hº C€„¥ŸzÅkÀ»wëZÚ EIñ˜øG_ЦWã5ªV¯'$ ‘ÿ Lâê^b…^ß¾zÿøÖ´UÌ·âpyòæÿ¹ø³Ñ_‹ð¬úÀ„¦ 4µá4–(Y¦{ß¡/Ÿ?Á`EWE‰RåLŒÍ°A WnXD sž<ùðH8m-m#‰*¿êà`à™Øæ\º"¢‰Þ¾~ŽüØÑkek'KõÉû ·Bµ¯vý¦OîßNÌ“pÍË9æ…J–@Âmkcóß×ÖÆVæ›+É‹Ù9ìÿÙÎUÒº†è9º¢ Ñ7a04ôùñ=:f±n…Ê5$Ç«µ›"Ò²Nƒ¦¢"ñw¯_©Q§RŒÆúfEF¶’æTtÊ ,núÓ1•ªÖúèáŽvC‚C`@C Aè‚—ñ±qÈ ÁB¢TžoŸ=1‰SúÇã„<Øñec›º„‡„ÄFÇÄÆÆ¦ƒRïÝF»ÐåáÝ[ÿ5—ðçÙÀÿƈfúCåþªnؤµ¶–N¶l9ËU¬&Ú²ô„²ø¢³ò9:áÎ2:Úºë…a«¨/ÕQOÝߤe;PÅ* úwê6í»' öS6qñäš‚ b¿xúЩPQÇ…pG®Q«‘‘±PÜ òô¦¤vøÓÔÖÓÓË–=ö¸b?¼EÐÒš.\iþWe+ÂE(q¡Š/;Y±rMÑýÛ7БÃÇMÇ’å«¢MÜÂe)y •˜ø3>~û†5M[»àÑ+øÃðþñ[—$›å¿jC@|aµëÜ _ìñöú~ëÚ¥›×.‰2(¸*0üá1£¸N0+¿çÖ„Pœ k—4oÝ¡EÛ.X7À5ùù“hUTªñODí;8lÖ¦SP€ß¶«ÃÂD¡;÷îÞèÚkàŒù+_¿z¶{Ûqñ“GöµhÛ pÀS€Ý¿}ýêáÊTT¹Úôɤ""Âoݼ!)|âXúët]2XÉlâkžÃ~¹6¤F¡¶{à+Ȇ‡áàúºô)î\&"<ü½ÛÛÂEŠ‹{väà‘ŒŒL^>|éüI¤»¾}‰ç(´éÔÃÚÆ62<{XŸ?}(ä—jE%º‹ëŒˆŒ0·´ÂܼèÉ0aax¦ÍÉ£ûqsç~¾ÞSþ· æN‹§uìÞ¦EPPàÛW/„ñY*Ï«;÷è7rÂLÌ á±lxNê} &xPV”ñŽÌ.‰!¨D¡)Dþ~xÈDnû|ÏŸ?3d†WU¹z-ì—ÅŸ#ö4صE\V8€œx#Í„™ 0É…?4a3±PM½a nví[µïŠÍmxÜâá;‡FâïÞ¹Z»nãm:!Ûµ/ž]6’ÙÄ?“c Š3ûúúìݹ©]§^†FƈÑÂtlׯY¹äéM¡_ð/Bæ.[Ÿ â»V-žý [82öGÓ©p EâM^ 5õööQ”éúzaáÏŸ>Â{”dã)H&;[›>àÁÉ̯Úl*W/RÜy[³§ø!õ#€g×V©ZM°ö…5ú„ •?ƒÜ“Dç/_<_Ò¹,Œ ""£$QpØÏøÆŸHÓÂ'óJ.Wk5SGÐñ(…ÍÓþ·tÍòùØ¿.u:$þÅeõ‚§ªhp–ÌŒ¥Ý³gNb5O¶!Ee+ 3ªü@Ú ˆ=ê´W•ºRW–¥H ðöMëHXûÒ"„’§‡ýäqú¹þø@šj3¯ärUV3uÓO©BEKº¿{5v¤arÝÛ ÖÿvÒOq¹“jUè¨V0ÖF$@$@$@$u à=›»÷â$ÿ¸cóE{‹³. 4h®2û-Ó ‹’@"8<ß!áóGˆà]†â×þØ( d"êï4!úS¢þÙ4-Zg^Éåj­fê:¦§R÷lÃW.ÛôOLOÅÓA;•9/øjÞtè.6A$@i `‘ðÎQU}8쫊$ë! d=ÿJ•9yœT!ë  ø]‚ý•=Ô!¥­rØO)1æ' 4øê)zMGÚ?*sÒ. jÐ+V/EõD½ü“ï%I‘¨ÌL$@$ „€žvt\LœŽŽ¦–Žè9æÑQ1ÔÑ×ÅóÝc¢¢£5D¯å‡H€H@%2– •X @&"`ÜÙÈ5Ö;28,ÎÜVWÏÊRÇÒú‹û×èèk»œ!þ¾Þ^qfZ÷ J‡Äe"½(* dXt2l×P0 Èê½Èÿ8òƒ‘‰i||¤¯FF`´F ž€Gî}w÷ÄN¼¾¦[PõzN‡ËD¨$ üfZ¿¹~u«¾R•#ÆLR7­¨O,\ú}^‡4T™Šö8¬^æ©–¸{ŸA›¶Nuq©‚ÖÖ6§Ï]¾vkZDR•0¬'Õt5£Œ"ãÜ gš›Ï25]di¹«@}ŽŽ ø§šÙ¢¿c7]Ê‘#LK7.,ÎÄHõ÷¬?5ªÛÙe_±NÎãMªÕ¬;dø¸TódA¹Š•,=yÆ|¹§˜HY“@ÆZ*§™Øšx-¶ÔGÉ)©œ£ÆOuÈW`ô°¾ÑQ‰ï°œ:kQû¼xˆlDD({÷ÎKçÏÄÅÅ¢`Ç.¢MoÙ°Jº=þÎ„Ž 2wÖ¤ï_¿Àìóõñž0fˆðT`}}ýÅ+7jhhíßURdÜGüwnÛ $:,}â0‚ž… ²nƒ&8ˆŒŒðüè±gçfO¥Ë–ïй×ߣKJŽK×¹lssóÀ€€ËÏœ=uLµzU©^«BŪËýO%ÕÞ»u-,°ûÔñCB%€ÅˆäëëýüÙ“SÇ…„¡*¹=›j¥2]ALóÇjiÄÄÇWstlR­Úü;sæÌill¬©©éiììœ-8Wa´††¡¶¦ðÞb¹:c£&­òØ;DFEx¸»Ÿ<~Èíݹ9Ó’(y…üðúvòØ¡{wnÈ­Pµ;r›`b&%`ii„Aòã·VVÖ’£J&UŠb‹ HéR ^â‹û©xaóÞÍU|›€iúÃëûÑC{Ü»ýûÀf, >.^SSCW+¾@=D¾óÖÈc«k$ úÔ‰‹t÷Öp²ÕÐ×Õ|öUÖ5øþœ .ZºLÉÑyÛ¦5·n\ƒµ”¿@Ávº9,ºvåÂß—5«ŠÀö-ÿܹyM¨-&&ÚÐPtIàÀ©P×7¯p\¦|¥? Kk©ÿݺaÆœ%÷ïÞD6]]½}íß³##Xÿ³W¿Áù7­_ùÙÓVKϾƒm¬m © ÂíW·nZkddÔ¶C·CFO;T.Ì÷o_8w2,,4[öœCþûõóççÏÉ͙߼~™j1`jhj ’P‰•µë›äVwKðö%¨Z­VhHH•j5ÅÎâNSÆ·{F8n:ºÚ¥KWÄ’û»woýý“`Ë”Ìkùب ¡VŒ¡ß{=­Ðc:e›h¼¸¢U¼^¶ç?4tclñJ8™µ_øTªZÓÓÃýí›W¸H:qñ°býýýÞ¿ãõýÛÔ™  rw{+.B;®_»X¬x)C£÷nownß$î!!ÿÚM»&ŽŠÚðÓ¥S÷¸˜ØƒûwÂLiÙ¦CÍÚõñÔŠãGö÷è=phÿn˜Ç•Û“I 66&::q%G\äέ땫ÖX]·n^kÒL:¼$0Ðïî­=û š>ytó.~¾>n®oÆMš•+wžØ˜˜ÇïïÙ¹E¨Ý}êÄá:õÙ\¿záÌé£}ûsÈ_ÀõíËõ«—bÕ÷žCF,TXK[ûƒû»[׋çlò;ì3`˜¥•Í“G÷¶mZi³çÈÕ½÷@ÙVÄÂçuÈ_¡Rõé“F}ûú‰ï\߬[µxòŒ…°æ¿~¥ÀÎÁµ|åâ™j5jëëÈeu„to/¯ˆÈp»ìÙ5žý’’ôè30g®<ð®Ÿ>~°eãê¡#Æ¿}ýâüÙ“B¾)3ž:~ðÓGÉ3çƒ@ùòU Œ°˜pþÌ [»ì.»Ã´·xMDDøôI£QÄÌÜ|ä¸) öô|¿aÍ2??_$Ú;äëÔ¥wÎÜyðç°oçÖW/EB4oåR£v} ܼ~5þÐäýýû铇[»t†R‚˜&¿táÌþÝÛMÍÌ;uí]¤hñȨ¨ËçOŸ=-ZÍÀùmlìÖ¬\ôéã¡T>ƒK9—+Z¬d£¦­V,™‹¾èйG©2åð™[·®=¸ôb!(…ÏŸ<6®]!‰FWO¯l…ÊpqÆŒf_ÄgQ622ÿÿn]obbÚªmÇe gK–•<ŽˆŒøèê^¦\%¬ Î’¥Ê\»r!¼¨0qŸ¢»WüŸ½«oêjÃÔݨQJ©PŠ—:-:`ÃÝm¸  îîC~c耡n(PÒRÚR÷Ô%Éÿ¦§„,¹I[*„rï“'Orî¹GÞsÎw>=wç†5wwêܘK+ùÛI/e2àÙ³éüy+mí0ÇRCƒÉ¸ú”ê è]н/oòFl|&“’ÿg³}áÜŸîß" øû<ÃoG羆A–ËÍɹsëú½Ûÿ >6ŽÎnœ¬,K+›÷nåçå’§d¬q±± 3äñƒ»CGŒ33ƒi¹±X-’kì3`ˆ‹«‡º†fb|&-)Á–Áf±0°èH"‹ÍúqÒ̶ŽÎ™é§þ8J¨\ßCa0ÔÐÔLIN>úxØ›×,{ÛžC;¶¬#ësuˮߖ,˜º'Öà¯ú/ˆ3H†LEUeÙ¢Ù.®ž=ûÐÕÓÿ.úäï‡RS’Ñ;Ip¨ªª :ŒWÉ‚.=óõ" P@°uш1ᾈÏôÙ?CꄪÇNœVcf5ž®H 29AgÔÔÕ££" –‡’Qàó%¹KÊ€yB ÚìèìîÞ¾ƒ¨ ¬?üŸ?©p!?ññêþ}ohjxXHA~ÅnNÅEÅÁ/ll› Ñê¾Áß‚ &³±²27?߆ÍVHL,£øx…GAø­›ö&CG!@þï CŸ(zÄ9°  ðð]XY,š@p} éÙšÙ·Ú¹eͱ#û01@´Éƒ2Ö8å¸À­ô¿d-bkëÔ¶i³­Wff¤¡1D¤¬¬ éaÙ¢Y `‹—­Ã¢#Æ„-Ú?vG'W,¨% fÂÊ—˜¿qÍ2NN¶{ûŽSgÍûeþtÌ¢gO¼ÝÜ;ÀÉ¥}Ä›zÆýä›6³‡©fö-³wçFÌ®Ý~€uf4Œ5%8ƒ‡URV^4wª†¦¼‚ɤ$€”ã[K‰ ™ z[µnGª€ shïöZªŽ.¶¶°²±Ý´njY¸´œ¨~íÛ88¢Q~ úÅŠ•PóUÕl¢Žba´u£”&¦EFŠ™ ,׈im¥ñÁX[Vbu…"%kVSshó©«×ndd N"11Áõ#q—Ì ïpm’é”ûwÿÅャ‹›‡ŒœÂ[NÎîÐUÀ¸Ò|ýò…Ê9£ƒšÃÈÓ¦­£››ÀÎ#ÆbŠ–yòØ!û­n\¹=1xn8¢`X1úPZ 8°òëæ?Wrs8ȃbÃß„Àù¾àÿÀ^ ñõ~F>ÜWÿ>oc'òàõËàÑñ,öo ‹&È,£R™†–&GBK—¥©©E28¹¸mß{dï¡“àŽúêZ´kø}ëŸ+k]tä·Ýàã ÅYO4»‘ø{ø›?¸§ë˜˜6ijnîýŸ=!¼ˆ`ž—) Öø±ŠÈ_?_/(ÞðˆÏãàcù*æý[0ñ(‰°Ðà–­¸\®’’¢¹¹›Í¤ÄP V VßÌ9 Á'¡@ˆ",,á x‘ùÞ¯¼Æ§ÞÄÎFX:ÊV9»y^ºpî4ÈyãêE؅ـ~K> óàóg¾På>{êåäâ-eÉp-ƒ)í.y$ôõKS“†Ð5¸ytxâ]î«FYšhbvEô§ÂêMS±/‘ÅÅaEEQ\n¦†F†º:¾Ã­òŸŽ‰áp ã-†‹I餡®‰ eª$ oB^%ÄÇñx|Ì1ÿ~66v$OLL44èø-:1d¬qÑ’Áîcmî>p|Ê´9çNOJLV‹èSž»bzc-ÆÀ¨ˆ»i/?…ÍA>XDÂE‡ |*Ôs?_-ZµEf,10÷Hôñzêgb"X JN®í™îg ‹ŠVZo~ß»ý/†ƒÕ±Kw˜—€^ݺ¡£§khh$  sõÒyqÀþðîM‚†4XgXa§Ø¹y­P§ƒY#x†:k]QÍ"FƒˆOèëW„+_ÈÀ€‡©ŠÜ!\ ù†æ‘¤cc:râ¯ßŽ™:cÞå‹gàP³­-Mî\€ê«%+{E›êñš7Ra5/Š{iaÒ656ùm‘Qãü|®ö ­&¹ù ±[É êÿÐÐ`' ·üžx¹{t– M'åž!,““M~ƒìêèêIÖ%™&$’é‘|„N©+Ï’P.W#+¼žø>‚ò[_ßê78mK+LaAA~|™³––Îc ¨f±Ùlb>&æ|äÈKJŠ²Ëæ.lÄý¡ZýkÛÎ.apãC"˜BÂ:gg—‡iéèÊ®…›ËÉѲä½´µuFRà®söÏã……ùij\,§è_p¸2ü|ÒúC¯þNDïÂjÀ ‘ð„Ádþ÷úå§¾! @WêêîyùâYgwÏ£w‘üpe!Aiø[RŒ^«RVš•õ±³ÅE**š¿eëµ›v“üÊ**11ï ·€WÏ€Á#ŒLÌ‚_úŸ;u‚,Ia™`Y¦LŸû:8ŒŽ ý°Q `ƒd€Øð!ŽüÎþX#e{Ò MFZùŠÍ^W¯Û‚‚J±Ná>t­LD‡à4v³uÛv”nXøàÏ0ÐJJJÒ `Ôžùöø¾oÆè\ü¥åM×ýɧàY+ól½ÊeƒÁåñ\-,z{zn9sÆØØ˜ÇhÄp8œŽâ%P,îš›—ƒÅK $=‡<¸ŒMÁkÃÙìaN9ÿI )×8„çõ[÷’ÌSŽØÖ¯\\RZ,ŒßV‹° %e¡R”Ú[ð¬ÎOp„ÂE—™þ)Ð[òÃRѵGO=—‹É­6!6 vÍZÂt`bfè'Ù¯z’õ‘8CyamÝÔÍ£“°SZ::))I’à€DÃ(-­<<#õcœ†4Xg(ô >ZèÕ ]ï°Ñã $¿uÖº¢A /¯œG/).QÒ£pÖ20¨ŽHþø2²qõ§,Š?W‘Ò€ÁF4{ÁÒÌôt84ÖH;% ‘; 8AU!)añ¼ØbFøo~BƒÁŒc+òXœ0%…0X‡al%I›>º‡m ,6Á_–"´ŒÐàBa Öy(ŒLàŒ+ Š0ÅаQš40‚"P,'¸=¶b9O'[°£@{ª§_ŠŠCFáô­Ê#—/ÐìJæyõrü„é™™™p§A@­dÉ”AÃFcÇ… ¸:ø’ÂH2e ´kð%ݺq|ZÀ>nÛu˜r’g+¬åMè«þƒG@ /ô‚òÏØ¤aXH0)¡¸¸¤ª¦|´ŽòbGˆª€š0vö-ç,XŸûÄëÑÔYóñœDT$u )GRq. ݰÐÉxz2|à›Øë>ýÕYôYˆ%Ø!ÄŒŒ40C+—Î…~T¬ ñÿ-À€‚Ñg@d $™Bhê§á5 ;cÎϤ0+ÜÛw¢àܳ€$êOxêûhùê-PI†‹g-û«BËÖî ü—è ŒSw—W……Öºº JJ`†Àâ:©=b)§{±”Ë‚€¥X@ÞÁú;:·ÆRÌyÌÞi3ç_ýûÜ_/icœÊà— ”›rƒ¹œD,Dð’0å `m8füTâ÷UÔ.ÄhÚ¬ûvm®êè2¿8” È%WCZ z7uDëµhãP{€Ü¹1ÙJL&*~°ØL–KQ‰eÆR~—}”±yS²_ðšb0+—Î[³|>¿.™líS<ƒ=EGOÏ¡ |^QN‰!¡¹¿Û}˜ÍMï>ƒ`Š›Fqqï›·lDPŸ6Nä.*prH6T>½ú þâ3¯~7F·[×9¸³òÝ„ª‡´`›æÙ©k•„ÖÜ?ùî;Á2® kC‹g>ÓZ`mc>ç“bðõ~_®ö¦.œñi–b®ª¨¨àdIp˜¢B‚±ëÍ›rùAøx;'WÌað"h9¾n½{U\\4|ô0PrBÂÇ9øi訨ÛS–úÂϱÑήȆµƒ¾àÈ6è–Øz|à$ZÜ{\Úw8¸w;inÁO#)1~ððqè#G€¦ŒJÅnÁ ö¨x±º âç[ì„ÿ\½H¨¾÷ïÚÚ¢uEŠE³aUÀÆ ª”ÌÌRE­ü¾C3m} @pZ(u0ì3Ïëóø> pf<…‚‚ʇ[Ú³•©ãôì3§Üà=™Þïãäri’tpŽžæYµd>‚ûô‚í sþ‚ˆ?+q‡#ÆL@¨",TÇ:ÂU}àБ`Áeלšš¼cïт‚Åó§Sf†Þ}ÇÖuCGŒ5vÏy}êäÿÀ@3ÁÐȤ´´8"ü͹SÇEŸõèÐ'œ®øø^ž‡nÃqÿÀžmC†Y·e °21áò}™(«&^8ûǰQÖmÞÍãòžø>¾[vØ‹´ Æ"ðOXþB¯¤àW™ðYBœOzZ ¤—C¿ŸÅ²ºaõRÑãù„žN(üıßòÊò'âF¤ÕHÒAðÁÉéiiÁ¯íÛ‰ÈNÙ|#wqÜê,p,Ff¦Àà[.·ŽVxÝ/œ]R— ö«|§Çëñ}øSá=ÃGþXT\ô>:Ь&„òOž6ÒTïÁ/eãY¥5.Ze-bkGz#xÑòuS>ÄîÙY~ e“^¿ÂI¦8.&+3ýÀî­pòÁÇ×ëѯk¶bŠ"_”î¡wˆl6mØG–Q–VŸqÂØùÓ'ÆL˜ÅGa~~XØkØ=€%8çÏüó#u€“œ*[·DU’«’°6 ·°oç&0!Äï2€µMSràAmTG—ùÅ€íÒ l4ÐPã·ØnŽc‰H¶ È„œ¬Ìg~>¢ljw„ac×RZ¡plêñ}¯ÔTY±)*ÊJù…8δ‘ °š—RóïªTBqÈÝ*å¯Rfœî´w×F(öªô”hæ†æó­˜ÿÓ¤Ï.~F – îOÖ,­¥òébåœÌ4÷ö)eT¨QD¿…I†îÝiÕ¦ªþ×±¤fÉ>ZÒùœï“]À`Ã&*|ãÐOò¿-[Àç)”vžàøðxäŒ|â\Ç­{GœC]Çõ~½ÕÑðë;ùo9y»të‰Ó k©© ±o¥gÑ nݼ¡®®)­òeÖʯ(&~(•_ œ NÞ0|E§›ú ÷§ÎÝz<~xçé/ÝM9GÀ›×ÜÑ)8/¥D×PQUOGIÏ ñ}àõ›˜‚¯MŠIÔf*hë>’º“Éykµy6Ã-—\«µÔ§ÂiXŸFSÞúŸ8ľ“þîHùi0-ÔðXÀ1o{™8í'1cå%Z%SÃÓÅU¤=kîâ°°àúzb`u¢Ÿ¯sòÔ³]M‹Yl³˜ÅLc—(ê(hñÙEDJp Áë'¶Xê)LuÞ^9ª§ vÿ¡ïí¯"XYŽš%ÇM¡  ÎWß4]]½õ[öâä7®|§ÀË— ÐW?øthhhäùt’cÀè¦ÑÐÐÈr礥!ëLùn À7‰@Žð|Ôšè>MökEº * P…¼²²Ê— P{=Yg âxP&ëÓËžJK |2Ìeu޾G#@#@#@#ð•  È(Æ›þØðb³ptI±à-ìleE>—WZ\ÂeÐ:¦¯d éfÒÐ| È—ÄÔ´?½É(?»ü~IM3»ø|Á»qihh¨e)P¼êk@n#ÀGTŠ8ÁÜÔ¢œ|ž¶¡¢’ž.[S?>1¡¤¤qÀ¹¹Ùé’yš¬]ç¦ @õ; T¿ÕÛ÷;@“Ææ Ùå§|²Tt7Ì·lpÿ\¹x`dddKQýñ§K   øÒ¸'XF¾SÓÐàó Ó²²K²yxA˜‚BbT,¾Y|ev§]·o‡/ÝXº~ú€€Ü½ ¯ïN.»ð+ªëkš¶PTQŽ*~I“þú0çäªCGMêÚ½ì&iëèmÞ}Lvžº¿;gá*ûmeÔ»dõv KkêæÖ¯ë÷˜5´øìº*3@Ÿ]xåìÕoXŸ#+™ß֮ŲÕ;Öm;dÞo,mŠÀo+»J>þ­ecñ‹ÔŠ¸Ñªêk´µ×jjnÓÕ=mm}ÞÊꢵõ!­m‹¹GÔÔä3yù\5Š·€U±vÎí§Ì\XÍB*ÿ¸‹{§§ ÞD_u‰€}«y‹×Ôet]4rŽ€ÜY„xª†5ià ŒÔÏIx-ç ÒÍ«¦Í^lnÑdÕâ™%%xGP­_ËÖîüóØþ˜wQ¨©  ïÊú ;•ÅÜÉͳ­ƒëáý[+û€Ì|Ï}ðb™Yäîæ}‡\¹øçëWhÙÌyËïÜøûÅ3ï*µRtúUéÁ¯43—©PÊç{XYõôðØ|ꔩ©©ºº:ŽTŽUÍi““c”cÆQ,{ƒ¯€7S_-Z·ëÒ­·iC‹ââ¸÷oïÞ¼úîmuÖj¤¢–§ÌExWtZjò¯¾xRòèG¿E TÊÍá` ¡ó &SGW/3]ÖW¿EŒ¾æ> ©éDxè+lˆýñìÜýÈma!/‘Ž÷s­Ùr`ùÏS òqKUUíÌÉC¢^°d=^ï”’ââqï/?7ˆ×,*ò+¤Ø¥EaI¤£Ã°êÙÛÜø!P³@Ð¥É3 Ö¶öùy-Z;ÊÞnñ"6[³}).*òõºW³eÊUiµZMu0*âÍgRÁ@ ég—ðyêêé e×ÑÓONŠÿ¼r>ã©/ÕåÏhê§Gø <†àí¿]Úµ{UP`¥§ÇPT„œÏÂÅuV}ÈTN{ÌTa"É’Æÿ»¸wì7xÌõ¿Ï„Ý[\ThÓÔÞQð.žŠLþª6>##mãÊŸYlVë6Î#ÇM}ÿ.’æÞªŠáçå—gJUùéê@/wüðnìV#ÆL±kÞê·Ýâe‚Rùòéœò€@ffúæÕ‹HK„î´n묬ª²qÕBG÷®ß÷ÕÑÑ‹‹}û×écéi‚hrʉAÙøCÇêêèŸ2§´´ôÎ?‡¿y½tõ¶_æL@fTtïÖ5ÏNÝttõŸú<|p÷úÈqÓY4‰Žzsòû‰ßšY£Æ16m˜•ÓADØLX†Fó~Y{ãÊ9´‡Ád CVfúàá?jië>õ¾íòYÒ¤Ž]~ðèØMYE5:2ô™ãyy9H‡^Ž(êjêÏŸz [&}C£ó§Ž"”bÃŽ£‹çN,)¿T~506:b‚±™9^N÷ú¥ÿå â.iƽÛWáÿ{ê÷ƒÿÉ?j¢±IC°Î!¯ÏþyxÂÔùQ‘¡ïß$yæ/Yõj‡t%´i뢢¦æóèî£ûÿ4èÓ¤’²tØE……ÛÖ/Á#Xt#ÇO‡÷ú•ÿù?âÍNH¤DIr  ùÞík=û uvë@e‰÷£»×.ÖÐÔ0d ¸½â’Ÿ‡·Üý°ØóóóMÌôô Ý#ìå€KFkçœìl4‰Åd^ƒ{õö¿ƒ;`jÁ®°}ã2æ  ×½ÖP²#tŠðý>^÷¢#ßÌïºVKG‡“•% |¬÷l_£ªMÓæ}Ž8úÛvØËÀ©›øÓÎ-¿bÙH›’Eý}þû–m….@0׊æiÕÖiïŽuàqçÿ²ÖÂÒê™ßɤróèüð޿སÎüåÒ_'^ñb°¾ zž›ËÁnfÑÿ¹YÃÆÈ¬¨¤hjÖtê(ü$K¦S¾`t ®Æ°‚¡OINtpèK$/¯‡wJKL‚»gãcßc<¾K š瘴‰!Y”ì”w®C‹I& ªbᤂ¡ ý÷u¡vðÜ‘!ÍìË©¼h±ÿ^¿õØhP„'^÷ v’áÁŒlè£×ÃÛpìzíÒYûBçݲJ‹ Jàþíë„í“ÝTrå ˜áœì,h@E¹Àw’€YAN}ðŸ`Ä•qK!ÁA:zFÆ‚¥äèìñ2ÀP äñݘP2ÌX>”íAk!´×AÏš äJ”*³UÕÔÇO,5D3óÆ×þ>Ö CóU IDATÖûá(òIŸû‚û—ìš´9Í øoäú ÓhCòMæþ­k@˜G¼ ‘ìš´™&™)ÒZ ™çñƒÛpVÁ(`À­œòqabB\ Ñúƒ—_ ùA¬Ë-Ú8¾ ÃÔž>ï€ÙE8É\»tFvá_ê.) ô‘ÅÅaEEQ\n¦†F†º:¾Ã­òŸŽ‰ápJ1´_,&¥ ºº&$Ìë’íÇØÁÀ l1a^>obeKòà/1‹N~ öâ©wQQfÔ­[ZSÇmCÄÚ¾ÿä–=¿ÃÿçâÙãÄ€L¹Ê`†265‡Í­°°« ¦$R»’’ÒÄió OÛGsÿ’£&™òyä½£ë­ë—°éceÁÔCŠ•FÍ$+­¥PÂC{6cš‘ò1ïÛ‘ZªŽ.¶¶ðzx ƒˆ Ö~²Á¡FðБìÂ6€ç„¶ m;·Ê´jøè) 5°ê;»v¸þw¹w@e¬d¹³¤‡Ý'M§ å•ë˜'DZhkëpy<|«k–š“S®69àp²IaÅÅÅÊÊ‚÷Ë$'Æ_½tCcÓ7¯ƒà˜‘“Sž§rÕÒ¹j Gˆð2þÏ}]<–)°Å.§Ü% ¬jcK['WOaM-mðUÒ&ÉM­¶Ž.~Ú» Lƒxé"ÿs>Nž’’"ÑI¥¤,P‡ëéé7jlµø×-ä %eåø¸÷b¥Á7“$bЍ¬¢ŒtX923Ê÷H³`n´ttñIOK%O+­ü†¡©© “ˆE6‹‹8D¡0%Âfˆ¶¬=û ·dm‡sÿö5HÎà]‚ž´svÿ÷Ú¨Kÿ<~€äÇFâØð·¤¸ˆ,%±Îâ/'»|hÐYMmÈ”(U¸¡OóãŒðЗϟ< àá?ìEË7’!º'%~ ¿³96"Ü’6ò>òŽƒ+·TIYE[KB…ÐwSšhï¤Í4Id´†”—~”P&ŠÒ¥NHœX—!·ÀI]ølFzª¶¶ìKjꔽ ¬¥.Á‚ó (Ý]-,z{zn9sÆØØ˜ÇhÄp8œŽô=ˆ( ·`øà„JÊæ­~è=ÈÐÈ“r „Ç—dᓃÌp´ƒëχâ Åb€–¬*·,üi<ž¼ëWÌÃÝÞFÀ·‡øQ®2]NV†¤'”Šl¥­–Ô¸JO²Sõ#å3È;ã#*A€jð[5«3 0{Žæ‘áÖoðhÄiiµ„®¨F’Ò’%òaÅ®·{ëjR~~™/¹°äoݸԳï¢€Ýøßb³B”l¹ÛÖ-©ü¾/»drWî³æ "¯û%­,ÎIÏÏ,Ñ2¶-biÃ÷ ;ê“k¬°«p{4|ÜþëH¸ô’U[Å÷ é¨@÷ƒLÉCGMîÞ³ÿÅs'¤ç¥ïÔ˜úmÛ¹‚·[µiª 0|µ¡àoƒ² |[Às_âì+ÌSáÄ€™ø?ò>'^Mÿ³²2¢ÂCáÔ[A>™·¡/G )ÉÆ À;QM m’¡¸¸l=ù ×É‚{÷Fîø°'Àgâqy)]ÌÎÊÄqØ¥àm2iÆÂÈ0h`³°4ÆNüI`àrßGËŠ­äW:i(É^ƒ=ûk.<‘)+3­° póÚ_¨¸(оU8D¡Ëædj|Ô ]SK+;[\¨ œi’ø“i­E!FâOIÇþWÂÌúú D|IË»Œ§i™ÊŸ\ºzÙÙzìRÄ&^—ü'€î..¯ ­uu””~ƒ0×IíK9Ý‹¥\ U„?‚Ð;8ΉëccbÀ§ÿÖ‹þÏ|!Ú 9Ž:¤»”§ 9º´·mÖòÀ® °ÈA_¹~/ î`ÁÌ1’ Aú½zñ4dƒ¦ö­àmE¹Ê0èZ:zX¿Äb&,ä]tdø›`Šìߵ޶6KbK‘RuòŽ‚Žô“ðgZå…WžšQ´¤ÚI˜‡Äïv‰¢âBâ 4nÒl¸wV»lºyA hb…–lL‘p©Å)’·(S@=¼oÆ&6vÐÇQæù¼D¹sÊTнçÿ^ìÃU+wý×7T³hðÁ7(2>“Úÿ?Ô-©ÉRÇ …1à÷Ë&¶ØqÀRh¼‘¾‚èÛu‚@óVm &üªwlXŽÏ–µ‹áÍ‚è•ã¦ów=˜‹Íó¡U[gò£JNbð;’Q‹´[Aþ~"„# ô:Øøq <¤e––àïÛ¾c7¨-¡¸í=pxèë 8ü¼zùŒááÀë€A!øÓĪ)L øëÙù{É2!-Ã9, ¢¢¢KûN’ÄR€ÂUÁ< âóí(Xk±1o‹KŠú &Ãý© Ažº/£"J”d¯A¸÷ÀqâÈ¡Ë\/RRú M†Ãö"£Ò*xÚäågÌí[R¼„„r¦Ik€´Öúù<ðìÔ½qtS…Ì:ÓÑ#††®s Áßa¡¯Äj|ôÂʦÜ‹AÍÜ=»"4-², ÝÏ÷æ’´æ}ÙtÄ€±ß|þüëÛ·ÒÒ233³²²ð]ª¨•ßwhæ±-ØMq,WJ06HDC«êêÑëavƒGüˆN©ª¨&&|÷‹<õdw3$'; Ù<;öwAR¼ÜìÑs~S®2 zRB\¿Á£0EA@ „eú>¾ëõèÖô9K‰á±Âºè ÊE'miÃY!Ox„1ZB +OÍjv0sPög¤¥صþÀ®°[‚¢ÖermtŠ.³ò`¸oÞ¸Ðù»^¢àD„ä‘8r qÌ^lÊZZ:ɉ‚¸²¼äÎ@Ù7=õìRcõÕE¡E,ðôåF=ÉÌPüC¸Çù©0|#ôªòǦÂ)Q_ Œ°—@;sç⟒…Ó)u€£‹çó§E]|Ý—23%J²× ‹[GUõy‹±V¸|½ïßý÷2ìæðnZ¼j+Z›šœpóÆ%ÊêHb•æhô‰£{0‹<»ôàdgSzéPÎ4i @”­íò™¡£&w‘s§ŽBå,:ýÄŽ E”B+;ø @½ôûá]8W¬F, à2êÇðª:º;ñ6¾yý"‥5ï˦3ÀãYàÜŒÌLHB̸r{oÛèh…‡Pþj)(¤ Ž’rÄæƒ÷àDmØ»(ƒªÐ© g7&,|Þ„ˆ Kb½~æûØÖ®%¹‚W^%Wýïß}ßQ”«L0èGvã ªekv"”H€ª¡„ㆦÏYr`ç4ïËÁ×R;墓¶´áÖ;lÔ$®—Ÿ‹Í Nv³’Ô¬6`B‡:àâÎÀ²‰59½ 6ª£Ë”C0qº¼…mƒQˆØ…#ÓSSΟþ k¶/ »–ÒJĹi=¾ï•šúÉT2§Š²R~AaðË;{{É»UMi¯Ç7óúϱ‰(dloœ¤øqÐã”ê7q¾íÑÔÎ^#Ý×'üªUÐùih>â>„Óu>ïqú©ê#€ˆaœO…c…>»¨ø¸îí=Rʨ:±äˆ~ “ ܻӪM;ÜUSU),útª,¬Y²Ý ¼yÞY 6ª½ ߈1'?ðǯðùE ¥]&:ùÇVÖ¨ûÙÑ~ ÐÔì[åúÚǰÐPiÄY´Ë††·nÞ t &ÙäÎ{(˜{±aûÎÎŽyOK 88#&¿4:h¾¾Înº_ò‡Ü‡Úwúî‰wyŒ¾ü5nÑWŒ@€R{ç€ü”b]CEU=%=ƒÄ÷I…ùEúMLa€JŠID€¦±^HüäýŠ;L7ý‹"@S³/ ?]¹¼ w'äÚ'£ÈG”²ExýìÄ0…°Ï>#H^€§ÛA#ðõ €ï'N›Z³H_tKk"–F v–fQ,›Ád±˜yì-#M~B‰"Íå6²Aìn¦‚2epí¶Œ.½Þ!@S³z7¤t‡>ùh—žÏFú1ÚD 2ࢤü3p¨hÂÌ-Z´1l`4ÿ§I÷nÿqò,"}}^QuÿT /löÕ¿ÿJKI©û^|-5þºvÅáãç÷üöDz•! ƒzÈh¼Ø”–‘Sx Íê ;=µcïÑÙ –hëêâÖg”S™ºè<44_)-­@…°s Û?oÑŠÁÃǨ©©ƒFa_`”û·+(++ï;|rßá?E{Ú«ï d³²nŠDiEÕ2ò夞ž¥,àïË® C~8ÐW ?æß4=pêzYÚGûŒò$Óß5‹Ç1i)sÓŽý'Žýöæu0þ–rñ’Ð/p…½ ©íZÁµ ×µ] ʯNEu€A@ßÀ %9©ÆÐ_ﯜkªŠ§¾?»¨êŒõgWZ÷žøß_ïÇÚÚÚxcckpïÖšjF£Æ–cÆO=|`Ghh°šºzó­™ÿ9 ¢²õ|#cQY8ª˜F¯Š€ÑÙ¿ïßE?¼sì„iÛ7­Fõ:îÛµY‘­ˆ¿¥¥%6M›E„…â·ƒ“kVf†Ž®¾h+Ý=:âí‡î¢£Â¥US½’3€WªÀRúÈÿ£î@åþ?e·GDK\} všš)ÉÉçO{óYà ‘™‘aÙ剕MTTøá» éäÚ>--õðþ ñqÈÓ§ÿ»A&Ëád;¼¿¸¤xþ¢å?ÏžÊ-c7Û88 ºrñøKc³q§™š™óyü—/~?ºOYZZ9ά¡yqqñ¿×/ß¿ó\_R“’þ½ñ7%¦ŽÎ} 30l›“sçÖõ{·ÿA6±‰‘ž–*Y;üC–¯ÙüÏõ¿¡bü÷þèÁ=Âò%æfÒÔŸ–.ü‰äQûï‹gC^⯮žþ/ËÖš™7Šyÿö÷Ãû0-GŒ™ §§?cöÏ¥%¥×¯ü…IéèìÆÉʲ´²ypïVèë— Rè ·´4ÐÿùÙS¿´Å:ü2`Èð±X˜Þ……«–- 8ܾymûž#;¶¬Ž-{K·º†ÆÖ]‡–þ<+++ÓÅÕ³gßhè×Éߥ¦$‹"†¢ÆMœÑ¬yK&ƒ™šš¼mãÊÂÂBÉóìô]ÿA#•P/šÚÎÉ+±c—nøæâÚ~×ö (ví¦Ý ñ«‡œ¿íÛŽöÀqȶ©^)õ.:òäñÀ9ÉÊÅ(48°w¤€£&˜64ÏÌÌ8êxhÈ+ÑFâw% üý€jvV–EcK‹uêÄ,v1ä#ÂÞ @äì7p›ÅÂXß¹)Ðh°X쾆¸¸wPVR |qîÏcEEE”øÀè衽ÉI k6î$í3¤¡¡9kʘ¢¢BJÀ·îÞ­­mo\½ˆê(gHý;Ù=›É”³]r”Q¦ªª*Hœ}‹Ö˜Ï|½H-³æþþæõ[7Èßk¶þsí"æŒì6Ðwe# ££;täx»fÍ1c±‡;²O4?ägIbK¹è(ɩ쪿ì]29Ñ袣"Nüï7ìSW/ýõëÚ-:÷*Ðð°±öl)).&ÀS_/°…DpkßÑ×çqÏÞ¿brdéé?rpÔøIdG¦,ª¦º,_ÀsïûNàÇ, Pæÿƒ‹ÏGf…n’@$&Æo\³Œ““íÞ¾ãÔYó~™?½¸HÀDººyîݵÜŸ—ÃF|é¯S§þ8 càÐQûvn2od™·fÅBpÿpÛÀNþ ›zóV­1„‚ÇÝ;<ñ}DªkÝÆqÃê¥NÌ:};~ôxÝæÝ‹Ì À¥µhÙSáÜ©(ìÎ’ŸgJ6•N©*”Dz ;[»‚‚|ðsñbm›6{x÷&“ɘ9wÑË€ç{wllli=ççe)ɉàAEk2|4xô‹ç*+)ÏY¸oäè £ÇNÞ½C *àrppÙ°f ¸MMí5›vž>ù¿ÏžXYÛ"bgÕòÙ™™ÂFV‰ ØÛ·Ü¼þWðš­Z·#‹] yÈxÊʪ(sÙ¢Y€}ñ²uþÏÒRSúölmc·qõ҂¼'Í9:wú¸$>ÂVA3´à§Éäï„ɳÐwpÿ2oÚÌ~ãÚeD…!,ä[øñ!.&99Ѧ©ErGœÒ”‹W(¬åQc' 4üÕËÀؘwDT–,‡’¼ œ ×6 SfaÕD„¿éÞ£7T¤vÊò-Œ X…3YÚl—eìû཰Fͪ¡©5ÿ—_ òóQ,„7xYÀÔ¬¡¡á˲-ž¾>ßé?ýü!.vÙ¢ŸJJJ i-JCCkÞÂ’ÄVÚ¢“$§ŸÝ°ºyÐÊÆvÓº¨káÒUÎníŸx?‚šÿøÿΙ¿ÔÑÙý™Ÿ7µ°%Øq-]­¨¤„Í {ÁßΈ ØÄ_>æ;|ôøÖm_<ó•QTõ{'_1:Ê>w¢BEܬù<Áç#÷Àç{ݽî÷ð_dCfÊþ?ê“ ÞÇ뼉IC’ 6tp*P°ù?.ÿ¹-E¢E#Kdàr¹JJŠææl6úB0šH¥psï€àÏZ¶rðó)·Âã)pÿHý2Üe3bcß?ñy„­×ûÑ=w¸Qf£«ŠDç¦vö )ŠŠŠÏü|šÚ5çúþ>æ­™yc==xKcd£"ÃüžxAl+ßÙÍóÒ…3ùyyà¡csmß0L ŒŒP|ÿ6 )Ž.îøáýøR`ç“"(Ûü&äLI<™ý_øÙØØIf£¬d»õÏüe”)&Y¦0幟Oìûwè hеm3¨d$3ÇÄD›*JLø]5òƒk‡½ •:î÷Ôf4"´ kÏžtl»tpç_´kðî­:zº††Ÿ¼!Ë:ÅÕÔÔ215Ų†±®2'Ú—ôô´¼Üh• øvpÒ¦¦ míšG…¿(¹¥¾ÞÀCérõïó6v‚~‘ Ƹ ºé솪A+?*",,4 \´Š*(~Àhâñàà@=}êŃåó§À”T aù;tévé¯Ó WHǼ……DI|DF~wíÞÓÂÒê÷#S• ÀïÝþðŠM*ÉÒêe f5trèš´A´×.^h¶m\ÕÀØdÆœŸ•>jÜdئ$q“¶À+\wŽ.hCø›LÈ;·®ÁÈL §œ!’õÖûáL–6Û)GÔéê¥óØô±§CCDPzõÒ_ÏÀÀÄTÀ¸¹wôök¤ÞX«„–gh¬ °cÂB+Z“‹;%±•¶è*CNkµ;U-»'6|B_¿²øÈBéöôÉcL³‹çN‰¦4<,´M[G7·ŽðPõDIqtq{öÔ ›äwOs‚KZQUm§d~ù²¨+³i<§®¢‚ü _a® _°/âJ|n¯¥Êf1Ù,†ó“N‘Ü%ß.nž]{ôÔÑÑ#/°†$=»ŒeÇr]þ»wU$‹à¯3 <ÂÈÄ,ø¥?4÷`ñýžx÷8fÄvÎnoßFÖ™ssËŽ"UP(.)QQ¡–C„ÚD23`ÖIž¢¿«ƒ@xxˆ““{JRRdxXÄ›8ŠØèç§«£—•ÀIùé©©°ˆÖ…Q€,—‘–Vž!-UWOÀ%Ÿ;s|À ‘ðÃã¼} àAHù¯ãJ…mnÜĺÿ áÆÆ¦ð/‡¼÷0§|¿>(­vdÝÄ´”¬B²a’y„)DjÅ_xÔäçç`òƒË?a ”ýCFŒþÊuE6›8êT©ã‘á¡,VËô´KK+XÒP86Wkë¦n>U¤£“’’$üëýè>„“i³@9çëýðï g+8±^à/h(ÌÍ8Ž)",ËÜ¿ycËðc%¼ïúÖ¶³ŠªǃWÜóÈéAŠ‚L5܇È_e˜Dkù<‚€IÈã•R.vxO ‡ f¢’²äØ Sgñ¸‚hHlE6Œ!’øˆnÈikg߻ߠ«—¡ü•xV¶øícýþ9ƒ ú(mGí>åâ…q_GOÙvlY f2óÌ-,gÎ^˜•yýÊÑBd,ð ׎®^FF9]‚!#C0Q¥Íd¨ßc'Ù;áL–6Û%G {wZZyè|jjùp¨Ð•¸º{^¾xÖÙÝóèÁ]’ÕÑ)UBz7¸X‹úëŠ>.ØR.:<(ä¯dÓ*5¯¶3çå•3„%Å%Jz~’\‰ñ2-Ó•m|I:u鮯o—è1…·Ú882ÌWAHëÁ/ËÖ`&ºfiEU³kò&0•ùLUn¹g?‹ÉÐ×T†š•Vþ>`5e¶6Ké,¦›AÃÿ èf¶nX÷@³~Ëž/«(h ñ[í¸‰Óûô !hæ"ÂCáš W-/xI¿Ä]•¤ç¤ïTXFŒž˜’šIjþñgä@¤˜Ø2³2´uôàƒHd}CCD؈Ö ¬6¶²¨@˜23RVFÆïe^1vö-ç,Xò&$Ìt›¶íªÔÒi3ç_ýûÜ_/­±?NeðË&­ÈÄV{Y-Ô;ºdÊ Ù"ªGLWa#Á»“ߨùÔÔ4Á ”ÕÿŸ’Eÿ6M‚ë ÔÏp«… òSv\š ŒŒ•0zBÁå7<²PBfFºŸ¯ ¶Môxql½ø›mhÄ“““d\YGþ³ÞÁ÷C_®­«wûæõììl]`€µ 9ÝÚw€×ïÖ«`èÃQ-Ûv:V ±@7ÃBCd‰V‹ H / `‡zpÏ6ø«ˆÞåñÄñ6H˜£Ä dƨ° °-|ø îêP?“übƒ²v%K6,55IEEÅÌÌOµmçFø8,­,£Á0dAUIÔÿèÐ)«@ƒT &þˆžº’<”çp8¨%K–?+0âpµòó-·~tïvžýI ¶[`=srC#ú 4÷`ýqn<ÐÆíÊ \Nv¶¡ñ§Ž`ÄmììÕÔÔ {À‡žÁÚÚº1ïÞ¢"ô Wt¿¿û®§d›‘‚n"J3¾…xQ$‘¡š%È@^ؤGn5žHnU@²pKa~؈gÌYôàî­àW-¹*\˜³~ÿ`B{Æda];´s™9ga`À ˜Î¥íbSšrñŠÂ…¡AH–6f bèÛ:º *Äʩ̧\wþ~öÍ[7mÖ}èÖ£ººFùàRÍú=޲{G9Û¥²ÿ3߮ݾG°¥tèô)bðÝ»¨ââ¢á£„±WšvCv3転À÷5>.¤ {è)È©è]iĶÂEW_AÆ–‡h½#Ës ÝÄþÕ¼eëý»¶®Y¾ˆ|þ¹z!µ ÅŽ^«õÉ.œ¥PÂTà±Ê™op j,±S(ý/>,ÐðáJ]¡ƒÌ’¥Añïëõè×5[á“€bÌ&™ëíÈ1 LJK‹±qîÔq’'(àN› |· ɧ„)à¢þ½~é×u[ÁOl. ¡¯ÚCÚߦÍZ@@š7&>ú`¾÷ïÞ‚@ÛûþÇÉÎ:ýÇQâÐ/Ú’ gÿ6j"¶áwñÄ÷ñݲ³zàJŽc[ì‹§€OL;·®Ã‰=ÃGO„&韫—àÅ(»Gž8x¼/J‚ÃäÀÈå„hï2¹]àýåóø¾}‹V8Ü#';3¨ìØÉ ÝܱuÝÐcÙÉãscÞFŸ:ù?ÑlÕ$(Jù7¯_J¶)ðûïÕwàÂ¥k4µ´`C‡ãõË@I|„Ïš[4†,ª£­Û©sw’¸lñONYuýKDø5> ÔI‰ñîÞ¼wGàƒ'mG›Ò”‹W"P†Î{à'¨²23Ÿz?¼}KpŽ“X9•Yà”ëÔì÷£ûÆM˜Ö§¡Ù¦”6CêߨU¾G”³]Ú(Ÿ?óìù+VoÉÍËÁÑ4ˆªVôÄûñÀ¡#ñ‚‘ÊWM甆øÁ½Û†ž°‡Ü+0pz•h€4b[ᢓV]=H—dMá“'ªÙëÞ³”z¢»|Íöac×RZ‰plêñ}¯ÔÔrÇDÊl*ÊJù…ØûMU¬"¥,A4ñºë5vék^3—Í‚}ýÇ~ž{[ X‡…an/<Í™¾D›}Ÿ'+,³úÀ,ž9uœœ´XýÒèhh¾j¾j‚“™æÞÞ#¥Œªݧè·0ÉÀÐàÁ½;­Ú´Ã]5U•²ƒÔ„WÍ’ýˆ¥­«4l7PKSU*Dž3ðcï‘-ëW"L_žÛùU·8=nX³ô«îÝøo„Ø·Òˆ³( ††·nÞÀÉÂÒP’/ €wºaw£D]föäžh±›Qö¶/7“’Ò²áÅb<}œöÉùAZ÷ªŸŽãÿaó}óºjï­~½t 44rˆMäpPêG“àe„S€pšHÏ>pØqrR|ýè—öN»õxüð޶n@! _ÀÞ·×±ö)»ìD!¤á;HÄ©,Ë~dH2×*R89§õ!ÞîâbµT#],€|" _8ûM‘-±UrqíØ¼F.ÚA7‚F€F@  Bm B½wé©$hx‡4>•ÌLgûlÞ„Ïœ2ê³§¤¨7Ô˜ ¥ñéôÓzƒÝú„@N¾€&ûõinÐ}¡ ø*ø‡L5Z,_Ç€V£#ô£4444_+9œ¬!n? vú¡SïC#ÝÐÕÑ2ÒщŽc³Ê_óµvn7€œ!Pc€ÚëWsM…ÂâÁ;†è‹F€F€F€F þ!à¬lß<£QBijOE¯ôçáÊÉæª†ßi¹iêh¶Š3/Œ‹Uüôëú×}ºG444uŒ€Ü[øü)ÚÆ“uŒEߨZÇÑÕÑÐÐÐÔ*úùJŠJšê8G'NYUE)+8¹ÛDKUK[YK'S­Vk§ §  øÖwà;M}«+ÅnÊZµ76s®²o!x'}Õêr@‡ŽšÔµ{¸U˜AôÙvÎí§Ì\(£´ªÞ»c·ï?‰×ZáÁ*µ¤ªQæÿuý³†”·ª™¸dõv KëjB?.?D(¾½˜téAìmoΓR{¥ÔäÄw¹q·<нãÃ÷áÛTÁ¨W¿a}ެÁ®­ßv¸ ¤‹ª{ìì[Í[LŸêQwÀ×ø^VwMÿfj’k Uc,S“—+pþÍÒòæç0ð^àJ]Óf/6·h²jñLò>ÅJ=Cg’c¾ì€.X²Þ´a#œÐ‡—Φ&%ø?ñ~x—Ǽ²:à¹Þ+9a'7϶®‡÷o•‘YÆ-EEÅÕ›ļ‹:´w³Œl2nUØTò,ÞC´nëoGöm}ÿNp W›v®c&ÌÜ»mðï}‡l\¹@FEU½5bÌÔ”ä„{·¯ÉxP,Ïí—2ÒReä§o}-°xiš¬$¿·YÉùFš(6P NynÖ°ÑÝÐK ÍôŒ¾¾Çc2˜Yñùò®´:j¢•M3È §OüæÿÌçkºu‰€¶Ž^n‡¼¹ôVGW/3]ÖWë²mt]ÕG Eëv?N™+,'<ôÙ÷MÍÌ¿ï=¤‰µ-“ÅÆ‹>|ßyñÔ‰³üºsÓ¯©)‰x*³ó–íØ¸<=-Û8¸téÑÇÈØ´¨° ø¥ÿ+çóór«ßBR‚\ CÙ:Ú¹E<>_ÇÓæó‡(¨þÁ¨T$–“µ­}A~^‹ÖŽ/žˆÅd²ëVS 2xû5ƒÏãÕTt9bÈÀžûóÈ ?M-m(žûôÞÄÊîø‘ÝhgT„à­Õ2® 3ÈxVôVË6ÎÜÒR›¦Í±ydgeTò)Ñl•l fòû·‘V6v„ã·²nš’” ú÷mTX…µ×ø*«‘f­*‚¯%—iÏ+êÙ];&¡0››ý6Û£cW†ŠŠ’²òwÝ&í;°X+ö@µ´¬BËFÙ~! ©¹ºŸÑµÚžÂ&}ˆ‹ xî;pظÏh$ýˆlêle7£šwuõ fÎ[žwüðn°"#ÆL±kÞê·ÝâãªY2ý¸ü ™™¾yõ"ÒÂpš˜6œµ`å3ßG×.ŸÁnjÚ¨{¯0îïß6zÒþë æ°Ñ“oßø›pÿ.îûuñìñ7!AšÚ:}Ž€&tÏÖÕ5ÕMùLìÞE,^I¡W (ðy½øŒÛjŒ$VÅF€vÎbÞFG†9¹xx#ø<ºÓº­3ÜK7®Z fbu5õçOÿó6Ž]~ðèØMYE5:2ô™ãyy9`øFŽŸ.TyÂŒøïÕ a¡¯FŸ–ŸŸolb¦§gxüèîø¸˜šº1äa@ù˜‚<.Öí«Àgi)‰ó¯kÜÄŒ2üjÒS“¡½†ø}¯nž]JJJoÿó7KçO***"^øõé?RIYiÙÚE……ÛÖ/iÙÆ±G¯Azú†˜cXÿ^nÉw'W?ßGV¶ÍÛ •å›wÛ°òg"`>ó¸ÜWΡ%Ý~èß¾Ãw¥¥‚–‹6)’ó\´ö·‘aMlìH-–Öv÷ï^oãà*üûèþ?Èlkעπz†I .?Aæ¿Ø*–iÞÈrü”¹9ò²{Ïn]”””ss³ÏüqXO¿A«¶N\×Õ³KÄ›à¿NëÑk £«'^``¯\<êàÔ^,\€N?{¼›úÕ¼•Ë{ñÌû测&÷N­œs²³Í5f1™Ï¯¤ð#{軵€~¡jk“^-Mš‡¨E6lÛ01-¼©­u®N¨«´´ÄÂÌè†W[QýMÈ[×öR¾›‚‘221{ž•™QXTˆÇ˧'Û¢±•Ï㻘rú†FçOÅ-L› ;Ž.ž;±¤¤“öÞ­kžºéèê?õyøàîõ‘ã¦7²hõæäÿöÃè'ÚklÉ]º÷9¼oKÿ!c"Ã_?¾_¾lçÿ²öþëA~¾ï"?"j«o³L1ªâàèÞõû¾::zq±oA.«$I4€•ŠŠêQ›6k ðÌ— 7aêü¨ÈP\òwþ’uwo^U¯3l«««Ïø)sòósá*ƒª‡Œš´{ËÊ:kCW¤¥­ÓoÐ((^Y,VHpà™?‰V:<`ðcÓ†ˆ+NE„½Æ]Êð«#Ýdr¶lÝNU]ýý»¨óårkþ -¢ ôì7 Ûßå 'I"ôkG>ºÜù÷JË6Nîžß©ª©ñ¸¥&'›­Ø«ßpH /S·  ÿÄ‘½KWm‡+ÁëW/kdåWWªÊÊ-àA­ ˾aúËgmÑ*­°çàû}¼îEG¾™ßu­–Ž'«üøh1÷l_»›žžÁèñ3ŽÚÖ¥{op'¤LX[:výþÐÞ-©CGN>vòÿîQ]ËÖŽ»·®Âœè«Ö·…Èžš’die @Øi'w,aòrór†™"FZZʵ˧E]€`Ñûóؾ䤄†æ§Î^ ^6ö}´4µut¡û¿vétvv¸gÙÞ2pÚqví°këʼܜQãgH–Yá<³#lÓØAÕÔÔƒ^@zNþBâ}ŽÝ &Γ¿ï‡qÓµ}—I3~Þ¸êçâ¢"Ô%\eÂz‘‚füùû~ O*øí—ÁM±64µk!ꔜœ°wëêœ\Ž“³ÇØI³Ö.Ÿ ç%±<ÂÂ{CÑo°yÍ/`ï¦ÌZ˜Ÿ›óðÞ¿¸kÛ´ù¾ë@dí[´6jòú•ó%q SäÍLÅŒÒôt•ôÔ¢T#{ûò·¾?½™À™7gòžýÇœ];ñKÿ³›Š¶äwüäÙ/žîݾÖÚÖn´ù>ï‘ SÛ³ ìòtéÖ[Z!^îݱò9øx K« g~OMMš2c¡›Gg2ȃ›!ìßµ{Jàó':w'€¡‘‰‘qHp€´òéôj" ¤* ƒ}Ž8úÛö„±ÙÆMüiç–_Á#H’âbh(@Ö,›­®¡9í§Å……‚~¿ëÑ¡‚ ¬fóªôøÙ“GgÌ]blÒ¤‰<æ#{ªTˆ\e.[€s`ÓÀ.§ëF–V¢Íƒ*gêÌ_.ýuâeÀ³Æ–Ö?N™·m㬠i;àWGº-,möîX‹.Ϙ»¬­£Û ?oÉÑD˜º§½…yKþ‚)…bbÊÌE &,%I73·P×Ðxéï'|ÅëW/líZJ–ÿy)rêOÙŠ¯è˜UÌ-)á•”àžP¨pKJÛær[WÀjCaÿË OA R’¡;BãõðTJÂVdDHDX”…÷o_‡hEò ³×ÃÛðÍí¸vé,"ƒUUe>øÜ—(_…ùyÃ@?%ùP'KUM]´Ùàª}݆á“çÎ?—eôˆÜÂôKJŒÇ̉‹}÷*ðy+[8:{À‚xñ L ž”‘ÙÁÑÍ-IOG~ûŸK’9+œç±1oY,¶™x!Ûwo#@wRR’È_N6 NiÙÖ Ž@Ø8¡í€#cQA8-R‘p•‘¿ÍZ´5~úï‡v‚ûG ò+*)šš5Bù(ÊZÉæañ^ÈÏž>.((022“Ì#L:ðŸkÁß+ñÎÍ+0‘[PƦ7¡¯ iÈ^È2ʧoÕ6ìRWRƒ (<#ÔÄØ„Åd㓘ÛÒǦWVG(gN7rÄp}i-15·ÐÒÖ½ëè9–UÄ›aN,.XðW6‰~pçz^.vc‹â?Ä`í¼ zŽ=XXÔ÷½98»íß¹žh”^û™440h€ XqÁ/_ÐñfÒ¨úéBªâîÙÅûáíøØ÷ ¾´ Š(J¢ÑÆÑõÖõKJŒ,L@¤!ÁA:z×Tt¶YpÕoaåKÀL;´g3ŒÃäD”°qT¾yË ­±©ùå €—…N´…PH}ˆ{‡½‹ŽëÕÌ^ ùHÛ¿:Òíõð, Í˵ɘ˜k6$Ø:•”Å—-õ,ãäÄxð¸)‰ñøAÐƒà„™)äNIb6'[SC³¦&€TKK'??O´#˜ÆÏa›×Ö-_°y#¥ž[ÜR%e1J* =:ý‹ Ÿ•Ø©“kðÝ7÷®úÚ6qÜ*’ÿ®“¾[1‹§ª¦ºuËø[*ªjRÚZµµt!IG[4^tÎà ¬2ÝÉá”g+))ÊÉ)§ê`•”UÉãZ=;õ€öH îBå߯ÑíîÍ+Nn—Η[ö+S§ª© x÷Æ–¶N®žÂ—•–š,I4àå&Œù$gFYH%.Pƒ €'íœÝÿ½vfÛ?ÿ«”­jÃ>#?æpïÃÉÉlx"n¿Á£@b‚?£À/þˆŽ®'+CÌãEØ*==ýF­ÿº…¤€hÇǽÇoi;àWGº…Q¹¥%%J:‚÷pAS¶û£³~~^Üq¶3!,b?z÷þ.:Àp #n 5ðo ºsikiçä–“ iEU>]€ïйÑÊŒtqµï|3¾‚E)_§T `\¢Ð9—}W‹úT8àÕ¶+”‹«6í ljZøŸAa åcø'+SÔJ¥©¡M ƒjGW¯\Ϥ©©Ò“©ËPdJ]M”©8 ¡òƒAç”D@>Ñ<† ŒÅôœì,½r&XG‡B[Éçýg¶Œ8ëÖ‹þÏ|Á¡9ä’Ý') ”ÐWuîÖ˳K¤(+©˜5l|åÂilàB„“S]Mƒ0ӘƚåSZïÑ“¸(ç¹X.xÐYYÛÁ³ó¯3Áã]t8\wðZyüuƒOŽð]=ø&•ÿýïš8uü`ÿÁ£“pçßr«žðqsè¨ÉÝ{ö¿xîOáS=‚  ‡p(XðPà’U[ .¢y„õ¢ûÐ9éèŽC˜Y.äKtšNkÒ¹Y m¶1-|Xhmj—ÀÑ1Ðe먔0.ø[\¾zÓÁÉ•²ÙœL )QSKK¨iCèΧÙR\ÄþHÉ¡]£,JZbIqÉ¡}›'N›Ížx0#gÀ‹'½û CàŠŠŠš0QZ tzµø8Œôc-1EI4 bqÀ>Nø3 ­r’ˆf€þŒø“À€Ë}Q­†UýaP{â÷…EQq!ñ7i6|’«^˜\<‘•™¦¥£‡ÍšÒ–’••JÎÌmnåw@¹èdíq>ں뫠ˆ‚Ä2imÛ¬U[ç­ë›˜™ž0#,丄Õaê¶nçòÔûy‡z·h刼*¶Ejv¹sRç3d”†³y‰,~®?_Ÿ­Àdó‚•x˜eŽ@ÜÒ¾©Åê\jλy«¶£Ž;6,ÇgËÚŰç“X €W/Ÿƒ}!¦Cxn@!J2øû¶ïØ JYØkzú:²¼·••U±J‘¡Ã`2¤ÂIߨiäg@Á ã øâ·jã8~ê¼×¯ #íîKÿgí;tƒá“§Ûý$‘x½ë@ßCn©ª¨&&|÷GB$™_˜âìâiŒÉLf5¦7‹Í²o)°¢&ÄÇÚÚ·Âl-Z;Gýý\Úw„Á;”É bå<ËøÝê6€g'nÁtkiÕÔ̼1ð÷uÐ œuV`âþö®®©®³ÑÝi€"¶Š`ww'vw·Øýš¯Ý…ík·€ ¡¤Jƒ4#GŒï?.ïußv7Nœ¾w¿ýtœûÜç<ç9õä9N]ÿô¹<¯Kðƒ<„£mCŒ’mðšŒYýx«°‰šðsEÿ\ ٙĻ0ÚÁR‚càñ§UãæzúFD9/ o-ÅîÕg0Ì$èš.Ýû#[@ºDª9Àd–±ÍT넇s3jꌫ£¡¬Þ¨´I1§DAS‰ <++‡ÒöO²Ë‘ýÛ’“âACMf!K¼QˆeMNŒ0t œ.ÿf y«ôóÆÁ—ŽÇÞ%O‘„ ñw@‰üK>ºçÚ¨q‹þCFcSÃ*djf9µüÎìÔH¿w›ë ‡!,èÃÀácQmêÁ=×~GuptC.?e.Æ­¯×'‘Ôy†d—½“)È”WÈfp"û,SF‘SÆ–—É“a°8%æl¥’Ò©ŒËF{/#lì|ß¹ñ' ,{èhçn_áƒæízéÔ¤é !@¾!ãoƒ ¦ÍY¦¤¨ù9 '?â-„tã¦IÓçç°XñqÑ•ëi$âp@z:‡sá £Kpœ”Çëg|ôc/102Æ!Q8èÅ“{|Dê / éé)·†ä»yÍ‚WÏŽ0‘fø†…|Æ ¬™@åzùÀH]ξÇ!9#ÇNƒC<;3ú*ðÞÓÐÐhárØAC>ú b¦ç|`1_?c‘úBlÐ„Ë à C˜b°è w̤Yˆê9yxÒ*XÖµ£·Í^° <2 ¯g`ˆÕ-êó§g7/ÞÇË Ö Í»á°³KgŽÂ>‡‹V„¹IøaÈZ0µ±‘¯X¿ §¡ Ü*;LI<ºä×r ^%Ý –IíõÒóY8ð¡vcÏ»Þurê(ÉËg–dŸ™z¾Ýâ¶gÏ]*? Žúƒ!zîäa£'ÃEÆÊÎþð=mŽ÷¸ì`úY¼rsvVV0Õ¼ ÆÎSŠ|˜s'N˜2÷̉Às²wÚ8v‚+„š>w94gü9bÜT|qŒDhpæ˜VÚŠßÒ½›—Ñ×°M Á#(Ыå¢hÃc¦ ±;/?7äcÌIdÛQÙ{Àðs¿"õ 5FrºˆØcèfõ-`Qú}ûÛÁ aãVoÚ‡óPÐ%¼îq¬ÿÇíì?xô8eœ¸˜¯·®CcÅÜ_¶ðQ޾C{6öê7láòM°&b'õtçæ¥à^ħaðwo]Z¶v;Œ_82¶¸±pzÁ¨ñÓ¡>Áæˆãbá×’O–ŠáƒèѳOjª¨û)”ò ƒ>ø[Y[ÿ8MµŠË&|c‡+ËF*Ê$ÈÉ0‘V&W&£Ì)Ó*æè•”é—”Xçq ûo®¯–¨,u Ì3ÆðpW{LŸ»býŠÙ@[è&üIHˆ‹whç˜R¾ªªï¿d‘ž¾Þ«Ïšµh§*ʃX¸{ IDATJ…ìÿ;G²Ë~nnN|lŒ•u“|Vf}Sƒ ã†óZͳ²²?M½r庶¶Vbrj=3QéïROÑmù©ÀáH4‰+?µ9ÍIqvÀÃÆÚれ—ðÐPa‹3ouúúzO?î(]tý‚’ûêŒh%F¶‚,‡oJe¥ÕRŽ– ÓW­¤A~©]vÑZÔà¢ñü0ÖbeÝ4,äRÏ ÓÃ<ÿÃ(i4þ|È3zúÐ4jÕªå㬨ô°EúfuY9¹oÞx9vèþèÑS ‹¤¤¤Ú&F™é©Úºú>SèþL è¢]Ç®^/f%4nš’ä@ ß; ÃíCP$…Wºw nÔ2>‚é\}@N6_EùuÉÜ‚ ).Òxh`Ð2dº÷Œ;ãpjmDx¢÷h®Ð 9P)UÔðEXfÌ—HSsËÏ_“_¾y§¯§‡+ÀÒ T4¢¢¢t Œ§­(ê\æJë¢hà8v$sã,5ú6qz0ü^ t Ò,] €F£¢9PÃ@@ðþëj¸Rº:šp¸q¾2OÌ-¹I„M´¿· _~ý¡9ðãøü)dÅÂÉ?އÆ@sàwç€ÄVîÿÝ—þ»ó…¦ŸæÍš4Ds€^öEó‡~Js€æÍ©å€ÄɶPAGÀqääÜK!e劋JpÈšœ¢|Y)§¤¨¸X¦âªÉVJc£9@s€æÍš4hР9ðÇs@êµâœÑ*¥©ìœ|ަ¾¼‚޶œ¶nBT"ŽVÔ50ÉÍÌNOýÆÑ`ú*µÌ-£ƒAÿøñI7æÍÿ8H¤ö)+“º[k„‘J—Ó 9@s@ú9 u @VpýöW5õ²2vº K&»X&7¬àèä¨Xü+SdD²œº~}”×XúùKSHs€æÍš•r@UE¶¶±‡SV\RÂdðËú8"-#';wÃК4hÐø×Y  üòŒ"6'JYu“¦¦‹ºúnmíË®ææ7-,Ž9jì^QzBE奱q>Sž“ÏQS‘<ñmÚ/X²Züôî7¸Oÿ!âÃ×<äøÉ3{õ$X¯°rAH^žlÝuWú ÂT©dù—º¦fUz¥ÚÀ«ÖmmÞ‚{É®øíVWõÚ^Õ%¬vÉ–ÿ87$KO5° c¬°rÁ*´µuŽžºL”K'C† 3lÔxAÊÿ¼Üÿ¥¦¦Ì.áàê7€aËîçOï{º¿&°AãE÷mwYù‰Ä_»N=Z6h$+'›˜ÿúùcO7í#FO¤˜œœÜŒ9‹UTÕìÙRX(Éøš\%†“ôW‰!ä[NíìÛíß½EŸér19€ÈŸâBvt ÷’i|4”å9»|åĈÂìKù–b``Àà®ùÔABÕ[šÄ$O4X~~ÞµKgEÃÐOiðrJcN«¤¤…¸û>)9ÐŒú8€½oÖ¼ehº85%ùþÝ›>åýò гç/‹ùúÏëD{ëÖ3]»i×Ô Ãˆ?)eE‰sFº,ï† Výέ[,(0×ÑaÈËãdÙR;å×LÅ47¦¦¬0ùß©}çc']¿zñã¡=l6ÛªQÇâ(¸È©Jüµmãø9<´€JŒÆ|nhÕ¤ ?¯e+;¢ã¹RÂpÎ’¼ßyܾ^a˜¬=U ©<üÎÓ­oñ½ò10`¼óLÈmâ/y”ü¬%Um瘟s33Ò ÂÍ[¼Ú Ô€*UGKNÄ·zïþ¼·ªÊ ^J0›òrs;’ @­ÚuW¬Ýìñæåk23Òk×1í7hò-yy…Ùó–0åäöïv)úÿ;e%ÞÆê1üG"ñ&üâ¼E¹oñ±¬lV“¦e™rššš ò Pš6iìØ®§¬,›•-//[\Lm÷‘ÈÒT=Îc—yýòiõÞÅ[Ü+n œrQm ÿ±Gs8¥¿{{±÷-]µ)!>æèÝhΤ©³›4k±gû¦ø¸˜ß½i4ý$ Þ@Š“—mmÓvòô9Ø.Åx*•%Åg)S 0•‡nwu5×Ô g±ÌÔÔ˜Ln¨O‰¶~Á ¾Ýûs¦nƒ ŒÉ¤Üäää{ãÚÅ7/Ÿ<ò÷óð÷ÁïV6vý€›“óìÉýO¢Ð±C»¶¬¬,3sËW/žäçåoa¡™>{Qƒ†VLYÙ¯QŸ/œ=.ØyÍš· ¤ì‰6í:ÄFG} …´D("#0/Y¹ÁãÍ‹{·).ÚµïØÃû·;w륣£çþæùãGw§L›gZß"âSÈñÃû`REdΔés1àlðuÞ¾y5äcIôQа}ϔػ­kϾ©ÉɤUžÓÒÒ>z¢U£Æ¸sçCÀûÓå~ ò?ûÉc¿DFàiÿAÃìÚ+*(¼¿vñ4vA‚™ÙYYõLÍ ²]:wr•}[§Ö¶mJKK:tîüáü™cøѤi‹7¯žñ"ñ[ŸFüƪ%së8"%=póú%”Cu6r<®¿~ëñšÄ‰²½Ýzöíܵ—’²ò§°‹gOäæ²«Æº Ýzcħ°Úuëɼý‚ ±wžî„Àø"XÄ·nZ…Ç&µêLš2ËиVTDxFFz!»àú•óˆHiãà#±ÑšM;Љ¶¶Ê**¯^<~öø¾`½ÂJ•FŒžÐ¼• NÇòô|s÷æ5Phd\kÂ䨷ŒS†Î:sò0_/'&ÄQ"$¹AÙe”¯…æ utõΞ8:f┫—Îsm´ƒ‡ú€? ¬tñÅç,\ûîÝNÀóâGFfF†Y}‹úæ–‘‘ŸŽÙ?dØhÛ6íÒÒR1° ú…u4‡¯Éuë™ çk?Ôû6ŽªjêI q˜¼¼ÃþSïþƒ´ut£¿F]8s,5åeßéa˜¡u˜S……V/æEXé„°uãfCGŽÇŠ”˜{éü©¸˜¯(DŸ:Om\«Nä§0bh©âkàoý§ºª²Ž®ŽLGAA®v}]ý¦-×Î. ‰àrF_W[YI¡¸˜: €\š(×y¼Ž™Ø«ï ŒÞ¬ÌŒó§~Žøî†«mÁÒÕ˜A¤5®J] kîÖ݇à\EW®Ù°ƒÿë×HMMí [vc<‡…!âqÔg“Úu°X¹^:òø1òóòr1yõô ŽÜMôþo݉?x̦—Ïc§VRVZ½lå\è?h8¬jêê)ß¾¹^> z”••Ç;ϰnÒœ÷ñt'(œ³`ù§°àgOÂìúðŸ›~¾ÜkVkæ3jÜd]]=|gÎ[c F&ê?yÆÖ +k†º r€œXvTTU!Øœ;õ7$?öè"v)¬‡c&L©U§®  IIF¥²"å[Õ+”|}õè Þ‚LÏõ0™¦ŠŠ¥ùù–ˆHJb&$àÛòM`÷‰›»ÏÞS>¸„™ðS¨XjÕÔÔ}½yä5h å…XŽçÍÿ÷¡=X, ñ•6²næéñzíŠù¯ž?"‰‡UæC€ÏŠÅ³—ÌŸ£æØñSÛ…°‡ä¤ Ÿ5ßÓ¶í:¼ór÷örï©­§"ÃSCÃ¥«6¾|öRú'·²m³cËÚu«`½˜5g‰ë•³‹çNVRRiß±› m‚%GìD@-â1ð&¢Õ3ç.)ÈÏ_½l.‚— iâ!Jújaiµmãªå‹g*+) 2Š(·¶nêöê„¡ëW.L˜ÌýðörÇÚúøþ]Ô éŸƒM½¤j±?‚üö*Œ+S¦/¸pöÄÂ9“sssLÍÌù mìºõìùoù¢`ÈÄ©\"…}€Í¬¾¹8&|&“Ë:??ïù³&B­"Öt>´ÊÊÜs«\Ö/Û¹em¿Ã .«W°|ØÈ±€_»bÁÖM+›·´éÚ½7`ó1ÀÁ¬IKæO}U®ñŠÓË|È»L°v²[,8¾>žP@Ê1f5jêûŽbÆá©¼‚ü‚%k ò Ø)(ýhÛ´uÂ`^8Ç ßêõÛ ¡EÁ#‚ò‘˜M0x„•Uã]ÛÖÏŸ9þò…SèzJ䬛5îÔñƒógMúè7cöb®–+Ið÷»×¯žÅÀæ“þ Ì¢',¶ÿYó—ݹueálgoOù‹WB—À(š5oɇ@¿ù3'>}r¿­#W2ÀGU”Møí ±…³Ù‘¥¥™jjªªø÷“yþ»q !,Ö7™"8Le™”!@jªêØÚ¡U ²),ä#_X•`Øó{ïmiiEÀÄÄDÖÞ­:bØìBDðC(·´j$ˆ e43Œñ††F¾ï<ãbc’’!ßà]„á)¢&àï~ïã%X YòøáÝÜ”H8`ºŽ‰†ÑÝÏ×K‚ µ0GAO…)·  öo^#aí;w»uý2BäÑè[Û¶4`"2$((@GWo”-‚Åi” )ù)F”´²± B_Cù~|ÿN~>žÌÃP´’ãA9´˜ð)‰\³qû¾C§\¶ï‡mÛãÍ+aÕ‘åµëšÂøôäá=¸òá4ò|ž—[ý³²216(U »6ûŸ&¾ÈÙ ‘صuºuã „!˜²Ü» —a”BuD|:+úK¤`┈Ùe@…Hû¶>ïÜÁ[ŒU'. ¹ YÙ™”uAn®oaéõöíJ _ 9˜sXÙ¾ÞžFa½º’ÌwêÐåúÕ ˜>Ä @9sA^‡ÎÝ_={ý•ÛYOhéhëë¢\œ¾ãkè ‹ŠBdû á‚ŽdݤF‘¦–ö£ûwÀaŒáÐ`®©XU”üüí  ޶–ô¬zõêÕ73c³¡*kiªã+'/¯¢¨ù-9ã[²ºš2Jdü%òC¹Î£÷Ÿ=}‡zc %%™À¡o`¸låFÌJÒL‰[tWò¾òÖýUB|üÊõÛtuõoº^Â#[{‡˜è/Ð1„"#ÂÃCƒš6kE¼‚hOÂY©VCIÕªðÅÓGØÙÁ(a3Æ~&£ `Ã2.¿:¾Ä{·\±Q¢Ó‘˜Dpìã?==c.@[‡~>^ÂÌ?‰ÃØÇ÷íptAàÇ °׿ôô´ŸTögsá$èD|±bc #ªƒõðĹëGO]AüÏÅs'…Y]i-+ ÂÿH‰”…Á¾Ã`”r8mêÕëëä´óÊ###" 8F-†ÕŠÅ2`³Ìá_A@y0BXn^숪jj‚:f§hdd£%d¾×9Ëâ9ˆ¼œƒG´lm§¤¬‚8T(p@Ë·L@C°à»ÐéCCƒX,.ZØ¿;A.AÀüß{#a »¶®¶äü+cÁ ž]Ž,m MñKà‰F”àÞ»ù9ñÂ,µ$NW`Örž>‡SÊ[ÅÎ-'/ã%~ÃèN€a!æpJ` C†œ 1JJÊh¯`9e %?)!Q¨¥­ƒ›"ˆ§¢™ ‹ôÄaÃ@Ÿjik y`÷¶Ô´”ü¼H Âêâ-×ÖÒÁÑ%d oFzºà[…ìBR..BÇ) Âú¿‡ÿš(GÊì†-\³ØI:#­b‡ý0”¢üÚ•³ƒ†ŒFd+;Rcõ Åì2T×¢• ƒÁüÈÕmµ|õ& ­œœl -M.=‚†x¼ÉÓçùk'ù Ã;˜ÁC [ù‡6¢›&8Ï6ˆ}ûv¹`M€™M°v¾H Û:v$Ë5´´°DˆÓw|¨DOXMm´ôŠðbzZŠ––.&ü·*’`JªH)¶ÒFI9•²¢|©¼ló–͸ËF)G]3ûƒƒHOÜ)§g¨ßxÀdÈÊ2¢£å)×ytQšWlí233ßyqóóD|Dw%ß‹înÏç/Z…-ŸXH˽ˆ.Û+”yE%¥˜òX/|²³ÄMîAÛäib6°‹uéÑ[KK‡SZŠE^M];#B€ÒÒR¥¦VüÀ"ŒX‚îܼjçàtòèþæ!öÊ¡#Ç’7âiGŒˆ|V’¦®®JÈË«{`¹PЩض°MÃ9Œ^†÷1ÕdP:‰¹´´þòO$>+¿hY±J„U ,e @¹Ww{û……žQPÀ¬àn¥¶*odÓÝeË“€…x`ºƒèoc׎—ÝpâÃr0cö¢{·¯yyºc'?i:£¬By ´¾´m׃»¶m€IÛÝû‘¼ EÊŽ¡‘ _ú ô ‚MkÏÁ–“•‡£Fz„8aw‡…uÑŠõ;·®ËΤ¶§ŠèNva¡œ<×éI|Å,Äu~òïøÀÈæÔÖÑTuøPÁv&cÁ3i güña02©(–×U?Ù…l²ÉPšYå²#|MNV§©¡ÉWuvV&¶d¢ò+ðgQq›•“ ; ?Ëÿf±åÿ·+ õ55µ 7:€Ž®.¢Ø)_]XTTDžùƒËï`¨yðÉ`ç#´JП™Á!Y8< #Óʺ)bHÂB‚(ãâD×(þS‡vaîß¾÷0ñ kïàˆLÄ9ضq@² ‰Š˜qÄŸï}<±ÌÍš¿ôà¾íð\‰_ IÙÑßñØH§Á—È…¹*Ÿh {{º B$$r(ߢ\:(! ³33¬7'ËѧYYxÉP×Ð 5451®ð'%U‚8ÓX ââ“pb[i)„l‹Y™y6÷2üÍÉÌ…ñ…[Žè SÁuZå:"gC?Ü­gVÖÜ%Q“ˆø]}ÔXgw·—ýiùEHä 9zp—`íb™_ûO–”ÅýP΄GŽ™0u×Öµð¬fËΰKÁÄŽe‹¥´fz æjÁ ¼vùüÖ7mÁ—2š‘žÆ¬ghH˜&+•%H½ô…1d`þÚáêüô©ZÌ3YØ33Kä5òûÏ<½þîi¡ÔIÀ0èÞ¼va舱í;u…à»i«Ööã&NËð;>>ò¢oaÚÍDÃìé`]»rã­?AýXYó•s­¤LÆúU 7­Y†ïº• ¡!@rA‰áâ¹ã‘ŸÂ—,_¯. ¶ ÖÎW’šš w–”# –”qI°œ\“)‡|5¨à¥Jˆ‹1f"PAh³lP%%øÊ›WO†à<Ÿb+ó½êìl}£ï»/ æ–ÁBò§ùðã'Àââ¢7åŠP˜E-ZÙ/—¹ ¢þD¸?nK¢…Û½c—žFÆ&ðcÀ w° ù_D[¸õÆD[—× í;U$`ÄÆFçåæôèÝb1øÖ¸©(†ˆÆOù©äpIÁs…åÞ}!d`È®ÆPŠŠá‚äëe8jà…D^)%Î*"©Ü>¼1°ñ/\FH ’Ûׯ4mÞjø¨ Hd‡„Dáùÿ™\W/žž³`U©R˜²£I<â l»¿yîÆÜ‡ù­ži}JßÀÞ¼xÚ£÷33 è00‚Äk‹’l‹…‰†ñLùTt¡¿Ÿ† 6~7:tî3AhpbÁáWĸŻÃD–EU©]¯>ÕÐPSVQªm\K^^ÞÐ@¿¶‰îÎõާv[žÜ¡yf§ÖŃÍw.`¯u.]7U΢¾©Ryl·ˆå:ïöúE×n½0üЭè2R£ƒ_áÔ±CPé!~U¯ù(>j"Ì"çO ôó7i:ž¾÷öÄavmæ,‚‹³ˆæÐø8@9C1UáONN06#,DÄ[~>ž]ºõÄoîZÍ“,‡,í¢"öȱ“°4ýˆ_½Þ ð󅶉H4¤$íÞ¶ùppÕdrõȦߪà |þôAÿÃø^÷ñ~Û¢¥‚18}mH¥GV¬%‚oUgëÄ"Áœ×o½²²âÌLKø†ã*Ž1i%óÚFØÇR£¸–?þ PëK^~.î9zVöè¨HÜ€rˆ×Sġ6+ »EÓüÖí%Brq>œ¿BlÕ>^ýÇÒ3‰­­cä‘A&(GHñ8çiˆ?Fù.V¢s§8OŸ»xÅúÝ[7PžN#ŒlXå/= ;+‚.bc¾ ¨šîßZ·yö¡›×RâGî9ÖyÛž#8šɈÂÒ÷ß§ÿ`œeƒ%¢†`jEÖ&%Nz¸¿Â‘J¸'*ÓÉ£pä.gàe‘°Q.‚Ÿ•™4e6ÎbÏLÏ€O Ús§ÿÆ »8ë ;1ò=øc?†Þ‚ÌT„!} >{ò¨ˆÚ)½|öhÊÌy¶î…Ï&m$¥ Š%\"¦ÎêÓoHTDÌÞ%ÅÔi¦”8+-¼qõüˆ1ÎÈ €YÔËÓíyùV 65ÎYAA”àd"˜‡·—q†¼˜¬ECY<æØ¨Ðƒ$Ø‹g»÷€Ó‹1Øp-À !£8g÷€ÜŒdÞ‡Û+Y9…ùKV!ø»¯¨ºžQv4 %ÎÀðÝ[×¼lÍf(Q‰ñ±öQ_C8%×ËçÆ9Ï€ü]˜Ÿ •R€¢Š‚O¡A©©ßö‹hå:óÐpF-ŽÌÒÑÕÃzuîôQ2€ n÷Gÿš1gÑ´Ù ŽÚGÆ_‰¨BØ#¨sÍ[µÞ°j \¯ž_ï²§À!—lï®ÍÃG3~ §¬4æKÔ¥ §„a Ë+åå\€áßÓýͺM»JKø¸½påü„É3×n܉° œòDjÔxäåá†ãË0Ý*­QâØ­íÛŽDˆUè– ý}%^PJ8àöúYŸCp$ ¯@‚´½3' :jšÁ"DÒ"ùíþ=®¤ŠO¥²¢¤ÚŰ´âŠ/”6õèÙ'5UTn ì1ù…AüMêV¤>P¢¿°;Ó·ìmvCq>€À¿£ˆø¬™‚²2¶LI'g›×aÔaÇâ×õ㸠&%$e¨ƒÓçLGföü8ò?ÃòÕ.ˆwBbÜŸÚ@´kæÜ¥Ø¨Ãþj¸É¸èã³Jç Rˆ}åÂ)hƒ‚è’ߎ9™iíSÊWuÂöÉû/Y¤§¯÷êųf-Z㩊²RáÿßÞ Ùe_C]Á,SUUNMKCF¹,£d|ÇS“Ü2nzR½Ê‚¾h†&Ö7¯›œ¡ëªš_Àdû¶Ý‡q¼ÛŸ½ª¶š.©6p6K—œ«„~‘æÀ/á@bìa‹3/=úúzO?À‘舔:€§±mP^J±¶¾¼²Ž–‚Ž^Rtra>[·¾ "ù’c’4™2êFÚ¾‘B›$¬©?£üa¹oaŽí;C¡§¥q˜ŒÃLÅûí`p.jÊ·d„é7nÒÑ2®—Ïüò&à›¡! ­›"I:&¶"mñG°ÑïÒ ä+‡{h[v6÷TâÞô¿6•—/%|¼pô#èŸéFœF!ý#å ±p‚ÿ”ÕÑ…4ppY§n=`—¥YAsà¿Ì©SòeTÜäìåMtÏÎ IDATŠdåÌ"Yfš\±¼–ŒF™Kž›¦_NáØ¢JÂ@k¸G×l܉ $\QÃõÒÕI ŒgÌ]„Ln$"Ÿ>~àG´Ð¤iË«Ï ÛXªXMógs „Ã,a‹›ŸV§žÙ’ë_<¹/,k_L^!Ëeåº-|ÀˆÒ–@˜Ð`5Âm")<<ÈëmÅÕ`5R-] Í©ã€Ô)‡Še¸' àËÆ2Ü3؈øÇ– @yüÿ¯å+.ûµеKpþ4¾Ò@‰¤h@6…¤PÑxhü$à]Ü›öãÈ‘†cû~AÊ9€SËfO#åDÒäѨHLÐPû¡C»k ©t4hÐøs ‡{¬¨Ä>ô²/1VÒˆhР9 Å«Jb @¥5U @žQ„SŸå$'‹›`Š‹JpÊŠœ¢|Y)§¤¨¸”A+Ub' Ls€æÍ)åÀØQÃ,-¾ßÝ!šÊˆÏ—®Þ C?¥9@s€æÍJ9 u €›5ˆTšÊÎÉçhêË+èhË©ë&$%âR$äçæf§Çã¨Ë†hÛ0+î`ª´‘4Íš4hH' ý·mÛ†¤Í××700°cÇŽçÎCa£FÆŒù°Qó§¶K'ÓhªhР9ðƒ7Ñê«ÿu‡oþf9²q jiŠ…aÌÔ·Ù±÷¿‡r"K’žÆfûfË&**øç·fFˆ“†¤9@s€æÍß‚~~~ý£¢¢<øåË—±cÇÆý{ÌoA?M$Íš4~ H— [ÆVa—F)«nÒÔtQWß­­}ÙÂÂÕÜü¦…Å1GÝ+JO¨¨¼46ÎgÊsòKU”(nûA¦·¶k7möRñ‘téÑ¿k¯âÃKrø˜)]º÷«*BM-®ê[¼ð+7î©gf’êÀWuïÃÛwîñ#ôˆ~wþÒ ÖM¸—òþ8µdÃE×È÷´ª#ªJÈ« üãܨvÕ•¾XmŽU¯ƒHzx§Æ¢ª´Õ sõ^ÿíÞÒ×׿|ù²¬¬lzzúoG¬ï}<®œãÊuøX4hÄí‚Oå^> ;Wçîýp,»°0.ö‹ëåS¬¬,”«ª©õè3¸I³ÖjêÙ™™_"?½|v[*1˜Ì]zÙÚ;éé èö=fòG¸*e @™ ‡Á½ý·sëÖ Ìutòò a’-µS~ÍTLsc*1ÀaÊ “ÿí: :îþí+!'± -ZÛ´qG`gU>-mÚ~ù^X@!…CŽ·h`]Ÿ×¤¹ ¹mTJ˜Žžþ¬y«üý¼Þu­”Ȉ°Ja~*€DÈËeÅÇE7kiççóVµ”ü/ø¨ªÔn[¿833£n½úSf-IIJÄLÄ)NIjJ¾â@Ö$LU¹ÁK›­}»ü¼\Û6N¤`lR{Îâõ>žoþ¹sºŠ‰IÝî}A ßÂݧÍÇ$>vx'qÓ“ˆÆJÇDFÄÓ$È\®’””Ý»ªÊhÈ(ÍÈHÛ¶~‰œ¼\+[‡Ñ¦ÇDGVC-” ChTØ£9œRš4¤ŸÙY™Vš)(*±¹w¶¶w$%ZuM‡r>êÐçOÁʪª 5epϸç^);{ášì¬¬3'¤&'*)+[7mÕʦͣû7ñtÔ¸iõê[Þ½~1æk¤¤š/e Žþgr-?Û]]Í55ÃY,355&“Ëšmý‚A} º÷çLÝ@“Iiÿ—““ï3`äý;W=Ý_<úø>èƒ~7maÓ£Ï]ý¼¼·—Ý_=A¡½CÇæ­ìrXÙõLÍߺ=ÏÏçÞF‰šñ“çÍØ¯‘ׯž!MûþmԤŧÐ䟼?ZÛ9ÆÇ|‰únkïH("#^ÔÓ7šµ`¥·ç›'¸· ó}º÷ÔÖ±³‚‚"Ò ¯œ?ýå3¢8ÒS¿ÁÜXÑ„ìlŒ*Y&óæµ³„x‡Ñ3bÌK«&Éü}<›µ²Ý·}/ZdÐ5[ÙµS—þpçÆb¤’0 ’\«ÖmUÔÔ¾%%œ<º—1“OIPÒÊÆ¡KÏþZZ:Ðe¯_>ž–‚Âu[¼}ó¬ióÖâÑ_#]/žÔÖÖí7p´‚¢Âj—}P|woY °¨ˆPëÆ-D‹\‚üÄ‹ˆeÚº~ aï7x4î‰{p÷Ê¡|ãOUUßwßïy᥶Cç^pÝ(*)G}½qå,Æ/[ˆß¥eÀW½ñKT¸qíº2>ßA$æçëIŒø¢n½üµk×9vª¾‘ItTî+dþsë2ýÖ¶ðx `ár—Oïµhi¯¤¢òöÍó7/ Ö+¬KCÿ!c7kÅ)å@yüÏMl„F&ÃÇL62®3²B>\½xVôØ <§ŽìÆíÔ”+<”o¡ÐÔÌRK[÷Ú…“CFN¼sãbq1”q™ÞF„‡|Àø!Þ‚bpâ_J°Nž±¨¤¸øÔѽƒÛ:uÆÁOÞV-šÂ._d‰O¥`šZ†Œ–à €+ç+ì4„]>{ë,%*Ê®leÛ®YKÛRNi§ÎaA˜$=<'¦ƒÉ¸{ãRVfúБ“44µßy¼üçÎU’rXRfÀSÎ8²Æ?àzùëׯð×®]»AƒUj‘G)fY»ôÇ«þƒÇÕ†ùͦ\sX{ïÞ¼ª§gÀ·¦ .›ñX¨{ö"+ËÄeUšûUjû Ll%Í[Ú)*+mÛ°”rðcrñu X¡¤¤ÇÅ}mÖÜûµ¼¼<„¬ö&uLñ¨vmÓoßBƒð»8+‹ô®;8u‘WP„‹ ¤„æRXX€ ‘ ºN]³V6mwm]IL‚í.2=×Àdš**–æç[ÊÉÉ$UXO[&$ȼ DËUá€QˆÉ .ŠçãE­:õàC ô{Ç[NÄo° .ž>ô-9±vÓéóV`wŽX«&Ø [,=Ä‹ÐB‚ƒü®œÿV¨AÃÇ1¡ |u™˜ÔqU±ˆð=‚ÜÿÖýEÔç°E]\4´´àÜAÞÕ50˜½pµÛ«Ç¯ž=àC…?Mj×Åæ±gÛj\u‰H{|0 6>´w3/ë&-FŒ™ºeý"`Ç‚³qÕ\UUµis– Ê^P*Lë78°sCAaþÈqÓzõÆw„íª¾EÃ#û·df¦ƒi%Ü»Ù(>– ÷<êäß{ãc:v›0yî¾ëž×3³<¸×ïÌZ°“÷ÞÿܹÌ„Gß’“ì:Pàå)ä§0x½±g>¶72"¼s÷¾˜6|-ZÙwèÒóØÁ©ÃG;?‚©lˆã‚€( €,Çà™8u¾Ï;·W{šÕ·œ6{ß[PÉP²wÇZHŸË×î úø^|ƒbßA£tt vlZ%pÚœ¥ù¹9¯_<êÓxXPàá½›ÑÑ&uêù™ãûDDÝTJ3@9x„½ ÃXð‡@ïÃÆ4nÚ?Ðp €Ëgð¾BFLaá+òrs`í€B%ˆïäÑ=Phy…(|q8†IÚÂÖÚln^^ÄYéÀ&:+)!nÛ†%p0Ö53DB”C%Ø•ˆªjhÕDX¢’’¾Ñæµ X2vҬϟBïß"¯ °h¹ xû•rX Ì"fœ°†üŽå¦¦¦^^^È€V%ú%>J>®  šš¬ ¤xpׯœ\–­ãø)s\Ö,HKKá[Ó(–Me ,ã[7,†Ò>wѺ*Íý*µýO6·´:°gÖaƒÿÛ·D¾®³±ß QX07­ž§ª¦>cîŠÂBn4æ{o·®= €¡q-ØCÅXáÿTÆÒíúqÊ9N?ï·Úд¹mäç0ôDyltä‘zõò!!.†”Í0¶C>øÒ?US“’$+ý£ éJA¦ ÄÛÏEEálvdii¦šZ†ª*þýdžÿn\B‹õ ªƒQ&ˤ ‚° ö!DA°S#ÂC`…h‚íöc€o}ó «þ„!ð¼qÞÐáó"äƒ'n›YX "„aflÁräÈÂæøþâ”oI0 FaxjR«.D¥BŒ F^A0¤†#Öe¾Ja_'â1ÂB?BC€,€­íaÅ"xO7~a°¡>¼çÊbeæéƒÛð„ð¡EÌÒ?·®ÀN0MX趃Sg×Ob£¡™¸½|¢;¿žÊýõODxpí:ü²8SÈ.PVV嫚÷OJ~ ƒoÚÂösDú¦»—Oï Æß£;Ü_?EPZýÏ­«0áìâC¸pù¦M;/[·SÔÇÓMXud9äoUõWÏ‚ ˆÛ ý ø !çå¬ì¬Ä„Xt¨ L ûM;Žßëw’0z=üç:‚Ê`Ê‚¥.<cuõ àxÁ¾W®ÍVãC9x(ñ ’§yk{ÿ÷^àíDÖ R,†nv67~Qð \=3s_owJéð@>  ÐSÉIñ|(9Yùí›§PJуÏÞ¬´Ò _‘Ix0°"sý<‘Ÿ‘%ÂPQ& Qþèþ ,ô5àZòresNB¤*€rX Ì"fœh~¯§ûŒŒª*ýKp”b%Û°ýÐæ]Cgƒ½?õ[Vu,˜è}¨ú††µY*lÙ„ #ëd|ìWʹ/ˆŠ.!9àþúvvìDÂ?e×´°ióäþ-x¶1×àá'°…jéèqûÎÆÎñƒ¿7¥ÈE3Ÿæ€˜&ç| Æ0ƒ ØÆ¾Ÿ÷÷˜Ø¤Äø#û¶ê!2ÖeçQ¸Ó±j¡.UuuD£•"g‹¾0ž¢DEM='»âGøÅ$¬R0)ó`~30º·©W¯¯“ÓÎ+W°IÀ1j1¬V,–˘eC"rÊ“€ùÅòòrÁU5A Ž©9’õ aÿƒÀA.9Ùçeò- ÿ;$NY0€–o™(È˃]GÅ6öŽŸBrr¸h+bgïôúÙ„,(à}AArDŽìÝ É†'Ô¾{·.÷î7á%aÁ! “`@NüÆÎFAQ ÿ#G9+«BøpBnƒ#{Ôøéþ†Œœœ,8óÝv« 6Á<‚mä-ÁbjjÖV7²P]C3-šš Ù ˆQÐR¤Ä£¤¨Œ¶S>" )ù) ^CK;ýßôM0A™˜™ÜÀ^Ž>Å+‚zÂÉûÓÓÓòórxÕBa•¢\SC;'‡+0YüJÊ¡O’rpq$ˆ‘<5ˆr¤Ì.Y½ ¿E“sÖ¿ùåØÌ4µµQMï~îtÉa±^>ýGt•`]D‰ààä‰$ŒŒ@üéçã9gÑuuÍÜ\äZMM-Jü99¬·¯Ž™0® (‚0§íïÖk üŠ1mœ†’cèA’½ä'ß6°ÃM¨âÈæŒþÉÊÊô‰ñ‘' À( lYK_WTTD®òÅÅlE%î¼ –³ˆ'‚†ßëFZõ–à(Åûw¬/.)†âMƒ!äÔ©;F{)‡ƒ±aó‰1C¹lþß`RÎýêµ÷?ò‹U!ýü‚]ƒ]Û7<½‹2ÊSñÁ"èïÕÚÎáÑ?7àN¼øÿÞËÿ?éfJÂäÈþï:vémlR7<4ÈÐÈ„¬ÛèSÜS42iêØŸ=ºƒkÈNÌרÏ{·®iܼ•S‡î(Ï_]“»éã#A}Uº´ðt··ÿXXh)GA¡¤¤i²¥¶*odÓÝeË“€á Ê‚?!™œ„\‹˜þ'nBpA¯ íÌøWy ©©ßeYA0%E…ü‚ þVÖÖ‚O«QÒ¶Àƒã‘UÀCœbýñ/Ä(â~#å¶ ¬Œ-SÒy²­_l…¦µHêÜ ‰çù£;8¯g/òG(©Z~S§¦õ-/œâF%IÕ§wÿa[ÿ¼Cñp"6*Þ8´_ÂvܲŒ†½k6í»åzž8³ì—´BD¥ðœNŸ»býŠÙ"`èG¼Hˆ‹whç˜R¾ª¶OÞÉ"=}½W/ž5kÑOU”• ÙX}¿$»ìo\»ªmÛ6öØØØ>}úXXXܾ}›²ã<=½6læ&Æð}¤y”òÓJÿ-À± Hç Np–rhhTá¡¡Âg^,úúzO?€™Uj©óø+´°¶óÏO)ÒÖ—WÖÑRÐÑKŠN.ÌgëÖ7¤˜“„$u#nàÊ/ÿÀ ö€ƒ,S’tøËI" ÀIDp Ä®j§ŽÝq†ŒôÐFRòðž4RU=F!L ©ÏPî*ƒïÝ«‡G‚oáP¦Á†ðd 5!>úGðHö]ÄÂYY7ÅéLHñëÕXOîd+¢±Õ<°d5jĈâW-£T|úiÈšç.k×±+üj¾jºFšÒéSزjšíeÕÙ±r 00óäŠ5 eÔË‹å9r¥¥u-‘»›)£H™ü«Ø #‚’yò« ¬âòœÔÔ4pÌ¿¯—b (ÁèBIq½Ÿ:i²º¹|þoÁˆ$UQáAÊ£•uó;×/ E5Vi¥!h§{ŸÁ£'Î,-)‰zp¯òk³+ÅIüBD|Ž ¼ ;vúö-_J’ÌW.£”’xºP8€ M\Mø9"´zǦIChhH„R$‘VÑHhР9@s@R$H$]Bs€æÍšÂ8 u!@¬\ÄçÓš4hÐø¯p€^öÿ+=M·“æÍ?ŽRDpXA¶˜S‘Cœ,SV®¸¨§¬È)Ê—á¤Ã"œŽN}¥Ô×;tƒhР9ð‡sÀÚ¢¶£#÷ViÞObb"þ41á?ÁÖÃÃ#42þçÝ<š4hü|H Vœ3Z%¢4•“ÏÑÔ—WÐÑ–ÓÖMˆJÄ‘º&¹™Ùé©ß8L_¥–¹e*?Ÿ?t 4hР9ð9éàÀïß¿H•%%qÛ¯]»6oÅ666ø34òêO¤†FMs€æÍÿ¤Nè ®Àþª¢¦Žã>ÓeX2ÙÅ2Ù¸ø gþ'GÅâ‡\™"#’åÔõë£<îùëô‡æÍš4~kx{{÷îÝ»Ò&<|ø°R€æÍš4Äá€tÝ,Ï(Ras¢”U7ijº¨«ïÖÖ¾laájn~ÓÂ☣Æî¥'TT^ç3å9ù5É߯¡ý‚%«Åaœda Œü}N§c‡.³ç/,§KÄäÀªu[›·àÞú<~òÌ^}‰ù%ØÖ]ë[4 |$¢ðW($U•Õk¸hÄZ ܵïXÝz¦|$IáíX/o IDATÔÃS'Î]v!·ø,¥!iü9€[ÀÖlÜñlø¯jr ,Ý¿ªiL½Ò候¿”)SRVæhnÞÛÑqÇ¥KˆUUUÅ¡±ª±9-rr sj±”q5˜²,nÝ¥¾ }Ó²µmÏÞëÔ5eFGE=øçVäçp‰÷j™5+—””¤|Kzpï–Ï;ÊZœ:ÚÙ·Û¿{ åSºP,Z¾ÎÔÌbñ¼)ÅEÿw'‘8ïW;ç䋬¢¢ú×ѳ€äpʲ³3½Þ¾¹sã ï1…â !a¾~‰Ì/Èó•‘c&)«¨ž9Qq_ŽpÝwøÔ²Ó333ÄÄ «§¿yÇ_3' />7€çÞíëi)Ôg2⩘5Ц§ªOÅï£_B^U›CÓÀ%‚ùˆû¿råŠh.­sÙ]§n=Àäæ²ÂB‚/Ÿ?…”¯lßsäøÑý_"ù¥¦ iü hkëää° 99îÕÑÑMOKýÑ8 :tîѵ{¯k––”@\•éܵWûN]]Ö-/--AwwïÙ×Á±£¡îú‰~öä~,Z¾ÞeÝÒÔ”oÁ —­MJŒoÙÚþÆÕó¾ÞžDaŸþCZ´´Ùæ²JR’."=‡Á½ý·sëÖ ÌutòòÅÅŲø”Ú)¿f*¦¹1•˜à0e…ÉÿNí;;éúÕ‹íÁÍ\Vš88vGÀCUe+&íª¥8ú]ÎÞÁiòô9_¢"ÒR…ŠJUENÓÀúØÐªIA~^ËVv´,Ñì   À÷Ýš™‘•cÞâ•ɉ Pªô: ü-9ßê½ûóÞª7ÞyºIÌ2§T"%ÕG!†F"tèÐ! 88˜ÄÖ¤IVªþÜ©#žnPù¦Ï^8dÄXüY%’$82«T/ ,>þŒ>Â]ºjSB|ÌÑ»±Nš:»I³{¶oŠ‹Ÿ4¤4sÀíÕS;{‡~‡Þ¾qÝ=pèÈ};\ ýƒf穳Í-\»t6*ò3bÛX5j×¾óñ#û^¿|<ÞyÆžíãØ¾“žžþ¡ý;‚>Lž6 ˜3ŒŒkõè=`»Ë*˜&%Õv)S`Mg2Ù³ÝÕÕ\S3œÅ2SSc2¹¡>%ÚúƒútïÏ™º *U“IÉ99ùÁ#ÆÞ¸vñÍ¿w úûyøûC+»þƒFèéäæä<{rÿÅSn8)ý6vmYYYfæ–¯^<ÉÏË%8‹…fúìE Zá2²¯QŸ/œ=.LAÇf³KÝ^=>jB­Z0B™òÕ‚û¡†¯¨¨ËSaaÁ†Õ‹Ad¿AÃìÛ8ªª©'%ÄØ[q³=ˆ0x„œ¬ìÃû·Ÿ=¾OP"+';iÊì–6v™é—ΟŒEyÿAáAª©«ã¾×ËgÃÂqæîÇöîÜójjê;÷ÿ½rñذ%5V~!ž6í:ÄFG} ….G*GO]^µda6j<§¤ôæõK ž0×_¿õxMÒŒ ÔääGn£¤[ϾÐÈ•”•?……\<{‚ÒRˆn…yzcħ°Ú°,¾ýÞz‰½ót'ÄbÄA)ߺ‰«‘›Ôª3iÊ,CãZQáé…ì‚ëWÎÃ ÚÆÁ ÎDy­Ù´=kkë ¬¢òêÅc²‹Åa,?FŒžÐ¼• NÇòô|s÷æ5PˆaÂ䨷ŒSö!àý™“‡á’ÂèÂHNŒ+ÜM‰œä1þ³³²ê™šA˾tî„ n€ “ÇÂb:eÆ<*Œkijiççç8²Í¬Ñ¾Sïþƒ´ut£¿F]8sŒ0i ÌæåóǘkJÊJÜÅÁJ)g(%ýD!_}©…´Ví:è¹?ß«—Î ’‡éÕwŽ®^VfÆùÓG?Gp}ƒõÍLž>O[G/ÐßçÜ©¿‰•šrê )ÎÔŸ[´²eeeÚµuÌÌÈ8~d¯eë>ótõâ™÷>^ ²‹áüì;`h§.=ŠKJþ¹óýÚlu ÍQcY7a½zöèÉ#î•äÆçÇïÞ½#ÛRZZÚ²eKqšÆ)ÃYq¥©)É>^à3f‡àx5ÎÖ„Yó–”—Ü¿{ÖWÞõÿ­ÛKÁY&lŽ ßûcwh¯¨ ðþÚÅÓÄMðâ,/â4‡†!9À»z¬^6r…Üñº²²2Æ€u“æØ&|<Ý „s,ÿ ³+ñçÚM»þsÓÏ÷ûûÙœ5n²®®¾3ç-ÉËÍÅŠ„ÇOž±uÃÊŸ]õÏ便=|ôD«F1/°þ×MÔX×ÔlÔg“ÚuЮ—Ά†|D¹àZŠBaÓíçQþƒ˜‰Á‰¶¨¨ªFEFÛös§ÿ^»i§Ÿ¯×ãÜ^?ÿú5™š™CðÛ°fqbBÅQfþï}ðÅ£{·®¯sÙ GÁÇ¿¡#Æ9°Á!A?ø7éÔ±§Ì|òð.ùâ’M¼.] dz®€É4UT,ÍÏ·”“Ãa¡-dÞâ·*|à.“!C¥aœAöõõæ‘× -”q•…‚‚ÂãGö''Å×­Wî•/QŸ!Ù£¼‘u³};7a¼b¯µoëDT‡ß|N?È)ãŒëÀÊų!{%%%lÛ´š•“íЮÃô9 —/šYÄ.¶×Ö¡=¡ØÚ·‹ ù3¤°½m»¯_>‰éÞs§¦¶vv¦P­Úö”é ýµÂ}Ͼ1ßøzÍÆÎ¡[Ï~ûvº¤§§ŒŸ4câÔY‡ömÖ³ÀfVßücàÿOB Ìd2fÎ]òÖãÕ“‡÷,-Î_¼æõ«'|ÊÊÜs«\Ö/ÃZ¹iû~²‹)ò9ºëÚ ç/]“—“óôñ?ƒ‡ùà¿sË:Èîµëšâ¬Z±x–88 kë¦;¶¬‹ŠüÔ¬yë “g­\2[Ä»Í[ØlݸŠÅÊ:r\ÿÁ#Ξ<ÂWc#ë¦CG;¸o[\lL—n½fÌ^¼yÃrb6ld½Íe5dk<(+6CEЃGdaÎÞ»uõsÄ'uuõÙ –wéÞëñƒ»|ä5iÞÞá¿v}‰ú„©Š3† ä¶m÷îr)-)]ºrƒ]Ûv^\‡åÔ£$Rœ©×¤ióSÇ^¾pjàQ³ç/ô÷]±xv£ÆM§ÌÁoh›”]Œ‰ÜΩӖ+ssXSfÌ'YÁó5jÙ¢™êjê –®ÁÊ$¢%µOÊ?âÇwRå+0µ —áv£W.œÆ&C€ mð®ÿcÆOœe¨…r¸ö8ÔÂÒjÛÆU…y°Ñ [¯]>[¥å…’~º’äê!l…¡Ü!H)(*"ŠRM]A¤ùÜ(PO÷×°Ú €I­Úzúúü(+ýI…X6—¬Üs!–\¢ XIŽÜ󓪫´˜kX‘âãbW/›‹ ³ú¼•"–uáÒµXú`é€ñb $`lâ”k)^¤œn5ЊjWanÙ`ûæµx}éªïÛÂÂabX¸l6Øò äXmâ(…xÈgO¿hÖoX‰W\/ŸÛ´ußô9‹ååÝ¿Sm")_”|-e5âr˜28êçsQQ8›YZ𩦖¡ªŠ?™ç¿—Âb!BªˆÁ(“eR†©©ªƒÐªk ùƒ(¼'0Lú½÷¶´´"`bb¢`AÇoÞ8oˆ)žoØìB(a÷n»ZZ5DˆÈ{žøëÈÙi3æcõONJV ïëNº\¿z.‚˜ÂBîj †Ì-×Kãc£¿ÆD!âYQž””ˆ ¦-Äa›oÒŒk ó}÷Â= ߺ¿Â¢flÌ=,Û¶iI¿Û¶kïõ¶ÂàAIùoTˆÔ[CC#ßwž)Á6ÿ*i”Mhec„^€-ðñý;ùå+>ïöø—Ï"º¬¾~åLø(Dµfãö}‡N¹lߜǛW‚|%¿áôNÁÔ ò|ž—;v²²2Ѳ‹y!íÚ8ì;|šøB”'ÙµuºuãJ~^,(îÝ„K0J aÑ„øý…k`¨ÆF Hÿx1((@GWŸ’$Zø= ýãÏàþÐ]«ëй; ÒÃÜÆ>y ¥£­¯oH€½xús“˜e”•Š3wxkäë#ô)Üà Ø ÿžEŠ9‹©÷ìéC8v0õ0SR’ „*Ø dC+«÷o»(§%‘âL=ðS=ÂÿpûÆe¬-0ö`ïÔÑÑ”] sÑËg@*¬Ë÷þõ@j©[Ï k0À óâ×µ"Ø¿K ÔW0Aj`a#Ç8c5Þä l7¯]g<ïúOÙ€¡®í;w»uý2Öa¬$È“±±åv˜Ë‹°&ÐåÂ8@®ÂVÊ=â½[®˜;˜A¯Ÿ?&üà§£§glÂÝ4Û:tðóñ"lpª–x9Ö„@º 0cÉÚ¿kszzšÄ+ª1„ðB#úNׂ‚,q„[•¬ÝÖÞ" :›BdDxxhPÓf­ð”r-E9åt«±¶T£"l7èD|Cƒ?’Ûð€°Gûûz“‰‹jêÙYæKøê±^_U55ÀÃÍþÎË #óæ5n,ñþpùâéV­íᯖTô,‰\º<0ê—1¥N›zõú:9í¼rÅÈȈHŽQ‹aµb± XÆ,sR! < ˜ÛÈÍË[ÁMAÀ´¾ÅÀ!#ŒL kCÊyS± þG°Ë‘¨1pðˆ–­í””U8¼ó@ IkË®ƒðôI#ñ«ÿ–õ+ŠKŠ ™åÂj!«PPTydªYŽè ƒŒ, p2Ó¿çe¤¥ÁxŒrx*ºôè­¥¥Ã)-E<Ì(ÄVšB°1®U' À›Dþ[ÿ€ù?44ˆ;½½Ü;AÎÖ"-mÜA<Ål\UÁ.2š ݇¥_K›ЇðÀîm©i)ùy9bÆÛikédgga#ðd¤§ RXÈ.$²¾ð¨¸ˆMv1/d ÿ{Ät%*ªj¶pÍB0jÂ{€Þ'ÊA¿¶w\»rvÐш,begÃ6P½`ýÜÜ-ˆçpJP— 7H I`\ǧ¤DqvV ‹†m;’¯hhirvO4e¥•Î'ñƒ¯44´† “¬²rä§ÞB`oÇ*;»"Íš]ÈÖ(ŸbøPN=J"Å™zÚ"LožÉŽ?%¬‹áï"ÓŠÒþÍÓÕ5€¼që^¡œœ\bG[WŸ¬Qƒþï½á¤3aê®­kaEÆ£-;p÷Ÿò§Ç+bÉ*â%õ7ý ½ ÑS²rrP”Ñ9YyUUz¡£!«ÉÉ+í‚ç‡U.`!°»¾¹%ÙXM M¾†CÿÆzDBDþ,ª€"„WÁNCÉ4vò%ñˆPܹõfejjjAo$t]Ý´jêPTTDžùƒËïˆZ ²‚Ђ@f׊•‘#ƒ02­¬›Î_¼2,$n,Jšbáÿ׈LoOw"Á†¯R¥ÏŽˆrœ¯÷ FÁ¹Œ‘(a„qß 99"pò>œzx*ŒÈœzº~ uM-‚*dd¤A…X¿j˜Ú©˜í•0+++^IŠIXAA>1Axêñðÿž^@’‹³°. ¬XÆ!´ñ%nй¼P⤠Ep€\=(WÊ=R–M,ò„5PCóû^àåþfúœEX0±ƒG~æú?kò3nât"î¦X…ˆX s“"5IŒDꊤ­£ƒÍ”Ò—/exhÈу»øê¶–J„¤_ޤOÿÁˆÎ@ŠF!7»wÇF,50e ?ü“ PL!ðçµEúB€28"d‡«kðÓ§þii™™™Y­23Kä5òûÏ<½þîi¡ÔIÀ7¯]:b,r) ¸Ãn ×ɸ‰ÓÀAüŽ…|1¦}Ñ<0,CþÖµkå7ÔØ(ka±XÚ `îo^À2` ¨gZ_I©Be¤$ÉØØ&p@ÂËohl«Fq1;9™›hWÃïÆ3È^ÈE~°Oþ+%Úߥ°E+“±~ÕBèÊø®[¹aí:‚þ¸¸èÆM›ãVdX-‚æcݸ9ô+ü‰X:Œ¾–z¿óèØ¥§‘± \1踃«j]ˆ‹‰¶.¯ÚwêFàÎËÍéÑ»?tËV›¶ä«÷ÿDê3\Rð\iéèôî;È»üdžÖ¶m Šeÿ™“Ëb2åpÆQŒ”89Y%?X»ˆ×ùj|óâ)N*03³€Z‚ ‰˜4ÈG”sGœ ¼ŽÈoHfò N»…|ä¹½~ѵ[/s‹† S™Ù"ð N= #òǧeûx¿uêÐ-BÕÝzT,D0%'% 9KØ‹ígˆhÈoôÈÓÓÓÉÉi&Ï¢°M ÀƒÉ"¬ß)»@XÕo^=1f"ÜÂÀ:ƒ¥?~|yV]Np€r…¶'úùxvéÖ/r×êŽk5þDFfQ{äØIðšRZ*·qDT\xö¸{Û…C“¯É,d‰·+RB\,¦V$8$±òVñÞÛç©Øµq„%O œ°µTâäÕ{òhU›‰˜ì)3çmغ>¤#)  XÂ8aê¬>ý†DE„½÷ñ,)æ,©1ÆY9ðòt{^~†RÉqª ²ÏAÉÙG Ïé£û·ÖmÞ…¥vÇæµ8à C‚—{’¢‡Ä›·F¥qÎ3 ƒæç‡‡C%³Rʹ#æ»CˆóÔ9H%8âSh“¦-PÈG^ÈÇ€×.àè$DáÃgšL¬HpêF‘?>õ(»"©±I­5vdge <Œ ›Ê‘»‡·yçîÙI‰wn» Òÿ;–œ/ÿH„rÊñÌ8 läØÉ£ÇO¾éz:3o]”] ŒÄýÃȇóÕ540–<Þ¼Æé'?¼¼«Ž.'8@¹Â`¤Ü]¯œŸ0yæÚ;ŒÎiÞÒ†d£—‡Ûàá£þŠÔ[ìV8y:<¡ À²áï›Ä–bE:zp÷ȱÎÛöaÈ0>úñ¦À/·w×æá£Æ?…SVó%êÒ…SxKØZú»u'N™õèþm$…¢-°Jc£™»`EP ¶‰SÇuïÝѪØ"‹ŠŠqvüޛȨ§n;ÃÒŠÐ?(êE`Sž}RSEå¦()*ä}ð7©K‘H´²¢îLß²·Ù 9į`mÆ¿£ˆø¬™‚²2¶LI'g›×aüÆÝÊpÿ'ž#º Oâ@ÌÿDƒ¥²‘3ç.ÅFŇöKÈìÚ£LÝU:oô—ÐùgTú[L½œÌ4‡vŽ)å«:aûäý—,ÒÓ×{õâY³­ñTEY©ðÿã %»ìO›89½{Wîh}øð!rŽŸ½úg º¿D” q‚ó/$ƒ®šæ@58ûEØâÌ‹M__ïÉã8]XRçðà4¶± ÊKù{gUÖÅqbèºD ÁÂnìîn]{WÝÕµu;×î\;± ¥¤kfèøþÃcgGfÑoÔó¾Y¾7÷[¿{ßxνçÞ›¯k¨¤¦ÇVÖ3ˆ‹ˆÏÉÊÕ¯a½6>2NGANËD×'Lj•¤UõgÇØvcçfX—ü3TVÖêˆ=â1ÚQ·^Cø&9qðÿ^BìÃó/ÃORzõ¾¤¡œœ.]º$š‚ŸŸ`-GGÁn! bW®\ù’Œ(.øÔµíØÙóÁm¢A~f2gdÉ©{²\”ÌòYò yРɬ|%¶œv‹§„õ:|CKxýDå•,ýü™[N¼î8 ¥Sמ·n\.ëA<…TcÓÉ¿ÌQRRÆBä{¶Šo@TUQ:²F€^½/i‘gÏž‰¯ƒRRRBšŸnè|ûömI^—Àisú¬!!?ÌNÙÔ¦D rdÎ`ª‘/§,ØŸ\l²&Øø97ÿX²¬üZ,Q9¼ß4Î+Àç›fI™‰À™ ø’Ÿ½z_Ò诂Ãñ‘’‚àD6ºˆ@ ˜6qX&HIï”@•ÚšemeóÒ¡b"@~$é%§ÐüHu¢º"@ˆ@… T™PáœËŒ $ŸÇç²àÄRTPdåç`—–ŠR¿° /Ÿ/OÆF™øè! D€"@ˆB@æ Õ\^ù~RnzV¡Ž¡’²ž.KK?&.‡"apF7åcB¡–b ®s¶‚àðdºˆ D€"@ˆ(?™3š'øY‡å„«kjå$Ëqå¸ùr\¬À'.,J°  H…•ÉkÜ1ô‘Ü'D”¿Î$Iˆ D€"@~Z²u°bQ®z.ÿ½šÆrZZëuuOØØœ©YóœÍî–Úëð÷ª«ß35ÍRP*Ìâ««J8ì ²±s‹‰Ó~ýÂDÊÝ¥y›1“f—_ž$+J`æ¯Kíë Nè8l|ûN=*]T~á² VÖ6Má÷¨rïËi”3#FìÏU[Í«YU(J%„…m]‰¸…"@ˆÀOE@æfø rEE-kÖìÖ²åÚãÇÍÌÌ444p˜Q”FTºCzºqº9OMpª¢|‘ôý€ê5lÜ®cw³jVyy9Ñîx\ÿZåíŠ\ÆLœ…dqœdrRÂí—ü_<©ò\(A†Àä ,¬j,]0-¿R'ìúù<Æ1Ìå‰óÉW®ß IÄ㦽xþøÆå˜Ã’ʽ”LTÄL[Έ½û@î' rÇ…ó;–¯Û¹ü™8¶œ)èê,øsÝü™cË–/? ¤3wá*œ-œ øîmÐùS‡qSvúô”"@ˆe2fÉÊ Nÿm׸ñëììšzzòJJPøqñÕ(¨${*¨*@ PAQšþïÒ¼u¯þ#®^8¸o[^nN­ÚöNM[–ÇP@š¼RS“×,™§ÈRlèà;+³^C§ÊYYa¡Á¢¹fÉÜ´´TK«ã§ÎKŒ‹}ñüQ…¢ …“ãð©\ܯ«¢4NÛûâÙc]Ýác¦ºõtæø¾¯W6J™"@ˆøÚdÌÀÖÿ òðìùëÌ™š::!<žµ¦¦‚‚ÀO©@×0»O÷ìN= '¬Á @‘‚οX,%·^ƒ¯^<åíu—yúúå‹€W¾¸¯ïàÔÙ­Ÿž¾!Æ/=ïyxÝ¿‰@8á4ttNçq­ª×|ìy'++“‰c`ä¸é5ll±QTxØ?§JÓì ù…y|(ˆ}65³€˜‘‰ÙÀ!cMÌ-øo^ù^<{,?6‹œ¶»W¿aPdaÎøŸ_YYeâô_³2ÒܽáÖs`pÀËWÂ^5³8Ùܳ.@«W`a‰míºÛ7®Ää•}=‡AÃ&¬Z2GZ‘tغõœ’’„„ØmîËÒ3xMœ[Ž?}Å¢Yùyy]º÷«aS{çæUii)xÑ çù•\5kÙ =õØÁÂÞ–ÊBEUÕÐÈdåâÙµìêb’ŽF;6¯RRVž3ÅK¿gÑQá«/­­;uëS½†íÖuK³s²˜Øµû€KçŽI«…"@ˆø È–€(T(õïòpŸÅbé³ÙPß¡]%'¦5OÓÚ¡e¥¥¥&/_¤¨ ÑHCC³  ?+ó¿AGa£††2÷Ð'^ûûÔ¨iËøÊ ^Šúy#÷OK¼>n^»0ÿÏu;T ;ŽâHÏ:Ä ¸&ÄÇàƒ@—ƒy‡Æ.00Èàe*Óh&;˜=í»ö266ÿ¼o‚k#,,*ÕܵÝÁÝ›‚ÄBÞ¸z¯FP€?f^žxÝÍHçA s>XɃ(«/­­›¹¶Cx<•xëÚ…I¿Ì'@"s $D€Ÿ–€lЀ¡ìcн©•UwW×u'Oš˜˜0‹€#5#yŽ<žÏ”WƒŠXP¼¸ô @ffœÔ54Åm‹ê5»vïghl sZ~˜VOçrÅ›>9†ƒªªZaQF÷‘,<Ñ.-™øõ—шE£­xŠJøö0~GZZ:=ú±ªQ‹¥†•R<-ÀÖ5àqRí_4;LY(±”ÝW/$í_¼˜'—–¡o¡ýã+|oœ]\Ë0´Ùº)ÿ« ¼VÒRJ%‹ „´Ô’@èˆ0EÜØ·c}JJrVfz9ÛEG[7=ŸÉŽ“*aÙnnn.ÜŠü¼\ çÙÁ“î4ŒŒººú¼?Öà^¢jjœ——Àîø‡^Û­ÇÀÙ W¤óx÷n]a¼’JÕ÷³_ñÊ02/~lqp.‚=\ìÀ3S[K—™)åÚ¶“Ž›_Xˆ¿ZZ³Ç«ÇtøRùº¶éôú¥4í?7'G襓———¼Jù¹*ª*Òª/±­Qx% 9©¤-äᕤˆW¾œíøY\$@ˆ Dà [€b:T'—×996Ðr”• ° @‘ßDý¡¢JŠ—¢Jñ"`ÌHr–‰‰Ž„ê7á¤ÉüóŸþ›×Îù>÷†f0`èX8ê0í'q7!'—¶uêïܼ#‘P—¬Ú†D°ÕÏÜi#Ä[ŠÝås'`Ô¶oð6èu÷Þƒ¡Í¬_µ¹„’„(œ´dm¶¬hœ¢)„¿÷68`Ê/ wl^ ÇqñÄòkÔ¸)¼¤–þµ(`S©©kÀa=&*•à$V|i¨k2“&ê^¥¥©S ”W]=}&¦ÒGqÈðlÉÌ B‹_¹yy°ë˜puM æ³=ZZlØŒÞÉÖÓK­Ôrpôáž?ÿîw„šæäd³õ ˜:b«nš Ì\N¶ BÏÄJ÷ñS}‡9®B‰KcÄ+Qá(ÐQ‘0—ÕwÐÈ­ë—éöëV,À*U8ƒ¼hlíàÊh½†%§Â½~åSÛ®tP„ÃD(EÌÏ×»E뎘ÂPq÷¾ƒƒÞ¼ð.rìÇH‡A ÍZ¶c„cb"³²ÒÛvèkWGP°*¼ü}žtuë‹™+t’özbO$Þ ‘³¦–6TstÔ"¬Z.,ÈÈLGïÒaë1Yc,.jX+\U%yúø¾¡ ]¼ èóI ‚%:vuš0Y<ó~ªxÅ`–T³°Îrdf¤ÃÿÍÑ©fT¢0«/­­Ÿ<º×«ÿpÌ×!#t¯9R"@ˆ ?0™›Ç @a¡UQQ~ZZ-¨~Ñ%ÃxÞ¿—{à‡áMm9¹DH`k Á"` Tlæƒs°«:Æ£#ßãÈ=uhø¨)›Ä'8𵄘"Apû¶µ«?gáJx#”g (¢>yt¿C—ÞXRìqõì“ê7rÊÉÎúbW¬¤BKÃ?}Œøcù&øv¼.ÀSÏ{7±ÝД™ wnZ->zZvQì§N.®>O=Ÿr¦¦Þê?tì• 'á*Çô&M]¹i©Ðã™§p9s|?ŽWƒÆ‰%×â>'¯|Ÿ±uõ'NÿMUE5ì]06¸¬(ÀGn 3å×EkÐ7Þ‡ת]Wо……‡öl4bB‡.½°þû¥ÿ3þ§S=Í¥”<êÛ{ÀˆKÜ¡æûú<ö,Þà Ëmû ¡¬¤ï£ÓG÷1ûíÜõ¸4÷÷•Š ¬í—+(*bjBÚV•(Ö¯?ö¼‹¶oX§ š’ÎÌ<ô¸z뀧ÏýSM]=>öã¾]äþÝ€Ó»¶­™6ë~¿B; !Y‰Õ—ÖÖ7¯G+L›µHCK~wϼˆn:T‰ZS"@ˆ ?ùZv‚±L‰¶GìÜÅ-)©¬íá ›•ðÊÏÎÞ^b" l–ý¨ð'[ž?¸Kã/Hûq­‚¡¡ÁMkpV/™›ðSv°wöËJÌÓ5TRÓc+ëÄEÄçdåê×0Ãg|d h™èÆ”89H«…oLÀº†-V‰`œ»vúø\:{ì@<;Ì,‰R D€"ð“9 WQÓ_§•¢VnK ™¬|mc9­¢Ø|¥BŸoY kwÓäT$®þÉÛ’ªÿÿ%€ìGNøEIIN7'Žü-¾Ñÿ·x”; D€"@2g0Åâ+¨ð±>ùpô2_àI¬('¯H-Gd“¶ÃÇG6ËF¥"D€"@ˆ€@•¼ øçÓEˆ D€"@ˆ€L¹m@A+;3}lûÞcÚö®¡¥§®ªÄð36Ô­ajý^Y©ÊŒ™n* D€"@ˆø dQ™nÍnPc[Ô]ÙÕ+ÝçEA”­žyÃæZl­F±V!òÑrñ_%Iˆ D€"@~|²8`¥©¬¤¬©¥ýÂÙÑ*lm5^@¼%ËT[M[GE›¦öã7 Õ"@ˆ D€|²h¼e…Ÿ‹??êÖ#Þ“{enjRDfÌ­Øû£n?.z\T«³ý 0dd¢322Ùú÷á*L’ªoБã¦tuœý-/]]½]ûOˆçø%¬ÆOžÑ¡³›xšåiÚ¼Õ¬y”!í’öþGxTp’•{$ñU•ƪrYT:ÖgáT:eŠHˆ D <* L—'¹/”QUHÓWK~ÀMÌ6¶¬fªd¤bdjv'è¼U5s}cãwr u ­pðf÷µ£;Ù¶N]("ûwo{êMûÃ|mÞ_=ýÏ6(ÎÊÎúêå  *Kg„Ÿ>~ˆ‰ý׆{vmþZÙÄ( D€ï•€l9…º¼¼¼®ÙQq9\~zØ{®k›vòjjJÊ*í;vݲmëðŠýL5’99Ö–¹žþS¥žpVFƒ(((ò˨ªG‘‘áÏžx 9¾ª¤t$ ”XB ,'yyy9yù¢BlüU®ÜÜÜ÷n}•¤¿N¢_È×)5¥Jˆ ²N@¶ KyG“¦õÍêj¼«Ö¨Z\òÛÚ¶6Û;ƒbAA¾•Ù¥ûY|9¥ˆ°°zªICkbj>vÂ4Ss‹°·Á©©)9¹ÙlÙº½“s3‡c]³Öý»7U”U MLŽìß…GpBؾçèÔñÃòóóÜ7í¾~õB»Ž]õô ¼Þñ¸qiüÄÕkØ„¾ ܳcSNÎ'[º¶j×¥GŸÍî+pçæ5¦H‹—­»qíâ‹çÞî Îa-(øƆ4?@øwÔ pJŠ¿qíÊìݹ¯íë6è?x¤¡QlLÔñ#û£#ÃøÙR u¶{¯þmÛwÎ/(¸rña8<Ž233€ËÀÀhç¶õý }êíÅLš5thìÖ³ßêå¿c&mÑòµçΜèÕg€¼‚üéã‡SS“GŒž¨ÃÖõ¼çì飢Õ,u/|U òóW¯ß>eÜÐ!#ÆêééO1¯ ¿àê¥yÞFo;X˜“¦Í±­m‡£ Ãß¿;zhOJräQì´ÔTë65jÖ {»gçf”¼IÓÉÉIø¹ˆ‰fÊŒ_†ú ©¨ª>¼ûî-[Ñ˲ºõacͪY¤¥¥ž9~((ð5“²(†s©ˆô•"@ˆ@¥ È–G95:%%>%‰“”/—loo§Èb)**ú\yuoÿÓÙ3'˜kõíÙ¥N;iVPÇ?ê¯^úΜ2úÖÍ«ÍZ¶JÖ±oàýèÁâ3ïß¹!-:›4]»jñŸ¿Ï‚ŸîÔéóΜ<4÷—qªªê­ÚtÕ±K÷]z¬[ýgRbÂsïÇ.M[2OMLÍŒLL_½|QFô¨ü¾Óí;`Øk¿YSÇÌ›9áþ=(zA¾|þÔœéã—/þú_ûN]…OkÖ²ýkåâ?Î126qnÖ¢Tă‡Cù^¼`Öêå 6rêЩ›¨@¬„bâÓÖfÏž¿ØÛëÞÜ_Æ/Y8ûCØ[F¸Y‹6þ91sêh(»=ûB ¾¾ÁÔ™¿]<rö´±Ï¼Íœ»PEEáe—J´„M\Z´pm»jÙÂ?Ì„f/úÈÑÑåÀîm çMû!.z¯¢¢flb:ÎÔƒûv5¡cçîëV/Y¶h^ËVíª[×”KôUÊœºl= Ñ ý"0E8œ”Ï–J45̘ié°™ío˜¯¢³r¢¯§†¦¦´òˆ‡ÿ:{’0þ÷_U¡€´©qDu4´¯×Ð}ÍRŒñÆY¿y`mnù.!°÷˜&342fæ …Q?$(p×6wñÄÊš¦—¦"@ˆ ! [.@øW5§(×ZÃ"$äja1ÂB[M«¿^~a²NɈ‡“ŽB¤ÕÄpprnüs ?i‰’Ñ‘jÙÖa<˜;tªð~çXïá SfÚýëÑøú•Ž»Ï€!>Ͻ…{˜°0N¨¤ŒJÁy 7pÑ–X ,ƒÀwÚ ›4ÕÒÖŠ •áC"ZGX‰ ñ±Ðþ•”•]Û´/£ú¥=úCÑêêçîֽϳO÷–-+ñ‚½xæeîÍ[ÂÅŽ…”­ª—ö;–ÁÏ÷¹­”áÆXÛº]g¬Tz€§e—J´ ÏŸ=vmݵF`ÇΟ,`‹ŽŒ°¯/°4”U”[µýdíMùY1’⯪04 ŒñÅ¡½0ywè µØâI!¤°°¨OÿÁø%0¯fÙ¼eŸgEÅk‹á7ø©ek³Jb:²hiaÚºUéu)²P0* D€T”€lÍD¨&››W³µJÉâV«gê}é™Eº…ª’RZ÷à„#-æ6;tøXa‘Ô]ñÏí®mFŽÔ¡s7ÌÈ¿xþD"‘w¡!~/žý¹Â“–öÒï¹D™²ÃÞ…ìÚ¾aÊô¹;·º#5hx¾>O[µípéüaD,ï³µ³Ç×Ñã§â³}Ó_X\v²ô´ï´Amk×Á>3ÊÊ*<.çÐÞ]%Ž=Lí.?5vÂt¸Ôggg…¾ ªWß¡œí~öÔ‘AÃÆ®\»¥_øÄÛóΧ[Ê”‡•Ä‚aF › >nî×/Ÿ‡WºÄòÀéhçöõ ?ef\LôÖ k ɲK%šLS3óEK×Âá祟ԅòX'0~ÊŒ¥«7‚ÖáÔ±¯/±<å }U…Hˆ‹ÍšPå¡#Çag¡‡" µÅ=ö¼‡Õ‹—»Ãéèe_aLÛ|üµvÓÎ"~ѵËg™eHÂ’cf`£ûÊCF9¾°ˆùáýñ£ûËY¯o/fm-Õ8üö…¡‰ DàKÈײ“ú+Ü‘;wqKJ*ñ9–˜ªŠrVvNÀ+?3Ë*ø·!;+#!îcõšv ü,kãQ#ŠNµs8ÜwáI'Oža³uãcMÍ­$‰‰ ²@€ÙtÆäQ²P¦ éiÉÍ[´L,þUgœ Dÿ ƒ îß½ÝÀ¡1žª«©æä~²«AÕþìË*  D@ö ÄF}öã,ZxCCƒ›×44¤z¹Ë– šŠ[×ÿ䘛›{= ¸ÁbÁjV‚ó}¬më ¹¨ëÈî"¹oÜx” ²L 11^¦ü¾ÕCÏOV0aj"@þªÌÐÖüdÏòÿc•(k"@ˆH ý 'á„I2$‚¥@"@ˆÀ÷E Ê €*©ö™G˜}J¥Æår=zäæöÉ~999‡Ž¬’|)"@ˆ(›¹•͇ž"@¾#²e@û7°&xúôéÍ›7ô¦¢"@¾k4öÿ]7ž" J@¶v’Ø6;vì ŒŠŠ’ø”‰ Dà ]€¾dÊ‚"ðmȺàëë»k×.Øžžž•&¢ÃÖ[»å@%¢/\¶ÁÊÚ‡žÜª]çJ¤@Q~68òyÃŽ£Ì!Óò #Çý²bÝ®Qf|/*ý²+øå)”“•­]½?–m\¹~·…¥u9£|±oðsáÖkP¾C¿Mu„¹ Ð7NÙ"@¾Ùr*UO7jÔ¨#GŽÔ¨QãåË—þþþ5ú,‹¹ W™U³Šm]¿Œ“–úÙXn];Ÿšœ$ñ~3–V5 ë˜[T/,ä'ÄÅܸòOXhp½†ÇLœ%,ÃÛ ×{v¸×ª]·C—žÕ,¬ù…Áo^]<{,;+ó›•S<#»: ô —,˜Ž’‹?-#dȈI‰ ±wo])C†uí9àò…/¥ž(\"üJx>ððyâÅÈtèÚ»k÷~ÛÖ/'ŒefnÑ¥û€6¶ Ь„ø˜Çž·_<}¤¦¦“c埳ÓRgiµïÔ£e›Noý ed÷c<" £©D€]®ÿ“НÆ£ ®®® .¼qã†èÙÀÒšðŸ|Ÿ—ìXWP¯­SÉ„‰Hˈ¿6EEÖøió<ïßÚÿ÷yyLÈð ™LÓÒRÖ.û¹g4l]=}¨t§ŽìQRQ4l|ïÃOÞýµKXFú(OrbBEµÿ2ü™)((–" ¼ ñ±åa"øÑ—/ú·çˆGiâÒ"+3£ISW¡`jVmúÜ%Ͻ^¹x’ËI53³ìäÖ€hÜNÝ ÇŠB IDATú¸4o³cÓÊä¤ñ4¼ÚèÇkSª ?-Ù5<˜››‹å¿h›ÂÂÂiÓ¦ 8ððáãGþlka8?ÿ“³ë…Qà ´zÉ<ü‹ŽŒ+òù×.†n1rÜtf¨/*<ìŸS™á=¸8´+2< šZ:“~™o]ÃöãLjcv0³ Fý†Œ±°ª‘ÁãÞ¼~Áÿœϖ*DJ²öºwC_ £ÃD,ÕÊÏŸüç'æuÿfמýKå¥\lÔª]OAA>%%qç¦Õ¹¹9#»ú†ÆgŽïC ðçY½qß‚YãÝŸ«¶>~x»~ÃÆjáagŽíãóùÐ/;víÝ¢U‡‚‚‚[×/0™6mѶkþðúcŦÛ×/ l­ÛumÙº£ŠªÚûwAgOÊÌL704ž=ÅÝ[—¹¨ª«?~xç὎MZ4hÔ„_ÈoêÚ.48Ö¬°ËßÙ­¯SSWPJIJ¸tîxXh䡘ÖkèÈãr95çqRØ^£fí]{ÚÅ޾ò{xª`+-#cS5u ¨¿§Žî)5W¦©¥ÝgÀˆZµíóòó?¸uÿÎõRýG˜˜UÃëyéìñÐ7`€7l䬢¦ºfé¯Âüºè/MMí)¿,ÌÌÊX¿j¡ÄFDñ²²²LLÍõô íÛÂäXª øZݺ[WÿôÑ}ýƬÓ©ºõ„ÎvñìQF†ÁÞî¢qá„ãàè²}Ó æ·¢T²:lýés›šY|Œ?yd7VbKÕkàØ½Ï`--v^^îmKÞžw”£Sóö]z²ÙzÑQÐR’hdb6dÄDcSóð°·H0'7§T¦è¨=û «ÛÀ±_øâù#+ç`2•êcÓÆ'PÎr*'(#D€È>5"##ׯ_­?¡ZÝ»woÖ,³‡’’n:wîljjZµd1>ø&À÷ä‘¿aiô8²ÿ Ñ{w®/•E×oŒ‹ý8hø¸ÎÝû>ºe3yvàk¿};7ÀMeÂô_“㣣«¶l?yji©)Ðu ŸøüÉèÈÐSË ÄʺVlÌÇRÂ.Í[+)©¬X4óBæÕª3º{E)oÛ¸)OõG#§f/ž=rhÜÔ¹i«ÍîK23Ò‡žÊdúôñ}‹e]Óöè²Øº}—ÝÛÖ¥¦& :vðÈ ûwmD¸ªšþn\»X[‡=ñº€×/ü|×¶«'ÑH¼üˆ›»Í}Yz¯‰sˑ㧯X4+?O`ýÖ®ÓàÄá¿ÏŸ>Ò­Gÿ1“f¾ò[µxv-»zP"ß¼òãó S¯~ãMkCùnÓÑmè¨);7¯b Ïü=~FtTÔÐÔš8ý·¤Äø7¯ýD>›BnNö±Û1N_Í¢ú¤ `KGE¼÷óyÒØ¹£Ž;86{ÿ.€3iÚüóÿ†qRÝÚfÌÄÙë×,äqpø·\ÍZv[7,g ,ÌÝ}å‚åkwíÚ¶&1>¶Œ7±~C§-îKaQ”1sˆ8Œ½ô{Ö{À°ºõáÂp';qh§hea; ¿öì;SÛ7­d†J1ÁWü\ìÞ±6>&£ B°[jðˆ‰ûvmˆøðÎEX>¸ÈºGß!ûþÞû1ʵMÇQã~Ù´îO„ž0ÃïÅÓmVØØÚ<ç±çÝRùvï3DOßhíòùÊÊ*§ÿš•‘þàî Ȉö1ñ¢–?„\€ÊÏŠ$‰ 2N@FCû_µj•žžÞDZøéÓ§¯_¿ Â?ÌÛ¶mû,Ó>FA9ÀgƼ%Ÿ†ALîcŒ9??ÿæµ Ö6vâ±üž?†rÅñù¯jÕªC+ 0pxóÚy¨&áBýž{7vn)‘B¾„ØnÛ°,=ÛgàˆemŸ8íW==&A]]}¦•ñ#Í¢¹Ô®SßÉ¥ÅõËgJeÍçjjiaÌášEkV¢½ÜD‡Á…!j¬7@RŽNÍ=¼…‘à¼ÜÜ[×ÏK¬/Æõ½Ü‚§8´ó+çOÙ×k…’ðKyx×7­‰23ÿoùŠx:âå‡ÌËO¡@#çO=³³³Í™ˆ1Ñ/}Ÿ ¯Ïc°º~å ºwP€?^"Ì«02þ~O÷îß²®Y Fˆ0SŒ4cÝÅ• ' 6Ø£·::‹©Œ ÕïÎkŸ5mˆw­QãfŒFŽfò}&ðÖƒ…A]P‘ð÷¡ïBëØ;0Ùy=¸–Õ¿K£ŒFô÷ñfttiÑ1EÓ°±‹ß‹',‡íáÔTð ã r 7p¹2/t°ÀiÚ?¢¼ô{Nrãò?5lj3`%¶òE££3dggÅÇ LÖæ®í=¸…è@áyï¦6ÏÀÈÌ ®Œ÷n^<¨†Š óׯüƒu/(&„?G¢}L.öo¢%ãÜ€3“ÔÓÓ·¬^sÁŸë˜”á솹çýûê•ÊZøµŒFä›7e\p˜AÙ‚ß¼„Œïsïésiiéddðà˜Ò"Ù¿)YYB¯R’Bg*ø˜A `aàIl©»7à ƒ÷£Â¯œ?K‰­gPÝÚóÂ4µ´uÔÔ4¦p„x71ŒôsŠ—&ã‚9ª£[²öI´I«QyÂɨ<”H†"ð]Q@AAî ˜””Äáp<<¿€ïíuWô‘ÏÓGØN@,šF¼¥ Ù·hÕ~à°qÀ¥Ò§ vÂÚ’ËçN :NßÐ(';Û:¼ôAiïÛŠ@×vy\î+ÿgâp°`£÷€ –¸c ,üðütA\žBˆ Dà§% _Ë®¾´Êcç¾Î]Ü’’JœJ%Š©ª(geç¼ò³³·—(P¡ÀËçϨ»×'$$4kÖìøñãø+LƒŽ;vïÞ½ãÇ ´:Lôì;°BéWTçŒÞ· .ËHòD@ö `¥<ˆ°Ì´ÒEý’à”•™‰Mx+û—D\´|Óù3G‚Þ”×Úÿ’¼d*nLôÇæ-Z&ÿª3M¢…A†÷ïÞnàÐOÕÕTsr?ÙUYø³ofYC¦jG…!D€üðb£>Hûq­»¡¡ÁMkeø0Ëè €±±1fúôéãææÖ¥KMMMOOOl „=@ÇŒómZ;”ctœ6õm²£\ˆÀÏC.ò ]6¯ìnù/xád,üˆùñ³¦ìˆ D€ÈÙ2¿ ööö^»víŒ3âããÕÔÔ†ºaÃlJˆ 2¢ÂUNÓÜÂjÊÌß=ï{dfÐEˆ@UÀyXmÚw{p÷úÿå]¬Äµ³oˆÓиœ*^#[U|("@ˆ _›€l¹}íÚRúD€Ÿ™¹ýÌ­Ou'Dà s.@¼ŒÒçÒÿ”© D€"@ˆ Dà# [.@X––—egÌ–Û™é<ÁIø"/Ø>‡àè°KN0Eˆ²š&¶íþÁÚƒªCˆ D€"@¾*Ù2¸©IéœdœÒŠ {´cËU%쪭­ X p<ÅžëÙÙÙ:zF_•%Nˆøñ R%ûÿÈ ÎJcî?ÙèÇ«6Õˆ"@þ% [sÐ}ñˆ¿`h_KKKOO‡éÀÀIÀêêꆃmÀ/’|Z*5. D€H$‘‘áùྂ‚¢Ä§L`Q±I@ D€üÀ¾èTÝ*碠¨Ð~h¶í;¯ßª_m Ge ët=»\9£s%š¹u\ ë¶2ªÛLÏÞEG^¡ üV»oÃù£•«HÓæ­fÍû£rq)Ö¨ø_Òú_Xþo]XÙ‘ã¦tuëàŽ}ý¿6ìÜú÷áêÖ5Ý7íÆß*)X•´Žh9?[ª*Éñ³¹üØNÍÔ¼šeuëÏ|¬¬€ÊRˆ D@ÙšW3·Ñ‹ JÕÑÓRÒcñUµM,µßû¦Õlh/¯(§¡£^MŽË–Ï ,sKZ…K…_¾ðOrb"›»¶qvi±yýªrF„Xø‡°¬ìÌòË“dåX[Ûô2Òªº5¼¿âb?^<{2$8àW&báÜë¬ì,&zßCÏœ<ä÷â9¾^¾p:%¥¬SÿÊŸã7h}Õk·L7”)Õ7ȱüÕ'I"@ˆ 2N@¶ ¹BÅç7ÃÔ5Õ²x±yyyY™Yòñr‰™EE ožFfgååæä¥Êg$©°ø8˜²°¬YìrrêíYNÉRb˜COˆÅ§rÑ)V9 °X¬óÞ¹y}û¦¿0çƒé˜ˆKðË P\ æ“0PÏÀ 6&†ùêåyO\Xb:?ã­'ñéÿ¥u¨?Hk 'D€" N@¶ m¶^ü»”¼œ$–²jñ*æÊc–/ScáOA^޲ª:„Åëƒ3s‹1ã§›š¿ IMMÉÉÍþçäø<š˜Ù¿ **ªÛ÷:~X~~ü"öíÞ–Îã øFF&‹–¯=wæD¯>`!œ>~855yÄè‰:l]ÏûwΞ>Ê4œ³Kóôtž‰‰96•:}â`hHP©b¸4uíÖ³®ž~Døû£w'%&ˆ ”'ô«ACG5tt*,(ôö~xéÜihÉLÄëW/4iÒ\M]ýþ]ÛW‘²DaÑMLÍGŽl^Í‚_PàïësêøAt×ò½´ìʵ&)>þƵ ËVoÔÔÔ™7IöÚ’“ÓÓÓ_½üwTgåöì3À¥y+eeÿ§ÈÍÍ-Õùïݾ.,­ƒc“þƒ†kë°!víò¹wo [2ð,BõÛu슦ôzxÇãÆ¥ñgT¯aú6pÏŽM8ÎOÚ»)L_"Š©3~c±”ðÂBlëÆ5–VÖLÀWC#“á£'À‰Çå^¹øÏó§8~òŒÌÌ SSstŒ¬¬Ì½;7ãÇA8Ý"@ˆøyÈ–Àã¤pS±êW1?¯°°ú”~UUU ؘ€º€N0 MyÔ³w›Zvk–ýž“9fü´Þý†œ>qD;¿0Ü@*øû°·ê꺺ÌrÇ&M×®Z -R£f­c‡÷$ÄÇÍœ·¨U›Ž "ÑÔÄï%¢Ø¹u\€ÌÊÈÃ`nð 0mÖo¯ü|¶m\SÝÚfæ¼?â˜>ÐÐÁiõ²ßñnö<¢gßA‡ö Œºˆ D€ü„dk0výÇ?ö:::FFFºººl6ÛÌÌ !PÅLLL455h``•H° Pñ°e© «Û0lí¿¨°0ômð›¿Òåûîóô1—›†D{ÝÏÎÊ25­ÆÄ‹Œ|ÏÌ”ÒOÛé·?V¼Áµ¯Où2!©Ï(((X»â(mC‡Ù¸mV]Ãù»T4L]:wä¯ý} ùü÷o§ó¸)ÉIoC‚ÐáèÈfÔÿCX(ÔkÐH4‘Öí:Ý¿}#*"-ŽÙ¶ž®¡¡qEsqnæzþìɬÌÌ´´T „7mÑšIAfñ¨?‡“ÃÂÒ ÷Ò„…™¢ðÕA'G¬»·®ÛØÖa}yeKÕKük«vÏÿsýF/É85iÆÈHëü˜è@¥ ýÃ¦Š‰‰OÐãú¥ŒtÓ"oƒ£"#`Ûûú<Á:Tqañi(Ä%bnQS (6zNØ»gO¼0ÁHÂß±Ìß¼ò³´ª!1:"@ˆøÈÖ ˆcƒŒôs8ÜàŸplöÝ?qaP( À̾b@â& ºl=.—•‹i¼Ô”JÎòct¿}çnl¶Jø hj "Àÿ‰ÝåôyæÝÅ­×îí% P`¥ `àäшŽôQc§`6`Ã_ËDSƒ×Ó7˜—››ÎMcžæççbúˆ¹çqÿk8ô$%š\ámlj7kÙF¨Íf'&Æ—? oc¤?5¹d-”]]½’,rrsГKŠ”— Á2„ÿ+€6{À5mlYJJ,–Ð%éË++Z)ñ{eeø\4½/x‰`o°”XVǽ´Î¿}óº½úaX=2üfÒ˜ávÑ”E[„û¯kš ~PâÑ–‚B\!‚_Nªpt %)ÉʺD×ÏÈHg¢äå竪ªHŒND€"@~2g@¹çr¹Øþ#ýéééP÷1€ñ~ ÿãz?l¨S‚ƒÀò‹AK].ÕÑaË+(06€ž¾~rrdrór¡E1˜"(MxNñ8r 5Á}õbŒ×âëªu[zPñUjàŸ Ä_l¬×ªm‡gO½† ³É}¥41¡<ÝT‚×ËóΈ1“*×Àè¿3ã0ºï÷â™h"i©)ϼ½0Ð^‰”™(Pga¢×2c̘¦HK-±CÄÓ,p¿AÃ!¶tÑ\ Ãc}\€ÄÓ‘Rve¥Åb‘ü¬vm]ÿ1ZÐùE/i½Î?›7¬†G~§n=ÇL˜Ï¢²³(õô³ï¦d‚A®4Nª‡‡°@ßГ–*AŽ‚ˆ D€üÄdË - £¶Øû%%%n?5277·³³ƒ`jjšœœŒÑS, ¦‹DEEdf¤wîÖ6@-[»ºõK<=¢#?Ô²­ƒ¸È¢C'7ñçñxPÚ 7à¶ÊÆàq||îë5lddl*./‚Òb‰á¥ógíÝ©©¥ƒõŽeËÓÓòÐ×7èÕw©Y5%%eC#ãÖí:xÿ®üÑ…’ÆÆ¦-[µE¯pnÚÒÐØøM@ÉbbFàáÝ[»õÂ~£ð7Cë7nÒ”ó®PFXlÚ»ï x°õôºuïó¬Ì ¦>+Œùìlu\IYÙµMû •¤ìÊ~6©‡÷o 6k‚!©£«‹W Œ(˜1.ü-(ȇåÀ/ž7¨ÐõÙwS"Šô ž‚K|Éì˜sÝ{õÃ[YÓ¦6šûé¯ •‡„‰ D€üðdn£ûÐï¡„AãdžAAAòÇ¿å° `XXX`6S8ÏRNNÂ6 øÇàå¨ SÝzô{Œ5 ùùhÅw¡!ôýs…;'-í¥Ÿ`×óR×Û €¤¤x™gçd/˜3ÅÛëáŸËÝS’?FG1ó¥#ˆ|ïÝœÎïß¹³dÿß[~[´"0à³°²ŒXô¨<И š9÷w6»¸œ=u¬<Kɸײµ8t '-eçwxê‹ ¾>sâðˆ±“acäde…„¼)5EPžÏž:2hØX¬L…óÌoÏ;eÎ'|VøÒùSc'LwtrÁÖR¡oƒêÕw(O™²+ûÙtà@ïֳﯿ/×ÒÖNKM}ôð®pó%ñ¸òròmÚwƆE8=6.îãáý^YûÙwS" ˜F7®žÿs¥;Œöµ+ †_€[Ö 5~ãöýðû:qd_ć0ñbS D€Ÿ™€|-»úÒꟙ™Þ¹‹[RRYg©ª(geç¼ò3³¬‚EuiÉqÜTXŒÑzøü`Ⱥ>´,üExñþ?Eñññ°äY*l}i…g§üò+t»‡÷>¿PÙéÐÓïš¶°lèÐzáw]‹rþ§ªl9™˜'9ÁÄÔ¬ìÕE0æŠä°ÛA‡Æø½UWSÍ)ÞMxUíÏ>µ D€”Ÿ@lÔi?΢‰Üô¸¦¡!Á铵«='$$`k˜EÀÐû³²²p`ÀO!,V‰_~)jØ'1!®ØØ’¿ný†gN,?V’$D€"@ˆ ?6Ù24´trs² ò²‹äóÒ³à䣢¦É/’ËÈìÿƒKàú/¯XŸ[$ÏÒÖ,Ù™§T aKøÉ¿ÌË8ÿس5%¥¬Œ»u©vD€"@ˆ mýJ" IDATD Ù2”UÔLª•kkð2;÷ãS†=úÙÀ‹ŸŸ¤Ö?Ue’6¥j"@ˆ¨ZUfhk–kKïª-=¥Fˆ å'À¡ ÑòÃ"I"@ˆÀK Ê €*ATÏ¡K³¦ å ‹ør…r‚ÿ°¥¼~ŠŠðWpXÉèÿ:ø}ð½*É—!D€"@ˆ ? Ù2Ú¶rjÕ¼^fvn!¿Hgo)Èð³³ò`ðCº?̃Â"–¢‚¼‚<?I7¥j"@ˆ D€TÙ20Äè¾ã*‹¥X»¦éˆ­=Ÿ„xÜñSTTÀâ_ÁD@q½óòø#¹þû­ªPP:D€"@ˆ DàÇ' [Fù¹Ü¬ ·^G[*¥¥¥çðg딟ÏgZCY‰µvÛe/K°#PU_n½)(*^9¢ª¦ô~Ø]ê¯ÍûŸ3>7·dgª¤bT "@ˆ Dà§! k6úüwwÿ"8ý †üq茞v~ÀPSUªael ¯9¬ØLs®ò|àáóÄ‹yÚ ‘³[ïAk–Ì•(Lßz ·ëØÝ¬šU^^Ntć;—Ã?„Vºäºú þ\7æØJ§@‰ D€"ðý1@ ò24“Óx'/<þ—ü.fÝöËL`3'[¸‡îÿ&$·£‚‚baaÉüƒd ýš\š·îÕÄÕ '÷mËËÍ©UÛÞ©iKQÃÉÉËcÁø×,EYiÿß PVáè D€"@>% [@ñ¶?% ~ƒ¾4w¶ÝºjtI(T=9({r|~ñ¡`¹ZµëbSËþÀîL¤^ý†+²Ο>bdb6dÄDcSóð°·8;,'7.ÍÛ4ttNçq­ª×|ìyçù“‡=û «ÛÀ^I/ž?ò¸r&Ô¾.n}›¹¶ËÏ/¸uýÂÀaãÏœRÜÙ­¯£s e%¥7¯ý/ž=š—›[’ —knY]QAáÜéCa¡Á)þO*Ëb)¹õ|õâ)o¯’ü_¿|ðÊ8†žŒó¡MLÍõô íÛÂå¤õ0æA^~þã·îß¹™úNÝúééff¦{Þóðºc&ÎB²¬Ø„ûý;×'%Æ‹·—(n4tÇ®½[´êPP hhá£R˜1oÉê%ó¸œTôè;´Ï¿vé´¡ñìù+pƒ,°lýÒÙ㜴”þƒÇhëè>}tïÊÅS¥Úµ^Çî}ki±óòro{\òö¼G§æí»ôd³õ¢£>üsâ@Jr"1+‚\4Ô5|ž>²®asëÆ¥ 7þí;õÐ74>s|TTTVoÜ·`Ö¸üüÍZ¶SVVÉÈàž<²'âûRÕ¡¯D€"@ˆ€ -ëz1P ô|Ì<}!P/ E½ ŠnálWìTËÏÇ»[šš™ò N.wo†n7z ¿O·mXack7vòœÇž%Ц­]½¿·þí2}ÒÓ7Z»|>t‰ÓÍÊHp÷†c“æMšoúëÏŒÌôÁ#& ‹­¨z Û­ë–fçd!¼k÷—ÎÃSÛÚu·o\þξžÃ aV-™SÒÿ¬¢æVh²—¾OEm¿ú ¶¸/…Î6š6ë計‹fihjMœþÔú7¯ýp°ô±Ûâc«YTŸ4cAdxXTÄûƒ{6ÃhÕâÙLš]{ô—Ø^Â7unÚj³û’ÌŒôa£§Š–D´ÒšHEUÕÐÈdåâÙµìê3õÝÛ ›W))+Ï™¿â¥ß³è¨pшè0ûvm€&­¦¦®ÃÖãZµëöè;dßßb?F¹¶é8jÜ/›Öý©««?lÔ”ýo|Ò®ƒ[5ËÏŸ7zü q8H¼n½F˜žÎíÞgHçîýNÝ[üFÌŒ‹‰^³t^~~¾¥uMie05·€Y»aÍéN°¾¹k»GnÅDE@Ãö¼wSº¿Q}‡&¡oÑ?xïε¬ìLñz C0»%|Ÿ?‚ö›À—ÕªUÇ fTLÌ,.ž=–““Íç`ZLZø|¾’²J«¨ÈBu˜Ùƒ2ŠAˆ D€! [3ÅKJÆõµ4U[ºÔIOÏ Ž }§¢¢dbÄ5%K]]Ó‚ÿI¹<®ž}å÷œyX§^ö»3÷p–hݾ œI;7ñì1u´u¡¸]üç F8+PŒp)«¨¨ª©qRJŽÐ„®££«‹pm6›“*p™ÀÅá¤07ÙÕÐÐ2rRÉ€¨¼¶4øˆËÉeþ«ÅâT+eU(yL,ú+ Á]G]CS¢ Àåq˜ˆðRd)þ¶h ó*)£=[T¯Ùµ{?CcS4´jxs•ÊHZ{‰*ôhèÔ”$&¢hAˆ°ÒÊðÜœXŒ@^^üʘûüü\U•RìÞ w#ŒÇŒ ÇnT°[ØzÕ­m›4uJjiëh³ÑoKººSÙÊ·48H03«Ä²‚᤬*8Ì›­kÀã¤Â\-˜Ä2„¿½|þD·ý M̂߼„wcK”ª}%D€"@d€lкTÕ”¬ª°XòÚZ‚m@54”32³Wm:/ò/Qø‹23sTUY<^ig!!\843ÃÉÊüopC°†Œ15«V·ÓÕ ñ”ËKÓÔÒFÔÒÖærKtJ¡í £¡l}F¿Á,7- Qx\[OऋÍÖgn0êUõðÞ-±1ÑÂdé¦Òb¢#Á“-Â5H Úü¿½¡¤OpÒ’s²sÖ®˜_j$~ä¸é7¯ó}î -yÀбX,,(‰ˆóXyÚ‹Çáhjé0U€wþ§uùÏ E?uÊ<ÕP׬œ6 矽;Üaó´éÐmðÈIî+@¹‡÷³zA˜µ¥U͵옯 Áf ,R\X9À–A£¤cKƒóiEJ¾AX›­‡`âE( ± xúâé#|à£5pØ„NÝzŸ;}XbšHˆ D€ÈÙrÂð¿©±îü½çNíÕ³³&Õ­þËønôÒÏuØ€Vƒz7Ò§åÄôu*¸X€: ¼®‡™‘š,ÓŽÌÎÌ„~‰{xVØ×w”Ø<þ>Oººõ-vËÖmß©§Ÿ`öà•ïó­:Â#£È»öF|òè^¯þÃÙºÛƒÇvuJL“ËCM†ÀXÛ´e[ÀÄTL§þCÆ”Š ?ŸÄÄØž}‡b¶ ±± ÖZ œ×ÕTÕâb?BûG3Õoؘ‰…5˜"`<ìòÙöò÷}æÒ¢µ’’„[µë,­Ø±1Q¶ö ðTÏÀ°^CÉIZ\& »Öâ/ãõ„y"Añ¼î¶íÐÍÒªꥪªܼ~åƒ%%6¶u°š¥mG7uuM&…#kÔ¬9+|umÛ… ”Gba Ý«ÿ0%k›ÚÒÊÈÖ5l±GL/¬µàó¥ä3¢@"@ˆ DàÿE@¶fp0/3 û~Ö¨fxóþËMí¯ßö³¯mÀøÄ´&jF¶t±{dmY2ô^Qp/ž=jîÚ[©01`|xßÖCǹ¶ëÌãr_ù?“˜ œË{±`‰;&%|}{o&ƒ#ÓÙ –c »7/c©hAña7¯ïÐ¥×´Y‹4´´áMñÌûAHà+‰ÉR`y<ó~˜••‰sz÷QöèÈ÷8 TD´ã¡=[°XvÁRwe%•¤„Xkgý³§ 5ë1ð |ÍĨÿ]Ks_©¨ÀÚ¾qùgÛËÿ…·±± 6óÁìPàkÁD¯KçŽ>¾:Ü´Ô 7/%Ê”;E«öØN ëN°pùô1Áf>¡!o.Ÿ;.ªoh”“ ¿ÿ€—>ðCÃòôÃÆ«ï!&e¸ìÃ6˜»p%—ÃyóoQ¥Á‘XðÞ-Ø2èå›à¹†Ì¦Ä2(++÷8ÒÀÈæÊûwoo¯t§‹"@ˆ}òµìêK+%vNìÜÅ-)©Ä÷]¢NéÊÊÎ xågg_²ZW¢X9‡šU·võˆÈDݧ~av–^>!5-MÒ³²S9éö6oÃcÖ±ŒIH31Òyöñæµ=¾œéC £È¿/ݰlá/Uë%¨“~Y°dÁ´ò—„$‰@U˜ùëÒÛÅÛ€VU‚”ÎJ *"ÒÄÔ Ó;eTY`ü¦Cc„êjª9¹Ÿ¬ þì›YÖ(#zDˆ UN 6ꃴgѼ nz\êTi­ |*+³ljšò‹Ú4­Ã/,ìÜÖA°5(îŠwuÑ©‰³,Í Š –ºXZm1ÂÚ¦}·WþÏ«Dû‡óƒ}ýàÀWØç±kÏ/_H˗‰ D€"@ˆ€Œ-ÀÏ?N8 ª}¿øÿ þ_°çÀ@¸`£ Á·Ðw*ëW¬Û…½S°È²B¥ ÃA¢“[ß¡£§ð BC®]>#M’‰ D€"@ˆ€Œ- 8à6>_ <•Î_…‰côÍkÿ¬Â))"P98Ì«r) D€"ð¨2 (àÍOˆªLˆøŽˆîzü›ŠJˆ UK@¶¶­ÚºQjD€"@ˆ D€”"Pe3UHKuYJ¬âÃs%_ùyX ù…"@ˆ D€" €,úlc#}¬ô…“}ÉÑ­"€a’ÆKJH‘^)zBˆ D€"@ˆ€d2ç$¯ ¯¡¡–ÏÇΟr…òò8ÊËóQÀë7ˆŒŒÂ}jGS] J®P¥Bÿ\½ià𱕊*ˆÔ©{ï»U::Eü?˜¿ô¯áã&‹@Z¸¸d÷¾ƒÖlÙ-þõBN_}P·A£ªJÊÌߦÍý©›Ü÷ ©ªd«6µ*)•ð=ýò²}y ¨‘±©Ù¾=½ü’¤cnauó15X%}„!D€ü,ªR®f,EE üGŒþó1!++Þ>H\QQ÷8âTQI¥(µä‡Î\ëÖ³•F˜Ò|ôꃹ…¥0dûþ“©m/8F-8àÕ¹ÓG«6GJ­Í{ŽÝô~]öF_ }FÔŠ»}í’÷£/I𻋛Áãmq_QUÅ®ÚÔªªTL:•+ÛÙ^B»«r)”ªE¿Á#ß¼òëÒÒá̱ høÇ w‰†kÕ‚¢Ôˆ DàG% UþUXYEI‰¥ÈIIŠŠTR(RWTWWg±XØüßÊÊÒ­{·zõê)±Xʪ*߸„Ñ‘áÝú0™š˜™›š[ääd3_ñèñƒ»•."K±Òq’ˆF&¦ŽMšâ¼…Ví:~›*ûùíÝß–S†Ó’•o¹ø P¨b,uAÍ®ÆÒ5›[·ï'èk¿añÑý;m:Xµµ16mdk7‰°Èw:}ÿæ•Þ›Ùõï†%xvÃjÜ{죻7Ž9íâíø¨ˆðô´ÔÆM,p«GŸn_ã•yöøA Ÿ×‡µ0ªì×nÞºvYJJ’•u«™ Ö;,èÕÎÂÃõé:ǽ˜ÝD^ø ÒÒÒ£õXëÀ²‡è%„@>ïÞ|x殮uku -¾’í»ö˜>nȨÝ»÷°aûþÝ[×÷íØÖ¿AÃùÊs&FGFìÛ¶)ÈÏ{p϶£÷$/hðX·nü*¨g»¦N^º¦Ì͈›jÂ4^LÊÄéó°¸4kÂpôÔ¶« ðËáæòØÖ¦]¿Î-âb,[ É›V'ÆÇÙÏŠrçÆeNmÓçÙc=Ю_Wtw›ö‡ŒÇÜÔÑÌÝV-ñr~hï6(üþIp+ëÖ÷o]ѯ˵ §Ø…>¼{óÙ£ûgŽ@ÞÍkYû7dd匌 µé¸`úØI³èÖÔGb¥Æg‹èš"@þxÕÌ•—“…y¬¡©©­¥UTX\\X$'+ƒ7â¤$ijÒRñ–‘–Â>`ìÞ=áŸ?"öGrRÂųÇÍ›6ƒ|Çî6°lð'¼¨°0+3óÊŒÌÀÁú¿têèÅÓãqaôcîqÀ9ÙYPηt fM­ö¿èíáæêò2ýmí®œ;‰xìm@°/æ™EÜ:wüþ§#Mù’d'8lâ§î||ÿV ¦?ùÊŸ=~(-%%6&*Èß;Ð× æ#â4\Ý72iÈWþ; žïƒï\ÇH»uí"&Œå¡üñƒÛ)ɉèô»7¯deeÔ©Ç”øöUàë –ÛéýÂU[§&#ŒWΞ€¼}½Ü°ê,däðV^Pq¼’FƦêêšgŽ;ûy½ðñtã’14n ©£{Ôi'¾#¯ýÝ»Ùãßµ/NÉŠÇ—å¨Ó®ÛFŸºÆ~ÊíÒ­7¾¨3ø¼ ôó÷öhѶ£kk0µqÍõ½ÔÀÇî€3ä/Ÿ;Ѩ‰•šº&£çê…S,žÅÅ—Ïg*^Hé3p(<±¸˜è’’üädg#lîÜœÌ8=°ËÜ¢9ߌìÄn½úÜ㘙‘ŽjŸ<¼þ'sKPGój<ôÝOdþ+9…;· (Tß¿1¬ßY*5~xkE)D€"ð¨^Ç€ÂÊ—–’ÄŸº:u Jõ_R*#—‡ó@qÓÄDÅ•KJT"RC‹k éÌ×b꽡¹…^’’1QÖÒÖ‰Žü›«S7›¤„xL3óÞb§r‹Væ3â:°4„s§Œ4xã•°a4#&¾%•!vã`ÂU×ÐTTQedÒÓSË„q£¨ˆFJzz“»Sýk‰|GßÑ aAÅ1:9ÿWÓÔÄÄ6¬p&1&’õ½à|¡¶ø. nLbll”IÖfw®W¥€ÃAš6w±»Ëc¸pУ¥[Ó´‘9"[22²ïCÞ2×p×¹KúúYPSÿíë‚‚ìÌ MFCJRÙÀwMЪ–e•”£"¾!€_¡ ÓçµëÜMNV^:¿PηV¨9–òâc¢™»ð?™Ñ‚‚:šWàIhp)Á”ûÇ$/7Gúë˜ç;~ÒÓRx+@)D€"ð·¨^æ¹’’S ‹ KŠYæII©hbq±BÖ%Ëì/ÍÊÅA¬tì.÷I`Óæ-ÁÅ1ƒ{ D,¬íˆ±èÝø¸Ø¶ºðvó¹ãë›6FìïÒ¹S`Ãñ !@˜ÕëÙ{@¿]›ù ±®‘q¿ÁÃ1‹9wéªySGÃyA‰îܸrîgLicNOJg€íÕ¹Goq ÄX#QB\áU¯ìÝÜœ\)‰²@pÄyóÆxãMDÊ€®­Øéµëò<¼ƒ±`Ùºã†bÕwÙÏÄ}ñJ Iá;røÊ *Žo«“”ÕÔØzTÔ4’þu`˜Ä¤ÄuM-Äî3>€ŽŽ^bkJþG^óÖx¹?CT 6Z Ô MÃêœÃ<>‘9|¿‚ˆ*11ú¸`mRRJL,óåÝÖᘺúµ¸Èn~2ÒÓôjÕb;3¸…U>ëVmgžš’ ÏáÆcOæi$|G~aàÉßaTTæ;X .hâÓ"åvßñÃÚaE/"@ˆøë ”Eó‹ù ÔGJJR]E ÑÿÊJJêª ã†›Î¯;k¬Üœ±ró'×Û·ph×’aÝEµµ4%%%„WONAçóào<ÎŽì;h#ìâ|·A#3Ì"þAô…IƒFL:ÜŠõËçç忮߶½“WÿÎÖÌ?,=µl—Kvêª;ïÛ±aå"eUf‡1NV±;›GaÊÉÉc÷b¬y5S _m:t®Q»lƒ7¶mÀpìɶÎ7/gâÇ÷Á-¬0_‹DLÌóÊÃÚÓÒ©)¤ë‘EÐàáÕ&+/‰üÈðϸբM½Z¼2å¦T|ä*Žo«°”™žÞ©» *€h4„­sÕäSh–ÆM™…ñŒ ]zõ}xïV¹µ"Ð¥GŸFf–/Ú¶iåòuŽø€¤Y“fØ^ àðñÌ-šaºZˆA D4ßbèÁÜ×Aþì„ác&ª¨ªa‚ÌÄX”¤üöµ‹ÓæÚã@ 3èÁ¦ün$%$`< ‹íˆÿ–’\³Vm^=ïÝš4c~L0÷ÍÎwoðÊOùàiÉI5õ „«­øø®‡î"@ˆÀŸG z­ Ø_NV¶V­ÚA5uuEK3ujÕ©Qš_£”5%/"*e ™"!.:@OáÊí¢O™Ù|»QBH?²oŽ[i×¹GvVf ¯w‹6푈‰ºySÇÌZ¸3ôX+?uh_È¿§="(bÕÒ9pÖnÙ³rñLvŒgñ±1xó-‰Sf-„Ý€íz˜·[ã0ÏéÄ%_O7„tïÙ¶G‘ÀØÊÎÎ ôñzþø ”ÎE gŸAÈFˆ6;ýêùS‹Wlܳm#;F¥"Ð^ø>â|üÒ·¸={Ä›QF1Q‘w\|1£;¨GÙ¡4\bB—$&þïß¼Šââb¢>††0ë¼… O©øÈTßVcmùüéKVo²13ßOÝãªB“ìçNÁé:wŸù¥$'mÛ¸’™J^[Awaã‡ùÓ0SþÔùnÛ]ç,]¹qÅâùÓÇ̘ï0ßa-žùü pi@º â¶XÌ^²G!‚Ÿs?=œ§“—±ÚóüéÃS‡÷ R~dÿìv:qüçO¡‹fN¸{ãN¨ðÞ IDAT%±B€óâùÿ÷=sô~4د9°k‹;Gú¾›f/^yþÖDtᬰKg *KPúwÇ.¦u[÷âœ"O·gh_Í?|³S" D€üÁDŒLøD÷2 †µÚ½‡MbbY(-_ ÙÏÉÍ{ý2€õäÞ~ÉËËcr\VN&;'刉–ô°ŠÕÖ`¢oYV}X´Tx¢†žVaRªÌ«Ð’ÜÜ<Þ2q¢ßŽV{<Ê{‹Rˆø“àÐà×¼ÒÂÎų&r#ô'5ü»Û‚ m]áÔcýÔ–ÖÀ̈YKÌeÈÊHç峂ý³¯[«îwׄ2"@ˆÀwˆ‰ôãÌ©MCCÝùÁ],j *¢z­dee¡¢¬ÿ™×¥§J’ˆögMè³þËcý*xÉ «æ³} 2ÆBÞ}s€ []"@ˆ D€¿œ@õrx;£¸¸Fnq9çý³s-tXkݦýŽÍk¾coÑ”Bˆ D€"@þ<ÕݨñÖØWJž„‰ø­ zˆçÙM¿u©òD€"@~*sL—§ó3jI:‰ DàÇ D„óy Ê«% D€"ð{¨2 Jš=r¸­‘¡QU…~={áJ…IŒ"@ˆ D€¨^¬ÿ–-[°;Æ××7((¨C‡'OžDbƒ ìììØw="‡ú•"@ˆ D€Aªõ©üýýaúúôiÏž=aaa#GŽŒŒŒÔJ'D€"@ˆ D \ÕÚ`×^CCãܹsbbbÉɬÇsVùËnìÔvºW¹ZN…Ö­:Œ›2ï§Aʉ D€"@ˆ@¹~@D¤¢Ç€2­]`¿¡Y˶嶼j†šÒ¹[ŸªÕIÚˆ D€"@ˆ@•ø ´¹¤¤$66Aÿ•uªœ)$D€"@ˆ ¿5êµ ˜/JýŸ?ÖÕÕÕÓÓ«_¿>_¾‰šÚºC†×®©_\Tôæ¥ÿ+g YO®U»nß#^XPðôám·gÙÙñÌäɳ½}øðÞuÄåååijéÈÈÊådg]8}(-5’ÝmZµh ÉäÄø›WÏ~ ¶hÖÚ¬i³â’âm;…¾{}ùÜ1E%å~ƒì ë›"léíëÀó§"£¨¨ÈÀ¡£­¬Ûfff\>{äc(ëÆô"D€"@ˆ ¿’Àoà‡§§'vÀž®8‘"î^ýü)TN^aüÔùm:tuytWAAiʬ¥7¯žñ÷ñ””T×Ðb+TRV™æÉÃÛ€Ç2vÒœØèÈM«ÖªSÑfÜÀìÒ™#7¯œÅ~€¡v“6¬š_ñ¶$ D€"@ˆ¨¿Gì~mmíJYÿ YöâââŒô47纆ÆHlbiñ%ÌÇÓµ¸¸(77'2â3ÃQM]kú¼å®Oï³­¤xÂúÇ…»ËÃ:õŒ0¯ë ?/$––”øx¹æææjiÕäê mšÚºúXpÈËËE)Ÿ?¾g¢£¾øù¸£>ð1TÔÔedd¹2ÒG"@ˆ D€"ð³ ü+ß÷Éþ>‡×®k$.&ŽWrRh*+«"t‡k ëôôÔ_OÎ[™éÌÇ¢¢Bx ŠŠ*ð%ðÓ¶c7%%åâ’ü/§ À¥MYE=#-… 7â¼…¼ÌGøp $¥¤¡“·&”Bˆ D€"@~ßÀøîÆ÷î?¬ ?ßqƒ=bý-¬ZÁj‡ª´´ÓƼ:]ÝÑ«Ugì¤ÙÇî„uΨªi0Ò˜®—•ÏÈHUU×4l̾ëc¢"pË~õV戢’%li©IŠÊªââpx ¢"@ˆ D€"ðø=B€¾¬öÄ„8XÿÖ­;0J‚ü½± تEÁª×ӯä—”–œ;y°  ÌÄÙìX#, @mú ÿŠ)|iiLí'ÆÇ"—ICsu m&{VF†Ú¿Û âb£ãb"û ¶“’’¯ó5ôˆ^D€"@ˆ D :ø V:wî\)X80òî\Áñü›Zååæ„} 115CbffúÁ½›a°õÇnEE–m())>s|ÿ艳GŽŸyúè^ûx¹õ·©SS?*"üì '¤`âßÏËHINÄ6_f€%éé:zÂÌõŽß½ :{ÜéÄá]lG-[»Ï0~ÄÞP©V0 D€"@ˆ¨rÕ׈ˆˆØ·oŸ¡¡¡¨¨(Žë©xË1¯Ÿ“• ùؘ¨í›W°3:ß½Æ\G„Ú㸖SáÙ˜ˆÍ?~pûVzZòÞí縊¾sãÞe‰ÿ^$&ÄnÛ´Œ-‰µ‚“GöpfÄÆ_¼Ù)KæŒçRK‰ D€"@ˆÀ/ P}ýÇ:thÅ) ÞÆÈØTQY%:*¼â¹H’"@ˆ D€ü=ª—ú!”‰Þa: C‡Žññ xóís¥c{®‰©ù˧ÓÓRùf¡D"@ˆ D€"ð—¨^ÀÙ W~¤?.=ú#Ù¹ò²ã‚ªP'©"D€"@ˆ ÿ-*s2²òþÛ–PéD€"@ˆ D€”K Ê€rKªˆÀƒ[—¤¥¥Ù’qqqÖÖÖÆÆÆ<ÀV`. yyy=ú©ˆZ’!D€"@ˆ D€!ÀmUÿ·\`ýs¾¾¾ÜÜÜ6mÚÄu‹ùøßÖ–J'D€"@ˆ ¿êåpácfýÕÔÔvîÜ9eÊLùÿv|©ÂD€"@ˆ D Z¨Ö€£U£†¦¦¦»»û‡Úµk—””T…ø¶î8X«¶AU)TQQu:ÊýЀªRNzˆ€ ššÚ»œt·JÒ%$$Ÿ¼Œ'[W‰6¾JFO˜ÖÓfß[Oü%-Zµ›»ðÿOð¨xY—üŽZU\yÅ%éªâ¬H’"ðǨֳðîÝ»/_¾54µÙ(Ù¯AŠAzHf7nܤ™¸••C"Þ‡Ž_ܱïèÜEËÍ›Z±³üW£‚ºïWðñp}û6èW–X‘²~R­þÙ¶¿®a}áà”¡(á¬è. Dà¯"P­f@^^ÞÖÖvãÆƒ RTTÌÍÍýo{HTTŒoòóóŸ=}È÷%þ ø¸˜–­Û1JÔÔ5Ô44 ò øêtX4kú¤‘›Ö.~ýrÒ´¹»õâ+ö[' ¿u£ª¤ò!ïÞF„®UU¨¤šÔŠ~ ª°OI Dàw'P½Nâ¢É¬˜››Ï™3gõêÕׯ_×××ão é mš£ÇO­©§_\Tèï{áìñÂÂ2ó±¾IÃ)3æËÉ)ú{Ÿ=u´¨¨zºöèÝ©KOi™÷ïÞž9q8++QË×n¾wç:¢"Ã8í¶nѶWß*ªjáŸ?>~01!+ì÷N›0,1tÄs «’¢ç7¯^,))RCº%œ€—‡[Ë6ío]¿1t—[§.ü-{pF'&ÄÇ>|p»°°p€íˆçO1ÝÊ[D‹fƒ‡ŽTTR†mt÷ÖÕgOœ!ÃÛ³HÄ?i†¶®^؇””äܼÜËçO!dEC[ûÔQ'¦Ç÷:=}¢†žHÝw€­u«vR’’~σþ6í;[5o™ž–VÛ ÆðÙ“‡a"£¸¸D·h#'¯¹{û¦¬¬ÌZu†Û×ÕÓOMM¹töDðÛWDÄÚÓÇ DZFzÙâÙ\ͫӳwÿÒâÒ÷o>yx©3ï°—’’3azƒ†EEDã7­ÂÖE¥á#Ç70m”_Pàòè¾óý[È÷»w¿Á;w/,*º}ã2WqÌGÓ†fƒ‡V×ÐŒ‰ŽÀ×'ò Ëøž8uvvv–ŽNM%eÌ:Þ¿ĸ²£{ö ª¦ž–šrê˜Ó‡Ð¶ßo+ßj÷éoÛ®cWÜÊÌH?vhß§ïl“wÿîuh«SÇÐvÄ|ë îß¹ñôÑ= «æ} Em³239ßa(ñmó}¿zé\¿¶"¢"ÏžLII5v2ZäêòøÊÅÓÈ%¨÷…×JHá“ébÔSVNîÓÇГG!÷iø¨ñªªjÓg/,*,ºs󲇻+~Çê›ˆŠ‰}þôáô‰CÉI‰\2o_¿ÄÔð~ù6™‰ Dà¯"ð8rrrXȇœv†}d=>çË@Ÿc‡ö””–Œ9~äèI»¶oä’aF8ßjS" D€üm~ƒ 8è˜666x,@VVVe;)6& ³­0›ÒÒR1ígX¿[&;aßdgeݽyͺe¤·hÕ“…È‚ “ËçO›7±„uÈÈ;ßc¹ ¥¥¥í;uÃD)‚ `v';3¸˜ZnѺ}eëLò\<^knÍÉ|‡}qq±‚‚¢Ž®.t dÑ­ ©êPXP€©ú'X‹ ÈŽE‰§î£b°éoñ[°°²þü*Ðß,—'rsrM™1óòp|\¿yk›«¶hò£‡÷>~A“¡?!!ŽS ‚ÕFC$%%ôõk‹‹‹C ×"ƒ•u+x}î®.EEEè\Æ^÷öULt$ÃÙßÏÛÈÈ„«bœáõݼzë9h`Iqñ3—Gð4PÐû`ŒA…×JxÃøO¼ƒß¼ªý_$v5 ïáþT¤çåýÿû^ŸŸù/ùÂÂ|·ri^+á gTeg— ’‚BIUÖ±K|‘àÕp-"*ÚàЦ–Í¥ed1¶‚ˆÐ2A‘oBZM·ˆ Dà/!P­€ˆˆtCffÙ_ÄOŸ>ÉÈÈHHHT¶o  Ûq˜‘E„7B€Ø`(0aªêGFzzZ*,0F¶þަ¥¦ÊË+`êŸ+5%ÙÛÃ+†˜½Â޲à-À”d,hKMáo·U¶!³{Ò¥kOìè@5Ð4ì¸å¼[Áj£žØh¢á½àØN%`ˆùþVmÚƒ!¾†Ë‰ŠŠ€õÚ˜,¯HU…Èðí}áµÞpAe úE‚­ÏFhiiH’.ûã9eé§t"@ˆø T߀7oÞœ8q]‚ õ‘#G>}ú´n]î`bA†é=¼™»Þžn°ÇOš‰åÜÜœÐ÷Á³$˜öóÙ¯XÏ:(À÷ÞmÖá!0`ºÍ]¸\ZZæ}È›GXg¼p½p*Ë¥s'GŸŠ¹Û¼œœ7~Þœ2W.œj7~ýæ]ˆñôp}üõTzý ää$¼…+Ù¸u¬áœœ¬ˆ/ŸÜͬ‚äEjˆtèÜçä`V=66êäQÖFU¾=‹iT§=ÛÆMœnÓgЧÐwpgtâìôûÊu[±FàÃ.qÿ6}.rX« ¨˜š’âþüÉ›—‚ªqóÚEì^¼|=ìÔ˜¨ˆÝ;6aáhûÖõC†¶=±¤´øKا³§ ÊΤcÍ FüæN8èÞíkب€t¾Ã^M]MVPTÆ*•ç‹çA~hÝþÝŽ¶ÃF­ß²ûeãccn|=m N¬ŽnÍå«7§§¥@Œ·ˆ5Ú¿×ÑvèÈ‰Óæ°Î/Ú¶ 1è¼b¼)o_â 1¦b±|NsâÜPÁjæ1j¼†–NQQAèûw\àÃ[×c«ñ°‘P«{·®aÃÙ“‡&Mƒ@Xoy-¸Gx+Ì7…oïÃR+á ç[ ý"á°#´ÛÜqZÑ ×§Ø€±bíV„*ù³Uqʼ ø]T4¥"@ˆÀŸJ@ÄȤ± ¶!µ{›ÄDa&—´”dNnÞë—ºµ*j *éÏÞbâka”´nÝÚÓÓç~â0G̲á!œy±]²C·¾B´ýâ[˜V\³iÇÌÉ£~q¹TÜ/&0ÈÖNT\ 迸\*Žü8´¤x,o ¨s)|âÒp#ÍšXâ§XVF:ïÛÇnTíÏþ7Š4"@þ1a‚~œ9!hh¨;?¸‹ nAdªoPvvv—.]ð `;;»N:qYÿ‚Úó¦ëÖ¬•ôïîáÿ°T4 D€"@ˆB z…aRŸ]׫W¯N˜0‡~2DMMó#Û"¤?ûó&<5ìgDú‰ D€"@ˆÀ¨^@¾C¾mŒTW›A?Ò¼_–' y°è/«ô \½|ö”BE"@ˆ Dà'¨2@Qžud5½ˆ D ÚH¶¥«ÚÖš*Fˆ UL Ê€*®W òrúzºšÊr“ ©—$Äç˼ÌR¿“Tû}Ÿ#É«¼tRHˆ D€"@þHÕÑ116Ô¯© âÒ%9*ùÉxþ‘¶d޶jD7ÕˆG)ú{¢ç—ˆý‘ýA"D€"@ˆ ?•@µs`ý[65SUaMóçää|Ž_œÙC«8¦¶tfåhm©œnj‘ºRÙö[ä—’ðSÇ)'D€"@ˆø T»c@1÷ë‡O‡‡‡ûúúâÐ)5ÜÓuÏÆO}ßáB¼QiäS¦ë³uD/"ðÛ°_³­vÃr«­®¡µÁñP¹bÿ¡€M¿¡}Ž@,›·ž{àëéö—wíŸÔüFæ–ºöÖÕ«]PöøÁ­Ïa¡ßÝ@5õ¥+·,™3þ»5üHƇw¯¥üYÏ‹ˆÃ3¶„ÉÏËû»Óð}‘›—+ˆOnnöÍ+t&• <”Nˆ  T/»~QSDþÀ`ª,)R\R£FG´ÏÅx£öÊѺR96êá»#ͶŒnü)¬[µï7xÔëçßÙSŸgdljÕ¢ § ""RCD¤´#å7xùû¼ø5µ¬Z,B´%&ÄâýkUÙR~íÊV¬‚òCß ‘,ÈÏ÷p{"D€n"@ˆàK z98ó»~÷ ¦ºjyË |wF6ù˜[vøÖž¥êÐm"_ÑíŸàïëÁ˜¦švíÙo×ÖÕпrÃnogÆ KKË„þpíâ©¢¢BëVšX¶ÈÎÊÐÔÔ©!*róÊ™OB l7v*=¦©¥##+—“uáô¡´Ô¾L)± ˆ‹KØôvçÆ¶¡ó*ÈïõK¦Gà+jëÔTUÕ8qdWzZêÛQp _<{èòød7±ên3HUM#;;Óõé7g$Ž›<j—­Ûë£ûâºÛ ´hÞZRBâÍ«ÀWNîâl†D#s‹Œô´¦V­2ÒRNÛ[·žq—žý0Jo\>ý2ÀÂ|ǘ¤”ÔP»‰FÆDEE’“öïØ˜ŸŸ‡ ”s'œ¾|þˆ\žAX‹²ªZFZêÅsG?|Ï…®mÇnºõ)-)uy|ÇÍ塘˜ØªM{îÙÉòåäåW¬ßµq傌Œ4vF Ôr± ùÝlXX¶”•—>â䘕¥®®9hø8ýÚu³2Òï]ôó„N.m………ÃGMÖÒ©‰ªbüç峞܇VX6kuhßV\ã;õâù£Ææ–2rráŸ?^:s¤¸¸ÎC›-Ûv*,,zxïú» ó'æ ™]Ä>Í[²ÎÕåAS3IiiO÷§h8'QQ±ÑfÖ5¬/*&ñùãå ÇS““ÚuêahdzìàvF²ß ‘bâ¢øF³i£Í-šg¦§×¬e &*zõâ Æ¶Æø6r’†¶nø§P¦E·¯ã,Žóšïp¤¿*¶vðó’ž–àãÁ«³"ã !@ɉñOÞ6kÒ¬ï`;Ç öy¹¹Ð9bÌTÇ ¢bbk™µ,¾äQ(2Ú ºaØÅ IDAT&'+çëåfP×hÁŒq¼5¡"@ˆøÛT/'~âÌìúåì†ÚÒYކ/®&Ö;oÄ,„ç)@@[JàÊxÅ{ÑØûv¬/)-?yn§n½a /ŒH§]a÷ãîÄi 6®ZÀD84jl¹có [ºÚŒ3mÿÎ /ˆ$¿@MýÚ0sƒü½8³³ýÃÆæVðå``ÁÄœ1wYdDøºåsåä&Ï\ ³þÍ«€ü¼Ü3ÇöÆÇÅèéL™½fwDø§ã‡v"hÊyŒÎž}Ô­¿{Ëêܼœa£&÷ìm{óê®Ú70;wò Ê^}›2ïíËd72ikøÍË€ââ"¾­ÃÚ…„„ÔºåsàXÖÔ3€)Ì)fÒмߠÇî‚ó @L”{S»”´´¶¶Þ†óaOŸç ›5À×7ã4±h‰QÊiý3úËÅÒ£÷ º†ÆÀ©©É STX€ã¦Î{û*àÈþmµj×4sQRB\dÄg(dk]¸lc€Ÿ×žmë 뛌Ÿ:ÿ…+ŸéçÚuŒöl_‡ŒÓç.kjÕÒÏÛÝ¢Y«&ÍZíøgeVv&óeÅ™(-#ƒ.Þ¹uµœœÂÜ¥k™†³°ÞóæµÿùSJJJ =xèØÃû|=zõ±ÅP'#"*ÚÄÊúøÁ\Õ7n¸wûzÐ6mÔd¨Ý¤ «æ£Õc'ÍññruÙv¯N]£É3¿:¡Îw8¡^ÍHì3`¸¤¤ÔÚe³1 §ÎZš—Ç'JªâãêUoC3‹ACÇ\»tzèÈI—ÏËÌLÇÎ6ò’G´ÛðÑSŽØöéãûzèתS.| D€¿@õÚ¬Rœ(^£H½ˆ;œë¹yù¹¹yl˯ªúÆýù#˜eˆquq¶hÖ’Qù…™õ‡½Û†“àÉ[î.ëÔ3RT¢'TU?Ô#''ëK.|%}=`ýã–¦¶nM}ƒÛ×ÏcŠó¸îÏaºé¡!oãb£1l`˾ ô­[¯>¯ÌLß»u =[XPððîu&#—Xtd8œúþ¾/TTÔîݾ„‚‚_‚TQUãÕɤ—È+(`Õ£"?£!œ’˜vuyˆX&TsØI‰ñ\z üÞ­ËÈåëáÚÔ’5>ý¼ÜqÁ È©QÃʺµ¿7Ÿ€¢r±À3¹}í|JJCëºzµ”UÔœï^CQ%ÌX[6oÃÔ‡­MG¯–¢’ÊSçÛ%%Åúî-߆»=sÆw ¯Ð7zú,‹³‰…õ‹çál€ð£{7øæâJÄr R°nàÍj/ç]h,   œï^¯ch‚»Y™Bßš7µÆµ±I#„ŠÁÓãÒ‰ XÿH|ü –±ŒŒ¬®~mY9,á ìãûwÁ/…×MÐpâÕ =M¬Z8ß¹†Õ$tî ×Ç|5Wj\]¿tÊ žñÌùËß¿{ýæë"׋—¼™y³Ð÷oá7¢ÏžÜÏÉáÿ=âVDŸ‰ DàO'P½Vâ dô¤² ¤3=3X6óú˜%»ñ}ýÏùÊââb¢%%˜†„nÅåËüxïdff0J`ÿ)*•M§aj­93#Mé_C?3£,6ÖUòãu Bdgg!^EVNž¯þoè ¢€ÄÄÅ/ßĨ‡ÑŒk}ƒz={ÒÐÒÅ ƒ×“”Ä43fI˶ˆÔÀ0ƒ0—«™ño×À¶ÌcÇᣔ”Àg`ûx@bÔ)b¢â°!˜7lš˜¨/° Œ¹Ï–m:qæjß¹§¼‚"&D»öèä_6«¡©Ý¼e;˜ˆ`VÓÐ ~ÅdŠ(´é;[iú_ÿ*L‡ À½ ƒék®—Y«Áù72"Î'!!¦ïÀ˜Ç ÑÒÆVÏz¨†Œ´LlLÌ;Äí`g*S1D¢c‰€?m¦ýÄ|<î¢vÄW¥ZÁwŒÖoÓõÉÉÉ,.*dϯ3š½_¸´íÐ û2! ªªŽ@®aáõè3UÅLó–mÿÝàóáàͬÛúz—sÖ­ ,ÞÏ{&(CÐàÿ¤§¦tíÙÅ¡JM›µô÷ãÞ·ŠÐ¸Üìl| POø¦-*ˆè¥¿Oëv]Q¾€Ø‚ÏÎ5pèèQãgð*AÃ{öŒy}˜¼h&»áŒ$fÖ3ÓÓ`ýãcÛöÝÙÙ1T‚¼íÆÍˆ‰ ¯àA«ÑÑ_Ð5»ô—½Ž¡±IƒÿŸ*¶xù?­ÚvæªßáÄ[&åe€w›]qýõg§£ ± ¦£_FŒî|çê…S‡ååÚtèV‘Œ¯^ú! ã ìй'¦-*’‹dˆ Dà'P½Vp®ÿý$ý^êÃ4YNüÄ:^° ÄEk Õ enÝK¬Å÷!L‡a®oæicï¦Ý¸i‹–oB ϧÐwŸ`÷k ŸÇ¬+0ÏŠƒež8ßfÒ"\·ž ŒBÄ—cÃ(".˜t/·þ¶#ujêGE„Ÿ=áôÇŽjÒ@X«99Ùx@ÿÁ£{ƒ xWÝ0à™ Vb%+.&êˆÓ¶Òü¼cw6víæýˆŠ¹vñ$³2ÆYah;yd·íˆ m;uÏHO¿Ä™'4µuð$,œôÄùNÙ**bÅ)+«#pŸ&¹|ý4üу›ì†3’>®õMÏ·_¯óÛלٱáVûås}6<Çv5 àó§Ð @oøiP¿HAQ‰9©‰S?ßáÄ[&åÖµs8jþ’uÙ9Yo_64k*H²"é=ûÚÂçÁž%ô~|f.X%u®l‹HžT9ø·9ÙÙwo^¬rÍ?[!6kO™µtÕÒ0²1˾ýŸåØËËY(s è²…åÄ[U,à8¬Þ¶Æ~Ö÷=˜ 'aã2‚ÍjêÕ†ƒtô@Ù¡¢¼ýŽ)XXóÏÞ±Cl´ut…l\AÓJ¿þ y÷Ƭ‰%ü Yé¼üo"ÍØ?ûºµêþŽ(¨ÎD€ß—@LD˜ gÎFih¨;?¸‹ nA-ýQZÞïN‡Y¿,¬Å¤ÚL,Îûw0ðÇÿFþÔ®ëÿ»kH‰ÀKÑJæÖX©øo«QñÒqr?NÞ„Å.Ìd¿þº©–%ölpYÿ×É+ ý:÷zèS)ë¿NÝúˆÔ‡6®÷»7A¸Æ¤Àcý#¤ +¨è‚.Ýû2qS¼è(…"@þ6Õ+ˆ¡ø½“d`£þ¥©|"sÞ?ÎüÁ®_Äý ‰üùÛ:ÚûÀ3Ë`æ>{r÷ØÐjKg–v³8bì´â¢¢Ð×wo]ªòªbÃÀº-N8ñæð×ç‘Uü… ?£'ÍÂ~!5œ?€@U…UÙ @FV9ÛÑþèÔ"@ˆ D€"ð»¨2 J@x¿xT)=Ö­Y§ìÑ‹"@ˆ D€ ¨^SiK Kq q<³);'‡îãIŸ¸ÎËÏ+).‘••ÅùØ8ð͉Šl-‰"@ˆ D€¿œ@µ;ýqÀ鎭PSSÛî¸}Ï®= ròr¸Øæ¸M]]O;ètð/ï6j> D€"@ˆø>ÕÑÀq~%__¸(».æ¸þšþ}­ýI¹6nÝS×°þORNjx9|ò²ƒÏUTTŽžûÁRx³·hÕnîÂe¼éì”Ѧõ´ D€óÖϨ¤ÃÊæMXOG®TM*Xaáb§ÎîÒÝF¸Ì÷Ýý¯¾Œåv÷÷5‡r"@ˆøª£À<††õÿ×â¾±ø™Û‚a¬\ç¸÷ÐiyEFIJY ¤/ÿNSËf° ÇOžÉ5nÐ)³æ/eRn]¿œ”P¾"’ø.è>ÐÆqìÜ‹ì× Å N½ïÒW•™†ÙCMðÞøìºv1V^¾là}ûøôÉ!…ቶoß²NWS×øDBRrÏÁÓó¯R–ð[ìšÃÝÙ ì{õȈ)**£á\ᨔ«¤âã&Í,×Gâ’ùy_F¦¯Û´ëÈ®Ÿþ¶ нg_¤”ÛÝo5I"@ˆøÙª×Q1Qú/\¸0èeB}V¦¯ƒ öËì‹ŠŠ‚ƒƒ € ¢STX»äÒ¹‚*›ž–’Ò¨qìFÈÏÏGÞ–­ÛÇÇŰ•xy¸VV![žåÛˆˆ”–”|·†¿!#h·lÝîÖuÖùñ0—Õ44 ¾}4é/ƒ€*•”sçéþüÄQ'ìNÑÑ­Ùoà0‡Uׯ^‚Çô¢Îœƒ„·†!ïÞò&~GŠ…¥5Î×7iÐÆwjjÊwh¨xMBß¿3iÐèÞík(¥¾I-_ßÄ”ý1)1¡Ü 0þ;*YÁ,?òe,·th‹VíÝ]]IŒÉ„øXæºÜw\ —§»D€"@~„@õržÞß²vÇÝ«Îxl ZåáéÁ·m®n®½÷X¼rÞ¢iü§ö9ßéÙ»¿óý›é©©œLš 6Z]C3&:âì©£‘_>ã.¢²³³ttj*)«äädÞ¿3%…ûa@Ø‚ü%ô“…U ÏÏ1ájfnáúì±~mF9¢ŽÜóåsb*¼<ž?r¾+**²Ð~íûwon^»¨ ¨4|äø¦ò \Ýw¾‹]¨nM}uuÍý{™šðm,%‚€—‡[Ë6í±>^nºôbÈ ¶gèˆ1æV%E%Ïo^½Vfï~ƒ;vî^XTtûÆe6CÄä@c#"úŦï k8 ÷0¤U›ò ññp CÞ½ÁÝ6í;[5o™‘–V§ž‘Ëç§îqfÁHE‰YY™BCvïØ¸vÓ®»Ý»sõlѪíNÇ ÆÈ<ÔOä~áþÌаþÝ[W_ù#ð&1.îþÝëÓg/Æs¬þÙ¶’»·oÒÖÑé;`(FiVf&Fò“‡ßÇY4sݪm·çOŒMÂ6…6&K  g2æ¸íðÑ%EÅW/ŸÅ-,gÙÍÔ„­Š]¤tíÑ»S—žxdïûwoÏœ8œ••ÁYâ‡w½û…ËjTßäñÃûýa„{a¬ÕŒ; ë3éé€ïãåŽDæ[ÆðlX0™»hÙË@?HbB½]Ç®p³33ÒÚX¾+..jß©kð›—§Žäíë–m¹d˜/cØÇP¾mÑÔÔ^¾v3z§Y³V2²².Oþ¨¤¤²zƒã¡ý;ß¿®ePg¸Ýx]=}tÊ¥³'‚ß¾BÆ­;>}ü£KZFzÙâÙÂK§»D€"@ªŠ€ÀIôª* RzTL6[yÑçX[›–‚2â aA2 q¾Þž}úâÀŸíés߸v~ÞŒñÞîsØÃÔ`Ì›X9°g•ÃüOCûÊW­ç ·­Ûá–……õû·¹99\b°TŽÜÝ»¿mÍšúÝzõÃ>æÛ7®@fÚ¬…xÈèâùÓ¶¬_Þº]Ç&͘ŒÐsìàû…3¢"Âù–H‰lññ±Ù™™õ ‘‚iWO÷ÿ/¹Ø “kÅÒ¹×Ú›7µêÒå4³nݺmÇ kìW.ĹWfllô¦µËfMóäáÝ)3çIJI2˜šy¸?[±tŽËãûBTa]âõË£ú 8e°dø±“ÇÌ9>=5…7riÿî-EE…KLÇ;&:277&ãì©£ìÝ“^‡•UU1÷ïíéæÍò‘XãSÈ 5™8eîé‡çÍœ…·&VÍ[uíÑNÈ’ùSQ¥±“¦si ÿüI¤†¨AíºH72nü:&&šýñÃû·p}gÌ]öqÁ¬‰':7…] ï€Gå9¬ööp…õ¯_«6­µ+Íž:fû–u©)Ih”¿¯×ƒ;7Ö?Jäí^v…µEFF2ëV-Þ²a\ !ĸn•”–úzº[·bqnÕº½Ç >KX¹|áÔ„©³ðó2fâToOwXÿðsæ-Z°¹3ÆŸ9qhÒô¹J**Œr㦛Ö-[¾dNÅ«A’D€"@~@õrjÄá­f®´úú¢=n›švnÄÙ<|Üã¾ · ÀH iü›—[´n‹‡-caeý!$øU ?,uÌüåæäš62cîbJ8## ×o^ÔújÜð¾0©«£‡?Û0³8 PNÉØ˜(ÌRO›½°§Mÿ£wcbX·¦^­Úu._8]XP€……'X³}Lo/wf‚–Yîà-‘R8 x¼x ›­sssÁ™}«y˶׮œGÈ `bfŽ[Ö-Ú<}t?9)![·8VÊEêëõ"==ÓÛ/Ü\àãéèè1Y¾|ùĬ”ÛYpödåä8 ÂÀ{÷ö&Ô¡ö‘óm,7 ¯„á”””ÂÚö÷ó622"ß²e{ÄáDE~ñõòÐÖÖ­SÇPˆ0«&ï^C?Fæƒ;7rxœX,Y`}xáÉ\>¾“¬ì7mÁwçÓ§÷FÆ `F+(*"îåãû`æ#Fúûw5õ TUÕˆ€½B` cjœ©×€×ÐÔZl¿æñƒ;X1ƒ@qq±¤¤„¾~mÄþ¡ãxWá #¨wø6YP[Ð (YÒÒR##¾Àñà›]P¢§‡+\Pì,Ç¢“ŸÏ ¾b<ÑQQö«6©©i\½ÄZxifÝêKxêÒ?††ÀqjlfÁä}òð>|­rÇß‚(‘"@ˆÀ÷¨^!@‰5âÙÍаV^rÆ›ç!W±bf†®éÛ¨=Ëâ”ÒfØÎîÛÈ«—þŒ˜’ŠjRr";KrR‚²²ó³¡ÌEAa¡´tÙ²—rüåöõñèÞ£¯ž^í7¯ƒðÍ·tøƒ†Ø!¤!.–µI@MM2X³q;# ã&&*’¹NOûžpm¾…þ ‰~Þ}ú÷ä˜vÅ$+,Ñ”¤$†:]E•5± ? ±LbRÂÿU¹ RÒ¹{/eeÕ’âb„„±·’#þ§Ü¼Œreç|câ+«¨¦¤”Õf}JJ9ýnP×°ÿ a°æ±9ö÷³La;‰áŽz}öÝ»7-Û¶G䉠ª¢&ɉe4à Ž…K•C&Ž SeV\§Ø‡÷ï÷áöñ=ÒC?¼ïØ©>f¦§%&Ä™™[b`ÃO`²$'&Ö®SæQs øfÍ[¥¦¦zy²„ðŠ‰Žº|þÔ€Áõtj¾~éñìIÆ'ç,ZPïpʰ¯ù¶»ƒÎç„+,ÈÇâÌŽ¡ [÷0)SÆ ãÚïñµž‘ðŽú úñÃûì,¾œ›ëã9óΜ¸s×^°ø™ˆÿ°!'Ïüú1wSÓR””UÅÄÄ@MC…2¥p øwoÂ7˜>ká>VË"÷pŽ·¼¼Â˜ Óàì=u¤´ôÿ›ãõ§ gsø¶ʹšÌõу“ÆØ —ñôx>dø˜ý»· ƒ_Šm?n®Oûôäïã‰}XÐ ~ë´‡O‘;¤Ò‰ D€T9êŸPƒõö|àwuÍ=§Ág®®½wÑáöœÏλ~Ëñá¡‘ç^¼bdð¿p0¼\Ÿ=éÖ£#àïSߤ!f(qàFûNݧüæµp \w#¾„oÛ¼;åjÖ¢5bTNÙ{öäÑñ“fÊÈÈ`¾ƒ‡‘––†ñsMxH· Í”ÎÛºi×¶Tì1í?p(fÊPÞ«÷”CÒÇûEÛö`ŽãºëWã˜yE~ 7mÌrÜý¦ìtæýUX˜Çò™7ÕÔÒáàûæ9FÜ ì =ßKÏ]¾qü½áyâèXl–íÚ½œ·ã‘™•!**Î>@kQQ°þ±k¥©es¾…2‰­ÛtÀ>+íç­]¾ïUó`y›5aÅ–DF†7üÚRØÍìm'~¬š öé‘g‡¡³‹@”N‡Î=´utÁgð°‘X=ãšþ‡$Ü0L`·jÓþC(kË/0¬iàãû¯;€Œ”š’Ü»ß ,vaÏFóm¼<ÝØú9/JJKŽÜ —lę̂6¾†F& ‡oDA83=]C»ìøWA½Ã)é¿"má[±rÝŸ?Ù±uýë—‚$‡  §Ž:ùû`İ~Uߨ4ÐR¸UØ?Í(H¥"@ˆøIªÙ @²€¨ˆ(¿#ï‹r‹2ó2åt¤³_åå%ôÖùü"R­«œTëŠÎžã€Âvº0ìÛ°¯£íБ§Í‰ŽÜ½mS~~^e±"’[P #FMpÚ½&‘ŸGK«avãÙ··£í°Që·ì–””йñõ,Kz}„¬ðF­\¹pj¨Ýøõ›wáYÄg?þzf¼Ähá0„ø±ËÂÆ€‰Óf¯Þ¸3âèʦ9«‰·ç+×nExXTd³Pn=q<ÞˆôHNJzý*ðàÞ\F3‚‘ŽÙ;fü4˜þ8j 9Õ"¤äþk+×o…u¸yýŠ3'Mš:êÂbeÂÄ^çSÇ ÆÖæéîÒ²u‡?a4nâ ”šœÂŽ‚ÃW{‘gÌYŒó…`¡bWë`§ªªªÍ]¸\ZZæ}È›Gœx›–b¯^-6Ÿ¡ÁºöÂ`ÃoÙ·kËÈ1·ï= ÈçN0¯&+i‡vM9òŒ¹Øì;bÔx -¢¢¬-\<{2în.SfÌßåtâõ«€#N»ùö— »¬Š´EPÅ„§çååa… T>è@ IDATL1˜[X®v`U|é©UëáaecûÖõC†¶=±¤´øKا³§ Ò@éD€"@~6#“ol Îò²³3»÷°ILä攑–’ÌÉÍÃÉ'ºµøï­Tôž–Å⇊ÿ´?NDBDZKQiE¥E5j Óˆº–¤ÛW¥þ]FmT§rô+U: ŸM‹Û÷Þ²aç>æŸ](é'liIñXáò`lH2ÏZÄ®w³&–~“•‘Îûö±Uû³O½Cˆ '&èÇ™S‰††ºóƒ»8õ[æê¶PÕ#Ñ«°®•]ZX**-R”URC"W¯µœ¤F Ç>` Ô`J'¿žаàP\TÜ«Ïýëë@%"@ˆ D€!P½îYÖ18}±ìFîžÂ“xË– pØK CîÛô™T?xÐĤ)s0™‰Óýyw„W¿*Sˆ D€?–@õr”:µùcISÃþbgOÆû/@M'D€"@ª*s奫Q³¨*D€"ÀC MØ–.iJ D€?”@•9UÂÏI­”ªX)y&D€"@ˆ 9êå0aia).!.RC$;'ÑÅSupçwâœGYYYœPSþòž£æ"@ˆ D€ï P½Æ4à€Ó 55µíŽÛ÷ìÚ£   '/‡‹mŽÛÔÕÕqüùA§ƒßÑTÊBˆ D€"@ˆ@utDDDJ¾¾pQv]Ìqý5zŽTœ€eóÖ“g,â•”Î+I)UE cW›5ÿì[ýÏÞªRHzˆ D€Ê¨Ž!@ÌchXÿ}!þÿþÿbn nèû jZVÎÏÎÊ€”YÓæ]{ôÛ¶i™àåÜÁÛzölÙ¬µ¼‚Bfzzèû·—Î)'Ý®:èP]½ZЇ'Ó}x|íÂI\|Ÿz5õ¥+·,™3žÉ–››ó}ªxs52·7y®Ÿûù“e+T†õL›ãü&è¨Ó6È£!®Ïøzº1’H)..ÂcÂ>…pc0)þ:ÐתÓ¾èÐ#V-ÚâÙ×ɉñð?†s¶3ôââËÖí@âÑýŽ5õ ,›µ:´o+CìîÍ‹È."*‚ŽHKMyÆyyEð}÷ù~WnØýâù#ó¦Í¥d¤7­æ¸Å 2¨c¤¬¢vñô‘AÃÆÞ¸r¦°eÍ÷ê74äíK|8‡÷me#…c6aêü¢Â£NÛyö-æâÓ‡cS3µÔÔä:õêggfdggqÉ0™‰ïì«@Ÿ¤„ØùK×Ô5B§ð¦D"@ˆ Õ@õÚ hh¼îä™3/¼Z÷ì%ˆnA`݉3$ƒy»—Þ]{ôçÀßuØ÷ï\Y¹xZ€¯çÄé a0 5=wÂiëú¥áa»÷Ä¥öó§˜kÄ4-,Îí°É êÖß½eõº󤤥{ö¶EÆ&–-¬¬ÛìÚ²jóºÅ&¦f‚jȤÃN2c‰»ë£‹§]9|上ÊÊ‚ª¤  4eÖR/×UKflY·„±6øÖ¤ed0Ù¹u½ý¹„×á7º«¤¬Ò¨‰UbR<ê,]||Ìž­k–-œìæâwï¶µÍZ¶kdfÁó~#ø?ALø?¸:÷nÎÊÊ|XÿB¾õŒLvo[ûÏšÅBjމÿwo^x6lÜ’èÔ'Èß“33•€øc“g,ÎËÍ=vp'_ë2¥%¥¾žpéqo±Ÿ÷ÿ—„ÔS‰ qp„ÈÐ-"@ˆ ÕŠ@õrb‹kà­djfþêÖÇ®f:qÂÂG$âI!(Ý»ŽÕ|Ä|³e7mö1$øu`qq1"ƒóss1ÉÜõ÷qÏÌLÇuÈÛ ==.µžîOo^=‡‰ÕY W­Ú´§Mû®Œ@˶îݺ”‘‘†ùø‡w¯›[4Gº…UK÷çàÕ€‡÷® ©!nÁ[ˆŠüäç…ùíÏŸB?„¾m`ÚDP•šXZG| C|ǹñ’|ëÀhpy|lHxMªùÝa#'oÛwsØ¿¿yõŒt€‰OxJ¹¹¹ZZ5+Þ48‡0 1B‡æéö‘*XsÀ¬°®^m(iÕ¶“û³‡ÑáPîúÔY¬º&¯ò÷!o´´k‘³²ní_1 JÒÓSa¡¢u¼ ‘‚+!)¡[³â×Q%f…Šo×7nÒ {T°îJ>}|7'7›¯B&QK§&Ö@n_?su¡ÓýÙ#f ã.ï7‚ïð¤Üá'äûèöìü4!Cέ¹¥u€Ÿ'VQ^øXµ`=AΦ§³èÇn>¶Ô®SÏ×Û _!LÐp«æ­¡¿¡™¼ !’œ·0äddå*(LbD€"@þsÕ+(®øÿ»}Uš6Ÿ}õÞ;w×ëWÓ‡Õ Ú´ÃEk¯]ù/˜àþÞnÝ{ ~ÄH#œ‰ìœ)ɉJJªÌÇ윲…~XB’ÒÜ4†!‚м±¸±™ÅÈñ3Wû“ÐÃGO¥ÅR"RCÛDD`ù¥¥”EqǷƪªjµ êa[*s+ёႪ„È„µpêÅ÷ÁÄ3†Î·Üß.ñ™CˆäÆL<¸TÙ%E³ÖØÊ©¤¤\\R‚ÿå*ØØü¼<61ì3ÉÌ`9„xæKI³VŠ”UÕ êÔÇÄ3[!BÀ“¾íÜÂx ðêй—Žn-¸›ZÚº©€’’ ÜHX½0ayåãc£o];׫Ï` mÝwo‚¡”—ŸË·ëdϬ05á fãÒ¬ªª=6‹—obÒá]ÄÅF1×¼ßÞáI¾L€®"ÃOÈ÷1##•g ¬s|×€‰þ>3ç/ÇÖà¿¡ÓùæÍÌ̸{ý–G° ÄÚñú:Ë€X÷þ2¼‰ŠŠÊ99Â|-Þ,”Bˆ Dà?$P½€Xž¹9åíÆÝzÊâ½+Ü#ç›KW:&þ»/#=Õøß)dÄvÞôôobý…kÃ]Øv¯‚üâãb`Û…}|ûàäá]ÌÞv^ئʪe~æˆÙéùØÖÌ|”•/›,LKKùø>¸‚Û!lÚ¸,BƒÑK…oXwËŽP*·M¿Ü0,€¸<º;pèèÝŽkø¢ÃîŽAÃÆìÛ±>&* ³_½•û¼Ø’ïçc:ÀבEå"Cм¥ëÜ\V|ûµYÖò”Íððþ{gÕòÅqéîPTP1PI±[±»ëÙÝÝú,ì.ìú[<LDÅ@¥»ãÿ[.oߺ%Í‚gÿûyÿ»sÏÌœùÎåzÎÌ™,:jJ·^ý¯_9÷ë“â4* %‚‰¬ªªÆ”Éÿñ‹ÉHÏØ¶q©±v¶J¼nñe‚5Eyü„ý=þ®—ÌÌm0H¿r£=£<óÖfV®Oœ°I”‰©þHÙj»uÑ—”ÿ×¼SGv…… ¢ýꥻÝÀQÇöàJGd ‚Ç>†½+¢<‰"@ˆ¨t¢„€Ës§Ý³ßæ¼ÏþÊüÎ{vþoéüÛV9ïß}{ý*ü¼¶x®ã浸¶xçt_ްŽž»tèÜ“¹ûæõ«†š7k‰ÝE¬Úu–SPx_°âö·ìÔ´E+EeŒ¸·jcY³–ÎçÏáÈ…Ð ì-¨ªÆ2÷1ðoÔÔ~>^X-ÓÂÝzdù5° kû ¤c5'“þÚÇ û¢L,èDø‡¾AcÄp ÒÂX…‰hc«Áºº¬ðn¾:*¡¤¿x¥•š|Ñ!ÌcÀÑ?Yû ;4µjq59%5¤UT =´bAPPÇ.½Ð°)Q¶—å»e~ûqdßV,Â^>žCÌùÔÒ© —ƸyK§»ÆS¤ßÀò°×33ÒssY“N|»>Àÿ¥aã¦Ø~61vÜ——Wdtàûøa´;**ËÊeddÑÔ‚ù(A:óüŠÌ„·Øÿ=b¸½q“æ§î±ß²Šù>r¾•ߨÂÉñj“¦-íD׃- OùõüÌãݺvvⴸū“âõÜõØmÁoý [“¡;°(ûƒŸ:ÿM€oÄÇ0!òt‹"@ˆ€H¹€¸ØØ,X-Rѯ¿¿ ÉIM‘RQÕ05;uXµy+m›ŽQ¯_É×ÑñznÀ3]ÀKöÑý,¬;2éÈÁX{Ÿ#FM˜˜ŠwasÞ,¼)èØµŽŽ®¸¸8B>.Ÿ;Ál×xÿî.=úÍœ·JAI9)!ÎËã)v ñ{åQ³f­ùË6dg³vÂÈ.S ÂÇGM˜¾xÕVH| ÆjE¤cáÁÑÛa 6./?ïKÄÇWÎð*À¤oë7xÔ€!c¬òÈÙëøê ¨„jŽ­çÏc¥»6ò¢ÃÀ?†É±M;⻾ûÂÌp¶s&o/\±IB\ò€ý†bA܈ãõ‹CFNÒÐÒÆZRÄÙ¾~)¨!ÌLxz;ö; ¸Ku1›´oÇ:®©$Î’¥¥¥ «©]1BÞ¿{X° ‚o×c…À¥³G‡Žš,_° Ðׂ…"øð}ü04îpl/V6/[·CZJ&úg¤³`'„ïãW,&\¬Jö÷(–_ô­Õ÷ïß‚ÿ í+hÝCx;uêÖƒëu`×úž}‡Ì_º:Îðp{ÄU/VÑÀÙ†cptÿߘS⺋ŸØÝ?4ä-o:gʰÑSð…dÔÏïØ³ —§»D€"@DŠ€X##»Ú㸥î=zGG ;HVF:-=#Ðß×ÈØ¸ô K=ò¿wóÆK(«I«¨¦úÊ¥†¼Qhj"!+—ùUJC+ëûWIUu YÙÌèŸíO*L\úJ˯D)ü½ç$³ hùÕB%Aæ.^÷°`PAU(.ö¥¿¦RvÙçOµtjcÎGH9ÌY‹!ÁoZ´4…—(/'›‘ùË™ ì×~m½BÊ¡[D€"Pæ"?‡ z9sÖ¥¥¥yßù.– R@´fÕ£²«p\5+³FN{o f7G¹Û¾°X`¼‚ æR: Uây°kÔÈ"ÎÚUÁ&’ÊD€"@*Ž€h9ØÜó¿„L |…îùC@Å=)T¨< –nÌËË»zádå©@5"@ˆ¨>D˰™Ã}W•&© gŽ©ÒM å«4æ´é*ÝFù¿7;¬4š@ˆ D " ”™”’Q‘zS]D€"@ˆ D€”€@™9%¨›7‹×óâm¦an]x(/oQ”Bˆ D€"@ˆ/ÑrýL[›JJIb§íÔ´Tìy/##ƒkìž—›'//*°¤¯¯/oc(…"@ˆ D€áDë 0F×#‡HIIihhØï´ß¿w¿’’’‚¢.víÜ¥©©‰ót>*¼Ut—"@ˆ D€¾DÑÀ¡¤Øñ\^çr\¤ómLE&ZXÙÎ[´²”5nÙ±'£±“¦÷ì= ”¥Qv¾pÃñ3ׄl|®¦¦~øäE¾yK“øÛ'¤X^J®X³Å¤¥i¥<~“§ÍéÒ½wið ÊËþ›$P¶éÅêD¾Us>'¬<_}(‘"@þ¢ÄCÃúoÁñ?øßæ¶àÎY³q§vÍšËÎLIN‚”©™Eo»ÁV/œC؃†÷²ãÞ›(6&z÷ŽMié©Âr瞷dz´ô´âäøƒdÑ¡ºzõV,žõƒiöâåë Œ7¯[öéã‡Ê1|Ô„ÎÝzAœ1ðúžã”Öƒ÷1VSK;òÛç gO~‰øˆ»ˆFHMMÑÑ©£¢ª–––züО¸¸Xv.üÏ|ø/´ÂÖþÌ-LÜ[XµÃèš¶v­U¶]¿z±ß€!bâbW.œ‰‹‹3þ/”öÌåÑÿ®œcäÍ-Úõ² ¦®Aës§FGýäT Ñ?~`¤Ó¦}ç6m-êÕ×Çè gŽ3Æ™„„¤Ý€!æV¶2ÒÒ~~¯®œ?õG‰úÂÃÍÒ¦=〼÷ ·N]Xãîø ¶gØÈq&­Ûäåäyx¸Â2COÁÊìÓopÇÎݳsrþ¹uQ[(Š1³ýÒÛnЖ +8;ÂnÀP+›ŠJJQ? ~ƒ»L§$%$è7läòøþ“‡÷8³àIE))ÉïCCöíÞ²aë޻ݻs“ý„@¸u‹ÁÃFáDîçîO ï:^÷íÃîôs–HJJý½ë$÷Ùo­¥£c7`žÒ”äd<ÉüRgÕ̵U»n®7µ°jÏ,GPÓŠE³s|Ȉ±y9¹×¯]€|+S³!ÃÇ2š°‹bk‚”®=útêÒSVNî]ðÛóǙ٠¶äûà>vƒÄÄÅóóò=zàÔàPöO¸ÔÒ®5zü”úú “ßû…;™¿²Úut55µíÿïÏVQQyÞâ•þ~¯ Ù·ÿÛŽ]±è?9)ñÔ±ƒ €é;XØí;u zã»–·wÌ-ÛqÉ ŠæÄÑýáa¡|ÛÂüµ¢wÌ̬ääå];?t¾Ãn Œ>0wñÈÏÍc¿8ÿr[¶6KJˆokiwì}#CãÞýBüòùÓ¯¼=‘ý·Ålå½”ôêë5±v]]ôìÕ Aoxu1¶zì»ì~7´'oJ!D€?€ÀAôJѤŽÒ•ýk|îêÓÙR};[Bb$õ㥗gß_Cw444gÌ]rëÆ¥ù3'zy¸Ï]¸¦S‚IË6'Žì_»bÁ‡°P»Ã+(]FF®f-¥ fœ>qhÔ¸)]»÷ÁDÁúU‹ll;Á B®&ÆÍsòØþ¹3&¼ö™6s¡e ÆÆÍŸ¹<\·ráµKçÆMšÁTj×0"¶®_±tát9YÙþƒFR¦Z¦ÿüù=59¹¡Ac´Sžîÿò>–âêeó¶lXnÒªM—‚€3skëv7¯_¾fÙ\&̽ˆX¾ÿ¶uÃÊÙÓÆ=~pwê¬ùÒ2ÒLÆ&Æ-<ÜŸ®^6×å‘“¢0/èïÛȰ § ¦,0l|æÔ‘y³&&ÆÇ1§À¡}Û1¡´lá |#¿}IOÏ8vhÏœicØ“^‡UÕÕ1öïåéæÅò‘l…Hâ4™sòð˜ SÙµ´nm~êèþå‹f~ýü‰)Ê/^±ÎËã¬DyÁÑÚ°zñœiãì·oŒ‹A£|^¾p¾sX`ý# oïðʰUÔ99yÈl\»dûæÕp9ðð°³”ò¢Ys“à Ày3&ÀŸ™9w©vÍZD„æ£Çý…ùL^¬¿bÞ—œ¥ù‹W?yìxŠù„g¿óÊS  D€üD+¨FÊ1æªöÍtõ Y}سqº]{Ó‚Ex2B: 6‚³íú ð÷aÄTÔÔcb£ÙYbc¢TU5˜Ÿ e.²²³ee §„Îu+##“•™™œXw”)++ auMMƒÆ–6Ø•UU1MÁ· ¶2øg>//Ó9¹Ùü›8u‘þÎIÀP+lD¾%TËÄW^}û–”ôü7¾ÍX¢q11L“Ñéjê¬1QŒŒ"IŒù5ÚJ8„”tîÞKUUŽ‚¸•”yÄÿÏȾ‹\©i¿˜øªjêˆ cÐeqq¿Y#[¿AÿAÃaÍ£§a?MvR5Fý™€¥¿Ü IDATox°ÁÁo,Ûµÿø1L<4‰.üƒ[¨§ò`Èü„#„IU5V\gïß#îÿÇ÷Èð75ôý»ŽºágrbVi·01MLˆcGÆÇFG×Ó/ô¨‘ÎYŽY[«øøøž¬!|"¿}½véì€Á#jêÔ ô÷A(ã“sfÔ;œ2ìkto[°:'аãy²³2ñqfGŒÐæû™”©†3á|ËçMLL,|Hðàz'ÈÊÉb6‰ï_1o9L ïK Ãú 6þ½—‘•(bäúܾ~™‰8BzFë€vÎ~ç–¦ßD€"ðG-ãú\½`ÛXÕuÏX&‘÷®ð.»óÏõÍÛö!z„Cô…qSvükšð_¬¿ð¢Jy7>.¿ æT †cSSRïÛ‰^A2Õ>öPxØ{K+ÛÅW¦² ¿ ð¯c}Çò¾°öCIE•Sþ÷?3³2¥ ‚1ðQPTd—Ã\ \;¶¬ÆÀ0R6oßÇ^®úÛ¦lÈÓܤµëãœ%CŒ‘3)pÛÔÕÕ¹êÅ¢rΔi38Þ¼âéápì„©bù7½…« S»n÷^ýà´ زzõô¯^<73#SRª0~IQA)©À6MˆkÀP¤¢¬Â¥ ¦/Ø“fØä+~]EùÐwA»ö‚ÅÏDü‡¿™ü׬‚ŸA¸Ÿ§¢ªŽ`wÆÐÐÒB¥L-\ÞªóÝÛð fÌ^tÅŠ°Ç¦:ø***›4ÎÞ…³'òóÿ[Ã*¨w8e8›Ã·-(œ«É\?á–O7D¸LÉî–þ¯³"!Aoïß!\œŸÈµ›³ßñ÷Âõ)ì—_Â?hhA8MºKˆ¨¦DËÀèþõçá×ÜÃkk(h*Ëfå䥦gǧfÖV1ÖSsú!..®¡$ó=.­³Iížmô„w ¯gOwëÑ— 'ðõñî?h$F(ß¾nס â4‚Þ /¡¬îÂ"?eføûÐOŸ>`NÀ¸™‰ŸW±Æï]] 5þô±h †·uõê¿ñ÷++õªJ9—ÏŸÂ4ײT¬1í?p‚æ¥eezõÀ ‡{{=ÇbVwÄ`t-0Ž™Ï—ˆOÆÍMÜž=Áp,Ö›r5\NN“6?~°<Æf&­´kê… Ìsl $'/[¼ßÀa˜:@gqfôõñÂî@›4…ÅŒõ! ÜŽGrJ’¸¸${Ìi|ýúÖ?V­´2mëîúDÖ6°FüÄáÂ!aÌ ­Ý´«EËÖ¾¯¼¿|ùÔ´¹ÉÓÇ?`7cq*V• ßW^}ì#ökÐ"ÏAŽ(ACG¿ö}‰)‹ÁÃGcöŒkø…À ÃØ³•Mû=»XûK Ĝ~b¿,ü„q w·O¿AǪW¿a[ ûíøêŸ—Ÿwòè¿fÌ›6kááý»12áÞcì_D!Wrb¢V­ZLvA½Ã)ÃYß¶üÖà«jY%–ò¯˜5 ÖobiŠô4ÄÛ€=ËÁVÞξ`~Â;‚sÅÙïÀhemÃXûŒ[Ël¸\èâ&±î»úˆòôxö¨`ÏxØ”sÕºm;yíûŠ &OŸ³n‹=FÄ”ÅÙœ½€7×5v <ìë—ÏÌ<Ào» Ûá‹ ±11~Gìæ2šŒtúÄq§ÃÀÅ.@(6;ñŒ ;ݹ±fÓ˜nÛ6­>æØ”is¡Ž œ@Ánbå±ÖùìéÃc—…mé-­;ÀÀF&ÏÄAñ±qì(8ü `-ò̹K°¿,uÞS`bª«kÌ[´JVVî]ȇ‡y›–b/·°ù„…uêÚ +€! ¿åàÞí£ÇM¶?p/ž=aÞB˜Ø?¼wڬ͜‡Å¾#ÇLÔª©“““OéÊȸ»¹L¹`ïa‡À߇÷ñí.v]Ei‹ ÅÊ)½”Řæ²ß±i舱£ÆNÎËÏÿpáÜI^U1k„/“ŽÀªµ+æsöûªEÓq+º jŽÃÚ/¼f'ij±-Ї"@ª+±FF¿Ø@œíLMMîÞ£wt4w 0§Œ¬ŒtZzv>©­Wìµ³¼LÝÇ%G'çL;óAB\ ›nF%eë¨H‡Ee l£ë×)ZJRo¾¥á¿Ç64©+gsæ7ú¼UP ¨DØ1Ó~ÿñí›×r®c®D}¨ê?@BÌÏv¶í£ ÞêÂÌ´hi y9ÙŒ_Ý(Û×þŸÖÔ^"@ˆ@iD~ôræ,VKKó¾ó]ìú-¨.Q›ø©’—¿¦«,õ,-!—•›Ÿ˜.%+•‘?ÖDQVZ<'¡AùòÙ15R0Í-°a‚LéD â  ¹9¹½úHNJúùã[Åë@5"@ˆ D€! jÀõ&\K¹W*þÛw¬­V ¨#‰€èÀžîS¦ÎÅ`ê·o_°»±–ˆ~ëHêH E«6Í[¶æÌü>þ~Þþ>/«bsHg"@ˆ(ÑrlîÛK{&U‚NtÆ·J¨JJþ!ü^ÁôoÑÒŒÝ^¿—dýÿ!ÝOÍ$Dà'Pf€²"kÃ{ú"@ˆ€ÈHàXÒP°>ž™`Yÿ¾4ö/²ýFŠ"@ʘ@™9e¢×J§µÅ*gsÏõÅ’'a"@ˆ`€Àl JÖ?=D€?Š€h9zj, D Ò é_é]@ "@*ž€xÅWI5"@ˆ D€"PYÈ(ò¦m­ÿš¹˜· ¹‹×7k…tA¼Y(…"@ˆ D€”+jè,\¾y‹ý Ee\‹Vm‘RJˆ(a×ÁsššÚìrfÌ[‰]=}¤|þîþì‘* /VHt‹"@ˆ D€”Œ@õ\›“Ý¥{ßÛ×/” ß\ÑQßMÍmîß½»êêšêšZÙYYŒ$náË7×o„+¤À²º%&&VCL,?/¯¬ ¤rˆ}ZššP²ð$à‚#˜ë_ýf†D€"PRbŒš Ê›ššÜ½Gïè‚Cã}ØgÂËË+’áLÏÈHWPP,Š$É*M %9YF–öÆ­Ò}X5”OOO—““+ŠºééiEƒ \A§ÍÓk¿ˆ IŒ"ÀE€¯mP¬×xZZª —3g]ZZš÷ï*(( ê‚2›hgÛ^PœéÏÝ­¬é´¯¢ "™ªMÀýÙ3½ú¬1úò&ðõKDß«î;ÉÈÉUÈ(‚«@¯ýòîY*ŸjF@mPô×ø}ç{e¤Ìl&#l®êþ;ïü{É2iB*‹ó¨WVíTïH èoà2„SôJ‹"Y†ŠQQD€$ Ü6(Ê{Rx Åjrù.nѪM±´!a"@ˆ(%Ê}ñVní¥DGÙ‰ ¢@ ^¤e8€QÖb2ö§Eë68dÞŸãxùÂ5g,Á_$E5é@Ê=áe“Š* ‚×já{/Þüù8å—+c™?–œ•2uÑk¿(E2D€ü™„¿„+ø5^^3øg EK³?³ƒ©ÕD€Ê%€×/^¬½ö+8UGˆ@5&PÞ¯ñ²›`mQ8þÔ¢µ3…Žátw0( Öi ?³Ô´‚‡œ0 #Àõ^e?ÖK8¿FçlY?–œ/szíWXwSED€TQBlƒŠ—× @íR›"@ˆ D€Toe7P»ÄÀò÷ñƵI+VÐ/3(ãL¬Þ|©u&zÂÿÌ~¯´Vs½W ÞÄþ~/9‡ÿ¹ÞÆe¢*çËœ^ûe‚” !D fTøk¼¼fðþù©Æ½HM#D€ˆ,^ë¿T¥×~@¦*ˆøC”÷k¼ÌfXáý¿Föøx3ÿÿu#À#ù‡ô%5ó"@«\þ Î¦r¼Wýý¼|ø¿”ùcÉó2§×¾h< ¤ "I@øK¸b_ãeæ,_àþøÿú#ÀW’;'ý&U™ïßBUn é^p¾W¹^¼líËü±äû2§×~x\HE"@*ƒ€ð—p¿ÆËÌààÖfø@¡¤êH€èÇúVǶQ›D’Ï`<-…>ñÉ𻤢WZÉßÕF÷‰ U›€pÛ (ïɲ{—™KGSKSxÇ0ÎMQ$…—Cw‰€ˆ(4üÉþñ~ª.ê1¯Ö¢¼Ë¶ÅEy™Ók¿l™SiD€T]BlƒŠ—…PàŽ¸<~XÄ.)ºd $1" šB‚߈¦b¤Uõ#Pô÷jFzZ1š/h´‰^ûÅ€H¢D€ÿ² Šþg•%èå\dÒ¥uòóó`þ·hiZäI"@ªÖ T~—®ôÚ¯GZ"P} ð}9«¹¥urrr¥¤$³³sÄP­ë?ô!D€*O `Ãj)I‰<Ö Ï/zíWùÎ¥"Pu ~9«M¥u²²säd¥¥$eÄÄÊëHbµ‡„‰ D L`¤?7773+[\ü—×;½öË/Bˆ(A/çb•VZ@BB"+;·XU’0 D€T\Ö?Ô¦×~Ué;Ò“jL€÷å\¬ÆÒ°}±p‘0 D JÐÓÕiok Õ‹uQ%›JJ"@ˆÀïð;BtŸ"Põ èë7`Q¬‹ªßnj D€ð! ÖȨ9Ÿä‚¤ÔÔäî=zGGÇ t"@ˆ D€"@DŠ€––æ}ç» J‚´¢Ad("@ˆ D€TCäTÃN¥&"@ˆ D€AÈD†Ò‰ D€"@ˆ@5$ rÀÈñS»öêW IS“ˆ D€"@ˆ€ÐЬ)Hìì,ô´4AH׫ßpü_s†ŽšÐ¥Gßæ­ÚÄFGÅÆD ‘ÿí­ÌŒŒß¾&%&üVR@‹Vf«6ÛKKK¿ ddVlØ™˜ÿóG$ß,êšZ[ìÞ¿s“ï]J$D€"@ˆ Uˆ€‚‚ü‡°÷ÒÒ2‚t.ÕA`’’3æ-syätlÿ1q±ú åæ•öP°Ð·‚t-zzzzZ»Ž]]ÜKJ*¹#QôêH’"@ˆ D€T¥rÔÔ5””\ÞËÊÌ@ƒƒ_³›ÝÆÂº{ï*jêŸ?…_>s,&:JFFfäøi›‰‰‰ÇÄüÜ·}cfFz¾ƒl:vÅh}JrÒ¹‡>~EPtÔ‡÷n£¨ÆÆÍ ­¡©ý=òËÕ §¾F|BâÆ‡ž=¹oÒÚL^AñÓ‡÷NÁaõ\¸“>…‡uëÓÿ¸nuêÖÛ¶SwY9¹÷!A—ÏŸHMNž2s¡¤”Ôúí yxÏß?"¿Qy®’é' D€"@ˆ}¥râãbâbGOœöÂÍ%âã‡ÔÔ¦Á›4ë?tÌÑ=Û¾}hß¹ÇÄówlXaÙ®“”´ôê…3rrstõôsssêèÖ³¶íô÷Ú%°þ‡“——ÇÉKMCsʬEÇö¾¶¶í<}Þ² ËæfffBF¿¡áî­kq1wéZSskog¼ ÿ·|ýöGÎÿ@CöÝVf–»õ>¸ks\lÌðqSÆL˜~dßöãw­Þd¿vɬb)Ï[#¥"@ˆ D€Ñ'PªEÀ¹99»¶®INJÎÓýiO»Áÿ)™Éæ§’² Bÿâã±€³Ø¢+_,eH˜"@ˆ D€ˆR…!L¿wÿ¡µtêHIJiji·ëØíãÇ0´ÊÝåa—žvØ!TLL æuË6æ¸04j Ó¿Çð cƒÆâééé\;ùûx`1A‹VÀ–>òò !ÿnëYtpîÜÂra ó3Y|¼žÛvì¦]«6öEÂòâ7~éi©XŒ$UµÂ™„"*_tH’"@ˆ D€ˆRÍ`5u ,ÏE„OZzê»··®^@Ûšãʹ‘ãÿ‚Wžžþ>ä-¬y á÷—²²rFFæ_¿Bøþ‘´jÖÌÉÎyÿ>ØùòYN.X§{ò }¿!£Æý5çÇ÷¯Gölcö*;Äù¸¹<ìÜ£/“Ë÷¥'Ö#Ì\°BFVZ]8uéXðàîÍek·‰KJìù{]•/–$Lˆ D€"@D„€X#£æ‚TÁØx÷½££c P: D€"@ˆ "E@KKó¾ó]%AZ•*HP¡”Nˆ D€"@ˆ€h @4û…´"D€"@ˆ åB€€rÁJ…"@ˆ D€Ñ$@€hö iEˆ D€"@Ê…9å‚• %D€"@ˆ ¢I TÛ€¢I8WZJ11ò%D³‹I+"@ˆ D€jB ??/???=# æw‰›TZ@FZ GzefeŠA1ÖèCˆ D€"@ˆ@Ù€í_£†””¤´”dn.Kø)­ ..–‘™‹#uKX?e#D€"@ˆ D hZ´4ÍÎΑ—“ÉÍÊ)Z>R¥uùà û×ÖkÀ§xJ"D€"@ˆ D ,D~G1°½K{_ûùS=Je"@ˆ D€ß(µí]ÀïÕ$ "@ˆ D€"@D‚9"Ñ ¤ D€"@ˆ¨äT gª…"@ˆ D€ˆrD¢H "@ˆ D€"P1ª0vÒôž½T 5®Z,¬lç-ZY)UóVª­]kß‘3¼éÕ,¥i‹V«Öo«fRSS?|òbÙ6jËŽý ˶L*"@ˆ¨ÒJ» ¨Æ·25“”’~ùâ9§Œ¤¤ÔνǎÞóö?;}ðð15kêÜ»]Hi%»¥¬¬:|Ôø&ÍšKKÉÆÄD¹º<|òð^ÉŠ*Ã\uuëõ4¼‘a I‰ÈȯO9{¸»¢|EEå~ƒ†µje¦ ¤ýÒëùý{·333¹ªîÓopç®=ÁÖËÓíÒ¹S¹¹%ß–³äá£&tîÖ‹rãêE§»7˰ÕUº(tÍÆm{æÏœ{z×¶ mÚZ¶2mû÷ÆUìFÉ+(ìÚwbë†åŸ#>•_Kñ75cΔŸ“““””ø>4ÈéŸ[ß¾~.q\튉.qQ”‘"@ˆ¨*ÊÅ0·l§¬¢#^C¼S×^™éÏÝ\"99Ù°k-mÚ³%faÑîâùålìÄ©¹¹yV-NMK©S[O³fÍò¨¥XeÖ©«·lõ&w×'ÿ»r.>.¶®ný¾†À‘‘]²r}BBü½Û¾G~SSWïØ¹{½ú Bßs–ofnÕ±KwØ ©)És­ìÕwÀ?·®K!Â/<ž=u´°§8ü 1ì6%&–Ÿ—'$o5»%..‘——+¨Q^n‡ŒÄdKTÔF¦­¹õïßÊÕúg*‚¾bñl)))M-íŽ]z¬\·uû–µŸÂéJéD€"@ˆà"P.F¦MZšÚ .))yûúe_/ÎZŸ»?]¼l=ìÝÌÌ ¤71n!!%ðÚWK»ÖèñSêë7LJL„QëýÂw1cûØÜÂFAQéû·/ûì·¦¤$Û jeÓAQI)êçÏ«B‚ßê×zõœ?s,>>?†áËHò-aò´9ñqqú 4löîØ¡=ƒ†Œ4³°Ž‰‰>vpwä·/È»c÷Q·g›63‘•“{wáìI¸4œµ+)«Œ=±‰q³Ì¬,—‡N÷¹t8td ¿ßå §™ôaïöîÜŒëºÉÈÊí·_š…Ÿ?¾G^:šeyÿú±²îðÌåÑ×/H¾ûÏACFñu0–ß³OÿüÜ|g§ÛÜÓ­§¿`ɪEs౦ Z¶60xäÚó¹ ÏËËgjgÒ$55¥v]MMíCûwB—£&Ö®« žW/8½ €˜„„¤:ÈÊVFZÚÏïÕ•ó§0eQK§Î¸IÓ1?/ßßïÕé!iÜ´Åàáca¶F~û n_">"¡S/<ÜàxàÏLo»A[6¬€a½jö{wnš™YÉÉË»ºô”•“{üö¼Ãñ””$$êë 9®N]ݬ¬,§;·0 Dh Rrsrü|^âyà$,|Q¦.x&™™¹ø?ïpLQQ ³I{vl‚Ç2uæÃÆFâ?¼?çpŒk8¿u›¶v†JròÃûwðTj¥"@ˆ Õ›@¹¬PTR6nÖ¦Obb¼q³æ**jœ1ZmÚÖ‚I„å„9Ø43ç-Á­…³'Ÿ9yxÌ„©ð Ðoà0#£¦;¶®;}ìÅs…Öö÷ïß¶nX9{Ú¸ÇîN5_ZFZP'……… ŒškhjqÊ*Á²ÝÕKógM„ã±ríVظófLxãï7pè(vvX™s]·j!â‹`dsU=}ö¢Ä„ø% ¦oß´ÊÚ¶#LONôMš4ç ‹ÊÏχL“¦Í_ûxqYÌ-Îtêê~ù7Èh\).ddäj×Ö]¶`æž›úôÔ¸ISXÛ‰ M[˜0’X´àéÁ :úí§ukóSG÷/_43!.nþâÕO;Ï›9vç”óTÔXÝj×°A#£­ëW,]8]NV¶ÿ H8dT€Ÿ/Ð-š;Åå ËÚÖÐМ1wÉ­—Eãåá>wár!µËÉÉãîÆµK¶o^Ý·ÿ˜­ø9xØXi™%ó¦î·ßjݾ“ì\·&Lžyúø¡9ÓÆ­]>ÿ}Hî61n>xĘ“ÇöÏ1!àµÏ´™ Ù¾Vã&Æ[7®\µt.g!éW.8 寵‹0 qáñü©¥u{&|WøÞžÏÛ´µêÚ£/ÜÔ¥ ¦Á3?e$ñœÌ_ºÚÃí žm(¿‰Èèxãò‚Y“7¬^¬W_¿s·žœÕáZÐ#Ê%æóÒÓа S ¿Ÿ÷²…3Íý 3K£ÇNá’LOÏ€O;gÚØ#vÁ»ÐoؼíâÊE?‰ D€êG \=½úˆ¶úØÙÍõñ£Nºõësópsµ´î€D¯­LÍ=Ý\ëèÖWW×t¼y ÁÍaïC0‡#íÚw¾vùÆ218‘ÚŒ Ö¤ h¸ˆHAdQzZšŽN]Aƒ±çW/_tïÕoÓ¶}ë·ì†)ÌH *CшâÀ81ìªä¤Ä—^ЉõôôÙUÎÎÊŠ‹‹}ü5–Ì) ---%-Ï«0¦8âxÓ¹RdedÓ3ҙČô4\ÈÊÊqÉ ªêæÿ.ÁýöíËógOÛš³”ôp{jY€¶u󭽞³ݹ>m-¬v<Å|1•»^/Ü™ù4$âS8¸¡áa¡!!A(¶ºÂ&Fwdef¡ûÚ˜±Ú >Ú5kª«k€Òº9,ï?܈~zZ:\Dîê9~³ðGn^ï IDATŒúc¬ýËç]½z¸ÆlŒã«è<X8!$;×-¸—(A^^!--Lp·}§n˜Ÿùüé#«¢ûwUÕÕ´´ ÃÃ?p:.× )Ì4Å+oOfæêµï+ÌN424Bi–Ö¶i «vÝÿù4®]:‡9 TÚÆÜ ÜŸ¹€ÀcŒ, ~ h ã ŒxΠG”K,!>OüU…@2è†gÏñæÕFFÜ¿ À,ówäóÊ«Q#–æ¼íâ*Ÿ~"@ˆ Õ@¹„1Á!’‘G`4¾\à<=]û†QaÃ&Mãb£™ÓÂÄ41!޽ž56:ºž~ í+(*2±œ%`Açî½TUÕórsUTÕ0á ¨c`‡Ýûç¾oîÕgÀ̹K0~ /BP ‰I…&8¬ÌÄÄÂë¬ÌL¹ÿFÙ1¯ÁTƒOUM³j m ‰õ[ì™DD@E~e™›ì"@0Ưúë”s1ýªjªœÂ̵MûÎã&NÃ5ü¢m›VgdfÈýkñƒ0Ò1ˆË• U0‘'H‡­Ü´EK\xyºÛ Š(Ó¶–áᡌYÏ•F-Bª˜ÄÔ‚Øt óS äÞø÷^æ§Œ¬lDÄGtV‘Nœ:+/—µ<1B’R’p?®\r0h$ÂxÀ /0UÔÔcbÿ[`¥ªªÁU;çO4æ2“’•‰9 øŠP+¹™Äèè !…°oس½o¿AXkñ1üÚå³0ÁÕ55 [Út`Ë(«ª2ý|Ý3ÞZù•—§…M,Ò€³ /2xÙ7x<`^«bgu¨‚ø"Φ†ŒÓÐÀPBRJJR²Ä9ž@<ä¨HL\¼ÿÀaXšŒ§"//¸àpƧÕo`€`¡Zµj£Ÿà–hZb|œqÓÂ$ˆÁHHˆÅEfV&Ì_&#œ=¾Z1‰hfzz:ìfÔˆ¬2çÎÌÈÀæHìtÄÇ3×Xh±g×ÄÝzÙM˜2s튒ÁB^¾qðb5ø÷ouúÇ:l Õc9 –:@Ñ_ì`3¨Š4.¶e+S®ìƒ†Æ3‰@2<äXÀTô‡н€P8f!°:bÇÖu˜¹BtÖÎ=ÇØAMLÕÓf.p¼yÅÓà “c'LËÿý_ o“)…"@ˆ¨Ê%¨(\‘Ò¡swC#ãÁè° a“!`£æ ·µ°yáÉZå‰ ¢ÁÃGc®ãÊØGñ.rrÙÙ™?~|ÇÝf&­´kꩃ¾( ¹` öê; ˆ££¢ŠUoá]{öEx ¬Õ>}!FˆS!˜î<|j„Â:µë2‘Öœ27¯]jnÒzèˆqÐóPF$ž>y•™1kþRD„cø¶–Ní£'0&œÙ=<\m;vEÉXYѳwööJœ2p?°(H"$ÉÆ¶#–X0w=Ý]¡<0ú¾|ÁÛ.á)¯¼<°Àý' v-c,]W—ÃFÇ 7²ÃîDàÂÔ̈àÕÀÅáÔùúxa ‚)Öª¶ïÔ]^A!èM $±ŒÁ¸9Ë1ÀdÚ%\oÎ]{ wà#ýäëÔÑ… vÌd4DÉÐÿ…gç»Bá®ëã ÃÚ\XɬY3 ô—ðÚyï¯HNJÀüÌËÌâ DLuèÜ}‡êðÜøû æè0úneÓè0ôþ( X?DÂúGO»¹ þˆ‚!ž4säØI&­Lo,DFI Žë.]þÛΕ]2¾~ý ëM˜(àm¥"@ˆ rœNÐÏ× Ãð¡!AÌ3ìœ0zÜdû'“.ž=ÁŒ²c—¬â]²j,§È¯Ÿ÷íÞŠ,!X³aÂH¾~ùÌ̪ Ñ;c&NÕÒ¬ ã/""|ï®-0ÔŠUoÉÞžîËVmÄ;ŒÚ{ÿÜ䀱{hßÎ!ÃÇlÚ¾OZZæç÷È[7¯r•WûÇ4bÕú¿ÅÅÅYçlJƒîí›×ö<|öü劊…çD|bm•ÃùÁx3¢8–¬Ü+šp)ÀHff¦ÃÙ¶û0vBø{—$DøŒÿ×k?of)WÉÂ&%%ØïØ4tÄØQc'çåçF„¸pî$² î¿·ÝÀÅ+6()+c¬“X3mظɈ1A]épü0ü.|Ø9dØèÉÓç²vsÚµ•™¸xòÐiòô9ë¶ØCÛæ`m®5®^:;nÒôÕë·§¤&cÄݤU.aØÓN̘»¡GŸ#™M9±ƒ)\Ml¹ƒ‘ýïß¿ž9y¹¥võâ™1§a~)#--$äï+/!U º…°{ÄÕx>/\Q [¾Ð¼E«à¨¾ yãpâ02Ý7|ô$´úžã ¬¦¸}ãòÄ)³°4"== 33Íš³Â´Ø!(¼š£§/çJJJzÿ.hËúÌôËógO°¬bõ†ɉñ¯_ûð*Œí°¦L›‹%˜ Â>T¼”Bˆ D€ü!Ä 4¹RS“»÷è#„…¬ŒtZzF ¿om=Ö¸fµÿ`›Èý{¶VÀvïåDrÓ¶½—.8`gÌr*ŸŠ%D€"@ˆ('‘ŸÃ[´4ň³¼œlF&kãx¾--ÍûÎw c¡ye*-ˆWJ)oØ“óÁo^—wET> D€"@ˆ€È¨´ ‘%R][°t  Ÿ:zPÐåêÚpj D€"@ˆ'rŠ÷<,ž?µxDFÚ~Û‘Ñ…!D€"@ˆ¨4Tiè©b"@ˆ D€"Pñªª°eÇþ†åÁK[»Ö¾#gJ_26ã?~æ6ôümQc'MïÙ{ÀoŬBÖ«W¿ˆÂ$Fˆ D€"@¸”c6bÇ©Lض’«Jlè¹÷°ŒŽ ±…â­ÿ]Âræbõ vŸŒ‰*ÆY°Å*¼‚…½=ž¥¥§Ud¥p!°_$»Æÿ]>wßÉ?;v±ÍhrJ6ô¼ëx½¸ýR‘­ ºˆ D€"@J@ \sËv8¨56&F¼†x§®½p„+ïyU+ÏŽ‹ÑÕ­c°¾ûúÂãY±´/®|± /™0ŽgÊËË-A^Š\‚\ÅÊ«ë]±d6SHNns¡ªªzãêylB_KGgúìE ñ±îÏ\ŠU "@ˆ D€ˆ8rq¼<ÝLZšÚ Ž3co_¿ìëÃçˆ%ØÊ999?†……½Ó«§ÏôæízÙ PS×øôñùÓG££~Î^°,ømà£ûwŽ8Êéî­WÞ:qtxX(NWµ0ÄÜÊVFZÚÏïÕ•ó§233ÇMšñóg¤ó[Êʪ»ö¿|áôã÷p‚ÒÚM»æÎCÇØ½‚m1û"»…‚¢ë€*û­Ì- „ã2I ‰{wn>t¾Ã$víѧS—ž8‹GVw8ž’’„x¡U¶AÆÂÊöë—O'ìïÓopÇÎݳsrþ¹u]KßþCpÌ-NoÅA­§ŽÄ ²œB€¢üpº{•¶ik™˜P¯¾¾„„Ä…3Çùú8RwêÌØÛÕÏÇëÂÙ“8㬖NtU§®.˜úù¼D{qÞ—n'ïãz™Ãk9o^¿ÌüÄÙU>/=44$€ ý$D€"@ˆ@U'P.€¢’2%…},&&fܬù‡°P„úð’«£[¿¡!s.)N~ôÀI·~}^Fï:tôôÕ5¶¼öE¸9ÚwêæòÐéó§0‘añ«ª«iiÕ|íç­SGWK»ÚZØ`Ì;;ë—cÏl;u½qí"Œ¬Ì,, hcf ÉР††MÄÄÅ šÜwú¶,,ZÃÆM‘Î¥I»ö¯]> ˜v`ì`1±7®^@P&âS8,xä²°j÷äá½ï‘_‘~íÒ9Lq`1SÚý{,káò˜IxòÐ ¥aÂñ߀ÜÜ\ii)]Ýz˜Á­¸¸X^ì8KÌü@` Ÿº†» Î,ˆ×‡ƒ‘š’r÷ö sKÜ‚V˜+ÈÍÍIHˆ‡«c`Ø„-ÏÖ³„Ä„Äc‡vïÙ¹ùÂÙ7§Á¥’Ý€¡ù5ÄÜž>¢*Ý"D€"@ˆ¨ŠÊe èmXÈÊÉKˆ‹ÿøþ _^4ËÎHˆoki˜ØÇéé9êšš-m:°…•UU£¢~øû½4³°¾çxÆîÅs§8‹’–‘VTTž8uV^.+ª†»¤”$&`g§¦$׫ßÀ°±ñý{Ž–Öítj×ilÔäÙÓ‡\Ù1EÀ¥^FF:,x&133¸VQUC±L"ü ̨ª©åd礧ÿ'¬¢¦†¹F&æßb#¿}½véì€Á#jêÔ ô÷¹rá l¸jdÿLIIf®áååå j(--•K˜íB`.BUMwé4dÄÌ¥HHJIIJ²[Ä©g!БBH‰úùýì飫ÖmE(3ûÄn=úš™[íØ²Ž7FHÚ”Nˆ D€"PU”‹À4úÂ)`ÄÚÓݵek³ž}ܸz1>.ÖËà Ø\¹¼^¸2:8ÐÁ6ŒkÁÀ`<ÂïÛùõKW. ö·nÓ»pÂj}T°.Y-âc8§“]K»&ß%®âÀÃ$Âà†Å FQQ CÿlÉÄøx%ÕB™/ðÓÃÝ_›4½oÿÁwç*¼X?ÕÕ5>~x,êšZ ñq¸4l4<–u«¢EXG  üýÞJ¹¬¨'q1q1&Kçn½:vé±}óê¢0)–Ú$Lˆ D€" Ê%¨X súç&VÖÂ8v}ü {¯~úúב““35³ÀX>Šz௢¢:`Ȉ—Þ¼ô®.†›’€ofÒŠ©Ë:vîùþ]0~¾ ÂfDaïCxwéAÒàá£544Qf denÛ?¤CçX‡€id ð÷á›÷özÞ®}')iiTÚµ{/FÚu±Jûð`b_D‹¯0Fè±ÊB^A¡wß/½< ×èçHXÿ¨º]‡Î¼Y¸R°ÜB¯¾>â‹jש;rÌäà7¯™ÀªºõèÝï®-)))8Ç ¬[ "@ˆ D€T-•oá!òþSøl°só—®^<3fâ4 Ég¤¥…„¼ñ}ÅÚ>>/_ØvìrûÆU^¸ˆûïm7pñŠ JÊÊñqqXNðÆßbïB‚aÍ¿Ç ú A”Qh3Àõ¹}ã Ö/Yµ Öpä×ÏûvîÄ-W£Æ+/¸ó­’••{òÆáÄa^ïîˆ5Zµn[bBÜkßÂÅÊã9f¢VMœœ,èpå‚oÆb¥`¤å«7±vò}yÈ{ûÆå‰Sfµncžžž†éŽfÍ[ /PCSsLß©Øm GAþ×®œcä»õ쫪ª¶ñï=ÌO89ûíY éCˆ D€"Pmˆ52j.¨1©©ÉÝ{ôŽŽŽ$€tYé´ôŒ@ßÚz „ˆÑ-"@ˆ D€"@JC òsx‹–¦ØxF^N6#ó—}q8‹ÕÒÒ¼ï|ƒÅ‚êªü AšQ: D€"@ˆ eN€€2GJ"@ˆ D€Ñ%@€èö iFˆ D€"@Êœ9eŽ” $D€"@ˆ ¢K :;ØÈòø™k8 € Ó­V­ßV‚>Ù±û¨^½ú%È(jYÔÔÔŸ¼( Zik×ÚwäŒ(hB:"@ˆ Dà!PŽÛ€¶25“”’~ùâ9Êá£&à´)®Äƒ{·¿öeM[¬¡ÑÒ•±è½n+#[ØÂʶgŸþ¬GÓÓ?}úpæÔæU¬¢ÐÌs–°³¼ |½gçæöºwéÖsýªÅÌñº8èÛ˜n\³[š²%Ùsrrp"ï]ÇØE´XUÿVx”Y?"¿9ÝemÊùÁ EÜtý–ÝÇÚwîÑ',4ØÃí) )=±¿w:vxÏoωûmë 0`ÈȸØX×'÷·î<¸iÝRœ ‡D£&MûV¯¾~RbŠų™rpðQãš·h}Nq¼´ãÍ+¾¯¼q ÇM´µ°¾|þôÒU›p:uQ*%"@ˆ D€Tiå⻫#^C'pef¤?wsacºzéìõ«Ø? aÜÜ$øm@ 8ZÛt€ÍgeÓ¾dÃ3~ê±CöAA8W«i3ñ…âK™ØØ˜ÕKç2YòòòpñÌåA[s+ú çç÷<|÷¶œÖ?#ŒSŠa¡JIIš[µ›4uVø‡Ð˜è(vÕ°YyO.+–b‚„qlðÓ'Ý’^VÄ„TQ¬[8»ÍÏÇ}‡ÃãëÙ323Ÿ¹úø1Œ}—ó(23sŸ¹<:b\:ºFÆÍÛ´µLJHÐoØÈåñ}×'ì 1·²•‘–öó{uåü)€ª¥Sgܤiµëèæçåûû½:}â „i>bÔÄÚuuÑÀ«‚ÞÀ Ãð6XµïÔ5èÿÙÓGÙõ"hËÎÓ'DJßþCl;vÅieÉI‰§ŽüöޝžL"_bºõô,YµhÎT¦_Z¶60xäÚó'O›“šš¢£SGEU s0¾ãâbGŒ™ˆóÔfÌY”“sçöµÐÖél6í;ã86I ‰{wn>t¾ƒ2xØheU´÷®ãõ§ïóÕª®®Þ·/ŸE|úÈø†/-Î,8êøÂÙL N‹ëÖ£O½z ÉàK•‰ D€êM \E%eãf-RR’`Ž7k«=1‘O\M-ÚˆQ9qt`@ù…§{ÿA#•p<­˜¸¸™…ÕÁ½;ú þ½i5®¯X×ÖÒÚÓÝ×­Z·ÍÍÉyåíÙ¦­••µ-㘙[[·ë¸yýò”ä¤ÉÓ Gå!!ÎÃÍ%)1é|‰}‰ø˜˜Ð´…I€Ë•BŒ§«ƒð1iÙfËúII ƒ‡±8ÌáÄ¡KçN!‘„522rPcå’Ypl–­Ü„}Lƒ ±ûì·ÂÁ ÍpW~Ñ©F M-í5wˆ‰‰Ã/Úµÿ8üL`-âš8皸r±*)« èëëפà ê´ÔÔÔ´”ëWÏ ’§t"@ˆ D€T'å²XO¯¾«ËçÝ\?zà¤[¿>/2Œ¬OŸ½èñƒ{ŒÕØ ÁÁ¦m­p Ë53#æ&“b]Æ7èM@½z…G#ò祷Fн_¸™™ÛHJJAØÜÂæÉC'„Ö`䨑cÀÌÂÚñÆU$âÖÓGÎL±ß¾~Þ¹uv-sí9xzÔ¸)X7Ì«*gÊ}'G¸ˆ6¹{û†¹¥ sKM]s÷ÁSÌ3lù÷¡!ðg|_zaø™o±Èûuï!‡¿¦Í…eÏ HGD| ~yL#ØvêzãÚE¸OY™YŽ7¯µ1³D:8h׬‰qt,ÀP7RÌÌ­">…c¹,ø°Ð @»ó­‘+177WZZJW·lhÁ=—fo`£LÆID “'–V¶È+''ª½ž?cÊyáñ Ö?®ßøûbö€¯Jbb5n\½€~þô­€31t+.`ýÃ}úöí WFxs¦{àäø¿+ççNÿãûw„`!¥(Ö?Z:eúÜçnOÑ‹V¢ö—^|Õ£D"@ˆ D€T3å2€ø`’•“—ÿñý¾¼Ô·óÏ­«œ·<ÝŸvíÑk:-­ÚyþkDB 55™ËÎÊ–Vgí꣢¦fÜ´Å?·þ‡k?Ÿ±ÅMZ™ú¼|tv}LÔO&œ „ÅĆ×GsÄÙcŒ™ zAËÌ9‹âïÜf•)èÃ6‘cc£Tÿ™†¾uýr&KJ*k*>’c'L}òÈ©CçînOaá)o™È¸yí²ìœ,Ù컈ÿa®¥e¤1´?qê,ÄL!†²¤”¤¸¸Ø•K\µaFåîÜ‚‘Á{ýÿÞËd”‘•ˆø/$†·^vJä·¯×.Å2Œš:uý}®\8ÃØëB²ð%æåén7p( #(+<<aHL ˜Ìa.²²³eeeø›‘‘ÇŒ¹•™™ \س½o¿A˜7ˆø~íòYŒÓsæÅÊï:ºz„<ê•–’^»y×»à·XJη v"–U ú?=-¡eÂ%é. D€"@ª+rqXB¶yéÑ»–on\»$//Ÿ“,Ly¬Ê­SW!àëV.ãÍÂÈ=#ƒal+ëp°‡¢h˜DD30ÓÓÓ••U™u¢X Ì[2âX° Q]]ÖðsfV¦TAT >XKÊ)Œqwf^B]S+á_37/7—mò²…{Û D ¶—AÝX~°kÛzŒèsÕ‹Œ¼ÁQl1 KCáÃûv~ý VaââNaÁÀ܅˃ßÂ- z{xaÄ[¡J\5òþôpwÅ3ã&MǪev <¯$W '1´"ô]Pë6–Öퟻ=ž——¯<ÜŒ=»¶`V§[/;øŠkW,à”Ù¶y5\£k7c uÇÎ=Ð)ׯý·¬œ·4& Æþ¥¤%íÝQN««UMéD€"@ˆå$¼yFMšõé7äø‘½ÎGÈ ó…q†\XS‹¨XiŸ#>qî‡Ã[ Í{Ž×7¬ZÂ|îÙÑ̤%b»½½ž·kß ëƒ‘¥k÷ÿ6õñöèܵ1¬nÛ¡+S`3“VØSCC†&ݶjcÎ÷_"Â6ÁºXˆuéÖ›³ön=úb–#÷î;PHЖóvíÑ÷Ì©Ã0v1MU°µçmÅoS\] 5.$1¹…q¥½h)JFÐþ‹ˆ W^†ÚZØ`ÚmÁ¢XÌ @291«™…Ô¢S».Ö`\Ãðø""Hˆ0n "†[X˜Ñµg_øu¾/_/j#ô_ˆ úmÄñ<ÀÊ-˜áúÔÓo€‡‰zõ`ÿVλ˜$ÁC˜5Á³B‰Øg >á‰Ãû°xéÌ#Ç].ý&D€"@ˆ@u'PŽ3‚ÐacPØÖËWoæøßås¯GŠçs׺qnYÃ[N}ý†Ú5u?tfÇ«Ä#šAùX- S»ÎªuÛâ^û¾bçÅÞ£1ësj IDATá^½~{Jj2vÎÁF¸…}â;vìŽýgkžÿÂýéƒû¬-h»ïûÊ ËL‘øÚ—µ[<ûóÊÛcùêM¬]€|_Þû‡{}F –åøÉ3œîÜdbúaŘ=oYàkŸ„>‹¡9ËçºFÜ?f¯Ø ¤¬‡íkÞøû6n‚½t°hú;?Ì„ÙØïØ4tÄX¬iÎËÏÿpá+ÄÅÝÍ{í=ìà ×·"tÄÈ1µjêääd…¾ þíႈ¡dÐ=þ¯×~Þ¼q¦à\‚á£';éúÕ‹Áoüy…Åjˆ!n Ó&5òk`›¦3'ñÊÀÓøü)ézõôï:þµÕ¨±ñ¢eë˜,‡N\Àº‚Mk—ª¨¨™[´C"h0·Øoá”Bˆ D€jL@¬‘QsAÍCä}÷½££c ]VF:-=#Ðß·¶ÿ%žBòò½…qî­;,šóVò D‘%°iÛÞKÞø‰¬†¤ D€"@ª.ÈÏá-Zš"D^N6#“ÿ3h––æ}ç»°ÔÒJ¤ ҧѽ‡ö²$ë_%Ѽ…eˆ> ~óZ4Õ#­ˆ D€"@•$=ÌÇ=‡NÇÇÅìÙùKt yJ8Z»vž:zkU·èhHš"@ˆ D€0DÈ@¬ü¬¿FSÇTEöÛ6TEµIg"@ˆ D€üD+èìj2 D€"@ˆ¨HäT$mª‹"@ˆ D€T2r*¹¨z"@ˆ D€"P‘ȨHÚT D€"@ˆ¨däTrPõD€"@ˆ D " P‘´©."@ˆ D€"PÉȨä ê‰ D€"@ˆ@E  "iS]D€"@ˆ D ’ PÉ@Õ"@ˆ D€Š$@@EÒ¦ºˆ D€"@ˆ@%  ’;€ª'D€"@ˆ I€€Š¤Mu"@ˆ D€J&@@%wUOˆ D€"@*’9I›ê"D€"@ˆ •L€€J"@ˆ D€T$r*’6ÕEˆ D€"@*™€9V¶ó­¬dT= D€"@ˆ¨ÖÊËX³q§U»ÅB÷1<ìÉcçbe!a"@ˆ D€"@ŠE@²XÒå',..ñóG$¾åW•Lˆ D€"@ˆ@E8’;÷µß¾éKÄGWTTÚ¾çÈò…³š·lݦ­eRB‚~ÃF.ï§¥¦XXµÛ³s3dôõ †ŒW§®nVV–Ó[OÞ«¥Sgܤiµëèæçåûû½:}â ĺõèö.<,”:’"@ˆ D€¢¨ 77ÇÛÓÝÒÊ–qÌÌ­Cƒß&&ÆC¿&Æ-voßpêø111sËvŒÆÊʪ󗮾zÑá…‡›´´ŒvÍZH8dT€ŸïöÍk$$$êêÕg$­m;eee’P”ž&"@ˆ D€"åµ€ ®‡»«™…µ¸¸Ò-­m=Ÿ»1B‚ßà:??Ÿ¥¹Õ§ð0÷g.999ii©Ÿ>~À-xÚ5kª«k wáµ+æ?}ò€«.úIˆ D€"@ˆ€ äD| OKM5jÒ\[±<º~~^ŒBˆÿáÕ V~TÔO®ô+—Ä%$Wmض~‹=ö âÍE)D€"@ˆ D€ü–@E„1Jx¸»Àp‰‰ò}å••™Å$rü³u‹‹mÙÊ”Kõ„¸¸Ó‘BFÆÍç.\ü6 "úm I€"@ˆ D€6rœÀÚ_))iæ‹k/7“Öm¬l:xº?Þ¯¼<ê70°²i\òò õê7€¼©™…’² †ä¤DüAHÄ"à†ÂK£»D€"@ˆ D€° ”ã ÀØ Sñejòòt;qdßÇïk×Õ }÷Vx$%%ìÞ±iØÈqÃGOÊÌ̸çxD†›Œ3k‚“ŽNIIF!´X8IºKˆ D€"@¸ˆ52j.Jjjr÷½££c ]VF:-=#Ðß·¶kœ^øþ@jJÊõk„‹Ñ]"@ˆ D€"@¸D~oÑÒ±0òr²ÿFÔóRÒÒÒ¼ï|WAA‰÷“RŽ!@\Ubi¯i[K7×Ç‚T¡t"@ˆ D€"@Ê›@9†qªn7`h·žvœ£¢~”w“¨|"@ˆ D€"@¨ ÀñæU|)AéD€"@ˆ D€T Š ª˜öP-D€"@ˆ D€! ¢ÀØIÓ{ö Dï’ÝÒÐÐ\±nëÞÃ]º÷.Y e•«ˆ ÄÉ ó­d*ݲc?³çéäisJ©¿MûÎ3ç.)«¶T@9œJ\ÝŠ5[LZrŸ/QâÒª_ƽûÙï?±kÿqlZ±l=|üÌ5ÙJlˆ˜¸øÔ™ ö<=}öÂJT£èU7m±uçA¼÷9sÌáj}ý†EÏË+YÄ÷oFO)ý»W4ÈþÇ…Ýq8²O‚hj[­ 5dÄØÒ”P‰y‹õ¬D=©êªB ¼B€ÖlÜ©«WoÅâÙÑÿý/^¾ÞÐÈxóºeŸ>~¨,:»ô ºeÝòÊR€]¯·Ç³´ô´b©áxóZLTT±²p ÿ½ëбÃ{ÂÃBK\BÙfÔÐÔÚ´mïôI#˶ØJ/ͪ]‡¶æÖ{vn¢IQdøf‡Û–áL¡ÞÈÐhÖüekW.À1y|勞(''g×èÒ…3pÎFÑsU˜äÇð°´ôÔ «®ô5kÖRK»æ‚Ù“óòrK_Z”ÐȈk—Ïø¾òF];v-e%x¿•²Fqq‰£§/órúÄA·§¼éEL)ñŸjË5±Šï8Q# ÊúT¹× (Ã$Ý@ ¼ýóG¤¥µ-úkOCK›}pe¡W×Ð ùÍ)lÝð/Jùýã\T5Øú¼ðxVYÜþ´z˵ëKó¼Ãñõ[ì_z= Â8÷¸IÓ¯]>W2럫*ªêéiåjý—†*^&ø–]çÕÐÔŒúù£/ÒP*M1;ùí[iJàÌ[‚÷[)«ê“G1…ümðÌ©#Áoñ3§àÈÈÊýàûbbùyy¯Fq«®øŽÎg¾¸ W<ÒJ¬±Ê½+‘U]åè¼ðp³´iÏ8˜ºò~áÖ©K/è¤[OÁ’U‹æLeNómÙÚlÀà‘kW̤n×}:ué)+'÷.ø-  ””¤Ù –¿ |tÿ.“eõúíNwo½òöÀQÁ#FOlbÜ,3+Ëå¡Ó}'GÎ2ÇMšaÒ² fº{ôî¿Ï~ëÿÛ;¨¨Ž.ÿ6ÀXkì={/1&&ö®X£±+±×X£QŒ½$ö[lÑØ{…XQ1vQ±wâÿ-¼lvß{¬(DÉÝÃáìΛúMywîÜ™yüè!wq9qDX„§çþ kWñ Á6¦D©²Ïüýsä̽w÷ö=;·(1äÙ:{êtèÔƒÕ%ìБãׯ]ùçoó̰ÒúðÞ½­›×gtqmóuçL®Y^G¼>í}}•Viçý<Ý\…Ÿ"E*÷~Cõûo«³fÏѼeûL™³<}úä×e‹ÎýyÆ<îPãÖ®=û†…†mÚ°šG˜(´hýuÙòUŸù/Yð“2îsõrúK—«dogçí}|Õ/ BBBÌãõõÌÿi©²ž>y2gÖ¤Üy |Y·çÑ®üeáñ£^xN—>£[ÛŽ˜<  cGÿ8„£u1»öìŸ8qÖ%xJuܹ}SMˆ$5uK•Ú‘Ô7o\»o÷vóœÅí×)R¦|pÿþ¯Ë]8ÖÚÆ*ÖMѼÈê÷€€§«V,jûu—CûÔ®ÓøÉãG‡öï¦I7jÖ:mºôwnû-[2ÿækøÇrŒ¨L1|ú²Nñ£ë•Ñ5sÖoûM‘"5Ù>wöô/‹ça¾’'o¾„‰qyßÒEsè,ÄéèèÔ¤EÛ|ù V·`î 5«CͰuŠ¥ËT¬U§¾“sV—.üùáƒûŠŸµ¿.¯[¿q‚„ V-[üäÉ£Vm¿¡*ìݵfÕRb£RÊ”«ÈºŠaÉ’å’&K¶w÷¶Û6á¹á«ºªV¯F«S3P»^ãJUkP#ÌmÌ™yõÊEõ_h64ãåú(gî+W.Ι5¥aã%Ë”ôè᜙“•6i\@"±nð«|Z¯asfhð¤»:°WM4iÒdLÛò,L?=êy°xÉ2c†à)J÷=»¶1ø8$uÒ¿çìùË÷íŽa±Îõ)¶ô>J”Ñ%S²d)¸ÈeÑÜ™t„îî.ž?»S0Gylù}í‰c¨¹bVIí÷0üù‹@­ê^¼D©:õ›Ò´žîܾi÷ÓxhK6ÔñMQüý³eÏ‘(Q"¯”¡Fƒfc6Ô܆†¾R¿‡‡Gð“žÕÊ­£Å OS©Û iåª5^…†ÒTÚ´ïÜý›VÜ8i=,¤Jídщ?UêÔßö–+W>??_uœÑ|õ(ƒc{Ú´égMŸ¨tL5‡ÊÞJ÷ïßÙ¶é·T©±¾[¹l!Ñ— óc¯®m™37?DgÍ®j‘4ƒ·ƒy–ÔŠ3w¬X©ÚçµëOñóäñcãCzmÞ e*$O‘òîí› ì\٩טÍ_µåÌÅ®°Z·fy‹Ví%ëqUi™ÄiÝØxû´ïØÍÅ5Ë•‹çéÁ!Aµ`K“¶N‘3qÚÏ“&ŒQj6EŠ”¦ü4¨OwFik©€•>NŸJ–<ùÕ+—Ïÿ H¯ƒX'G ê0È€FËaI˜ áÇ÷'ެÙ-J*?…€9XÜpÿþÝ9så%=–¼E)°é-tÑ‚EŠ*ù M{yî׫•¥ÊÕø¼6ãÈ€ÞÃÂBÛvìŠÏ£ž‡b” ¼êÒgt9}ê8?»ôèàÿ´ï.Æ -_©*¯ óhÏŸuÖçäokW ìÓ•W{ãfn¼Ø† t;jPÑb%>ýÌ49á“¿@ÏCû† ìµw×V5¸^ž5³gžh´ß4nyÆû¤{×v}{uܻ璮qXGgç~ƒGñ<À;Œ™À·ý†íٽͽ[û_ÍéØÕ=µ““yðK0ðÍš6‘²+âH¡ÂE¯]½BºÛ·nb(Q<ש×(Wî|ãFЧKR„ë<ðü9"vë5 }†Œût[²ðg·6ß$Nœ8aÂÝÜû_÷½Ò§G‡Åóg·j×I1&¶.æ¬i¨Pò£T‡yBí:t[8wVÏÎm†úör¤¨­~ JÊìnÞOÓ‡îÍØZ§AS‚ðíÐÉ}颹ßvÿš÷jÖ¬WL„»$v#÷žÿÓt‹‚ç+PxÿÞ£‡÷_¶dn×ý’%K®YR ÞV¯\Âô€B)‚ÔÝ»·ÇÒ£s›Ý;6wêþ­½µ½¦h ¯CûïÝ»óM—o+U«šÅm×^ý[·âÛníxêÕgïÍ€Š£foßò›ì1úùó²MU"!ö>JµöíõÍÓ'ÝZw$,Žô¯ —/‡ôïѧg‡ƒûwáhPjÌSÌ_ p£æ­æÏ™Þ«k»3§NtîÖǤðûßÿìí“fÈè2 w×…ófµlÓ±Fͯ&Œ>rhß •ªY£!A¨‹ ßC¾§ó³déòå+Vý~ä ïöRwz`…ÈûuÔ°~´¥IF?}¢q§a™²]±èÛîí‘]† ÇÌ™¶}ö´wƒ&&ur´Ôl÷íBñÁ ¤v7•FãmÚú¹3mâ÷eÊWRÝù’7q£‡ ÐËÜÑâ»qïS<+V’éëèïúýésêëN=qĦL¹ÊÊÓL®™Ó¦KwÚû„yÌôš—/ŸO?Ò\úÇCPP0“¢ž[ÿ4ãG$Ô"J([²¡Æ_ @á{wóêKÕ¡F“ƒ^c6,øXüÔìY¥ËV,U¦<ó®a{,TD b=,XwU<£4Y¿z9Ò9³beœÁQïÕS¼xé?OÔ·Û-¿ëšY½tñÏ|y ðûX&ÃüW¾_¹|é?Úæ§ÙU•„Ô¤Y!4~;hfLuD–ýôóÚÆ~Gö¬Y„ef•/_AqÃ{ui½|é|FxƒÈ-^µj†ñŒVB°W•„¬=Í×éS'zui»cû¦²þÑéÔìEÛ¤­SD|?êu¨l¹¨‚.ÿéß@*È™;Ïc†}7¨7ïÍReËëåwëäÌI2¬ÙÙÙõwïüm÷v¨QÂÂLv†zР äÑœ@,N ëyx?/94â(¥îÞ¹¥²æ…¤tÞå…‹?rX׸µjxÂb>ÄûƒW;²×)ï£ÌæQ¼a©2¼O }õŠ[Öl9°ˆà;Âîî&ušAí–*[qÝš/_¼@͆š¹Lù¨äWQÓ­¶ypÍ™5ÛG|)^¢ôùó>çÿciŽ&VðҥˀOfÖ®@eKA"ÂÃ÷í݉žÒ]¼p.sÖìÅ1…Ôú³â¦ß š ßÑìÙ¹• ¬mük <<ÜÎ.I–,Ù˜šòHY²ˆfãwã:¡Nó"Qµmg˚ÖÚØ ÔDK–*·iïô/2³o÷óÌìÞ±ɺí™û1î}ŠÏ£GLMŽïD˜+O^ÖmΜ>áœ6­K&SÛ+[®ò‰£^Æ’™š"}]IDÄk–kN?’;w>å‘-ÙP#aB®,¼øøx;§‰j49è5fƒÁŽõwÍžEÒ,¶<~üˆZØôÛ5”Á°`óσÐàá{•qÆàÕsäCÊ2Ž^µ2ªäÌ“ŸõØ<ùòoßúûG9ó0æÉ[Pm¢í_š]UÉ­š4ïÁ7};¨åE g¾ä1vxÀSS‹ŠQÅÊÕyÓ×”6ƒZÚºRT‹W­9+Í·­yTzãªucca-që¦ßhE´çsgÿ±6®Æm“ÖL‘q’CæÄcÒr>È©€åªŒ?²‘-ò%ÅÇ:Ï8j&§æ–‘-eÊT.™2!¤P¹´FƒF¨†’/BÀ‚@,š‘SùÚõ%JœØëŸ"þ¯Cu4aß!wûú^R†Hͺ¡ë*æ<å­FCwtrºsûÖiïct¼-×–.[aùҫ׬äZZKŠ”©,<4E ŸêOúKPÐËÛ‘3êÔNΛìs”ÏãGÓèÄÝ Œj(Ä‘z šû¤”CÒdØQ g^‡µŒ¹•þ ªCÍ¡¡!–‡ù‡3‰òð*„®Ìw–¼=| 8>zUé «W,©ß¨yWŸÓ'0.R¦ˆjê| ˆœ4ò¡êÍÛ¶}RSºÑÐI§Á›'¡~‡$ì(,̽ùÿUjÍ°Š£qïSü¨ed´d M«c¦Ä¾ä’ßÖ®,U®â¼ÙS ’0”ý£\õ6˘1Ãò}ÛlφÚ1™¹ED„Ñà_49è5fƒÁ  z=‹¦bVQua0,X$áïµá>äÓ4μz" rÈ#^=/žfËþQž¼¶oÙX¶|E—L®yóå?°o§)æèvÍ®ªÌîÔ¤£D/‡Ø°Uÿ¬ fÌnñ£‡H}Aà!yŠ,èEhánñª5g¥ù¶5®7®Z76z(˘ên½ìEÛ³4SDøF‡˜/a”’ÞÞGȤ¦T@OäVyJ)B_…Ú9GlÖyƳfr*Cû÷ðêܽ½= ë׬4h„6V‡xûˆÝ -Û÷Êe”ýýVu2‡‹(éâ¹â%Ê”-_ùðÁ=Ü1éaüR<`%‰ ⩊@[а±ÛyŸÓÈñн;oSĈáƒÝ-VÍÈyÙ#— Ž(/K’xú$JæÐSÕhæY3{N &Nb§¦‹i fGÖgÎÖ#ì7ØÛ hï ‚ðhÛæ Ö]{ôi2¤ C›xáÜŸ³§{„Ò+—}Ø‹çÏgO›xëæ ƒxŒ=õÂvRŒ#•9@štéü#혭‹‰š]/*ô…S~K]V«N»ŽÝ°OP}ÚRRÕ3IcÛ­þL*5ß ‹©›¥´éMv&Ê¥õÉãGôJjÎë Z<ÆCQMØï'L3Í«þ¹Â`ÐÕ ¾<}R `”5ÞhÉþþ&™é$Iâ¨ÞÍ[Ù,Ý2ª~Pe(TÔcÜ4âHK§Ì¡}Ò¿œœ©så±mÕñwм‰Q *¦äjr˜õÐÆGLhS¦vT<³{D …ŠŽ?ú –÷(#–-™gc„Š·h ¨× 4S1M™ƒƒÓ¤Q^ù̩̽%øßß B‚CÔ1$Eò”æóO͘ÍÓ¤j«ŒÉ’¥dŒâ©×Áýº÷fxa"zåò?6BDع[ïëWyyDvoÝ®S‚צÖûö=zÙ`@0ÈŒ^Ïb:D(YzU¾è Ñ›7xõDßÙþ÷?”ýØ…39d2Àkɽè>"cŽf`×ìªJ‰Ô¤£D!³ýÉcz¸à½ÀKV‘\ñÀê´Å+L¯1[°5g¥ù¶UÒW5 BM™êoµ [8hš>  Rô<´—UîGðR€‘hJ‘[?2HNñÌ Ìdž¿ 3!6°|q÷ÎmÛåëÅå¿I vM€`ºò—X**sĘ2×ø¢6š“f{ѬëA¿JõÏ1ôG»Ð¨™kÙÊLúÏ3§S§v¬ß¸ù±£ž¼œÈRõ½»·5kƒn ½5ëݪŪu´¸°95'j-Œék}UczMoÆyÖÌÞÇ÷ȃ«k² UÀhÆÿIÉ2ìÝa(DØâ¿ª;×ô¬:F¼Ž˜ÿó „T%Ì«R½fëöч޽{‹­Ûæñ³bmIUÿhÔØ&ËÙùœa‚ ¦šÅ ˜˜/sÞQ“íüŸ>ž5Õƒ7 ©h–ôâ9ŽeàF­ à ½»xÜÿÝ(,snÝôSÖháG³):¥Iƒè¦Ú¿éÕf͘ظ©[‡.½LÇnü8õ*ž1ˆïХ爱“¨ŽÏb÷­^ Öî‡ìa#Á°Q˜âœ:µ[”þ5{úÄfníÇý8‹:bSÛÞ¨:H=â¯Ë·jß™7qðË—.œe’f¸ êa;1tÄx è§J pœHº .aa¯.]<¿jÙ¢79Úê5x½„V¯XÌ©_Sæ˜Núãp¡"kúäÀ(v¾rýÓÇOÐzhúÑs<|ho³–í\³dÃ>aþOQfxæ†MZÌžþ£^@k÷_Ï騹í¬ÏÖ±™»hrÐlÌT¢õ€`cïÐìYG¼ Qâp6l·ü¾;{e<Ô,ºªf¡l|õð"»rIã–‹Î#(_¾h:í€\ìZ¡­* EÛü4»ªE&£D³Pª#³‘Ù3~ìҽϬišˆÌƒoX·Šmý‡ŽA³vç–ß´Éãx³Ælý¶Ub ÕW5 ‚Em¾uûNŸÖ¬…rT¦OGƒéL“2eÎÊDN‰AS*0ˆÜú‘ArŠgVùxE¦Låˆ%{ù`¥×Ѷ0óTÏ3´NK\þËäΧ+`¯Vóó/>Ô8:CEæ`o÷2(ØçôÉLY£v´ØNá{ŲEçcÚü_ñù!æù_ŸåÈvŸ#îÇe¡¸ìrÆe\¦+iÅ ¬såÎûóÌÉï0EÎd¢«‘l3°Vÿì Nƒ}‡É½“¨bÀá]õŽÌY²õî?ŒûÚÞIAŒ#áàãMWc­aìMžª>”·-Öq,VsP¯Ôˆwü|‹|ü ³¾dI‚# Ï4?éҥݾm3Ç k>Å1ÖM€ôf]c•ógOéyxÝ?Ä<¿‡%K¶@†éßP–Ìð:É3ö`ÕkÔÂè%nòqTÕ5•­¥q“¢q*oÉámz&a‘&s¡%«×¨yœU‹ôoÜ*ÌŸ~(o[v8ËÛ'Û‹&>…À{B ÖM€4ËÙ{ÀwÞ·àç™¶lØÕŒ!î?Ä<Ç=%IQœ>†ÙGøq(­çÁ½'Žzx~W0ëî>ðÂå˜ÂwíÛÄó¯pP2Ì—U}ݹGxX6iëDqû6;a?”·-—c|öE[7>xp/vHH¬B  ü›&@±X,‰Z! „€B@Ä/¼ Püª)B@! „€ø0ü {ئÆ.Ûñ þn,[0môω¼î}‡(žÇzLçbƆ·=Ö…2çcýÔF—7ªãoÿ"v9sV’u<&ÿœ5[vk÷8vùW²Q `‘qgN½({Žœq\Þ8HîŒqÏ4‰÷päÿ@IJ¶…€ˆ=±¸€“w¿øª~&WWîLõö>¾nÕ/¶ŸqùN Ì™eD]ãÙ±Ç]˜¹óäO”8Ñ;·öíÚÆ½B1ŽíM~7z"›%÷ëñð/Ã~ƒFr²ï÷#šßû¦Ñ~þ9±TéòS&~o[[üWý—9ÛÂ'üä/X„óX¬ا«µc¸ÔkÜ|õÊÅ'¿Ù&Ý~œ5göß+§=ÆAžmOâšï•—A¦£lcüyW]/Æ€B@!ð6bkP±RµÆ-Úp¾Ï™“\ÌÙ¤Eë>‡9˜Ëß&»Æa9ÞÁÜWÞûö©k欇áÚí5«–r¿Uæ,ÙÙ=fË€œ¨wG›Š±‡û÷îpÝãÆÈ; ¸a*MºôÊuƒÆ¡Þ“§ïCl—(V9@b›³^üÏûtíÐ’§2º 3±gç6\pÃOó‹‡õ¾[w¥²¸ÌèÎíÛï6æ÷'6Z;ïO~b/'lùåúmå²HR‘ž{¨%f! >,±2à|ÏMÝšüaº–»{gsA‘ÇŒò•ªîÛ½®ãþ¶ÿ°\¹òùùùÎ5…»Ê-¨qnãf­9¾ôð_·ëáûÒëÔoÊÕ˜Üë´sû¦Ý‘WwqœÜÐQã·lZÏ¢ö­›×Ïžþû\QÖaçý<mFG/^¸oQ#ófOSSÄÔ¤TérÏ2ftå­¼jùÂKL×ߨÛ;4mцû#Â"<=÷oX»Š© €;€P©r‡ëˆ!}¬¦KŸÑÂu<¶OÃÞ†3ç ¶ùºKþ‚…M·;y,^²Ì˜á,8ìÙ¹­C§,æ(4¸‡hýÚ•\‚pî\ËñQ®ræ¾råâœYS6nQ²LùGΙ9™ûñO»:x`wÁBE’&ó½rqÙ’ùˆÅÝÝ\<vçöÍJ„ÜÛµå÷µÜŽ§Òæ †U8ç4iýŸ>Y²`6WwYcTü³ÀÅÍSÅJ”b¢»lÉ<¥^xÄ5Ã4uúˆ÷‰#Jºz?“k–vºfpq½zé=:8$hõŠ%Ä ÙÌÌ3©|ç@°ˆ“và/¹?Ì\YðQÎ<_wêéäœöÔÉ£‹çÿ¤´yë¶g­ÂŸó" )fïà°ïNeÄÀu@‹ÊÊ’%{Š©ûþüE m»º›»µÏ_ PÈ«W{wnݾu#‘0Ê¡(]¦Bò)MW°MÇOÎìÚ³oXhئ «Økž뺠»µíˆ‰7þþÛjî2ÌۗFs7kæ,nã}⣖P”¨wn‰¾zå’BÌ”)WQY^Óˆ昩”(]¾ÏpôÌÿi©²hðsfMâf½/ë6à(ë•¿,äz&[4GG§&-Úr *·žö>¾`î Œ<õºù§M¦M›~Öô‰=ݪ#óÝ;·;ÃN›¯»Ò&HÈ¥~Ç ç¶JMJ˜›2n(š&ìT¿¬Óð=¼fÁ¼Êw! „€J V&¼òS¤Hyüˆ—š ¯"ï“G ,ªÈ eËW™9eOŸâG“•fp¼`£æ­`ÈÍ…Õk|ѹ[Ÿ1#ƒyX'_Â|É›»ç·ƒ÷ëΔ¬q37æuúÛÛÙ÷ê7”òîØöûê•KÌM€4ZøÑŒÇÈøyÎ,v!÷sÿ&Eò”îý†¾zµÌeÎ!GÎÜz9)S¶âô)ãî߻۫ïÐ!ÃÇq.!"x½†Í4i9còJ(pM;œöÖ½gÿ/¾ª‡¼èyp‡2Èäš9mºt§#[‚ú)T´Ó*ŒïÕ‹ˆÎ‰"×Ç41â^¨ÐÇ‹Ìæ¯DÉ24†A}ºQ/¸SãF}ý:¢Ç·•t5?§ªwéÑ—;h·oÙ˜;wÞ^}†îÛkšê43ó¬Fû½d™ “çÿ<ƒúvÛ¦ß80[ñ€ Q4…„=qüHîÜùÔ€Û·˜^u¯óhQÒ ýãröôI%ES*žA @ñ¸sû廓,²Á;€ÛsüL2±ÅùC àÐW¯ÐÍçΗ_õ°{ÇVÊe‘ƒœ[F­õÛóðþ2å+!ݽsKõ‚X¶\%~"è.RüÈaK“'²…韺¸©KV?%K—»qÝ÷؇)>÷Ï_8çC ÊSmVZ5R¹Úg(D¹Ý†HPµ::;¥K—A‰D¯FnÞ¸®h—Y™¡,…ŠÃ©²×­YñòÅ ÞÖ¬T”)_Ù<«Êw[Úu̪KŒ9—,UnÓ†_1ÍBó½o÷ó$ô8˜û¸ßë¬Òœ8æø,àØO´à8fËšCõ¶g×VZ‰óŽÍ¥ËVÀýÌéÎiÓºdÊÌ÷²å*Ÿ8êea-S±rõ;¶\¹|.à •rRµÆ»wïМˆŸÔp•z!f´ÝÏŸqÏåæ ë”t5æ¬ÙS¤L‰ôO ̨ÏúœTrnÐÌÌ Dûq€Ù)9ñ>q4[äˆaÐöÌcÛµ}?Ÿ?ÏR¥MÜ jV“«¬Ùr¬^¹”ÎNýîÞiZâ#ðâXeDBIlP ëºpÍ’ÝÙ9-û”¨kêèˆ×AæðJ 1kô¦ çÿ¤viYëÈ•Ç|PÚ‚;çΞQè©Y5ΆŘiP@[º…Ú¶Y•Z¿f9HÑ_  ƒ‚È4Ôù®Y²²²Á04–³ òã#b<á‹2«#s´'<<œ‹\2eBÛà I·2 dœy*„€xo ÄÊ @àó@ÖǹS-9«·ÊOÓ¸Ì'äUˆƒƒ½G'çÇï+Žˆ°?R¾gÿ(ûq3fÌ„žÈ÷nSÜy µ® 1ÿ'êRáㇳåˆR…Ĭ=¤J娏y+Vó%N’$qbó¼½x5ð†¾ µsþ»Q‘=ƒlXz…µ±ë™·mìúÔA›¦ŽÍ$‘hL°²}xÍ3¬ŽÌÑv6}Ñþ;wïcgoïyhßú5+ (é1w! „À{N V&hzP–(]öÀÞ]Jù“$±cáK}[p`¯Œ=´ê3uªÔÊ÷ÎÝzo\¿ÊËó rFëv¼6-ìF~Lf9oúA¡˜;o”’Œ^gçXȘ"}ýúü9Ÿ’eʸÇ:.Œ¦ü8–ÉÉgµê´ëØmøàÞª» çþœ=ÝÃ:”µ‹^`/ÎbŽjZ­ĜڠFÒ¦O¯ÆÏrÁÉãGÒçu#"ãÓ'¦U‹UM†æ~ôâ±.ŽžKÌ8³ÄÇ9ME`Rt™%ñwËÄWâ$vjÒØÈéeCÓ=]ºô×}¯ð(mú T‡âÇëàþNÝ{ŸÿÓ'"<üÊå‹© —-51âÇ)M:Õ'û_©å'F튡sÚtJºš­sêÔŽÌ ”94ØÃ@ oÔÌ,²jüS³íYaØA,VÚUºô 7Í€–ÒêðÁî×–3Ä™âüç ¡Þ"¤u]<õ’ÚÑ[ve&]^ãR«O­ÛCænt ùX‰b“&@¶Dãl¼Û®§dÕx@£"œœUU‹Ä [Y¼ÌGf㎃*‡I52fêÕg¯³û÷ïiV ,æZJN˜Ú\ü! Þ±bĺnͲFM[•*SånF—L{ô~ñòm)6»x¹â™³D‘°•PDuë– Úw[¢2ðƒXO*yóDj©Q³vòäÃ÷úÕ+ -Þ¤y›ô\Ðraˆ©.q’T†Hÿ|ÿ4rK®ñG3çØ%Ï]¼š­{Æa•§+Y€µ1– ž1†®ñEílÙ?:ùÏÝŸxCIùIÉ2ü§:VÂÃ#Ìâíf3„Â;5wž|HÞz9Ñ«‘ý»wÔ¬U7GŽ\ÌL %Ë0•Ò‹DqÏÁ¥B¥ª0'ét2œõñÆí¬¨°ªãèì\ë«úG"ozöìY"{J@M†~4ã‰mÎdEûWu›€q¹JõÏ4 <|x…W×,B@÷Š@‚Üù ëeËÑšŸùða”q¶¦7{»—AÁ>§OfÊj¹—WÓÿûéˆÚrÒô¹¾n¾Å6¶³ÊuÈ(™vn3mR|›róŠe‹0Þ&’¸ Ëñ–Ç«4n’SR‰cÎÕ?«•+wÞŸgN~‡eä$GŽ Òœ3(ÆïÛáƒ]zôCÎ6—€Íi 29vò¨ïúÛ¨ˆIådOîˆYð÷9”A{xŸ³mcÞâf@{?;ŽˆÄ›ÿewü|‹|ü ЉdI‚Ctëéҥݾm3ú;=V±µ —Þ{åÎÙ|¨m8£VíúϞݿ§÷þplÎÛÓàhm,Οýûöƒ·3žÅœ‘5Ù/Èz¦bÕkÔZ¿vEÜ0ÄØ£jšö팛äŒSÉ•;߃û÷Pùs_AÁÂE]¾PÏ?–9ùª÷TÜÿËâf@{¯:ιº¥ìB@ü‹þÓºîØ©³(ŽÈüiÆ›üþÅZ±1éÞ¾ã”Ï?ÏüàrncßoÑræ£ÎÝ{st GÖzÜ{âh”~¬æŸƒð»»¼pÁÇëðØŽÇ^–¸Á—­>ì»e¯ð‚9Ó4-Öb/u‰9ˆ¶£½“2¾oçJ"B@¼)1zSbâ_! „€B@ü Þ• P¬œô/ð$…€B@! „€°€Ll€ôÖ^ )6täø·Ž&æ°Ë¹S·ÞSf.ìÒ£OÌc±9$6ñÓ~Zlí»NÝ#ÏQ}·''çÙó—+q¶þºË_Öç;Ž9Hê$ô¦Ù~Sÿï$“ɇK€M½Y³e›ü«$n’{‡©èõèØ.‘^ü:÷ä€wX@‰J!—bkÇŸ5jÞ:[ö\«ÎÑ:¿­YÁ5õ±T°v»ß»s›øb)þ8ˆKŽS¥JÅ•¥çÎù,™?û¦[¨ÐÇœÔÙ»GnV~‡ÑÆ}Tœ ß´E»}ºê%}ÔóÀË —zOcæ~Í÷ÊË Óq¥òy'¾=‘½+DÅŽœˆº|é<áÕ‹™ 8¦Ë×-ô<ØèF KªxãpÒ©³ÑSl¸ÿ×å‹”»X\æT.÷åŽÏ½»·sc¨E*5Íâï‹Ï3¹fI›6ý¬é9öÄÚ?7ãr›LBEM—CyFsNK¶9¹×†s$IÈ\Ü$!®pÊñQ®ræ¾råâœYS6nQ²LùGΙ9™«1™\uèÔcp¿J14Z¿v%w˜—ÈÏïZÁ‚E9žå‡gmÚ°úÊåK\Eæ_G¼>í}|á¼™æ¥ãfb²íš9KxX˜÷‰c+—-¤‚ð Ô”’: éß“S´kÕ©ï䜆kÚ–.üùáƒûÖñ¿øªw0mÛºawäÝ^êG/ÛÜû[§~ãÒå*ÙÛÙy{_õË‚ë˜ \X¯xïžùr«+­ÛuvLí8kÆD‡¤Ö5e°Ný¦\›Ë%t;·oR²ÍÊL™rÍŸ9‡©Ç5žv^¸H1{‡ý{wZ“I–Wܸœ0Q¢kW//]‰žÌrIDAT4GQÇj6x²‘5{Žæ-ÛgÊœ…6óë²E¥ož7å`{n˜ª[¿1óêUË?yò¨UÛoR;:qïw~áY¯‹qºb£¦n©R;ÂsóƵÊ}vÖ•¨W/ѶC’6îwUñšYsøË—/Ü[µZM&ÒÆ…yØž¹ÕÏÜñe†Üoеgº¥éâ8mÒ8Ú¼•Ò ]\\!@TsgMyòä1O ,Ò¨Yk*ñÎm¿eKæß¼qÍz”P#Ñ«#ãâPƒ[6­¯Vã ÆºƒûwÑÂ;|Ó3ûG¹.]ü“ŽÉH¥G’tiN_|UŸÛÙ7–,˜ÍmV8~”3Ï×z:9§=uò¨2šÓ앚#¤y¨h¿×øü+ÌI“rò/‹ærÑxÞÏÿ飞œ;lä„­›ãZ7ë×"òÑ«DGG§&-Úr)/cÃÔ‚¹3H‘¾Ì%keËW |æ¿dÁOŠh®vyeÐc••çD‰-[ ¢Ùø£­Añ „€ˆ1X™ —0ζíÐõÐÁ½×}/¿xþ<ÚüåÌç‡ÈUÎ~ƒG”*[ž áÍíììú»wÍ’57²ó´K¾7®]íß»KÊ)Ýû ½ÿ٪ ¦5¬Ò?ªtkÝüÝ»·Çò,0 \ùʺ; wìˆ9"“'Œâ5ƒ,Òoð(‹TNƧ› ¨!¥©wf[&Ò3“a,CØ2ÍJ´¨º·ÃhûfÌhÊ”­äë{Ù¢ñ˜{ž5m&@zF_œä;vä`.hÔ¬UMÍ›•&MÚ®½úÏ™=ùÏ3§+Uù´WŸACúõ°%Ìã׬#[ŠS¼d™ñßC">ós®XV:i¥*5vlû]³¼8r÷HÓmfNõð½zq?QÂDŠÏ’e*LòÍ$ýE €™´î•š#¤^ºÖî%J•«ñyíÉF?~ü€rÛŽ]OŽzþ´f-eÑ%SúŒ.§O'¬õÀ«1$Æ‘[W"•BŠ·nú é߃ ÚÑ€(ù/T¸(³#¦¾”õР¾Ý,ÊU @áñßG;/RôÅ·ÎÕáC{·oÙ˜;wÞ^}†îÛû÷ ÍJpFݼù ÒÓóæÍ.#O¾‚tä&±Ê “kæ´éÒö>¡Ù>ßTÍa]›â"„€xS±² 8,,lüè!¼•[¸µ›4}º dq㜡:E>ãïÜÙ3Ù²™.ÆjˆƒÕ]2eB°¼qÝ7,,”14k¶«W.åQ&»wnC!m-¡ìì’dÉ’ Ém«¢4÷sìÃO™ zD(wqɬ<½q㪲à’É5ÚTräCHÿ|Ñó2iãº_ÙÉÆ¾]ÛŒ‹`ïŽUËcà>pØ÷Sç –Sý£å Xâ9qÌ‹Yͱ#žàÅ1[ÖÆqª%²®€œ>Cgç4ÄsÝ÷ŠE$lØ@m†ÿ§TJ®<ùU»wl¥ˆ­rµÏX~ñ»~ ŒÈ ŽÎNˆÂñðÞ]¿fþ¹cáð}¥JW0έò´RµëV/§‚˜•m\¿ºDIËúµ%Õ0”pAAAsgO¦D¶´ŸóžAÇÌ aúÄñ#¹sç³NQå <ÚµÝt—3ÚS*Å¢˜$êyh?ë`´ØëÍϦeƒ/Yºíœö Õ+—. Ü.RÜ"u,Î6¬]ÁšÌïááûöî¤IÐÀ.^8g=—3‹º¡éM95Â#[*Ñ<ãvm¿³(’¹‹W³X¾bÕ5«~±†l£ ¹bœÁóÙÓ'™³ñ¥x‰Ò—/œƒð÷îÞô2¨@¡"±iÖ‘-ÅÙ¶eÃóÀg |&·j'eÇ ¹Š•«ïܱåÊå ´1Â>xpOñ¼mÓoLöˆÐûÄQe4D³WZéZ?bQkÏÎ-ÄL_[½b)K.4SÞG]\³¤KŸÿ¥ÊTð>q„¦kKÇÁÌ€кÑÙ»fÉÊò#—aJY$Á§Ÿßu¯Ãû©²CûwóN!ÃåbYYòõññvNcò@¿`½éŸnu …¼ÏIk—.ü™7OÜså-ÀÂNÞÈ~Ê€neíÙÜåˆçA F¹Ãö*ÍÏüé™Ó'œÓ¦uÉdzÅ”-WùÄQ/|¾iû …€q6ä©B be€| y¯Xº€/¬ä¶iß…Õ€i¿/•§¡¯Bíœø~hÿÂvîÞ1ÎóоõkV¦I“¥õȱ“Ÿˆøwným 8Þ¹}‹åÝúš³àësúº"E>P“Æ zÍZŽŽÎÈOX žWaÿ£|±%|ø›¤>šþQmbôèÑÅÏÇQ_”ŸÖÿ‘ªy òÇw±b%¿éæ~ëæu^W¦„"å>Ì¢¾¿ ±Oj¢dðQKdágÕŠEõ¶ÀvåY@ÀÖM¿‘¨¹‡T©7o…:Qâ$I'6·íñˆÒFóVË•+oÙ UÔ€©UQFqDHE&V¾#è,ò±AV•GvövhÁÛwênÚS°›8Ib&1¾æŒ“U’$±1¤·ƒfMYä +vcg̘‰ä‘öjÌÜT„¥˜È(J$ØXHá¦½Ý šû¤”CÒd´ ¬YðoÝàhÐtŽþaª6E7n\³È¶ª²6øW„††`OoáÙüçŒ)j×mˆšüÆ5_™H-š•H&õ"1n‡Ñö;‹h1µ“ÓÄ)sÀ®—srxáÜŸ³§{èy°Å]¯‹¡òãXîÏjÕi×±ÛðÁ½5+‘56½æmêýN/,}œ}&Í[åÎ[à±çóÌ'O‘"*”N3Ö‹3àé“‹ªO™Vùû›d>‹QBõ WG1(Žy–4[8À®è×õò¯é®Ù+­GHÍ©…f„8ø?Ufiô ÿH{3Ö66v;ïsó6eŠ-G/Õ]ˆf@RtrvVgËš~ltä”:µ#ÓŠ*W«‰-5ÙºyÃ7wžüheøþégÑœ Ìö/D¦ÏQ¥ ïÁ6ãÞÝÛšµAÙ‰b˜øÙ³KTì-VŠ#FälìCˆçÏâô!´òèJïÝ»‹gŒqÓgp±†¥—еOÅEÏÿ‰£žÕk|ŽÛ˜«ÁÙÄöMWË3OªÕ¨U´X –#ðÌÊ;{þ®]³4ÎÑÌÀÇ÷ áêš…§XEkj…7v¶±¥—R)ÿk3o}v!#‹'±³«X¥ºf¢ûwï¨Y«.'½R; %BªÃÂ'“4xa•û •ª¢ 3÷ —íý{w4mÙõž‘˜©#%Ôȱ“«TûL33ÆŽìvݽsKŸA#õjÊ¢ø·nù!+ІÑÜGÎSŠY¿Q3$Dg6—[˜¬ÃÀŸŸ~ZË8¶ãG<Ù.Lí³ë‘sçÉgKmZÄ©ÙÅhTÔÿ™b…G.°hV¢^½眧šýŽDœ>W3lˆd‰”Œ±?C¼Ý¼q½@a“ìnê2U£ºLàóg &Vö-hFeáxòÄQl¸1'~FždÉ“Ÿ;ëƒóQÂ<ˆfiÇ–ÔU?z$ìÛÍ‘_9så¥ïP¿lì¶%ZÍ^i=BÚ•êA¿JõÏ1ô‡v£fnØ«(ÚtöN 1×oÜüØQOEh¶¥ãD›´Í€¤xû¦Cƒ¾ éÍG¬†xƒÔ¬U‡G< G )a±Âð’ÿ¸_¼¨|·´ÿyöìUF÷´%]ÅÃø«W!ÍÜڱЪÌôÚ§fã'’ÌY²-VÒöŧBÀFo0–Ù#Þ‚‚ƒXÃíÕgpjGG4+è?Ö¬4™ùbÊyòø‘ïF{ jâ° ãÓ¤MÏq4)S9†a÷yêäqÐYÓ&6nÖjÌ„ivvö÷ïÞùmý¯DÂVcÎZáÐkŸ3'wmÛÜ¢Uût\ÂÂ^¡ÎYµl‘y*(þ=îÿn”†ìÜÒ<}\/½ÜêùÿuÅ– Ø øüE §X ß+1@æü9“DbþáP‹Ï¿¬‹‰ÔÂyìe´¶Î×Ì’ú²Eóºöê‡=Gªcˆ¬éÍÜ1OÞüÍ[µà³ÿEsg«‹æŠŸ ëV¶ïØCÕ  —ì+¤u )zÁ_—/nÕ¾3³»à—//\8KµZ¤ÄlmüäÙœ´å÷uÊþ Õ^¶±ûÿ²N6a§L• ,}9BŠÙ‰ïÕKÑMÓ»°ëkZ¡Ù~ÌC±•³cç^h Yñùëô*ÍhGŠÉ„aüäYsóÆ5Å<|`èœþuÄ©S' âáË“<Æ4iÞºeë¯Ãoø^]¶t¾që§š]Œ£j«T¯Iob çîÝ[ÁI@ÍJÔ«ë„,\˜Õ[÷;&rW.i×4ùcbÿèáý% bóîÙ¹µC—ž#ÆN¢ebUÏ6eÉÒÖMë¾ãàÅiè·oùgm.Ç=5nêÖ¡K¯»·oNûqœr™ù(1oö45Í:Ò,ŽqºOõHrBç5q Ëb´ðÅ f[ØÎi¦¢Ù+­GHͰzŽL8© ÷¾C9ëâ…³‹æE]<‚F€“j*UýtÃ:ÓèÊGoˆÓ‹YÓ]ˆ¦gRœ=}b3·öã~œEëå„4u€¦Gæ0,*¶éØõËÚ ¯^:Ï^ù°P“ žÅ‡}#eËWV]/^8ÏiZÖ °Ïùp»ÚxÁ ìÝÅ2 ß^‡4hÒböô•çzíS³ñ„ iÜÀnèÅY!C rç3½e5?˜)×üüˇi>Uìí^ûœ>™)«iž|Œ Ë¢Ìõ]¶Öû”§*LùëÖo6}Êïå\Ξۼo{OòÃyS›6®ŽÖú=É­dã¿@ K~L}÷ï±<(öÊ®Rr mì%!1 !ðŸ"pÇÏ·ÈÇŸ (I–Ô!8òKÍOºti·oÛœ<¹®½}¬˜ifE!€ÅȰî"ý¿Qc`Yã=”þߨÿMÏœà)Òÿ³êß«RcÊ&²T°PÑ‚…‹ž=ÍZÜ;Ì<†”UkÔ<°oç;ŒS¢B@¼±bôNr&‘! „€xK\ѹGo6¤qóÚ‚9ÓbvÞT ò€ [w÷.øxŽæ"ÈD.A„€oI@&o P‚ÿw `½-ö?ÿÝê—’ ¸ò…¿¸Ï,{½º}Ó2îÓ•…€¶ [(‰! „€B@!OÈ žT¤C! „€B@ØB@&¶P?B@! „€B ž @<©H)†B@! „€°…€Ll¡$~„€B@! „@þäm2!a…€B@! „€°…²7¸->õü¼í ,,. """ import logging; logger = logging.getLogger(__name__); logger.info("import") #Standard Library import datetime #Third Party #Our Modules from engine.start import PATHS from engine.config import METADATA #includes METADATA only. No other environmental setup is executed. from engine.jackclient import AgordejoJackClient from .nsmservercontrol import NsmServerControl from .watcher import Watcher from .findprograms import programDatabase class Callbacks(object): """GUI methods register themselves here. These methods get called by us, the engine. None of these methods produce any return value. The lists may be unordered. We need the lists for audio feedbacks in parallel to GUI updates. Or whatever parallel representations we run.""" def __init__(self): global jackClient self.message = [] #Session Management self.sessionOpenReady = [] self.sessionOpenLoading = [] self.sessionClosed = [] self.sessionsChanged = [] #update in the file structure. redraw list of sessions. self.sessionLocked = [] # incremental update. Sends the name of the session project and a bool if locked self.sessionFileChanged = [] #incremental update. Reports the session name, not the session file self.clientStatusChanged = [] #every status including GUI and dirty self.singleInstanceActivateWindow = [] #this is for the single-instance feature. Show the GUI window and activate it when this signal comes. self.dataClientNamesChanged = [] self.dataClientDescriptionChanged = [] self.dataClientTimelineMaximumDurationChanged = [] #in minutes. this is purely a GUI construct. the jackClient knows no limit!. #JackClient Callbacks. For the GUI they are mirrored here. These are mutable, shared lists. #The callback functions are in jackClient directly and api functions can call them. self.setPlaybackSeconds = jackClient.callback_setPlaybackSeconds def _dataClientNamesChanged(self, data): """If there is a dataclient in the session it will allow us to read and write metadata. The GUI instructs us to send a write instruction over OSC, we wait for the OSC answer and forward that to the GUI. If the dataClient joins the session it will trigger an unrequested callback (from the GUIs perspectvive). If the client leaves the callback will send a single None. This is a sign for the GUI to reset all data to the nsmd state. We do not mix nsmd data and dataClient. A dataclient can join and leave at every time, we keep the GUI informed. """ for func in self.dataClientNamesChanged: func(data) def _dataClientDescriptionChanged(self, data): """see _dataClientNamesChanged. In short: str for data, None if nsm-data leaves session""" for func in self.dataClientDescriptionChanged: func(data) def _dataClientTimelineMaximumDurationChanged(self, minutes:int): """ This callback is still used, even if nsm-data is not in the session. It will then purely be a roundtrip from gui widget -> api -> gui-callback without saving anything. For compatibility reasons it will still send a "None" when nsm-data leaves the session. The GUI can then just continue with the current value. It just means that the values will not be saved in the session. """ for func in self.dataClientTimelineMaximumDurationChanged: func(minutes) def _singleInstanceActivateWindow(self): for func in self.singleInstanceActivateWindow: func() def _sessionOpenReady(self, nsmSessionExportDict): """A project got opened, most likely by ourselves, but also by another party. This will also fire if we start and detect that a session is already open and running. """ for func in self.sessionOpenReady: func(nsmSessionExportDict) def _sessionOpenLoading(self, nsmSessionExportDict): """ A session begins loading. Show a spinning clock or so... """ for func in self.sessionOpenLoading: func(nsmSessionExportDict) def _sessionClosed(self): """The current session got closed. Present a list of sessions to choose from again. This is also send at GUI start if there is no session open presently.""" for func in self.sessionClosed: func() def _sessionsChanged(self): """The project list changed. This can happen through internal changes like deletion or duplication but also through external changes through a filemanager. Always sends a full update of everything, with no indication of what changed.""" listOfProjectDicts = nsmServerControl.exportSessionsAsDicts() for func in self.sessionsChanged: func(listOfProjectDicts) return listOfProjectDicts def _sessionLocked(self, name:str, status:bool): """Called by the Watcher through the event loop Name is "nsmSessionName" from _nsmServerControl.exportSessionsAsDicts Sends True if project is locked""" for func in self.sessionLocked: func(name, status) def _sessionFileChanged(self, name:str, timestamp:str): """ This happens everytime the session gets saved. Called by the Watcher through the event loop. Name is "nsmSessionName" from _nsmServerControl.exportSessionsAsDicts. timestamp has same format as nsmServerControl.exportSessionsAsDicts""" for func in self.sessionFileChanged: func(name, timestamp) def _clientStatusChanged(self, clientInfoDict:dict): """A single function for all client changes. Adding and deletion is also included. GUI hide/show and dirty is also here. A GUI needs to check if it already knows the clientId or not.""" for func in self.clientStatusChanged: func(clientInfoDict) def startEngine(): logger.info("Start Engine") global eventLoop assert eventLoop global nsmServerControl nsmServerControl = NsmServerControl( sessionOpenReadyHook=callbacks._sessionOpenReady, sessionOpenLoadingHook=callbacks._sessionOpenLoading, sessionClosedHook=callbacks._sessionClosed, clientStatusHook=callbacks._clientStatusChanged, singleInstanceActivateWindowHook=callbacks._singleInstanceActivateWindow, dataClientNamesHook=callbacks._dataClientNamesChanged, dataClientDescriptionHook=callbacks._dataClientDescriptionChanged, dataClientTimelineMaximumDurationChangedHook=callbacks._dataClientTimelineMaximumDurationChanged, parameterNsmOSCUrl=PATHS["url"], sessionRoot=PATHS["sessionRoot"], startupSession=PATHS["startupSession"], ) #Watch session tree for changes. global sessionWatcher sessionWatcher = Watcher(nsmServerControl) sessionWatcher.timeStampHook = callbacks._sessionFileChanged sessionWatcher.lockFileHook = callbacks._sessionLocked sessionWatcher.sessionsChangedHook = callbacks._sessionsChanged #This is the main callback that informs of new or updated sessions callbacks.sessionClosed.append(sessionWatcher.resume) #Watcher only active in "Choose a session mode" callbacks.sessionOpenReady.append(sessionWatcher.suspend) eventLoop.slowConnect(sessionWatcher.process) #Start Event Loop Processing eventLoop.fastConnect(nsmServerControl.process) eventLoop.fastConnect(jackClient._setPlaybackSeconds) eventLoop.slowConnect(nsmServerControl.listenToAnotherInstanceAttempt) #Send initial data #The decision if we are already in a session on startup or in "choose a session mode" is handled by callbacks #This is not to actually gather the data, but only to inform the GUI. logger.info("Send initial cached data to GUI.") callbacks._sessionsChanged() #send session list c = currentSession() #sessionName if c: callbacks._sessionOpenReady(nsmServerControl.sessionAsDict(c)) #Send client list. This is only necessary when attaching to an URL or using NSM-URL Env var #When we do --load-session we receive client updates live. #But this little redundancy doesn't hurt, we just sent them. Better safe than sorry. for clientId, clientDict in nsmServerControl.internalState["clients"].items(): callbacks._clientStatusChanged(clientDict) else: callbacks._sessionClosed() #explicit is better than implicit. Otherwise a GUI might start in the wrong state #nsmServerControl blocks until it has a connection to nsmd. That means at this point we are ready to send commands. #Until we return from startEngine a GUI will also not create its mainwindow. logger.info("Engine start complete") #Info def ourOwnServer(): """Report if we started nsmd on our own. If not we will not kill it when we quit""" return nsmServerControl.ourOwnServer def sessionRoot(): return nsmServerControl.sessionRoot def currentSession(): return nsmServerControl.internalState["currentSession"] def sessionList()->list: """Updates the list each call. Use only this from a GUI for active query. Otherwise sessionRemove and sessionCopy will not have updated the list""" r = nsmServerControl.exportSessionsAsDicts() return [s["nsmSessionName"] for s in r] def requestSessionList(): """For the rare occasions where that is needed""" callbacks._sessionsChanged() #send session list def buildSystemPrograms(progressHook=None): """Build a list of dicts with the .desktop files (or similar) of all NSM compatible programs present on the system""" programDatabase.build(progressHook) def systemProgramsSetWhitelist(executableNames:tuple): """will replace the current list""" programDatabase.userWhitelist = tuple(executableNames) #Needs rebuild through the GUI. We have no callback for this. def systemProgramsSetBlacklist(executableNames:tuple): """will replace the current list""" programDatabase.userBlacklist = tuple(executableNames) #Needs rebuild through the GUI. We have no callback for this. def getNsmClients()->list: """Returns a, probably cached, list of dicts that represent all nsm capable clients on this system. Including the list of clients the user added themselves""" return programDatabase.getNsmClients() def getNsmExecutables()->set: """Cached access fort fast membership tests. Is this program in the PATH? This is just to check if an executable is available on this system. The content of the set are executable names, the same as ["agordejoExec"] from getNsmClients elements """ return programDatabase.nsmExecutables def getUnfilteredExecutables()->list: """Return a list of unique names without paths or directories of all exectuables in users $PATH. This is intended for a program starter prompt. GUI needs to supply tab completition or search itself""" return programDatabase.unfilteredExecutables #Session Control #No project running #There is no callback for _sessionsChanged because we poll that in the event loop. def sessionNewTimestamped(): """convenience function. Create a new session without requiring a name and add suggested infrastructure clients""" nsmExecutables = getNsmExecutables() #type set, cached, very fast. connectionSaver = METADATA["preferredClients"]["data"] dataMeta = METADATA["preferredClients"]["connections"] startclients = [] if connectionSaver in nsmExecutables: startclients.append(connectionSaver) if dataMeta in nsmExecutables: startclients.append(dataMeta) #now = datetime.datetime.now().replace(second=0, microsecond=0).isoformat()[:-3] now = datetime.datetime.now().replace(microsecond=0).isoformat() name = now sessionNew(name, startclients) def sessionNew(newName:str, startClients:list=[]): nsmServerControl.new(newName, startClients) def sessionRename(nsmSessionName:str, newName:str): """only for non-open sessions""" nsmServerControl.renameSession(nsmSessionName, newName) def sessionCopy(nsmSessionName:str, newName:str, progressHook=None): """Create a copy of the session. Removes the lockfile, if any. Has some safeguards inside so it will not crash. If progressHook is provided (e.g. by a GUI) it will be called at regular intervals to inform of the copy process, or at least that it is still running. """ nsmServerControl.copySession(nsmSessionName, newName, progressHook) def sessionOpen(nsmSessionName:str): """Saves the current session and loads a different existing session.""" nsmServerControl.open(nsmSessionName) def sessionQuery(nsmSessionName:str): """For the occasional out-of-order information query. Exports a single session project in the format of nsmServerControl.exportSessionsAsDicts""" return nsmServerControl.sessionAsDict(nsmSessionName) def sessionDelete(nsmSessionName:str): nsmServerControl.deleteSession(nsmSessionName) #While Project is open def sessionSave(): """Saves the current session.""" nsmServerControl.save() def sessionClose(blocking=False): """Saves and closes the current session.""" nsmServerControl.close(blocking) def sessionAbort(blocking=False): """Close without saving the current session.""" nsmServerControl.abort(blocking) def sessionSaveAs(nsmSessionName:str): """Duplicate in NSM terms. Make a copy, close the current one, open the new one. However, it will NOT send the session clossed signal, just a session changed one.""" nsmServerControl.duplicate(nsmSessionName) def setDescription(text:str): nsmServerControl.setDescription(text) def setTimelineMaximumDuration(minutes:int): nsmServerControl.setTimelineMaximumDuration(int(minutes)) #Client Handling def clientAdd(executableName): nsmServerControl.clientAdd(executableName) #status hook triggers clientStatusChanged callback def clientStop(clientId:str): nsmServerControl.clientStop(clientId) def clientResume(clientId:str): """Opposite of clientStop""" nsmServerControl.clientResume(clientId) def clientRemove(clientId:str): """Client must be already stopped! We will do that without further question. Remove from the session. Will not delete the save-files, but make them inaccesible""" nsmServerControl.clientRemove(clientId) def clientSave(clientId:str): """Saves only the given client""" nsmServerControl.clientSave(clientId) def clientToggleVisible(clientId:str): """Works only if client announced itself with this feature""" nsmServerControl.clientToggleVisible(clientId) def clientHideAll(): nsmServerControl.allClientsHide() def clientShowAll(): nsmServerControl.allClientsShow() def clientNameOverride(clientId:str, name:str): """An agordejo-specific function that requires the client nsm-data in the session. If nsm-data is not present this function will write nothing, not touch any data. It will still send a callback to revert any GUI changes back to the original name. We accept empty string as a name to remove the name override """ nsmServerControl.clientNameOverride(clientId, name) def executableInSession(executable:str)->dict: """Returns None if no client with this executable is in the session, else returns a dict with its export-data. If multiple clients with this exe are in the session only one is returned, whatever Python thinks is good""" for clientId, dic in nsmServerControl.internalState["clients"].items(): if executable == dic["executable"]: return dic else: return None #Global Datastructures, set in startEngine nsmServerControl = None eventLoop = None sessionWatcher = None jackClient = AgordejoJackClient() #Create before callbacks callbacks = Callbacks() #This needs to be defined before startEngine() so a GUI can register its callbacks. The UI will then startEngine and wait to reveice the initial round of callbacks ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1665611335.9505882 agordejo-0.4.2/engine/comparedirectories.py0000644000175000017500000000356514321633110017550 0ustar00nilsnils#! /usr/bin/env python3 # -*- coding: utf-8 -*- """ Copyright 2022, Nils Hilbricht, Germany ( https://www.hilbricht.net ) The Non-Session-Manager by Jonathan Moore Liles : http://non.tuxfamily.org/nsm/ New Session Manager, by LinuxAudio.org: https://github.com/linuxaudio/new-session-manager With help from code fragments from https://github.com/attwad/python-osc ( DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE v2 ) This file is part of the Laborejo Software Suite ( https://www.laborejo.org ). This application is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . """ #https://stackoverflow.com/questions/24937495/how-can-i-calculate-a-hash-for-a-filesystem-directory-using-python import hashlib from _hashlib import HASH as Hash from pathlib import Path from typing import Union def md5_update_from_dir(directory, hash): assert Path(directory).is_dir() for path in sorted(Path(directory).iterdir(), key=lambda p: str(p).lower()): hash.update(path.name.encode()) if path.is_file(): with open(path, "rb") as f: for chunk in iter(lambda: f.read(4096), b""): hash.update(chunk) elif path.is_dir(): hash = md5_update_from_dir(path, hash) return hash def md5_dir(directory): return md5_update_from_dir(directory, hashlib.md5()).hexdigest() ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1665611335.967255 agordejo-0.4.2/engine/config.py0000644000175000017500000000537114321633110015127 0ustar00nilsnils#! /usr/bin/env python3 # -*- coding: utf-8 -*- #Do not change these during runtime! METADATA={ #The pretty name of this program. Used for NSM display and Jack client name #Can contain everything a linux file/path supports. Never change this or it will break the #session, making your file unable to load and destroying saved Jack connections. "name" : "Agordejo", #Set this to the name the user types into a terminal. #MUST be the same as the binary name as well as the name in configure. #Program reports that as proc title so you can killall it by name. #Should not contain spaces or special characters. We use this as save file extension as well #to distinguish between compatible program versions. In basic programs this will just be e.g. #patroneo. But in complex programs with a bright future it will be "laborejo1" "laborejo2" etc. "shortName" : "agordejo", #A very short description used in various places: Desktop file, overview on the website, #release announcements, entries in software directories etc. "tagline" : 'Music and audio production session manager based on NSM.', "version" : "0.4.2", "year" : "2022", "author" : "Laborejo Software Suite", "url" : "https://www.laborejo.org/agordejo", #English is automatic. "supportedLanguages" : {"German":"de.qm", "Italian":"it.qm", }, #Show the About Dialog the first time the program starts up. This is the initial state for a #new instance in NSM, not the saved state! Decide on how annoying it would be for every new #instance to show about. Fluajho does not show it because you add it many times into a session. #Patroneo does because its only added once. "showAboutDialogFirstStart" : False, "preferredClients" : {"data":"nsm-data", "connections":"jackpatch", "proxy":"nsm-proxy"}, #Various strings for the README #Extra whitespace will be stripped so we don't need to worry about docstring indentation "description" : """ Agordejo (Esperanto: 'place to set things up') is a music production session manager. It is used to start your programs, remember their (JACK) interconnections and make your life easier in general. """ + "\n" + """ You can create a session, or project, add programs to it and then use commands to save, start/stop, hide/show all programs at once, or individually. At a later date you can then re-open the session and continue where you left off. """ + "\n" + """ Agordejo does not re-invent the wheel but instead uses the New-Session-Manager daemon and enhances it with some tricks of its own, that always remain 100% compatible with the original sessions. """, #this is the dict-comma. "dependencies" : "\n".join("* "+dep for dep in ("nsmd: New Session Manager", "pyxdg: python-xdg")), } ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1665611335.9505882 agordejo-0.4.2/engine/findprograms.py0000644000175000017500000002350714321633110016356 0ustar00nilsnils#! /usr/bin/env python3 # -*- coding: utf-8 -*- """ Copyright 2022, Nils Hilbricht, Germany ( https://www.hilbricht.net ) This file is part of the Laborejo Software Suite ( https://www.laborejo.org ), This is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . """ import logging; logger = logging.getLogger(__name__); logger.info("import") import pathlib import configparser import os import stat import xdg.DesktopEntry #pyxdg https://www.freedesktop.org/wiki/Software/pyxdg/ import xdg.IconTheme #pyxdg https://www.freedesktop.org/wiki/Software/pyxdg/ from engine.start import PATHS def nothing(*args): pass class SupportedProgramsDatabase(object): """Find all binaries with NSM support. Resources are: * Agordejo internal program list of known working programs. * Internal blacklist of known redundant programs (such as non-daw) or nonsense entries, like Agordejo itself * Finally, local to a users system: User whitelist for any program, user blacklist. Those two have the highest priority. """ def __init__(self): self.progressHook = nothing #prevents the initial programstart from sending meaningless messages for the cached data. Set and reverted in self.build self.blackList = set(("nsmd", "non-daw", "carla", "agordejo", "adljack", "agordejo.bin", "non-midi-mapper", "non-mixer-noui")) #only programs that have to do with audio and music. There is another general blacklist that speeds up discovery self.whiteList = set(("thisdoesnotexisttest", "patroneo", "vico", "tembro", "laborejo", "fluajho", "carla-rack", "carla-patchbay", "carla-jack-multi", "carla-jack-single", "ardour5", "ardour6", "nsm-data", "jackpatch", "nsm-proxy", "ADLplug", "ams", "drumkv1_jack", "synthv1_jack", "samplv1_jack", "padthv1_jack", "luppp", "non-mixer", "non-timeline", "non-sequencer", "OPNplug", "qmidiarp", "qtractor", "zynaddsubfx", "jack_mixer", "hydrogen", "mfp", "shuriken", "guitarix", "radium", "ray-proxy", "ray-jackpatch", "amsynth", "mamba", "qseq66", "synthpod", "tap192", )) #shortcut list and programs not found by buildCache_grepExecutablePaths because they are just shellscripts and do not contain /nsm/server/announce. self.userWhitelist = () #added dynamically by api.systemProgramsSetWhitelist add to morePrograms. highest priority self.userBlacklist = () #added dynamically by api.systemProgramsSetBlacklist as blacklist. highest priority self.programs = [] #main data structure of this file. list of dicts. guaranteed keys: agordejoExec, name, agordejoFullPath. And probably others, like description and version. self.nsmExecutables = set() #set of executables for fast membership, if a GUI wants to know if they are available. Needs to be build "manually" with self.programs. no auto-property for a list. at least we don't want to do the work. self.unfilteredExecutables = None #in build() #self.build() needs to be called when the program is ready, e.g. a GUI is set up and has the progressHook ready def _isexe(self, path): """executable by owner""" return path.is_file() and stat.S_IXUSR & os.stat(path)[stat.ST_MODE] == 64 def _executableNameToFullPath(self, exeName:str, executableSystemPaths:set)->pathlib.Path: for directory in executableSystemPaths: p = pathlib.Path(directory, exeName) if p.exists(): return p else: return None def gatherAllNsmClients(self)->list: """ We parse .desktop files for the nsm flag and export our own list of dicts with various entries. see below. """ executableSystemPaths = set([pathlib.Path(p).resolve() for p in os.environ["PATH"].split(os.pathsep)]) #resolve filters out symlinks, like ArchLinux's /sbin and /bin. set() makes it unique """Go through all dirs including subdirs""" xdgPaths = ( pathlib.Path("/usr/share/applications"), pathlib.Path("/usr/local/share/applications"), pathlib.Path(pathlib.Path.home(), ".local/share/applications"), ) self.progressHook("") result = [] for basePath in xdgPaths: try: #TODO: this whole part of the program is a mess. Confiparser and Qt in a bundle segfault too often. self.progressHook(f"{basePath}") except Exception as e: logger.error(e) pass for f in basePath.glob('**/*'): if f.is_file() and f.suffix == ".desktop": try: #the xdg lib will still raise errors when encountering a malformed .desktop file desktopEntry = xdg.DesktopEntry.DesktopEntry(f) except Exception as e: logger.error(f"Desktop file {f} has problems: {e}") continue """ #Don't validate. This is over-zealous and will mark deprecation and unknown categories. try: desktopEntry.validate() except xdg.Exceptions.ValidationError as e: logger.error(f"Desktop file {f} has problems: {e}") continue """ agorExec = desktopEntry.get("X-NSM-Exec") #If there is a specific executable to start from nsm, use this. If not we use the normal executable below. if not agorExec: n = pathlib.Path(desktopEntry.getExec()).name agorExec = n.split(" ")[0].strip() # this will fail with special filenames, such as spaces in filenames. But it is already the fallback for programs not adhering to the nsm specs. not our problem anymore. blacklisted = agorExec in self.blackList or agorExec in self.userBlacklist if blacklisted: logger.info(f"{agorExec} is blacklisted. Skip.") continue isNSM = ( bool(desktopEntry.get("X-NSM-Capable")) or bool(desktopEntry.get("X-NSM-capable")) or agorExec in self.whiteList or agorExec in self.userWhitelist ) if isNSM: absExecPath = self._executableNameToFullPath(agorExec, executableSystemPaths) if absExecPath is None: logger.warning(f"Couldn't find actual path for {agorExec} eventhough we searched with the name from it's desktop file. If this program is not installed at all this is a false-negative error. Don't worry.") continue if not self._isexe(absExecPath): logger.error(f"{absExecPath} was derived from .desktop file and exist, but it not executable!") continue data = { "agordejoName" : desktopEntry.getName(), "agordejoExec" : agorExec , #to prevent 'carla-rack %u'. This is what nsm will call. "agordejoIconPath" : xdg.IconTheme.getIconPath(desktopEntry.getIcon()), "agordejoFullPath" : absExecPath, #This is only for information. nsm calls agordejoExec "agordejoDescription" : desktopEntry.getComment(), } result.append(data) self.progressHook("") return result def build(self, progressHook=None): """Can be called at any time by the user to update after installing new programs""" if progressHook: #receives one string which indicates what files is currently parsed. #Just the pure path, this will not get translated! #The purpose is to show a "we are not frozen!" feedback to the user. #It doesn't really matter what is reported back as long as it changes often self.progressHook = progressHook logger.info("Building launcher database. This might take a minute") self.progressHook("") self.programs = self.gatherAllNsmClients() self.progressHook("") self.unfilteredExecutables = self.buildCache_unfilteredExecutables() self.nsmExecutables = set(d["agordejoExec"] for d in self.programs) self.progressHook("") self.progressHook = nothing logger.info("Building launcher database done.") def getNsmClients(self)->list: """Return the main data structure of this file: a list of dicts """ return self.programs def buildCache_unfilteredExecutables(self): """Just a list of all exectuables of this systems PATH. This is used for the GUIs "start any program" with auto completion. """ result = [] executablePaths = [pathlib.Path(p) for p in os.environ["PATH"].split(os.pathsep)] for path in executablePaths: self.progressHook(f"{path}") result += [str(pathlib.Path(f).relative_to(path)) for f in path.glob("*") if self._isexe(f)] return sorted(list(set(result))) programDatabase = SupportedProgramsDatabase() ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1665611335.9505882 agordejo-0.4.2/engine/jackclient.py0000644000175000017500000001015614321633110015766 0ustar00nilsnils#! /usr/bin/env python3 # -*- coding: utf-8 -*- """ Copyright 2022, Nils Hilbricht, Germany ( https://www.hilbricht.net ) This file is part of the Laborejo Software Suite ( https://www.laborejo.org ), This is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . """ import logging; logger = logging.getLogger(__name__); logger.info("import") #Standard Library import atexit import sys #Third Party import engine.jacklib as jacklib from ctypes import pointer #Our Modules from engine.config import METADATA #includes METADATA only. No other environmental setup is executed. from engine.jacklib.helpers import get_jack_status_error_string class AgordejoJackClient(object): """Singleton. Created in api.startEngine. If client cannot be started the program will exit from here. Most error sources are already ruled out in start.py.""" def __init__(self): status = jacklib.jack_status_t() self._jacklibClient = jacklib.client_open(METADATA["name"], jacklib.JackNoStartServer, status) err = get_jack_status_error_string(status) if not status.value == 0: #Decide if a name collision is important or not. Agordejo cannot be started two times anyway. So this would be another client called Agordejo? if status.value & jacklib.JackNameNotUnique: logger.warning(f"Another JACK client called {METADATA['name']} exist. We will rename ourselve, but this is most likely a real problem. Agordejo is not supposed to be started twice. Please investigate!") else: logger.error("JackClient error: " + status.value) sys.exit(0) #atexit will trigger atexit.register(lambda c=self._jacklibClient: jacklib.client_close(c)) #Callbacks. They are mirrored by the api Callbacks without the callback_ prefix so a GUI can directly access them. #However, they are mutable lists. And we define all actual callback-sender here. the api calls them via our Object/Instance self.callback_setPlaybackSeconds = [] def _setPlaybackSeconds(self): """Added to the fast event loop. Therefore called VERY often. Yes, this is not a super accurate function because while we iterate transport already progresses""" pos = jacklib.jack_position_t() #pos._fields_ state = jacklib.transport_query(self._jacklibClient, pointer(pos)) #this actually sets the pos and info. We need this, even if we don't use "state" here. #if not pos.frame_rate: # return currenTimeInSeconds = round(pos.frame / pos.frame_rate, 8) for func in self.callback_setPlaybackSeconds: func(currenTimeInSeconds, not state == jacklib.JackTransportStopped) #current time in seconds and if playback is running #Public API. Called via api.jackClient.rewind() def _seek(self, frame:int): jacklib.transport_locate(self._jacklibClient, frame) def seek(self, seconds): pos = jacklib.jack_position_t() state = jacklib.transport_query(self._jacklibClient, pointer(pos)) #this actually sets the pos and info. We need this, even if we don't use "state" here. self._seek(int(seconds * pos.frame_rate)) def rewind(self): self._seek(0) def playPause(self): pos = jacklib.jack_position_t() #pos._fields_ state = jacklib.transport_query(self._jacklibClient, pointer(pos)) if state == jacklib.JackTransportStopped: jacklib.transport_start(self._jacklibClient) elif state == jacklib.JackTransportStarting: pass else: jacklib.transport_stop(self._jacklibClient) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1665611335.947255 agordejo-0.4.2/engine/jacklib/COPYING0000644000175000017500000003556414321633110015751 0ustar00nilsnils GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, 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 software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, 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 redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's 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 give any other recipients of the Program a copy of this License along with the Program. 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 Program or any portion of it, thus forming a work based on the Program, 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) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, 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 Program, 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 Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) 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; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, 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 executable. 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. If distribution of executable or 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 counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program 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. 5. 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 Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program 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. 7. 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 Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program 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 Program. 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. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program 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. 9. The Free Software Foundation may publish revised and/or new versions of the 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 Program 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 Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, 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 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "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 PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. 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 PROGRAM 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 PROGRAM (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 PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1665611335.947255 agordejo-0.4.2/engine/jacklib/README.md0000644000175000017500000000356014321633110016164 0ustar00nilsnils# pyjacklib Python bindings for `libjack` using [ctypes], which allow you to write JACK client applications in Python. The library provides a low-level, almost unaltered mapping of the `libjack` [C API], plus a few additional convenience functions. The source code repository contains a few [example scripts] to show its usage. **Note:** **pyjacklib** *as a stand-alone project is in an early beta-stage and the API may still change slightly before a 1.0 release. You have been warned!* ## Dependencies To use the library, your system needs to have the following installed at run-time: * The [JACK] library * A Python 3 implementation, which supports `ctypes` To build and install the library you need: * [setuptools] * (optional) [pip] ## Building and Installing You can download and install **pyjacklib** directly from PyPI using `pip`: ```con pip install pyjacklib ``` Or you can download the latest source archive or clone the git repository and run the following from inside the unpacked source directory resp. the root of your checkout: ```con python setup.py install ``` You can also build a wheel with: ```con pip wheel . ``` ... and install it using `pip install`. ## License **pyjacklib** is licensed under the GNU Public License Version v2, or any later version. Please see the file [COPYING] for more information. ## Authors Created by *Filipe Coelho (falkTX)* as part of [Cadence]. Turned into a stand-alone project and enhanced by *Christopher Arndt*. [C API]: https://jackaudio.org/api/ [Cadence]: https://github.com/falkTX/Cadence.git [COPYING]: https://github.com/jackaudio/pyjacklib/blob/master/COPYING [ctypes]: https://docs.python.org/3/library/ctypes.html [example scripts]: https://github.com/jackaudio/pyjacklib/tree/master/examples [JACK]: https://jackaudio.org/ [pip]: https://pypi.org/project/pip/ [setuptools]: https://pypi.org/project/setuptools/ ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1665611335.9505882 agordejo-0.4.2/engine/jacklib/__init__.py0000644000175000017500000000012214321633110017005 0ustar00nilsnilsfrom .version import __version__ # noqa from .api import * # noqa ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1665611335.9505882 agordejo-0.4.2/engine/jacklib/api.py0000644000175000017500000016220314321633110016030 0ustar00nilsnils#!/usr/bin/env python3 # -*- coding: utf-8 -*- # JACK ctypes definitions for usage in Python applications # Copyright (C) 2010-2020 Filipe Coelho # 2016-2021 Christopher Arndt # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # any later version. # # This program 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 General Public License for more details. # # For a full copy of the GNU General Public License see the COPYING file # ------------------------------------------------------------------------------------------------- # Imports (Global) from __future__ import absolute_import, print_function, unicode_literals from ctypes import (ARRAY, CFUNCTYPE, POINTER, Structure, byref, c_char_p, c_double, c_float, c_int, c_int32, c_size_t, c_uint8, c_uint32, c_uint64, c_ulong, c_void_p, cdll, pointer) from collections import namedtuple from sys import platform # ------------------------------------------------------------------------------------------------- # Load JACK shared library try: if platform == "darwin": jlib = cdll.LoadLibrary("libjack.dylib") elif platform in ("win32", "win64", "cygwin"): jlib = cdll.LoadLibrary("libjack.dll") else: jlib = cdll.LoadLibrary("libjack.so.0") except OSError: jlib = None raise ImportError("JACK is not available in this system") # ------------------------------------------------------------------------------------------------- # JACK2 test try: if jlib.jack_get_version_string: JACK2 = True else: JACK2 = False except AttributeError: JACK2 = False # ------------------------------------------------------------------------------------------------- # Pre-Types c_enum = c_int c_uchar = c_uint8 class _jack_port(Structure): _fields_ = [] class _jack_client(Structure): _fields_ = [] # ------------------------------------------------------------------------------------------------- # Defines ENCODING = "utf-8" JACK_MAX_FRAMES = 4294967295 JACK_LOAD_INIT_LIMIT = 1024 JACK_DEFAULT_AUDIO_TYPE = "32 bit float mono audio" JACK_DEFAULT_MIDI_TYPE = "8 bit raw midi" JACK_UUID_SIZE = 36 JACK_UUID_STRING_SIZE = JACK_UUID_SIZE + 1 JACK_UUID_EMPTY_INITIALIZER = 0 # Meta data _JACK_METADATA_PREFIX = "http://jackaudio.org/metadata/" JACK_METADATA_CONNECTED = _JACK_METADATA_PREFIX + "connected" JACK_METADATA_EVENT_TYPES = _JACK_METADATA_PREFIX + "event-types" JACK_METADATA_HARDWARE = _JACK_METADATA_PREFIX + "hardware" JACK_METADATA_ICON_LARGE = _JACK_METADATA_PREFIX + "icon-large" JACK_METADATA_ICON_NAME = _JACK_METADATA_PREFIX + "icon-name" JACK_METADATA_ICON_SMALL = _JACK_METADATA_PREFIX + "icon-small" JACK_METADATA_ORDER = _JACK_METADATA_PREFIX + "order" JACK_METADATA_PORT_GROUP = _JACK_METADATA_PREFIX + "port-group" JACK_METADATA_PRETTY_NAME = _JACK_METADATA_PREFIX + "pretty-name" JACK_METADATA_SIGNAL_TYPE = _JACK_METADATA_PREFIX + "signal-type" # ------------------------------------------------------------------------------------------------- # Helper functions def _e(s, encoding=ENCODING): if encoding: return s.encode(encoding) return s def _d(s, encoding=ENCODING): if encoding: return s.decode(encoding) return s # ------------------------------------------------------------------------------------------------- # Types jack_client_t = _jack_client jack_default_audio_sample_t = c_float jack_midi_data_t = c_uchar jack_nframes_t = c_uint32 jack_port_id_t = c_uint32 jack_port_t = _jack_port jack_time_t = c_uint64 jack_unique_t = c_uint64 jack_uuid_t = c_uint64 jack_options_t = c_enum # JackOptions jack_status_t = c_enum # JackStatus jack_transport_state_t = c_enum # JackTransportState jack_position_bits_t = c_enum # JackPositionBits jack_session_event_type_t = c_enum # JackSessionEventType jack_session_flags_t = c_enum # JackSessionFlags jack_custom_change_t = c_enum # JackCustomChange jack_latency_callback_mode_t = c_enum # JackLatencyCallbackMode jack_property_change_t = c_enum # JacKPropertyChange # JACK2 only: jack_port_type_id_t = c_uint32 # enum JackOptions JackNullOption = 0x00 JackNoStartServer = 0x01 JackUseExactName = 0x02 JackServerName = 0x04 JackLoadName = 0x08 JackLoadInit = 0x10 JackSessionID = 0x20 JackOpenOptions = JackSessionID | JackServerName | JackNoStartServer | JackUseExactName JackLoadOptions = JackLoadInit | JackLoadName | JackUseExactName # enum JackStatus JackFailure = 0x01 JackInvalidOption = 0x02 JackNameNotUnique = 0x04 JackServerStarted = 0x08 JackServerFailed = 0x10 JackServerError = 0x20 JackNoSuchClient = 0x40 JackLoadFailure = 0x80 JackInitFailure = 0x100 JackShmFailure = 0x200 JackVersionError = 0x400 JackBackendError = 0x800 JackClientZombie = 0x1000 # enum JackLatencyCallbackMode JackCaptureLatency = 0 JackPlaybackLatency = 1 # enum JackPortFlags JackPortIsInput = 0x1 JackPortIsOutput = 0x2 JackPortIsPhysical = 0x4 JackPortCanMonitor = 0x8 JackPortIsTerminal = 0x10 JackPortIsControlVoltage = 0x100 # enum JackTransportState JackTransportStopped = 0 JackTransportRolling = 1 JackTransportLooping = 2 JackTransportStarting = 3 # JACK2 only: JackTransportNetStarting = 4 # enum JackPositionBits JackPositionBBT = 0x10 JackPositionTimecode = 0x20 JackBBTFrameOffset = 0x40 JackAudioVideoRatio = 0x80 JackVideoFrameOffset = 0x100 JACK_POSITION_MASK = (JackPositionBBT | JackPositionTimecode | JackBBTFrameOffset | JackAudioVideoRatio | JackVideoFrameOffset) # enum JackSessionEventType JackSessionSave = 1 JackSessionSaveAndQuit = 2 JackSessionSaveTemplate = 3 # enum JackSessionFlags JackSessionSaveError = 0x01 JackSessionNeedTerminal = 0x02 # enum JackCustomChange JackCustomRemoved = 0 JackCustomAdded = 1 JackCustomReplaced = 2 # enum JackPropertyChange PropertyCreated = 0 PropertyChanged = 1 PropertyDeleted = 2 # ------------------------------------------------------------------------------------------------- # Structs class jack_midi_event_t(Structure): _fields_ = [ ("time", jack_nframes_t), ("size", c_size_t), ("buffer", POINTER(jack_midi_data_t)) ] class jack_latency_range_t(Structure): _fields_ = [ ("min", jack_nframes_t), ("max", jack_nframes_t) ] class jack_position_t(Structure): _fields_ = [ ("unique_1", jack_unique_t), ("usecs", jack_time_t), ("frame_rate", jack_nframes_t), ("frame", jack_nframes_t), ("valid", jack_position_bits_t), ("bar", c_int32), ("beat", c_int32), ("tick", c_int32), ("bar_start_tick", c_double), ("beats_per_bar", c_float), ("beat_type", c_float), ("ticks_per_beat", c_double), ("beats_per_minute", c_double), ("frame_time", c_double), ("next_time", c_double), ("bbt_offset", jack_nframes_t), ("audio_frames_per_video_frame", c_float), ("video_offset", jack_nframes_t), ("padding", ARRAY(c_int32, 7)), ("unique_2", jack_unique_t) ] class jack_session_event_t(Structure): _fields_ = [ ("type", jack_session_event_type_t), ("session_dir", c_char_p), ("client_uuid", c_char_p), ("command_line", c_char_p), ("flags", jack_session_flags_t), ("future", c_uint32) ] class jack_session_command_t(Structure): _fields_ = [ ("uuid", c_char_p), ("client_name", c_char_p), ("command", c_char_p), ("flags", jack_session_flags_t) ] class jack_property_t(Structure): _fields_ = [ ('key', c_char_p), ('data', c_char_p), ('type', c_char_p) ] class jack_description_t(Structure): _fields_ = [ ('subject', jack_uuid_t), ('property_cnt', c_uint32), ('properties', POINTER(jack_property_t)), ('property_size', c_uint32) ] # ------------------------------------------------------------------------------------------------- # Callbacks JackLatencyCallback = CFUNCTYPE(None, jack_latency_callback_mode_t, c_void_p) JackProcessCallback = CFUNCTYPE(c_int, jack_nframes_t, c_void_p) JackThreadCallback = CFUNCTYPE(c_void_p, c_void_p) JackThreadInitCallback = CFUNCTYPE(None, c_void_p) JackGraphOrderCallback = CFUNCTYPE(c_int, c_void_p) JackXRunCallback = CFUNCTYPE(c_int, c_void_p) JackBufferSizeCallback = CFUNCTYPE(c_int, jack_nframes_t, c_void_p) JackSampleRateCallback = CFUNCTYPE(c_int, jack_nframes_t, c_void_p) JackPortRegistrationCallback = CFUNCTYPE(None, jack_port_id_t, c_int, c_void_p) JackClientRegistrationCallback = CFUNCTYPE(None, c_char_p, c_int, c_void_p) # JACK2 only: JackClientRenameCallback = CFUNCTYPE(c_int, c_char_p, c_char_p, c_void_p) JackPortConnectCallback = CFUNCTYPE(None, jack_port_id_t, jack_port_id_t, c_int, c_void_p) # JACK2 only: JackPortRenameCallback = CFUNCTYPE(None, jack_port_id_t, c_char_p, c_char_p, c_void_p) JackFreewheelCallback = CFUNCTYPE(None, c_int, c_void_p) JackShutdownCallback = CFUNCTYPE(None, c_void_p) JackInfoShutdownCallback = CFUNCTYPE(None, jack_status_t, c_char_p, c_void_p) JackSyncCallback = CFUNCTYPE(c_int, jack_transport_state_t, POINTER(jack_position_t), c_void_p) JackTimebaseCallback = CFUNCTYPE(None, jack_transport_state_t, jack_nframes_t, POINTER(jack_position_t), c_int, c_void_p) JackSessionCallback = CFUNCTYPE(None, POINTER(jack_session_event_t), c_void_p) JackCustomDataAppearanceCallback = CFUNCTYPE(None, c_char_p, c_char_p, jack_custom_change_t, c_void_p) JackPropertyChangeCallback = CFUNCTYPE(None, jack_uuid_t, c_char_p, jack_property_change_t, c_void_p) JackErrorCallback = CFUNCTYPE(None, c_char_p) # ------------------------------------------------------------------------------------------------- # Functions try: jlib.jack_get_version_string.argtypes = None jlib.jack_get_version_string.restype = c_char_p except AttributeError: jlib.jack_get_version_string = None try: jlib.jack_client_open.argtypes = [c_char_p, jack_options_t, POINTER(jack_status_t), c_char_p] jlib.jack_client_open.restype = POINTER(jack_client_t) except AttributeError: jlib.jack_client_open = None try: jlib.jack_client_rename.argtypes = [POINTER(jack_client_t), c_char_p] jlib.jack_client_rename.restype = c_char_p except AttributeError: jlib.jack_client_rename = None try: jlib.jack_client_close.argtypes = [POINTER(jack_client_t)] jlib.jack_client_close.restype = c_int except AttributeError: jlib.jack_client_close = None try: jlib.jack_client_name_size.argtypes = None jlib.jack_client_name_size.restype = c_int except AttributeError: jlib.jack_client_name_size = None try: jlib.jack_get_client_name.argtypes = [POINTER(jack_client_t)] jlib.jack_get_client_name.restype = c_char_p except AttributeError: jlib.jack_get_client_name = None try: jlib.jack_activate.argtypes = [POINTER(jack_client_t)] jlib.jack_activate.restype = c_int except AttributeError: jlib.jack_activate = None try: jlib.jack_deactivate.argtypes = [POINTER(jack_client_t)] jlib.jack_deactivate.restype = c_int except AttributeError: jlib.jack_deactivate = None try: jlib.jack_get_client_pid.argtypes = [c_char_p] jlib.jack_get_client_pid.restype = c_int except AttributeError: jlib.jack_get_client_pid = None try: jlib.jack_is_realtime.argtypes = [POINTER(jack_client_t)] jlib.jack_is_realtime.restype = c_int except AttributeError: jlib.jack_is_realtime = None # JACK2 only: def get_version_string(): if jlib.jack_get_version_string: return jlib.jack_get_version_string() return None def client_open(client_name, options, status, uuid=""): if jlib.jack_client_open: return jlib.jack_client_open(_e(client_name), options, status, _e(uuid) if uuid else None) return None def client_rename(client, new_name): if jlib.jack_client_rename: return jlib.jack_client_rename(client, _e(new_name)) return None def client_close(client): if jlib.jack_client_close: return jlib.jack_client_close(client) return -1 def client_name_size(): if jlib.jack_client_name_size: return jlib.jack_client_name_size() return 0 def get_client_name(client): if jlib.jack_get_client_name: return jlib.jack_get_client_name(client) return None def activate(client): if jlib.jack_activate: return jlib.jack_activate(client) return -1 def deactivate(client): if jlib.jack_deactivate: return jlib.jack_deactivate(client) return -1 # JACK2 only: def get_client_pid(name): if jlib.jack_get_client_pid: return jlib.jack_get_client_pid(_e(name)) return 0 def is_realtime(client): if jlib.jack_is_realtime: return jlib.jack_is_realtime(client) return 0 # ------------------------------------------------------------------------------------------------- # Non-Callback API _thread_callback = None try: jlib.jack_cycle_wait.argtypes = [POINTER(jack_client_t)] jlib.jack_cycle_wait.restype = jack_nframes_t except AttributeError: jlib.jack_cycle_wait = None try: jlib.jack_cycle_signal.argtypes = [POINTER(jack_client_t), c_int] jlib.jack_cycle_signal.restype = None except AttributeError: jlib.jack_cycle_signal = None try: jlib.jack_set_process_thread.argtypes = [POINTER(jack_client_t), JackThreadCallback, c_void_p] jlib.jack_set_process_thread.restype = c_int except AttributeError: jlib.jack_set_process_thread = None def cycle_wait(client): if jlib.jack_cycle_wait: return jlib.jack_cycle_wait(client) return 0 def cycle_signal(client, status): if jlib.jack_cycle_signal: jlib.jack_cycle_signal(client, status) def set_process_thread(client, thread_callback, arg): if jlib.jack_set_process_thread: global _thread_callback _thread_callback = JackThreadCallback(thread_callback) return jlib.jack_set_process_thread(client, _thread_callback, arg) return -1 # ------------------------------------------------------------------------------------------------- # Client Callbacks _thread_init_callback = _shutdown_callback = _info_shutdown_callback = None _process_callback = _freewheel_callback = _bufsize_callback = _srate_callback = None _client_registration_callback = _client_rename_callback = None _port_registration_callback = _port_connect_callback = _port_rename_callback = None _graph_callback = _xrun_callback = _latency_callback = None _property_change_callback = None try: jlib.jack_set_thread_init_callback.argtypes = [ POINTER(jack_client_t), JackThreadInitCallback, c_void_p ] jlib.jack_set_thread_init_callback.restype = c_int except AttributeError: jlib.jack_set_thread_init_callback = None try: jlib.jack_on_shutdown.argtypes = [POINTER(jack_client_t), JackShutdownCallback, c_void_p] jlib.jack_on_shutdown.restype = None except AttributeError: jlib.jack_on_shutdown = None try: jlib.jack_on_info_shutdown.argtypes = [ POINTER(jack_client_t), JackInfoShutdownCallback, c_void_p ] jlib.jack_on_info_shutdown.restype = None except AttributeError: jlib.jack_on_info_shutdown = None try: jlib.jack_set_process_callback.argtypes = [ POINTER(jack_client_t), JackProcessCallback, c_void_p ] jlib.jack_set_process_callback.restype = c_int except AttributeError: jlib.jack_set_process_callback = None try: jlib.jack_set_freewheel_callback.argtypes = [ POINTER(jack_client_t), JackFreewheelCallback, c_void_p ] jlib.jack_set_freewheel_callback.restype = c_int except AttributeError: jlib.jack_set_freewheel_callback = None try: jlib.jack_set_buffer_size_callback.argtypes = [ POINTER(jack_client_t), JackBufferSizeCallback, c_void_p ] jlib.jack_set_buffer_size_callback.restype = c_int except AttributeError: jlib.jack_set_buffer_size_callback = None try: jlib.jack_set_sample_rate_callback.argtypes = [ POINTER(jack_client_t), JackSampleRateCallback, c_void_p ] jlib.jack_set_sample_rate_callback.restype = c_int except AttributeError: jlib.jack_set_sample_rate_callback = None try: jlib.jack_set_client_registration_callback.argtypes = [ POINTER(jack_client_t), JackClientRegistrationCallback, c_void_p ] jlib.jack_set_client_registration_callback.restype = c_int except AttributeError: jlib.jack_set_client_registration_callback = None try: jlib.jack_set_client_rename_callback.argtypes = [ POINTER(jack_client_t), JackClientRenameCallback, c_void_p ] jlib.jack_set_client_rename_callback.restype = c_int except AttributeError: jlib.jack_set_client_rename_callback = None try: jlib.jack_set_port_registration_callback.argtypes = [ POINTER(jack_client_t), JackPortRegistrationCallback, c_void_p ] jlib.jack_set_port_registration_callback.restype = c_int except AttributeError: jlib.jack_set_port_registration_callback = None try: jlib.jack_set_port_connect_callback.argtypes = [ POINTER(jack_client_t), JackPortConnectCallback, c_void_p ] jlib.jack_set_port_connect_callback.restype = c_int except AttributeError: jlib.jack_set_port_connect_callback = None try: jlib.jack_set_port_rename_callback.argtypes = [ POINTER(jack_client_t), JackPortRenameCallback, c_void_p ] jlib.jack_set_port_rename_callback.restype = c_int except AttributeError: jlib.jack_set_port_rename_callback = None try: jlib.jack_set_graph_order_callback.argtypes = [ POINTER(jack_client_t), JackGraphOrderCallback, c_void_p ] jlib.jack_set_graph_order_callback.restype = c_int except AttributeError: jlib.jack_set_graph_order_callback = None try: jlib.jack_set_xrun_callback.argtypes = [POINTER(jack_client_t), JackXRunCallback, c_void_p] jlib.jack_set_xrun_callback.restype = c_int except AttributeError: jlib.jack_set_xrun_callback = None try: jlib.jack_set_latency_callback.argtypes = [ POINTER(jack_client_t), JackLatencyCallback, c_void_p ] jlib.jack_set_latency_callback.restype = c_int except AttributeError: jlib.jack_set_latency_callback = None def set_thread_init_callback(client, thread_init_callback, arg): if jlib.jack_set_thread_init_callback: global _thread_init_callback _thread_init_callback = JackThreadInitCallback(thread_init_callback) return jlib.jack_set_thread_init_callback(client, _thread_init_callback, arg) return -1 def on_shutdown(client, shutdown_callback, arg): if jlib.jack_on_shutdown: global _shutdown_callback _shutdown_callback = JackShutdownCallback(shutdown_callback) jlib.jack_on_shutdown(client, _shutdown_callback, arg) def on_info_shutdown(client, info_shutdown_callback, arg): if jlib.jack_on_info_shutdown: global _info_shutdown_callback _info_shutdown_callback = JackInfoShutdownCallback(info_shutdown_callback) jlib.jack_on_info_shutdown(client, _info_shutdown_callback, arg) def set_process_callback(client, process_callback, arg): if jlib.jack_set_process_callback: global _process_callback _process_callback = JackProcessCallback(process_callback) return jlib.jack_set_process_callback(client, _process_callback, arg) return -1 def set_freewheel_callback(client, freewheel_callback, arg): if jlib.jack_set_freewheel_callback: global _freewheel_callback _freewheel_callback = JackFreewheelCallback(freewheel_callback) return jlib.jack_set_freewheel_callback(client, _freewheel_callback, arg) return -1 def set_buffer_size_callback(client, bufsize_callback, arg): if jlib.jack_set_buffer_size_callback: global _bufsize_callback _bufsize_callback = JackBufferSizeCallback(bufsize_callback) return jlib.jack_set_buffer_size_callback(client, _bufsize_callback, arg) return -1 def set_sample_rate_callback(client, srate_callback, arg): if jlib.jack_set_sample_rate_callback: global _srate_callback _srate_callback = JackSampleRateCallback(srate_callback) return jlib.jack_set_sample_rate_callback(client, _srate_callback, arg) return -1 def set_client_registration_callback(client, client_registration_callback, arg): if jlib.jack_set_client_registration_callback: global _client_registration_callback _client_registration_callback = JackClientRegistrationCallback(client_registration_callback) return jlib.jack_set_client_registration_callback(client, _client_registration_callback, arg) return -1 # JACK2 only: def set_client_rename_callback(client, client_rename_callback, arg): if jlib.jack_set_client_rename_callback: global _client_rename_callback _client_rename_callback = JackClientRenameCallback(client_rename_callback) return jlib.jack_set_client_rename_callback(client, _client_rename_callback, arg) return -1 def set_port_registration_callback(client, port_registration_callback, arg): if jlib.jack_set_port_registration_callback: global _port_registration_callback _port_registration_callback = JackPortRegistrationCallback(port_registration_callback) return jlib.jack_set_port_registration_callback(client, _port_registration_callback, arg) return -1 def set_port_connect_callback(client, connect_callback, arg): if jlib.jack_set_port_connect_callback: global _port_connect_callback _port_connect_callback = JackPortConnectCallback(connect_callback) return jlib.jack_set_port_connect_callback(client, _port_connect_callback, arg) return -1 # JACK2 only: def set_port_rename_callback(client, rename_callback, arg): if jlib.jack_set_port_rename_callback: global _port_rename_callback _port_rename_callback = JackPortRenameCallback(rename_callback) return jlib.jack_set_port_rename_callback(client, _port_rename_callback, arg) return -1 def set_graph_order_callback(client, graph_callback, arg): if jlib.jack_set_graph_order_callback: global _graph_callback _graph_callback = JackGraphOrderCallback(graph_callback) return jlib.jack_set_graph_order_callback(client, _graph_callback, arg) return -1 def set_xrun_callback(client, xrun_callback, arg): if jlib.jack_set_xrun_callback: global _xrun_callback _xrun_callback = JackXRunCallback(xrun_callback) return jlib.jack_set_xrun_callback(client, _xrun_callback, arg) return -1 def set_latency_callback(client, latency_callback, arg): if jlib.jack_set_latency_callback: global _latency_callback _latency_callback = JackLatencyCallback(latency_callback) return jlib.jack_set_latency_callback(client, _latency_callback, arg) return -1 # ------------------------------------------------------------------------------------------------- # Server Control jlib.jack_set_freewheel.argtypes = [POINTER(jack_client_t), c_int] jlib.jack_set_freewheel.restype = c_int jlib.jack_set_buffer_size.argtypes = [POINTER(jack_client_t), jack_nframes_t] jlib.jack_set_buffer_size.restype = c_int jlib.jack_get_sample_rate.argtypes = [POINTER(jack_client_t)] jlib.jack_get_sample_rate.restype = jack_nframes_t jlib.jack_get_buffer_size.argtypes = [POINTER(jack_client_t)] jlib.jack_get_buffer_size.restype = jack_nframes_t jlib.jack_engine_takeover_timebase.argtypes = [POINTER(jack_client_t)] jlib.jack_engine_takeover_timebase.restype = c_int jlib.jack_cpu_load.argtypes = [POINTER(jack_client_t)] jlib.jack_cpu_load.restype = c_float def set_freewheel(client, onoff): return jlib.jack_set_freewheel(client, onoff) def set_buffer_size(client, nframes): return jlib.jack_set_buffer_size(client, nframes) def get_sample_rate(client): return jlib.jack_get_sample_rate(client) def get_buffer_size(client): return jlib.jack_get_buffer_size(client) def engine_takeover_timebase(client): return jlib.jack_engine_takeover_timebase(client) def cpu_load(client): return jlib.jack_cpu_load(client) # ------------------------------------------------------------------------------------------------- # Port Functions jlib.jack_port_register.argtypes = [POINTER(jack_client_t), c_char_p, c_char_p, c_ulong, c_ulong] jlib.jack_port_register.restype = POINTER(jack_port_t) jlib.jack_port_unregister.argtypes = [POINTER(jack_client_t), POINTER(jack_port_t)] jlib.jack_port_unregister.restype = c_int jlib.jack_port_get_buffer.argtypes = [POINTER(jack_port_t), jack_nframes_t] jlib.jack_port_get_buffer.restype = c_void_p jlib.jack_port_name.argtypes = [POINTER(jack_port_t)] jlib.jack_port_name.restype = c_char_p jlib.jack_port_short_name.argtypes = [POINTER(jack_port_t)] jlib.jack_port_short_name.restype = c_char_p jlib.jack_port_flags.argtypes = [POINTER(jack_port_t)] jlib.jack_port_flags.restype = c_int jlib.jack_port_type.argtypes = [POINTER(jack_port_t)] jlib.jack_port_type.restype = c_char_p if JACK2: jlib.jack_port_type_id.argtypes = [POINTER(jack_port_t)] jlib.jack_port_type_id.restype = jack_port_type_id_t jlib.jack_port_is_mine.argtypes = [POINTER(jack_client_t), POINTER(jack_port_t)] jlib.jack_port_is_mine.restype = c_int jlib.jack_port_connected.argtypes = [POINTER(jack_port_t)] jlib.jack_port_connected.restype = c_int jlib.jack_port_connected_to.argtypes = [POINTER(jack_port_t), c_char_p] jlib.jack_port_connected_to.restype = c_int jlib.jack_port_get_connections.argtypes = [POINTER(jack_port_t)] jlib.jack_port_get_connections.restype = POINTER(c_char_p) jlib.jack_port_get_all_connections.argtypes = [POINTER(jack_client_t), POINTER(jack_port_t)] jlib.jack_port_get_all_connections.restype = POINTER(c_char_p) jlib.jack_port_tie.argtypes = [POINTER(jack_port_t), POINTER(jack_port_t)] jlib.jack_port_tie.restype = c_int jlib.jack_port_untie.argtypes = [POINTER(jack_port_t)] jlib.jack_port_untie.restype = c_int jlib.jack_port_set_name.argtypes = [POINTER(jack_port_t), c_char_p] jlib.jack_port_set_name.restype = c_int jlib.jack_port_set_alias.argtypes = [POINTER(jack_port_t), c_char_p] jlib.jack_port_set_alias.restype = c_int jlib.jack_port_unset_alias.argtypes = [POINTER(jack_port_t), c_char_p] jlib.jack_port_unset_alias.restype = c_int jlib.jack_port_get_aliases.argtypes = [POINTER(jack_port_t), POINTER(ARRAY(c_char_p, 2))] jlib.jack_port_get_aliases.restype = c_int jlib.jack_port_request_monitor.argtypes = [POINTER(jack_port_t), c_int] jlib.jack_port_request_monitor.restype = c_int jlib.jack_port_request_monitor_by_name.argtypes = [POINTER(jack_client_t), c_char_p, c_int] jlib.jack_port_request_monitor_by_name.restype = c_int jlib.jack_port_ensure_monitor.argtypes = [POINTER(jack_port_t), c_int] jlib.jack_port_ensure_monitor.restype = c_int jlib.jack_port_monitoring_input.argtypes = [POINTER(jack_port_t)] jlib.jack_port_monitoring_input.restype = c_int jlib.jack_connect.argtypes = [POINTER(jack_client_t), c_char_p, c_char_p] jlib.jack_connect.restype = c_int jlib.jack_disconnect.argtypes = [POINTER(jack_client_t), c_char_p, c_char_p] jlib.jack_disconnect.restype = c_int jlib.jack_port_disconnect.argtypes = [POINTER(jack_client_t), POINTER(jack_port_t)] jlib.jack_port_disconnect.restype = c_int jlib.jack_port_name_size.argtypes = None jlib.jack_port_name_size.restype = c_int jlib.jack_port_type_size.argtypes = None jlib.jack_port_type_size.restype = c_int try: jlib.jack_port_uuid.argtypes = [POINTER(jack_port_t)] jlib.jack_port_uuid.restype = jack_uuid_t except AttributeError: jlib.jack_port_uuid = None try: jlib.jack_port_type_get_buffer_size.argtypes = [POINTER(jack_client_t), c_char_p] jlib.jack_port_type_get_buffer_size.restype = c_size_t except AttributeError: jlib.jack_port_type_get_buffer_size = None def port_register(client, port_name, port_type, flags, buffer_size): return jlib.jack_port_register(client, _e(port_name), _e(port_type), flags, buffer_size) def port_unregister(client, port): return jlib.jack_port_unregister(client, port) def port_get_buffer(port, nframes): return jlib.jack_port_get_buffer(port, nframes) def port_name(port): return _d(jlib.jack_port_name(port)) def port_short_name(port): return _d(jlib.jack_port_short_name(port)) def port_flags(port): return jlib.jack_port_flags(port) def port_type(port): return _d(jlib.jack_port_type(port)) # JACK2 only: def port_type_id(port): return jlib.jack_port_type_id(port) def port_is_mine(client, port): return jlib.jack_port_is_mine(client, port) def port_connected(port): return jlib.jack_port_connected(port) def port_connected_to(port, port_name): return jlib.jack_port_connected_to(port, _e(port_name)) def port_get_connections(port): ports = jlib.jack_port_get_connections(port) if not ports: return for port_name in ports: if port_name is None: break yield _d(port_name) def port_get_all_connections(client, port): ports = jlib.jack_port_get_all_connections(client, port) if not ports: return for port_name in ports: if port_name is None: break yield _d(port_name) def port_tie(src, dst): return jlib.jack_port_tie(src, dst) def port_untie(port): return jlib.jack_port_untie(port) def port_set_name(port, port_name): return jlib.jack_port_set_name(port, _e(port_name)) def port_set_alias(port, alias): return jlib.jack_port_set_alias(port, _e(alias)) def port_unset_alias(port, alias): return jlib.jack_port_unset_alias(port, _e(alias)) def port_get_aliases(port): # NOTE - this function has no 2nd argument in jacklib # Instead, aliases will be passed in return value, in form of (int ret, str alias1, str alias2) name_size = port_name_size() alias_type = c_char_p * 2 aliases = alias_type(b" " * name_size, b" " * name_size) ret = jlib.jack_port_get_aliases(port, pointer(aliases)) return ret, _d(aliases[0]), _d(aliases[1]) def port_request_monitor(port, onoff): return jlib.jack_port_request_monitor(port, onoff) def port_request_monitor_by_name(client, port_name, onoff): return jlib.jack_port_request_monitor_by_name(client, _e(port_name), onoff) def port_ensure_monitor(port, onoff): return jlib.jack_port_ensure_monitor(port, onoff) def port_monitoring_input(port): return jlib.jack_port_monitoring_input(port) def connect(client, source_port, destination_port): return jlib.jack_connect(client, _e(source_port), _e(destination_port)) def disconnect(client, source_port, destination_port): return jlib.jack_disconnect(client, _e(source_port), _e(destination_port)) def port_disconnect(client, port): return jlib.jack_port_disconnect(client, port) def port_name_size(): return jlib.jack_port_name_size() def port_type_size(): return jlib.jack_port_type_size() def port_type_get_buffer_size(client, port_type): if jlib.jack_port_type_get_buffer_size: return jlib.jack_port_type_get_buffer_size(client, _e(port_type)) return 0 def port_uuid(port): if jlib.jack_port_uuid: return jlib.jack_port_uuid(port) return -1 # ------------------------------------------------------------------------------------------------- # Latency Functions jlib.jack_port_set_latency.argtypes = [POINTER(jack_port_t), jack_nframes_t] jlib.jack_port_set_latency.restype = None try: jlib.jack_port_get_latency_range.argtypes = [ POINTER(jack_port_t), jack_latency_callback_mode_t, POINTER(jack_latency_range_t) ] jlib.jack_port_get_latency_range.restype = None except AttributeError: jlib.jack_port_get_latency_range = None try: jlib.jack_port_set_latency_range.argtypes = [ POINTER(jack_port_t), jack_latency_callback_mode_t, POINTER(jack_latency_range_t) ] jlib.jack_port_set_latency_range.restype = None except AttributeError: jlib.jack_port_set_latency_range = None jlib.jack_recompute_total_latencies.argtypes = [POINTER(jack_client_t)] jlib.jack_recompute_total_latencies.restype = c_int jlib.jack_port_get_latency.argtypes = [POINTER(jack_port_t)] jlib.jack_port_get_latency.restype = jack_nframes_t jlib.jack_port_get_total_latency.argtypes = [POINTER(jack_client_t), POINTER(jack_port_t)] jlib.jack_port_get_total_latency.restype = jack_nframes_t jlib.jack_recompute_total_latency.argtypes = [POINTER(jack_client_t), POINTER(jack_port_t)] jlib.jack_recompute_total_latency.restype = c_int def port_set_latency(port, nframes): jlib.jack_port_set_latency(port, nframes) def port_get_latency_range(port, mode, range_): if jlib.jack_port_get_latency_range: jlib.jack_port_get_latency_range(port, mode, range_) def port_set_latency_range(port, mode, range_): if jlib.jack_port_set_latency_range: jlib.jack_port_set_latency_range(port, mode, range_) def recompute_total_latencies(): return jlib.jack_recompute_total_latencies() def port_get_latency(port): return jlib.jack_port_get_latency(port) def port_get_total_latency(client, port): return jlib.jack_port_get_total_latency(client, port) def recompute_total_latency(client, port): return jlib.jack_recompute_total_latency(client, port) # ------------------------------------------------------------------------------------------------- # Port Searching jlib.jack_get_ports.argtypes = [POINTER(jack_client_t), c_char_p, c_char_p, c_ulong] jlib.jack_get_ports.restype = POINTER(c_char_p) jlib.jack_port_by_name.argtypes = [POINTER(jack_client_t), c_char_p] jlib.jack_port_by_name.restype = POINTER(jack_port_t) jlib.jack_port_by_id.argtypes = [POINTER(jack_client_t), jack_port_id_t] jlib.jack_port_by_id.restype = POINTER(jack_port_t) def get_ports(client, port_name_pattern=None, type_name_pattern=None, flags=0): return jlib.jack_get_ports(client, _e(port_name_pattern or ''), _e(type_name_pattern or ''), flags) def port_by_name(client, port_name): return jlib.jack_port_by_name(client, _e(port_name)) def port_by_id(client, port_id): return jlib.jack_port_by_id(client, port_id) # ------------------------------------------------------------------------------------------------- # Time Functions jlib.jack_frames_since_cycle_start.argtypes = [POINTER(jack_client_t)] jlib.jack_frames_since_cycle_start.restype = jack_nframes_t jlib.jack_frame_time.argtypes = [POINTER(jack_client_t)] jlib.jack_frame_time.restype = jack_nframes_t jlib.jack_last_frame_time.argtypes = [POINTER(jack_client_t)] jlib.jack_last_frame_time.restype = jack_nframes_t try: # JACK_OPTIONAL_WEAK_EXPORT jlib.jack_get_cycle_times.argtypes = [ POINTER(jack_client_t), POINTER(jack_nframes_t), POINTER(jack_time_t), POINTER(jack_time_t), POINTER(c_float) ] jlib.jack_get_cycle_times.restype = c_int except AttributeError: jlib.jack_get_cycle_times = None jlib.jack_frames_to_time.argtypes = [POINTER(jack_client_t), jack_nframes_t] jlib.jack_frames_to_time.restype = jack_time_t jlib.jack_time_to_frames.argtypes = [POINTER(jack_client_t), jack_time_t] jlib.jack_time_to_frames.restype = jack_nframes_t jlib.jack_get_time.argtypes = None jlib.jack_get_time.restype = jack_time_t def frames_since_cycle_start(client): return jlib.jack_frames_since_cycle_start(client) def frame_time(client): return jlib.jack_frame_time(client) def last_frame_time(client): return jlib.jack_last_frame_time(client) def get_cycle_times(client, current_frames, current_usecs, next_usecs, period_usecs): # JACK_OPTIONAL_WEAK_EXPORT if jlib.jack_frames_to_time: return jlib.jack_get_cycle_times(client, current_frames, current_usecs, next_usecs, period_usecs) return -1 def frames_to_time(client, nframes): return jlib.jack_frames_to_time(client, nframes) def time_to_frames(client, time): return jlib.jack_time_to_frames(client, time) def get_time(): return jlib.jack_get_time() # ------------------------------------------------------------------------------------------------- # Error Output # TODO # ------------------------------------------------------------------------------------------------- # Misc _error_callback = None jlib.jack_free.argtypes = [c_void_p] jlib.jack_free.restype = None try: jlib.jack_set_error_function.argtypes = [JackErrorCallback] jlib.jack_set_error_function.restype = None except AttributeError: jlib.jack_set_error_function = None def set_error_function(error_callback): global _error_callback if jlib.jack_set_error_function: _error_callback = JackErrorCallback(error_callback) jlib.jack_set_error_function(_error_callback) def free(ptr): return jlib.jack_free(ptr) # ------------------------------------------------------------------------------------------------- # Transport _sync_callback = _timebase_callback = None jlib.jack_release_timebase.argtypes = [POINTER(jack_client_t)] jlib.jack_release_timebase.restype = c_int jlib.jack_set_sync_callback.argtypes = [POINTER(jack_client_t), JackSyncCallback, c_void_p] jlib.jack_set_sync_callback.restype = c_int jlib.jack_set_sync_timeout.argtypes = [POINTER(jack_client_t), jack_time_t] jlib.jack_set_sync_timeout.restype = c_int jlib.jack_set_timebase_callback.argtypes = [ POINTER(jack_client_t), c_int, JackTimebaseCallback, c_void_p ] jlib.jack_set_timebase_callback.restype = c_int jlib.jack_transport_locate.argtypes = [POINTER(jack_client_t), jack_nframes_t] jlib.jack_transport_locate.restype = c_int jlib.jack_transport_query.argtypes = [POINTER(jack_client_t), POINTER(jack_position_t)] jlib.jack_transport_query.restype = jack_transport_state_t jlib.jack_get_current_transport_frame.argtypes = [POINTER(jack_client_t)] jlib.jack_get_current_transport_frame.restype = jack_nframes_t jlib.jack_transport_reposition.argtypes = [POINTER(jack_client_t), POINTER(jack_position_t)] jlib.jack_transport_reposition.restype = c_int jlib.jack_transport_start.argtypes = [POINTER(jack_client_t)] jlib.jack_transport_start.restype = None jlib.jack_transport_stop.argtypes = [POINTER(jack_client_t)] jlib.jack_transport_stop.restype = None def release_timebase(client): return jlib.jack_release_timebase(client) def set_sync_callback(client, sync_callback, arg): global _sync_callback _sync_callback = JackSyncCallback(sync_callback) return jlib.jack_set_sync_callback(client, _sync_callback, arg) def set_sync_timeout(client, timeout): return jlib.jack_set_sync_timeout(client, timeout) def set_timebase_callback(client, conditional, timebase_callback, arg): global _timebase_callback _timebase_callback = JackTimebaseCallback(timebase_callback) return jlib.jack_set_timebase_callback(client, conditional, _timebase_callback, arg) def transport_locate(client, frame): return jlib.jack_transport_locate(client, frame) def transport_query(client, pos): return jlib.jack_transport_query(client, pos) def get_current_transport_frame(client): return jlib.jack_get_current_transport_frame(client) def transport_reposition(client, pos): return jlib.jack_transport_reposition(client, pos) def transport_start(client): return jlib.jack_transport_start(client) def transport_stop(client): return jlib.jack_transport_stop(client) # ------------------------------------------------------------------------------------------------- # MIDI jlib.jack_midi_get_event_count.argtypes = [c_void_p] jlib.jack_midi_get_event_count.restype = jack_nframes_t jlib.jack_midi_event_get.argtypes = [POINTER(jack_midi_event_t), c_void_p, c_uint32] jlib.jack_midi_event_get.restype = c_int jlib.jack_midi_clear_buffer.argtypes = [c_void_p] jlib.jack_midi_clear_buffer.restype = None jlib.jack_midi_max_event_size.argtypes = [c_void_p] jlib.jack_midi_max_event_size.restype = c_size_t jlib.jack_midi_event_reserve.argtypes = [c_void_p, jack_nframes_t, c_size_t] jlib.jack_midi_event_reserve.restype = POINTER(jack_midi_data_t) jlib.jack_midi_event_write.argtypes = [ c_void_p, jack_nframes_t, POINTER(jack_midi_data_t), c_size_t ] jlib.jack_midi_event_write.restype = c_int jlib.jack_midi_get_lost_event_count.argtypes = [c_void_p] jlib.jack_midi_get_lost_event_count.restype = c_uint32 def midi_get_event_count(port_buffer): return jlib.jack_midi_get_event_count(port_buffer) def midi_event_get(event, port_buffer, event_index): return jlib.jack_midi_event_get(event, port_buffer, event_index) def midi_clear_buffer(port_buffer): return jlib.jack_midi_clear_buffer(port_buffer) def midi_max_event_size(port_buffer): return jlib.jack_midi_max_event_size(port_buffer) def midi_event_reserve(port_buffer, time, data_size): return jlib.jack_midi_event_reserve(port_buffer, time, data_size) def midi_event_write(port_buffer, time, data, data_size): return jlib.jack_midi_event_write(port_buffer, time, data, data_size) def midi_get_lost_event_count(port_buffer): return jlib.jack_midi_get_lost_event_count(port_buffer) # ------------------------------------------------------------------------------------------------- # Session _session_callback = None try: jlib.jack_set_session_callback.argtypes = [ POINTER(jack_client_t), JackSessionCallback, c_void_p ] jlib.jack_set_session_callback.restype = c_int except AttributeError: jlib.jack_set_session_callback = None try: jlib.jack_session_reply.argtypes = [POINTER(jack_client_t), POINTER(jack_session_event_t)] jlib.jack_session_reply.restype = c_int except AttributeError: jlib.jack_session_reply = None try: jlib.jack_session_event_free.argtypes = [POINTER(jack_session_event_t)] jlib.jack_session_event_free.restype = None except AttributeError: jlib.jack_session_event_free = None try: jlib.jack_client_get_uuid.argtypes = [POINTER(jack_client_t)] jlib.jack_client_get_uuid.restype = c_char_p except AttributeError: jlib.jack_client_get_uuid = None try: jlib.jack_session_notify.argtypes = [ POINTER(jack_client_t), c_char_p, jack_session_event_type_t, c_char_p ] jlib.jack_session_notify.restype = POINTER(jack_session_command_t) except AttributeError: jlib.jack_session_notify = None try: jlib.jack_session_commands_free.argtypes = [POINTER(jack_session_command_t)] jlib.jack_session_commands_free.restype = None except AttributeError: jlib.jack_session_commands_free = None try: jlib.jack_get_uuid_for_client_name.argtypes = [POINTER(jack_client_t), c_char_p] jlib.jack_get_uuid_for_client_name.restype = c_char_p except AttributeError: jlib.jack_get_uuid_for_client_name = None try: jlib.jack_get_client_name_by_uuid.argtypes = [POINTER(jack_client_t), c_char_p] jlib.jack_get_client_name_by_uuid.restype = c_char_p except AttributeError: jlib.jack_get_client_name_by_uuid = None try: jlib.jack_reserve_client_name.argtypes = [POINTER(jack_client_t), c_char_p, c_char_p] jlib.jack_reserve_client_name.restype = c_int except AttributeError: jlib.jack_reserve_client_name = None try: jlib.jack_client_has_session_callback.argtypes = [POINTER(jack_client_t), c_char_p] jlib.jack_client_has_session_callback.restype = c_int except AttributeError: jlib.jack_client_has_session_callback = None try: jlib.jack_uuid_parse.argtypes = [c_char_p, POINTER(jack_uuid_t)] jlib.jack_uuid_parse.restype = c_int except AttributeError: jlib.jack_uuid_parse = None try: jlib.jack_uuid_unparse.argtypes = [jack_uuid_t, c_char_p] jlib.jack_uuid_unparse.restype = None except AttributeError: jlib.jack_uuid_unparse = None def set_session_callback(client, session_callback, arg): if jlib.jack_set_session_callback: global _session_callback _session_callback = JackSessionCallback(session_callback) return jlib.jack_set_session_callback(client, _session_callback, arg) return -1 def session_reply(client, event): if jlib.jack_session_reply: return jlib.jack_session_reply(client, event) return -1 def session_event_free(event): if jlib.jack_session_event_free: jlib.jack_session_event_free(event) def client_get_uuid(client): if jlib.jack_client_get_uuid: return _d(jlib.jack_client_get_uuid(client)) return None def session_notify(client, target, type_, path): if jlib.jack_session_notify: return jlib.jack_session_notify(client, _e(target), type_, _e(path)) return jack_session_command_t() def session_commands_free(cmds): if jlib.jack_session_commands_free: jlib.jack_session_commands_free(cmds) def get_uuid_for_client_name(client, client_name): if jlib.jack_get_uuid_for_client_name: return jlib.jack_get_uuid_for_client_name(client, _e(client_name)) return None def get_client_name_by_uuid(client, client_uuid): if jlib.jack_get_client_name_by_uuid: return jlib.jack_get_client_name_by_uuid(client, _e(client_uuid)) return None def reserve_client_name(client, name, uuid): if jlib.jack_reserve_client_name: return jlib.jack_reserve_client_name(client, _e(name), _e(uuid)) return -1 def client_has_session_callback(client, client_name): if jlib.jack_client_has_session_callback: return jlib.jack_client_has_session_callback(client, _e(client_name)) return -1 def uuid_parse(uuid_cstr): if jlib.jack_uuid_parse and uuid_cstr is not None: uuid = jack_uuid_t() res = jlib.jack_uuid_parse(uuid_cstr, byref(uuid)) return uuid if res != -1 else None return -1 def uuid_unparse(uuid, encoding=ENCODING): if jlib.jack_uuid_unparse: uuid_str = c_char_p(b" " * JACK_UUID_STRING_SIZE) jlib.jack_uuid_unparse(uuid, uuid_str) return _d(uuid_str.value, encoding) return "" # ------------------------------------------------------------------------------------------------- # Custom _custom_appearance_callback = None try: jlib.jack_custom_publish_data.argtypes = [POINTER(jack_client_t), c_char_p, c_void_p, c_size_t] jlib.jack_custom_publish_data.restype = c_int except AttributeError: jlib.jack_custom_publish_data = None try: jlib.jack_custom_get_data.argtypes = [ POINTER(jack_client_t), c_char_p, c_char_p, POINTER(c_void_p), POINTER(c_size_t) ] jlib.jack_custom_get_data.restype = c_int except AttributeError: jlib.jack_custom_get_data = None try: jlib.jack_custom_unpublish_data.argtypes = [POINTER(jack_client_t), c_char_p] jlib.jack_custom_unpublish_data.restype = c_int except AttributeError: jlib.jack_custom_unpublish_data = None try: jlib.jack_custom_get_keys.argtypes = [POINTER(jack_client_t), c_char_p] jlib.jack_custom_get_keys.restype = POINTER(c_char_p) except AttributeError: jlib.jack_custom_get_keys = None try: jlib.jack_custom_set_data_appearance_callback.argtypes = [ POINTER(jack_client_t), JackCustomDataAppearanceCallback, c_void_p ] jlib.jack_custom_set_data_appearance_callback.restype = c_int except AttributeError: jlib.jack_custom_set_data_appearance_callback = None def custom_publish_data(client, key, data, size): if jlib.jack_custom_publish_data: return jlib.jack_custom_publish_data(client, _e(key), data, size) return -1 def custom_get_data(client, client_name, key): # NOTE - this function has no extra arguments in jacklib # Instead, data and size will be passed in return value # in form of (int ret, void* data, size_t size) if jlib.jack_custom_get_data: data = c_void_p(0) size = c_size_t(0) ret = jlib.jack_custom_get_data(client, _e(client_name), _e(key), pointer(data), pointer(size)) return ret, data, size return -1, None, 0 def custom_unpublish_data(client, key): if jlib.jack_custom_unpublish_data: return jlib.jack_custom_unpublish_data(client, _e(key)) return -1 def custom_get_keys(client, client_name): if jlib.jack_custom_get_keys: return jlib.jack_custom_get_keys(client, _e(client_name)) return None def custom_set_data_appearance_callback(client, custom_callback, arg): if jlib.jack_custom_set_data_appearance_callback: global _custom_appearance_callback _custom_appearance_callback = JackCustomDataAppearanceCallback(custom_callback) return jlib.jack_custom_set_data_appearance_callback(client, _custom_appearance_callback, arg) return -1 # ------------------------------------------------------------------------------------------------- # Meta data Property = namedtuple('Property', ('key', 'value', 'type')) try: jlib.jack_free_description.argtypes = [POINTER(jack_description_t), c_int] jlib.jack_free_description.restype = None jlib.jack_get_all_properties.argtypes = [POINTER(POINTER(jack_description_t))] jlib.jack_get_all_properties.restype = c_int jlib.jack_get_properties.argtypes = [jack_uuid_t, POINTER(jack_description_t)] jlib.jack_get_properties.restype = c_int jlib.jack_get_property.argtypes = [jack_uuid_t, c_char_p, POINTER(c_char_p), POINTER(c_char_p)] jlib.jack_get_property.restype = c_int jlib.jack_remove_all_properties.argtypes = [POINTER(jack_client_t)] jlib.jack_remove_all_properties.restype = c_int jlib.jack_remove_properties.argtypess = [POINTER(jack_client_t), POINTER(jack_uuid_t)] jlib.jack_remove_properties.restype = c_int jlib.jack_remove_property.argtypes = [POINTER(jack_client_t), POINTER(jack_uuid_t), c_char_p] jlib.jack_remove_property.restype = c_int jlib.jack_set_property.argtypes = [ POINTER(jack_client_t), jack_uuid_t, c_char_p, c_char_p, c_char_p ] jlib.jack_set_property.restype = c_int jlib.jack_set_property_change_callback.argtypes = [ POINTER(jack_client_t), JackPropertyChangeCallback, c_void_p ] jlib.jack_set_property_change_callback.restype = c_int except AttributeError: jlib.jack_free_description = None jlib.jack_get_properties = None jlib.jack_get_property = None jlib.jack_remove_all_properties = None jlib.jack_remove_properties = None jlib.jack_remove_property = None jlib.jack_set_property = None jlib.jack_set_property_change_callback = None def free_description(description, free_description_itself=0): jlib.jack_free_description(description, free_description_itself) def _decode_property(prop, encoding=ENCODING): key, value, type_ = prop.key, prop.data, prop.type decode_value = True try: key = _d(key, encoding) except UnicodeDecodeError: pass if type_: try: type_ = _d(type_, encoding) except UnicodeDecodeError: pass else: decode_value = type_.startswith('text/') if decode_value: try: value = _d(value, encoding) except UnicodeDecodeError: pass return Property(key, value, type_) def get_all_properties(encoding=ENCODING): descriptions = POINTER(jack_description_t)() ret = jlib.jack_get_all_properties(byref(descriptions)) results = {} if ret != -1: for p_idx in range(ret): description = descriptions[p_idx] if description.property_cnt: results[description.subject] = [ _decode_property(description.properties[p_idx], encoding) for p_idx in range(description.property_cnt) ] jlib.jack_free_description(description, 0) free(descriptions) return results def get_properties(subject, encoding=ENCODING): description = jack_description_t() ret = jlib.jack_get_properties(subject, byref(description)) results = [] if ret != -1: for p_idx in range(description.property_cnt): results.append(_decode_property(description.properties[p_idx], encoding)) jlib.jack_free_description(byref(description), 0) return results def get_client_properties(client, client_uuid, encoding=ENCODING): if isinstance(client_uuid, str): client_uuid = get_uuid_for_client_name(client, client_uuid) return get_properties(uuid_parse(client_uuid), encoding) def get_port_properties(client, port, encoding=ENCODING): if not isinstance(port, POINTER(jack_port_t)): port = port_by_name(client, port) return get_properties(port_uuid(port), encoding) def get_property(subject, key, encoding=ENCODING): # FIXME: how to handle non-null terminated data in value? # We wouldn't know the length of the data in the value buffer. # This seems to be an oversight in the JACK meta data API. value_c = c_char_p() type_c = c_char_p() ret = jlib.jack_get_property(subject, _e(key), byref(value_c), byref(type_c)) value = value_c.value if ret != -1: decode_value = True if type_c: try: type_ = _d(type_c.value, encoding) except UnicodeDecodeError: # If type can't be decoded, we assume it's neither a mimetype # nor a URI, so we don't know how to interpret it and won't use # it to decide whether to decode the property value. type_ = type_c.value else: decode_value = type_.startswith('text/') free(type_c) else: type_ = None if decode_value: try: value = _d(value_c.value, encoding) except UnicodeDecodeError: pass free(value_c) return Property(key, value, type_) def get_client_property(client, client_uuid, key, encoding=ENCODING): if isinstance(client_uuid, str): client_uuid = get_uuid_for_client_name(client, client_uuid) return get_property(uuid_parse(client_uuid), key, encoding) def get_port_property(client, port, key, encoding=ENCODING): if not isinstance(port, POINTER(jack_port_t)): port = port_by_name(client, port) return get_property(port_uuid(port), key, encoding) def get_port_pretty_name(client, port, encoding=ENCODING): prop = get_port_property(client, port, JACK_METADATA_PRETTY_NAME, encoding) return prop.value if prop else None def remove_all_properties(client): return jlib.jack_remove_property(client) def remove_properties(client, subject): return jlib.jack_remove_property(client, subject) def remove_client_properties(client, client_uuid): if isinstance(client_uuid, str): client_uuid = get_uuid_for_client_name(client, client_uuid) return remove_properties(client, uuid_parse(client_uuid)) def remove_port_properties(client, port): if not isinstance(port, POINTER(jack_port_t)): port = port_by_name(client, port) return remove_properties(client, port_uuid(port)) def remove_property(client, subject, key, encoding=ENCODING): return jlib.jack_remove_property(client, subject, _e(key, encoding)) def remove_client_property(client, client_uuid, key, encoding=ENCODING): if isinstance(client_uuid, str): client_uuid = get_uuid_for_client_name(client, client_uuid) return remove_property(client, uuid_parse(client_uuid), key, encoding) def remove_port_property(client, port, key, encoding=ENCODING): if not isinstance(port, POINTER(jack_port_t)): port = port_by_name(client, port) return remove_property(client, port_uuid(port), key, encoding) def set_property(client, subject, key, value, type=None, encoding=ENCODING): if value is not None and encoding: value = _e(value, encoding) if type is not None and encoding: type = _e(type, encoding) return jlib.jack_set_property(client, subject, _e(key, encoding), value, type) def set_client_property(client, client_uuid, key, value, type=None, encoding=ENCODING): if isinstance(client_uuid, str): client_uuid = get_uuid_for_client_name(client, client_uuid) uuid = uuid_parse(client_uuid) return set_property(client, uuid, key, value, type, encoding) if uuid != -1 else -1 def set_port_property(client, port, key, value, type=None, encoding=ENCODING): if not isinstance(port, POINTER(jack_port_t)): port = port_by_name(client, port) uuid = port_uuid(port) return set_property(client, uuid, key, value, type, encoding) if uuid != -1 else -1 def set_port_pretty_name(client, port, value, encoding=ENCODING): return set_port_property(client, port, JACK_METADATA_PRETTY_NAME, value, 'text/plain', encoding) def set_property_change_callback(client, callback, arg=None): if jlib.jack_set_property_change_callback: global _property_change_callback _property_change_callback = JackPropertyChangeCallback(callback) return jlib.jack_set_property_change_callback(client, _property_change_callback, arg) return -1 ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1665611335.9505882 agordejo-0.4.2/engine/jacklib/helpers.py0000644000175000017500000001047714321633110016726 0ustar00nilsnils#!/usr/bin/env python3 # -*- coding: utf-8 -*- # Helper functions for extra jacklib functionality # Copyright (C) 2012-2013 Filipe Coelho # 2016-2021 Christopher Arndt # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # any later version. # # This program 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 General Public License for more details. # # For a full copy of the GNU General Public License see the COPYING file # ------------------------------------------------------------------------------------------------- # Try Import jacklib from __future__ import absolute_import, print_function, unicode_literals from . import api as jacklib # ------------------------------------------------------------------------------------------------- # Get JACK error status as string def get_jack_status_error_string(cStatus): status = cStatus.value if status == 0x0: return "" errorString = [] if status == jacklib.JackFailure: # Only include this generic message if no other error status is set errorString.append("Overall operation failed") if status & jacklib.JackInvalidOption: errorString.append("The operation contained an invalid or unsupported option") if status & jacklib.JackNameNotUnique: errorString.append("The desired client name was not unique") if status & jacklib.JackServerStarted: errorString.append("The JACK server was started as a result of this operation") if status & jacklib.JackServerFailed: errorString.append("Unable to connect to the JACK server") if status & jacklib.JackServerError: errorString.append("Communication error with the JACK server") if status & jacklib.JackNoSuchClient: errorString.append("Requested client does not exist") if status & jacklib.JackLoadFailure: errorString.append("Unable to load internal client") if status & jacklib.JackInitFailure: errorString.append("Unable to initialize client") if status & jacklib.JackShmFailure: errorString.append("Unable to access shared memory") if status & jacklib.JackVersionError: errorString.append("Client's protocol version does not match") if status & jacklib.JackBackendError: errorString.append("Backend Error") if status & jacklib.JackClientZombie: errorString.append("Client is being shutdown against its will") return ";\n".join(errorString) + "." # ------------------------------------------------------------------------------------------------- # Convert C char** -> Python list def c_char_p_p_to_list(c_char_p_p, encoding=jacklib.ENCODING, errors='ignore'): i = 0 retList = [] if not c_char_p_p: return retList while True: new_char_p = c_char_p_p[i] if not new_char_p: break retList.append(new_char_p.decode(encoding=encoding, errors=errors)) i += 1 jacklib.free(c_char_p_p) return retList # ------------------------------------------------------------------------------------------------- # Convert C void* -> string def voidptr2str(void_p): char_p = jacklib.cast(void_p, jacklib.c_char_p) string = str(char_p.value, encoding="utf-8") return string # ------------------------------------------------------------------------------------------------- # Convert C void* -> jack_default_audio_sample_t* def translate_audio_port_buffer(void_p): return jacklib.cast(void_p, jacklib.POINTER(jacklib.jack_default_audio_sample_t)) # ------------------------------------------------------------------------------------------------- # Convert a JACK midi buffer into a python variable-size list def translate_midi_event_buffer(void_p, size): if not void_p: return () elif size == 1: return (void_p[0],) elif size == 2: return (void_p[0], void_p[1]) elif size == 3: return (void_p[0], void_p[1], void_p[2]) elif size == 4: return (void_p[0], void_p[1], void_p[2], void_p[3]) else: return () ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1665611335.9505882 agordejo-0.4.2/engine/jacklib/version.py0000644000175000017500000000002614321633110016736 0ustar00nilsnils__version__ = "0.1.0" ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1665611335.9505882 agordejo-0.4.2/engine/nsmservercontrol.py0000644000175000017500000026061614321633110017314 0ustar00nilsnils#! /usr/bin/env python3 # -*- coding: utf-8 -*- """ Copyright 2022, Nils Hilbricht, Germany ( https://www.hilbricht.net ) The Non-Session-Manager by Jonathan Moore Liles : http://non.tuxfamily.org/nsm/ New Session Manager, by LinuxAudio.org: https://github.com/linuxaudio/new-session-manager With help from code fragments from https://github.com/attwad/python-osc ( DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE v2 ) This file is part of the Laborejo Software Suite ( https://www.laborejo.org ). This application is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . """ import logging; logger = logging.getLogger(__name__); logger.info("import") #Standard Library import struct import socket from os import getenv #to get NSM env var from shutil import rmtree as shutilrmtree from shutil import copytree as shutilcopytree from multiprocessing import Process from urllib.parse import urlparse #to convert NSM env var import subprocess import atexit import pathlib import json from uuid import uuid4 from datetime import datetime from sys import exit as sysexit from time import sleep from ctypes import c_ulong from functools import lru_cache #Our files from .comparedirectories import md5_dir def nothing(*args, **kwargs): pass class _IncomingMessage(object): """Representation of a parsed datagram representing an OSC message. An OSC message consists of an OSC Address Pattern followed by an OSC Type Tag String followed by zero or more OSC Arguments. """ def __init__(self, dgram): #NSM Broadcasts are bundles, but very simple ones. We only need to care about the single message it contains. #Therefore we can strip the bundle prefix and handle it as normal message. if b"#bundle" in dgram: bundlePrefix, singleMessage = dgram.split(b"/", maxsplit=1) dgram = b"/" + singleMessage # / eaten by split self.LENGTH = 4 #32 bit self._dgram = dgram self._parameters = [] self.parse_datagram() def get_int(self, dgram, start_index): """Get a 32-bit big-endian two's complement integer from the datagram. Args: dgram: A datagram packet. start_index: An index where the integer starts in the datagram. Returns: A tuple containing the integer and the new end index. Raises: ValueError if the datagram could not be parsed. """ try: if len(dgram[start_index:]) < self.LENGTH: raise ValueError('Datagram is too short') return ( struct.unpack('>i', dgram[start_index:start_index + self.LENGTH])[0], start_index + self.LENGTH) except (struct.error, TypeError) as e: raise ValueError('Could not parse datagram %s' % e) def get_string(self, dgram, start_index): """Get a python string from the datagram, starting at pos start_index. We receive always the full string, but handle only the part from the start_index internally. In the end return the offset so it can be added to the index for the next parameter. Each subsequent call handles less of the same string, starting further to the right. According to the specifications, a string is: "A sequence of non-null ASCII characters followed by a null, followed by 0-3 additional null characters to make the total number of bits a multiple of 32". Args: dgram: A datagram packet. start_index: An index where the string starts in the datagram. Returns: A tuple containing the string and the new end index. Raises: ValueError if the datagram could not be parsed. """ #First test for empty string, which is nothing, followed by a terminating \x00 padded by three additional \x00. if dgram[start_index:].startswith(b"\x00\x00\x00\x00"): return "", start_index + 4 #Otherwise we have a non-empty string that must follow the rules of the docstring. offset = 0 try: while dgram[start_index + offset] != 0: offset += 1 if offset == 0: raise ValueError('OSC string cannot begin with a null byte: %s' % dgram[start_index:]) # Align to a byte word. if (offset) % self.LENGTH == 0: offset += self.LENGTH else: offset += (-offset % self.LENGTH) # Python slices do not raise an IndexError past the last index, # do it ourselves. if offset > len(dgram[start_index:]): raise ValueError('Datagram is too short') data_str = dgram[start_index:start_index + offset] return data_str.replace(b'\x00', b'').decode('utf-8'), start_index + offset except IndexError as ie: raise ValueError('Could not parse datagram %s' % ie) except TypeError as te: raise ValueError('Could not parse datagram %s' % te) def get_float(self, dgram, start_index): """Get a 32-bit big-endian IEEE 754 floating point number from the datagram. Args: dgram: A datagram packet. start_index: An index where the float starts in the datagram. Returns: A tuple containing the float and the new end index. Raises: ValueError if the datagram could not be parsed. """ try: return (struct.unpack('>f', dgram[start_index:start_index + self.LENGTH])[0], start_index + self.LENGTH) except (struct.error, TypeError) as e: raise ValueError('Could not parse datagram %s' % e) def parse_datagram(self): try: self._address_regexp, index = self.get_string(self._dgram, 0) if not self._dgram[index:]: # No params is legit, just return now. return # Get the parameters types. type_tag, index = self.get_string(self._dgram, index) if type_tag.startswith(','): type_tag = type_tag[1:] # Parse each parameter given its type. for param in type_tag: if param == "i": # Integer. val, index = self.get_int(self._dgram, index) elif param == "f": # Float. val, index = self.get_float(self._dgram, index) elif param == "s": # String. val, index = self.get_string(self._dgram, index) else: logger.warning("Unhandled parameter type: {0}".format(param)) continue self._parameters.append(val) except ValueError as pe: #raise ValueError('Found incorrect datagram, ignoring it', pe) # Raising an error is not ignoring it! logger.warning("Found incorrect datagram, ignoring it. {}".format(pe)) @property def oscpath(self): """Returns the OSC address regular expression.""" return self._address_regexp @staticmethod def dgram_is_message(dgram): """Returns whether this datagram starts as an OSC message.""" return dgram.startswith(b'/') @property def size(self): """Returns the length of the datagram for this message.""" return len(self._dgram) @property def dgram(self): """Returns the datagram from which this message was built.""" return self._dgram @property def params(self): """Convenience method for list(self) to get the list of parameters.""" return list(self) def __iter__(self): """Returns an iterator over the parameters of this message.""" return iter(self._parameters) class _OutgoingMessage(object): def __init__(self, oscpath): self.LENGTH = 4 #32 bit self.oscpath = oscpath self._args = [] def write_string(self, val): dgram = val.encode('utf-8') diff = self.LENGTH - (len(dgram) % self.LENGTH) dgram += (b'\x00' * diff) return dgram def write_int(self, val): return struct.pack('>i', val) def write_float(self, val): return struct.pack('>f', val) def add_arg(self, argument): t = {str:"s", int:"i", float:"f"}[type(argument)] self._args.append((t, argument)) def build(self): dgram = b'' #OSC Path dgram += self.write_string(self.oscpath) if not self._args: dgram += self.write_string(',') return dgram # Write the parameters. arg_types = "".join([arg[0] for arg in self._args]) dgram += self.write_string(',' + arg_types) for arg_type, value in self._args: f = {"s":self.write_string, "i":self.write_int, "f":self.write_float}[arg_type] dgram += f(value) return dgram class NsmServerControl(object): """ The ServerControl can be started in three modes, regarding nsmd. We expect that starting our own nsmd will be the majority of cases. SessionRoot parameter is only honored if we start nsmd ourselves. Ascending lookup priority: 1) Default is to start our own nsmd. A single-instance watcher will prevent multiple programs on the same system. 2) When $NSM_URL is found as environment we will connect to that nsmd. 3) When hostname and portnumber are given explicitely as instance variables we will first test if a server is running at that URL, if not we will start our own with these parameters. This is not only a pure implemenation of the protocol. It is extended by us reacting to and storing incoming data. This data can be interpreted and enhanced by looking at the session dir ourselves. However, we don't do anything that is not possible by the original nsmd + human interaction. 100% Compatibility is the highest priority. The big problems are the async nature of communication, message come out of order or interleaved, and nsm is not consistent in its usage of osc-paths. For example it starts listing sessions with /nsm/gui/server/message, but sends the content with /reply [/nsm/server/list, nsmSessionName] and then ends it with /nsm/server/list [0, Done] (no reply!). So three message types, three callbacks for one logically connected process. To update our internal session information we therefore need to split the functionality into severall seemingly unconnected callbacks and you need to know how the protocol works to actually know the order of operations. Switch logging to info to learn more. We have a mix between NSM callbacks and our own functions. Most important there is a watchdog that looks at the session directory and creates its own callbacks if something changes. A typical operation, say sessionDelete or sessionCopy looks like this: * Ask (blocking) nsmd for a current list of sessions, update our internal state * Perform a file operation, like copy or delete or lift a lock * Let our watchdog discover the changes in the file system and trigger another (non-blocking) request for a list of sessions to adjust our internal state to reality. Granted, we could just call our blocking query again at the end, but we would still need to let the watchdog run for operations that the user does with a filemanager, which would end up in redundant calls. Bottom line: _updateSessionListBlocking is called at the beginning of a function, but not at the end. Docs: http://non.tuxfamily.org/nsm/ http://non.tuxfamily.org/wiki/Non%20Session%20Manager http://non.tuxfamily.org/wiki/ApplicationsSupportingNsm http://non.tuxfamily.org/nsm/API.html """ def __init__(self, sessionOpenReadyHook, sessionOpenLoadingHook, sessionClosedHook, clientStatusHook, singleInstanceActivateWindowHook, dataClientNamesHook, dataClientDescriptionHook, dataClientTimelineMaximumDurationChangedHook, parameterNsmOSCUrl=None, sessionRoot=None, startupSession=None, useCallbacks=True): """If useCallbacks is False you will see every message in the log. This is just a development mode to see all messages, unfiltered. Normally we have special hook functions that save and interpret data, so they don't show in the logs""" #Deactivate hooks for now. During init no hooks may be called, #but some functions want to do that already. We setup the true hooks at the end of init self.sessionOpenReadyHook= self.sessionOpenLoadingHook= self.sessionClosedHook= self.clientStatusHook= self.singleInstanceActivateWindowHook= self.dataClientNamesHook= self.dataClientDescriptionHook= nothing self.sessions_lockfile_directory = getenv("XDG_RUNTIME_DIR") self._queue = list() #Incoming OSC messages are buffered here. #Status variables that are set by our callbacks self.internalState = { "sessions" : set(), #nsmSessionNames:str . We use set for unqiue, just in case. But we also clear that on /nsm/gui/server/message ['Listing sessions'] to handle deleted sessions "currentSession" : None, "port" : None, #Our GUI port "serverPort" : None, #nsmd port "nsmUrl" : None, #the environment variable "clients" : {}, #clientId:dict see self._initializeEmptyClient . Gets replaced with a new dict instance on session changes. "broadcasts" : [], #in the order they appeared "datastorage" : None, #URL, if present in the session } self.dataStorage = None #Becomes DataStorage() every time a datastorage client does a broadcast announce. self._addToNextSession = [] #A list of executables in PATH. Filled by new, waits for reply that session is created and then will send clientNew and clear the list. if useCallbacks: self.callbacks = { "/nsm/gui/session/name" : self._reactCallback_activeSessionChanged, #"/nsm/gui/session/root" #Session root is an active blocking call in init "/nsm/gui/client/label" : self._reactCallback_ClientLabelChanged, "/nsm/gui/client/new" : self._reactCallback_ClientNew, "/nsm/gui/session/session" : self._reactCallback_SessionSession, "/nsm/gui/client/status" : self._reactCallback_statusChanged, #handles multiple status keywords "/reply" : self._reactCallback_reply, #handles multiple replies "/error" : self._reactCallback_error, "/nsm/gui/client/has_optional_gui" : self._reactCallback_clientHasOptionalGui, "/nsm/gui/client/gui_visible" : self._reactCallback_clientGuiVisible, "/nsm/gui/client/pid" : self._reactCallback_clientPid, "/nsm/gui/client/dirty" : self._reactCallback_clientDirty, "/nsm/gui/server/message" : self._reactCallback_serverMessage, "/nsm/gui/gui_announce" : self._reactCallback_guiAnnounce, #we rarely receive that, especially not in init. "/nsm/server/list" : self._reactCallback_serverList, "/nsm/server/broadcast" : self._reactCallback_broadcast, } else: #This is just a development mode to see all messages, unfiltered self.callbacks = set() #empty set is easiest to check #Networking and Init for our control part, not for the server self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) #internet, udp self.sock.bind(('', 0)) #pick a free port on localhost. self.sock.setblocking(False) self.internalState["port"] = self.sock.getsockname()[1] #only happens once, ports don't change during runtime #self.sock.close() Do not close, this runs until the end of the program ###Testing of existing servers, starting and connecting #First handle the NSM URL, or generate on. #self.nsmOSCUrl must be a tuple compatible to the result of urlparse. (hostname, port) self.singleInstanceSocket = None if parameterNsmOSCUrl: #There is either a user provided nsm url via --url commandline or paramter the GUI detected a running nsmd and let the use choose that. o = urlparse(parameterNsmOSCUrl) #self.nsmOSCUrl = (o.hostname, o.port) #this forces lowercase. in rare circumstances this is not correct and we must be case sensitive. fix: self.nsmOSCUrl = o.netloc.split(":")[0], o.port else: envResult = self._getNsmOSCUrlFromEnvironment() if envResult: #In case there is no actual nsmd running but there still was a NSM_URL env var, e.g. over the network, use this. #There is a corner case that the env is local but the user chose to ignore the GUI way (nsmd 1.6.0) to proivde us directly with a specific URL. self.nsmOSCUrl = envResult else: #This is the default case. User just starts the GUI. The other modes are concious decisions to either start with URL as parameter or in an NSM environment. #We need to test if the user accidentaly opened a second GUI, which would start a second server. self._setupAndTestForSingleInstance() #This might quit the whole program and we will never see the next line. #TODO: there is a corner case: 1) manually start nsmd locally 2) start agordejo first time and connect to running nsmd 3) start 2nd agordejo and connect to the same server. #it will not prevent it. But we need it because all other scenarios prevent agordejo multiple times for actual different servers, which is of course wanted. #Better let something uncorrect slip than to prevent legit usecases! #The important check here is to see if Agordejo, with internal nsmd, was started twice or not. Which works. That's all. self.nsmOSCUrl = self._generateFreeNsmOSCUrl() assert self.nsmOSCUrl self.internalState["serverPort"] = self.nsmOSCUrl[1] #only happens once, ports don't change during runtime self.internalState["nsmUrl"] = f"osc.udp://{self.nsmOSCUrl[0]}:{self.nsmOSCUrl[1]}/" #only happens once, ports don't change during runtime #Then check if a server is running there. If not start one. self.ourOwnServer = None #Might become a subprocess handle if self._isNsmdRunning(self.nsmOSCUrl): #serverport = self.nsmOSCUrl[1] #No further action required. GUI announce below this testing. pass else: self._startNsmdOurselves(sessionRoot, startupSession) #Session root can be a commandline parameter we forward to the server if we start it ourselves. startupSession is an autoloader. Both are usually None. assert type(self.ourOwnServer) is subprocess.Popen, (self.ourOwnServer, type(self.ourOwnServer)) #Wait for the server, or test if it is reacting. self._waitForPingResponseBlocking() logger.info("nsmd is ready @ {}".format(self.nsmOSCUrl)) #Tell nsmd that we are a GUI and want to receive general messages async, not only after we request something self.sessionRoot = self._initial_announce() #Triggers "hi" and session root self.internalState["sessionRoot"] = self.sessionRoot self._forceProcessOnceToEmptyQueue() #process any leftover messages. atexit.register(self.quit) #mostly does stuff when we started nsmd ourself #Activate hooks for api callbacks, now that we are finished here. #Otherwise the hooks will get called from our functions (e.g. new client) while we are still during init self.sessionOpenReadyHook = sessionOpenReadyHook #self.sessionAsDict(nsmSessionName) as parameter self.sessionOpenLoadingHook = sessionOpenLoadingHook #self.sessionAsDict(nsmSessionName) as parameter self.sessionClosedHook = sessionClosedHook #no parameter. This is also "choose a session" mode self.clientStatusHook = clientStatusHook #all client status is done via this single hook. GUIs need to check if they already know the client or not. self.dataClientNamesHook = dataClientNamesHook self.dataClientDescriptionHook = dataClientDescriptionHook self.dataClientTimelineMaximumDurationChangedHook = dataClientTimelineMaximumDurationChangedHook self.singleInstanceActivateWindowHook = singleInstanceActivateWindowHook #added to self.listenToAnotherInstanceAttempt() to listen for a message from another wannabe-instance self._receiverActive = True logger.info("nsmservercontrol init is complete. Ready for event loop") #Now an external event loop can add self.process #Internal Methods def _setupAndTestForSingleInstance(self): """ On program startup trigger this if there is already another instance of us running. This is before anything NSM was done. """ self.singleInstanceSocket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) logger.info("Testing if another non-specific Agordejo is running.") try: # This is for the first agordejo instance, when no other is running. We set up # a socket and listen throughout the runtime of our instance. # # Create an abstract socket, by prefixing it with null. # this relies on a feature only in linux, when current process quits, the # socket will be deleted. self.singleInstanceSocket.bind('\0' + "agordejo") self.singleInstanceSocket.listen(1) self.singleInstanceSocket.setblocking(False) logger.info("No other non-specific Agordejo found. Starting GUI") #Continue in self.listenToAnotherInstanceAttempt() return True except socket.error: # This is for the 2nd agordejo instance that has detected there is already another # instance running. We will exit here before anything related to NSM has happened. logger.error("GUI for this nsmd server already running. Informing the existing application to show itself.") self.singleInstanceSocket.connect('\0' + "agordejo") self.singleInstanceSocket.send("agordejoactivate".encode()); self.singleInstanceSocket.close() sysexit(1) #triggers atexit #print ("not executed") return False def listenToAnotherInstanceAttempt(self): """ This is for the agordejo instance that keeps running. We inform the GUI via hook to show info to the user that a 2nd instance wanted to start. Tests our unix socket for an incoming signal. if received forward to the engine->gui Must be added to an event loop, so will only start once the program has started. Can be added to a slower event loop, so it is not in self.process """ if self.singleInstanceSocket: try: connection, client_address = self.singleInstanceSocket.accept() #This blocks and waits for a message incoming = connection.recv(1024) if incoming and incoming == b"agordejoactivate": self.singleInstanceActivateWindowHook() except BlockingIOError: #happens while no data is received. Has nothing to do with blocking or not. In fact: this happens when in non-blocking mode. pass except socket.timeout: pass def _setPause(self, state:bool): """Set both the socket and the thread into waiting mode or not. With this we can wait for answers until we resume async operation This is a potential freeze/lock operation if we make a request to an external nsmd, which already closed. We will check if the socket is still open before going into blocking mode. """ if not self.ourOwnServer and not self._isNsmdRunning(self.nsmOSCUrl): #see docstring #Try to clean up try: self.sock.shutdown(2) except: pass try: self.sock.close() except: pass #This is a reason to let the program exit. logger.error("Wanted to do a blocking call but external nsmd found not running anymore. It probably closed or crashed. Will close our GUI now. There is no risk of data loss because we were just an attached GUI anyway.") sysexit() #return None switch to return None to let it crash and see the python traceback if state: self.sock.setblocking(True) #explicitly wait. self.sock.settimeout(0.5) self._receiverActive = False logger.info("Suspending receiving async mode.") else: self.sock.setblocking(False) self._receiverActive = True logger.info("Resuming receiving async mode.") def _forceProcessOnceToEmptyQueue(self): """Sometimes we want to make sure everything is processed until we continue. For example in our init. Initial usecase was connecting to a running nsmd with session. The api first callback to export sessions to the GUI was freezing because listSession was chocking on leftover messages from gui_announce to a running session, which sends the session name and a list of clients. The latter is not happening when starting the server ourselves, so we weren't expecting this. To be honest, this is really a patch to work around a design flaw and we hope this is a one-off corner case.""" logger.info("Force processing queue") #First gather all osc messages still in the pipe while True: try: data, addr = self.sock.recvfrom(1024) msg = _IncomingMessage(data) if msg: self._queue.append(msg) except BlockingIOError: #happens while no data is received. Has nothing to do with blocking or not. break except socket.timeout: break #Now process them all. This is different than normal self.process(). for msg in self._queue: if msg.oscpath in self.callbacks: self.callbacks[msg.oscpath](msg.params) else: logger.warning(f"Unhandled message with path {msg.oscpath} and parameters {msg.params}") self._queue.clear() logger.info("Ended force processing queue") def process(self): """Use this in an external event loop""" if self._receiverActive: while True: try: data, addr = self.sock.recvfrom(1024) msg = _IncomingMessage(data) if msg: self._queue.append(msg) except BlockingIOError: #happens while no data is received. Has nothing to do with blocking or not. #If we lose connection to nsmd (because it was killed remotely) this will stop as well. break except socket.timeout: #Try again next time. break for msg in self._queue: if msg.oscpath in self.callbacks: self.callbacks[msg.oscpath](msg.params) else: logger.warning(f"Unhandled message with path {msg.oscpath} and parameters {msg.params}") self._queue.clear() def _getNsmOSCUrlFromEnvironment(self): """Return the nsm osc url or None""" nsmOSCUrl = getenv("NSM_URL") if not nsmOSCUrl: return None else: #osc.udp://hostname:portnumber/ o = urlparse(nsmOSCUrl) return o.hostname, o.port def _generateFreeNsmOSCUrl(self): #Instead of reading out the NSM port we get a free port ourselves and set up nsmd with that tempServerSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) #internet, udp tempServerSock.bind(('', 0)) #pick a free port on localhost. address, tempServerSockPort = tempServerSock.getsockname() tempServerSock.close() #We need to close it because nsmd will open it right away. nsmOSCUrl = ("0.0.0.0", tempServerSockPort) #compatible to result of urlparse logger.info("Generated our own free NSM_URL to start a server @ {}".format(nsmOSCUrl)) return nsmOSCUrl def _isNsmdRunning(self, nsmOSCUrl): """Test if the port is open or not""" logger.info(f"Testing if a server is running @ {nsmOSCUrl}") hostname, port = nsmOSCUrl tempServerSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) #internet, udp try: tempServerSock.bind((hostname, port)) logger.info(f"No external nsmd found (we tested if port is closed) @ {nsmOSCUrl}") return False except: logger.info(f"External nsmd found (we tested if port is closed) @ {nsmOSCUrl}") return True finally: tempServerSock.close() def _startNsmdOurselves(self, sessionRoot:str, startupSession:str): assert self.nsmOSCUrl hostname, port = self.nsmOSCUrl arguments = ["nsmd","--osc-port", str(port)] if sessionRoot: arguments += ["--session-root", sessionRoot] if startupSession: logger.info(f"Got start-session as command line parameter. Fowarding to nsmd command line: {startupSession}") arguments += ["--load-session", startupSession] #nsmd allows all executables in $PATH. For technical reasons our GUI extends this PATH before we start the server. #This is a convenience service for fellow developers, that does not belong in the server control. #However, if you wonder why there are are more applications from unknown PATHs check qtgui/settings.py self.ourOwnServer = subprocess.Popen(arguments) def _blockingRequest(self, path:str, arguments:list, answerPath:str, answerArguments:list, repeat=False)->list: """During start-up we need to wait for replies. Also some operations only make sense if we got data back. This is an abstraction that deals with messages that may come out-of-order and keeps them for later, but at least prevents our side from sending messages out-of-order itself. Default is: send once, wait for answer. repeat=True sends multiple times until an answer arrives. Returns list of arguments, can be empty. """ assert not self._queue, [(m.oscpath, m.params) for m in self._queue] logger.info(f"[wait for answer]: Sending {path}: {arguments}") self._setPause(True) out_msg = _OutgoingMessage(path) for arg in arguments: out_msg.add_arg(arg) if not repeat: self.sock.sendto(out_msg.build(), self.nsmOSCUrl) #Wait for answer ready = False while not ready: if repeat: #we need to send multiple times. self.sock.sendto(out_msg.build(), self.nsmOSCUrl) try: data, addr = self.sock.recvfrom(1024) msg = _IncomingMessage(data) if answerArguments and msg.oscpath == answerPath and msg.params == answerArguments: result = msg.params logger.info(f"[wait from {path}] Received {answerPath}: {result}") ready = True elif msg.oscpath == answerPath: result = msg.params logger.info(f"[wait from {path}] Received {answerPath}: {result}") ready = True else: logger.warning(f"Waiting for {answerPath} from nsmd, but got: {msg.oscpath} with {msg.params}. Adding to queue for later.") self._queue.append(msg) except BlockingIOError: #happens while no data is received. Has nothing to do with blocking or not. continue except socket.timeout: continue self._setPause(False) return result def _waitForPingResponseBlocking(self): self._blockingRequest(path="/osc/ping", arguments=[], answerPath="/reply", answerArguments=["/osc/ping",], repeat=True) def _initial_announce(self)->pathlib.Path: """nsm/gui/gui_announce triggers a multi-stage reply. First we get "hi", then we get the session root. We wait for session root and then clean 'hi' from the queue. When we connect to a running nsmd we also receive /nsm/gui/session/name with the current session (or empty string for no current). If in a session we will receive a list of clients which ends the gui_announce stage. Returns session root as pathlib-path.""" resultArguments = self._blockingRequest(path="/nsm/gui/gui_announce", arguments=[], answerPath="/nsm/gui/session/root", answerArguments=[]) if len(self._queue) == 1 and self._queue[0].oscpath == "/nsm/gui/gui_announce" and self._queue[0].params == ["hi"]: logger.info("Got 'hi'. We are now the registered nsmd GUI as per our initial /nsm/gui/gui_announce") self._queue.clear() #this is safe because we tested above that there is exactly the hi message in the queue. else: logging.error(f"For ValueError below: {[(m.oscpath, m.params) for m in self._queue]}") raise ValueError("We were expecting a clean _queue with only 'hi' as leftover, but instead there were unhandled messages. see print above. Better abort than a wrong program state") #all ok return pathlib.Path(resultArguments[0]) #General Commands def send(self, arg): """ Intended for a text input / command line interface. Sends anything to nsmd, separated by semicolon. First part is the message address, the rest are string-parameters.""" args = arg.split() msg = _OutgoingMessage(args[0]) for p in args[1:]: msg.add_arg(p) self.sock.sendto(msg.build(), self.nsmOSCUrl) def gui_announce(self): """This is just the announce without any answer. This is a last-resort method if another GUI "stole" our slot. For our own initial announce we use self._initial_announce()""" msg = _OutgoingMessage("/nsm/gui/gui_announce") self.sock.sendto(msg.build(), self.nsmOSCUrl) def ping(self): msg = _OutgoingMessage("/osc/ping") self.sock.sendto(msg.build(), self.nsmOSCUrl) def list(self): msg = _OutgoingMessage("/nsm/server/list") self.sock.sendto(msg.build(), self.nsmOSCUrl) def _updateSessionListBlocking(self): """To ensure correct data on session operations we manage ourselves, like copy, rename and delete. Ask nsmd for projects in session root and update our internal state. This will return None without doing anything when we are already in a session. This will wait for an answer and block all other operations. First is /nsm/gui/server/message ['Listing sessions'] Then session names come one reply at a time such as /reply ['/nsm/server/list', 'test3'] Finally /nsm/server/list [0, 'Done.'] , not a reply """ #In the past we only regenerated if we are not in a session. However, that was overzealous. #Some GUI functions did not work. Better regenerate that list as often as we want. logger.info("Requesting project list from session server in blocking mode") self._setPause(True) msg = _OutgoingMessage("/nsm/server/list") self.sock.sendto(msg.build(), self.nsmOSCUrl) #Wait for /reply ready = False while True: try: data, addr = self.sock.recvfrom(1024) except socket.timeout: continue msg = _IncomingMessage(data) if not ready and msg.oscpath == "/nsm/gui/server/message" and msg.params == ["Listing sessions"]: self.internalState["sessions"].clear() # new clients are added as /reply /nsm/server/list callbacks ready = True else: if len(msg.params) != 2: logger.warning(f"Expected project but got path {msg.oscpath} with {msg.params}. Adding to queue for later.") self._queue.append(msg) continue #This is what we want: elif msg.oscpath == "/reply" and msg.params[0] == "/nsm/server/list": #/reply ['/nsm/server/list', 'test3'] for a real session or #/reply ['/nsm/server/list', ''] as "list ended" marker if msg.params[1]: self.internalState["sessions"].add(msg.params[1]) logger.info(f"Received session name: {msg.params[1]}") else: #empty string break elif msg.params[0] == 0 and msg.params[1] == "Done.": # legacy nsmd sent the wrong message. Fixed in new-session-manager june 2020 break else: logger.warning(f"Expected project but got path {msg.oscpath} with {msg.params}. Adding to queue for later.") self._queue.append(msg) continue self._setPause(False) def quit(self): """Called through atexit. Thanks to start.py sys exception hook this will also trigger on PyQt crash""" if self.ourOwnServer: msg = _OutgoingMessage("/nsm/server/quit") self.sock.sendto(msg.build(), self.nsmOSCUrl) returncode = self.ourOwnServer.wait() logger.info("Stopped our own server with return code {}".format(returncode)) def broadcast(self, path:str, arguments:list): """/nsm/server/broadcast s:path [arguments...] http://non.tuxfamily.org/nsm/API.html 1.2.7.1 /nsm/server/broadcast s:path [arguments...] /nsm/server/broadcast /tempomap/update "0,120,4/4:12351234,240,4/4" All clients except the sender recive: /tempomap/update "0,120,4/4:12351234,240,4/4" """ logger.info(f"Sending broadcast with path {path} and args {arguments}") message = _OutgoingMessage("/nsm/server/broadcast") message.add_arg(path) for arg in arguments: message.add_arg(arg) #type autodetect self.sock.sendto(message.build(), self.nsmOSCUrl) #Primarily Without Session def open(self, nsmSessionName:str): if nsmSessionName in self.internalState["sessions"]: msg = _OutgoingMessage("/nsm/server/open") msg.add_arg(nsmSessionName) #s:project_name self.sock.sendto(msg.build(), self.nsmOSCUrl) else: logger.warning(f"Session {nsmSessionName} not found. Not forwarding to nsmd.") def new(self, newName:str, startClients:list=[])->str: """Saves the current session and creates a new session. Only works if dir does not exist yet. """ basePath = pathlib.Path(self.sessionRoot, newName) if basePath.exists(): return None self._addToNextSession = startClients msg = _OutgoingMessage("/nsm/server/new") msg.add_arg(newName) #s:project_name self.sock.sendto(msg.build(), self.nsmOSCUrl) #Only with Current Session def save(self): msg = _OutgoingMessage("/nsm/server/save") self.sock.sendto(msg.build(), self.nsmOSCUrl) def close(self, blocking=False): if not blocking: msg = _OutgoingMessage("/nsm/server/close") self.sock.sendto(msg.build(), self.nsmOSCUrl) else: msg = _OutgoingMessage("/nsm/server/close") self.sock.sendto(msg.build(), self.nsmOSCUrl) #Drive the process loop ourselves. This will still trigger updates but the mainloop will wait. while self.internalState["currentSession"]: self.process() def abort(self, blocking=False): if not blocking: msg = _OutgoingMessage("/nsm/server/abort") self.sock.sendto(msg.build(), self.nsmOSCUrl) else: msg = _OutgoingMessage("/nsm/server/abort") self.sock.sendto(msg.build(), self.nsmOSCUrl) #Drive the process loop ourselves. This will still trigger updates but the mainloop will wait. while self.internalState["currentSession"]: self.process() def duplicate(self, newName:str)->str: """Saves the current session and creates a new session. Requires an open session and uses nsmd to do the work. If you want to do copy of any session use our owns self.sessionCopy""" msg = _OutgoingMessage("/nsm/server/duplicate") msg.add_arg(newName) #s:project_name self.sock.sendto(msg.build(), self.nsmOSCUrl) #Client Commands for Loaded Session def clientAdd(self, executableName:str): """Adds a client to the current session. executable must be in $PATH. We do not trust NSM to perform the right checks. It will add an empty path or wrong path. """ if not pathlib.Path(executableName).name == executableName: logger.warning(f"{executableName} must be just an executable file in your $PATH. We expected: {pathlib.Path(executableName).name} . We will not ask nsmd to add it as client") return False allPaths = getenv("PATH") assert allPaths, allPaths binaryPaths = allPaths.split(":") #TODO: There is a corner case that NSMD runs in a different $PATH environment. executableInPath = any(pathlib.Path(bp, executableName).is_file() for bp in binaryPaths) if executableInPath: msg = _OutgoingMessage("/nsm/server/add") msg.add_arg(executableName) #s:executable_name self.sock.sendto(msg.build(), self.nsmOSCUrl) return True else: logger.warning("Executable {} not found. We will not ask nsmd to add it as client".format(executableName)) return False def clientStop(self, clientId:str): msg = _OutgoingMessage("/nsm/gui/client/stop") msg.add_arg(clientId) #s:clientId self.sock.sendto(msg.build(), self.nsmOSCUrl) def clientResume(self, clientId:str): """Opposite of clientStop""" msg = _OutgoingMessage("/nsm/gui/client/resume") msg.add_arg(clientId) #s:clientId self.sock.sendto(msg.build(), self.nsmOSCUrl) def clientRemove(self, clientId:str): """Client needs to be stopped already. We will do that and wait for an answer. Remove from the session. Will not delete the save-files, but make them inaccesible. There is never a point in nsmservercontrol where self.internalState["clients"] is emptied. nsmd actually sends a clientRemove for every client at session stop. """ #We have a blocking operation in here so we need to be extra cautios that the client exists. if not clientId in self.internalState["clients"]: return False self.clientStop(clientId) #We need to wait for an answer. #Drive the process loop ourselves. This will still trigger updates but the mainloop will wait. logger.info(f"Waiting for {clientId} to be status 'stopped'") while not self.internalState["clients"][clientId]["lastStatus"] == "stopped": self.process() msg = _OutgoingMessage("/nsm/gui/client/remove") msg.add_arg(clientId) #s:clientId self.sock.sendto(msg.build(), self.nsmOSCUrl) #Flood lazy lagging nsmd until it removed the client. #We will receive a few -10 "No such client." errors but that is ok. while True: if not clientId in self.internalState["clients"]: break if self.internalState["clients"][clientId]["lastStatus"] == "removed": break self.sock.sendto(msg.build(), self.nsmOSCUrl) self.process() def clientSave(self, clientId:str): """Saves only the given client""" msg = _OutgoingMessage("/nsm/gui/client/save") msg.add_arg(clientId) #s:clientId self.sock.sendto(msg.build(), self.nsmOSCUrl) def clientHide(self, clientId:str): """Hides the client. Works only if client announced itself with this feature""" msg = _OutgoingMessage("/nsm/gui/client/hide_optional_gui") msg.add_arg(clientId) #s:clientId self.sock.sendto(msg.build(), self.nsmOSCUrl) def clientShow(self, clientId:str): """Shows the client. Works only if client announced itself with this feature""" msg = _OutgoingMessage("/nsm/gui/client/show_optional_gui") msg.add_arg(clientId) #s:clientId self.sock.sendto(msg.build(), self.nsmOSCUrl) #Callbacks def _reactCallback_guiAnnounce(self, parameters:list): """This should not happen, but let's keep it in in case of edge-case multi GUI scenarios""" assert parameters == ["hi"], parameters logger.info("We got an unexpected 'hi', as if requesting gui_announce. Our own initial GUI announce as received and processed silently earlier already.") def _reactCallback_error(self, parameters:list): logger.error(parameters) def _reactCallback_reply(self, parameters:list): """This is a difficult function because replies arrive for many unrelated things, like status. We do our best to send all replies on the right way""" success = False l = len(parameters) if l == 2: originalMessage, data = parameters logger.info(f"Got reply for {originalMessage} with {data}") reply = { "/nsm/server/list" : self._reactReply_nsmServerList, "/nsm/server/new" : self._reactReply_nsmServerNew, "/nsm/server/close" : self._reactReply_nsmServerClose, "/nsm/server/open" : self._reactReply_nsmServerOpen, "/nsm/server/save" : self._reactReply_nsmServerSave, "/nsm/server/abort" : self._reactReply_nsmServerAbort, "/nsm/server/duplicate" : self._reactReply_nsmServerDuplicate, "/nsm/server/add" : self._reactReply_nsmServerAdd, } if originalMessage in reply: reply[originalMessage](data) success = True elif l == 3: originalMessage, errorCode, answer = parameters logger.info(f"Got reply for {originalMessage} with code {errorCode} saying {answer}") if originalMessage == "/nsm/server/add": assert errorCode == 0, parameters self._reactReply_nsmServerAdd(answer) success = True elif l == 1: singleMessage = parameters[0] """For unknown reasons these replies do not repeat the originalMessage""" if singleMessage == "/osc/ping": logger.info(singleMessage) success = True elif singleMessage == "Client removed.": self._reactReply_nsmServerRemoved() success = True elif singleMessage == "Client stopped.": self._reactReply_nsmServerStopped() success = True #After all these reactions and checks the function will eventually return here. if not success: raise NotImplementedError(parameters) def _reactCallback_serverMessage(self, parameters:list): """Messages are normally harmless and uninteresting. Howerver, we need to use some of them for actual tasks. In opposite to reply and status this all go in our function for now, until refactoring""" if parameters == ["Listing session"]: #this feels bad! A simple message is not a reliable state token and could change in the future. #we cannot put that into our own /list outgoing message because other actions like "new" also trigger this callback self.internalState["sessions"].clear() # new clients are added as /reply /nsm/server/list callbacks if parameters[0].startswith("Opening session"): #This gets send only when an existing session starts loading. It will not trigger on new sessions, be it really new or duplicate. #e.g. /nsm/gui/server/message ["Opening session FOO"] nsmSessionName = parameters[0].replace("Opening session ", "") logger.info(f"Starting to load clients of session: {nsmSessionName}") self.sessionOpenLoadingHook(self.sessionAsDict(nsmSessionName)) #notify the api->UI else: logger.info("/nsm/gui/server/message " + repr(parameters)) def _reactCallback_broadcast(self, parameters:list): """We have nothing to do with broadcast. But we save them, so they can be shown on request parameters[0] is an osc path:str without naming constraints the rest is a list of arguments. Attention: a broadcast is not saved by the server. You either are in the session to receive it or you will miss it. If we run Agordejo as attached GUI (incl. --load-session) a broadcast after the session was loaded, where programs announce themselves to all other clients, will not be received here. Such is the case with our data-client. """ logger.info(f"Received broadcast. Saving in internal state: {parameters}") self.internalState["broadcasts"].append(parameters) #Our little trick. We know and like some clients better than others. #If we detect our own data-storage we remember our friends. #It is possible that another datastorage broadcasts, then we overwrite the URL. if parameters and parameters[0] == "/agordejo/datastorage/announce": path, clientId, messageSizeLimit, url = parameters assert "osc.udp" in url logger.info(f"Got announce from agordejo datastorage clientId {clientId} @ {url}") o = urlparse(url) self.dataStorage = DataStorage(self, clientId, messageSizeLimit, (o.hostname, o.port), self.sock) def _reactCallback_serverList(self, parameters:list): """This finalizes a new session list. Here we send new data to the GUI etc.""" l = len(parameters) if l == 2: errorCode, message = parameters assert errorCode == 0, errorCode assert message == "Done.", message #don't miss the dot after Done logger.info("/nsm/server/list is done and has transmitted all available sessions to us") else: raise NotImplementedError(parameters) def _reactCallback_activeSessionChanged(self, parameters:list): """We receive this trough /nsm/gui/session/name This is called when the session has already changed. This also happens when you connect to a headless nsmd with a running session. We expect two parameters: [session name, session path] both of which could be "". If we start nsmd ourselves into an empty state we expect session name to be empty Session path is the subdirectory relative to session root. The session root is not included. !The unqiue name is the session path, not the name! Shortly before we received /nsm/gui/session/session which indicates the attempt to create a new one, I guess! :) If you want to react to the attempt to open a session you need to use /nsm/gui/server/message ["Opening session FOO"] OR creating a new session, after which nsmd will open that session without a message. Empty string is "No session" or "Choose A Session" mode. """ l = len(parameters) if l == 2: nsmSessionName, sessionPath = parameters if not nsmSessionName and not sessionPath: #No session loaded. We are in session-choosing mode. logger.info("Session closed or never started. Choose-A-Session mode.") self.internalState["currentSession"] = None #sessionCloseHooked triggers rebuilding of the session list, which will not work when there is a current session. self.sessionClosedHook() else: #Session path is the subdirectory relative to session root. The session root is not included. sessionPath = sessionPath.lstrip("/") #we strip for technical reasons. logger.info(f"Current Session changed. We are now {nsmSessionName} in {sessionPath}") self.internalState["currentSession"] = sessionPath #This is after the session, received after all programs have loaded. #We have a counterpart-message reaction that signals the attempt to load. self.sessionOpenReadyHook(self.sessionAsDict(sessionPath)) #notify the api->UI for autoClientExecutableInPath in self._addToNextSession: self.clientAdd(autoClientExecutableInPath) self._addToNextSession = [] #reset elif l == 0: #Another way of "no session". self.internalState["currentSession"] = None #sessionCloseHooked triggers rebuilding of the session list, which will not work when there is a current session. self.sessionClosedHook() else: raise NotImplementedError(parameters) def _initializeEmptyClient(self, clientId:str): """NSM reuses signals. It is quite possible that this will be called multiple times, e.g. after opening a session. This is not a reaction callback, we call this ourselves only in _reactCallback_ClientNew """ #if not self.internalState["currentSession"]: # logger.warning(f"We received a clientNew for ID {clientId} but no session open was received." # "This would happen in an old nsmd version. If you see the GUI with an open session and a client list you can ignore this warning") if clientId in self.internalState["clients"]: return logger.info(f"Creating new internal entry for client {clientId}") client = { "clientId":clientId, #for convenience, included internally as well "dumbClient":True, #Bool. Real nsm or just any old program? status "Ready" switches this. "executable":None, #Every client announces to the GUI with the exectuable name. True nsm clients later overwrite with a pretty name which we save as "reportedName" "reportedName":None, #str . The reported name is first the executable name, for status started. But for NSM clients it gets replaced with a reported name. "label":None, #str "lastStatus":None, #str "statusHistory":[], #list "hasOptionalGUI": False, #bool "visible": None, # bool "dirty": None, # bool } self.internalState["clients"][clientId] = client def _setClientData(self, clientId:str, parameter:str, value): if clientId in self.internalState["clients"]: self.internalState["clients"][clientId][parameter] = value return True else: logger.warning(f"Client {clientId} not found in internal status storage. If the session was just closed this is most likely a known race condition. Everything is fine in this case.") return False def _reactCallback_ClientLabelChanged(self, parameters:list): """osc->add_method( "/nsm/gui/client/label", "ss", osc_handler, osc, "path,display_name" ); """ l = len(parameters) if l == 2: clientId, label = parameters logger.info(f"Label for client {clientId} changed to {label}") self._setClientData(clientId, "label", label) self.clientStatusHook(self.internalState["clients"][clientId]) else: raise NotImplementedError(parameters) def _reactCallback_clientPid(self, parameters:list): clientId, pid = parameters self._setClientData(clientId, "pid", pid) def _reactCallback_SessionSession(self, parameters:list): """This is received only when a new session gets created and followed by /nsm/gui/client/new and then a reply for /reply /nsm/server/new Session created""" #This is the counterpart to Message "Opening Session", but for really new or freshly duplicated session. logger.info(f"Attempt to create session: {parameters}") self.sessionOpenLoadingHook(self.sessionAsDict(parameters[0])) #notify the api->UI def _reactCallback_ClientNew(self, parameters:list): """/nsm/gui/client/new ['nBAVO', 'jackpatch'] This is both add client or open. The message comes twice. Once when you add a client, then parameters will contain the executable name. If the client reports itself as NSM compatible through announce we will also get the Open message through this function. Then the name changes from executableName to a reportedName, which will remain for the rest of the session. Executable name is still important to look up icons in the GUI. This message is usually followed by /nsm/gui/client/status """ l = len(parameters) if l == 2: clientId, name = parameters if not clientId in self.internalState["clients"]: self._initializeEmptyClient(clientId) self._setClientData(clientId, "executable", name) logger.info(f"Client started {name}:{clientId}") else: self._setClientData(clientId, "reportedName", name) logger.info(f"Client upgraded to NSM-compatible: {name}:{clientId}") self.clientStatusHook(self.internalState["clients"][clientId]) else: raise NotImplementedError(parameters) def _reactCallback_clientDirty(self, parameters:list): """/nsm/gui/client/dirty ['nMAJH', 1] """ l = len(parameters) if l == 2: clientId, dirty = parameters dirty = bool(dirty) self._setClientData(clientId, "dirty", dirty) logger.info(f"Client {clientId} save status dirty is now: {dirty}") self.clientStatusHook(self.internalState["clients"][clientId]) else: raise NotImplementedError(parameters) def _reactCallback_clientGuiVisible(self, parameters:list): """/nsm/gui/client/gui_visible ['nMAJH', 0] """ l = len(parameters) if l == 2: clientId, visible = parameters visible = bool(visible) self._setClientData(clientId, "visible", visible) logger.info(f"Client {clientId} visibility is now: {visible}") self.clientStatusHook(self.internalState["clients"][clientId]) else: raise NotImplementedError(parameters) def _reactCallback_clientHasOptionalGui(self, parameters:list): """/nsm/gui/client/has_optional_gui ['nFDBK'] nsmd sends us this as reaction to a clients announce capabilities list """ l = len(parameters) if l == 1: clientId = parameters[0] self._setClientData(clientId, "hasOptionalGUI", True) logger.info(f"Client {clientId} supports optional GUI") else: raise NotImplementedError(parameters) def _reactCallback_statusChanged(self, parameters:list): """ Handles all status messages. Some changes, like removed and quit, are only available as status. This means that status removed is the opposite of /nsm/gui/client/new, even if it doesn't read like it. /nsm/gui/client/status ['nFDBK', 'open'] /nsm/gui/client/status ['nMAJH', 'launch'] /nsm/gui/client/status ['nLUPX', 'ready'] /nsm/gui/client/status ['nLUPX', 'save'] /nsm/gui/client/status ['nFHLB', 'quit'] /nsm/gui/client/status ['nLUPX', 'removed'] /nsm/gui/client/status ['nLUPX', 'stopped'] /nsm/gui/client/status ['nLUPX', 'noop'] #For dumb clients! no nsm support! /nsm/gui/client/status ['nLUPX', 'switch'] /nsm/gui/client/status ['nLUPX', 'error'] """ l = len(parameters) if l == 2: clientId, status = parameters logger.info(f"Client status {clientId} now {status}") r = self._setClientData(clientId, "lastStatus", status) if r: #a known race condition at quit may delete this in between calls self.internalState["clients"][clientId]["statusHistory"].append(status) if status == "ready": #we need to check for this now. Below in actions is after the statusHook and too late. self._setClientData(clientId, "dumbClient", False) self.clientStatusHook(self.internalState["clients"][clientId]) else: raise NotImplementedError(parameters) #Now handle our actions. For better readability in separate functions. actions = { "open": self._reactStatus_open, "launch": self._reactStatus_launch, "ready": self._reactStatus_ready, "save": self._reactStatus_save, "quit": self._reactStatus_quit, "removed": self._reactStatus_removed, "stopped": self._reactStatus_stopped, "noop": self._reactStatus_noop, "switch": self._reactStatus_switch, "error": self._reactStatus_error, }[status](clientId) actions #pylint does not like temporary dicts for case-switch def _reactStatus_removed(self, clientId:str): """Remove the client entry from our internal state. This also covers crashes.""" if clientId in self.internalState["clients"]: #race condition at quit del self.internalState["clients"][clientId] if self.dataStorage and clientId == self.dataStorage.ourClientId: #We only care about the current data-storage, not another instance that was started before it. self.dataClientNamesHook(None) self.dataClientDescriptionHook(None) self.dataClientTimelineMaximumDurationChangedHook(None) self.dataStorage = None def _reactStatus_stopped(self, clientId:str): """The client has stopped and can be restarted. The status is not saved. NSM will try to open all clients on session open and end in "ready" """ if self.dataStorage and clientId == self.dataStorage.ourClientId: #We only care about the current data-storage, not another instance that was started before it. self.dataClientNamesHook(None) self.dataClientDescriptionHook(None) self.dataClientTimelineMaximumDurationChangedHook(None) self.dataStorage = None def _reactStatus_launch(self, clientId:str): """ Launch is a transitional status for NSM clients but the terminal status for dumb clients """ pass def _reactStatus_open(self, clientId:str): """ """ pass def _reactStatus_ready(self, clientId:str): """ This is sent after startup but also after every save. It signals that the client can react to nsm signals, not that it is ready for something else. Note that this is *After* the clientStatusHook, so any data changed here is not submitted to the api/GUI yet. E.g. you can't change dumbClient to True here if that is needed directly after start by the GUI. """ pass def _reactStatus_save(self, clientId:str): """ """ pass def _reactStatus_quit(self, clientId:str): """ """ pass def _reactStatus_noop(self, clientId:str): """ Dumb clients, or rather nsmd, react with noop on signals they cannot understand, like saving. """ pass def _reactStatus_switch(self, clientId:str): """ """ pass def _reactStatus_error(self, clientId:str): """ """ logger.error(f"{clientId} has error status!") def _reactReply_nsmServerOpen(self, answer:str): assert answer == "Loaded.", answer def _reactReply_nsmServerSave(self, answer:str): assert answer == "Saved.", answer def _reactReply_nsmServerClose(self, answer:str): assert answer == "Closed.", answer def _reactReply_nsmServerAbort(self, answer:str): assert answer == "Aborted.", answer def _reactReply_nsmServerAdd(self, answer:str): """Reaction to add client""" assert answer == "Launched.", answer def _reactReply_nsmServerRemoved(self): pass def _reactReply_nsmServerStopped(self): pass def _reactReply_nsmServerDuplicate(self, answer:str): """There are a lot of errors possible here, reported through nsmd /error, because we are dealing with the file system. Our own GUI and other safeguards should protect us from most though Positive answers are 'Duplicated.' when nsmd finished copying and 'Loaded.' when the new session is loaded. Or so one would think... the messages arrive the other way around. Anyway, both are needed to signify a succesful duplication. """ assert answer == "Loaded." or answer == "Duplicated.", answer #We don't need any callbacks here, nsmd sends a session change on top of the duplicate replies. def _reactReply_nsmServerNew(self, answer:str): """Created. arrives when a new session is created for the first time and directory is mkdir Session created arrives when a session was opened and nsm created its internal "session". We do not need to react to the new signal because we watch the dir for new sessions ourselves and the currently active session is send through "/nsm/gui/session/name" : self._reactCallback_activeSessionChanged, """ assert answer == 'Created.' or answer == "Session created", answer def _reactReply_nsmServerList(self, nsmSessionName:str): """Session names come one reply at a time. We reacted to the message /nsm/gui/server/message ['Listing sessions'] by clearing our internal session status and will save the new ones here /reply ['/nsm/server/list', 'test3'] Do not confuse reply server list with the message /nsm/server/list [0, 'Done.'] The latter is a top level message :( """ self.internalState["sessions"].add(nsmSessionName) #Our own functions def allClientsHide(self): for clientId, clientDict in self.internalState["clients"].items(): if clientDict["hasOptionalGUI"]: self.clientHide(clientId) def allClientsShow(self): for clientId, clientDict in self.internalState["clients"].items(): if clientDict["hasOptionalGUI"]: self.clientShow(clientId) def clientToggleVisible(self, clientId:str): if self.internalState["clients"][clientId]["hasOptionalGUI"]: if self.internalState["clients"][clientId]["visible"]: self.clientHide(clientId) else: self.clientShow(clientId) #data-storage / nsm-data def clientNameOverride(self, clientId:str, name:str): """An agordejo-specific function that requires the client nsm-data in the session. If nsm-data is not present this function will write nothing, not touch any data. It will still send a callback to revert any GUI changes back to the original name. We accept empty string as a name to remove the name override """ if self.dataStorage: assert clientId in self.internalState["clients"], self.internalState["clients"] self.dataStorage.setClientOverrideName(clientId, name) #triggers callback #data-storage / nsm-data def setDescription(self, text:str): if self.dataStorage: self.dataStorage.setDescription(text) #data-storage / nsm-data def setTimelineMaximumDuration(self, minutes:int): if self.dataStorage: self.dataStorage.setTimelineMaximumDuration(minutes) def _checkDirectoryForSymlinks(self, path)->bool: for p in path.rglob("*"): if p.is_symlink(): return True return False def _simple_hash(self, s:str) -> int: """This is a translation from the nsm/file.c function of the same name. We use it to find the lock file on our system. It is a djb2 hash modulo 65521 """ hashAddress = 5381 for i, char in enumerate(s): hashAddress = ((hashAddress << 5) + hashAddress ) + ord(char) hashAddress = c_ulong(hashAddress).value #wrap around for whatever number of bits unsinged long is on this system. 2**64 most likely return hashAddress % 65521 @lru_cache(maxsize=128) def _get_lock_file_name(self, session_name:str, full_absolute_session_path:str) -> pathlib.Path: """This is a translation from the nsm/nsmd.c function of the same name. We use it to find the lock file on our system. To avoid collisions of two simple session names under either different subdirs or even different session roots.""" #session_name in Agordejo includes subdirs. We want only the basename, like in nsmd. Luckily they are paths. session_name = pathlib.Path(session_name).name #basename session_hash:int = self._simple_hash(full_absolute_session_path) session_lock = pathlib.Path(self.sessions_lockfile_directory, "nsm", session_name + str(session_hash)) return session_lock def _checkIfLocked(self, nsmSessionName:str)->bool: #basePath = pathlib.Path(self.sessionRoot, nsmSessionName) #assert basePath.exists() #lockFile = pathlib.Path(basePath, ".lock") lockFile = self._get_lock_file_name(nsmSessionName, str(pathlib.Path(self.sessionRoot, nsmSessionName))) return lockFile.exists() def getSessionFiles(self, nsmSessionName:str)->list: """Return all session files, useful to present to the user, e.g. as warning before deletion""" self._updateSessionListBlocking() basePath = pathlib.Path(self.sessionRoot, nsmSessionName) assert basePath.exists() return [f.as_posix() for f in basePath.rglob("*")] #Includes directories themselves #Only files, no directories themselves. #result = [] #for path, dirs, files in walk(basePath): # for file in files: # result.append(pathlib.Path(path, file).as_posix()) #return result def deleteSession(self, nsmSessionName:str): """Delete project directory with all data. No undo. Only if session is not locked""" self._updateSessionListBlocking() if not nsmSessionName in self.internalState["sessions"]: logger.warning(f"{nsmSessionName} is not a session") return False basePath = pathlib.Path(self.sessionRoot, nsmSessionName) assert basePath.exists() if not self._checkIfLocked(nsmSessionName): try: logger.info(f"Deleting session {nsmSessionName}: {self.getSessionFiles(nsmSessionName)}") shutilrmtree(basePath) except PermissionError: logger.warning(f"Tried to delete {basePath} but permission was denied.") else: logger.warning(f"Tried to delete {basePath} but it is locked") self._updateSessionListBlocking() #if we don't update our internal representation the watchdog will go mad. def renameSession(self, nsmSessionName:str, newName:str): """Only works if session is not locked and dir does not exist yet""" self._updateSessionListBlocking() newPath = pathlib.Path(self.sessionRoot, newName) oldPath = pathlib.Path(self.sessionRoot, nsmSessionName) assert oldPath.exists() if self._checkIfLocked(nsmSessionName): logger.warning(f"Can't rename {nsmSessionName} to {newName}. {nsmSessionName} is locked.") return False elif newPath.exists(): logger.warning(f"Can't rename {nsmSessionName} to {newName}. {newName} already exists.") return False else: logger.info(f"Renaming {nsmSessionName} to {newName}.") tmp = pathlib.Path(oldPath.name+str(uuid4())) #Can't move itself into a subdir in itself. move to temp first. We don't use tempdir because that could be on another partition. we already know we can write here. oldPath.rename(tmp) pathlib.Path(newPath).mkdir(parents=True, exist_ok=True) tmp.rename(newPath) assert newPath.exists() def copySession(self, nsmSessionName:str, newName:str, progressHook=None): """Copy a whole tree. Keep symlinks as symlinks. Lift lock. If progressHook is provided (e.g. by a GUI) it will be called at regular intervals to inform of the copy process, or at least that it is still running. """ self._updateSessionListBlocking() source = pathlib.Path(self.sessionRoot, nsmSessionName) destination = pathlib.Path(self.sessionRoot, newName) if destination.exists(): logger.warning(f"Can't copy {nsmSessionName} to {newName}. {newName} already exists.") return False elif not nsmSessionName in self.internalState["sessions"]: logger.warning(f"{nsmSessionName} is not a session") return elif not source.exists(): logger.warning(f"Can't copy {nsmSessionName} because it does not exist.") return False #All is well. try: def mycopy(): shutilcopytree(source, destination, symlinks=True, dirs_exist_ok=False) #raises an error if dir already exists. But we already test above. if progressHook: def waiter(copyProcess): """Compare the final size with the current size and generate a percentage from it, which we send as progress""" sourceDirectorySize = sum(f.stat().st_size for f in source.glob('**/*') if f.is_file()) - 2048 #padded so we don't create an infinite loop from a rounding error destinationDirectorySize = sum(f.stat().st_size for f in destination.glob('**/*') if f.is_file()) #destinationDirectorySize does not start at 0. the copy() function might already by running before waiter() starts. while destinationDirectorySize < sourceDirectorySize: if not copyProcess.is_alive(): break percentString = str( int((destinationDirectorySize / sourceDirectorySize) * 100)) + "%" progressHook(percentString) sleep(0.5) #don't send too much. two times a second is plenty. #For next round destinationDirectorySize = sum(f.stat().st_size for f in destination.glob('**/*') if f.is_file()) """ #This moves both processes away from the main thread. It works, but Qt will not update anymore #We need a way to just spawn one extra process and wait/processHook in the main process processes = [] for function in (waiter, mycopy): proc = Process(target=function) proc.start() processes.append(proc) for proc in processes: proc.join() """ proc = Process(target=mycopy) proc.start() waiter(proc) #has the while loop to wait and check proc proc.join() #finish #Do a check if both dirs are equal progressHook("Veryfying file-integrity. This may take a while...") #string gets translated in qt gui mainwindow. Don't change just this here. sourceHash = md5_dir(source) desinationHash = md5_dir(destination) if not sourceHash == desinationHash: logger.error("ERROR! Copied session data is different from source session. Please check you data!") progressHook("ERROR! Copied session data is different from source session. Please check you data!") #ERROR! is a keyword for the gui wait dialog to not switch away. This gets translated in the Qt GUI mainwindow. Don't change this string else: mycopy() except Exception as e: #we don't want to crash if user tries to copy to /root or so. logger.error(e) return False #Export to the User Interface def sessionAsDict(self, nsmSessionName:str)->dict: assert self.sessionRoot entry = {} entry["nsmSessionName"] = nsmSessionName entry["name"] = pathlib.Path(nsmSessionName).name basePath = pathlib.Path(self.sessionRoot, nsmSessionName) sessionFile = pathlib.Path(basePath, "session.nsm") if not sessionFile.exists(): #This is a reason to let the program exit. logger.error("Got wrong session directory from nsmd. Race condition after delete? In any case a breaking error (please report). Quitting. Project was: " + repr(sessionFile)) sysexit() #return None switch to return None to let it crash and see the python traceback timestamp = datetime.fromtimestamp(sessionFile.stat().st_mtime).isoformat(sep=" ", timespec='minutes') entry["lastSavedDate"] = timestamp entry["sessionFile"] = sessionFile entry["lockFile"] = pathlib.Path(basePath, ".lock") entry["fullPath"] = str(basePath) #No generator expression for the next one. We need to watch out for PermissionError (sudo chmod 000) sizeInBytes = 0 for f in basePath.glob('**/*'): try: if f.exists() and f.is_file(): sizeInBytes += f.stat().st_size except PermissionError: logger.error(f"PermissionError for {f}. It is possible that the file is read-protected. Trying to load the session anyway, please be careful.") entry["sizeInBytes"] = sizeInBytes entry["numberOfClients"] = len(open(sessionFile).readlines()) entry["hasSymlinks"] = self._checkDirectoryForSymlinks(basePath) entry["parents"] = basePath.relative_to(self.sessionRoot).parts[:-1] #tuple of each dir between NSM root and nsmSessionName/session.nsm, exluding the actual project name. This is the tree entry["locked"] = self._checkIfLocked(nsmSessionName) #not for direct display return entry def exportSessionsAsDicts(self)->list: """Return a list of dicts of projects with additional information: """ logger.info("Exporting sessions to dict. Will call blocking list sessions next") results = [] #assert not self.internalState["currentSession"], self.internalState["currentSession"] #Do not request session list while in active session self._updateSessionListBlocking() for nsmSessionName in self.internalState["sessions"]: result = self.sessionAsDict(nsmSessionName) results.append(result) return results class DataStorage(object): """Interface to handle the external datastorage client url is pre-processed (host, port) Our init is the same as announcing the nsm-data client in the session. That means everytime nsm-data sends a new/open reply we get created. Thus we will send all our data to parent and subsequently to GUI-callbacks in init. Keys are strings, While nsmd OSC support int, str and float we use json exclusively. We send json string and parse the received data. Try to use only ints, floats, strin gs, lists and dicts. Client pretty names are limited to 512 chars, depending on our OSC message size. nsm-data will just cut to 512 chars. So a GUI should better protect that limit. """ def __init__(self, parent, ourClientId, messageSizeLimit:int, url:tuple, sock): logger.info("Create new DataStorage instance") self.parent = parent self.messageSizeLimit = messageSizeLimit # e.g. 512 self.ourClientId = ourClientId self.clients = parent.internalState["clients"] #shortcut. Mutable, persistent dict, until instance gets deleted. self.url = url self.sock = sock self.ip, self.port = self.sock.getsockname() #Get initial data. Directly send to the api->GUI. self.data = self.getAll() #blocks. our local copy. = {"clientOverrideNames":{clientId:nameOverride}, "description":"str", "timelineMaximumDuration":"minutes in int"} self.namesToParentAndCallbacks() self.descriptionToParentAndCallbacks() self.timelineMaximumDurationToParentAndCallbacks() def namesToParentAndCallbacks(self): self.parent.dataClientNamesHook(self.data["clientOverrideNames"]) def descriptionToParentAndCallbacks(self): """Every char!!!""" self.parent.dataClientDescriptionHook(self.data["description"]) def timelineMaximumDurationToParentAndCallbacks(self): self.parent.dataClientTimelineMaximumDurationChangedHook(self.data["timelineMaximumDuration"]) def _waitForMultipartMessage(self, pOscpath:str)->str: """Returns a json string, as if the message was sent as a single one. Can consist of only one part as well.""" logger.info(f"Waiting for multi message {pOscpath} in blocking mode") self.parent._setPause(True) jsonString = "" chunkNumberOfParts = float("+inf") #zero based currentPartNumber = float("-inf") #zero based while True: if currentPartNumber >= chunkNumberOfParts: break try: data, addr = self.sock.recvfrom(1024) except socket.timeout: break msg = _IncomingMessage(data) if msg.oscpath == pOscpath: currentPartNumber, l, jsonChunk = msg.params jsonString += jsonChunk chunkNumberOfParts = l #overwrite infinity the first time and redundant afterwards. else: self.parent._queue.append(msg) self.parent._setPause(False) logger.info(f"Message complete with {chunkNumberOfParts} chunks.") return jsonString def getAll(self): """Mirror everything from nsm-data""" msg = _OutgoingMessage("/agordejo/datastorage/getall") msg.add_arg(self.ip) msg.add_arg(self.port) self.sock.sendto(msg.build(), self.url) jsonString = self._waitForMultipartMessage("/agordejo/datastorage/reply/getall") return json.loads(jsonString) def setTimelineMaximumDuration(self, minutes:int): msg = _OutgoingMessage("/agordejo/datastorage/settimelinemaximum") msg.add_arg(json.dumps(minutes)) self.sock.sendto(msg.build(), self.url) self.getTimelineMaximumDuration() def getTimelineMaximumDuration(self): msg = _OutgoingMessage("/agordejo/datastorage/gettimelinemaximum") msg.add_arg(self.ip) msg.add_arg(self.port) self.sock.sendto(msg.build(), self.url) #Wait in blocking mode self.parent._setPause(True) while True: try: data, addr = self.sock.recvfrom(1024) except socket.timeout: break msg = _IncomingMessage(data) if msg.oscpath == "/agordejo/datastorage/reply/gettimelinemaximum": jsonMinutes = msg.params[0] #list of one answerMinutes = json.loads(jsonMinutes) break else: self.parent._queue.append(msg) self.parent._setPause(False) #Got answer assert type(answerMinutes) is int, (answerMinutes, type(answerMinutes)) self.data["timelineMaximumDuration"] = answerMinutes self.timelineMaximumDurationToParentAndCallbacks() def setClientOverrideName(self, clientId:str, value): """We accept empty string as a name to remove the name override""" assert clientId in self.clients, self.clients msg = _OutgoingMessage("/agordejo/datastorage/setclientoverridename") msg.add_arg(clientId) msg.add_arg(json.dumps(value)) self.sock.sendto(msg.build(), self.url) self.getClientOverrideName(clientId) #verifies data and triggers callback def getClientOverrideName(self, clientId:str): msg = _OutgoingMessage("/agordejo/datastorage/getclientoverridename") msg.add_arg(clientId) msg.add_arg(self.ip) msg.add_arg(self.port) self.sock.sendto(msg.build(), self.url) #Wait in blocking mode self.parent._setPause(True) while True: try: data, addr = self.sock.recvfrom(1024) except socket.timeout: break msg = _IncomingMessage(data) if msg.oscpath == "/agordejo/datastorage/reply/getclient": replyClientId, jsonName = msg.params assert replyClientId == clientId, (replyClientId, clientId) break else: self.parent._queue.append(msg) self.parent._setPause(False) #Got answer answer = json.loads(jsonName) if answer: self.data["clientOverrideNames"][clientId] = answer else: #It is possible that a client not present in our storage will send an empty string. Protect. if clientId in self.data["clientOverrideNames"]: del self.data["clientOverrideNames"][clientId] self.namesToParentAndCallbacks() def _chunkstring(self, string): return [string[0+i:self.messageSizeLimit+i] for i in range(0, len(string), self.messageSizeLimit)] def setDescription(self, text:str): """This most likely arrives one char at time with the complete text""" chunks = self._chunkstring(text) descriptionId = str(id(text))[:8] for index, chunk in enumerate(chunks): msg = _OutgoingMessage("/agordejo/datastorage/setdescription") msg.add_arg(descriptionId) msg.add_arg(index) msg.add_arg(chunk) msg.add_arg(self.ip) msg.add_arg(self.port) self.sock.sendto(msg.build(), self.url) #No echo answer. #We cheat a bit and inform parents with the new text directly. self.data["description"] = text self.descriptionToParentAndCallbacks() #and back #Generic Functions. Not in use and not ready. def _test(self): self.readAll() self.setDescription("Ein Jäger aus Kurpfalz,\nDer reitet durch den grünen Wald,\nEr schießt das Wild daher,\nGleich wie es ihm gefällt.") self.read("welt") self.create("welt", "world") self.read("welt") self.create("str", "bar") self.create("int", 1) self.create("list", [1, 2, 3]) self.create("tuple", (1, 2, 3)) #no tuples, everything will be a list. self.create("dict", {1:2, 3:4, 5:6}) self.update("str", "rolf") self.delete("str") def read(self, key:str): """Request one value""" msg = _OutgoingMessage("/agordejo/datastorage/read") msg.add_arg(key) msg.add_arg(self.ip) msg.add_arg(self.port) self.sock.sendto(msg.build(), self.url) def readAll(self): """Request all data""" msg = _OutgoingMessage("/agordejo/datastorage/readall") msg.add_arg(self.ip) msg.add_arg(self.port) self.sock.sendto(msg.build(), self.url) def create(self, key:str, value): """Write/Create one value.""" msg = _OutgoingMessage("/agordejo/datastorage/create") msg.add_arg(key) msg.add_arg(json.dumps(value)) self.sock.sendto(msg.build(), self.url) def update(self, key:str, value): """Update a value, but only if it exists""" msg = _OutgoingMessage("/agordejo/datastorage/update") msg.add_arg(key) msg.add_arg(json.dumps(value)) self.sock.sendto(msg.build(), self.url) def delete(self, key:str): """Delete a key/value completely""" msg = _OutgoingMessage("/agordejo/datastorage/delete") msg.add_arg(key) self.sock.sendto(msg.build(), self.url) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1665611335.9505882 agordejo-0.4.2/engine/start.py0000644000175000017500000002711414321633110015016 0ustar00nilsnils#! /usr/bin/env python3 # -*- coding: utf-8 -*- """ Copyright 2022, Nils Hilbricht, Germany ( https://www.hilbricht.net ) This file is part of the Laborejo Software Suite ( https://www.laborejo.org ), This application is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . """ #This is the first file in the program to be actually executed, after the executable which uses this as first instruction. """ We use a 'wrong' scheme of importing modules here because there are multiple exit conditions, good and bad. We don't want to use all the libraries, including the big Qt one, only to end up displaying the --version and exit. Same with the tests if jack or nsm are running. """ #Give at least some feedback when C libs crash. #Will still not work for the common case that PyQt crashes and ends Python. #But every bit helps when hunting bugs. import faulthandler; faulthandler.enable() from engine.config import * #includes METADATA only. No other environmental setup is executed. from qtgui.helper import setPaletteAndFont #our error boxes shall look like the rest of the program """ Check parameters first. It is possible that we will just --help or --version and exit. In this case nothing gets loaded. """ import os.path import argparse parser = argparse.ArgumentParser(description=f"""{METADATA["name"]} - Version {METADATA["version"]} - Copyright {METADATA["year"]} by {METADATA["author"]} - {METADATA["url"]}""") parser.add_argument("-v", "--version", action='version', version="{} {}".format(METADATA["name"], METADATA["version"])) parser.add_argument("-V", "--verbose", action='store_true', help="(Development) Switch the logger to INFO and print out all kinds of information to get a high-level idea of what the program is doing. You can also set the environment variable LSS_DEBUG=1") parser.add_argument("-u", "--url", action='store', dest="url", help="Force URL for the session. If there is already a running session we will connect to it. Otherwise we will start one there. Default is local host with random port. Example: osc.udp://myhost.localdomain:14294/") parser.add_argument("--nsm-url", action='store', dest="url", help="Same as --url.") parser.add_argument("-l", "--load-session", action='store', dest="session", help="Session to open on startup, must exist. Overrides --continue") parser.add_argument("-c", "--continue", action='store_true', dest="continueLastSession", help="Autostart last active session.") parser.add_argument("-i", "--hide", action='store_true', dest="starthidden", help="Start GUI hidden in tray, only if tray available on system.") parser.add_argument("--session-root", action='store', dest="sessionRoot", help="Root directory of all sessions. Defaults to $XDG_DATA_HOME/nsm/") args = parser.parse_args() #Check for an alternative way to enable the logger. import os if args.verbose: os.environ["LSS_DEBUG"] = "1" #for children programs. elif not args.verbose and os.getenv("LSS_DEBUG"): args.verbose = True import logging if args.verbose: #development logging.basicConfig(level=logging.INFO, format='[' + METADATA["shortName"] + '] %(levelname)s %(asctime)s %(name)s: %(message)s',) #logging.getLogger().setLevel(logging.INFO) #development else: #production logging.basicConfig(level=logging.ERROR, format='[' + METADATA["shortName"] + '] %(levelname)s %(asctime)s %(name)s: %(message)s',) #logging.getLogger().setLevel(logging.ERROR) #production logger = logging.getLogger(__name__) logger.info("import") """set up python search path before the program starts We need to be earliest, so let's put it here. This is influence during compiling by creating a temporary file "compiledprefix.py". Nuitka complies that in, when make is finished we delete it. #Default mode is a self-contained directory relative to the uncompiled patroneo python start script """ import sys import os import os.path from PyQt5.QtWidgets import QApplication, QStyleFactory logger.info(f"Python Version {sys.version}") try: from compiledprefix import prefix compiledVersion = True logger.info("Compiled prefix found: {}".format(prefix)) except ModuleNotFoundError as e: compiledVersion = False logger.info("Compiled version: {}".format(compiledVersion)) if compiledVersion: PATHS={ #this gets imported "root": "", "bin": os.path.join(prefix, "bin"), "doc": os.path.join(prefix, "share", "doc", METADATA["shortName"]), "desktopfile": os.path.join(prefix, "share", "applications", METADATA["shortName"] + ".desktop"), #not ~/Desktop but our desktop file "share": os.path.join(prefix, "share", METADATA["shortName"]), "templateShare": os.path.join(prefix, "share", METADATA["shortName"], "template"), "sessionRoot": args.sessionRoot, "url": args.url, "startupSession": args.session, "startHidden": args.starthidden, #bool "continueLastSession": args.continueLastSession, #bool } _root = os.path.dirname(__file__) _root = os.path.abspath(os.path.join(_root, "..")) else: _root = os.path.dirname(__file__) _root = os.path.abspath(os.path.join(_root, "..")) PATHS={ #this gets imported "root": _root, "bin": _root, "doc": os.path.join(_root, "documentation", "out"), "desktopfile": os.path.join(_root, "desktop", "desktop.desktop"), #not ~/Desktop but our desktop file "share": os.path.join(_root, "engine", "resources"), "templateShare": os.path.join(_root, "template", "engine", "resources"), "sessionRoot": args.sessionRoot, "url": args.url, "startupSession": args.session, "startHidden": args.starthidden, #bool "continueLastSession": args.continueLastSession, #bool } if PATHS["startupSession"]: logger.warning("--continue ignored because --load-session was used.") PATHS["continueLastSession"] = None #just in case. See --help string logger.info("PATHS: {}".format(PATHS)) #Construct QAppliction before constantsAndCOnfigs, which has the fontDB #QApplication.setDesktopSettingsAware(False) #We need our own font so the user interface stays predictable QApplication.setDesktopFileName(PATHS["desktopfile"]) qtApp = QApplication(sys.argv) setPaletteAndFont(qtApp) QApplication.setStyle(QStyleFactory.create("Fusion")) setPaletteAndFont(qtApp) def exitWithMessage(message:str): title = f"""{METADATA["name"]} Error""" if sys.stdout.isatty(): sys.exit(title + ": " + message) else: from PyQt5.QtWidgets import QMessageBox #This is the start file for the Qt client so we know at least that Qt is installed and use that for a warning. QMessageBox.critical(qtApp.desktop(), title, message) sys.exit(title + ": " + message) def setProcessName(executableName): """From https://stackoverflow.com/questions/31132342/change-process-name-while-executing-a-python-script """ import ctypes, ctypes.util lib = ctypes.cdll.LoadLibrary(None) prctl = lib.prctl prctl.restype = ctypes.c_int prctl.argtypes = [ctypes.c_int, ctypes.c_char_p, ctypes.c_ulong, ctypes.c_ulong, ctypes.c_ulong] def set_proctitle(new_title): result = prctl(15, new_title, 0, 0, 0) if result != 0: raise OSError("prctl result: %d" % result) set_proctitle(executableName.encode()) libpthread_path = ctypes.util.find_library("pthread") if not libpthread_path: return libpthread = ctypes.CDLL(libpthread_path) if hasattr(libpthread, "pthread_setname_np"): _pthread_setname_np = libpthread.pthread_setname_np _pthread_self = libpthread.pthread_self _pthread_self.argtypes = [] _pthread_self.restype = ctypes.c_void_p _pthread_setname_np.argtypes = [ctypes.c_void_p, ctypes.c_char_p] _pthread_setname_np.restype = ctypes.c_int if _pthread_setname_np is None: return _pthread_setname_np(_pthread_self(), executableName.encode()) def _is_jack_running(): """Check for JACK""" import ctypes import os if not args.verbose: silent = os.open(os.devnull, os.O_WRONLY) stdout = os.dup(1) stderr = os.dup(2) os.dup2(silent, 1) #stdout os.dup2(silent, 2) #stderr cjack = ctypes.cdll.LoadLibrary("libjack.so.0") class jack_client_t(ctypes.Structure): _fields_ = [] cjack.jack_client_open.argtypes = [ctypes.c_char_p, ctypes.c_int, ctypes.POINTER(ctypes.c_int)] #the two ints are enum and pointer to enum. #http://jackaudio.org/files/docs/html/group__ClientFunctions.html#gab8b16ee616207532d0585d04a0bd1d60 cjack.jack_client_open.restype = ctypes.POINTER(jack_client_t) ctypesJackClient = cjack.jack_client_open("probe".encode("ascii"), 0x01, None) #0x01 is the bit set for do not autostart JackNoStartServer try: ret = bool(ctypesJackClient.contents) except ValueError: #NULL pointer access ret = False cjack.jack_client_close(ctypesJackClient) if not args.verbose: os.dup2(stdout, 1) #stdout os.dup2(stderr, 2) #stderr return ret def checkJackOrExit(prettyName): import sys if not _is_jack_running(): exitWithMessage("JACK Audio Connection Kit is not running. Please start it.") def isAnotherAgordejoInstanceRunning()->bool: import socket tempSocket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) try: # This is for the first agordejo instance, when no other is running. We set up # a socket and listen throughout the runtime of our instance. # # Create an abstract socket, by prefixing it with null. # this relies on a feature only in linux, when current process quits, the # socket will be deleted. tempSocket.bind('\0' + "agordejo") tempSocket.listen(1) tempSocket.setblocking(False) return False except socket.error: # This is for the 2nd agordejo instance that has detected there is already another # instance running. We will exit here before anything related to NSM has happened. tempSocket.connect('\0' + "agordejo") tempSocket.send("agordejoactivate".encode()); tempSocket.close() return True def checkAgordejoOrExit(): if (not args.url) and isAnotherAgordejoInstanceRunning(): exitWithMessage("Another Agordejo instance is already running. Informing it of our start attempt.") checkAgordejoOrExit() checkJackOrExit(METADATA["name"]) try: #Only cosmetics setProcessName(METADATA["shortName"]) except: pass #Capture Ctlr+C / SIGINT and let @atexit handle the rest. import signal import sys def signal_handler(sig, frame): sys.exit(0) #atexit will trigger signal.signal(signal.SIGINT, signal_handler) #Catch Exceptions even if PyQt crashes. import sys sys._excepthook = sys.excepthook def exception_hook(exctype, value, traceback): """This hook purely exists to call sys.exit(1) even on a Qt crash so that atexit gets triggered""" #print(exctype, value, traceback) logger.error("Caught crash in execpthook. Trying too execute atexit anyway") sys._excepthook(exctype, value, traceback) sys.exit(1) sys.excepthook = exception_hook ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1665611335.9505882 agordejo-0.4.2/engine/watcher.py0000644000175000017500000001712414321633110015316 0ustar00nilsnils#! /usr/bi n/env python3 # -*- coding: utf-8 -*- """ Copyright 2022, Nils Hilbricht, Germany ( https://www.hilbricht.net ) This file is part of the Laborejo Software Suite ( https://www.laborejo.org ), This application is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . """ import logging; logger = logging.getLogger(__name__); logger.info("import") #Standard Library from datetime import datetime import pathlib import os #Our Modules from engine.start import PATHS def fast_scandir(dir): """ Get all subdirectories recursively. https://stackoverflow.com/questions/973473/getting-a-list-of-all-subdirectories-in-the-current-directory""" try: subfolders= [f.path for f in os.scandir(dir) if f.is_dir()] except PermissionError: subfolders = [] for dir in list(subfolders): subfolders.extend(fast_scandir(dir)) return subfolders class Watcher(object): """ Initialize with the server controller The watcher will should only run when the program is in the mode to choose a session. If a session is loaded it will technically work, but will be a waste of resources. The watcher will also trigger the nsmController to redundantly query for a session list, for example when a new empty sessions gets created or duplicated. Once because nsmd sends the "new" signal, and then our watcher will notice the change itself. However, this happens only so often and can be accepted, so the program does not need more checks and exceptions. Inform you via callback when: a) a session dir was deleted b) a new directory got created. c) session directory renamed or moved d) session.nsm changed timestamp e) A lockfile appeared or disappeared We cannot poll nsmServerControl.exportSessionsAsDicts() because that triggers an nsmd response including log message and will flood their console. Instead this class only offers incremental updates. Another way is to watch the dir for changes ourselves in in some cases request a new project list from nsmd. Lockfile functionality goes beyond what NSM offers. A session-daemon can open only one project at a time. If you try to open another project with a second GUI (but same NSM-URL) it goes wrong a bit, at least in the new-session-manager GUI. They will leave a zombie lockfile. However, it still is possible to open a second nsmd instance. In this case the lockfile prevents opening a session twice. And we are reflecting the opened state of the project, no matter from which daemon. Horray for us :) Clients can only be added or removed while a session is locked. We do not check nor update number of clients or symlinks. Therefore we advice a GUI to deactivate the display of these two values while a session is locked (together with size) """ def __init__(self, nsmServerControl): self.active = True self._nsmServerControl = nsmServerControl assert self._nsmServerControl.sessionRoot self._directories = fast_scandir(self._nsmServerControl.sessionRoot) logger.info("Requestion our own copy of the session list. Don't worry about the apparent redundant call :)") self._lastExport = self._nsmServerControl.exportSessionsAsDicts() #list of dicts self._lastTimestamp = {d["nsmSessionName"]:d["lastSavedDate"] for d in self._lastExport} #str:str #Init all values with None will send the initial state via callback on program start, which is what the user wants to know. self._lastLockfile = {d["nsmSessionName"]:None for d in self._lastExport} #str:bool self.timeStampHook = None # a single function that gets informed of changes, most likely the api callback self.lockFileHook = None # a single function that gets informed of changes, most likely the api callback self.sessionsChangedHook = None # the api callback function api.callbacks._sessionsChanged. Rarely used. def resume(self, *args): """For api callbacks""" self.active = True #If we returned from an open session that will surely have changed. Trigger a single poll self.process() logger.info("Watcher resumed") def suspend(self, *args): """For api callbacks""" self.active = False logger.info("Watcher suspended") def _update(self): current_directories = fast_scandir(self._nsmServerControl.sessionRoot) if not self._directories == current_directories: self._directories = current_directories self._lastExport = self.sessionsChangedHook() #will gather its own data, send it to api callbacks, but also return for us. self._lastTimestamp = {d["nsmSessionName"]:d["lastSavedDate"] for d in self._lastExport} #str:str self._lastLockfile = {d["nsmSessionName"]:None for d in self._lastExport} #str:bool def process(self): """Add this to your event loop. We look for any changes in the directory structure. If we detect any we simply trigger a new NSM export and a new NSM generated project list via callback. We do not expect this to happen often. This will also trigger if we add a new session ourselves. This *is* our way to react to new Sessions. """ if not self.active: return if self.sessionsChangedHook: self._update() #Now check the incremental hooks. #No hooks, no reason to process if not (self.timeStampHook or self.lockFileHook): logger.info("No watcher-hooks to process") return for entry in self._lastExport: nsmSessionName = entry["nsmSessionName"] try: #Timestamp of session.nsm if self.timeStampHook: timestamp = datetime.fromtimestamp(entry["sessionFile"].stat().st_mtime).isoformat(sep=" ", timespec='minutes') #same format as server control export if not timestamp == self._lastTimestamp[nsmSessionName]: #This will only trigger on a minute-based slot, which is all we want and need. This is for relaying information to the user, not for advanced processing. self._lastTimestamp[nsmSessionName] = timestamp self.timeStampHook(nsmSessionName, timestamp) #Lockfiles if self.lockFileHook: lockfileState = self._nsmServerControl._checkIfLocked(nsmSessionName) if not self._lastLockfile[nsmSessionName] == lockfileState: self._lastLockfile[nsmSessionName] = lockfileState self.lockFileHook(nsmSessionName, lockfileState) except PermissionError: logger.warning(f"File Permission error for {entry}") self._lastExport.remove(entry) #avoid stumbling upon this again self._update() except FileNotFoundError: logger.warning(f"File not found error for {entry}") self._lastExport.remove(entry) #avoid stumbling upon this again self._update() ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1665611335.9539216 agordejo-0.4.2/qtgui/addclientprompt.py0000644000175000017500000001272514321633110016740 0ustar00nilsnils#! /usr/bin/env python3 # -*- coding: utf-8 -*- """ Copyright 2022, Nils Hilbricht, Germany ( https://www.hilbricht.net ) This file is part of the Laborejo Software Suite ( https://www.laborejo.org ). This application is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . """ import logging; logger = logging.getLogger(__name__); logger.info("import") #Standard Library #Engine import engine.api as api #Third Party from PyQt5 import QtCore, QtWidgets class PromptWidget(QtWidgets.QDialog): wordlist = None minlen = None def __init__(self, parent): super().__init__(parent) layout = QtWidgets.QFormLayout() #layout = QtWidgets.QVBoxLayout() updateWordlist() #this is a fast index update, we can call that every time to be sure. Otherwise newly added executable-PATHs will not be reflected in the dialog without a program-database update, which takes too long to be convenient. self.setLayout(layout) self.setWindowFlag(QtCore.Qt.Popup, True) self.comboBox = QtWidgets.QComboBox(self) self.comboBox.setEditable(True) self.comboBox.currentTextChanged.connect(self.check) #not called when text is changed programatically if PromptWidget.wordlist: completer = QtWidgets.QCompleter(PromptWidget.wordlist) completer.setModelSorting(QtWidgets.QCompleter.CaseInsensitivelySortedModel) completer.setCaseSensitivity(QtCore.Qt.CaseInsensitive) completer.setCompletionMode(QtWidgets.QCompleter.PopupCompletion) completer.activated.connect(self.process) #To avoid double-press enter to select one we hook into the activated signal and trigger process self.comboBox.setCompleter(completer) self.comboBox.setMinimumContentsLength(PromptWidget.minlen) labelString = QtCore.QCoreApplication.translate("PromptWidget", "Type in the name of an executable file on your system.") else: labelString = QtCore.QCoreApplication.translate("PromptWidget", "No program database found. Please update through Control menu.") label = QtWidgets.QLabel(labelString) layout.addWidget(label) self.comboBox.setSizeAdjustPolicy(QtWidgets.QComboBox.AdjustToMinimumContentsLength) layout.addWidget(self.comboBox) errorString = QtCore.QCoreApplication.translate("PromptWidget", "Command not found or not accepted!
Parameters, --switches and relative paths are not allowed.
Use nsm-proxy or write a starter-script instead.") errorString = "" + errorString + "" self.errorLabel = QtWidgets.QLabel(errorString) layout.addWidget(self.errorLabel) self.errorLabel.hide() #shown in process or check self.buttonBox = QtWidgets.QDialogButtonBox(QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel) self.buttonBox.accepted.connect(self.process) self.buttonBox.rejected.connect(self.reject) layout.addWidget(self.buttonBox) self.exec() #blocks until the dialog gets closed def abortHandler(self): pass def check(self, currentText): """Called every keypress. We do preliminary error and collision checking here, so the engine does not have to throw an error """ self.errorLabel.hide() #start in good faith ok = self.buttonBox.button(QtWidgets.QDialogButtonBox.Ok) if currentText in PromptWidget.wordlist: #this is taken literally. Any extra char or whitespace counts as false ok.setEnabled(True) else: ok.setEnabled(False) self.errorLabel.show() def process(self): """Careful! Calling this eats python errors without notice. Make sure your objects exists and your syntax is correct""" assert PromptWidget.wordlist assert PromptWidget.minlen curText = self.comboBox.currentText() if not curText or curText == " ": #TODO: qt weirdness. This is a case when focus is lost from a valid entry. The field is filled from a chosen value, from the list. But it says " ". #Do not show the errorLabel. This is a qt bug or so. return if curText in PromptWidget.wordlist: api.clientAdd(curText) logger.info(f"Prompt accepted {curText} and will send it to clientAdd") self.done(True) else: logger.info(f"Prompt did not accept {curText}.Showing info to the user.") self.errorLabel.show() def updateWordlist(): """in case programs are installed while the session is running the user can manually call a database update""" PromptWidget.wordlist = api.getUnfilteredExecutables() if PromptWidget.wordlist: PromptWidget.minlen = len(max(PromptWidget.wordlist, key=len)) else: logger.error("Executable list came back empty! Most likely an error in application database build. Not trivial!") def askForExecutable(parent): PromptWidget(parent) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1665611335.9539216 agordejo-0.4.2/qtgui/changelog.py0000644000175000017500000000401114321633110015463 0ustar00nilsnils#! /usr/bin/env python3 # -*- coding: utf-8 -*- """ Copyright 2022, Nils Hilbricht, Germany ( https://www.hilbricht.net ) This file is part of the Laborejo Software Suite ( https://www.laborejo.org ), This application is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . """ import logging; logger = logging.getLogger(__name__); logger.info("import") #Standard Lib #System Wide Modules from PyQt5 import QtCore, QtWidgets, QtGui #Local Modules from engine.config import * #imports METADATA from engine.start import PATHS class Changelog(QtWidgets.QWidget): """ A modal window that is hidden. Just shows the file CHANGELOG """ def __init__(self, mainWindow): super().__init__() self.mainWindow = mainWindow ourLayout = QtWidgets.QVBoxLayout() #ourLayout.setSpacing(0) #ourLayout.setContentsMargins(0,0,0,0) self.setLayout(ourLayout) self.setWindowTitle(METADATA["name"] + " " + QtCore.QCoreApplication.translate("TemplateChangelog", "Changelog")) introtext = QtCore.QCoreApplication.translate("TemplateChangelog", "The Changelog is only available in English.") ourLayout.addWidget(QtWidgets.QLabel(introtext)) textEdit = QtWidgets.QPlainTextEdit() textEdit.setReadOnly(True) with open(PATHS["doc"] + "/CHANGELOG", "r") as f: textEdit.setPlainText(f.read()) ourLayout.addWidget(textEdit) self.hide() def closeEvent(self, event): self.hide() ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1665611335.9505882 agordejo-0.4.2/qtgui/descriptiontextwidget.py0000644000175000017500000001130114321633110020170 0ustar00nilsnils#! /usr/bin/env python3 # -*- coding: utf-8 -*- """ Copyright 2022, Nils Hilbricht, Germany ( https://www.hilbricht.net ) This file is part of the Laborejo Software Suite ( https://www.laborejo.org ) This application is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . """ import logging; logger = logging.getLogger(__name__); logger.info("import") #Standard Library #Engine import engine.api as api #Third Party from PyQt5 import QtCore, QtGui, QtWidgets class DescriptionController(object): """Not a subclass. Controls a TextEditWidget to hold the session-description from nsm-data. Can be used on multiple text widgets. One controller for one textwidget.""" def __init__(self, mainWindow, parentGroupBox:QtWidgets.QGroupBox, plainTextWidget:QtWidgets.QPlainTextEdit): self.mainWindow = mainWindow self.description = plainTextWidget self.parentGroupBox = parentGroupBox self.placeHolderDescription = QtCore.QCoreApplication.translate("LoadedSessionDescription", "Double click to add the client nsm-data to write here.\nUse it for notes, TODO, references etc…") #We do NOT use the Qt placeholder text because that shows even when editing is possible. Our visual feedback is changing from placeholder to empty. self.description.setEnabled(False) self._reactCallback_dataClientDescriptionChanged(None) #set up description self.parentGroupBox.mouseDoubleClickEvent = self._doubleClickToResumeOrAddNsmData self.description.textChanged.connect(self.sendDescriptionToApi) #TODO: this is every keystroke, but it is impossible to solve a multi-GUI network system where a save signal comes from outside.. we need update every char. Maybe a genius in the future will solve this. #self.description.focusOutEvent = self._descriptionFocusOut api.callbacks.dataClientDescriptionChanged.append(self._reactCallback_dataClientDescriptionChanged) #Session description def sendDescriptionToApi(self): """We cannot send every keystroke over the OSC-network. Therefore we wait: This is called by several events that "feel" like editing is done now: Focus out, Ctlr+S, Alt+S.""" api.setDescription(self.description.toPlainText()) #this is not save yet. Just forward to data client. #def _descriptionFocusOut(self, event): # self.sendDescriptionToApi() # QtWidgets.QPlainTextEdit.focusOutEvent(self.description, event) def _reactCallback_dataClientDescriptionChanged(self, desc:str): """Put the session description into our text field. We send each change, so we receive this signal each detail change. The cursor changes in between so we force the position. """ self.description.blockSignals(True) if not desc is None: #may be None for closing session oldPos = self.description.textCursor().position() self.description.setPlainText(desc) #plain textedit self.description.setEnabled(True) c = self.description.textCursor() c.setPosition(oldPos) self.description.setTextCursor(c) else: self.description.setEnabled(False) self.description.setPlainText(self.placeHolderDescription) self.description.blockSignals(False) def _doubleClickToResumeOrAddNsmData(self, event): """Intended for doubleClickEvent, so we get an event. Do nothing when nsm-data is present. Add it when it was never there. Resume it if stopped in the session. When QPlainTextEdit is disabled it will forward doubleClick to the parent widget, which is this function. If enabled the groupBox description and frame will be clickable, we do nothing in this case. """ if self.description.isEnabled(): pass else: d = api.executableInSession("nsm-data") if d: api.clientResume(d["clientId"]) else: api.clientAdd("nsm-data") QtWidgets.QGroupBox.mouseDoubleClickEvent(self.parentGroupBox, event) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1665611335.9505882 agordejo-0.4.2/qtgui/designer/mainwindow.py0000644000175000017500000006325014321633110017522 0ustar00nilsnils# -*- coding: utf-8 -*- # Form implementation generated from reading ui file 'mainwindow.ui' # # Created by: PyQt5 UI code generator 5.15.6 # # WARNING: Any manual changes made to this file will be lost when pyuic5 is # run again. Do not edit this file unless you know what you are doing. from PyQt5 import QtCore, QtGui, QtWidgets class Ui_MainWindow(object): def setupUi(self, MainWindow): MainWindow.setObjectName("MainWindow") MainWindow.resize(953, 737) self.centralwidget = QtWidgets.QWidget(MainWindow) self.centralwidget.setObjectName("centralwidget") self.verticalLayout = QtWidgets.QVBoxLayout(self.centralwidget) self.verticalLayout.setContentsMargins(0, 0, 0, 0) self.verticalLayout.setSpacing(0) self.verticalLayout.setObjectName("verticalLayout") self.mainPageSwitcher = QtWidgets.QStackedWidget(self.centralwidget) self.mainPageSwitcher.setLineWidth(0) self.mainPageSwitcher.setObjectName("mainPageSwitcher") self.tabPage = QtWidgets.QWidget() self.tabPage.setObjectName("tabPage") self.verticalLayout_12 = QtWidgets.QVBoxLayout(self.tabPage) self.verticalLayout_12.setObjectName("verticalLayout_12") self.jackTransportControls = QtWidgets.QGroupBox(self.tabPage) self.jackTransportControls.setObjectName("jackTransportControls") self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.jackTransportControls) self.horizontalLayout_2.setObjectName("horizontalLayout_2") self.jackTransportPlayPause = QtWidgets.QPushButton(self.jackTransportControls) self.jackTransportPlayPause.setObjectName("jackTransportPlayPause") self.horizontalLayout_2.addWidget(self.jackTransportPlayPause) self.jackTransportRewind = QtWidgets.QPushButton(self.jackTransportControls) self.jackTransportRewind.setObjectName("jackTransportRewind") self.horizontalLayout_2.addWidget(self.jackTransportRewind) self.jackTransportTimeline = QtWidgets.QProgressBar(self.jackTransportControls) self.jackTransportTimeline.setProperty("value", 24) self.jackTransportTimeline.setObjectName("jackTransportTimeline") self.horizontalLayout_2.addWidget(self.jackTransportTimeline) self.jackTransportMaxTime = QtWidgets.QSpinBox(self.jackTransportControls) self.jackTransportMaxTime.setMinimum(1) self.jackTransportMaxTime.setMaximum(999) self.jackTransportMaxTime.setProperty("value", 5) self.jackTransportMaxTime.setObjectName("jackTransportMaxTime") self.horizontalLayout_2.addWidget(self.jackTransportMaxTime) self.verticalLayout_12.addWidget(self.jackTransportControls) self.tabbyCat = QtWidgets.QTabWidget(self.tabPage) self.tabbyCat.setObjectName("tabbyCat") self.tab_detailed = QtWidgets.QWidget() self.tab_detailed.setObjectName("tab_detailed") self.horizontalLayout = QtWidgets.QHBoxLayout(self.tab_detailed) self.horizontalLayout.setContentsMargins(0, 0, 0, 0) self.horizontalLayout.setSpacing(0) self.horizontalLayout.setObjectName("horizontalLayout") self.detailedStackedWidget = QtWidgets.QStackedWidget(self.tab_detailed) self.detailedStackedWidget.setObjectName("detailedStackedWidget") self.stack_no_session = QtWidgets.QWidget() self.stack_no_session.setObjectName("stack_no_session") self.l_2 = QtWidgets.QHBoxLayout(self.stack_no_session) self.l_2.setContentsMargins(9, 9, 9, 9) self.l_2.setSpacing(6) self.l_2.setObjectName("l_2") self.widget = QtWidgets.QWidget(self.stack_no_session) self.widget.setObjectName("widget") self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.widget) self.verticalLayout_3.setObjectName("verticalLayout_3") self.button_new_session = QtWidgets.QPushButton(self.widget) self.button_new_session.setObjectName("button_new_session") self.verticalLayout_3.addWidget(self.button_new_session) self.button_new_quick_session = QtWidgets.QPushButton(self.widget) self.button_new_quick_session.setObjectName("button_new_quick_session") self.verticalLayout_3.addWidget(self.button_new_quick_session) self.button_load_selected_session = QtWidgets.QPushButton(self.widget) self.button_load_selected_session.setEnabled(False) self.button_load_selected_session.setObjectName("button_load_selected_session") self.verticalLayout_3.addWidget(self.button_load_selected_session) self.button_rename_selected_session = QtWidgets.QPushButton(self.widget) self.button_rename_selected_session.setEnabled(False) self.button_rename_selected_session.setObjectName("button_rename_selected_session") self.verticalLayout_3.addWidget(self.button_rename_selected_session) self.button_copy_selected_session = QtWidgets.QPushButton(self.widget) self.button_copy_selected_session.setEnabled(False) self.button_copy_selected_session.setObjectName("button_copy_selected_session") self.verticalLayout_3.addWidget(self.button_copy_selected_session) self.button_delete_selected_session = QtWidgets.QPushButton(self.widget) self.button_delete_selected_session.setEnabled(False) self.button_delete_selected_session.setObjectName("button_delete_selected_session") self.verticalLayout_3.addWidget(self.button_delete_selected_session) self.checkBoxNested = QtWidgets.QCheckBox(self.widget) self.checkBoxNested.setChecked(True) self.checkBoxNested.setObjectName("checkBoxNested") self.verticalLayout_3.addWidget(self.checkBoxNested) spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) self.verticalLayout_3.addItem(spacerItem) self.l_2.addWidget(self.widget) self.session_tree = QtWidgets.QTreeWidget(self.stack_no_session) self.session_tree.setObjectName("session_tree") self.session_tree.headerItem().setText(0, "1") self.l_2.addWidget(self.session_tree) self.detailedStackedWidget.addWidget(self.stack_no_session) self.stack_loaded_session = QtWidgets.QWidget() self.stack_loaded_session.setObjectName("stack_loaded_session") self.verticalLayout_4 = QtWidgets.QVBoxLayout(self.stack_loaded_session) self.verticalLayout_4.setContentsMargins(9, 9, 9, 9) self.verticalLayout_4.setSpacing(0) self.verticalLayout_4.setObjectName("verticalLayout_4") self.vSplitterProgramsLog = QtWidgets.QSplitter(self.stack_loaded_session) self.vSplitterProgramsLog.setOrientation(QtCore.Qt.Vertical) self.vSplitterProgramsLog.setObjectName("vSplitterProgramsLog") self.hSplitterLauncherClients = QtWidgets.QSplitter(self.vSplitterProgramsLog) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(7) sizePolicy.setHeightForWidth(self.hSplitterLauncherClients.sizePolicy().hasHeightForWidth()) self.hSplitterLauncherClients.setSizePolicy(sizePolicy) self.hSplitterLauncherClients.setOrientation(QtCore.Qt.Horizontal) self.hSplitterLauncherClients.setChildrenCollapsible(False) self.hSplitterLauncherClients.setObjectName("hSplitterLauncherClients") self.session_programs = QtWidgets.QGroupBox(self.hSplitterLauncherClients) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(7) sizePolicy.setHeightForWidth(self.session_programs.sizePolicy().hasHeightForWidth()) self.session_programs.setSizePolicy(sizePolicy) self.session_programs.setObjectName("session_programs") self.verticalLayout_5 = QtWidgets.QVBoxLayout(self.session_programs) self.verticalLayout_5.setContentsMargins(0, 0, 0, 0) self.verticalLayout_5.setSpacing(0) self.verticalLayout_5.setObjectName("verticalLayout_5") self.loadedSessionsLauncher = QtWidgets.QTreeWidget(self.session_programs) self.loadedSessionsLauncher.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection) self.loadedSessionsLauncher.setIconSize(QtCore.QSize(64, 64)) self.loadedSessionsLauncher.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel) self.loadedSessionsLauncher.setHorizontalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel) self.loadedSessionsLauncher.setObjectName("loadedSessionsLauncher") self.loadedSessionsLauncher.headerItem().setText(0, "1") self.verticalLayout_5.addWidget(self.loadedSessionsLauncher) self.session_loaded = QtWidgets.QGroupBox(self.hSplitterLauncherClients) self.session_loaded.setObjectName("session_loaded") self.verticalLayout_6 = QtWidgets.QVBoxLayout(self.session_loaded) self.verticalLayout_6.setContentsMargins(0, 0, 0, 0) self.verticalLayout_6.setSpacing(0) self.verticalLayout_6.setObjectName("verticalLayout_6") self.loadedSessionClients = QtWidgets.QTreeWidget(self.session_loaded) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(3) sizePolicy.setHeightForWidth(self.loadedSessionClients.sizePolicy().hasHeightForWidth()) self.loadedSessionClients.setSizePolicy(sizePolicy) self.loadedSessionClients.setAlternatingRowColors(True) self.loadedSessionClients.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel) self.loadedSessionClients.setHorizontalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel) self.loadedSessionClients.setObjectName("loadedSessionClients") self.loadedSessionClients.headerItem().setText(0, "1") self.verticalLayout_6.addWidget(self.loadedSessionClients) self.loadedSessionDescriptionGroupBox = QtWidgets.QGroupBox(self.vSplitterProgramsLog) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(2) sizePolicy.setHeightForWidth(self.loadedSessionDescriptionGroupBox.sizePolicy().hasHeightForWidth()) self.loadedSessionDescriptionGroupBox.setSizePolicy(sizePolicy) self.loadedSessionDescriptionGroupBox.setObjectName("loadedSessionDescriptionGroupBox") self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.loadedSessionDescriptionGroupBox) self.verticalLayout_2.setContentsMargins(0, 0, 0, 0) self.verticalLayout_2.setSpacing(0) self.verticalLayout_2.setObjectName("verticalLayout_2") self.loadedSessionDescription = QtWidgets.QPlainTextEdit(self.loadedSessionDescriptionGroupBox) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.loadedSessionDescription.sizePolicy().hasHeightForWidth()) self.loadedSessionDescription.setSizePolicy(sizePolicy) self.loadedSessionDescription.setObjectName("loadedSessionDescription") self.verticalLayout_2.addWidget(self.loadedSessionDescription) self.verticalLayout_4.addWidget(self.vSplitterProgramsLog) self.detailedStackedWidget.addWidget(self.stack_loaded_session) self.horizontalLayout.addWidget(self.detailedStackedWidget) self.tabbyCat.addTab(self.tab_detailed, "") self.tab_information = QtWidgets.QWidget() self.tab_information.setObjectName("tab_information") self.verticalLayout_11 = QtWidgets.QVBoxLayout(self.tab_information) self.verticalLayout_11.setObjectName("verticalLayout_11") self.informationTreeWidget = QtWidgets.QTreeWidget(self.tab_information) self.informationTreeWidget.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers) self.informationTreeWidget.setAlternatingRowColors(True) self.informationTreeWidget.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel) self.informationTreeWidget.setItemsExpandable(False) self.informationTreeWidget.setObjectName("informationTreeWidget") self.informationTreeWidget.headerItem().setTextAlignment(0, QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) item_0 = QtWidgets.QTreeWidgetItem(self.informationTreeWidget) item_0 = QtWidgets.QTreeWidgetItem(self.informationTreeWidget) item_0 = QtWidgets.QTreeWidgetItem(self.informationTreeWidget) item_0 = QtWidgets.QTreeWidgetItem(self.informationTreeWidget) item_0 = QtWidgets.QTreeWidgetItem(self.informationTreeWidget) self.informationTreeWidget.header().setVisible(False) self.informationTreeWidget.header().setDefaultSectionSize(150) self.informationTreeWidget.header().setMinimumSectionSize(50) self.verticalLayout_11.addWidget(self.informationTreeWidget) self.tabbyCat.addTab(self.tab_information, "") self.verticalLayout_12.addWidget(self.tabbyCat) self.mainPageSwitcher.addWidget(self.tabPage) self.messagePage = QtWidgets.QWidget() self.messagePage.setObjectName("messagePage") self.verticalLayout_13 = QtWidgets.QVBoxLayout(self.messagePage) self.verticalLayout_13.setObjectName("verticalLayout_13") self.messageLabel = QtWidgets.QLabel(self.messagePage) self.messageLabel.setAlignment(QtCore.Qt.AlignCenter) self.messageLabel.setObjectName("messageLabel") self.verticalLayout_13.addWidget(self.messageLabel) self.waitDialogErrorButton = QtWidgets.QPushButton(self.messagePage) self.waitDialogErrorButton.setObjectName("waitDialogErrorButton") self.verticalLayout_13.addWidget(self.waitDialogErrorButton) self.mainPageSwitcher.addWidget(self.messagePage) self.verticalLayout.addWidget(self.mainPageSwitcher) MainWindow.setCentralWidget(self.centralwidget) self.statusbar = QtWidgets.QStatusBar(MainWindow) self.statusbar.setObjectName("statusbar") MainWindow.setStatusBar(self.statusbar) self.menubar = QtWidgets.QMenuBar(MainWindow) self.menubar.setGeometry(QtCore.QRect(0, 0, 953, 20)) self.menubar.setObjectName("menubar") self.menuControl = QtWidgets.QMenu(self.menubar) self.menuControl.setObjectName("menuControl") self.menuSession = QtWidgets.QMenu(self.menubar) self.menuSession.setObjectName("menuSession") self.menuClientNameId = QtWidgets.QMenu(self.menubar) self.menuClientNameId.setObjectName("menuClientNameId") MainWindow.setMenuBar(self.menubar) self.actionMenuQuit = QtWidgets.QAction(MainWindow) self.actionMenuQuit.setObjectName("actionMenuQuit") self.actionAbout = QtWidgets.QAction(MainWindow) self.actionAbout.setObjectName("actionAbout") self.actionHide_in_System_Tray = QtWidgets.QAction(MainWindow) self.actionHide_in_System_Tray.setObjectName("actionHide_in_System_Tray") self.actionSessionAddClient = QtWidgets.QAction(MainWindow) self.actionSessionAddClient.setObjectName("actionSessionAddClient") self.actionSessionSave = QtWidgets.QAction(MainWindow) self.actionSessionSave.setObjectName("actionSessionSave") self.actionSessionSaveAs = QtWidgets.QAction(MainWindow) self.actionSessionSaveAs.setObjectName("actionSessionSaveAs") self.actionSessionSaveAndClose = QtWidgets.QAction(MainWindow) self.actionSessionSaveAndClose.setObjectName("actionSessionSaveAndClose") self.actionSessionAbort = QtWidgets.QAction(MainWindow) self.actionSessionAbort.setObjectName("actionSessionAbort") self.actionClientStop = QtWidgets.QAction(MainWindow) self.actionClientStop.setObjectName("actionClientStop") self.actionClientResume = QtWidgets.QAction(MainWindow) self.actionClientResume.setObjectName("actionClientResume") self.actionClientSave_separately = QtWidgets.QAction(MainWindow) self.actionClientSave_separately.setObjectName("actionClientSave_separately") self.actionClientRemove = QtWidgets.QAction(MainWindow) self.actionClientRemove.setObjectName("actionClientRemove") self.actionClientToggleVisible = QtWidgets.QAction(MainWindow) self.actionClientToggleVisible.setObjectName("actionClientToggleVisible") self.actionShow_All_Clients = QtWidgets.QAction(MainWindow) self.actionShow_All_Clients.setObjectName("actionShow_All_Clients") self.actionHide_All_Clients = QtWidgets.QAction(MainWindow) self.actionHide_All_Clients.setObjectName("actionHide_All_Clients") self.actionRebuild_Program_Database = QtWidgets.QAction(MainWindow) self.actionRebuild_Program_Database.setObjectName("actionRebuild_Program_Database") self.actionClientRename = QtWidgets.QAction(MainWindow) self.actionClientRename.setObjectName("actionClientRename") self.actionSettings = QtWidgets.QAction(MainWindow) self.actionSettings.setObjectName("actionSettings") self.actionManual = QtWidgets.QAction(MainWindow) self.actionManual.setObjectName("actionManual") self.actionChangelog = QtWidgets.QAction(MainWindow) self.actionChangelog.setObjectName("actionChangelog") self.actionSplit_Session_View_The_Other_Way = QtWidgets.QAction(MainWindow) self.actionSplit_Session_View_The_Other_Way.setCheckable(True) self.actionSplit_Session_View_The_Other_Way.setObjectName("actionSplit_Session_View_The_Other_Way") self.menuControl.addAction(self.actionManual) self.menuControl.addAction(self.actionChangelog) self.menuControl.addAction(self.actionRebuild_Program_Database) self.menuControl.addAction(self.actionSplit_Session_View_The_Other_Way) self.menuControl.addAction(self.actionHide_in_System_Tray) self.menuControl.addAction(self.actionSettings) self.menuControl.addAction(self.actionMenuQuit) self.menuSession.addAction(self.actionSessionAddClient) self.menuSession.addAction(self.actionSessionSave) self.menuSession.addAction(self.actionSessionSaveAs) self.menuSession.addAction(self.actionSessionSaveAndClose) self.menuSession.addAction(self.actionSessionAbort) self.menuSession.addSeparator() self.menuSession.addAction(self.actionShow_All_Clients) self.menuSession.addAction(self.actionHide_All_Clients) self.menuClientNameId.addAction(self.actionClientRename) self.menuClientNameId.addAction(self.actionClientToggleVisible) self.menuClientNameId.addAction(self.actionClientSave_separately) self.menuClientNameId.addAction(self.actionClientStop) self.menuClientNameId.addAction(self.actionClientResume) self.menuClientNameId.addSeparator() self.menuClientNameId.addAction(self.actionClientRemove) self.menubar.addAction(self.menuControl.menuAction()) self.menubar.addAction(self.menuSession.menuAction()) self.menubar.addAction(self.menuClientNameId.menuAction()) self.retranslateUi(MainWindow) self.mainPageSwitcher.setCurrentIndex(0) self.tabbyCat.setCurrentIndex(0) self.detailedStackedWidget.setCurrentIndex(0) QtCore.QMetaObject.connectSlotsByName(MainWindow) def retranslateUi(self, MainWindow): _translate = QtCore.QCoreApplication.translate MainWindow.setWindowTitle(_translate("MainWindow", "Agordejo")) self.jackTransportControls.setTitle(_translate("MainWindow", "Global Playback Controls")) self.jackTransportPlayPause.setText(_translate("MainWindow", "PlayPause")) self.jackTransportRewind.setText(_translate("MainWindow", "Rewind")) self.jackTransportTimeline.setFormat(_translate("MainWindow", " time-placeholder")) self.jackTransportMaxTime.setSuffix(_translate("MainWindow", " min")) self.button_new_session.setText(_translate("MainWindow", "New")) self.button_new_quick_session.setText(_translate("MainWindow", "Quick New")) self.button_load_selected_session.setText(_translate("MainWindow", "Load Selected")) self.button_rename_selected_session.setText(_translate("MainWindow", "Rename Selected")) self.button_copy_selected_session.setText(_translate("MainWindow", "Copy Selected")) self.button_delete_selected_session.setText(_translate("MainWindow", "Delete Selected")) self.checkBoxNested.setText(_translate("MainWindow", "Tree View")) self.session_programs.setTitle(_translate("MainWindow", "Double-click to load program")) self.session_loaded.setTitle(_translate("MainWindow", "In current session")) self.loadedSessionDescriptionGroupBox.setTitle(_translate("MainWindow", "Session Notes")) self.tabbyCat.setTabText(self.tabbyCat.indexOf(self.tab_detailed), _translate("MainWindow", "Full View")) __sortingEnabled = self.informationTreeWidget.isSortingEnabled() self.informationTreeWidget.setSortingEnabled(False) self.informationTreeWidget.topLevelItem(0).setText(0, _translate("MainWindow", "JACK")) self.informationTreeWidget.topLevelItem(0).setText(1, _translate("MainWindow", "version and running")) self.informationTreeWidget.topLevelItem(1).setText(0, _translate("MainWindow", "NSM Server Mode")) self.informationTreeWidget.topLevelItem(1).setText(1, _translate("MainWindow", "Self-started, connected to, environment var")) self.informationTreeWidget.topLevelItem(2).setText(0, _translate("MainWindow", "NSM Url")) self.informationTreeWidget.topLevelItem(2).setText(1, _translate("MainWindow", "osc.upd ip port")) self.informationTreeWidget.topLevelItem(3).setText(0, _translate("MainWindow", "Session Root")) self.informationTreeWidget.topLevelItem(3).setText(1, _translate("MainWindow", "/home/usr/NSM Sessions")) self.informationTreeWidget.topLevelItem(4).setText(0, _translate("MainWindow", "Program Database")) self.informationTreeWidget.topLevelItem(4).setText(1, _translate("MainWindow", "Last Updated")) self.informationTreeWidget.setSortingEnabled(__sortingEnabled) self.tabbyCat.setTabText(self.tabbyCat.indexOf(self.tab_information), _translate("MainWindow", "Information")) self.messageLabel.setText(_translate("MainWindow", "Processing")) self.waitDialogErrorButton.setText(_translate("MainWindow", "I understand that I will need to resolve this problem on my own!")) self.menuControl.setTitle(_translate("MainWindow", "Control")) self.menuSession.setTitle(_translate("MainWindow", "SessionName")) self.menuClientNameId.setTitle(_translate("MainWindow", "ClientNameId")) self.actionMenuQuit.setText(_translate("MainWindow", "Quit")) self.actionMenuQuit.setShortcut(_translate("MainWindow", "Ctrl+Shift+Q")) self.actionAbout.setText(_translate("MainWindow", "About")) self.actionHide_in_System_Tray.setText(_translate("MainWindow", "Hide in System Tray")) self.actionHide_in_System_Tray.setShortcut(_translate("MainWindow", "Ctrl+Q")) self.actionSessionAddClient.setText(_translate("MainWindow", "Add Client (Prompt)")) self.actionSessionAddClient.setShortcut(_translate("MainWindow", "A")) self.actionSessionSave.setText(_translate("MainWindow", "Save")) self.actionSessionSave.setShortcut(_translate("MainWindow", "Ctrl+S")) self.actionSessionSaveAs.setText(_translate("MainWindow", "Save and Clone under different name")) self.actionSessionSaveAs.setShortcut(_translate("MainWindow", "Ctrl+Shift+S")) self.actionSessionSaveAndClose.setText(_translate("MainWindow", "Save and Close")) self.actionSessionSaveAndClose.setShortcut(_translate("MainWindow", "Ctrl+W")) self.actionSessionAbort.setText(_translate("MainWindow", "Close without Save (\"Abort\")")) self.actionSessionAbort.setShortcut(_translate("MainWindow", "Ctrl+Shift+W")) self.actionClientStop.setText(_translate("MainWindow", "Stop")) self.actionClientStop.setShortcut(_translate("MainWindow", "Alt+O")) self.actionClientResume.setText(_translate("MainWindow", "Resume")) self.actionClientResume.setShortcut(_translate("MainWindow", "Alt+R")) self.actionClientSave_separately.setText(_translate("MainWindow", "Save separately")) self.actionClientSave_separately.setShortcut(_translate("MainWindow", "Alt+S")) self.actionClientRemove.setText(_translate("MainWindow", "Remove")) self.actionClientRemove.setShortcut(_translate("MainWindow", "Alt+X")) self.actionClientToggleVisible.setText(_translate("MainWindow", "Toggle Visible")) self.actionClientToggleVisible.setShortcut(_translate("MainWindow", "Alt+T")) self.actionShow_All_Clients.setText(_translate("MainWindow", "Show All Clients")) self.actionHide_All_Clients.setText(_translate("MainWindow", "Hide All Clients")) self.actionRebuild_Program_Database.setText(_translate("MainWindow", "Rebuild Program Database")) self.actionClientRename.setText(_translate("MainWindow", "Rename")) self.actionClientRename.setShortcut(_translate("MainWindow", "F2")) self.actionSettings.setText(_translate("MainWindow", "Settings")) self.actionManual.setText(_translate("MainWindow", "Manual")) self.actionChangelog.setText(_translate("MainWindow", "News and Changelog")) self.actionSplit_Session_View_The_Other_Way.setText(_translate("MainWindow", "Split Session View the other way")) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1665611335.9505882 agordejo-0.4.2/qtgui/designer/mainwindow.ui0000644000175000017500000006305214321633110017507 0ustar00nilsnils MainWindow 0 0 953 737 Agordejo 0 0 0 0 0 0 0 Global Playback Controls PlayPause Rewind 24 time-placeholder min 1 999 5 0 Full View 0 0 0 0 0 0 6 9 9 9 9 New Quick New false Load Selected false Rename Selected false Copy Selected false Delete Selected Tree View true Qt::Vertical 20 40 1 0 9 9 9 9 Qt::Vertical 0 7 Qt::Horizontal false 0 7 Double-click to load program 0 0 0 0 0 QAbstractItemView::NoSelection 64 64 QAbstractItemView::ScrollPerPixel QAbstractItemView::ScrollPerPixel 1 In current session 0 0 0 0 0 0 3 true QAbstractItemView::ScrollPerPixel QAbstractItemView::ScrollPerPixel 1 0 2 Session Notes 0 0 0 0 0 0 0 Information QAbstractItemView::NoEditTriggers true QAbstractItemView::ScrollPerPixel false false 50 150 AlignTrailing|AlignVCenter JACK AlignTrailing|AlignVCenter version and running NSM Server Mode AlignTrailing|AlignVCenter Self-started, connected to, environment var NSM Url AlignTrailing|AlignVCenter osc.upd ip port Session Root AlignTrailing|AlignVCenter /home/usr/NSM Sessions Program Database AlignTrailing|AlignVCenter Last Updated Processing Qt::AlignCenter I understand that I will need to resolve this problem on my own! 0 0 953 20 Control SessionName ClientNameId Quit Ctrl+Shift+Q About Hide in System Tray Ctrl+Q Add Client (Prompt) A Save Ctrl+S Save and Clone under different name Ctrl+Shift+S Save and Close Ctrl+W Close without Save ("Abort") Ctrl+Shift+W Stop Alt+O Resume Alt+R Save separately Alt+S Remove Alt+X Toggle Visible Alt+T Show All Clients Hide All Clients Rebuild Program Database Rename F2 Settings Manual News and Changelog true Split Session View the other way ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1665611335.9505882 agordejo-0.4.2/qtgui/designer/newsession.py0000644000175000017500000000470514321633110017543 0ustar00nilsnils# -*- coding: utf-8 -*- # Form implementation generated from reading ui file 'newsession.ui' # # Created by: PyQt5 UI code generator 5.15.4 # # WARNING: Any manual changes made to this file will be lost when pyuic5 is # run again. Do not edit this file unless you know what you are doing. from PyQt5 import QtCore, QtGui, QtWidgets class Ui_NewSession(object): def setupUi(self, NewSession): NewSession.setObjectName("NewSession") NewSession.resize(448, 222) self.verticalLayout = QtWidgets.QVBoxLayout(NewSession) self.verticalLayout.setObjectName("verticalLayout") self.nameGroupBox = QtWidgets.QGroupBox(NewSession) self.nameGroupBox.setObjectName("nameGroupBox") self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.nameGroupBox) self.verticalLayout_3.setContentsMargins(0, 0, 0, 0) self.verticalLayout_3.setSpacing(0) self.verticalLayout_3.setObjectName("verticalLayout_3") self.verticalLayout.addWidget(self.nameGroupBox) self.checkBoxJack = QtWidgets.QCheckBox(NewSession) self.checkBoxJack.setChecked(True) self.checkBoxJack.setObjectName("checkBoxJack") self.verticalLayout.addWidget(self.checkBoxJack) self.checkBoxData = QtWidgets.QCheckBox(NewSession) self.checkBoxData.setChecked(True) self.checkBoxData.setObjectName("checkBoxData") self.verticalLayout.addWidget(self.checkBoxData) self.buttonBox = QtWidgets.QDialogButtonBox(NewSession) self.buttonBox.setOrientation(QtCore.Qt.Horizontal) self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok) self.buttonBox.setObjectName("buttonBox") self.verticalLayout.addWidget(self.buttonBox) self.retranslateUi(NewSession) self.buttonBox.accepted.connect(NewSession.accept) self.buttonBox.rejected.connect(NewSession.reject) QtCore.QMetaObject.connectSlotsByName(NewSession) def retranslateUi(self, NewSession): _translate = QtCore.QCoreApplication.translate NewSession.setWindowTitle(_translate("NewSession", "Dialog")) self.nameGroupBox.setTitle(_translate("NewSession", "New Session Name")) self.checkBoxJack.setText(_translate("NewSession", "Save JACK Connections\n" "(adds clients \'jackpatch\')")) self.checkBoxData.setText(_translate("NewSession", "Client Renaming and Session Notes\n" "(adds client \'nsm-data\')")) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1665611335.9505882 agordejo-0.4.2/qtgui/designer/newsession.ui0000644000175000017500000000507114321633110017525 0ustar00nilsnils NewSession 0 0 448 222 Dialog New Session Name 0 0 0 0 0 Save JACK Connections (adds clients 'jackpatch') true Client Renaming and Session Notes (adds client 'nsm-data') true Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok buttonBox accepted() NewSession accept() 248 254 157 274 buttonBox rejected() NewSession reject() 316 260 286 274 ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1665611335.9505882 agordejo-0.4.2/qtgui/designer/projectname.py0000644000175000017500000000404414321633110017651 0ustar00nilsnils# -*- coding: utf-8 -*- # Form implementation generated from reading ui file 'projectname.ui' # # Created by: PyQt5 UI code generator 5.14.1 # # WARNING! All changes made in this file will be lost! from PyQt5 import QtCore, QtGui, QtWidgets class Ui_ProjectName(object): def setupUi(self, ProjectName): ProjectName.setObjectName("ProjectName") ProjectName.resize(537, 84) self.gridLayout = QtWidgets.QGridLayout(ProjectName) self.gridLayout.setObjectName("gridLayout") self.buttonBox = QtWidgets.QDialogButtonBox(ProjectName) self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok) self.buttonBox.setObjectName("buttonBox") self.gridLayout.addWidget(self.buttonBox, 1, 1, 1, 1) self.labelError = QtWidgets.QLabel(ProjectName) self.labelError.setObjectName("labelError") self.gridLayout.addWidget(self.labelError, 2, 0, 1, 1) self.labelDescription = QtWidgets.QLabel(ProjectName) self.labelDescription.setObjectName("labelDescription") self.gridLayout.addWidget(self.labelDescription, 0, 0, 1, 1) self.name = QtWidgets.QLineEdit(ProjectName) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(3) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.name.sizePolicy().hasHeightForWidth()) self.name.setSizePolicy(sizePolicy) self.name.setObjectName("name") self.gridLayout.addWidget(self.name, 1, 0, 1, 1) self.retranslateUi(ProjectName) QtCore.QMetaObject.connectSlotsByName(ProjectName) def retranslateUi(self, ProjectName): _translate = QtCore.QCoreApplication.translate ProjectName.setWindowTitle(_translate("ProjectName", "Form")) self.labelError.setText(_translate("ProjectName", "Error Message")) self.labelDescription.setText(_translate("ProjectName", "Choose a project name. Use / for subdirectories")) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1665611335.9505882 agordejo-0.4.2/qtgui/designer/projectname.ui0000644000175000017500000000253614321633110017642 0ustar00nilsnils ProjectName 0 0 537 84 Form QDialogButtonBox::Cancel|QDialogButtonBox::Ok Error Message Choose a project name. Use / for subdirectories 3 0 ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1665611335.9505882 agordejo-0.4.2/qtgui/designer/settings.py0000644000175000017500000001174114321633110017204 0ustar00nilsnils# -*- coding: utf-8 -*- # Form implementation generated from reading ui file 'settings.ui' # # Created by: PyQt5 UI code generator 5.15.0 # # WARNING: Any manual changes made to this file will be lost when pyuic5 is # run again. Do not edit this file unless you know what you are doing. from PyQt5 import QtCore, QtGui, QtWidgets class Ui_Dialog(object): def setupUi(self, Dialog): Dialog.setObjectName("Dialog") Dialog.resize(626, 387) self.verticalLayout = QtWidgets.QVBoxLayout(Dialog) self.verticalLayout.setObjectName("verticalLayout") self.SettingsTab = QtWidgets.QTabWidget(Dialog) self.SettingsTab.setTabPosition(QtWidgets.QTabWidget.North) self.SettingsTab.setTabShape(QtWidgets.QTabWidget.Rounded) self.SettingsTab.setObjectName("SettingsTab") self.tab_2 = QtWidgets.QWidget() self.tab_2.setObjectName("tab_2") self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.tab_2) self.verticalLayout_3.setObjectName("verticalLayout_3") self.label_help_launcher_whitelist = QtWidgets.QLabel(self.tab_2) self.label_help_launcher_whitelist.setWordWrap(True) self.label_help_launcher_whitelist.setObjectName("label_help_launcher_whitelist") self.verticalLayout_3.addWidget(self.label_help_launcher_whitelist) self.launcherWhitelistPlainTextEdit = QtWidgets.QPlainTextEdit(self.tab_2) self.launcherWhitelistPlainTextEdit.setObjectName("launcherWhitelistPlainTextEdit") self.verticalLayout_3.addWidget(self.launcherWhitelistPlainTextEdit) self.label_help_launcher_blacklist = QtWidgets.QLabel(self.tab_2) self.label_help_launcher_blacklist.setWordWrap(True) self.label_help_launcher_blacklist.setObjectName("label_help_launcher_blacklist") self.verticalLayout_3.addWidget(self.label_help_launcher_blacklist) self.launcherBlacklistPlainTextEdit = QtWidgets.QPlainTextEdit(self.tab_2) self.launcherBlacklistPlainTextEdit.setObjectName("launcherBlacklistPlainTextEdit") self.verticalLayout_3.addWidget(self.launcherBlacklistPlainTextEdit) self.SettingsTab.addTab(self.tab_2, "") self.tab = QtWidgets.QWidget() self.tab.setObjectName("tab") self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.tab) self.verticalLayout_2.setObjectName("verticalLayout_2") self.label_help_programstart = QtWidgets.QLabel(self.tab) self.label_help_programstart.setWordWrap(True) self.label_help_programstart.setObjectName("label_help_programstart") self.verticalLayout_2.addWidget(self.label_help_programstart) self.label_help_path_rules = QtWidgets.QLabel(self.tab) self.label_help_path_rules.setWordWrap(True) self.label_help_path_rules.setObjectName("label_help_path_rules") self.verticalLayout_2.addWidget(self.label_help_path_rules) self.programPathsPlainTextEdit = QtWidgets.QPlainTextEdit(self.tab) self.programPathsPlainTextEdit.setObjectName("programPathsPlainTextEdit") self.verticalLayout_2.addWidget(self.programPathsPlainTextEdit) self.SettingsTab.addTab(self.tab, "") self.verticalLayout.addWidget(self.SettingsTab) self.buttonBox = QtWidgets.QDialogButtonBox(Dialog) self.buttonBox.setOrientation(QtCore.Qt.Horizontal) self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok) self.buttonBox.setObjectName("buttonBox") self.verticalLayout.addWidget(self.buttonBox) self.buttonBox.raise_() self.SettingsTab.raise_() self.retranslateUi(Dialog) self.SettingsTab.setCurrentIndex(0) self.buttonBox.accepted.connect(Dialog.accept) self.buttonBox.rejected.connect(Dialog.reject) QtCore.QMetaObject.connectSlotsByName(Dialog) def retranslateUi(self, Dialog): _translate = QtCore.QCoreApplication.translate Dialog.setWindowTitle(_translate("Dialog", "Settings")) self.label_help_launcher_whitelist.setText(_translate("Dialog", "Whitelist - Add executable names (not paths) to the program launcher. One executable per line.")) self.label_help_launcher_blacklist.setText(_translate("Dialog", "Blacklist - Exclude executable names (not paths) from the program launcher. One executable per line.")) self.SettingsTab.setTabText(self.SettingsTab.indexOf(self.tab_2), _translate("Dialog", "Launcher")) self.label_help_programstart.setText(_translate("Dialog", "For advanced users only! Add executable paths to the environment, just for Agordejo and NSM. Changes need a program restart afterwards. If you want your programs in the application launcher use the launcher tab.")) self.label_help_path_rules.setText(_translate("Dialog", "Add one absolute path to a directory (e.g. /home/user/audio-bin) per line. No wildcards. Trailing slashes/ don\'t matter.")) self.SettingsTab.setTabText(self.SettingsTab.indexOf(self.tab), _translate("Dialog", "$PATH")) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1665611335.9505882 agordejo-0.4.2/qtgui/designer/settings.ui0000644000175000017500000001013614321633110017166 0ustar00nilsnils Dialog 0 0 626 387 Settings QTabWidget::North QTabWidget::Rounded 0 Launcher Whitelist - Add executable names (not paths) to the program launcher. One executable per line. true Blacklist - Exclude executable names (not paths) from the program launcher. One executable per line. true $PATH For advanced users only! Add executable paths to the environment, just for Agordejo and NSM. Changes need a program restart afterwards. If you want your programs in the application launcher use the launcher tab. true Add one absolute path to a directory (e.g. /home/user/audio-bin) per line. No wildcards. Trailing slashes/ don't matter. true Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok buttonBox SettingsTab buttonBox accepted() Dialog accept() 248 254 157 274 buttonBox rejected() Dialog reject() 316 260 286 274 ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1665611335.9505882 agordejo-0.4.2/qtgui/designer/usermanual.py0000644000175000017500000000507514321633110017523 0ustar00nilsnils# -*- coding: utf-8 -*- # Form implementation generated from reading ui file 'usermanual.ui' # # Created by: PyQt5 UI code generator 5.11.3 # # WARNING! All changes made in this file will be lost! from PyQt5 import QtCore, QtGui, QtWidgets class Ui_TemplateUserManual(object): def setupUi(self, TemplateUserManual): TemplateUserManual.setObjectName("TemplateUserManual") TemplateUserManual.resize(776, 733) self.verticalLayout = QtWidgets.QVBoxLayout(TemplateUserManual) self.verticalLayout.setObjectName("verticalLayout") self.widget = QtWidgets.QWidget(TemplateUserManual) self.widget.setObjectName("widget") self.horizontalLayout = QtWidgets.QHBoxLayout(self.widget) self.horizontalLayout.setObjectName("horizontalLayout") self.home = QtWidgets.QPushButton(self.widget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.home.sizePolicy().hasHeightForWidth()) self.home.setSizePolicy(sizePolicy) self.home.setFlat(False) self.home.setObjectName("home") self.horizontalLayout.addWidget(self.home) self.back = QtWidgets.QPushButton(self.widget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.back.sizePolicy().hasHeightForWidth()) self.back.setSizePolicy(sizePolicy) self.back.setShortcut("Backspace") self.back.setObjectName("back") self.horizontalLayout.addWidget(self.back) spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) self.horizontalLayout.addItem(spacerItem) self.verticalLayout.addWidget(self.widget) self.textBrowser = QtWidgets.QTextBrowser(TemplateUserManual) self.textBrowser.setObjectName("textBrowser") self.verticalLayout.addWidget(self.textBrowser) self.retranslateUi(TemplateUserManual) QtCore.QMetaObject.connectSlotsByName(TemplateUserManual) def retranslateUi(self, TemplateUserManual): _translate = QtCore.QCoreApplication.translate TemplateUserManual.setWindowTitle(_translate("TemplateUserManual", "Form")) self.home.setText(_translate("TemplateUserManual", "Home")) self.back.setText(_translate("TemplateUserManual", "Back")) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1665611335.9505882 agordejo-0.4.2/qtgui/designer/usermanual.ui0000644000175000017500000000371414321633110017506 0ustar00nilsnils TemplateUserManual 0 0 776 733 Form 0 0 Home false 0 0 Back Backspace Qt::Horizontal 40 20 ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1665611335.9539216 agordejo-0.4.2/qtgui/eventloop.py0000644000175000017500000000553014321633110015556 0ustar00nilsnils#! /usr/bin/env python3 # -*- coding: utf-8 -*- """ Copyright 2022, Nils Hilbricht, Germany ( https://www.hilbricht.net ) This file is part of the Laborejo Software Suite ( https://www.laborejo.org ). This application is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . """ import logging; logger = logging.getLogger(__name__); logger.info("import") from PyQt5 import QtCore class EventLoop(object): def __init__(self): """The loop for all things GUI and controlling the GUI (e.g. by a control midi in port) By default use fastConnect. 0 ms means "if there is time". 10ms-20ms is smooth. 100ms is still ok. Influences everything. Control Midi In Latency, playback cursor scrolling smoothnes etc. But not realtime. This is not the realtime loop. Converting midi into instrument sounds or playing back sequenced midi data is not handled by this loop at all. Creating a non-qt class for the loop is an abstraction layer that enables the engine to work without modification for non-gui situations. In this case it will use its own loop, like python async etc. A qt event loop needs the qt-app started. Otherwise it will not run. We init the event loop outside of main but call start from the mainWindow. """ self.fastLoop = QtCore.QTimer() self.slowLoop = QtCore.QTimer() def fastConnect(self, function): self.fastLoop.timeout.connect(function) def slowConnect(self, function): self.slowLoop.timeout.connect(function) def fastDisconnect(self, function): """The function must be the exact instance that was registered""" self.fastLoop.timeout.disconnect(function) def slowDisconnect(self, function): """The function must be the exact instance that was registered""" self.slowLoop.timeout.disconnect(function) def start(self): """The event loop MUST be started after the Qt Application instance creation""" logger.info("Starting fast qt event loop") self.fastLoop.start(20) logger.info("Starting slow qt event loop") self.slowLoop.start(200) def stop(self): logger.info("Stopping fast qt event loop") self.fastLoop.stop() logger.info("Stopping slow qt event loop") self.slowLoop.stop() ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1665611335.9505882 agordejo-0.4.2/qtgui/helper.py0000644000175000017500000002212114321633110015015 0ustar00nilsnils#! /usr/bin/env python3 # -*- coding: utf-8 -*- """ Copyright 2022, Nils Hilbricht, Germany ( https://www.hilbricht.net ) This file is part of Laborejo ( https://www.laborejo.org ) This application is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . """ from PyQt5 import QtCore, QtGui, QtWidgets from hashlib import md5 def iconFromString(st, size=128): px = QtGui.QPixmap(size,size) color = stringToColor(st) px.fill(color) return QtGui.QIcon(px) def sizeof_fmt(num, suffix='B'): """https://stackoverflow.com/questions/1094841/reusable-library-to-get-human-readable-version-of-file-size""" for unit in ['','Ki','Mi','Gi','Ti','Pi','Ei','Zi']: if abs(num) < 1024.0: return "%3.1f%s%s" % (num, unit, suffix) num /= 1024.0 return "%.1f%s%s" % (num, 'Yi', suffix) def stringToColor(st): """Convert a string to QColor. Same string, same color Is used for group coloring""" if st: c = md5(st.encode()).hexdigest() return QtGui.QColor(int(c[0:9],16) % 255, int(c[10:19],16) % 255, int(c[20:29],16)% 255, 255) else: return QtGui.QColor(255,255,255,255) #Return White def invertColor(qcolor): r = 255 - qcolor.red() g = 255 - qcolor.green() b = 255 - qcolor.blue() return QtGui.QColor(r, g, b, qcolor.alpha()) def removeInstancesFromScene(qtGraphicsClass): """"Remove all instances of a qt class that implements .instances=[] from the QGraphicsScene. Don't use for items or anything in the notation view. This is used by the likes of the conductor only since they exist only once and gets redrawn completely each time.""" for instance in qtGraphicsClass.instances: instance.setParentItem(None) instance.scene().removeWhenIdle(instance) qtGraphicsClass.instances = [] def callContextMenu(listOfLabelsAndFunctions): menu = QtWidgets.QMenu() for text, function in listOfLabelsAndFunctions: if text == "separator": menu.addSeparator() else: a = QtWidgets.QAction(text, menu) menu.addAction(a) a.triggered.connect(function) pos = QtGui.QCursor.pos() pos.setY(pos.y() + 5) menu.exec_(pos) def stretchLine(qGraphicsLineItem, factor): line = qGraphicsLineItem.line() line.setLength(line.length() * factor) qGraphicsLineItem.setLine(line) def stretchRect(qGraphicsRectItem, factor): r = qGraphicsRectItem.rect() r.setRight(r.right() * factor) qGraphicsRectItem.setRect(r) class QHLine(QtWidgets.QFrame): def __init__(self): super().__init__() self.setFrameShape(QtWidgets.QFrame.HLine) self.setFrameShadow(QtWidgets.QFrame.Sunken) def setPaletteAndFont(qtApp): """Set our programs color This is in its own function because it is a annoying to scroll by that in init. http://doc.qt.io/qt-5/qpalette.html""" fPalBlue = QtGui.QPalette() fPalBlue.setColor(QtGui.QPalette.Disabled, QtGui.QPalette.Window, QtGui.QColor(32, 35, 39)) fPalBlue.setColor(QtGui.QPalette.Active, QtGui.QPalette.Window, QtGui.QColor(37, 40, 45)) fPalBlue.setColor(QtGui.QPalette.Inactive, QtGui.QPalette.Window, QtGui.QColor(37, 40, 45)) fPalBlue.setColor(QtGui.QPalette.Disabled, QtGui.QPalette.WindowText, QtGui.QColor(89, 95, 104)) fPalBlue.setColor(QtGui.QPalette.Active, QtGui.QPalette.WindowText, QtGui.QColor(223, 237, 255)) fPalBlue.setColor(QtGui.QPalette.Inactive, QtGui.QPalette.WindowText, QtGui.QColor(223, 237, 255)) fPalBlue.setColor(QtGui.QPalette.Disabled, QtGui.QPalette.Base, QtGui.QColor(48, 53, 60)) fPalBlue.setColor(QtGui.QPalette.Active, QtGui.QPalette.Base, QtGui.QColor(55, 61, 69)) fPalBlue.setColor(QtGui.QPalette.Inactive, QtGui.QPalette.Base, QtGui.QColor(55, 61, 69)) fPalBlue.setColor(QtGui.QPalette.Disabled, QtGui.QPalette.AlternateBase, QtGui.QColor(60, 64, 67)) fPalBlue.setColor(QtGui.QPalette.Active, QtGui.QPalette.AlternateBase, QtGui.QColor(69, 73, 77)) fPalBlue.setColor(QtGui.QPalette.Inactive, QtGui.QPalette.AlternateBase, QtGui.QColor(69, 73, 77)) fPalBlue.setColor(QtGui.QPalette.Disabled, QtGui.QPalette.ToolTipBase, QtGui.QColor(182, 193, 208)) fPalBlue.setColor(QtGui.QPalette.Active, QtGui.QPalette.ToolTipBase, QtGui.QColor(182, 193, 208)) fPalBlue.setColor(QtGui.QPalette.Inactive, QtGui.QPalette.ToolTipBase, QtGui.QColor(182, 193, 208)) fPalBlue.setColor(QtGui.QPalette.Disabled, QtGui.QPalette.ToolTipText, QtGui.QColor(42, 44, 48)) fPalBlue.setColor(QtGui.QPalette.Active, QtGui.QPalette.ToolTipText, QtGui.QColor(42, 44, 48)) fPalBlue.setColor(QtGui.QPalette.Inactive, QtGui.QPalette.ToolTipText, QtGui.QColor(42, 44, 48)) fPalBlue.setColor(QtGui.QPalette.Disabled, QtGui.QPalette.Text, QtGui.QColor(96, 103, 113)) fPalBlue.setColor(QtGui.QPalette.Active, QtGui.QPalette.Text, QtGui.QColor(210, 222, 240)) fPalBlue.setColor(QtGui.QPalette.Inactive, QtGui.QPalette.Text, QtGui.QColor(210, 222, 240)) fPalBlue.setColor(QtGui.QPalette.Disabled, QtGui.QPalette.Button, QtGui.QColor(51, 55, 62)) fPalBlue.setColor(QtGui.QPalette.Active, QtGui.QPalette.Button, QtGui.QColor(59, 63, 71)) fPalBlue.setColor(QtGui.QPalette.Inactive, QtGui.QPalette.Button, QtGui.QColor(59, 63, 71)) fPalBlue.setColor(QtGui.QPalette.Disabled, QtGui.QPalette.ButtonText, QtGui.QColor(98, 104, 114)) fPalBlue.setColor(QtGui.QPalette.Active, QtGui.QPalette.ButtonText, QtGui.QColor(210, 222, 240)) fPalBlue.setColor(QtGui.QPalette.Inactive, QtGui.QPalette.ButtonText, QtGui.QColor(210, 222, 240)) fPalBlue.setColor(QtGui.QPalette.Disabled, QtGui.QPalette.BrightText, QtGui.QColor(255, 255, 255)) fPalBlue.setColor(QtGui.QPalette.Active, QtGui.QPalette.BrightText, QtGui.QColor(255, 255, 255)) fPalBlue.setColor(QtGui.QPalette.Inactive, QtGui.QPalette.BrightText, QtGui.QColor(255, 255, 255)) fPalBlue.setColor(QtGui.QPalette.Disabled, QtGui.QPalette.Light, QtGui.QColor(59, 64, 72)) fPalBlue.setColor(QtGui.QPalette.Active, QtGui.QPalette.Light, QtGui.QColor(63, 68, 76)) fPalBlue.setColor(QtGui.QPalette.Inactive, QtGui.QPalette.Light, QtGui.QColor(63, 68, 76)) fPalBlue.setColor(QtGui.QPalette.Disabled, QtGui.QPalette.Midlight, QtGui.QColor(48, 52, 59)) fPalBlue.setColor(QtGui.QPalette.Active, QtGui.QPalette.Midlight, QtGui.QColor(51, 56, 63)) fPalBlue.setColor(QtGui.QPalette.Inactive, QtGui.QPalette.Midlight, QtGui.QColor(51, 56, 63)) fPalBlue.setColor(QtGui.QPalette.Disabled, QtGui.QPalette.Dark, QtGui.QColor(18, 19, 22)) fPalBlue.setColor(QtGui.QPalette.Active, QtGui.QPalette.Dark, QtGui.QColor(20, 22, 25)) fPalBlue.setColor(QtGui.QPalette.Inactive, QtGui.QPalette.Dark, QtGui.QColor(20, 22, 25)) fPalBlue.setColor(QtGui.QPalette.Disabled, QtGui.QPalette.Mid, QtGui.QColor(28, 30, 34)) fPalBlue.setColor(QtGui.QPalette.Active, QtGui.QPalette.Mid, QtGui.QColor(32, 35, 39)) fPalBlue.setColor(QtGui.QPalette.Inactive, QtGui.QPalette.Mid, QtGui.QColor(32, 35, 39)) fPalBlue.setColor(QtGui.QPalette.Disabled, QtGui.QPalette.Shadow, QtGui.QColor(13, 14, 16)) fPalBlue.setColor(QtGui.QPalette.Active, QtGui.QPalette.Shadow, QtGui.QColor(15, 16, 18)) fPalBlue.setColor(QtGui.QPalette.Inactive, QtGui.QPalette.Shadow, QtGui.QColor(15, 16, 18)) fPalBlue.setColor(QtGui.QPalette.Disabled, QtGui.QPalette.Highlight, QtGui.QColor(32, 35, 39)) fPalBlue.setColor(QtGui.QPalette.Active, QtGui.QPalette.Highlight, QtGui.QColor(14, 14, 17)) fPalBlue.setColor(QtGui.QPalette.Inactive, QtGui.QPalette.Highlight, QtGui.QColor(27, 28, 33)) fPalBlue.setColor(QtGui.QPalette.Disabled, QtGui.QPalette.HighlightedText, QtGui.QColor(89, 95, 104)) fPalBlue.setColor(QtGui.QPalette.Active, QtGui.QPalette.HighlightedText, QtGui.QColor(217, 234, 253)) fPalBlue.setColor(QtGui.QPalette.Inactive, QtGui.QPalette.HighlightedText, QtGui.QColor(223, 237, 255)) fPalBlue.setColor(QtGui.QPalette.Disabled, QtGui.QPalette.Link, QtGui.QColor(79, 100, 118)) fPalBlue.setColor(QtGui.QPalette.Active, QtGui.QPalette.Link, QtGui.QColor(156, 212, 255)) fPalBlue.setColor(QtGui.QPalette.Inactive, QtGui.QPalette.Link, QtGui.QColor(156, 212, 255)) fPalBlue.setColor(QtGui.QPalette.Disabled, QtGui.QPalette.LinkVisited, QtGui.QColor(51, 74, 118)) fPalBlue.setColor(QtGui.QPalette.Active, QtGui.QPalette.LinkVisited, QtGui.QColor(64, 128, 255)) fPalBlue.setColor(QtGui.QPalette.Inactive, QtGui.QPalette.LinkVisited, QtGui.QColor(64, 128, 255)) qtApp.setPalette(fPalBlue) font = QtGui.QFont("DejaVu Sans", 10) font.setPixelSize(12) qtApp.setFont(font) return fPalBlue ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1665611335.9539216 agordejo-0.4.2/qtgui/jacktransport.py0000644000175000017500000001315614321633110016433 0ustar00nilsnils#! /usr/bin/env python3 # -*- coding: utf-8 -*- """ Copyright 2022, Nils Hilbricht, Germany ( https://www.hilbricht.net ) This file is part of the Laborejo Software Suite ( https://www.laborejo.org ), This application is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . """ import logging; logger = logging.getLogger(__name__); logger.info("import") #Standard Library from datetime import timedelta #Third Party from PyQt5 import QtCore, QtGui, QtWidgets #Engine import engine.api as api class JackTransportControls(object): """Singleton object for code-structure reasons""" def __init__(self, mainWindow): self.mainWindow = mainWindow self.ui = self.mainWindow.ui self.visible = None self.currentMaximumInSeconds = self.ui.jackTransportMaxTime.value() * 60 #widget is in minutes self.resolutionFactor = 10 #Around 10 seems to be the maximum. The progressbar will still "Jump" above that. self.ui.jackTransportPlayPause.setCheckable(True) self.ui.jackTransportPlayPause.setText("") self.ui.jackTransportPlayPause.setIcon(QtGui.QIcon(':playpause.png')) self.ui.jackTransportRewind.setText("") self.ui.jackTransportRewind.setIcon(QtGui.QIcon(':tostart.png')) api.callbacks.sessionOpenReady.append(self.show) api.callbacks.sessionClosed.append(self.hide) api.callbacks.setPlaybackSeconds.append(self._react_playbackSeconds) api.callbacks.dataClientTimelineMaximumDurationChanged.append(self._callback_MaximumChanged) self.ui.jackTransportMaxTime.valueChanged.connect(self._gui_MaximumChanged) self.ui.jackTransportPlayPause.clicked.connect(api.jackClient.playPause) self.ui.jackTransportRewind.clicked.connect(api.jackClient.rewind) #Maximum Resolution of 100 is not enough. It is too "steppy" self.ui.jackTransportTimeline.setMaximum(100 * self.resolutionFactor) self.ui.jackTransportTimeline.reset() self.ui.jackTransportTimeline.mousePressEvent = self._timelineMousePressEvent self.ui.jackTransportTimeline.mouseMoveEvent = self._timelineMouseMoveEvent self.hide() def hide(self, *args): self.ui.jackTransportControls.hide() self.visible = False def show(self, *args): self.ui.jackTransportControls.show() self.visible = True def _gui_MaximumChanged(self, minutes:int): """Send to the api. Our own widget will get changed via callback.""" api.setTimelineMaximumDuration(minutes) def _callback_MaximumChanged(self, minutes:int): """Get a new value from the api. This is either from loading or a roundtrip from our own widget change. Can be None if nsm-data left the session. In this case we just ignore and continue.""" if minutes: #0 is not possible. May be None. self.currentMaximumInSeconds = minutes * 60 self.ui.jackTransportMaxTime.blockSignals(True) self.ui.jackTransportMaxTime.setValue(minutes) self.ui.jackTransportMaxTime.blockSignals(False) def _react_playbackSeconds(self, seconds, isTransportRunning): if self.visible: #optimisation realPercent = seconds / self.currentMaximumInSeconds progressPercent = realPercent * 100 * self.resolutionFactor #100 because that is 0.xy -> xy% ) progressPercent = int(progressPercent) prettyTime = str(timedelta(seconds=int(seconds))) #timedelta without int will print microseconds if self.currentMaximumInSeconds < 3600: #less than an hour prettyTime = prettyTime[2:] if progressPercent <= 100 * self.resolutionFactor: self.ui.jackTransportTimeline.setValue(progressPercent) else: self.ui.jackTransportTimeline.setValue(100 * self.resolutionFactor) if isTransportRunning: self.ui.jackTransportPlayPause.setChecked(True) self.ui.jackTransportTimeline.setFormat("â–¶ " + prettyTime) #we don't use the Qt format substitutions. We just set a fixed value. else: self.ui.jackTransportPlayPause.setChecked(False) self.ui.jackTransportTimeline.setFormat("â–  " + prettyTime) def _timelineMousePressEvent(self, event): """The positions are in pixels. If the window is different we get different numbers. Normalize first.""" percentage = event.x() / self.ui.jackTransportTimeline.width() #x is relative to the widget. same as localPos().x() seconds = percentage * self.currentMaximumInSeconds api.jackClient.seek(seconds) def _timelineMouseMoveEvent(self, event): """We did not set any qt-flags on this widget so mouseMoveEvent only works when the left mouse button is down, and not just simply by hovering. Which is exactly what we want: dragging""" percentage = event.x() / self.ui.jackTransportTimeline.width() #x is relative to the widget. same as localPos().x() seconds = percentage * self.currentMaximumInSeconds api.jackClient.seek(seconds) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1665611335.9539216 agordejo-0.4.2/qtgui/mainwindow.py0000644000175000017500000006254314321633110015726 0ustar00nilsnils#! /usr/bin/env python3 # -*- coding: utf-8 -*- """ Copyright 2022, Nils Hilbricht, Germany ( https://www.hilbricht.net ) This file is part of the Laborejo Software Suite ( https://www.laborejo.org ). This application is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . """ import logging; logger = logging.getLogger(__name__); logger.info("import") #Standard Library from sys import exit as sysexit #Third Party from PyQt5 import QtCore, QtGui, QtWidgets logger.info(f"PyQt Version: {QtCore.PYQT_VERSION_STR}") #Engine from engine.config import METADATA #includes METADATA only. No other environmental setup is executed. from engine.start import PATHS, qtApp import engine.api as api #This loads the engine and starts a session. #Qt from .systemtray import SystemTray from .eventloop import EventLoop from .designer.mainwindow import Ui_MainWindow from .usermanual import UserManual from .changelog import Changelog from .helper import setPaletteAndFont from .helper import iconFromString from .sessiontreecontroller import SessionTreeController from .opensessioncontroller import OpenSessionController from .projectname import ProjectNameWidget from .addclientprompt import askForExecutable, updateWordlist from .waitdialog import WaitDialog from .resources import * from .settings import SettingsDialog from .jacktransport import JackTransportControls from .movesessionroot import xdgVersionChange from .startchooserunningnsmd import checkForRunningNsmd api.eventLoop = EventLoop() #Setup the translator before classes are set up. Otherwise we can't use non-template translation. #to test use LANGUAGE=de_DE.UTF-8 ./agordejo language = QtCore.QLocale().languageToString(QtCore.QLocale().language()) logger.info("{}: Language set to {}".format(METADATA["name"], language)) if language in METADATA["supportedLanguages"]: translator = QtCore.QTranslator() translator.load(METADATA["supportedLanguages"][language], ":/translations/") #colon to make it a resource URL qtApp.installTranslator(translator) else: """silently fall back to English by doing nothing""" def nothing(*args): pass class RecentlyOpenedSessions(object): """Class to make it easier handle recently opened session with qt settings, type conversions limiting the size of the list and uniqueness""" def __init__(self): self.data = [] def load(self, dataFromQtSettings): """Handle qt settings load. triggered by restoreWindowSettings in mainWindow init""" if dataFromQtSettings: for name in dataFromQtSettings: self.add(name) def add(self, nsmSessionName:str): if nsmSessionName in self.data: #Just sort self.data.remove(nsmSessionName) self.data.append(nsmSessionName) return self.data.append(nsmSessionName) if len(self.data) > 3: self.data.pop(0) assert len(self.data) <= 3, len(self.data) def get(self)->list: """List of nsmSessionName strings""" sessionList = api.sessionList() self.data = [n for n in self.data if n in sessionList] return self.data def last(self)->str: """Return the last active session. Useful for continue-mode command line arg. """ if self.data: return self.get()[-1] else: return None class MainWindow(QtWidgets.QMainWindow): def __init__(self): super().__init__() self.qtApp = qtApp self.qtApp.setWindowIcon(QtGui.QIcon(":icon.png")) #non-template part of the program self.qtApp.setApplicationName(f"{METADATA['name']}") self.qtApp.setApplicationDisplayName(f"{METADATA['name']}") self.qtApp.setOrganizationName("Laborejo Software Suite") self.qtApp.setOrganizationDomain("laborejo.org") self.qtApp.setApplicationVersion(METADATA["version"]) QtGui.QIcon.setThemeName("hicolor") #audio applications can be found here. We have no need for other icons. logger.info("Init MainWindow") #QtGui.QIcon.setFallbackThemeName("hicolor") #only one, not a list. This is the fallback if the theme can't be found. Not if icons can't be found in a theme. #iconPaths = QtGui.QIcon.themeSearchPaths() #iconPaths += ["/usr/share/icons/hicolor", "/usr/share/pixmaps"] #QtGui.QIcon.setThemeSearchPaths(iconPaths) logger.info(f"Program icons path: {QtGui.QIcon.themeSearchPaths()}, {QtGui.QIcon.themeName()}") QtCore.QT_TRANSLATE_NOOP("NOOPEngineStrings", "ERROR! Copied session data is different from source session. Please check you data!") QtCore.QT_TRANSLATE_NOOP("NOOPEngineStrings", "Veryfying file-integrity. This may take a while...") #Set up the user interface from Designer and other widgets self.ui = Ui_MainWindow() self.ui.setupUi(self) self.fPalBlue = setPaletteAndFont(self.qtApp) assert self.ui.tabbyCat.currentIndex() == 0, self.ui.tabbyCat.currentIndex() # this is critical. If you left the Qt Designer with the wrong tab open this is the error that happens. It will trigger the tab changed later that will go wrong because setup is not complete yet and you'll get AttributeError self.userManual = UserManual(mainWindow=self) self.changelog = Changelog(mainWindow=self) self.ui.mainPageSwitcher.setCurrentIndex(0) #1 is messageLabel 0 is the tab widget SettingsDialog.loadFromSettingsAndSendToEngine() #set blacklist, whitelist for programdatabase and addtional executable paths for environment #TODO: Hide information tab until the feature is ready self.ui.tabbyCat.removeTab(1) self.programIcons = {} #executableName:QIcon. Filled by self._updateGUIWithCachedPrograms which calls _updateIcons self.sessionController = SessionController(mainWindow=self) self.systemTray = SystemTray(mainWindow=self) self.connectMenu() self.recentlyOpenedSessions = RecentlyOpenedSessions() #Setup JackTransportControls Widget. It configures itself on init: self.jackTransportControls = JackTransportControls(mainWindow = self) #not a widget, just an object #self.ui.stack_loaded_session is only visible when there is a loaded session and the full view tab is active #we link the session context menu to the session menu menu. self.ui.stack_loaded_session.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) self.ui.stack_loaded_session.customContextMenuRequested.connect(self.customContextMenu) #nsmd 1.6.0 xdgVersionChange(self.qtApp) #may present a blocking dialog, may do nothing. #If another Agordejo is running, and no --nsm-url was set, we will never reach this point. checkForRunningNsmd(self.qtApp, PATHS) #may present a blocking dialog, may do nothing. Injects nsm url into PATHS #Api Callbacks api.callbacks.sessionClosed.append(self.reactCallback_sessionClosed) api.callbacks.sessionOpenReady.append(self.reactCallback_sessionOpen) api.callbacks.singleInstanceActivateWindow.append(self.activateAndRaise) #Use our wait-dialog to build the program database. logger.info("Instructing engine to build program database") QtCore.QTimer.singleShot(0, self.updateProgramDatabase) #includes self._updateGUIWithCachedPrograms() #Starting the engine sends initial GUI data. Every window and widget must be ready to receive callbacks here api.eventLoop.start() api.startEngine() self.restoreWindowSettings() #populates recentlyOpenedSessions if PATHS["startHidden"] and self.systemTray.available: logger.info("Starting hidden") self.toggleVisible(force=False) else: logger.info("Starting visible") self.toggleVisible(force=True) if PATHS["continueLastSession"]: #will be None if --load-session=NAME was given as command line parameter and --continue on top. continueSession = self.recentlyOpenedSessions.last() if continueSession: logger.info(f"Got continue session as command line parameter. Opening: {continueSession}") api.sessionOpen(continueSession) else: logger.info(f"Got continue session as command line parameter but there is no session available.") if not self.isVisible(): text = QtCore.QCoreApplication.translate("mainWindow", "Agordejo ready") self.systemTray.showMessage("Agordejo", text, QtWidgets.QSystemTrayIcon.Information, 2000) #title, message, icon, timeout. #has messageClicked() signal. logger.info("Ready for user input. Exec_ Qt.") qtApp.exec_() #No code after exec_ except atexit def tabtest(self): import subprocess from time import sleep #xdotool search --name xeyes #xdotool search --pid 12345 subprocess.Popen(["patchage"], shell=True, stdin=None, stdout=None, stderr=None, close_fds=True) #parameters are for not waiting sleep(1) result = subprocess.run(["xdotool", "search", "--name", "patchage"], stdout=subprocess.PIPE).stdout.decode('utf-8') if "\n" in result: windowID = int(result.split("\n")[0]) else: windowID = int(result) window = QtGui.QWindow.fromWinId(int(windowID)) window.setFlags(QtCore.Qt.FramelessWindowHint) widget = QtWidgets.QWidget.createWindowContainer(window) self.ui.tabbyCat.addTab(widget, "Patchage") def hideEvent(self, event): if self.systemTray.available: super().hideEvent(event) else: event.ignore() def activateAndRaise(self): self.toggleVisible(force=True) getattr(self, "raise")() #raise is python syntax. Can't use that directly self.activateWindow() text = QtCore.QCoreApplication.translate("mainWindow", "Another GUI tried to launch.") self.systemTray.showMessage("Agordejo", text, QtWidgets.QSystemTrayIcon.Information, 2000) #title, message, icon, timeout. #has messageClicked() signal. def _updateGUIWithCachedPrograms(self): logger.info("Updating entire program with cached program lists") updateWordlist() #addclientprompt.py self._updateIcons() self.sessionController.openSessionController.launcherTable.buildPrograms() def _updateIcons(self): logger.info("Creating icon database") self.programIcons.clear() for entry in api.getNsmClients(): exe = entry["agordejoExec"] icon = None if "agordejoIconPath" in entry and entry["agordejoIconPath"]: #not null icon = QtGui.QIcon(entry["agordejoIconPath"]) if not icon or icon.isNull(): #the DB cache could be wrong. Deinstalled a program and not updated the DB. if "icon" in entry: icon = QtGui.QIcon.fromTheme(entry["icon"]) else: icon = QtGui.QIcon.fromTheme(exe) if icon.isNull(): icon = QtGui.QIcon.fromTheme(exe) if icon.isNull(): icon = iconFromString(exe) assert not icon.isNull() self.programIcons[exe] = icon def updateProgramDatabase(self): """Display a progress-dialog that waits for the database to be build. Automatically called on first start or when instructed by the user""" text = QtCore.QCoreApplication.translate("mainWindow", "Updating Program Database.\nThank you for your patience.\nIf progress freezes please kill and restart the whole program.") settings = QtCore.QSettings("LaborejoSoftwareSuite", METADATA["shortName"]) if settings.contains("engineCache"): #remove haywire data from previous releases settings.remove("engineCache") logger.info("Asking api to generate program and icon database while waiting") diag = WaitDialog(self, text, api.buildSystemPrograms) #save in local var to keep alive self._updateGUIWithCachedPrograms() def reactCallback_sessionClosed(self): self.setWindowTitle("") def reactCallback_sessionOpen(self, nsmSessionExportDict): self.setWindowTitle(nsmSessionExportDict["nsmSessionName"]) self.recentlyOpenedSessions.add(nsmSessionExportDict["nsmSessionName"]) def toggleVisible(self, force:bool=None): if force is None: newState = not self.isVisible() else: newState = force if newState: logger.info("Show") self.restoreWindowSettings() self.show() self.setVisible(True) else: logger.info("Hide") self.storeWindowSettings() self.hide() self.setVisible(False) #self.systemTray.buildContextMenu() #Don't. This crashes Qt through some delayed execution of who knows what. Workaround: tray context menu now say "Show/Hide" and not only the actual state. def toggleSplitSessionView(self, state:bool): self.ui.actionSplit_Session_View_The_Other_Way.blockSignals(True) if state: orientation = QtCore.Qt.Vertical #this is weird. Is the Qt enum wrongly labeled? This splits in an upper and lower part. else: orientation = QtCore.Qt.Horizontal self.ui.actionSplit_Session_View_The_Other_Way.setChecked(state) #This funtion can be called by functions, e.g. restoreWindowSettings on startup. self.ui.hSplitterLauncherClients.setOrientation(orientation) self.ui.actionSplit_Session_View_The_Other_Way.blockSignals(False) def _askBeforeQuit(self, nsmSessionName): """If you quit while in a session ask what to do. The TrayIcon context menu uses different functions and directly acts, without a question""" text = QtCore.QCoreApplication.translate("AskBeforeQuit", "About to quit but session {} still open").format(nsmSessionName) informativeText = QtCore.QCoreApplication.translate("AskBeforeQuit", "Do you want to save?") title = QtCore.QCoreApplication.translate("AskBeforeQuit", "About to quit") box = QtWidgets.QMessageBox() box.setWindowFlag(QtCore.Qt.Popup, True) box.setIcon(box.Warning) box.setText(text) box.setWindowTitle(title) box.setInformativeText(informativeText) stay = box.addButton(QtCore.QCoreApplication.translate("AskBeforeQuit", "Don't Quit"), box.RejectRole) #0 box.addButton(QtCore.QCoreApplication.translate("AskBeforeQuit", "Save"), box.YesRole) #1 box.addButton(QtCore.QCoreApplication.translate("AskBeforeQuit", "Discard Changes"), box.DestructiveRole) #2 box.setDefaultButton(stay) ret = box.exec() #Return values are NOT the button roles. if ret == 2: logger.info("Quit: Don't save.") api.sessionAbort(blocking=True) return True elif ret == 1: logger.info("Quit: Close and Save. Waiting for clients to close.") api.sessionClose(blocking=True) return True else: #Escape, window close through WM etc. logger.info("Quit: Changed your mind, stay in session.") return False def abortAndQuit(self): """For the context menu. A bit A bit redundant, but that is ok :)""" api.sessionAbort(blocking=True) self._callSysExit() def closeAndQuit(self): api.sessionClose(blocking=True) self._callSysExit() def _callSysExit(self): """The process of quitting After sysexit the atexit handler gets called. That closes nsmd, if we started ourselves. """ self.storeWindowSettings() sysexit(0) #directly afterwards @atexit is handled, but this function does not return. logging.error("Code executed after sysexit. This message should not have been visible.") def menuRealQuit(self): """Called by the menu. The TrayIcon provides another method of quitting that does not call this function, but it will call _actualQuit. """ if api.ourOwnServer() and api.currentSession(): result = self._askBeforeQuit(api.currentSession()) else: result = True if result: self._callSysExit() #contains self.storeWindowSettings def closeEvent(self, event): """Window manager close. Ignore. We use it to send the GUI into hiding.""" event.ignore() self.toggleVisible(force=False) def connectMenu(self): #Control self.ui.actionManual.triggered.connect(self.userManual.show) self.ui.actionChangelog.triggered.connect(self.changelog.show) self.ui.actionRebuild_Program_Database.triggered.connect(self.updateProgramDatabase) self.ui.actionSettings.triggered.connect(self._reactMenu_settings) self.ui.actionSplit_Session_View_The_Other_Way.toggled.connect(self.toggleSplitSessionView) self.ui.actionHide_in_System_Tray.triggered.connect(lambda: self.toggleVisible(force=False)) self.ui.actionMenuQuit.triggered.connect(self.menuRealQuit) def _reactMenu_settings(self): widget = SettingsDialog(self) #blocks until closed if widget.success: self.updateProgramDatabase() def customContextMenu(self, qpoint): pos = QtGui.QCursor.pos() pos.setY(pos.y() + 5) self.ui.menuSession.exec_(pos) def storeWindowSettings(self): """Window state is not saved in the real save file. That would lead to portability problems between computers, like different screens and resolutions. For convenience that means we just use the damned qt settings and save wherever qt wants. bottom line: get a tiling window manager. """ settings = QtCore.QSettings("LaborejoSoftwareSuite", METADATA["shortName"]) settings.setValue("geometry", self.saveGeometry()) settings.setValue("windowState", self.saveState()) #settings.setValue("visible", self.isVisible()) Deprecated. see restoreWindowSettings settings.setValue("recentlyOpenedSessions", self.recentlyOpenedSessions.get()) settings.setValue("tab", self.ui.tabbyCat.currentIndex()) settings.setValue("sessionSplitOrientationNonDefault", self.ui.actionSplit_Session_View_The_Other_Way.isChecked()) def restoreWindowSettings(self): """opposite of storeWindowSettings. Read there.""" logger.info("Restoring window settings, geometry and recently opened session") settings = QtCore.QSettings("LaborejoSoftwareSuite", METADATA["shortName"]) actions = { "geometry":self.restoreGeometry, "windowState":self.restoreState, "recentlyOpenedSessions":self.recentlyOpenedSessions.load, "tab": lambda i: self.ui.tabbyCat.setCurrentIndex(int(i)), "sessionSplitOrientationNonDefault": lambda b: self.toggleSplitSessionView(bool(b)), } types = { "recentlyOpenedSessions": list, "tab": int, "sessionSplitOrientationNonDefault": bool, } for key in settings.allKeys(): if key in actions: #if not it doesn't matter. this is all uncritical. if key in types: actions[key](settings.value(key, type=types[key])) else: actions[key](settings.value(key)) #Deprecated. Always open the GUI when started normally, saving minimzed has little value. #Instead we introduced a command line options and .desktop option to auto-load the last session and start Agordejo GUI hidden. """ if self.systemTray.available and settings.contains("visible") and settings.value("visible") == "false": self.setVisible(False) else: self.setVisible(True) #This is also the default state if there is no config """ class SessionController(object): """Controls the StackWidget that contains the Session Tree and Opened Session/Client. Can be controlled from up and down the hierarchy. """ def __init__(self, mainWindow): super().__init__() self.mainWindow = mainWindow self.ui = self.mainWindow.ui self.sessionTreeController = SessionTreeController(mainWindow=mainWindow) self.openSessionController = OpenSessionController(mainWindow=mainWindow) self._connectMenu() #Callbacks api.callbacks.sessionOpenReady.append(lambda nsmSessionExportDict: self._switch("open")) #When loading ist done. This takes a while when non-nsm clients are in the session api.callbacks.sessionClosed.append(lambda: self._setMenuEnabled(None)) api.callbacks.sessionClosed.append(lambda: self._switch("choose")) #The rest is handled by the widget itself. It keeps itself updated, no matter if visible or not. api.callbacks.sessionOpenReady.append(lambda nsmSessionExportDict: self._setMenuEnabled(nsmSessionExportDict)) api.callbacks.sessionOpenLoading.append(lambda nsmSessionExportDict: self._switch("open")) #Convenience Signals to directly disable the client messages on gui instruction. #This is purely for speed and preventing the user from sending a signal while the session is shutting down self.mainWindow.ui.actionSessionAbort.triggered.connect(lambda: self._setMenuEnabled(None)) self.mainWindow.ui.actionSessionSaveAndClose.triggered.connect(lambda: self._setMenuEnabled(None)) #GUI signals self.mainWindow.ui.tabbyCat.currentChanged.connect(self._activeTabChanged) #self._activeTabChanged(self.mainWindow.ui.tabbyCat.currentIndex()) def _activeTabChanged(self, index:int): """index 0 is open session, 1 is info etc""" if index == 0: #detailed state = bool(api.currentSession()) else: #quick and information and future tabs state = False self.ui.menuClientNameId.menuAction().setVisible(state) #already deactivated self.ui.menuSession.menuAction().setVisible(state) #already deactivated #It is not enough to disable the menu itself. Shortcuts will still work. We need the children! for action in self.ui.menuSession.actions(): action.setEnabled(state) if state and not self.openSessionController.clientTabe.clientsTreeWidget.currentItem(): state = False #we wanted to activate, but there is no client selected. for action in self.ui.menuClientNameId.actions(): action.setEnabled(state) def _connectMenu(self): #Session #Only Active when a session is currently available self.ui.actionSessionSave.triggered.connect(api.sessionSave) self.ui.actionSessionAbort.triggered.connect(api.sessionAbort) self.ui.actionSessionSaveAs.triggered.connect(self._reactMenu_SaveAs) #NSM "Duplicate" self.ui.actionSessionSaveAndClose.triggered.connect(api.sessionClose) self.ui.actionShow_All_Clients.triggered.connect(api.clientShowAll) self.ui.actionHide_All_Clients.triggered.connect(api.clientHideAll) self.ui.actionSessionAddClient.triggered.connect(lambda: askForExecutable(self.mainWindow)) #Prompt version def _reactMenu_SaveAs(self): """Only when a session is open. We could either check the session controller or the simple one for the name.""" currentName = api.currentSession() assert currentName widget = ProjectNameWidget(parent=self.mainWindow, startwith=currentName+"-new") if widget.result: api.sessionSaveAs(widget.result) def _setMenuEnabled(self, nsmSessionExportDictOrNone): """We receive the sessionDict or None""" state = bool(nsmSessionExportDictOrNone) if state: self.ui.menuSession.setTitle(nsmSessionExportDictOrNone["nsmSessionName"]) self.ui.menuSession.menuAction().setVisible(True) self.ui.menuClientNameId.menuAction().setVisible(True) #session controller might disable that else: self.ui.menuSession.setTitle("Session") self.ui.menuSession.menuAction().setVisible(False) self.ui.menuClientNameId.menuAction().setVisible(False) #already deactivated #self.ui.menuSession.setEnabled(state) #It is not enough to disable the menu itself. Shortcuts will still work. for action in self.ui.menuSession.actions(): action.setEnabled(state) #Maybe the tab state overrules everything self._activeTabChanged(self.mainWindow.ui.tabbyCat.currentIndex()) def _switch(self, page:str): """Only called by the sub-controllers. For example when an existing session gets opened""" if page == "choose": pageIndex = 0 elif page == "open": pageIndex = 1 else: raise ValueError(f"_switch accepts choose or open, not {page}") self.mainWindow.ui.detailedStackedWidget.setCurrentIndex(pageIndex) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1665611335.9539216 agordejo-0.4.2/qtgui/movesessionroot.py0000644000175000017500000002150414321633110017020 0ustar00nilsnils#! /usr/bin/env python3 # -*- coding: utf-8 -*- """ Copyright 2022, Nils Hilbricht, Germany ( https://www.hilbricht.net ) This file is part of the Laborejo Software Suite ( https://www.laborejo.org ). This application is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . """ import logging; logger = logging.getLogger(__name__); logger.info("import") #Standard Library import pathlib from os import getenv, listdir, access, W_OK, R_OK, rmdir from subprocess import check_output from shutil import move as shutil_move from sys import exit as sysexit from sys import argv as sysargv from uuid import uuid4 #Third Party from PyQt5 import QtCore, QtGui, QtWidgets logger.info(f"PyQt Version: {QtCore.PYQT_VERSION_STR}") def nsmVersionGreater160()->bool: maj, min, patch = check_output(["nsmd", "--version"]).decode().split(" ")[1].rstrip("\n").split(".") maj = int(maj) min = int(min) if maj <= 1 and min < 6: return False else: return True def xdgVersionChange(qtApp): """Before we start the engine: nsmd >= 1.6.0 changed the default session root from ~/NSM Sessions to XDG. It will still prioritize the old path, if found. We ask the user to move his files to the correct location now so that the engine can use it this run. This check can be made independently from any --session-root parameter and without nsmd started. """ if any("--session-root" in param for param in sysargv): #This is not our problem anymore. return if not nsmVersionGreater160(): return #see docstring parent = qtApp.desktop() try: oldexists = pathlib.Path("~/NSM Sessions").expanduser().exists() except RuntimeError: #no home dir! oldexists = False xdgdatahome = getenv("XDG_DATA_HOME") if not xdgdatahome: try: xdgdatahome = pathlib.Path("~/.local/share").expanduser() except RuntimeError: #If there is no xdg env and no home dir let us crash and exit. logger.error("There is neither $XDG_DATA_HOME nor a home directory! Cannot start in this environment") #self._callSysExit(). Do not use this because it stores window settings, in a settings dir which might not be available under the circumstances above. sysexit(0) #directly afterwards @atexit is handled, but this function does not return. if not oldexists: return olddir = pathlib.Path("~/NSM Sessions").expanduser() if not olddir.is_dir(): logger.error(f"{olddir} exists but it not a directory. Cannot continue with moving session directories. Please handle it yourself.") errorString = QtCore.QCoreApplication.translate("movesessionroot", "{} exists but it not a directory. Cannot continue with moving session directories. Please handle it yourself.".format(olddir)) QtWidgets.QMessageBox.critical(parent, errorString) return newdir = pathlib.Path(xdgdatahome, "nsm") newexists = newdir.exists() title = QtCore.QCoreApplication.translate("movesessionroot", "Default session directory changed") if newexists: #Must merge. text = QtCore.QCoreApplication.translate("movesessionroot", "Detected both the new NSM 1.6.0 official session directory:\n{}\nAND the old one:\n{}\n\nDo you want Agordejo to move your old files and try to merge them with the new sessions? (Recommended)\n\nYou now have the chance to manually make a backup of your files before pressing 'Yes'".format(newdir, olddir)) else: #Simply move. text = QtCore.QCoreApplication.translate("movesessionroot", "With NSM version 1.6.0 the official session directory moved to:\n{}\nYou are still using the old one:\n{}\n\nDo you want Agordejo to move your files? (Recommended)\n\nYou now have the chance to manually make a backup of your files before pressing 'Yes'".format(newdir, olddir)) userAllowedMove = QtWidgets.QMessageBox.warning(parent, title, text, QtWidgets.QMessageBox.Yes|QtWidgets.QMessageBox.No) if not userAllowedMove == QtWidgets.QMessageBox.Yes: return #Test for read and write permissions if not access(olddir, R_OK): logger.error(f"{olddir} doesn't have READ permissions. Cannot continue with moving session directories. Please handle it yourself.") t = QtCore.QCoreApplication.translate("movesessionroot", "{} doesn't have READ permissions.\nCannot continue moving session directories. Please handle it yourself.".format(olddir)) QtWidgets.QMessageBox.critical(parent, title, t, QtWidgets.QMessageBox.Ok) return if not access(olddir, W_OK): logger.error(f"{olddir} doesn't have WRITE permissions. Cannot continue with moving session directories. Please handle it yourself.") t = QtCore.QCoreApplication.translate("movesessionroot", "{} doesn't have WRITE permissions.\nCannot continue moving session directories. Please handle it yourself.".format(olddir)) QtWidgets.QMessageBox.critical(parent, title, t, QtWidgets.QMessageBox.Ok) return if newexists and not access(newdir, R_OK): logger.error(f"{newdir} doesn't have READ permissions. Cannot continue with moving session directories. Please handle it yourself.") t = QtCore.QCoreApplication.translate("movesessionroot", "{} doesn't have READ permissions.\nCannot continue moving session directories. Please handle it yourself.".format(newdir)) QtWidgets.QMessageBox.critical(parent, title, t, QtWidgets.QMessageBox.Ok) return if newexists and not access(newdir, W_OK): logger.error(f"{newdir} doesn't have WRITE permissions. Cannot continue with moving session directories. Please handle it yourself.") t = QtCore.QCoreApplication.translate("movesessionroot", "{} doesn't have WRITE permissions.\nCannot continue moving session directories. Please handle it yourself.".format(newdir)) QtWidgets.QMessageBox.critical(parent, title, t, QtWidgets.QMessageBox.Ok) return #Tests done. Attempting to actually move the dir. if newexists: #We need to merge two directories if not newdir.is_dir(): logger.error(f"{newdir} exists but it not a directory. Cannot continue with moving session directories. Please handle it yourself.") t = QtCore.QCoreApplication.translate("movesessionroot", "{} exists but it not a directory.\nCannot continue moving session directories. Please handle it yourself.".format(newdir)) QtWidgets.QMessageBox.critical(parent, title, t, QtWidgets.QMessageBox.Ok) return #Are the file conflicts? oldlist = listdir(olddir) newlist = listdir(newdir) if set(oldlist).intersection( set(newlist) ): #Same sessions in both directory. We take the easy way out. unique_newdir = pathlib.Path(newdir, "backup-" + str(uuid4())) logger.info(f"Moving and renaming {olddir} to {unique_newdir} as new session root") shutil_move(olddir, unique_newdir) #this moves and renames the old ~/NSM Sessions. It will disappear from the home dir. else: #no. we can just move. But we need to move the contents, not the olddir itself into the newdir for oldsession in oldlist: shutil_move(pathlib.Path(olddir, oldsession), newdir) #this moves and renames the old ~/NSM Sessions. It will disappear from the home dir. else: #the new directory does not exist yet. We can move it. logger.info(f"Moving and renaming {olddir} to {newdir} as new session root") shutil_move(olddir, newdir) #this moves and renames the old ~/NSM Sessions. It will disappear from the home dir. #If for some reason the old dir is empty and still exists, do this: if olddir.exists(): try: rmdir(olddir) #will raise OSError if not empty. except OSError: logger.error(f"We tried to move and merge {olddir} to {newdir} but afterwards the source directory was not empty. There is nothing we can do here. Please handle it yourself. Please look out for incomplete data.") t = QtCore.QCoreApplication.translate("movesessionroot", "We tried to move and merge {} to {} but afterwards the source directory was not empty. There is nothing we can do here. Please handle it yourself. Please look out for incomplete data.".format(olddir, newdir)) QtWidgets.QMessageBox.critical(parent, title, t, QtWidgets.QMessageBox.Ok) return ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1665611335.9539216 agordejo-0.4.2/qtgui/opensessioncontroller.py0000644000175000017500000006011414321633110020213 0ustar00nilsnils#! /usr/bin/env python3 # -*- coding: utf-8 -*- """ Copyright 2022, Nils Hilbricht, Germany ( https://www.hilbricht.net ) This file is part of the Laborejo Software Suite ( https://www.laborejo.org ), This application is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . """ import logging; logger = logging.getLogger(__name__); logger.info("import") #Standard Library #Third Party from PyQt5 import QtCore, QtGui, QtWidgets import xdg.IconTheme #pyxdg https://www.freedesktop.org/wiki/Software/pyxdg/ #Engine import engine.api as api #Qt from .descriptiontextwidget import DescriptionController from .helper import iconFromString iconSize = QtCore.QSize(16,16) class ClientItem(QtWidgets.QTreeWidgetItem): """ Item on the right side. Clients of the session, in various states. clientDict = { "clientId":clientId, #for convenience, included internally as well "dumbClient":True, #Bool. Real nsm or just any old program? status "Ready" switches this. "reportedName":None, #str "label":None, #str "lastStatus":None, #str "statusHistory":[], #list "hasOptionalGUI": False, #bool "visible": None, # bool "dirty": None, # bool } """ allItems = {} # clientId : ClientItem def __init__(self, parentController, clientDict:dict): ClientItem.allItems[clientDict["clientId"]] = self self.parentController = parentController self.clientDict = clientDict parameterList = [] #later in update super().__init__(parameterList, type=1000) #type 0 is default qt type. 1000 is subclassed user type) self.defaultFlags = self.flags() self.setFlags(self.defaultFlags | QtCore.Qt.ItemIsEditable) #We have editTrigger to none so we can explicitly allow to only edit the name column on menuAction #self.treeWidget() not ready at this point self.updateData(clientDict) self.updateIcon(clientDict) def dataClientNameOverride(self, name:str): """Either string or None. If None we reset to nsmd name""" logger.info(f"Custom name for id {self.clientDict['clientId']} {self.clientDict['reportedName']}: {name}") if name: text = name else: text = self.clientDict["reportedName"] index = self.parentController.clientsTreeWidgetColumns.index("reportedName") self.setText(index, text) def updateData(self, clientDict:dict): """Arrives via parenTreeWidget api callback statusChanged, which is nsm status changed""" self.clientDict = clientDict for index, key in enumerate(self.parentController.clientsTreeWidgetColumns): if clientDict[key] is None: t = "" else: value = clientDict[key] if key == "visible": if value == True: t = "✔" else: t = "✖" elif key == "dirty": if value == True: t = QtCore.QCoreApplication.translate("OpenSession", "not saved") else: t = QtCore.QCoreApplication.translate("OpenSession", "clean") elif key == "reportedName" and self.parentController.clientOverrideNamesCache: if clientDict["clientId"] in self.parentController.clientOverrideNamesCache: t = self.parentController.clientOverrideNamesCache[clientDict["clientId"]] logger.info(f"Update Data: custom name for id {self.clientDict['clientId']} {self.clientDict['reportedName']}: {t}") else: t = str(value) else: t = str(value) self.setText(index, t) nameColumn = self.parentController.clientsTreeWidgetColumns.index("reportedName") if clientDict["reportedName"] is None: self.setText(nameColumn, clientDict["executable"]) def updateIcon(self, clientDict:dict): """Just called during init""" programIcons = self.parentController.mainWindow.programIcons if not programIcons: return #later again. assert "executable" in clientDict, clientDict iconColumn = self.parentController.clientsTreeWidgetColumns.index("reportedName") if clientDict["executable"] in programIcons: icon = programIcons[clientDict["executable"]] assert icon, icon self.setIcon(iconColumn, icon) #reported name is correct here. this is just the column. else: #Not NSM client added by the prompt widget result = xdg.IconTheme.getIconPath(clientDict["executable"]) #First attempt: let's hope the icon name has something to do with the executable name if result: icon = QtGui.QIcon.fromTheme(result) else: icon = QtGui.QIcon.fromTheme(clientDict["executable"]) if not icon.isNull(): self.setIcon(iconColumn, icon) else: self.setIcon(iconColumn, iconFromString(clientDict["executable"])) #draw our own. class ClientTable(object): """Controls the QTreeWidget that holds loaded clients""" def __init__(self, mainWindow, parent): self.mainWindow = mainWindow self.parent = parent self.clientOverrideNamesCache = None #None or dict. Dict is never truly empty, it has at least empty categories. self.sortByColumnValue = 0 #by name self.sortDescendingValue = 0 # Qt::SortOrder which is 0 for ascending and 1 for descending self.clientsTreeWidget = self.mainWindow.ui.loadedSessionClients self.clientsTreeWidget.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) self.clientsTreeWidget.customContextMenuRequested.connect(self.clientsContextMenu) self.clientsTreeWidget.setSelectionMode(QtWidgets.QAbstractItemView.SingleSelection) self.clientsTreeWidget.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows) self.clientsTreeWidget.setIconSize(iconSize) self.clientsTreeWidget.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers) #We only allow explicit editing. self.clientsTreeWidgetColumns = ("reportedName", "label", "lastStatus", "visible", "dirty", "clientId") #basically an enum self.clientHeaderLabels = [ QtCore.QCoreApplication.translate("OpenSession", "Name"), QtCore.QCoreApplication.translate("OpenSession", "Label"), QtCore.QCoreApplication.translate("OpenSession", "Status"), QtCore.QCoreApplication.translate("OpenSession", "Visible"), QtCore.QCoreApplication.translate("OpenSession", "Changes"), QtCore.QCoreApplication.translate("OpenSession", "ID"), ] self.clientsTreeWidget.setHeaderLabels(self.clientHeaderLabels) self.clientsTreeWidget.setSortingEnabled(True) self.clientsTreeWidget.setAlternatingRowColors(True) #Signals self.clientsTreeWidget.currentItemChanged.connect(self._reactSignal_currentClientChanged) self.clientsTreeWidget.itemDoubleClicked.connect(self._reactSignal_itemDoubleClicked) #This is hide/show or restart and NOT edit self.clientsTreeWidget.itemDelegate().closeEditor.connect(self._reactSignal_itemEditingFinished) self.clientsTreeWidget.model().layoutAboutToBeChanged.connect(self._reactSignal_rememberSorting) #self.clientsTreeWidget.model().layoutChanged.connect(self._reactSignal_restoreSorting) #Convenience Signals to directly disable the client messages on gui instruction. #This is purely for speed and preventing the user from sending a signal while the session is shutting down self.mainWindow.ui.actionSessionAbort.triggered.connect(lambda: self._updateClientMenu(deactivate=True)) self.mainWindow.ui.actionSessionSaveAndClose.triggered.connect(lambda: self._updateClientMenu(deactivate=True)) #API Callbacks api.callbacks.sessionOpenLoading.append(self._cleanClients) api.callbacks.sessionOpenReady.append(self._updateClientMenu) api.callbacks.sessionClosed.append(lambda: self._updateClientMenu(deactivate=True)) api.callbacks.clientStatusChanged.append(self._reactCallback_clientStatusChanged) api.callbacks.dataClientNamesChanged.append(self._reactCallback_dataClientNamesChanged) def _adjustColumnSize(self): self.clientsTreeWidget.sortItems(self.sortByColumnValue, self.sortDescendingValue) for index in range(self.clientsTreeWidget.columnCount()): self.clientsTreeWidget.resizeColumnToContents(index) #And a bit more extra space for index in range(self.clientsTreeWidget.columnCount()): self.clientsTreeWidget.setColumnWidth(index, self.clientsTreeWidget.columnWidth(index)+25) def _cleanClients(self, nsmSessionExportDict:dict): """Reset everything to the initial, empty state. We do not reset in in openReady because that signifies that the session is ready. And not in session closed because we want to setup data structures.""" ClientItem.allItems.clear() self.clientsTreeWidget.clear() def allItems(self): return ClientItem.allItems def clientsContextMenu(self, qpoint): """Reuses the menubar menus""" pos = QtGui.QCursor.pos() pos.setY(pos.y() + 5) item = self.clientsTreeWidget.itemAt(qpoint) if not type(item) is ClientItem: self.mainWindow.ui.menuSession.exec_(pos) return if not item is self.clientsTreeWidget.currentItem(): #Some mouse combinations can lead to getting a different context menu than the clicked item. self.clientsTreeWidget.setCurrentItem(item) menu = self.mainWindow.ui.menuClientNameId menu.exec_(pos) def _startEditingName(self, *args): currentItem = self.clientsTreeWidget.currentItem() self.editableItem = currentItem column = self.clientsTreeWidgetColumns.index("reportedName") self.clientsTreeWidget.editItem(currentItem, column) def _reactSignal_itemEditingFinished(self, qLineEdit, returnCode): """This is a hacky signal. It arrives every change, programatically or manually. We therefore only connect this signal right after a double click and disconnect it afterwards. And we still need to block signals while this is running. returnCode: no clue? Integers all over the place... """ treeWidgetItem = self.editableItem self.editableItem = None self.clientsTreeWidget.blockSignals(True) if treeWidgetItem: #We send the signal directly. Updating is done via callback. newName = treeWidgetItem.text(0) if not newName == treeWidgetItem.clientDict["reportedName"]: api.clientNameOverride(treeWidgetItem.clientDict["clientId"], newName) self.clientsTreeWidget.blockSignals(False) def _reactSignal_currentClientChanged(self, treeWidgetItem, previousItem): """Cache the current id for the client menu and shortcuts""" if treeWidgetItem: self.currentClientId = treeWidgetItem.clientDict["clientId"] else: self.currentClientId = None self._updateClientMenu() def _reactSignal_itemDoubleClicked(self, item:QtWidgets.QTreeWidgetItem, column:int): if item.clientDict["lastStatus"] == "stopped": api.clientResume(item.clientDict["clientId"]) elif item.clientDict["hasOptionalGUI"]: api.clientToggleVisible(item.clientDict["clientId"]) def _reactCallback_clientStatusChanged(self, clientDict:dict): """The major client callback. Maps to nsmd status changes. We will create and delete client tableWidgetItems based on this """ assert clientDict clientId = clientDict["clientId"] if clientId in ClientItem.allItems: if clientDict["lastStatus"] == "removed": index = self.clientsTreeWidget.indexOfTopLevelItem(ClientItem.allItems[clientId]) self.clientsTreeWidget.takeTopLevelItem(index) del ClientItem.allItems[clientId] else: ClientItem.allItems[clientId].updateData(clientDict) self._updateClientMenu() #Update here is fine because shutdown sets to status removed. else: #Create new. Item will be parented by Qt, so Python GC will not delete item = ClientItem(parentController=self, clientDict=clientDict) self.clientsTreeWidget.addTopLevelItem(item) self._adjustColumnSize() #Do not put a general menuUpdate here. It will re-open the client menu during shutdown, enabling the user to send false commands to the client. def _reactCallback_dataClientNamesChanged(self, clientOverrideNames:dict): """We either expect a dict or None. If None we return after clearing the data. We clear every callback and re-build. The dict can be content-empty of course.""" logger.info(f"Received dataStorage names update: {clientOverrideNames}") #Clear current GUI data. for clientInstance in ClientItem.allItems.values(): clientInstance.dataClientNameOverride(None) if clientOverrideNames is None: #This only happens if there was a client present and that exits. self.clientOverrideNamesCache = None else: #Real data #assert "origin" in data, data . Not in a fresh session, after adding! #assert data["origin"] == "https://www.laborejo.org/agordejo/nsm-data", data["origin"] self.clientOverrideNamesCache = clientOverrideNames #Can be empty dict as well clients = ClientItem.allItems for clientId, name in clientOverrideNames.items(): #It is possible on session start, that a client has not yet loaded but we already receive a name override. nsm-data is instructed to only announce after session has loaded, but that can go wrong when nsmd has a bad day. #Long story short: better to not rename right now, have some name mismatch and wait for a general update later, which will happen after every client load anyway. if clientId in clients: clients[clientId].dataClientNameOverride(name) self._updateClientMenu() #Update because we need to en/disable the rename action self._adjustColumnSize() def _updateClientMenu(self, deactivate=False): """The client menu changes with every currentItem edit to reflect the name and capabilities""" ui = self.mainWindow.ui menu = ui.menuClientNameId if deactivate: currentItem = None else: currentItem = self.clientsTreeWidget.currentItem() if currentItem: clientId = currentItem.clientDict["clientId"] state = True #if currentItem.clientDict["label"]: # name = currentItem.clientDict["label"] #else: # name = currentItem.clientDict["reportedName"] name = currentItem.text(self.clientsTreeWidgetColumns.index("reportedName")) else: state = False name = "Client" menu.setTitle(name) #menu.setEnabled(state) #It is not enough to disable the menu itself. Shortcuts will still work. for action in menu.actions(): action.setEnabled(state) if state: ui.actionClientRename.triggered.disconnect() ui.actionClientRename.triggered.connect(self._startEditingName) #ui.actionClientRename.triggered.connect(lambda: self.clientsTreeWidget.editItem(currentItem, self.clientsTreeWidgetColumns.index("reportedName"))) ui.actionClientSave_separately.triggered.disconnect() ui.actionClientSave_separately.triggered.connect(lambda: api.clientSave(clientId)) ui.actionClientStop.triggered.disconnect() ui.actionClientStop.triggered.connect(lambda: api.clientStop(clientId)) #ui.actionClientStop.triggered.connect(self._updateClientMenu) #too early. We need to wait for the status callback ui.actionClientResume.triggered.disconnect() ui.actionClientResume.triggered.connect(lambda: api.clientResume(clientId)) #ui.actionClientResume.triggered.connect(self._updateClientMenu) #too early. We need to wait for the status callback ui.actionClientRemove.triggered.disconnect() ui.actionClientRemove.triggered.connect(lambda: api.clientRemove(clientId)) #Deactivate depending on the state of the program if currentItem.clientDict["lastStatus"] == "stopped": ui.actionClientSave_separately.setEnabled(False) ui.actionClientStop.setEnabled(False) ui.actionClientToggleVisible.setEnabled(False) ui.actionClientResume.setEnabled(True) else: ui.actionClientResume.setEnabled(False) #Hide and show shall only be enabled and connected if supported by the client try: ui.actionClientToggleVisible.triggered.disconnect() except TypeError: #TypeError: disconnect() failed between 'triggered' and all its connections pass if currentItem.clientDict["hasOptionalGUI"]: ui.actionClientToggleVisible.setEnabled(True) ui.actionClientToggleVisible.triggered.connect(lambda: api.clientToggleVisible(clientId)) else: ui.actionClientToggleVisible.setEnabled(False) #Only rename when dataclient is present #None or dict, even empty dict if self.clientOverrideNamesCache is None: ui.actionClientRename.setEnabled(False) else: ui.actionClientRename.setEnabled(True) def _reactSignal_rememberSorting(self, *args): self.sortByColumnValue = self.clientsTreeWidget.header().sortIndicatorSection() self.sortDescendingValue = self.clientsTreeWidget.header().sortIndicatorOrder() def _reactSignal_restoreSorting(self, *args): """Do not use as signal!!! Will lead to infinite recursion since Qt 5.12.2""" #self.clientsTreeWidget.sortItems(self.sortByColumnValue, self.sortDescendingValue) raise RuntimeError() class LauncherProgram(QtWidgets.QTreeWidgetItem): """ An item on the left side of the window. Used to start programs and show info, but nothing more. """ allItems = {} # clientId : ClientItem def __init__(self, parentController, launcherDict:dict): LauncherProgram.allItems[launcherDict["agordejoExec"]] = self self.parentController = parentController self.launcherDict = launcherDict self.executable = launcherDict["agordejoExec"] parameterList = [] #later in update super().__init__(parameterList, type=1000) #type 0 is default qt type. 1000 is subclassed user type) self.updateData(launcherDict) def updateData(self, launcherDict:dict): """Arrives via parenTreeWidget api callback""" self.launcherDict = launcherDict for index, key in enumerate(self.parentController.columns): if (not key in launcherDict) or launcherDict[key] is None: t = "" else: t = str(launcherDict[key]) self.setText(index, t) programIcons = self.parentController.mainWindow.programIcons if not programIcons: return #later again if launcherDict["agordejoExec"] in programIcons: icon = programIcons[launcherDict["agordejoExec"]] self.setIcon(self.parentController.columns.index("agordejoName"), icon) #name is correct here. this is just the column. class LauncherTable(object): """Controls the QTreeWidget that holds programs in the PATH. """ def __init__(self, mainWindow, parent): self.mainWindow = mainWindow self.parent = parent self.sortByColumnValue = 0 # by name self.sortDescendingValue = 0 # Qt::SortOrder which is 0 for ascending and 1 for descending self.launcherWidget = self.mainWindow.ui.loadedSessionsLauncher self.launcherWidget.setIconSize(iconSize) self.columns = ("agordejoName", "agordejoDescription", "agordejoFullPath") #basically an enum self.headerLables = [ QtCore.QCoreApplication.translate("Launcher", "Name"), QtCore.QCoreApplication.translate("Launcher", "Description"), QtCore.QCoreApplication.translate("Launcher", "Path"), ] self.launcherWidget.setHeaderLabels(self.headerLables) self.launcherWidget.setSortingEnabled(True) self.launcherWidget.setAlternatingRowColors(True) self.launcherWidget.setSelectionMode(QtWidgets.QAbstractItemView.SingleSelection) self.launcherWidget.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows) ##The actual program entries are handled by the LauncherProgram item class #self.buildPrograms() #Don't call here. MainWindow calls it when everything is ready. #Signals self.launcherWidget.itemDoubleClicked.connect(self._reactSignal_launcherItemDoubleClicked) def _adjustColumnSize(self): self.launcherWidget.sortItems(self.sortByColumnValue, self.sortDescendingValue) for index in range(self.launcherWidget.columnCount()): self.launcherWidget.resizeColumnToContents(index) #And a bit more extra space for index in range(self.launcherWidget.columnCount()): self.launcherWidget.setColumnWidth(index, self.launcherWidget.columnWidth(index)+25) def _reactSignal_launcherItemDoubleClicked(self, item): api.clientAdd(item.executable) def buildPrograms(self): """Called by mainWindow.updateProgramDatabase Receive entries from the engine. """ self.launcherWidget.clear() programs = api.getNsmClients() for entry in programs: item = LauncherProgram(parentController=self, launcherDict=entry) self.launcherWidget.addTopLevelItem(item) self._adjustColumnSize() class OpenSessionController(object): """Not a subclass. Controls the visible tab, when a session is open. There is only one open instance at a time that controls the GUI and cleans itself.""" def __init__(self, mainWindow): self.mainWindow = mainWindow self.clientTabe = ClientTable(mainWindow=mainWindow, parent=self) self.launcherTable = LauncherTable(mainWindow=mainWindow, parent=self) self.descriptionController = DescriptionController(mainWindow, self.mainWindow.ui.loadedSessionDescriptionGroupBox, self.mainWindow.ui.loadedSessionDescription) self.sessionLoadedPanel = mainWindow.ui.session_loaded #groupbox self.sessionProgramsPanel = mainWindow.ui.session_programs #groupbox #API Callbacks api.callbacks.sessionOpenReady.append(self._reactCallback_sessionOpen) logger.info("Full View Open Session Controller ready") def allSessionItems(self): """Can be used by external parts, like the tray icon""" return self.clientTabe.allItems().values() #dict clientId:SessionItem def _reactCallback_sessionOpen(self, nsmSessionExportDict:dict): """Open does not mean we come from the session chooser. Switching does not close a session""" #self.description.clear() #Deletes the placesholder and text! self.sessionLoadedPanel.setTitle(nsmSessionExportDict["nsmSessionName"]) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1665611335.9539216 agordejo-0.4.2/qtgui/projectname.py0000644000175000017500000001463314321633110016056 0ustar00nilsnils#! /usr/bin/env python3 # -*- coding: utf-8 -*- """ Copyright 2022, Nils Hilbricht, Germany ( https://www.hilbricht.net ) This file is part of the Laborejo Software Suite ( https://www.laborejo.org ), This application is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . """ import logging; logger = logging.getLogger(__name__); logger.info("import") #Standard Library import pathlib import os #Third Party from PyQt5 import QtCore, QtWidgets #QtGui from .designer.projectname import Ui_ProjectName from .designer.newsession import Ui_NewSession #Engine from engine.config import METADATA #includes METADATA only. No other environmental setup is executed. import engine.api as api class ProjectNameWidget(QtWidgets.QDialog): """Ask the user for a project name. Either for renaming, new or copy. Will have a live-detection """ def __init__(self, parent, startwith:str="", start=True, alsoDisable=None): super().__init__(parent) self.ui = Ui_ProjectName() self.ui.setupUi(self) self.alsoDisable = alsoDisable #in case we are embedded #self.ui.labelDescription self.ui.labelError.setText("") self.ui.name.setText(startwith) self.check(startwith) self.ui.name.textEdited.connect(self.check) #not called when text is changed programatically self.result = None self.ui.buttonBox.accepted.connect(self.process) self.ui.buttonBox.rejected.connect(self.reject) if start: self.setWindowFlag(QtCore.Qt.Popup, True) self.setModal(True) self.setFocus(True) self.ui.name.setFocus(True) self.exec_() def check(self, currentText): """Called every keypress. We do preliminary error and collision checking here, so the engine does not have to throw an error """ if currentText.endswith("/"): currentText = currentText[:-1] fullpath = pathlib.Path(api.sessionRoot(), currentText) path = pathlib.Path(currentText) errorMessage = "" if currentText == "": errorMessage = QtCore.QCoreApplication.translate("ProjectNameWidget", "Name must not be empty.") elif pathlib.PurePosixPath(path).match("/*") or str(pathlib.PurePosixPath(path)).startswith("/"): errorMessage = QtCore.QCoreApplication.translate("ProjectNameWidget", "Name must be a relative path.") elif pathlib.PurePosixPath(path).match("../*") or pathlib.PurePosixPath(path).match("*..*"): errorMessage = QtCore.QCoreApplication.translate("ProjectNameWidget", "Moving to parent directory not allowed.") elif "/" in currentText and fullpath.parent.exists() and not os.access(fullpath.parent, os.W_OK): errorMessage = QtCore.QCoreApplication.translate("ProjectNameWidget", "Writing in this directory is not permitted.") elif fullpath.exists(): errorMessage = QtCore.QCoreApplication.translate("ProjectNameWidget", "Name is already in use.") ok = self.ui.buttonBox.button(QtWidgets.QDialogButtonBox.Ok) if errorMessage: if self.alsoDisable: self.alsoDisable.setEnabled(False) ok.setEnabled(False) self.ui.labelError.setText(""+errorMessage+"") else: #print (path) if self.alsoDisable: self.alsoDisable.setEnabled(True) self.ui.labelError.setText("") ok.setEnabled(True) def process(self): """Careful! Calling this eats python errors without notice. Make sure your objects exists and your syntax is correct""" self.result = self.ui.name.text() self.done(True) class NewSessionDialog(QtWidgets.QDialog): def __init__(self, parent, startwith:str=""): super().__init__(parent) self.ui = Ui_NewSession() self.ui.setupUi(self) #send our childWidget our own ok button so it can disable it for the name check self.ok = self.ui.buttonBox.button(QtWidgets.QDialogButtonBox.Ok) #Embed a project name widget self.projectName = ProjectNameWidget(self, startwith, start=False, alsoDisable=self.ok) self.projectName.ui.buttonBox.hide() self.ui.nameGroupBox.layout().addWidget(self.projectName) self.projectName.ui.name.returnPressed.connect(self.ok.click) #We want to accept everything when return is pressed. self.projectName.reject = self.reject #forward escape to our reject self.result = None nsmExecutables = api.getNsmExecutables() #type set, cached, very fast. data = METADATA["preferredClients"]["data"] con = METADATA["preferredClients"]["connections"] self.ui.checkBoxJack.setEnabled(con in nsmExecutables) self.ui.checkBoxJack.setVisible(con in nsmExecutables) self.ui.checkBoxData.setEnabled(data in nsmExecutables) self.ui.checkBoxData.setVisible(data in nsmExecutables) self.ui.buttonBox.accepted.connect(self.process) self.ui.buttonBox.rejected.connect(self.reject) self.setModal(True) self.setWindowFlag(QtCore.Qt.Popup, True) self.projectName.ui.name.setFocus(True) self.exec_() def process(self): """Careful! Calling this eats python errors without notice. Make sure your objects exists and your syntax is correct""" assert self.ok.isEnabled() data = METADATA["preferredClients"]["data"] con = METADATA["preferredClients"]["connections"] startclients = [] if self.ui.checkBoxJack.isChecked(): startclients.append(con) if self.ui.checkBoxData.isChecked(): startclients.append(data) self.result = { "name" : self.projectName.ui.name.text(), "startclients" : startclients, } self.done(True) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1665611397.1479487 agordejo-0.4.2/qtgui/resources.py0000644000175000017500000027152714321633205015575 0ustar00nilsnils# -*- coding: utf-8 -*- # Resource object code # # Created by: The Resource Compiler for PyQt5 (Qt v5.15.6) # # WARNING! All changes made in this file will be lost! from PyQt5 import QtCore qt_resource_data = b"\ \x00\x00\x02\x70\ \x3c\ \x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\x3d\x22\x68\x74\x74\x70\x3a\ \x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x32\x30\x30\ \x30\x2f\x73\x76\x67\x22\x20\x76\x69\x65\x77\x42\x6f\x78\x3d\x22\ \x30\x20\x30\x20\x31\x32\x30\x20\x31\x32\x30\x22\x3e\x3c\x70\x61\ \x74\x68\x20\x64\x3d\x22\x4d\x33\x2e\x32\x37\x32\x20\x35\x38\x2e\ \x37\x37\x32\x63\x30\x20\x33\x31\x2e\x32\x30\x34\x20\x32\x35\x2e\ \x32\x39\x36\x20\x35\x36\x2e\x35\x20\x35\x36\x2e\x35\x20\x35\x36\ \x2e\x35\x20\x33\x31\x2e\x32\x30\x33\x20\x30\x20\x35\x36\x2e\x35\ \x2d\x32\x35\x2e\x32\x39\x36\x20\x35\x36\x2e\x35\x2d\x35\x36\x2e\ \x35\x20\x30\x2d\x31\x36\x2e\x32\x36\x34\x2d\x36\x2e\x38\x38\x32\ \x2d\x33\x30\x2e\x39\x31\x2d\x31\x37\x2e\x38\x38\x2d\x34\x31\x2e\ \x32\x32\x4c\x38\x34\x2e\x30\x33\x20\x33\x32\x2e\x38\x38\x35\x63\ \x36\x2e\x39\x31\x32\x20\x36\x2e\x34\x38\x32\x20\x31\x31\x2e\x32\ \x34\x34\x20\x31\x35\x2e\x36\x38\x38\x20\x31\x31\x2e\x32\x34\x34\ \x20\x32\x35\x2e\x38\x39\x20\x30\x20\x31\x39\x2e\x35\x37\x34\x2d\ \x31\x35\x2e\x39\x32\x36\x20\x33\x35\x2e\x35\x2d\x33\x35\x2e\x35\ \x20\x33\x35\x2e\x35\x2d\x31\x39\x2e\x35\x37\x35\x20\x30\x2d\x33\ \x35\x2e\x35\x2d\x31\x35\x2e\x39\x32\x36\x2d\x33\x35\x2e\x35\x2d\ \x33\x35\x2e\x35\x22\x20\x66\x69\x6c\x6c\x3d\x22\x23\x30\x31\x30\ \x31\x30\x31\x22\x2f\x3e\x3c\x70\x61\x74\x68\x20\x64\x3d\x22\x4d\ \x39\x35\x2e\x32\x37\x32\x20\x35\x38\x2e\x37\x37\x32\x63\x30\x20\ \x31\x39\x2e\x35\x37\x35\x2d\x31\x35\x2e\x39\x32\x35\x20\x33\x35\ \x2e\x35\x2d\x33\x35\x2e\x35\x20\x33\x35\x2e\x35\x2d\x31\x39\x2e\ \x35\x37\x34\x20\x30\x2d\x33\x35\x2e\x35\x2d\x31\x35\x2e\x39\x32\ \x36\x2d\x33\x35\x2e\x35\x2d\x33\x35\x2e\x35\x20\x30\x2d\x31\x30\ \x2e\x32\x20\x34\x2e\x33\x33\x32\x2d\x31\x39\x2e\x34\x30\x36\x20\ \x31\x31\x2e\x32\x34\x35\x2d\x32\x35\x2e\x38\x39\x6c\x2d\x31\x34\ \x2e\x33\x36\x35\x2d\x31\x35\x2e\x33\x33\x43\x31\x30\x2e\x31\x35\ \x34\x20\x32\x37\x2e\x38\x36\x20\x33\x2e\x32\x37\x32\x20\x34\x32\ \x2e\x35\x30\x37\x20\x33\x2e\x32\x37\x32\x20\x35\x38\x2e\x37\x37\ \x63\x30\x20\x33\x31\x2e\x32\x30\x34\x20\x32\x35\x2e\x32\x39\x37\ \x20\x35\x36\x2e\x35\x20\x35\x36\x2e\x35\x20\x35\x36\x2e\x35\x20\ \x33\x31\x2e\x32\x30\x34\x20\x30\x20\x35\x36\x2e\x35\x2d\x32\x35\ \x2e\x32\x39\x37\x20\x35\x36\x2e\x35\x2d\x35\x36\x2e\x35\x22\x20\ \x66\x69\x6c\x6c\x3d\x22\x23\x30\x31\x30\x31\x30\x31\x22\x2f\x3e\ \x3c\x70\x61\x74\x68\x20\x64\x3d\x22\x4d\x34\x39\x2e\x37\x38\x35\ \x20\x30\x48\x36\x39\x2e\x37\x36\x76\x36\x31\x2e\x35\x37\x36\x48\ \x34\x39\x2e\x37\x38\x35\x7a\x22\x20\x66\x69\x6c\x6c\x3d\x22\x23\ \x30\x31\x30\x31\x30\x31\x22\x2f\x3e\x3c\x2f\x73\x76\x67\x3e\ \x00\x00\x00\x60\ \x3c\ \x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\x3d\x22\x68\x74\x74\x70\x3a\ \x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x32\x30\x30\ \x30\x2f\x73\x76\x67\x22\x20\x76\x69\x65\x77\x42\x6f\x78\x3d\x22\ \x30\x20\x30\x20\x31\x32\x30\x20\x31\x32\x30\x22\x3e\x3c\x70\x61\ \x74\x68\x20\x64\x3d\x22\x4d\x31\x36\x20\x31\x36\x68\x38\x38\x76\ \x38\x38\x48\x31\x36\x7a\x22\x2f\x3e\x3c\x2f\x73\x76\x67\x3e\ \x00\x00\x00\xb4\ \x3c\ \x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\x3d\x22\x68\x74\x74\x70\x3a\ \x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x32\x30\x30\ \x30\x2f\x73\x76\x67\x22\x20\x76\x69\x65\x77\x42\x6f\x78\x3d\x22\ \x30\x20\x30\x20\x38\x20\x38\x22\x3e\x3c\x70\x61\x74\x68\x20\x64\ \x3d\x22\x4d\x33\x2e\x31\x31\x20\x37\x2e\x31\x37\x4c\x2e\x30\x34\ \x38\x20\x34\x2e\x31\x30\x36\x6c\x31\x2e\x31\x34\x34\x2d\x31\x2e\ \x31\x34\x34\x20\x33\x2e\x30\x36\x32\x20\x33\x2e\x30\x36\x32\x7a\ \x22\x2f\x3e\x3c\x70\x61\x74\x68\x20\x64\x3d\x22\x4d\x38\x2e\x30\ \x36\x33\x20\x32\x2e\x32\x30\x38\x4c\x33\x2e\x37\x33\x35\x20\x36\ \x2e\x35\x33\x36\x6c\x2d\x31\x2e\x32\x32\x2d\x31\x2e\x32\x32\x4c\ \x36\x2e\x38\x34\x33\x2e\x39\x38\x38\x7a\x22\x2f\x3e\x3c\x2f\x73\ \x76\x67\x3e\ \x00\x00\x04\xfc\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x26\x00\x00\x00\x1e\x08\x06\x00\x00\x00\x40\x14\x6c\x6e\ \x00\x00\x00\x04\x73\x42\x49\x54\x08\x08\x08\x08\x7c\x08\x64\x88\ \x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x03\xb4\x00\x00\x03\xb4\ \x01\xd8\x39\x88\xbf\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\ \x74\x77\x61\x72\x65\x00\x77\x77\x77\x2e\x69\x6e\x6b\x73\x63\x61\ \x70\x65\x2e\x6f\x72\x67\x9b\xee\x3c\x1a\x00\x00\x00\x0b\x74\x45\ \x58\x74\x54\x69\x74\x6c\x65\x00\x53\x68\x61\x70\x65\x26\xef\x99\ \x91\x00\x00\x04\x62\x49\x44\x41\x54\x58\x85\xb5\x97\x5d\x4c\x5b\ \x75\x18\xc6\x9f\xf7\x9c\xf2\x3d\xd6\x76\xa0\x63\xe8\xc2\x5a\xac\ \x71\x94\x42\x34\xc4\xcc\x69\x08\xc4\x68\x62\x62\x30\x8e\x48\x30\ \x3a\xe3\xe2\x85\xde\xb8\x68\x8c\xba\xb9\x0b\x13\xbd\xf1\x13\x33\ \x2e\x4c\x8c\x66\x1a\x3f\x92\x05\x75\xd9\x84\xed\x82\xcc\x91\x90\ \xb9\xc4\x8c\x7d\x40\x01\x49\x18\x2d\x6c\x8e\x39\x07\x96\x02\x2b\ \x50\x7a\xce\xe3\x85\x80\xdd\xe9\x29\x3d\x94\xee\xb9\xeb\xfb\x7f\ \xde\xf7\xfd\x35\xff\xaf\xf3\x17\x64\x40\xfe\xc0\xec\x66\x4d\x5b\ \x28\x86\x6a\x2b\x50\x35\x4e\xce\xe4\xce\x8c\xef\xdc\xba\x75\x6e\ \x3d\x35\x25\x9d\xa4\xae\x2e\xda\x8a\xcb\xc2\xbb\x40\x3c\x4d\x41\ \x1d\x80\x12\x13\xdb\x65\x01\x3a\x00\x1c\xfd\xc3\x65\x3f\xd5\x24\ \xa2\xdd\x36\x30\x92\x4a\x5f\x20\xfc\xb2\x08\xde\x01\x70\xf7\x1a\ \x52\xfb\x85\xb2\xcf\x57\x6e\x3f\x9e\x71\xb0\xde\xd1\x29\x17\x74\ \x7e\x27\x90\x87\xd7\x00\x64\x6c\xf7\x4b\x9e\x16\xdb\xed\xf1\x14\ \x4d\x67\x04\xcc\x3f\x32\x55\x43\x41\x3b\xcc\xa7\x6c\x6d\x22\x07\ \x45\x97\x06\x9f\xc7\x31\xb2\x9a\x4d\x49\x80\x08\x86\xea\x7a\x7a\ \x98\xb5\xfc\x7b\x60\x6c\xa6\x82\x82\x5f\x33\x02\x05\x00\x22\x15\ \x54\x71\xf2\xfc\xf0\xcc\x1d\xab\xda\x8c\x81\xbe\xe0\xd4\x21\x10\ \x3e\x2a\x68\x12\xc1\x14\x74\x9c\x05\x51\x9e\x24\xff\x6f\x02\xdf\ \xaa\x0a\x4f\x50\xcf\x1a\xcc\xd5\x16\xe6\x22\x59\x6a\x91\xa2\x49\ \x05\x85\x0d\x00\x9e\x03\x50\x90\x24\xb7\x5b\x9d\xb3\x3f\xe6\xf5\ \x4a\xd4\x6c\xd0\x96\x24\xa9\x46\x74\x5c\x00\xf1\x3b\xc4\x14\x4a\ \x17\xe0\x53\x65\x2e\xfa\x5e\x2c\x27\x2f\x1f\x88\x6d\xf1\xb9\x37\ \x5c\x5f\x1a\x9b\x06\x10\x04\x70\x7c\x20\x38\xfb\xae\xc6\xc5\x8f\ \x01\x79\xde\xa4\x46\xad\x96\x1f\x7e\x0d\xc0\x47\x66\x00\x09\x53\ \x19\x27\x3b\x04\x8f\x9b\xc4\xa3\x24\x9a\x7c\x6e\xc7\x5b\x5e\xef\ \x9d\xb3\x36\xe8\x1b\x75\xc8\x6f\xfe\x91\xd0\x2e\xa3\xd1\xeb\xda\ \xf0\x57\x95\xdb\xb9\x5b\x80\x37\x4d\x3b\x10\xfb\x07\xae\x84\x37\ \xad\x15\xcc\x5c\x82\xbd\xd5\xe5\x8e\x9f\x0d\xd1\x02\x8a\xfc\xd4\ \x37\x32\xf5\x01\xc9\x84\x9a\x3e\xb7\xe3\x13\x40\x5a\x4c\xaa\x39\ \xb4\x45\xfd\xf5\xf5\x81\x11\x31\x08\x4e\x55\xb9\x1c\x5f\x24\x45\ \x16\xbc\xed\x0f\x86\x0f\xf7\x8c\x8f\xe7\x1b\x07\xd5\xb9\x8d\xfb\ \x01\x5c\x32\x49\x6b\x5c\x1f\x98\xc0\x06\xa2\xbe\x37\x10\x3a\x18\ \xbf\x6b\x4d\xf4\x4c\xf6\x7c\xfe\x99\xc1\x40\xa8\x2c\x3e\xe8\xf5\ \x4a\x94\xe0\xfb\x26\xfe\xed\x17\x47\xa6\xef\x4d\x1f\x6c\x09\x4f\ \x20\x7b\x73\x36\x85\x4f\x5f\x08\x86\xb6\xad\xe2\xab\xd6\x20\x3d\ \x7d\x81\x50\x6d\x7c\x30\x5f\xd3\x8f\x02\x48\xd8\x85\xa2\x68\x0f\ \x59\x01\x63\x2a\x3a\x02\x0f\xda\x28\x67\xfb\x47\x43\xf5\xab\x78\ \x8a\x01\xe9\xec\x0b\x86\x5e\x5c\x8e\x2d\x9d\xf8\x7d\x09\x10\xba\ \x94\x5a\x01\x4b\x75\x1b\x04\x08\xb6\x92\x78\x76\x7e\xc2\x71\x3a\ \x85\x37\x07\x94\xaf\x7b\x03\xa1\x83\x6d\xa4\xba\x54\x7e\xdc\x68\ \xa2\x30\x01\x2c\xd9\x39\x16\xaf\x08\x80\x33\x14\x76\x28\x9a\x7e\ \xc4\x77\x4f\xd1\x95\xf8\xc1\x81\x4b\x61\x0b\x25\xe2\x24\xb4\x25\ \xcc\x89\x28\x8b\x56\xc1\x02\x00\x4e\x12\x7a\x47\xbe\xe6\xec\xf4\ \x78\x64\x61\x6d\xdd\x57\xb4\x00\xe1\x2b\xd5\x2e\xe7\x37\x2b\x11\ \xe2\x2e\xa3\x89\xe4\xb5\x94\x60\xcc\x9b\x7f\xb5\xba\xa4\xe4\x66\ \x9a\x20\x2b\x12\x60\x82\x60\x63\x95\xcb\xd9\xbd\x1c\xfb\xef\x7e\ \xd4\x2a\x13\xbc\x94\xab\x29\xc1\x32\x01\x05\xa0\x57\x05\x9f\xaa\ \x70\x3b\xc7\xe2\x83\x59\xaa\xde\x4c\x40\x35\x78\x49\x3d\xd6\x6d\ \x88\xa5\x71\xf2\xa7\xd6\x8f\xd1\xdc\xc8\x4e\x23\xd4\xd0\xd0\x8d\ \x42\x82\x07\x12\xdc\x82\xf3\xd5\x9e\xa2\x3f\x6f\x27\x18\x41\x7c\ \xe8\x73\xd9\x9b\x6b\x4a\x4b\x23\xb7\x0c\x90\x12\xcd\xce\xfa\x0a\ \xc0\xe6\x04\x2e\xe2\xb0\x59\x31\x2b\xbb\xf2\x96\x06\xfe\xc0\x74\ \x0b\x04\xff\x54\xb9\xed\xf1\xa7\xf8\x4d\x21\x5f\xf0\x95\x3b\x8f\ \x18\x73\xba\xba\x68\xeb\x1f\x0d\xb7\x02\x68\x32\xf9\x2b\x57\x17\ \xf2\x22\x9f\x9b\xf5\xb2\xfc\x69\xdd\x46\xaa\xf7\x8d\x86\xbf\x04\ \xb1\x67\x29\xf5\x98\x68\x7c\x43\x17\x75\x46\x55\x62\x5b\x2a\xdd\ \xce\x5e\x63\xce\xc5\xd1\xd0\xfd\xaa\xae\xb4\x12\x7c\xc4\xb4\x39\ \xf9\x92\xaf\xdc\x79\x28\x6d\xb0\xe1\x61\xe6\xcc\xab\xe1\x1f\x08\ \x18\x2f\xdc\x28\xc1\x4e\x11\x69\x57\x84\xc3\x42\x84\x62\xba\x14\ \x29\x8a\x78\x49\x36\x00\xa8\x47\xf2\xe5\xd2\xe6\x73\xd9\x9b\x45\ \xc4\xf4\xa6\xb1\x04\xb6\xf4\x55\xbb\xc7\x8a\xd7\x92\x04\xe7\xa2\ \x39\x91\x5a\xe3\x5a\x8c\x97\xa5\xc5\x6f\x93\xac\x03\x10\x9c\xcb\ \x10\x56\x77\x2c\xa6\x3e\xb1\x1a\x14\x60\x11\xac\x62\x5b\xc1\x35\ \x35\x12\xad\x03\xf8\x3d\x2c\x5c\xf2\x49\xa4\x01\xd2\x32\x39\x66\ \x7f\xf4\x01\x4f\xe1\x8d\x54\xe6\x35\xbf\xc4\x7b\x83\xe1\x1d\x42\ \x7e\x06\x60\x87\xd5\x1c\x81\xb4\x2b\xaa\xb2\xcf\x5b\x56\x38\x68\ \x3d\x27\x4d\xf9\x47\x67\xb6\x53\xd7\x1b\x01\x3e\x09\xa0\x12\xff\ \xbf\x86\x74\x80\xd7\x49\x19\x12\xb0\x43\x74\x39\x96\xea\x0d\x99\ \x51\xb0\x78\x91\x14\xff\xe5\xb0\x43\x34\x5b\xf6\xc4\x58\xc1\x64\ \x7d\xbd\xc4\xd6\x5b\xf3\x5f\x2e\x0a\xbd\x45\xc2\xd5\x25\x47\x00\ \x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ \x00\x00\x02\xbb\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x26\x00\x00\x00\x1e\x08\x06\x00\x00\x00\x40\x14\x6c\x6e\ \x00\x00\x00\x04\x73\x42\x49\x54\x08\x08\x08\x08\x7c\x08\x64\x88\ \x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x03\xb4\x00\x00\x03\xb4\ \x01\xd8\x39\x88\xbf\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\ \x74\x77\x61\x72\x65\x00\x77\x77\x77\x2e\x69\x6e\x6b\x73\x63\x61\ \x70\x65\x2e\x6f\x72\x67\x9b\xee\x3c\x1a\x00\x00\x00\x0b\x74\x45\ \x58\x74\x54\x69\x74\x6c\x65\x00\x53\x68\x61\x70\x65\x26\xef\x99\ \x91\x00\x00\x02\x21\x49\x44\x41\x54\x58\x85\xcd\xd7\x3d\x68\x13\ \x71\x18\xc7\xf1\xdf\xf3\xbf\x88\x16\xdb\x26\xb1\x20\x22\x08\xf1\ \x42\x17\x4d\xd2\x2a\x75\x10\xba\x58\x70\x71\x89\x08\x82\x83\xae\ \x5d\xec\xe0\x26\xb8\x58\x9c\xc4\xd9\x0a\x8e\x22\x14\x37\x11\x71\ \xe9\xa0\xe0\x6b\xa0\x8b\xb9\x4b\xa7\x34\x97\x48\x41\x07\xb9\xe6\ \xce\x06\x94\x36\x77\x8f\x43\xcf\xe6\x4f\x68\x2f\x2f\x77\x97\xf8\ \x5d\x92\x70\x2f\xf9\xdc\x93\x67\x09\x69\x86\xf5\x74\x7b\x33\xbe\ \x30\x33\x43\x3b\x08\x98\x6e\x58\x97\x19\x58\xf1\x39\xa5\x94\x53\ \x13\xd9\xb5\x6a\xe3\x84\xc3\xcd\x1f\x7e\xf7\x12\x00\xe6\x0f\x1f\ \xfb\xf5\xb6\xb8\xbe\x75\x3c\x28\x0c\x00\xf9\x1f\x25\xf2\x5e\xfc\ \xcf\xf3\x60\x60\xf0\x2c\x09\xe7\xcb\xd7\xb2\x79\x36\x04\x5c\x28\ \x09\xe9\xbd\x2a\x14\xa5\xa0\x55\xeb\xf9\xa1\x69\xa4\x44\xdb\xe7\ \x51\x30\xbd\x2c\x56\xea\x8b\xcc\xdc\x71\xdc\x51\xd6\x0e\x03\x76\ \x57\xe0\x7e\xc9\xb0\x97\x3f\x6f\x6c\x8c\x0c\x5c\xe4\xb5\x1f\x0c\ \x00\xc0\x84\x1b\xa3\x3b\x63\x9f\xf4\x75\xf3\xd4\x20\x41\xff\x3a\ \x10\xe6\x75\x8e\x45\xac\x50\xaa\xd8\x17\x06\xa2\x91\xea\x04\x03\ \xc0\x27\x5d\xe2\xf7\xba\x61\xdf\x8a\x9e\xd3\xaa\x0b\x18\x00\xe0\ \x08\x83\x9f\x69\x15\xeb\x21\x33\x77\x7b\x4d\xa0\x7a\xf9\x12\x02\ \xe1\xae\x5e\xb3\x5f\x97\xcb\xe6\x78\x64\x22\xaf\xde\x9f\x9e\x71\ \xe5\xb7\xa2\x7c\x2c\xd6\xac\xd3\x11\x78\xf6\xea\xf7\x67\xc9\x92\ \x8b\xd5\x52\xad\x7e\x29\x54\x8d\x54\x90\x7d\x99\x70\x5d\x5a\xd1\ \x0c\xfb\x76\x68\x1a\xa9\x58\xf0\xeb\xf9\xb1\x66\x58\xb9\xed\xcd\ \xf8\x02\x60\x87\x82\x02\x82\x4d\x2c\xd2\x82\x4e\xac\x09\xd0\x9d\ \x9c\x1a\x5f\x02\x00\xdd\xb0\x42\x20\xed\x16\x04\x66\x0a\xc1\xd7\ \x33\xa9\xc4\xbb\xd0\x34\x52\xfd\xc2\x74\x16\xc8\x67\x52\xc9\x6a\ \xa8\x1a\xa9\x3e\x76\x8c\xdf\x8c\x38\xce\xec\x54\x2a\x11\x19\x0a\ \xe8\x6d\x62\x0c\xc6\xa3\xac\x9a\xb8\x47\x44\x6e\x64\x22\xaf\x6e\ \x61\x7f\x08\x34\x9f\x4d\xc7\x9f\x47\xaa\x91\xea\x02\x46\xdf\x05\ \xe3\x6a\x26\x1d\x5f\x8d\x9e\xd3\xaa\x13\xac\x10\x13\xb1\x6b\x67\ \x52\x47\x7d\xff\x6a\x45\xd1\x81\xcb\x4f\x8c\x17\x8d\x43\x5b\x73\ \xc3\x40\x01\xfb\x4f\x8c\x99\xf9\x41\x2e\x9d\x5c\x1c\x34\x46\xae\ \x1d\xd6\x00\xf1\xcd\x29\x35\xf9\x6a\x28\x1a\xa9\x16\x8c\x50\x71\ \x9b\x4e\x7e\x7a\x72\x62\x6d\x88\x9e\xbd\xbc\x1d\xa3\x0f\xcd\xa6\ \x72\xf1\x7f\x41\x01\x80\x20\xe0\x89\xf9\x6d\x7c\xee\xfc\xe4\xd8\ \xcf\x61\x63\xe4\xfe\x02\x9c\xf5\xa3\xbb\x96\xa3\x3a\x87\x00\x00\ \x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ \x00\x00\x01\x4f\ \x3c\ \x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\x3d\x22\x68\x74\x74\x70\x3a\ \x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x32\x30\x30\ \x30\x2f\x73\x76\x67\x22\x20\x76\x69\x65\x77\x42\x6f\x78\x3d\x22\ \x30\x20\x30\x20\x31\x32\x30\x20\x31\x32\x30\x22\x3e\x3c\x70\x61\ \x74\x68\x20\x64\x3d\x22\x4d\x36\x30\x20\x31\x39\x2e\x30\x39\x43\ \x32\x32\x2e\x33\x38\x32\x20\x31\x39\x2e\x30\x39\x2e\x30\x35\x33\ \x20\x36\x30\x20\x2e\x30\x35\x33\x20\x36\x30\x53\x32\x32\x2e\x33\ \x38\x33\x20\x31\x30\x30\x2e\x39\x31\x20\x36\x30\x20\x31\x30\x30\ \x2e\x39\x31\x20\x31\x31\x39\x2e\x39\x35\x20\x36\x30\x20\x31\x31\ \x39\x2e\x39\x35\x20\x36\x30\x20\x39\x37\x2e\x36\x31\x38\x20\x31\ \x39\x2e\x30\x39\x20\x36\x30\x20\x31\x39\x2e\x30\x39\x7a\x6d\x30\ \x20\x36\x35\x2e\x33\x32\x63\x2d\x31\x33\x2e\x34\x36\x20\x30\x2d\ \x32\x34\x2e\x34\x31\x2d\x31\x30\x2e\x39\x35\x2d\x32\x34\x2e\x34\ \x31\x2d\x32\x34\x2e\x34\x31\x53\x34\x36\x2e\x35\x34\x20\x33\x35\ \x2e\x35\x39\x20\x36\x30\x20\x33\x35\x2e\x35\x39\x20\x38\x34\x2e\ \x34\x30\x37\x20\x34\x36\x2e\x35\x34\x20\x38\x34\x2e\x34\x30\x37\ \x20\x36\x30\x73\x2d\x31\x30\x2e\x39\x35\x20\x32\x34\x2e\x34\x31\ \x2d\x32\x34\x2e\x34\x31\x20\x32\x34\x2e\x34\x31\x7a\x22\x2f\x3e\ \x3c\x63\x69\x72\x63\x6c\x65\x20\x63\x79\x3d\x22\x36\x30\x2e\x35\ \x38\x33\x22\x20\x63\x78\x3d\x22\x36\x30\x22\x20\x72\x3d\x22\x31\ \x34\x2e\x34\x30\x39\x22\x2f\x3e\x3c\x2f\x73\x76\x67\x3e\ \x00\x00\x06\xec\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x40\x00\x00\x00\x40\x08\x06\x00\x00\x00\xaa\x69\x71\xde\ \x00\x00\x00\x04\x73\x42\x49\x54\x08\x08\x08\x08\x7c\x08\x64\x88\ \x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x02\x4d\x00\x00\x02\x4d\ \x01\x88\x6f\xfb\x19\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\ \x74\x77\x61\x72\x65\x00\x77\x77\x77\x2e\x69\x6e\x6b\x73\x63\x61\ \x70\x65\x2e\x6f\x72\x67\x9b\xee\x3c\x1a\x00\x00\x06\x69\x49\x44\ \x41\x54\x78\x9c\xed\x9b\xdd\x4b\x14\x5d\x1c\xc7\xbf\xdb\xae\xfb\ \x96\xde\x74\x11\x68\xc8\x9a\xa5\x8f\xe4\xe3\x5f\x90\x18\xa2\x48\ \xe2\xd2\xcb\x4d\xe0\x45\x37\x81\x61\x50\xac\x84\x94\x44\x98\x04\ \x4a\x42\xc8\x9a\x75\xd7\xd5\x42\xf0\x10\xd4\x85\x2b\x46\xf4\x76\ \xa3\x77\xb9\x2f\xca\x4c\x25\xba\xbb\xae\x65\xd6\x75\x39\x2f\xbb\ \x3b\xf3\x5c\xc4\x2e\xfb\xac\xae\x7b\xde\xb4\x60\x9f\x2f\x88\x3b\ \x33\xbf\xf3\x3d\x67\x3f\x3b\xe7\xfc\xce\x9c\x99\x01\x80\x27\x00\ \x0c\x00\x66\x99\xfd\x19\x00\x9e\x58\x00\x64\x00\x1c\x40\x79\xca\ \xb0\xe0\x17\x0d\x9c\x3c\x79\x12\xad\xad\xad\xbf\xb9\x3d\xfb\xa3\ \xb9\xb9\x39\xcc\xcf\xcf\xe7\xb6\x4d\x00\xe6\xf0\xf0\xb0\x59\x2e\ \x1a\x1e\x1e\xce\x75\x85\x72\x3d\xf5\x73\x2a\x7b\x00\x36\x51\x46\ \xdf\xbf\x7f\xc7\x8b\x17\x2f\x44\xd9\xed\xaa\xee\xee\x6e\x1c\x3e\ \x7c\x58\x88\x97\x10\x00\xa6\x69\x62\x7a\x7a\x1a\x7d\x7d\x7d\x22\ \xec\x4a\xea\xf1\xe3\xc7\xb8\x74\xe9\x12\x2c\x16\x0b\xb7\x97\x90\ \x2e\xa0\x28\x0a\xa2\xd1\xa8\x08\x2b\x22\x45\xa3\x51\x28\x8a\x22\ \xc4\x4b\x08\x80\x4c\x26\x03\x49\x92\x44\x58\x11\x49\x92\x24\x64\ \x32\x19\x21\x5e\x42\x00\xb8\x5c\x2e\xc8\xb2\x2c\xc2\x8a\x48\xb2\ \x2c\xc3\xe5\x72\x09\xf1\x12\x02\x40\x55\x55\x7c\xfb\xf6\x4d\x84\ \x15\x91\x36\x37\x37\xa1\x69\x9a\x10\x2f\x21\x00\x56\x57\x57\x45\ \xd8\xfc\x96\x3a\x85\x00\x08\x85\x42\x22\x6c\xa8\xb4\xb0\xb0\x20\ \xc4\x87\x1b\xc0\xd6\xd6\x16\xc2\xe1\xb0\x88\xb6\x50\x29\x12\x89\ \x08\xc9\x04\xdc\x00\xd2\xe9\xf4\xbe\x0e\x80\x59\xc9\xb2\x8c\x54\ \x2a\xc5\xed\xc3\x0d\x60\xbf\x33\x40\x56\xb2\x2c\xc3\xed\x76\x73\ \xfb\x70\x03\x48\xa5\x52\xf8\xfa\xf5\x2b\x53\xd9\xce\xce\x4e\x74\ \x76\x76\x32\x95\xdd\xd8\xd8\x10\x92\x09\xb8\xa7\xc2\x2b\x2b\x2b\ \x4c\xe5\xec\x76\x3b\xa6\xa6\xa6\x60\xb5\x5a\xd1\xd2\xd2\xc2\xf4\ \x65\x62\xb1\x18\x5a\x5a\x5a\x98\xea\xcf\x8a\xfb\x0c\x60\x1d\x00\ \x07\x07\x07\xd1\xd4\xd4\x84\x86\x86\x06\x5c\xbf\x7e\x7d\x5f\xeb\ \xce\x17\x17\x00\x45\x51\x98\x1a\x51\x5b\x5b\x8b\x5b\xb7\x6e\xe5\ \xb6\x6f\xdf\xbe\x8d\xba\xba\x3a\x6a\x9f\x70\x38\xcc\x9d\x09\xb8\ \x00\xa4\x52\x29\xa6\x01\xf0\xc1\x83\x07\x38\x78\xf0\x60\x6e\xdb\ \xed\x76\x63\x62\x62\x82\xda\x47\x44\x26\xe0\x02\xc0\x92\x01\xba\ \xba\xba\x70\xee\xdc\xb9\x6d\xfb\xcf\x9f\x3f\x8f\x9e\x9e\x1e\x2a\ \x2f\x11\xd7\x04\x5c\x00\xd2\xe9\x34\x36\x36\x36\x88\xe3\x1d\x0e\ \x07\xa6\xa6\xa6\x8a\x1e\x9f\x9c\x9c\x84\xd3\xe9\x24\xf6\xfb\xfc\ \xf9\x33\xd2\xe9\x34\x71\xfc\x4e\xe2\x02\xb0\xba\xba\x0a\xd3\x34\ \x89\xe3\x87\x86\x86\xd0\xd8\xd8\x58\xf4\xf8\xb1\x63\xc7\x70\xe3\ \xc6\x0d\xaa\x36\xc4\x62\x31\xaa\xf8\x42\x71\x01\x88\x44\x22\xc4\ \xb1\x1e\x8f\x87\xe8\xcb\x0d\x0d\x0d\xa1\xbe\xbe\x9e\xd8\x97\x37\ \x13\x30\x03\x50\x14\x85\xea\x22\xe8\xe1\xc3\x87\x44\x33\x37\x97\ \xcb\x85\x47\x8f\x1e\x11\xfb\x86\xc3\x61\xa8\xaa\x4a\x1c\x5f\x28\ \x66\x00\x34\x19\xe0\xec\xd9\xb3\xf0\x7a\xbd\xc4\xde\xa7\x4f\x9f\ \xc6\x99\x33\x67\x88\x62\x65\x59\x86\xae\xeb\xc4\xde\x85\x62\x06\ \xe0\x74\x3a\x89\x96\xc1\x1c\x0e\x07\xfc\x7e\x3f\xb5\xff\xe4\xe4\ \x24\x1c\x0e\x47\xc9\x38\x49\x92\xb8\x32\x01\x33\x00\xc3\x30\xf0\ \xe5\xcb\x97\x92\x71\x9a\xa6\x31\x0d\x54\xc9\x64\x92\xe8\x97\xe5\ \xcd\x04\xcc\x00\x62\xb1\x18\x71\x06\xb8\x72\xe5\x0a\xd5\x5c\x5f\ \xd7\x75\xf4\xf7\xf7\x13\xf9\x9b\xa6\x89\x44\x22\x41\xec\x5d\x28\ \x66\x00\x34\x19\x60\x79\x79\x99\xaa\x1b\x4c\x4c\x4c\xe0\xc3\x87\ \x0f\x7b\xd2\x96\x42\x31\x01\x50\x55\x95\x7a\x19\xec\xee\xdd\xbb\ \x44\xbf\xd4\xfa\xfa\x3a\x46\x47\x47\xa9\xbc\x43\xa1\x10\x73\x26\ \x60\x02\xa0\xeb\x3a\xf5\x14\x78\x6b\x6b\x0b\x83\x83\x83\x25\xe3\ \x06\x06\x06\xf0\xe3\xc7\x0f\x2a\x6f\x9e\x4c\xc0\x04\x80\x34\x03\ \x14\xea\xd9\xb3\x67\x98\x9d\x9d\x2d\x7a\xfc\xd5\xab\x57\x78\xfe\ \xfc\x39\xb5\xaf\x24\x49\x54\x53\xe8\x7c\x31\x01\x30\x4d\x13\xeb\ \xeb\xeb\x4c\x15\xfa\x7c\xbe\x1d\x4f\x57\x5d\xd7\x71\xed\xda\x35\ \x26\xcf\x64\x32\x49\x35\x25\xcf\x17\x13\x80\x78\x3c\xce\x5c\xe1\ \xca\xca\x0a\xee\xdf\xbf\xbf\x6d\xff\xf8\xf8\x38\x3e\x7d\xfa\xc4\ \xe4\xc9\x93\x09\x98\x00\xf0\xce\xbf\xc7\xc6\xc6\x10\x8f\xc7\x73\ \xdb\xc9\x64\x12\xe3\xe3\xe3\x5c\x9e\xac\x99\x80\x1a\x80\xa6\x69\ \xdc\x00\x14\x45\xc1\xc0\xc0\x40\x6e\xfb\xea\xd5\xab\xf8\xf9\xf3\ \x27\x97\x27\x6b\x26\xa0\x5e\x14\xd5\x34\x4d\xc8\x9d\xe0\xe9\xe9\ \x69\xcc\xcc\xcc\xc0\x66\xb3\x21\x18\x0c\x72\xfb\x49\x92\x04\x5d\ \xd7\xa9\x07\x43\x6a\x00\x0e\x87\x43\xd8\x7d\x00\x9f\xcf\x27\xc4\ \x07\xf8\x95\x0a\x59\x32\x01\x35\x00\x8b\xc5\x82\x64\x32\x49\x5d\ \xd1\x4e\xe2\x5d\xcc\xc8\xd7\xda\xda\x1a\x53\x39\xea\x31\x20\x91\ \x48\xc0\x30\x0c\xa6\xca\xf6\x52\x86\x61\x30\x41\xa0\x06\xc0\x33\ \xef\xde\x6b\xb1\xb4\x8d\x0a\x80\xa6\x69\xbf\xe5\x56\x38\xa9\x42\ \xa1\x10\xf5\x1d\x26\x6a\x00\xfb\xf9\x2c\x10\xad\x24\x49\xda\x5b\ \x00\x22\x33\xc0\x5e\x48\x96\x65\xa2\x55\xa4\x7c\x51\x01\xb0\x58\ \x2c\x5c\x8b\x0f\xf9\xaa\xaf\xaf\xc7\xcc\xcc\x0c\x5e\xbf\x7e\x8d\ \xa6\xa6\x26\x21\x9e\xf1\x78\x9c\xfa\xd9\x41\x2a\x00\x6b\x6b\x6b\ \xdc\x19\xc0\x6e\xb7\xe3\xe6\xcd\x9b\x90\x24\x09\x3d\x3d\x3d\xe8\ \xe8\xe8\x40\x34\x1a\xc5\xbd\x7b\xf7\x98\xaf\xe8\xb2\x32\x0c\x83\ \x3a\x45\x53\x01\xe0\x7d\x18\xb2\xd8\x97\x2d\x84\xc2\xa3\xc5\xc5\ \x45\xaa\x78\x62\x00\xba\xae\x33\x3f\x98\x74\xe4\xc8\x11\x04\x02\ \x81\x92\xa7\x7b\xb6\x5b\x04\x83\x41\x1c\x3d\x7a\x94\xa9\xae\xf7\ \xef\xdf\x53\x0d\x84\xc4\x00\x54\x55\xa5\x1e\x00\x2b\x2a\x2a\xe0\ \xf3\xf9\xf0\xf1\xe3\x47\x5c\xbc\x78\x91\xb8\x9c\xd7\xeb\x85\x24\ \x49\x18\x19\x19\xa1\xee\x16\xb2\x2c\xef\x0d\x00\xbb\xdd\x4e\x05\ \xa0\xbd\xbd\x1d\x91\x48\x04\x7e\xbf\x1f\x95\x95\x95\xc4\xe5\xb2\ \x72\xb9\x5c\xb8\x73\xe7\x0e\x96\x96\x96\xd0\xdd\xdd\x4d\x5c\x8e\ \x36\x13\x10\x03\xb0\xd9\x6c\xff\xb9\x86\x2f\xa6\x9a\x9a\x1a\x04\ \x02\x01\xbc\x7d\xfb\x16\x27\x4e\x9c\x20\x6e\x48\x31\x1d\x3f\x7e\ \x1c\xb3\xb3\xb3\x08\x06\x83\xf0\x78\x3c\x25\xe3\x63\xb1\x18\x0e\ \x1c\x20\x1f\xda\x88\x23\x13\x89\xc4\xae\x0f\x28\xb3\x9e\xee\xa4\ \xf2\x7a\xbd\x90\x65\x19\x23\x23\x23\xbb\xfe\xc2\x99\x4c\x86\x2a\ \x13\x10\x03\x58\x5a\x5a\x2a\x7a\xec\xd4\xa9\x53\x08\x87\xc3\xf0\ \xfb\xfd\xa8\xaa\xaa\x22\xae\x9c\x56\x6e\xb7\x3b\xd7\x2d\xba\xba\ \xba\x8a\xc6\xed\xd6\xd6\x42\x11\x01\x28\x96\x01\xaa\xab\xab\x11\ \x08\x04\xf0\xee\xdd\x3b\x34\x37\x37\x13\x57\xca\xab\x86\x86\x06\ \xbc\x7c\xf9\x12\xc1\x60\x10\xb5\xb5\xb5\xdb\x8e\x2f\x2c\x2c\x10\ \x2f\x93\x13\xad\x07\xa8\xaa\x8a\xba\xba\x3a\x5c\xbe\x7c\x39\xb7\ \xaf\xb1\xb1\x11\x17\x2e\x5c\x40\x55\x55\x15\x14\x45\x81\xd3\xe9\ \xa4\xea\x7b\xbc\x52\x55\x15\x6d\x6d\x6d\x98\x9f\x9f\xc7\xd3\xa7\ \x4f\xb1\xbc\xbc\x9c\x3b\xe6\xf1\x78\xa0\xaa\x2a\xec\x76\x7b\x49\ \x1f\x22\x00\x6e\xb7\x1b\xbd\xbd\xbd\xe8\xed\xed\xdd\xf1\xb8\x69\ \x9a\xb9\xa7\xb5\xb2\x53\xd1\x62\xff\x8b\xed\xcb\xfa\xe4\xaf\x36\ \xe7\x6f\x17\xee\x07\x00\xab\xd5\x8a\x43\x87\x0e\xa1\xbf\xbf\x7f\ \x5b\x9b\x48\x33\x01\x11\x00\x9b\xcd\x06\x9b\x4d\xd8\xfb\x55\x7f\ \x94\xfe\x7f\x6d\x2e\xfb\xe1\xcd\x9b\x37\xc2\xde\xc2\xf8\xd3\x35\ \x37\x37\x97\xfb\x5c\xf6\x2f\x4f\x5b\x01\xfc\x05\xe0\x6f\xfc\x82\ \x51\x4e\x32\x01\xfc\xf3\x2f\xe8\x31\x0e\x04\xd7\xcb\x8f\x12\x00\ \x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ \x00\x00\x01\x25\ \x3c\ \x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\x3d\x22\x68\x74\x74\x70\x3a\ \x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x32\x30\x30\ \x30\x2f\x73\x76\x67\x22\x20\x76\x69\x65\x77\x42\x6f\x78\x3d\x22\ \x30\x20\x30\x20\x34\x34\x32\x20\x33\x38\x33\x22\x3e\x3c\x74\x69\ \x74\x6c\x65\x3e\x53\x68\x61\x70\x65\x20\x2b\x20\x53\x68\x61\x70\ \x65\x3c\x2f\x74\x69\x74\x6c\x65\x3e\x3c\x67\x20\x66\x69\x6c\x6c\ \x2d\x72\x75\x6c\x65\x3d\x22\x65\x76\x65\x6e\x6f\x64\x64\x22\x3e\ \x3c\x70\x61\x74\x68\x20\x64\x3d\x22\x4d\x34\x34\x31\x2e\x34\x33\ \x20\x33\x38\x32\x2e\x33\x38\x48\x30\x4c\x32\x32\x30\x2e\x37\x33\ \x20\x30\x6c\x32\x32\x30\x2e\x37\x20\x33\x38\x32\x2e\x33\x38\x7a\ \x4d\x38\x37\x2e\x37\x20\x33\x33\x31\x2e\x37\x33\x68\x32\x36\x36\ \x2e\x30\x31\x4c\x32\x32\x30\x2e\x37\x33\x20\x31\x30\x31\x2e\x33\ \x31\x20\x38\x37\x2e\x37\x20\x33\x33\x31\x2e\x37\x33\x68\x2e\x30\ \x30\x33\x7a\x22\x2f\x3e\x3c\x70\x61\x74\x68\x20\x64\x3d\x22\x4d\ \x32\x30\x33\x20\x31\x37\x37\x68\x33\x35\x76\x39\x34\x2e\x37\x38\ \x68\x2d\x33\x35\x56\x31\x37\x37\x7a\x6d\x30\x20\x31\x31\x30\x2e\ \x37\x32\x68\x33\x35\x76\x32\x37\x2e\x38\x39\x68\x2d\x33\x35\x76\ \x2d\x32\x37\x2e\x38\x39\x7a\x22\x2f\x3e\x3c\x2f\x67\x3e\x3c\x2f\ \x73\x76\x67\x3e\ \x00\x00\x03\x3c\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x26\x00\x00\x00\x1e\x08\x06\x00\x00\x00\x40\x14\x6c\x6e\ \x00\x00\x00\x04\x73\x42\x49\x54\x08\x08\x08\x08\x7c\x08\x64\x88\ \x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x03\xb5\x00\x00\x03\xb5\ \x01\x0a\x7e\x6a\x5b\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\ \x74\x77\x61\x72\x65\x00\x77\x77\x77\x2e\x69\x6e\x6b\x73\x63\x61\ \x70\x65\x2e\x6f\x72\x67\x9b\xee\x3c\x1a\x00\x00\x00\x0b\x74\x45\ \x58\x74\x54\x69\x74\x6c\x65\x00\x53\x68\x61\x70\x65\x26\xef\x99\ \x91\x00\x00\x02\xa2\x49\x44\x41\x54\x58\x85\xbd\x97\xcd\x4b\x54\ \x51\x18\x87\x9f\xf7\x5e\x23\xec\xeb\x8e\x4a\xa1\x60\xda\x28\xd6\ \xc2\x98\x89\xb2\x02\x8d\x42\x8a\x72\x61\x9b\x48\x6a\x59\x1b\x97\ \xd5\x4e\xff\x80\x36\x2e\x5a\xb4\x89\xa0\x8d\xb4\x49\x32\x22\xab\ \x85\x8b\x4a\x6a\xe1\xca\x42\x87\x8c\x4a\xfc\xca\x16\x42\x99\x73\ \x8b\x9a\x4a\xef\x7d\x5b\xe8\x84\x44\xcc\x39\x3a\x73\xfd\x2d\x67\ \x9e\xc3\x7d\xce\xef\x3d\x9c\x3b\x23\xa9\x89\xb4\x92\x23\xae\x14\ \x55\xd4\xc7\xb7\xcc\xe6\x62\x4c\x19\x1e\xff\x72\x44\x1c\xb7\x32\ \x19\xf7\x7a\x4c\xec\xc0\x80\x16\x95\x56\xa7\xaf\x39\x26\x50\x44\ \x24\x1f\xa9\xd4\x64\xba\xdd\x11\xe7\x99\x03\x3b\x4c\xec\xe8\x8c\ \x5f\x5a\x56\xed\xf7\x0b\x72\xa9\x28\x9f\x87\xe6\xca\xc0\x80\x16\ \x95\x55\xf9\x57\x51\x3a\x6c\xf8\xe1\xf1\xaf\xbb\x83\x85\xf0\x21\ \xb0\x07\x20\x12\xb1\xd1\x19\xbf\x34\x58\xf0\xef\x02\xc7\x6d\xf8\ \x91\x49\xbf\x45\x34\xec\x01\xbc\xec\x67\xc6\x51\xae\x36\x4b\x3b\ \xd7\x41\x7b\xa9\xf9\xcb\xa2\xfa\x78\xa5\x14\x14\xb8\xb1\xe5\x9d\ \xdf\x01\x62\x26\x76\x6c\x4c\x37\x66\x9c\xaf\x37\x51\xbd\xf0\xbf\ \xef\x0b\xd6\xd8\x8a\x9d\x1b\xa5\xde\x4c\x7d\xaf\xc8\xb8\xfe\x73\ \xe4\xff\x52\x50\x80\xc6\x4c\x3b\xff\x37\xa3\x93\xf3\xfb\x16\xc3\ \x85\x3e\xa0\x2a\x17\x97\x97\xd8\xab\xb1\x6f\xdb\x33\xae\x7f\x0f\ \x38\x6a\xc3\xa7\x26\xd2\x6d\x81\xd2\x0d\x6c\x32\xb1\x6b\x16\x7b\ \x3d\x31\x9f\x0c\x08\xfa\x80\x6a\xab\x05\xca\x09\xa0\x15\xb0\xba\ \x17\xd7\x74\xc6\x52\x13\xe9\xb6\x10\x19\x14\x5b\x29\x40\xd1\xd3\ \xb6\x52\xab\x16\x53\x55\x49\x4d\xa6\x3b\x80\x1e\x2c\xc6\x91\x4f\ \xac\x47\x39\x32\x3b\xbb\x39\x35\xe5\xdf\x16\xe5\x4c\x94\x42\xd9\ \x58\x89\x8d\x8c\xcd\x55\x4a\xc6\x7d\x80\x72\x20\x6a\xa1\x6c\x8c\ \xa3\x0c\x43\x1a\xc4\x75\x87\xd6\x53\x0a\x22\x78\x25\x15\x2a\x46\ \x31\xc7\x61\x48\x83\xa0\x01\xe1\xe5\x7a\x08\xfd\x7d\xae\x0d\x94\ \xac\x2b\xfb\xa8\xc5\x3f\x8f\xa9\x70\x3f\x6a\xa1\x6c\xac\x47\x99\ \x2c\x2f\xff\x9e\xd8\xe5\x9d\x45\xe8\x04\xc2\x08\x9d\x80\x55\x9e\ \x31\x11\xd1\x44\x3c\xd6\x05\x9c\x07\x7e\x44\xa3\xb4\x94\x35\x1d\ \xfe\x44\x4d\xac\xd7\x41\x1b\x15\xa6\x6d\xd7\x08\xf2\x08\xc8\xf9\ \xff\x22\x6f\x31\x80\xbd\x35\x25\x23\x41\xe0\x1e\x04\x5e\x58\x2d\ \x10\x9e\x00\xe7\xb0\x6c\x3a\xaf\xeb\x62\x7f\xdd\xd6\x4f\xc5\x81\ \x77\x12\x95\x6e\x1b\x3e\x51\x13\xeb\x75\x45\x9b\x80\x0f\x91\x8a\ \x01\xd4\xd5\xc9\xaf\x44\xad\x77\x51\x45\xaf\x00\x81\x89\xaf\x8f\ \x97\x0c\x2f\x06\x6e\x03\x86\xa6\x0b\x76\xc1\x26\xe3\x25\xd7\x55\ \xa4\x15\x48\x9b\x58\x9b\xa6\x0b\x7a\xf3\x27\xe3\x5e\x7f\xa8\xce\ \x61\xe0\xad\x89\x35\x35\x5d\xf0\x57\xd2\xbe\xda\x6d\xef\xdd\x0d\ \xd2\x04\x3c\xb5\xe1\x57\x34\xed\x47\x2a\x06\x50\xbf\xd3\xfb\x32\ \x37\xed\xb5\xa0\x74\xd9\xc9\x79\xfd\xa1\x3a\x87\x80\x77\x91\x8a\ \x01\x34\x37\xcb\x62\xa2\x36\xd6\x09\xb4\x03\xbf\x4d\xfc\x72\xd3\ \x8d\x2c\x37\x1d\xf9\xaf\x8b\x44\x4d\xec\x96\x88\x9e\xd2\x90\xcf\ \x26\x36\xdb\xb4\xc0\x8d\x3f\xdd\x96\xf1\x25\x4a\xd3\x97\xc3\x00\ \x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ \x00\x00\x00\x21\ \x3c\ \xb8\x64\x18\xca\xef\x9c\x95\xcd\x21\x1c\xbf\x60\xa1\xbd\xdd\xa7\ \x00\x00\x00\x05\x69\x74\x5f\x49\x54\x88\x00\x00\x00\x02\x01\x01\ \ \x00\x00\x3e\x00\ \x3c\ \xb8\x64\x18\xca\xef\x9c\x95\xcd\x21\x1c\xbf\x60\xa1\xbd\xdd\xa7\ \x00\x00\x00\x05\x64\x65\x5f\x44\x45\x42\x00\x00\x04\x58\x00\x00\ \x00\x41\x00\x00\x0d\xc8\x00\x00\x04\x92\x00\x00\x12\x54\x00\x00\ \x04\xd4\x00\x00\x22\xe1\x00\x00\x54\xc7\x00\x00\x16\x76\x00\x02\ \x73\xfe\x00\x00\x0d\x16\x00\x04\x87\x9b\x00\x00\x32\x56\x00\x04\ \xd6\x8d\x00\x00\x25\x8d\x00\x04\xd6\x8d\x00\x00\x32\x8d\x00\x04\ \xe5\x7b\x00\x00\x15\x4b\x00\x04\xf6\x35\x00\x00\x32\xb8\x00\x05\ \x48\x35\x00\x00\x0b\x4a\x00\x05\x48\x35\x00\x00\x23\x36\x00\x05\ \x48\x35\x00\x00\x30\x6a\x00\x05\x68\xa8\x00\x00\x0b\x73\x00\x05\ \x68\xa8\x00\x00\x30\x96\x00\x05\x8c\x04\x00\x00\x17\xdd\x00\x05\ \x98\xc5\x00\x00\x02\x29\x00\x05\x98\xc5\x00\x00\x19\x9d\x00\x05\ \xa1\x05\x00\x00\x31\x10\x00\x05\xab\x60\x00\x00\x1d\x0d\x00\x1c\ \x8e\x95\x00\x00\x1b\xd2\x00\x29\x46\x88\x00\x00\x02\x61\x00\x3f\ \x4b\x4f\x00\x00\x33\xb1\x00\x43\x02\x93\x00\x00\x31\x3e\x00\x47\ \x96\xc4\x00\x00\x0d\xe8\x00\x48\x36\xff\x00\x00\x0e\xb5\x00\x48\ \x37\x02\x00\x00\x0e\xd9\x00\x48\x37\x03\x00\x00\x0e\xfd\x00\x48\ \x37\x04\x00\x00\x0f\x21\x00\x48\x37\x08\x00\x00\x0f\x45\x00\x52\ \x78\xbc\x00\x00\x23\x07\x00\x58\xc0\x60\x00\x00\x34\xb0\x00\x6a\ \x2b\x7e\x00\x00\x23\xd3\x00\x80\x80\x83\x00\x00\x01\x28\x00\xa8\ \x87\xd4\x00\x00\x18\xed\x00\xad\x41\x0e\x00\x00\x31\xb7\x00\xe5\ \x78\xa9\x00\x00\x21\xef\x01\x43\xa7\x7e\x00\x00\x04\x20\x01\x6c\ \x14\x93\x00\x00\x1c\x3d\x01\x71\x98\x3f\x00\x00\x34\x51\x01\x99\ \x40\x64\x00\x00\x1b\xa7\x01\x9e\xa3\x45\x00\x00\x21\x96\x01\xce\ \x99\x65\x00\x00\x18\x0e\x01\xd7\x71\xee\x00\x00\x02\x8b\x01\xe1\ \x97\x5e\x00\x00\x26\x6e\x02\x0a\xed\x1e\x00\x00\x00\x67\x02\x30\ \x48\xfe\x00\x00\x26\xef\x02\x4c\x1f\x64\x00\x00\x10\x6d\x02\x83\ \x1f\x6e\x00\x00\x27\xe7\x02\xda\x36\xee\x00\x00\x27\x76\x02\xe5\ \x82\x07\x00\x00\x12\x75\x03\x1e\xbf\x15\x00\x00\x16\x22\x03\x7f\ \x5c\xdc\x00\x00\x16\x50\x03\x98\xbd\x62\x00\x00\x0d\x41\x04\x26\ \x57\x6e\x00\x00\x35\xb3\x04\x67\xb4\xfe\x00\x00\x05\xb1\x04\xab\ \x8f\x01\x00\x00\x10\xa9\x04\xab\x8f\x03\x00\x00\x10\xce\x04\xab\ \x8f\x07\x00\x00\x11\x74\x04\xaf\x83\x57\x00\x00\x21\x71\x04\xff\ \x6f\xd9\x00\x00\x1a\xc3\x05\x2c\xaa\x07\x00\x00\x1d\xff\x05\x38\ \x5b\x7c\x00\x00\x15\xed\x05\x3f\x2f\x2e\x00\x00\x25\xb1\x05\x46\ \xa4\xee\x00\x00\x14\xac\x05\x47\x2f\xd7\x00\x00\x16\x9e\x05\x54\ \xc4\xe9\x00\x00\x13\x85\x05\x6c\xc7\x3c\x00\x00\x32\xf7\x05\x82\ \xae\x8e\x00\x00\x30\xc2\x05\x8c\x46\xc5\x00\x00\x18\x7d\x05\x8c\ \x48\x35\x00\x00\x18\xb4\x05\x8c\xac\x35\x00\x00\x19\x33\x05\x8c\ \xe0\x44\x00\x00\x19\x6c\x05\xa6\x7a\xf1\x00\x00\x1e\x31\x05\xaa\ \x8b\xc3\x00\x00\x23\x62\x05\xe6\x3d\xae\x00\x00\x2f\x4e\x06\x25\ \x05\x65\x00\x00\x17\x66\x06\x39\x75\x8d\x00\x00\x11\xd5\x06\x79\ \xf9\xd4\x00\x00\x24\x02\x06\x7b\x87\x94\x00\x00\x1d\xd1\x06\x8e\ \x99\xee\x00\x00\x2d\x7f\x07\x5f\xff\x0e\x00\x00\x09\x8d\x07\x6e\ \x5a\x79\x00\x00\x0f\x94\x07\x75\xd1\x0e\x00\x00\x1f\xba\x07\x7b\ \x80\xb3\x00\x00\x24\x45\x08\x18\x71\x89\x00\x00\x35\x50\x08\x4b\ \x9e\x39\x00\x00\x20\x94\x08\x4c\x83\x51\x00\x00\x13\xdd\x08\x4e\ \x0c\xb7\x00\x00\x31\x76\x08\x94\x32\x13\x00\x00\x12\xb7\x08\xc4\ \x9b\xe2\x00\x00\x09\x0f\x08\xe6\xdb\xb5\x00\x00\x16\xf3\x09\x42\ \x2c\xe4\x00\x00\x15\x76\x09\x4d\x67\xfe\x00\x00\x0b\x0a\x09\x68\ \xe3\x3e\x00\x00\x15\x0b\x09\x7c\xcf\x74\x00\x00\x15\xa1\x09\x9f\ \xca\xd3\x00\x00\x1b\x5d\x09\xcf\xe7\x17\x00\x00\x17\x2d\x09\xe8\ \x4d\x83\x00\x00\x22\xa6\x0a\x2f\xc5\xf3\x00\x00\x2e\x79\x0a\x65\ \xb9\x1c\x00\x00\x10\x35\x0a\x65\xca\x45\x00\x00\x30\x1d\x0a\x75\ \x64\x69\x00\x00\x1c\x90\x0a\xb4\xa1\xee\x00\x00\x36\xd3\x0a\xba\ \x1f\xaf\x00\x00\x34\xe2\x0a\xc2\xca\x31\x00\x00\x2f\x9a\x0a\xd7\ \xdb\xc5\x00\x00\x1d\x38\x0a\xe1\x95\xe4\x00\x00\x11\x99\x0a\xf7\ \x7b\x42\x00\x00\x1b\x13\x0b\x75\x94\x47\x00\x00\x1d\x93\x0b\x80\ \x57\x65\x00\x00\x19\xd2\x0b\x97\x7b\xad\x00\x00\x2d\x07\x0b\xfc\ \xa6\x8e\x00\x00\x2b\x0f\x0c\x7c\x88\xff\x00\x00\x01\x81\x0c\xa4\ \x22\xae\x00\x00\x37\x6a\x0c\xbb\x01\x73\x00\x00\x09\x50\x0c\xbb\ \x01\x73\x00\x00\x1b\xfc\x0c\xd1\x5e\x0e\x00\x00\x2f\xd1\x0c\xfc\ \x45\xce\x00\x00\x28\xa2\x0d\x09\xf9\x75\x00\x00\x23\x94\x0d\x1b\ \xa6\xf3\x00\x00\x0d\x93\x0d\x44\x5a\x01\x00\x00\x10\xf3\x0d\x44\ \x5a\x03\x00\x00\x11\x1e\x0d\x44\x5a\x07\x00\x00\x11\x49\x0d\x6c\ \xe5\x0e\x00\x00\x2e\xae\x0d\xe6\xc5\xa4\x00\x00\x0f\x69\x0e\x61\ \x3e\x04\x00\x00\x01\xe3\x0e\x68\xb0\x7f\x00\x00\x0e\x7e\x0e\x81\ \x4e\x34\x00\x00\x00\x00\x0e\x8f\x55\x45\x00\x00\x1a\x68\x0e\xb5\ \x5d\x1e\x00\x00\x2c\x2c\x0e\xc3\x60\x16\x00\x00\x0b\x9c\x0f\x10\ \x07\x39\x00\x00\x36\x80\x0f\x53\xf3\xc5\x00\x00\x25\x46\x0f\x6b\ \x84\x53\x00\x00\x13\x2a\x0f\x7b\x26\x8d\x00\x00\x2e\xfa\x0f\x9d\ \x2c\x77\x00\x00\x17\x95\x0f\xb7\x6f\xd9\x00\x00\x0e\x14\x0f\xb7\ \x6f\xd9\x00\x00\x33\x49\x69\x00\x00\x39\x7d\x03\x00\x00\x00\x38\ \x00\x50\x00\x72\x00\x6f\x00\x67\x00\x72\x00\x61\x00\x6d\x00\x6d\ \x00\x20\x00\x73\x00\x6f\x00\x6c\x00\x6c\x00\x20\x00\x62\x00\x65\ \x00\x65\x00\x6e\x00\x64\x00\x65\x00\x74\x00\x20\x00\x77\x00\x65\ \x00\x72\x00\x64\x00\x65\x00\x6e\x08\x00\x00\x00\x00\x06\x00\x00\ \x00\x0d\x41\x62\x6f\x75\x74\x20\x74\x6f\x20\x71\x75\x69\x74\x07\ \x00\x00\x00\x0d\x41\x73\x6b\x42\x65\x66\x6f\x72\x65\x51\x75\x69\ \x74\x01\x03\x00\x00\x00\x78\x00\x50\x00\x72\x00\x6f\x00\x67\x00\ \x72\x00\x61\x00\x6d\x00\x6d\x00\x20\x00\x73\x00\x6f\x00\x6c\x00\ \x6c\x00\x20\x00\x62\x00\x65\x00\x65\x00\x6e\x00\x64\x00\x65\x00\ \x74\x00\x20\x00\x77\x00\x65\x00\x72\x00\x64\x00\x65\x00\x6e\x00\ \x2c\x00\x20\x00\x61\x00\x62\x00\x65\x00\x72\x00\x20\x00\x53\x00\ \x65\x00\x73\x00\x73\x00\x69\x00\x6f\x00\x6e\x00\x20\x00\x7b\x00\ \x7d\x00\x20\x00\x69\x00\x73\x00\x74\x00\x20\x00\x6e\x00\x6f\x00\ \x63\x00\x68\x00\x20\x00\x6f\x00\x66\x00\x66\x00\x65\x00\x6e\x08\ \x00\x00\x00\x00\x06\x00\x00\x00\x27\x41\x62\x6f\x75\x74\x20\x74\ \x6f\x20\x71\x75\x69\x74\x20\x62\x75\x74\x20\x73\x65\x73\x73\x69\ \x6f\x6e\x20\x7b\x7d\x20\x73\x74\x69\x6c\x6c\x20\x6f\x70\x65\x6e\ \x07\x00\x00\x00\x0d\x41\x73\x6b\x42\x65\x66\x6f\x72\x65\x51\x75\ \x69\x74\x01\x03\x00\x00\x00\x28\x00\xc4\x00\x6e\x00\x64\x00\x65\ \x00\x72\x00\x75\x00\x6e\x00\x67\x00\x65\x00\x6e\x00\x20\x00\x56\ \x00\x65\x00\x72\x00\x77\x00\x65\x00\x72\x00\x66\x00\x65\x00\x6e\ \x08\x00\x00\x00\x00\x06\x00\x00\x00\x0f\x44\x69\x73\x63\x61\x72\ \x64\x20\x43\x68\x61\x6e\x67\x65\x73\x07\x00\x00\x00\x0d\x41\x73\ \x6b\x42\x65\x66\x6f\x72\x65\x51\x75\x69\x74\x01\x03\x00\x00\x00\ \x2c\x00\x4d\x00\xf6\x00\x63\x00\x68\x00\x74\x00\x65\x00\x6e\x00\ \x20\x00\x53\x00\x69\x00\x65\x00\x20\x00\x73\x00\x70\x00\x65\x00\ \x69\x00\x63\x00\x68\x00\x65\x00\x72\x00\x6e\x00\x3f\x08\x00\x00\ \x00\x00\x06\x00\x00\x00\x14\x44\x6f\x20\x79\x6f\x75\x20\x77\x61\ \x6e\x74\x20\x74\x6f\x20\x73\x61\x76\x65\x3f\x07\x00\x00\x00\x0d\ \x41\x73\x6b\x42\x65\x66\x6f\x72\x65\x51\x75\x69\x74\x01\x03\x00\ \x00\x00\x1a\x00\x4e\x00\x69\x00\x63\x00\x68\x00\x74\x00\x20\x00\ \x42\x00\x65\x00\x65\x00\x6e\x00\x64\x00\x65\x00\x6e\x08\x00\x00\ \x00\x00\x06\x00\x00\x00\x0a\x44\x6f\x6e\x27\x74\x20\x51\x75\x69\ \x74\x07\x00\x00\x00\x0d\x41\x73\x6b\x42\x65\x66\x6f\x72\x65\x51\ \x75\x69\x74\x01\x03\x00\x00\x00\x12\x00\x53\x00\x70\x00\x65\x00\ \x69\x00\x63\x00\x68\x00\x65\x00\x72\x00\x6e\x08\x00\x00\x00\x00\ \x06\x00\x00\x00\x04\x53\x61\x76\x65\x07\x00\x00\x00\x0d\x41\x73\ \x6b\x42\x65\x66\x6f\x72\x65\x51\x75\x69\x74\x01\x03\x00\x00\x00\ \x0a\x00\x24\x00\x50\x00\x41\x00\x54\x00\x48\x08\x00\x00\x00\x00\ \x06\x00\x00\x00\x05\x24\x50\x41\x54\x48\x07\x00\x00\x00\x06\x44\ \x69\x61\x6c\x6f\x67\x01\x03\x00\x00\x01\x02\x00\x45\x00\x69\x00\ \x6e\x00\x6e\x00\x20\x00\x61\x00\x62\x00\x73\x00\x6f\x00\x6c\x00\ \x75\x00\x74\x00\x65\x00\x72\x00\x20\x00\x50\x00\x66\x00\x61\x00\ \x64\x00\x20\x00\x70\x00\x72\x00\x6f\x00\x20\x00\x5a\x00\x65\x00\ \x69\x00\x6c\x00\x65\x00\x20\x00\x28\x00\x7a\x00\x2e\x00\x42\x00\ \x2e\x00\x20\x00\x2f\x00\x68\x00\x6f\x00\x6d\x00\x65\x00\x2f\x00\ \x75\x00\x73\x00\x65\x00\x72\x00\x2f\x00\x61\x00\x75\x00\x64\x00\ \x69\x00\x6f\x00\x2d\x00\x62\x00\x69\x00\x6e\x00\x29\x00\x2e\x00\ \x20\x00\x53\x00\x63\x00\x68\x00\x72\x00\xe4\x00\x67\x00\x73\x00\ \x74\x00\x72\x00\x69\x00\x63\x00\x68\x00\x20\x00\x61\x00\x6d\x00\ \x20\x00\x45\x00\x6e\x00\x64\x00\x65\x00\x20\x00\x73\x00\x70\x00\ \x69\x00\x65\x00\x6c\x00\x74\x00\x20\x00\x6b\x00\x65\x00\x69\x00\ \x6e\x00\x65\x00\x20\x00\x52\x00\x6f\x00\x6c\x00\x6c\x00\x65\x00\ \x2e\x00\x20\x00\x4b\x00\x65\x00\x69\x00\x6e\x00\x65\x00\x20\x00\ \x57\x00\x69\x00\x6c\x00\x64\x00\x63\x00\x61\x00\x72\x00\x64\x00\ \x73\x00\x20\x00\x77\x00\x69\x00\x65\x00\x20\x00\x2a\x00\x20\x00\ \x6f\x00\x64\x00\x65\x00\x72\x00\x20\x00\x2e\x00\x2e\x08\x00\x00\ \x00\x00\x06\x00\x00\x00\x78\x41\x64\x64\x20\x6f\x6e\x65\x20\x61\ \x62\x73\x6f\x6c\x75\x74\x65\x20\x70\x61\x74\x68\x20\x74\x6f\x20\ \x61\x20\x64\x69\x72\x65\x63\x74\x6f\x72\x79\x20\x28\x65\x2e\x67\ \x2e\x20\x2f\x68\x6f\x6d\x65\x2f\x75\x73\x65\x72\x2f\x61\x75\x64\ \x69\x6f\x2d\x62\x69\x6e\x29\x20\x70\x65\x72\x20\x6c\x69\x6e\x65\ \x2e\x20\x4e\x6f\x20\x77\x69\x6c\x64\x63\x61\x72\x64\x73\x2e\x20\ \x54\x72\x61\x69\x6c\x69\x6e\x67\x20\x73\x6c\x61\x73\x68\x65\x73\ \x2f\x20\x64\x6f\x6e\x27\x74\x20\x6d\x61\x74\x74\x65\x72\x2e\x07\ \x00\x00\x00\x06\x44\x69\x61\x6c\x6f\x67\x01\x03\x00\x00\x01\x12\ \x00\x42\x00\x6c\x00\x61\x00\x63\x00\x6b\x00\x6c\x00\x69\x00\x73\ \x00\x74\x00\x20\x00\x2d\x00\x20\x00\x41\x00\x75\x00\x73\x00\x66\ \x00\xfc\x00\x68\x00\x72\x00\x62\x00\x61\x00\x72\x00\x65\x00\x20\ \x00\x44\x00\x61\x00\x74\x00\x65\x00\x69\x00\x6e\x00\x61\x00\x6d\ \x00\x65\x00\x6e\x00\x20\x00\x28\x00\x6b\x00\x65\x00\x69\x00\x6e\ \x00\x65\x00\x20\x00\x67\x00\x61\x00\x6e\x00\x7a\x00\x65\x00\x6e\ \x00\x20\x00\x50\x00\x66\x00\x61\x00\x64\x00\x65\x00\x29\x00\x20\ \x00\x68\x00\x69\x00\x6e\x00\x7a\x00\x75\x00\x66\x00\xfc\x00\x67\ \x00\x65\x00\x6e\x00\x20\x00\x75\x00\x6d\x00\x20\x00\x64\x00\x69\ \x00\x65\x00\x73\x00\x65\x00\x20\x00\x61\x00\x75\x00\x73\x00\x20\ \x00\x64\x00\x65\x00\x6d\x00\x20\x00\x50\x00\x72\x00\x6f\x00\x67\ \x00\x72\x00\x61\x00\x6d\x00\x6d\x00\x73\x00\x74\x00\x61\x00\x72\ \x00\x74\x00\x65\x00\x72\x00\x20\x00\x7a\x00\x75\x00\x20\x00\x65\ \x00\x6e\x00\x74\x00\x66\x00\x65\x00\x72\x00\x6e\x00\x65\x00\x6e\ \x00\x2e\x00\x20\x00\x45\x00\x69\x00\x6e\x00\x20\x00\x50\x00\x72\ \x00\x6f\x00\x67\x00\x72\x00\x61\x00\x6d\x00\x6d\x00\x20\x00\x70\ \x00\x72\x00\x6f\x00\x20\x00\x5a\x00\x65\x00\x69\x00\x6c\x00\x65\ \x00\x2e\x08\x00\x00\x00\x00\x06\x00\x00\x00\x64\x42\x6c\x61\x63\ \x6b\x6c\x69\x73\x74\x20\x2d\x20\x45\x78\x63\x6c\x75\x64\x65\x20\ \x65\x78\x65\x63\x75\x74\x61\x62\x6c\x65\x20\x6e\x61\x6d\x65\x73\ \x20\x28\x6e\x6f\x74\x20\x70\x61\x74\x68\x73\x29\x20\x66\x72\x6f\ \x6d\x20\x74\x68\x65\x20\x70\x72\x6f\x67\x72\x61\x6d\x20\x6c\x61\ \x75\x6e\x63\x68\x65\x72\x2e\x20\x4f\x6e\x65\x20\x65\x78\x65\x63\ \x75\x74\x61\x62\x6c\x65\x20\x70\x65\x72\x20\x6c\x69\x6e\x65\x2e\ \x07\x00\x00\x00\x06\x44\x69\x61\x6c\x6f\x67\x01\x03\x00\x00\x02\ \x70\x00\x4e\x00\x75\x00\x72\x00\x20\x00\x66\x00\xfc\x00\x72\x00\ \x20\x00\x66\x00\x6f\x00\x72\x00\x74\x00\x67\x00\x65\x00\x73\x00\ \x63\x00\x68\x00\x72\x00\x69\x00\x74\x00\x74\x00\x65\x00\x6e\x00\ \x65\x00\x20\x00\x42\x00\x65\x00\x6e\x00\x75\x00\x74\x00\x7a\x00\ \x65\x00\x72\x00\x21\x00\x20\x00\x48\x00\x69\x00\x65\x00\x72\x00\ \x20\x00\x61\x00\x62\x00\x73\x00\x6f\x00\x6c\x00\x75\x00\x74\x00\ \x65\x00\x20\x00\x50\x00\x66\x00\x61\x00\x64\x00\x65\x00\x20\x00\ \x68\x00\x69\x00\x6e\x00\x7a\x00\x75\x00\x66\x00\xfc\x00\x67\x00\ \x65\x00\x6e\x00\x2c\x00\x20\x00\x69\x00\x6e\x00\x20\x00\x64\x00\ \x65\x00\x6e\x00\x65\x00\x6e\x00\x20\x00\x73\x00\x69\x00\x63\x00\ \x68\x00\x20\x00\x61\x00\x75\x00\x73\x00\x66\x00\xfc\x00\x68\x00\ \x72\x00\x62\x00\x61\x00\x72\x00\x65\x00\x20\x00\x44\x00\x61\x00\ \x74\x00\x65\x00\x69\x00\x65\x00\x6e\x00\x20\x00\x62\x00\x65\x00\ \x66\x00\x69\x00\x6e\x00\x64\x00\x65\x00\x6e\x00\x2e\x00\x20\x00\ \x44\x00\x69\x00\x65\x00\x73\x00\x65\x00\x20\x00\x45\x00\x69\x00\ \x6e\x00\x73\x00\x74\x00\x65\x00\x6c\x00\x6c\x00\x75\x00\x6e\x00\ \x67\x00\x65\x00\x6e\x00\x20\x00\x67\x00\x65\x00\x6c\x00\x74\x00\ \x65\x00\x6e\x00\x20\x00\x6e\x00\x75\x00\x72\x00\x20\x00\x66\x00\ \xfc\x00\x72\x00\x20\x00\x41\x00\x67\x00\x6f\x00\x72\x00\x64\x00\ \x65\x00\x6a\x00\x6f\x00\x2f\x00\x4e\x00\x53\x00\x4d\x00\x2e\x00\ \x20\x00\xc4\x00\x6e\x00\x64\x00\x65\x00\x72\x00\x75\x00\x6e\x00\ \x67\x00\x65\x00\x6e\x00\x20\x00\x62\x00\x65\x00\x6e\x00\xf6\x00\ \x74\x00\x69\x00\x67\x00\x65\x00\x6e\x00\x20\x00\x65\x00\x69\x00\ \x6e\x00\x65\x00\x6e\x00\x20\x00\x6b\x00\x6f\x00\x6d\x00\x70\x00\ \x6c\x00\x65\x00\x74\x00\x74\x00\x65\x00\x6e\x00\x20\x00\x50\x00\ \x72\x00\x6f\x00\x67\x00\x72\x00\x61\x00\x6d\x00\x6d\x00\x6e\x00\ \x65\x00\x75\x00\x73\x00\x74\x00\x61\x00\x72\x00\x74\x00\x2e\x00\ \x20\x00\x55\x00\x6d\x00\x20\x00\x50\x00\x72\x00\x6f\x00\x67\x00\ \x72\x00\x61\x00\x6d\x00\x6d\x00\x65\x00\x20\x00\x7a\x00\x75\x00\ \x6d\x00\x20\x00\x53\x00\x63\x00\x68\x00\x6e\x00\x65\x00\x6c\x00\ \x6c\x00\x73\x00\x74\x00\x61\x00\x72\x00\x74\x00\x65\x00\x72\x00\ \x20\x00\x68\x00\x69\x00\x6e\x00\x7a\x00\x75\x00\x7a\x00\x75\x00\ \x66\x00\xfc\x00\x67\x00\x65\x00\x6e\x00\x20\x00\x62\x00\x69\x00\ \x74\x00\x74\x00\x65\x00\x20\x00\x64\x00\x65\x00\x6e\x00\x20\x00\ \x54\x00\x61\x00\x62\x00\x20\x00\x26\x00\x71\x00\x75\x00\x6f\x00\ \x74\x00\x3b\x00\x50\x00\x72\x00\x6f\x00\x67\x00\x72\x00\x61\x00\ \x6d\x00\x6d\x00\x73\x00\x74\x00\x61\x00\x72\x00\x74\x00\x65\x00\ \x72\x00\x26\x00\x71\x00\x75\x00\x6f\x00\x74\x00\x3b\x00\x20\x00\ \x62\x00\x65\x00\x6e\x00\x75\x00\x74\x00\x7a\x00\x65\x00\x6e\x00\ \x2e\x08\x00\x00\x00\x00\x06\x00\x00\x00\xd3\x46\x6f\x72\x20\x61\ \x64\x76\x61\x6e\x63\x65\x64\x20\x75\x73\x65\x72\x73\x20\x6f\x6e\ \x6c\x79\x21\x20\x41\x64\x64\x20\x65\x78\x65\x63\x75\x74\x61\x62\ \x6c\x65\x20\x70\x61\x74\x68\x73\x20\x74\x6f\x20\x74\x68\x65\x20\ \x65\x6e\x76\x69\x72\x6f\x6e\x6d\x65\x6e\x74\x2c\x20\x6a\x75\x73\ \x74\x20\x66\x6f\x72\x20\x41\x67\x6f\x72\x64\x65\x6a\x6f\x20\x61\ \x6e\x64\x20\x4e\x53\x4d\x2e\x20\x43\x68\x61\x6e\x67\x65\x73\x20\ \x6e\x65\x65\x64\x20\x61\x20\x70\x72\x6f\x67\x72\x61\x6d\x20\x72\ \x65\x73\x74\x61\x72\x74\x20\x61\x66\x74\x65\x72\x77\x61\x72\x64\ \x73\x2e\x20\x49\x66\x20\x79\x6f\x75\x20\x77\x61\x6e\x74\x20\x79\ \x6f\x75\x72\x20\x70\x72\x6f\x67\x72\x61\x6d\x73\x20\x69\x6e\x20\ \x74\x68\x65\x20\x61\x70\x70\x6c\x69\x63\x61\x74\x69\x6f\x6e\x20\ \x6c\x61\x75\x6e\x63\x68\x65\x72\x20\x75\x73\x65\x20\x74\x68\x65\ \x20\x6c\x61\x75\x6e\x63\x68\x65\x72\x20\x74\x61\x62\x2e\x07\x00\ \x00\x00\x06\x44\x69\x61\x6c\x6f\x67\x01\x03\x00\x00\x00\x1e\x00\ \x50\x00\x72\x00\x6f\x00\x67\x00\x72\x00\x61\x00\x6d\x00\x6d\x00\ \x73\x00\x74\x00\x61\x00\x72\x00\x74\x00\x65\x00\x72\x08\x00\x00\ \x00\x00\x06\x00\x00\x00\x08\x4c\x61\x75\x6e\x63\x68\x65\x72\x07\ \x00\x00\x00\x06\x44\x69\x61\x6c\x6f\x67\x01\x03\x00\x00\x00\x1a\ \x00\x45\x00\x69\x00\x6e\x00\x73\x00\x74\x00\x65\x00\x6c\x00\x6c\ \x00\x75\x00\x6e\x00\x67\x00\x65\x00\x6e\x08\x00\x00\x00\x00\x06\ \x00\x00\x00\x08\x53\x65\x74\x74\x69\x6e\x67\x73\x07\x00\x00\x00\ \x06\x44\x69\x61\x6c\x6f\x67\x01\x03\x00\x00\x01\x04\x00\x57\x00\ \x68\x00\x69\x00\x74\x00\x65\x00\x6c\x00\x69\x00\x73\x00\x74\x00\ \x20\x00\x2d\x00\x20\x00\x41\x00\x75\x00\x73\x00\x66\x00\xfc\x00\ \x68\x00\x72\x00\x62\x00\x61\x00\x72\x00\x65\x00\x20\x00\x44\x00\ \x61\x00\x74\x00\x65\x00\x69\x00\x6e\x00\x61\x00\x6d\x00\x65\x00\ \x6e\x00\x20\x00\x28\x00\x6b\x00\x65\x00\x69\x00\x6e\x00\x65\x00\ \x20\x00\x67\x00\x61\x00\x6e\x00\x7a\x00\x65\x00\x6e\x00\x20\x00\ \x50\x00\x66\x00\x61\x00\x64\x00\x65\x00\x29\x00\x20\x00\x68\x00\ \x69\x00\x6e\x00\x7a\x00\x75\x00\x66\x00\xfc\x00\x67\x00\x65\x00\ \x6e\x00\x20\x00\x75\x00\x6d\x00\x20\x00\x64\x00\x69\x00\x65\x00\ \x73\x00\x65\x00\x20\x00\x69\x00\x6d\x00\x20\x00\x50\x00\x72\x00\ \x6f\x00\x67\x00\x72\x00\x61\x00\x6d\x00\x6d\x00\x73\x00\x74\x00\ \x61\x00\x72\x00\x74\x00\x65\x00\x72\x00\x20\x00\x61\x00\x6e\x00\ \x7a\x00\x75\x00\x7a\x00\x65\x00\x69\x00\x67\x00\x65\x00\x6e\x00\ \x2e\x00\x20\x00\x45\x00\x69\x00\x6e\x00\x20\x00\x50\x00\x72\x00\ \x6f\x00\x67\x00\x72\x00\x61\x00\x6d\x00\x6d\x00\x20\x00\x70\x00\ \x72\x00\x6f\x00\x20\x00\x5a\x00\x65\x00\x69\x00\x6c\x00\x65\x00\ \x2e\x08\x00\x00\x00\x00\x06\x00\x00\x00\x5e\x57\x68\x69\x74\x65\ \x6c\x69\x73\x74\x20\x2d\x20\x41\x64\x64\x20\x65\x78\x65\x63\x75\ \x74\x61\x62\x6c\x65\x20\x6e\x61\x6d\x65\x73\x20\x28\x6e\x6f\x74\ \x20\x70\x61\x74\x68\x73\x29\x20\x74\x6f\x20\x74\x68\x65\x20\x70\ \x72\x6f\x67\x72\x61\x6d\x20\x6c\x61\x75\x6e\x63\x68\x65\x72\x2e\ \x20\x4f\x6e\x65\x20\x65\x78\x65\x63\x75\x74\x61\x62\x6c\x65\x20\ \x70\x65\x72\x20\x6c\x69\x6e\x65\x2e\x07\x00\x00\x00\x06\x44\x69\ \x61\x6c\x6f\x67\x01\x03\x00\x00\x00\x18\x00\x42\x00\x65\x00\x73\ \x00\x63\x00\x68\x00\x72\x00\x65\x00\x69\x00\x62\x00\x75\x00\x6e\ \x00\x67\x08\x00\x00\x00\x00\x06\x00\x00\x00\x0b\x44\x65\x73\x63\ \x72\x69\x70\x74\x69\x6f\x6e\x07\x00\x00\x00\x08\x4c\x61\x75\x6e\ \x63\x68\x65\x72\x01\x03\x00\x00\x00\x08\x00\x4e\x00\x61\x00\x6d\ \x00\x65\x08\x00\x00\x00\x00\x06\x00\x00\x00\x04\x4e\x61\x6d\x65\ \x07\x00\x00\x00\x08\x4c\x61\x75\x6e\x63\x68\x65\x72\x01\x03\x00\ \x00\x00\x08\x00\x50\x00\x66\x00\x61\x00\x64\x08\x00\x00\x00\x00\ \x06\x00\x00\x00\x04\x50\x61\x74\x68\x07\x00\x00\x00\x08\x4c\x61\ \x75\x6e\x63\x68\x65\x72\x01\x03\x00\x00\x00\xea\x00\x55\x00\x6d\ \x00\x20\x00\x68\x00\x69\x00\x65\x00\x72\x00\x20\x00\x7a\x00\x75\ \x00\x20\x00\x73\x00\x63\x00\x68\x00\x72\x00\x65\x00\x69\x00\x62\ \x00\x65\x00\x6e\x00\x20\x00\x64\x00\x6f\x00\x70\x00\x70\x00\x65\ \x00\x6c\x00\x6b\x00\x6c\x00\x69\x00\x63\x00\x6b\x00\x65\x00\x6e\ \x00\x20\x00\x53\x00\x69\x00\x65\x00\x20\x00\x61\x00\x75\x00\x66\ \x00\x20\x00\x64\x00\x69\x00\x65\x00\x73\x00\x65\x00\x73\x00\x20\ \x00\x46\x00\x65\x00\x6c\x00\x64\x00\x20\x00\x28\x00\x73\x00\x74\ \x00\x61\x00\x72\x00\x74\x00\x65\x00\x74\x00\x20\x00\x6e\x00\x73\ \x00\x6d\x00\x2d\x00\x64\x00\x61\x00\x74\x00\x61\x00\x29\x00\x0a\ \x00\x46\x00\xfc\x00\x72\x00\x20\x00\x4e\x00\x6f\x00\x74\x00\x69\ \x00\x7a\x00\x65\x00\x6e\x00\x2c\x00\x20\x00\x54\x00\x4f\x00\x44\ \x00\x4f\x00\x2c\x00\x20\x00\x52\x00\x65\x00\x66\x00\x65\x00\x72\ \x00\x65\x00\x6e\x00\x7a\x00\x65\x00\x6e\x00\x2c\x00\x20\x00\x51\ \x00\x75\x00\x65\x00\x6c\x00\x6c\x00\x65\x00\x6e\x00\x20\x00\x65\ \x00\x74\x00\x63\x00\x2e\x08\x00\x00\x00\x00\x06\x00\x00\x00\x63\ \x44\x6f\x75\x62\x6c\x65\x20\x63\x6c\x69\x63\x6b\x20\x74\x6f\x20\ \x61\x64\x64\x20\x74\x68\x65\x20\x63\x6c\x69\x65\x6e\x74\x20\x6e\ \x73\x6d\x2d\x64\x61\x74\x61\x20\x74\x6f\x20\x77\x72\x69\x74\x65\ \x20\x68\x65\x72\x65\x2e\x0a\x55\x73\x65\x20\x69\x74\x20\x66\x6f\ \x72\x20\x6e\x6f\x74\x65\x73\x2c\x20\x54\x4f\x44\x4f\x2c\x20\x72\ \x65\x66\x65\x72\x65\x6e\x63\x65\x73\x20\x65\x74\x63\xc3\xa2\xc2\ \x80\xc2\xa6\x07\x00\x00\x00\x18\x4c\x6f\x61\x64\x65\x64\x53\x65\ \x73\x73\x69\x6f\x6e\x44\x65\x73\x63\x72\x69\x70\x74\x69\x6f\x6e\ \x01\x03\x00\x00\x00\x08\x00\x20\x00\x6d\x00\x69\x00\x6e\x08\x00\ \x00\x00\x00\x06\x00\x00\x00\x04\x20\x6d\x69\x6e\x07\x00\x00\x00\ \x0a\x4d\x61\x69\x6e\x57\x69\x6e\x64\x6f\x77\x01\x03\x00\x00\x00\ \x22\x00\x20\x00\x74\x00\x69\x00\x6d\x00\x65\x00\x2d\x00\x70\x00\ \x6c\x00\x61\x00\x63\x00\x65\x00\x68\x00\x6f\x00\x6c\x00\x64\x00\ \x65\x00\x72\x08\x00\x00\x00\x00\x06\x00\x00\x00\x11\x20\x74\x69\ \x6d\x65\x2d\x70\x6c\x61\x63\x65\x68\x6f\x6c\x64\x65\x72\x07\x00\ \x00\x00\x0a\x4d\x61\x69\x6e\x57\x69\x6e\x64\x6f\x77\x01\x03\xff\ \xff\xff\xff\x08\x00\x00\x00\x00\x06\x00\x00\x00\x16\x2f\x68\x6f\ \x6d\x65\x2f\x75\x73\x72\x2f\x4e\x53\x4d\x20\x53\x65\x73\x73\x69\ \x6f\x6e\x73\x07\x00\x00\x00\x0a\x4d\x61\x69\x6e\x57\x69\x6e\x64\ \x6f\x77\x01\x03\xff\xff\xff\xff\x08\x00\x00\x00\x00\x06\x00\x00\ \x00\x01\x41\x07\x00\x00\x00\x0a\x4d\x61\x69\x6e\x57\x69\x6e\x64\ \x6f\x77\x01\x03\x00\x00\x00\x08\x00\xdc\x00\x62\x00\x65\x00\x72\ \x08\x00\x00\x00\x00\x06\x00\x00\x00\x05\x41\x62\x6f\x75\x74\x07\ \x00\x00\x00\x0a\x4d\x61\x69\x6e\x57\x69\x6e\x64\x6f\x77\x01\x03\ \x00\x00\x00\x38\x00\x50\x00\x72\x00\x6f\x00\x67\x00\x72\x00\x61\ \x00\x6d\x00\x6d\x00\x20\x00\x68\x00\x69\x00\x6e\x00\x7a\x00\x75\ \x00\x66\x00\xfc\x00\x67\x00\x65\x00\x6e\x00\x20\x00\x28\x00\x50\ \x00\x72\x00\x6f\x00\x6d\x00\x70\x00\x74\x00\x29\x08\x00\x00\x00\ \x00\x06\x00\x00\x00\x13\x41\x64\x64\x20\x43\x6c\x69\x65\x6e\x74\ \x20\x28\x50\x72\x6f\x6d\x70\x74\x29\x07\x00\x00\x00\x0a\x4d\x61\ \x69\x6e\x57\x69\x6e\x64\x6f\x77\x01\x03\x00\x00\x00\x10\x00\x41\ \x00\x67\x00\x6f\x00\x72\x00\x64\x00\x65\x00\x6a\x00\x6f\x08\x00\ \x00\x00\x00\x06\x00\x00\x00\x08\x41\x67\x6f\x72\x64\x65\x6a\x6f\ \x07\x00\x00\x00\x0a\x4d\x61\x69\x6e\x57\x69\x6e\x64\x6f\x77\x01\ \x03\xff\xff\xff\xff\x08\x00\x00\x00\x00\x06\x00\x00\x00\x05\x41\ \x6c\x74\x2b\x4f\x07\x00\x00\x00\x0a\x4d\x61\x69\x6e\x57\x69\x6e\ \x64\x6f\x77\x01\x03\xff\xff\xff\xff\x08\x00\x00\x00\x00\x06\x00\ \x00\x00\x05\x41\x6c\x74\x2b\x52\x07\x00\x00\x00\x0a\x4d\x61\x69\ \x6e\x57\x69\x6e\x64\x6f\x77\x01\x03\xff\xff\xff\xff\x08\x00\x00\ \x00\x00\x06\x00\x00\x00\x05\x41\x6c\x74\x2b\x53\x07\x00\x00\x00\ \x0a\x4d\x61\x69\x6e\x57\x69\x6e\x64\x6f\x77\x01\x03\xff\xff\xff\ \xff\x08\x00\x00\x00\x00\x06\x00\x00\x00\x05\x41\x6c\x74\x2b\x54\ \x07\x00\x00\x00\x0a\x4d\x61\x69\x6e\x57\x69\x6e\x64\x6f\x77\x01\ \x03\xff\xff\xff\xff\x08\x00\x00\x00\x00\x06\x00\x00\x00\x05\x41\ \x6c\x74\x2b\x58\x07\x00\x00\x00\x0a\x4d\x61\x69\x6e\x57\x69\x6e\ \x64\x6f\x77\x01\x03\xff\xff\xff\xff\x08\x00\x00\x00\x00\x06\x00\ \x00\x00\x0c\x43\x6c\x69\x65\x6e\x74\x4e\x61\x6d\x65\x49\x64\x07\ \x00\x00\x00\x0a\x4d\x61\x69\x6e\x57\x69\x6e\x64\x6f\x77\x01\x03\ \x00\x00\x00\x66\x00\x53\x00\x63\x00\x68\x00\x6c\x00\x69\x00\x65\ \x00\xdf\x00\x65\x00\x6e\x00\x20\x00\x6f\x00\x68\x00\x6e\x00\x65\ \x00\x20\x00\x7a\x00\x75\x00\x20\x00\x53\x00\x70\x00\x65\x00\x69\ \x00\x63\x00\x68\x00\x65\x00\x72\x00\x6e\x00\x20\x00\x28\x00\x26\ \x00\x71\x00\x75\x00\x6f\x00\x74\x00\x3b\x00\x41\x00\x62\x00\x62\ \x00\x72\x00\x65\x00\x63\x00\x68\x00\x65\x00\x6e\x00\x26\x00\x71\ \x00\x75\x00\x6f\x00\x74\x00\x3b\x00\x29\x08\x00\x00\x00\x00\x06\ \x00\x00\x00\x1c\x43\x6c\x6f\x73\x65\x20\x77\x69\x74\x68\x6f\x75\ \x74\x20\x53\x61\x76\x65\x20\x28\x22\x41\x62\x6f\x72\x74\x22\x29\ \x07\x00\x00\x00\x0a\x4d\x61\x69\x6e\x57\x69\x6e\x64\x6f\x77\x01\ \x03\x00\x00\x00\x12\x00\x53\x00\x74\x00\x65\x00\x75\x00\x65\x00\ \x72\x00\x75\x00\x6e\x00\x67\x08\x00\x00\x00\x00\x06\x00\x00\x00\ \x07\x43\x6f\x6e\x74\x72\x6f\x6c\x07\x00\x00\x00\x0a\x4d\x61\x69\ \x6e\x57\x69\x6e\x64\x6f\x77\x01\x03\x00\x00\x00\x10\x00\x4b\x00\ \x6f\x00\x70\x00\x69\x00\x65\x00\x72\x00\x65\x00\x6e\x08\x00\x00\ \x00\x00\x06\x00\x00\x00\x0d\x43\x6f\x70\x79\x20\x53\x65\x6c\x65\ \x63\x74\x65\x64\x07\x00\x00\x00\x0a\x4d\x61\x69\x6e\x57\x69\x6e\ \x64\x6f\x77\x01\x03\xff\xff\xff\xff\x08\x00\x00\x00\x00\x06\x00\ \x00\x00\x06\x43\x74\x72\x6c\x2b\x51\x07\x00\x00\x00\x0a\x4d\x61\ \x69\x6e\x57\x69\x6e\x64\x6f\x77\x01\x03\xff\xff\xff\xff\x08\x00\ \x00\x00\x00\x06\x00\x00\x00\x06\x43\x74\x72\x6c\x2b\x53\x07\x00\ \x00\x00\x0a\x4d\x61\x69\x6e\x57\x69\x6e\x64\x6f\x77\x01\x03\xff\ \xff\xff\xff\x08\x00\x00\x00\x00\x06\x00\x00\x00\x0c\x43\x74\x72\ \x6c\x2b\x53\x68\x69\x66\x74\x2b\x51\x07\x00\x00\x00\x0a\x4d\x61\ \x69\x6e\x57\x69\x6e\x64\x6f\x77\x01\x03\xff\xff\xff\xff\x08\x00\ \x00\x00\x00\x06\x00\x00\x00\x0c\x43\x74\x72\x6c\x2b\x53\x68\x69\ \x66\x74\x2b\x53\x07\x00\x00\x00\x0a\x4d\x61\x69\x6e\x57\x69\x6e\ \x64\x6f\x77\x01\x03\xff\xff\xff\xff\x08\x00\x00\x00\x00\x06\x00\ \x00\x00\x0c\x43\x74\x72\x6c\x2b\x53\x68\x69\x66\x74\x2b\x57\x07\ \x00\x00\x00\x0a\x4d\x61\x69\x6e\x57\x69\x6e\x64\x6f\x77\x01\x03\ \xff\xff\xff\xff\x08\x00\x00\x00\x00\x06\x00\x00\x00\x06\x43\x74\ \x72\x6c\x2b\x57\x07\x00\x00\x00\x0a\x4d\x61\x69\x6e\x57\x69\x6e\ \x64\x6f\x77\x01\x03\x00\x00\x00\x0e\x00\x4c\x00\xf6\x00\x73\x00\ \x63\x00\x68\x00\x65\x00\x6e\x08\x00\x00\x00\x00\x06\x00\x00\x00\ \x0f\x44\x65\x6c\x65\x74\x65\x20\x53\x65\x6c\x65\x63\x74\x65\x64\ \x07\x00\x00\x00\x0a\x4d\x61\x69\x6e\x57\x69\x6e\x64\x6f\x77\x01\ \x03\x00\x00\x00\x44\x00\x44\x00\x6f\x00\x70\x00\x70\x00\x65\x00\ \x6c\x00\x6b\x00\x6c\x00\x69\x00\x63\x00\x6b\x00\x20\x00\x75\x00\ \x6d\x00\x20\x00\x50\x00\x72\x00\x6f\x00\x67\x00\x72\x00\x61\x00\ \x6d\x00\x6d\x00\x20\x00\x7a\x00\x75\x00\x20\x00\x73\x00\x74\x00\ \x61\x00\x72\x00\x74\x00\x65\x00\x6e\x08\x00\x00\x00\x00\x06\x00\ \x00\x00\x1c\x44\x6f\x75\x62\x6c\x65\x2d\x63\x6c\x69\x63\x6b\x20\ \x74\x6f\x20\x6c\x6f\x61\x64\x20\x70\x72\x6f\x67\x72\x61\x6d\x07\ \x00\x00\x00\x0a\x4d\x61\x69\x6e\x57\x69\x6e\x64\x6f\x77\x01\x03\ \xff\xff\xff\xff\x08\x00\x00\x00\x00\x06\x00\x00\x00\x02\x46\x32\ \x07\x00\x00\x00\x0a\x4d\x61\x69\x6e\x57\x69\x6e\x64\x6f\x77\x01\ \x03\x00\x00\x00\x1a\x00\x56\x00\x6f\x00\x6c\x00\x6c\x00\x65\x00\ \x20\x00\x41\x00\x6e\x00\x73\x00\x69\x00\x63\x00\x68\x00\x74\x08\ \x00\x00\x00\x00\x06\x00\x00\x00\x09\x46\x75\x6c\x6c\x20\x56\x69\ \x65\x77\x07\x00\x00\x00\x0a\x4d\x61\x69\x6e\x57\x69\x6e\x64\x6f\ \x77\x01\x03\x00\x00\x00\x3c\x00\x47\x00\x6c\x00\x6f\x00\x62\x00\ \x61\x00\x6c\x00\x65\x00\x72\x00\x20\x00\x5a\x00\x75\x00\x67\x00\ \x72\x00\x69\x00\x66\x00\x66\x00\x20\x00\x61\x00\x75\x00\x66\x00\ \x73\x00\x20\x00\x50\x00\x6c\x00\x61\x00\x79\x00\x62\x00\x61\x00\ \x63\x00\x6b\x08\x00\x00\x00\x00\x06\x00\x00\x00\x18\x47\x6c\x6f\ \x62\x61\x6c\x20\x50\x6c\x61\x79\x62\x61\x63\x6b\x20\x43\x6f\x6e\ \x74\x72\x6f\x6c\x73\x07\x00\x00\x00\x0a\x4d\x61\x69\x6e\x57\x69\ \x6e\x64\x6f\x77\x01\x03\x00\x00\x00\x2c\x00\x56\x00\x65\x00\x72\ \x00\x73\x00\x74\x00\x65\x00\x63\x00\x6b\x00\x65\x00\x20\x00\x61\ \x00\x6c\x00\x6c\x00\x65\x00\x20\x00\x43\x00\x6c\x00\x69\x00\x65\ \x00\x6e\x00\x74\x00\x73\x08\x00\x00\x00\x00\x06\x00\x00\x00\x10\ \x48\x69\x64\x65\x20\x41\x6c\x6c\x20\x43\x6c\x69\x65\x6e\x74\x73\ \x07\x00\x00\x00\x0a\x4d\x61\x69\x6e\x57\x69\x6e\x64\x6f\x77\x01\ \x03\x00\x00\x00\x26\x00\x4d\x00\x69\x00\x6e\x00\x69\x00\x6d\x00\ \x69\x00\x65\x00\x72\x00\x65\x00\x6e\x00\x20\x00\x7a\x00\x75\x00\ \x6d\x00\x20\x00\x54\x00\x72\x00\x61\x00\x79\x08\x00\x00\x00\x00\ \x06\x00\x00\x00\x13\x48\x69\x64\x65\x20\x69\x6e\x20\x53\x79\x73\ \x74\x65\x6d\x20\x54\x72\x61\x79\x07\x00\x00\x00\x0a\x4d\x61\x69\ \x6e\x57\x69\x6e\x64\x6f\x77\x01\x03\x00\x00\x00\x70\x00\x49\x00\ \x63\x00\x68\x00\x20\x00\x76\x00\x65\x00\x72\x00\x73\x00\x74\x00\ \x65\x00\x68\x00\x65\x00\x2c\x00\x20\x00\x64\x00\x61\x00\x73\x00\ \x73\x00\x20\x00\x69\x00\x63\x00\x68\x00\x20\x00\x64\x00\x69\x00\ \x65\x00\x73\x00\x65\x00\x73\x00\x20\x00\x50\x00\x72\x00\x6f\x00\ \x62\x00\x6c\x00\x65\x00\x6d\x00\x20\x00\x73\x00\x65\x00\x6c\x00\ \x62\x00\x73\x00\x74\x00\x20\x00\x6c\x00\xf6\x00\x73\x00\x65\x00\ \x6e\x00\x20\x00\x6d\x00\x75\x00\x73\x00\x73\x00\x21\x08\x00\x00\ \x00\x00\x06\x00\x00\x00\x40\x49\x20\x75\x6e\x64\x65\x72\x73\x74\ \x61\x6e\x64\x20\x74\x68\x61\x74\x20\x49\x20\x77\x69\x6c\x6c\x20\ \x6e\x65\x65\x64\x20\x74\x6f\x20\x72\x65\x73\x6f\x6c\x76\x65\x20\ \x74\x68\x69\x73\x20\x70\x72\x6f\x62\x6c\x65\x6d\x20\x6f\x6e\x20\ \x6d\x79\x20\x6f\x77\x6e\x21\x07\x00\x00\x00\x0a\x4d\x61\x69\x6e\ \x57\x69\x6e\x64\x6f\x77\x01\x03\x00\x00\x00\x2e\x00\x4d\x00\x6f\ \x00\x6d\x00\x65\x00\x6e\x00\x74\x00\x61\x00\x6e\x00\x20\x00\x69\ \x00\x6e\x00\x20\x00\x64\x00\x65\x00\x72\x00\x20\x00\x53\x00\x65\ \x00\x73\x00\x73\x00\x69\x00\x6f\x00\x6e\x08\x00\x00\x00\x00\x06\ \x00\x00\x00\x12\x49\x6e\x20\x63\x75\x72\x72\x65\x6e\x74\x20\x73\ \x65\x73\x73\x69\x6f\x6e\x07\x00\x00\x00\x0a\x4d\x61\x69\x6e\x57\ \x69\x6e\x64\x6f\x77\x01\x03\x00\x00\x00\x16\x00\x49\x00\x6e\x00\ \x66\x00\x6f\x00\x72\x00\x6d\x00\x61\x00\x74\x00\x69\x00\x6f\x00\ \x6e\x08\x00\x00\x00\x00\x06\x00\x00\x00\x0b\x49\x6e\x66\x6f\x72\ \x6d\x61\x74\x69\x6f\x6e\x07\x00\x00\x00\x0a\x4d\x61\x69\x6e\x57\ \x69\x6e\x64\x6f\x77\x01\x03\x00\x00\x00\x08\x00\x4a\x00\x41\x00\ \x43\x00\x4b\x08\x00\x00\x00\x00\x06\x00\x00\x00\x04\x4a\x41\x43\ \x4b\x07\x00\x00\x00\x0a\x4d\x61\x69\x6e\x57\x69\x6e\x64\x6f\x77\ \x01\x03\xff\xff\xff\xff\x08\x00\x00\x00\x00\x06\x00\x00\x00\x0c\ \x4c\x61\x73\x74\x20\x55\x70\x64\x61\x74\x65\x64\x07\x00\x00\x00\ \x0a\x4d\x61\x69\x6e\x57\x69\x6e\x64\x6f\x77\x01\x03\x00\x00\x00\ \x20\x00\x4c\x00\x61\x00\x64\x00\x65\x00\x20\x00\x41\x00\x75\x00\ \x73\x00\x67\x00\x65\x00\x77\x00\xe4\x00\x68\x00\x6c\x00\x74\x00\ \x65\x08\x00\x00\x00\x00\x06\x00\x00\x00\x0d\x4c\x6f\x61\x64\x20\ \x53\x65\x6c\x65\x63\x74\x65\x64\x07\x00\x00\x00\x0a\x4d\x61\x69\ \x6e\x57\x69\x6e\x64\x6f\x77\x01\x03\x00\x00\x00\x10\x00\x48\x00\ \x61\x00\x6e\x00\x64\x00\x62\x00\x75\x00\x63\x00\x68\x08\x00\x00\ \x00\x00\x06\x00\x00\x00\x06\x4d\x61\x6e\x75\x61\x6c\x07\x00\x00\ \x00\x0a\x4d\x61\x69\x6e\x57\x69\x6e\x64\x6f\x77\x01\x03\xff\xff\ \xff\xff\x08\x00\x00\x00\x00\x06\x00\x00\x00\x0f\x4e\x53\x4d\x20\ \x53\x65\x72\x76\x65\x72\x20\x4d\x6f\x64\x65\x07\x00\x00\x00\x0a\ \x4d\x61\x69\x6e\x57\x69\x6e\x64\x6f\x77\x01\x03\xff\xff\xff\xff\ \x08\x00\x00\x00\x00\x06\x00\x00\x00\x07\x4e\x53\x4d\x20\x55\x72\ \x6c\x07\x00\x00\x00\x0a\x4d\x61\x69\x6e\x57\x69\x6e\x64\x6f\x77\ \x01\x03\x00\x00\x00\x06\x00\x4e\x00\x65\x00\x75\x08\x00\x00\x00\ \x00\x06\x00\x00\x00\x03\x4e\x65\x77\x07\x00\x00\x00\x0a\x4d\x61\ \x69\x6e\x57\x69\x6e\x64\x6f\x77\x01\x03\x00\x00\x00\x24\x00\x4e\ \x00\x65\x00\x77\x00\x73\x00\x20\x00\x75\x00\x6e\x00\x64\x00\x20\ \x00\x43\x00\x68\x00\x61\x00\x6e\x00\x67\x00\x65\x00\x6c\x00\x6f\ \x00\x67\x08\x00\x00\x00\x00\x06\x00\x00\x00\x12\x4e\x65\x77\x73\ \x20\x61\x6e\x64\x20\x43\x68\x61\x6e\x67\x65\x6c\x6f\x67\x07\x00\ \x00\x00\x0a\x4d\x61\x69\x6e\x57\x69\x6e\x64\x6f\x77\x01\x03\x00\ \x00\x00\x12\x00\x50\x00\x6c\x00\x61\x00\x79\x00\x50\x00\x61\x00\ \x75\x00\x73\x00\x65\x08\x00\x00\x00\x00\x06\x00\x00\x00\x09\x50\ \x6c\x61\x79\x50\x61\x75\x73\x65\x07\x00\x00\x00\x0a\x4d\x61\x69\ \x6e\x57\x69\x6e\x64\x6f\x77\x01\x03\x00\x00\x00\x10\x00\x41\x00\ \x72\x00\x62\x00\x65\x00\x69\x00\x74\x00\x65\x00\x74\x08\x00\x00\ \x00\x00\x06\x00\x00\x00\x0a\x50\x72\x6f\x63\x65\x73\x73\x69\x6e\ \x67\x07\x00\x00\x00\x0a\x4d\x61\x69\x6e\x57\x69\x6e\x64\x6f\x77\ \x01\x03\xff\xff\xff\xff\x08\x00\x00\x00\x00\x06\x00\x00\x00\x10\ \x50\x72\x6f\x67\x72\x61\x6d\x20\x44\x61\x74\x61\x62\x61\x73\x65\ \x07\x00\x00\x00\x0a\x4d\x61\x69\x6e\x57\x69\x6e\x64\x6f\x77\x01\ \x03\x00\x00\x00\x20\x00\x53\x00\x63\x00\x68\x00\x6e\x00\x65\x00\ \x6c\x00\x6c\x00\x73\x00\x74\x00\x61\x00\x72\x00\x74\x00\x20\x00\ \x4e\x00\x65\x00\x75\x08\x00\x00\x00\x00\x06\x00\x00\x00\x09\x51\ \x75\x69\x63\x6b\x20\x4e\x65\x77\x07\x00\x00\x00\x0a\x4d\x61\x69\ \x6e\x57\x69\x6e\x64\x6f\x77\x01\x03\x00\x00\x00\x0e\x00\x42\x00\ \x65\x00\x65\x00\x6e\x00\x64\x00\x65\x00\x6e\x08\x00\x00\x00\x00\ \x06\x00\x00\x00\x04\x51\x75\x69\x74\x07\x00\x00\x00\x0a\x4d\x61\ \x69\x6e\x57\x69\x6e\x64\x6f\x77\x01\x03\x00\x00\x00\x38\x00\x50\ \x00\x72\x00\x6f\x00\x67\x00\x72\x00\x61\x00\x6d\x00\x6d\x00\x64\ \x00\x61\x00\x74\x00\x65\x00\x6e\x00\x62\x00\x61\x00\x6e\x00\x6b\ \x00\x20\x00\x67\x00\x65\x00\x6e\x00\x65\x00\x72\x00\x69\x00\x65\ \x00\x72\x00\x65\x00\x6e\x08\x00\x00\x00\x00\x06\x00\x00\x00\x18\ \x52\x65\x62\x75\x69\x6c\x64\x20\x50\x72\x6f\x67\x72\x61\x6d\x20\ \x44\x61\x74\x61\x62\x61\x73\x65\x07\x00\x00\x00\x0a\x4d\x61\x69\ \x6e\x57\x69\x6e\x64\x6f\x77\x01\x03\x00\x00\x00\x12\x00\x45\x00\ \x6e\x00\x74\x00\x66\x00\x65\x00\x72\x00\x6e\x00\x65\x00\x6e\x08\ \x00\x00\x00\x00\x06\x00\x00\x00\x06\x52\x65\x6d\x6f\x76\x65\x07\ \x00\x00\x00\x0a\x4d\x61\x69\x6e\x57\x69\x6e\x64\x6f\x77\x01\x03\ \x00\x00\x00\x14\x00\x55\x00\x6d\x00\x62\x00\x65\x00\x6e\x00\x65\ \x00\x6e\x00\x6e\x00\x65\x00\x6e\x08\x00\x00\x00\x00\x06\x00\x00\ \x00\x06\x52\x65\x6e\x61\x6d\x65\x07\x00\x00\x00\x0a\x4d\x61\x69\ \x6e\x57\x69\x6e\x64\x6f\x77\x01\x03\x00\x00\x00\x18\x00\x4e\x00\ \x61\x00\x6d\x00\x65\x00\x6e\x00\x20\x00\xe4\x00\x6e\x00\x64\x00\ \x65\x00\x72\x00\x6e\x08\x00\x00\x00\x00\x06\x00\x00\x00\x0f\x52\ \x65\x6e\x61\x6d\x65\x20\x53\x65\x6c\x65\x63\x74\x65\x64\x07\x00\ \x00\x00\x0a\x4d\x61\x69\x6e\x57\x69\x6e\x64\x6f\x77\x01\x03\x00\ \x00\x00\x14\x00\x46\x00\x6f\x00\x72\x00\x74\x00\x66\x00\xfc\x00\ \x68\x00\x72\x00\x65\x00\x6e\x08\x00\x00\x00\x00\x06\x00\x00\x00\ \x06\x52\x65\x73\x75\x6d\x65\x07\x00\x00\x00\x0a\x4d\x61\x69\x6e\ \x57\x69\x6e\x64\x6f\x77\x01\x03\x00\x00\x00\x0c\x00\x52\x00\x65\ \x00\x77\x00\x69\x00\x6e\x00\x64\x08\x00\x00\x00\x00\x06\x00\x00\ \x00\x06\x52\x65\x77\x69\x6e\x64\x07\x00\x00\x00\x0a\x4d\x61\x69\ \x6e\x57\x69\x6e\x64\x6f\x77\x01\x03\x00\x00\x00\x12\x00\x53\x00\ \x70\x00\x65\x00\x69\x00\x63\x00\x68\x00\x65\x00\x72\x00\x6e\x08\ \x00\x00\x00\x00\x06\x00\x00\x00\x04\x53\x61\x76\x65\x07\x00\x00\ \x00\x0a\x4d\x61\x69\x6e\x57\x69\x6e\x64\x6f\x77\x01\x03\x00\x00\ \x00\x54\x00\x53\x00\x70\x00\x65\x00\x69\x00\x63\x00\x68\x00\x65\ \x00\x72\x00\x6e\x00\x20\x00\x75\x00\x6e\x00\x64\x00\x20\x00\x6d\ \x00\x69\x00\x74\x00\x20\x00\x61\x00\x6e\x00\x64\x00\x65\x00\x72\ \x00\x65\x00\x6d\x00\x20\x00\x4e\x00\x61\x00\x6d\x00\x65\x00\x6e\ \x00\x20\x00\x6e\x00\x65\x00\x75\x00\x20\x00\xf6\x00\x66\x00\x66\ \x00\x6e\x00\x65\x00\x6e\x08\x00\x00\x00\x00\x06\x00\x00\x00\x23\ \x53\x61\x76\x65\x20\x61\x6e\x64\x20\x43\x6c\x6f\x6e\x65\x20\x75\ \x6e\x64\x65\x72\x20\x64\x69\x66\x66\x65\x72\x65\x6e\x74\x20\x6e\ \x61\x6d\x65\x07\x00\x00\x00\x0a\x4d\x61\x69\x6e\x57\x69\x6e\x64\ \x6f\x77\x01\x03\x00\x00\x00\x2e\x00\x53\x00\x70\x00\x65\x00\x69\ \x00\x63\x00\x68\x00\x65\x00\x72\x00\x6e\x00\x20\x00\x75\x00\x6e\ \x00\x64\x00\x20\x00\x53\x00\x63\x00\x68\x00\x6c\x00\x69\x00\x65\ \x00\xdf\x00\x65\x00\x6e\x08\x00\x00\x00\x00\x06\x00\x00\x00\x0e\ \x53\x61\x76\x65\x20\x61\x6e\x64\x20\x43\x6c\x6f\x73\x65\x07\x00\ \x00\x00\x0a\x4d\x61\x69\x6e\x57\x69\x6e\x64\x6f\x77\x01\x03\x00\ \x00\x00\x22\x00\x53\x00\x65\x00\x70\x00\x65\x00\x72\x00\x61\x00\ \x74\x00\x20\x00\x73\x00\x70\x00\x65\x00\x69\x00\x63\x00\x68\x00\ \x65\x00\x72\x00\x6e\x08\x00\x00\x00\x00\x06\x00\x00\x00\x0f\x53\ \x61\x76\x65\x20\x73\x65\x70\x61\x72\x61\x74\x65\x6c\x79\x07\x00\ \x00\x00\x0a\x4d\x61\x69\x6e\x57\x69\x6e\x64\x6f\x77\x01\x03\xff\ \xff\xff\xff\x08\x00\x00\x00\x00\x06\x00\x00\x00\x2b\x53\x65\x6c\ \x66\x2d\x73\x74\x61\x72\x74\x65\x64\x2c\x20\x63\x6f\x6e\x6e\x65\ \x63\x74\x65\x64\x20\x74\x6f\x2c\x20\x65\x6e\x76\x69\x72\x6f\x6e\ \x6d\x65\x6e\x74\x20\x76\x61\x72\x07\x00\x00\x00\x0a\x4d\x61\x69\ \x6e\x57\x69\x6e\x64\x6f\x77\x01\x03\x00\x00\x00\x1e\x00\x53\x00\ \x65\x00\x73\x00\x73\x00\x69\x00\x6f\x00\x6e\x00\x20\x00\x4e\x00\ \x6f\x00\x74\x00\x69\x00\x7a\x00\x65\x00\x6e\x08\x00\x00\x00\x00\ \x06\x00\x00\x00\x0d\x53\x65\x73\x73\x69\x6f\x6e\x20\x4e\x6f\x74\ \x65\x73\x07\x00\x00\x00\x0a\x4d\x61\x69\x6e\x57\x69\x6e\x64\x6f\ \x77\x01\x03\xff\xff\xff\xff\x08\x00\x00\x00\x00\x06\x00\x00\x00\ \x0c\x53\x65\x73\x73\x69\x6f\x6e\x20\x52\x6f\x6f\x74\x07\x00\x00\ \x00\x0a\x4d\x61\x69\x6e\x57\x69\x6e\x64\x6f\x77\x01\x03\xff\xff\ \xff\xff\x08\x00\x00\x00\x00\x06\x00\x00\x00\x0b\x53\x65\x73\x73\ \x69\x6f\x6e\x4e\x61\x6d\x65\x07\x00\x00\x00\x0a\x4d\x61\x69\x6e\ \x57\x69\x6e\x64\x6f\x77\x01\x03\x00\x00\x00\x1a\x00\x45\x00\x69\ \x00\x6e\x00\x73\x00\x74\x00\x65\x00\x6c\x00\x6c\x00\x75\x00\x6e\ \x00\x67\x00\x65\x00\x6e\x08\x00\x00\x00\x00\x06\x00\x00\x00\x08\ \x53\x65\x74\x74\x69\x6e\x67\x73\x07\x00\x00\x00\x0a\x4d\x61\x69\ \x6e\x57\x69\x6e\x64\x6f\x77\x01\x03\x00\x00\x00\x24\x00\x5a\x00\ \x65\x00\x69\x00\x67\x00\x65\x00\x20\x00\x61\x00\x6c\x00\x6c\x00\ \x65\x00\x20\x00\x43\x00\x6c\x00\x69\x00\x65\x00\x6e\x00\x74\x00\ \x73\x08\x00\x00\x00\x00\x06\x00\x00\x00\x10\x53\x68\x6f\x77\x20\ \x41\x6c\x6c\x20\x43\x6c\x69\x65\x6e\x74\x73\x07\x00\x00\x00\x0a\ \x4d\x61\x69\x6e\x57\x69\x6e\x64\x6f\x77\x01\x03\x00\x00\x00\x3e\ \x00\x53\x00\x65\x00\x73\x00\x73\x00\x69\x00\x6f\x00\x6e\x00\x66\ \x00\x65\x00\x6e\x00\x73\x00\x74\x00\x65\x00\x72\x00\x20\x00\x61\ \x00\x6e\x00\x64\x00\x65\x00\x72\x00\x73\x00\x20\x00\x61\x00\x75\ \x00\x66\x00\x74\x00\x65\x00\x69\x00\x6c\x00\x65\x00\x6e\x08\x00\ \x00\x00\x00\x06\x00\x00\x00\x20\x53\x70\x6c\x69\x74\x20\x53\x65\ \x73\x73\x69\x6f\x6e\x20\x56\x69\x65\x77\x20\x74\x68\x65\x20\x6f\ \x74\x68\x65\x72\x20\x77\x61\x79\x07\x00\x00\x00\x0a\x4d\x61\x69\ \x6e\x57\x69\x6e\x64\x6f\x77\x01\x03\x00\x00\x00\x08\x00\x53\x00\ \x74\x00\x6f\x00\x70\x08\x00\x00\x00\x00\x06\x00\x00\x00\x04\x53\ \x74\x6f\x70\x07\x00\x00\x00\x0a\x4d\x61\x69\x6e\x57\x69\x6e\x64\ \x6f\x77\x01\x03\x00\x00\x00\x2e\x00\x53\x00\x69\x00\x63\x00\x68\ \x00\x74\x00\x62\x00\x61\x00\x72\x00\x6b\x00\x65\x00\x69\x00\x74\ \x00\x20\x00\x75\x00\x6d\x00\x73\x00\x63\x00\x68\x00\x61\x00\x6c\ \x00\x74\x00\x65\x00\x6e\x08\x00\x00\x00\x00\x06\x00\x00\x00\x0e\ \x54\x6f\x67\x67\x6c\x65\x20\x56\x69\x73\x69\x62\x6c\x65\x07\x00\ \x00\x00\x0a\x4d\x61\x69\x6e\x57\x69\x6e\x64\x6f\x77\x01\x03\x00\ \x00\x00\x16\x00\x42\x00\x61\x00\x75\x00\x6d\x00\x61\x00\x6e\x00\ \x73\x00\x69\x00\x63\x00\x68\x00\x74\x08\x00\x00\x00\x00\x06\x00\ \x00\x00\x09\x54\x72\x65\x65\x20\x56\x69\x65\x77\x07\x00\x00\x00\ \x0a\x4d\x61\x69\x6e\x57\x69\x6e\x64\x6f\x77\x01\x03\xff\xff\xff\ \xff\x08\x00\x00\x00\x00\x06\x00\x00\x00\x0f\x6f\x73\x63\x2e\x75\ \x70\x64\x20\x69\x70\x20\x70\x6f\x72\x74\x07\x00\x00\x00\x0a\x4d\ \x61\x69\x6e\x57\x69\x6e\x64\x6f\x77\x01\x03\xff\xff\xff\xff\x08\ \x00\x00\x00\x00\x06\x00\x00\x00\x13\x76\x65\x72\x73\x69\x6f\x6e\ \x20\x61\x6e\x64\x20\x72\x75\x6e\x6e\x69\x6e\x67\x07\x00\x00\x00\ \x0a\x4d\x61\x69\x6e\x57\x69\x6e\x64\x6f\x77\x01\x03\x00\x00\x01\ \x10\x00\x45\x00\x52\x00\x52\x00\x4f\x00\x52\x00\x21\x00\x20\x00\ \x4b\x00\x6f\x00\x70\x00\x69\x00\x65\x00\x72\x00\x74\x00\x65\x00\ \x20\x00\x44\x00\x61\x00\x74\x00\x65\x00\x6e\x00\x20\x00\x64\x00\ \x65\x00\x72\x00\x20\x00\x53\x00\x65\x00\x73\x00\x73\x00\x69\x00\ \x6f\x00\x6e\x00\x20\x00\x75\x00\x6e\x00\x74\x00\x65\x00\x72\x00\ \x73\x00\x63\x00\x68\x00\x65\x00\x69\x00\x64\x00\x65\x00\x6e\x00\ \x20\x00\x73\x00\x69\x00\x63\x00\x68\x00\x20\x00\x76\x00\x6f\x00\ \x6e\x00\x20\x00\x64\x00\x65\x00\x72\x00\x20\x00\x55\x00\x72\x00\ \x73\x00\x70\x00\x72\x00\xfc\x00\x6e\x00\x6c\x00\x69\x00\x6e\x00\ \x67\x00\x6c\x00\x69\x00\x63\x00\x68\x00\x65\x00\x6e\x00\x21\x00\ \x20\x00\x42\x00\x69\x00\x74\x00\x74\x00\x65\x00\x20\x00\xfc\x00\ \x62\x00\x65\x00\x72\x00\x70\x00\x72\x00\xfc\x00\x66\x00\x65\x00\ \x6e\x00\x20\x00\x53\x00\x69\x00\x65\x00\x20\x00\x64\x00\x69\x00\ \x65\x00\x20\x00\x49\x00\x6e\x00\x74\x00\x65\x00\x67\x00\x72\x00\ \x69\x00\x74\x00\xe4\x00\x74\x00\x20\x00\x64\x00\x65\x00\x72\x00\ \x20\x00\x6b\x00\x6f\x00\x70\x00\x69\x00\x65\x00\x72\x00\x74\x00\ \x65\x00\x6e\x00\x20\x00\x44\x00\x61\x00\x74\x00\x65\x00\x6e\x00\ \x21\x08\x00\x00\x00\x00\x06\x00\x00\x00\x53\x45\x52\x52\x4f\x52\ \x21\x20\x43\x6f\x70\x69\x65\x64\x20\x73\x65\x73\x73\x69\x6f\x6e\ \x20\x64\x61\x74\x61\x20\x69\x73\x20\x64\x69\x66\x66\x65\x72\x65\ \x6e\x74\x20\x66\x72\x6f\x6d\x20\x73\x6f\x75\x72\x63\x65\x20\x73\ \x65\x73\x73\x69\x6f\x6e\x2e\x20\x50\x6c\x65\x61\x73\x65\x20\x63\ \x68\x65\x63\x6b\x20\x79\x6f\x75\x20\x64\x61\x74\x61\x21\x07\x00\ \x00\x00\x11\x4e\x4f\x4f\x50\x45\x6e\x67\x69\x6e\x65\x53\x74\x72\ \x69\x6e\x67\x73\x01\x03\x00\x00\x00\x82\x00\xdc\x00\x62\x00\x65\ \x00\x72\x00\x70\x00\x72\x00\xfc\x00\x66\x00\x65\x00\x20\x00\x49\ \x00\x6e\x00\x74\x00\x65\x00\x67\x00\x72\x00\x69\x00\x74\x00\xe4\ \x00\x74\x00\x20\x00\x64\x00\x65\x00\x72\x00\x20\x00\x44\x00\x61\ \x00\x74\x00\x65\x00\x69\x00\x65\x00\x6e\x00\x2e\x00\x20\x00\x42\ \x00\x69\x00\x74\x00\x74\x00\x65\x00\x20\x00\x68\x00\x61\x00\x62\ \x00\x65\x00\x6e\x00\x20\x00\x53\x00\x69\x00\x65\x00\x20\x00\x65\ \x00\x74\x00\x77\x00\x61\x00\x73\x00\x20\x00\x47\x00\x65\x00\x64\ \x00\x75\x00\x6c\x00\x64\x00\x2e\x00\x2e\x00\x2e\x08\x00\x00\x00\ \x00\x06\x00\x00\x00\x32\x56\x65\x72\x79\x66\x79\x69\x6e\x67\x20\ \x66\x69\x6c\x65\x2d\x69\x6e\x74\x65\x67\x72\x69\x74\x79\x2e\x20\ \x54\x68\x69\x73\x20\x6d\x61\x79\x20\x74\x61\x6b\x65\x20\x61\x20\ \x77\x68\x69\x6c\x65\x2e\x2e\x2e\x07\x00\x00\x00\x11\x4e\x4f\x4f\ \x50\x45\x6e\x67\x69\x6e\x65\x53\x74\x72\x69\x6e\x67\x73\x01\x03\ \x00\x00\x00\x84\x00\x55\x00\x6d\x00\x62\x00\x65\x00\x6e\x00\x65\ \x00\x6e\x00\x6e\x00\x65\x00\x6e\x00\x20\x00\x75\x00\x6e\x00\x64\ \x00\x20\x00\x53\x00\x65\x00\x73\x00\x73\x00\x69\x00\x6f\x00\x6e\ \x00\x2d\x00\x4e\x00\x6f\x00\x74\x00\x69\x00\x74\x00\x7a\x00\x65\ \x00\x6e\x00\x20\x00\x61\x00\x6b\x00\x74\x00\x69\x00\x76\x00\x69\ \x00\x65\x00\x72\x00\x65\x00\x6e\x00\x20\x00\x28\x00\x6d\x00\x69\ \x00\x74\x00\x20\x00\x43\x00\x6c\x00\x69\x00\x65\x00\x6e\x00\x74\ \x00\x20\x00\x27\x00\x6e\x00\x73\x00\x6d\x00\x2d\x00\x64\x00\x61\ \x00\x74\x00\x61\x00\x27\x00\x29\x08\x00\x00\x00\x00\x06\x00\x00\ \x00\x3a\x43\x6c\x69\x65\x6e\x74\x20\x52\x65\x6e\x61\x6d\x69\x6e\ \x67\x20\x61\x6e\x64\x20\x53\x65\x73\x73\x69\x6f\x6e\x20\x4e\x6f\ \x74\x65\x73\x0a\x28\x61\x64\x64\x73\x20\x63\x6c\x69\x65\x6e\x74\ \x20\x27\x6e\x73\x6d\x2d\x64\x61\x74\x61\x27\x29\x07\x00\x00\x00\ \x0a\x4e\x65\x77\x53\x65\x73\x73\x69\x6f\x6e\x01\x03\xff\xff\xff\ \xff\x08\x00\x00\x00\x00\x06\x00\x00\x00\x06\x44\x69\x61\x6c\x6f\ \x67\x07\x00\x00\x00\x0a\x4e\x65\x77\x53\x65\x73\x73\x69\x6f\x6e\ \x01\x03\x00\x00\x00\x2a\x00\x4e\x00\x61\x00\x6d\x00\x65\x00\x20\ \x00\x66\x00\xfc\x00\x72\x00\x20\x00\x6e\x00\x65\x00\x75\x00\x65\ \x00\x20\x00\x53\x00\x65\x00\x73\x00\x73\x00\x69\x00\x6f\x00\x6e\ \x08\x00\x00\x00\x00\x06\x00\x00\x00\x10\x4e\x65\x77\x20\x53\x65\ \x73\x73\x69\x6f\x6e\x20\x4e\x61\x6d\x65\x07\x00\x00\x00\x0a\x4e\ \x65\x77\x53\x65\x73\x73\x69\x6f\x6e\x01\x03\x00\x00\x00\x68\x00\ \x53\x00\x70\x00\x65\x00\x69\x00\x63\x00\x68\x00\x65\x00\x72\x00\ \x65\x00\x20\x00\x4a\x00\x41\x00\x43\x00\x4b\x00\x2d\x00\x56\x00\ \x65\x00\x72\x00\x62\x00\x69\x00\x6e\x00\x64\x00\x75\x00\x6e\x00\ \x67\x00\x65\x00\x6e\x00\x20\x00\x28\x00\x6d\x00\x69\x00\x74\x00\ \x20\x00\x43\x00\x6c\x00\x69\x00\x65\x00\x6e\x00\x74\x00\x20\x00\ \x27\x00\x6a\x00\x61\x00\x63\x00\x6b\x00\x70\x00\x61\x00\x74\x00\ \x63\x00\x68\x00\x27\x00\x29\x08\x00\x00\x00\x00\x06\x00\x00\x00\ \x30\x53\x61\x76\x65\x20\x4a\x41\x43\x4b\x20\x43\x6f\x6e\x6e\x65\ \x63\x74\x69\x6f\x6e\x73\x0a\x28\x61\x64\x64\x73\x20\x63\x6c\x69\ \x65\x6e\x74\x73\x20\x27\x6a\x61\x63\x6b\x70\x61\x74\x63\x68\x27\ \x29\x07\x00\x00\x00\x0a\x4e\x65\x77\x53\x65\x73\x73\x69\x6f\x6e\ \x01\x03\x00\x00\x00\x14\x00\xc4\x00\x6e\x00\x64\x00\x65\x00\x72\ \x00\x75\x00\x6e\x00\x67\x00\x65\x00\x6e\x08\x00\x00\x00\x00\x06\ \x00\x00\x00\x07\x43\x68\x61\x6e\x67\x65\x73\x07\x00\x00\x00\x0b\ \x4f\x70\x65\x6e\x53\x65\x73\x73\x69\x6f\x6e\x01\x03\x00\x00\x00\ \x04\x00\x49\x00\x44\x08\x00\x00\x00\x00\x06\x00\x00\x00\x02\x49\ \x44\x07\x00\x00\x00\x0b\x4f\x70\x65\x6e\x53\x65\x73\x73\x69\x6f\ \x6e\x01\x03\x00\x00\x00\x0a\x00\x4c\x00\x61\x00\x62\x00\x65\x00\ \x6c\x08\x00\x00\x00\x00\x06\x00\x00\x00\x05\x4c\x61\x62\x65\x6c\ \x07\x00\x00\x00\x0b\x4f\x70\x65\x6e\x53\x65\x73\x73\x69\x6f\x6e\ \x01\x03\x00\x00\x00\x08\x00\x4e\x00\x61\x00\x6d\x00\x65\x08\x00\ \x00\x00\x00\x06\x00\x00\x00\x04\x4e\x61\x6d\x65\x07\x00\x00\x00\ \x0b\x4f\x70\x65\x6e\x53\x65\x73\x73\x69\x6f\x6e\x01\x03\x00\x00\ \x00\x0c\x00\x53\x00\x74\x00\x61\x00\x74\x00\x75\x00\x73\x08\x00\ \x00\x00\x00\x06\x00\x00\x00\x06\x53\x74\x61\x74\x75\x73\x07\x00\ \x00\x00\x0b\x4f\x70\x65\x6e\x53\x65\x73\x73\x69\x6f\x6e\x01\x03\ \x00\x00\x00\x18\x00\x53\x00\x69\x00\x63\x00\x68\x00\x74\x00\x62\ \x00\x61\x00\x72\x00\x6b\x00\x65\x00\x69\x00\x74\x08\x00\x00\x00\ \x00\x06\x00\x00\x00\x07\x56\x69\x73\x69\x62\x6c\x65\x07\x00\x00\ \x00\x0b\x4f\x70\x65\x6e\x53\x65\x73\x73\x69\x6f\x6e\x01\x03\x00\ \x00\x00\x0a\x00\x63\x00\x6c\x00\x65\x00\x61\x00\x6e\x08\x00\x00\ \x00\x00\x06\x00\x00\x00\x05\x63\x6c\x65\x61\x6e\x07\x00\x00\x00\ \x0b\x4f\x70\x65\x6e\x53\x65\x73\x73\x69\x6f\x6e\x01\x03\x00\x00\ \x00\x1a\x00\x75\x00\x6e\x00\x67\x00\x65\x00\x73\x00\x70\x00\x65\ \x00\x69\x00\x63\x00\x68\x00\x65\x00\x72\x00\x74\x08\x00\x00\x00\ \x00\x06\x00\x00\x00\x09\x6e\x6f\x74\x20\x73\x61\x76\x65\x64\x07\ \x00\x00\x00\x0b\x4f\x70\x65\x6e\x53\x65\x73\x73\x69\x6f\x6e\x01\ \x03\x00\x00\x00\xb2\x00\x57\x00\xe4\x00\x68\x00\x6c\x00\x65\x00\ \x6e\x00\x20\x00\x53\x00\x69\x00\x65\x00\x20\x00\x65\x00\x69\x00\ \x6e\x00\x65\x00\x6e\x00\x20\x00\x4e\x00\x61\x00\x6d\x00\x65\x00\ \x6e\x00\x20\x00\x66\x00\xfc\x00\x72\x00\x20\x00\x64\x00\x69\x00\ \x65\x00\x20\x00\x53\x00\x65\x00\x73\x00\x73\x00\x69\x00\x6f\x00\ \x6e\x00\x2e\x00\x20\x00\x42\x00\x65\x00\x6e\x00\x75\x00\x74\x00\ \x7a\x00\x65\x00\x6e\x00\x20\x00\x53\x00\x69\x00\x65\x00\x20\x00\ \x2f\x00\x20\x00\x75\x00\x6d\x00\x20\x00\x55\x00\x6e\x00\x74\x00\ \x65\x00\x72\x00\x76\x00\x65\x00\x72\x00\x7a\x00\x65\x00\x69\x00\ \x63\x00\x68\x00\x6e\x00\x69\x00\x73\x00\x73\x00\x65\x00\x20\x00\ \x65\x00\x69\x00\x6e\x00\x7a\x00\x75\x00\x72\x00\x69\x00\x63\x00\ \x68\x00\x74\x00\x65\x00\x6e\x08\x00\x00\x00\x00\x06\x00\x00\x00\ \x2f\x43\x68\x6f\x6f\x73\x65\x20\x61\x20\x70\x72\x6f\x6a\x65\x63\ \x74\x20\x6e\x61\x6d\x65\x2e\x20\x55\x73\x65\x20\x2f\x20\x66\x6f\ \x72\x20\x73\x75\x62\x64\x69\x72\x65\x63\x74\x6f\x72\x69\x65\x73\ \x07\x00\x00\x00\x0b\x50\x72\x6f\x6a\x65\x63\x74\x4e\x61\x6d\x65\ \x01\x03\x00\x00\x00\x1a\x00\x46\x00\x65\x00\x68\x00\x6c\x00\x65\ \x00\x72\x00\x6d\x00\x65\x00\x6c\x00\x64\x00\x75\x00\x6e\x00\x67\ \x08\x00\x00\x00\x00\x06\x00\x00\x00\x0d\x45\x72\x72\x6f\x72\x20\ \x4d\x65\x73\x73\x61\x67\x65\x07\x00\x00\x00\x0b\x50\x72\x6f\x6a\ \x65\x63\x74\x4e\x61\x6d\x65\x01\x03\xff\xff\xff\xff\x08\x00\x00\ \x00\x00\x06\x00\x00\x00\x04\x46\x6f\x72\x6d\x07\x00\x00\x00\x0b\ \x50\x72\x6f\x6a\x65\x63\x74\x4e\x61\x6d\x65\x01\x03\x00\x00\x00\ \x70\x00\x44\x00\x65\x00\x72\x00\x20\x00\x5a\x00\x75\x00\x67\x00\ \x72\x00\x69\x00\x66\x00\x66\x00\x20\x00\x61\x00\x75\x00\x66\x00\ \x20\x00\x64\x00\x61\x00\x73\x00\x20\x00\x45\x00\x6c\x00\x74\x00\ \x65\x00\x72\x00\x6e\x00\x76\x00\x65\x00\x72\x00\x7a\x00\x65\x00\ \x69\x00\x63\x00\x68\x00\x6e\x00\x69\x00\x73\x00\x20\x00\x69\x00\ \x73\x00\x74\x00\x20\x00\x6e\x00\x69\x00\x63\x00\x68\x00\x74\x00\ \x20\x00\x65\x00\x72\x00\x6c\x00\x61\x00\x75\x00\x62\x00\x74\x00\ \x2e\x08\x00\x00\x00\x00\x06\x00\x00\x00\x27\x4d\x6f\x76\x69\x6e\ \x67\x20\x74\x6f\x20\x70\x61\x72\x65\x6e\x74\x20\x64\x69\x72\x65\ \x63\x74\x6f\x72\x79\x20\x6e\x6f\x74\x20\x61\x6c\x6c\x6f\x77\x65\ \x64\x2e\x07\x00\x00\x00\x11\x50\x72\x6f\x6a\x65\x63\x74\x4e\x61\ \x6d\x65\x57\x69\x64\x67\x65\x74\x01\x03\x00\x00\x00\x44\x00\x44\ \x00\x65\x00\x72\x00\x20\x00\x4e\x00\x61\x00\x6d\x00\x65\x00\x20\ \x00\x69\x00\x73\x00\x74\x00\x20\x00\x62\x00\x65\x00\x72\x00\x65\ \x00\x69\x00\x74\x00\x73\x00\x20\x00\x69\x00\x6e\x00\x20\x00\x42\ \x00\x65\x00\x6e\x00\x75\x00\x74\x00\x7a\x00\x75\x00\x6e\x00\x67\ \x00\x2e\x08\x00\x00\x00\x00\x06\x00\x00\x00\x17\x4e\x61\x6d\x65\ \x20\x69\x73\x20\x61\x6c\x72\x65\x61\x64\x79\x20\x69\x6e\x20\x75\ \x73\x65\x2e\x07\x00\x00\x00\x11\x50\x72\x6f\x6a\x65\x63\x74\x4e\ \x61\x6d\x65\x57\x69\x64\x67\x65\x74\x01\x03\x00\x00\x00\x44\x00\ \x4e\x00\x61\x00\x6d\x00\x65\x00\x20\x00\x6d\x00\x75\x00\x73\x00\ \x73\x00\x20\x00\x65\x00\x69\x00\x6e\x00\x20\x00\x72\x00\x65\x00\ \x6c\x00\x61\x00\x74\x00\x69\x00\x76\x00\x65\x00\x72\x00\x20\x00\ \x50\x00\x66\x00\x61\x00\x64\x00\x20\x00\x73\x00\x65\x00\x69\x00\ \x6e\x00\x2e\x08\x00\x00\x00\x00\x06\x00\x00\x00\x1d\x4e\x61\x6d\ \x65\x20\x6d\x75\x73\x74\x20\x62\x65\x20\x61\x20\x72\x65\x6c\x61\ \x74\x69\x76\x65\x20\x70\x61\x74\x68\x2e\x07\x00\x00\x00\x11\x50\ \x72\x6f\x6a\x65\x63\x74\x4e\x61\x6d\x65\x57\x69\x64\x67\x65\x74\ \x01\x03\x00\x00\x00\x34\x00\x4e\x00\x61\x00\x6d\x00\x65\x00\x20\ \x00\x64\x00\x61\x00\x72\x00\x66\x00\x20\x00\x6e\x00\x69\x00\x63\ \x00\x68\x00\x74\x00\x20\x00\x6c\x00\x65\x00\x65\x00\x72\x00\x20\ \x00\x73\x00\x65\x00\x69\x00\x6e\x00\x2e\x08\x00\x00\x00\x00\x06\ \x00\x00\x00\x17\x4e\x61\x6d\x65\x20\x6d\x75\x73\x74\x20\x6e\x6f\ \x74\x20\x62\x65\x20\x65\x6d\x70\x74\x79\x2e\x07\x00\x00\x00\x11\ \x50\x72\x6f\x6a\x65\x63\x74\x4e\x61\x6d\x65\x57\x69\x64\x67\x65\ \x74\x01\x03\x00\x00\x00\x6a\x00\x53\x00\x69\x00\x65\x00\x20\x00\ \x68\x00\x61\x00\x62\x00\x65\x00\x6e\x00\x20\x00\x6b\x00\x65\x00\ \x69\x00\x6e\x00\x65\x00\x20\x00\x53\x00\x63\x00\x68\x00\x72\x00\ \x65\x00\x69\x00\x62\x00\x72\x00\x65\x00\x63\x00\x68\x00\x74\x00\ \x65\x00\x20\x00\x66\x00\xfc\x00\x72\x00\x20\x00\x64\x00\x69\x00\ \x65\x00\x73\x00\x65\x00\x73\x00\x20\x00\x56\x00\x65\x00\x72\x00\ \x7a\x00\x65\x00\x69\x00\x63\x00\x68\x00\x6e\x00\x69\x00\x73\x00\ \x2e\x08\x00\x00\x00\x00\x06\x00\x00\x00\x2b\x57\x72\x69\x74\x69\ \x6e\x67\x20\x69\x6e\x20\x74\x68\x69\x73\x20\x64\x69\x72\x65\x63\ \x74\x6f\x72\x79\x20\x69\x73\x20\x6e\x6f\x74\x20\x70\x65\x72\x6d\ \x69\x74\x74\x65\x64\x2e\x07\x00\x00\x00\x11\x50\x72\x6f\x6a\x65\ \x63\x74\x4e\x61\x6d\x65\x57\x69\x64\x67\x65\x74\x01\x03\x00\x00\ \x01\xb8\x00\x42\x00\x65\x00\x66\x00\x65\x00\x68\x00\x6c\x00\x20\ \x00\x6e\x00\x69\x00\x63\x00\x68\x00\x74\x00\x20\x00\x67\x00\x65\ \x00\x66\x00\x75\x00\x6e\x00\x64\x00\x65\x00\x6e\x00\x20\x00\x6f\ \x00\x64\x00\x65\x00\x72\x00\x20\x00\x6e\x00\x69\x00\x63\x00\x68\ \x00\x74\x00\x20\x00\x61\x00\x6b\x00\x7a\x00\x65\x00\x70\x00\x74\ \x00\x69\x00\x65\x00\x72\x00\x74\x00\x21\x00\x26\x00\x6c\x00\x74\ \x00\x3b\x00\x62\x00\x72\x00\x26\x00\x67\x00\x74\x00\x3b\x00\x50\ \x00\x61\x00\x72\x00\x61\x00\x6d\x00\x65\x00\x74\x00\x65\x00\x72\ \x00\x2c\x00\x20\x00\x2d\x00\x2d\x00\x73\x00\x63\x00\x68\x00\x61\ \x00\x6c\x00\x74\x00\x65\x00\x72\x00\x20\x00\x75\x00\x6e\x00\x64\ \x00\x20\x00\x72\x00\x65\x00\x6c\x00\x61\x00\x74\x00\x69\x00\x76\ \x00\x65\x00\x20\x00\x50\x00\x66\x00\x61\x00\x64\x00\x65\x00\x20\ \x00\x73\x00\x69\x00\x6e\x00\x64\x00\x20\x00\x6e\x00\x69\x00\x63\ \x00\x68\x00\x74\x00\x20\x00\x65\x00\x72\x00\x6c\x00\x61\x00\x75\ \x00\x62\x00\x74\x00\x2e\x00\x26\x00\x6c\x00\x74\x00\x3b\x00\x62\ \x00\x72\x00\x26\x00\x67\x00\x74\x00\x3b\x00\x42\x00\x65\x00\x6e\ \x00\x75\x00\x74\x00\x7a\x00\x65\x00\x6e\x00\x20\x00\x53\x00\x69\ \x00\x65\x00\x20\x00\x73\x00\x74\x00\x61\x00\x74\x00\x74\x00\x64\ \x00\x65\x00\x73\x00\x73\x00\x65\x00\x6e\x00\x20\x00\x65\x00\x69\ \x00\x6e\x00\x20\x00\x50\x00\x72\x00\x6f\x00\x67\x00\x72\x00\x61\ \x00\x6d\x00\x6d\x00\x20\x00\x77\x00\x69\x00\x65\x00\x20\x00\x6e\ \x00\x73\x00\x6d\x00\x2d\x00\x70\x00\x72\x00\x6f\x00\x78\x00\x79\ \x00\x20\x00\x6f\x00\x64\x00\x65\x00\x72\x00\x20\x00\x73\x00\x63\ \x00\x68\x00\x72\x00\x65\x00\x69\x00\x62\x00\x65\x00\x6e\x00\x20\ \x00\x73\x00\x69\x00\x65\x00\x20\x00\x73\x00\x65\x00\x6c\x00\x62\ \x00\x73\x00\x74\x00\x20\x00\x65\x00\x69\x00\x6e\x00\x20\x00\x53\ \x00\x74\x00\x61\x00\x72\x00\x74\x00\x65\x00\x72\x00\x73\x00\x63\ \x00\x72\x00\x69\x00\x70\x00\x74\x00\x2e\x08\x00\x00\x00\x00\x06\ \x00\x00\x00\x94\x43\x6f\x6d\x6d\x61\x6e\x64\x20\x6e\x6f\x74\x20\ \x66\x6f\x75\x6e\x64\x20\x6f\x72\x20\x6e\x6f\x74\x20\x61\x63\x63\ \x65\x70\x74\x65\x64\x21\x3c\x62\x72\x3e\x50\x61\x72\x61\x6d\x65\ \x74\x65\x72\x73\x2c\x20\x2d\x2d\x73\x77\x69\x74\x63\x68\x65\x73\ \x20\x61\x6e\x64\x20\x72\x65\x6c\x61\x74\x69\x76\x65\x20\x70\x61\ \x74\x68\x73\x20\x61\x72\x65\x20\x6e\x6f\x74\x20\x61\x6c\x6c\x6f\ \x77\x65\x64\x2e\x3c\x62\x72\x3e\x55\x73\x65\x20\x6e\x73\x6d\x2d\ \x70\x72\x6f\x78\x79\x20\x6f\x72\x20\x77\x72\x69\x74\x65\x20\x61\ \x20\x73\x74\x61\x72\x74\x65\x72\x2d\x73\x63\x72\x69\x70\x74\x20\ \x69\x6e\x73\x74\x65\x61\x64\x2e\x07\x00\x00\x00\x0c\x50\x72\x6f\ \x6d\x70\x74\x57\x69\x64\x67\x65\x74\x01\x03\x00\x00\x00\xbe\x00\ \x4b\x00\x65\x00\x69\x00\x6e\x00\x65\x00\x20\x00\x50\x00\x72\x00\ \x6f\x00\x67\x00\x72\x00\x61\x00\x6d\x00\x6d\x00\x2d\x00\x44\x00\ \x61\x00\x74\x00\x65\x00\x6e\x00\x62\x00\x61\x00\x6e\x00\x6b\x00\ \x20\x00\x67\x00\x65\x00\x66\x00\x75\x00\x6e\x00\x64\x00\x65\x00\ \x6e\x00\x2e\x00\x20\x00\x42\x00\x69\x00\x74\x00\x74\x00\x65\x00\ \x20\x00\x62\x00\x65\x00\x6e\x00\x75\x00\x74\x00\x7a\x00\x65\x00\ \x6e\x00\x20\x00\x53\x00\x69\x00\x65\x00\x20\x00\x64\x00\x61\x00\ \x73\x00\x20\x00\x53\x00\x74\x00\x65\x00\x75\x00\x65\x00\x72\x00\ \x75\x00\x6e\x00\x67\x00\x73\x00\x6d\x00\x65\x00\x6e\x00\xfc\x00\ \x20\x00\x75\x00\x6d\x00\x20\x00\x64\x00\x69\x00\x65\x00\x73\x00\ \x65\x00\x20\x00\x7a\x00\x75\x00\x20\x00\x65\x00\x72\x00\x73\x00\ \x74\x00\x65\x00\x6c\x00\x6c\x00\x65\x00\x6e\x00\x2e\x08\x00\x00\ \x00\x00\x06\x00\x00\x00\x3e\x4e\x6f\x20\x70\x72\x6f\x67\x72\x61\ \x6d\x20\x64\x61\x74\x61\x62\x61\x73\x65\x20\x66\x6f\x75\x6e\x64\ \x2e\x20\x50\x6c\x65\x61\x73\x65\x20\x75\x70\x64\x61\x74\x65\x20\ \x74\x68\x72\x6f\x75\x67\x68\x20\x43\x6f\x6e\x74\x72\x6f\x6c\x20\ \x6d\x65\x6e\x75\x2e\x07\x00\x00\x00\x0c\x50\x72\x6f\x6d\x70\x74\ \x57\x69\x64\x67\x65\x74\x01\x03\x00\x00\x00\x84\x00\x53\x00\x63\ \x00\x68\x00\x72\x00\x65\x00\x69\x00\x62\x00\x65\x00\x6e\x00\x20\ \x00\x53\x00\x69\x00\x65\x00\x20\x00\x64\x00\x65\x00\x6e\x00\x20\ \x00\x4e\x00\x61\x00\x6d\x00\x65\x00\x6e\x00\x20\x00\x65\x00\x69\ \x00\x6e\x00\x65\x00\x72\x00\x20\x00\x61\x00\x75\x00\x73\x00\x66\ \x00\xfc\x00\x68\x00\x72\x00\x62\x00\x61\x00\x72\x00\x65\x00\x6e\ \x00\x20\x00\x44\x00\x61\x00\x74\x00\x65\x00\x69\x00\x20\x00\x76\ \x00\x6f\x00\x6e\x00\x20\x00\x69\x00\x68\x00\x72\x00\x65\x00\x6d\ \x00\x20\x00\x53\x00\x79\x00\x73\x00\x74\x00\x65\x00\x6d\x00\x2e\ \x08\x00\x00\x00\x00\x06\x00\x00\x00\x36\x54\x79\x70\x65\x20\x69\ \x6e\x20\x74\x68\x65\x20\x6e\x61\x6d\x65\x20\x6f\x66\x20\x61\x6e\ \x20\x65\x78\x65\x63\x75\x74\x61\x62\x6c\x65\x20\x66\x69\x6c\x65\ \x20\x6f\x6e\x20\x79\x6f\x75\x72\x20\x73\x79\x73\x74\x65\x6d\x2e\ \x07\x00\x00\x00\x0c\x50\x72\x6f\x6d\x70\x74\x57\x69\x64\x67\x65\ \x74\x01\x03\x00\x00\x00\x3e\x00\x53\x00\x65\x00\x73\x00\x73\x00\ \x69\x00\x6f\x00\x6e\x00\x20\x00\x7b\x00\x7d\x00\x20\x00\x73\x00\ \x6f\x00\x6c\x00\x6c\x00\x20\x00\x67\x00\x65\x00\x6c\x00\xf6\x00\ \x73\x00\x63\x00\x68\x00\x74\x00\x20\x00\x77\x00\x65\x00\x72\x00\ \x64\x00\x65\x00\x6e\x08\x00\x00\x00\x00\x06\x00\x00\x00\x1a\x41\ \x62\x6f\x75\x74\x20\x74\x6f\x20\x64\x65\x6c\x65\x74\x65\x20\x53\ \x65\x73\x73\x69\x6f\x6e\x20\x7b\x7d\x07\x00\x00\x00\x0b\x53\x65\ \x73\x73\x69\x6f\x6e\x54\x72\x65\x65\x01\x03\x00\x00\x00\x9a\x00\ \x41\x00\x6c\x00\x6c\x00\x65\x00\x20\x00\x44\x00\x61\x00\x74\x00\ \x65\x00\x69\x00\x65\x00\x6e\x00\x20\x00\x61\x00\x75\x00\x73\x00\ \x20\x00\x64\x00\x69\x00\x65\x00\x73\x00\x65\x00\x6d\x00\x20\x00\ \x50\x00\x72\x00\x6f\x00\x6a\x00\x65\x00\x6b\x00\x74\x00\x76\x00\ \x65\x00\x72\x00\x7a\x00\x65\x00\x69\x00\x63\x00\x68\x00\x6e\x00\ \x69\x00\x73\x00\x20\x00\x77\x00\x65\x00\x72\x00\x64\x00\x65\x00\ \x6e\x00\x20\x00\x75\x00\x6e\x00\x77\x00\x69\x00\x65\x00\x64\x00\ \x65\x00\x72\x00\x62\x00\x72\x00\x69\x00\x6e\x00\x67\x00\x6c\x00\ \x69\x00\x63\x00\x68\x00\x20\x00\x67\x00\x65\x00\x6c\x00\xf6\x00\ \x73\x00\x63\x00\x68\x00\x74\x00\x2e\x08\x00\x00\x00\x00\x06\x00\ \x00\x00\x40\x41\x6c\x6c\x20\x66\x69\x6c\x65\x73\x20\x69\x6e\x20\ \x74\x68\x65\x20\x70\x72\x6f\x6a\x65\x63\x74\x20\x64\x69\x72\x65\ \x63\x74\x6f\x72\x79\x20\x77\x69\x6c\x6c\x20\x62\x65\x20\x69\x72\ \x72\x65\x76\x65\x72\x73\x69\x62\x6c\x79\x20\x64\x65\x6c\x65\x74\ \x65\x64\x2e\x07\x00\x00\x00\x0b\x53\x65\x73\x73\x69\x6f\x6e\x54\ \x72\x65\x65\x01\x03\x00\x00\x00\x0e\x00\x43\x00\x6c\x00\x69\x00\ \x65\x00\x6e\x00\x74\x00\x73\x08\x00\x00\x00\x00\x06\x00\x00\x00\ \x07\x43\x6c\x69\x65\x6e\x74\x73\x07\x00\x00\x00\x0b\x53\x65\x73\ \x73\x69\x6f\x6e\x54\x72\x65\x65\x01\x03\x00\x00\x00\x20\x00\x53\ \x00\x65\x00\x73\x00\x73\x00\x69\x00\x6f\x00\x6e\x00\x20\x00\x6b\ \x00\x6f\x00\x70\x00\x69\x00\x65\x00\x72\x00\x65\x00\x6e\x08\x00\ \x00\x00\x00\x06\x00\x00\x00\x0c\x43\x6f\x70\x79\x20\x53\x65\x73\ \x73\x69\x6f\x6e\x07\x00\x00\x00\x0b\x53\x65\x73\x73\x69\x6f\x6e\ \x54\x72\x65\x65\x01\x03\x00\x00\x00\x24\x00\x4b\x00\x6f\x00\x70\ \x00\x69\x00\x65\x00\x72\x00\x65\x00\x20\x00\x7b\x00\x7d\x00\x20\ \x00\x6e\x00\x61\x00\x63\x00\x68\x00\x20\x00\x7b\x00\x7d\x08\x00\ \x00\x00\x00\x06\x00\x00\x00\x10\x43\x6f\x70\x79\x69\x6e\x67\x20\ \x7b\x7d\x20\x74\x6f\x20\x7b\x7d\x07\x00\x00\x00\x0b\x53\x65\x73\ \x73\x69\x6f\x6e\x54\x72\x65\x65\x01\x03\x00\x00\x00\x1e\x00\x53\ \x00\x65\x00\x73\x00\x73\x00\x69\x00\x6f\x00\x6e\x00\x20\x00\x6c\ \x00\xf6\x00\x73\x00\x63\x00\x68\x00\x65\x00\x6e\x08\x00\x00\x00\ \x00\x06\x00\x00\x00\x0e\x44\x65\x6c\x65\x74\x65\x20\x53\x65\x73\ \x73\x69\x6f\x6e\x07\x00\x00\x00\x0b\x53\x65\x73\x73\x69\x6f\x6e\ \x54\x72\x65\x65\x01\x03\x00\x00\x00\x10\x00\x4c\x00\xf6\x00\x73\ \x00\x63\x00\x68\x00\x65\x00\x6e\x00\x21\x08\x00\x00\x00\x00\x06\ \x00\x00\x00\x07\x44\x65\x6c\x65\x74\x65\x21\x07\x00\x00\x00\x0b\ \x53\x65\x73\x73\x69\x6f\x6e\x54\x72\x65\x65\x01\x03\x00\x00\x00\ \x20\x00\x53\x00\x65\x00\x73\x00\x73\x00\x69\x00\x6f\x00\x6e\x00\ \x20\x00\x62\x00\x65\x00\x68\x00\x61\x00\x6c\x00\x74\x00\x65\x00\ \x6e\x08\x00\x00\x00\x00\x06\x00\x00\x00\x0c\x4b\x65\x65\x70\x20\ \x53\x65\x73\x73\x69\x6f\x6e\x07\x00\x00\x00\x0b\x53\x65\x73\x73\ \x69\x6f\x6e\x54\x72\x65\x65\x01\x03\x00\x00\x00\x24\x00\x4c\x00\ \x65\x00\x74\x00\x7a\x00\x74\x00\x65\x00\x20\x00\x53\x00\x70\x00\ \x65\x00\x69\x00\x63\x00\x68\x00\x65\x00\x72\x00\x75\x00\x6e\x00\ \x67\x08\x00\x00\x00\x00\x06\x00\x00\x00\x09\x4c\x61\x73\x74\x20\ \x53\x61\x76\x65\x07\x00\x00\x00\x0b\x53\x65\x73\x73\x69\x6f\x6e\ \x54\x72\x65\x65\x01\x03\x00\x00\x00\x08\x00\x4e\x00\x61\x00\x6d\ \x00\x65\x08\x00\x00\x00\x00\x06\x00\x00\x00\x04\x4e\x61\x6d\x65\ \x07\x00\x00\x00\x0b\x53\x65\x73\x73\x69\x6f\x6e\x54\x72\x65\x65\ \x01\x03\x00\x00\x00\x08\x00\x50\x00\x66\x00\x61\x00\x64\x08\x00\ \x00\x00\x00\x06\x00\x00\x00\x04\x50\x61\x74\x68\x07\x00\x00\x00\ \x0b\x53\x65\x73\x73\x69\x6f\x6e\x54\x72\x65\x65\x01\x03\x00\x00\ \x00\x20\x00\x53\x00\x65\x00\x73\x00\x73\x00\x69\x00\x6f\x00\x6e\ \x00\x20\x00\x75\x00\x6d\x00\x62\x00\x65\x00\x6e\x00\x6e\x00\x65\ \x00\x6e\x08\x00\x00\x00\x00\x06\x00\x00\x00\x0e\x52\x65\x6e\x61\ \x6d\x65\x20\x53\x65\x73\x73\x69\x6f\x6e\x07\x00\x00\x00\x0b\x53\ \x65\x73\x73\x69\x6f\x6e\x54\x72\x65\x65\x01\x03\x00\x00\x00\x0a\ \x00\x47\x00\x72\x00\xf6\x00\xdf\x00\x65\x08\x00\x00\x00\x00\x06\ \x00\x00\x00\x04\x53\x69\x7a\x65\x07\x00\x00\x00\x0b\x53\x65\x73\ \x73\x69\x6f\x6e\x54\x72\x65\x65\x01\x03\x00\x00\x00\x10\x00\x53\ \x00\x79\x00\x6d\x00\x6c\x00\x69\x00\x6e\x00\x6b\x00\x73\x08\x00\ \x00\x00\x00\x06\x00\x00\x00\x08\x53\x79\x6d\x6c\x69\x6e\x6b\x73\ \x07\x00\x00\x00\x0b\x53\x65\x73\x73\x69\x6f\x6e\x54\x72\x65\x65\ \x01\x03\x00\x00\x00\x12\x00\x43\x00\x68\x00\x61\x00\x6e\x00\x67\ \x00\x65\x00\x6c\x00\x6f\x00\x67\x08\x00\x00\x00\x00\x06\x00\x00\ \x00\x09\x43\x68\x61\x6e\x67\x65\x6c\x6f\x67\x07\x00\x00\x00\x11\ \x54\x65\x6d\x70\x6c\x61\x74\x65\x43\x68\x61\x6e\x67\x65\x6c\x6f\ \x67\x01\x03\x00\x00\x00\x4e\x00\x44\x00\x65\x00\x6e\x00\x20\x00\ \x43\x00\x68\x00\x61\x00\x6e\x00\x67\x00\x65\x00\x6c\x00\x6f\x00\ \x67\x00\x20\x00\x67\x00\x69\x00\x62\x00\x74\x00\x20\x00\x65\x00\ \x73\x00\x20\x00\x6e\x00\x75\x00\x72\x00\x20\x00\x61\x00\x75\x00\ \x66\x00\x20\x00\x45\x00\x6e\x00\x67\x00\x6c\x00\x69\x00\x73\x00\ \x63\x00\x68\x00\x2e\x08\x00\x00\x00\x00\x06\x00\x00\x00\x2b\x54\ \x68\x65\x20\x43\x68\x61\x6e\x67\x65\x6c\x6f\x67\x20\x69\x73\x20\ \x6f\x6e\x6c\x79\x20\x61\x76\x61\x69\x6c\x61\x62\x6c\x65\x20\x69\ \x6e\x20\x45\x6e\x67\x6c\x69\x73\x68\x2e\x07\x00\x00\x00\x11\x54\ \x65\x6d\x70\x6c\x61\x74\x65\x43\x68\x61\x6e\x67\x65\x6c\x6f\x67\ \x01\x03\x00\x00\x00\x0c\x00\x5a\x00\x75\x00\x72\x00\xfc\x00\x63\ \x00\x6b\x08\x00\x00\x00\x00\x06\x00\x00\x00\x04\x42\x61\x63\x6b\ \x07\x00\x00\x00\x12\x54\x65\x6d\x70\x6c\x61\x74\x65\x55\x73\x65\ \x72\x4d\x61\x6e\x75\x61\x6c\x01\x03\xff\xff\xff\xff\x08\x00\x00\ \x00\x00\x06\x00\x00\x00\x04\x46\x6f\x72\x6d\x07\x00\x00\x00\x12\ \x54\x65\x6d\x70\x6c\x61\x74\x65\x55\x73\x65\x72\x4d\x61\x6e\x75\ \x61\x6c\x01\x03\x00\x00\x00\x14\x00\x53\x00\x74\x00\x61\x00\x72\ \x00\x74\x00\x73\x00\x65\x00\x69\x00\x74\x00\x65\x08\x00\x00\x00\ \x00\x06\x00\x00\x00\x04\x48\x6f\x6d\x65\x07\x00\x00\x00\x12\x54\ \x65\x6d\x70\x6c\x61\x74\x65\x55\x73\x65\x72\x4d\x61\x6e\x75\x61\ \x6c\x01\x03\x00\x00\x00\x20\x00\x42\x00\x65\x00\x6e\x00\x75\x00\ \x74\x00\x7a\x00\x65\x00\x72\x00\x68\x00\x61\x00\x6e\x00\x64\x00\ \x62\x00\x75\x00\x63\x00\x68\x08\x00\x00\x00\x00\x06\x00\x00\x00\ \x0b\x55\x73\x65\x72\x20\x4d\x61\x6e\x75\x61\x6c\x07\x00\x00\x00\ \x12\x54\x65\x6d\x70\x6c\x61\x74\x65\x55\x73\x65\x72\x4d\x61\x6e\ \x75\x61\x6c\x01\x03\x00\x00\x00\x38\x00\x50\x00\x72\x00\x6f\x00\ \x67\x00\x72\x00\x61\x00\x6d\x00\x6d\x00\x20\x00\x68\x00\x69\x00\ \x6e\x00\x7a\x00\x75\x00\x66\x00\xfc\x00\x67\x00\x65\x00\x6e\x00\ \x20\x00\x28\x00\x50\x00\x72\x00\x6f\x00\x6d\x00\x70\x00\x74\x00\ \x29\x08\x00\x00\x00\x00\x06\x00\x00\x00\x13\x41\x64\x64\x20\x43\ \x6c\x69\x65\x6e\x74\x20\x28\x50\x72\x6f\x6d\x70\x74\x29\x07\x00\ \x00\x00\x08\x54\x72\x61\x79\x49\x63\x6f\x6e\x01\x03\x00\x00\x00\ \x5e\x00\x53\x00\x63\x00\x68\x00\x6c\x00\x69\x00\x65\x00\xdf\x00\ \x65\x00\x6e\x00\x20\x00\x6f\x00\x68\x00\x6e\x00\x65\x00\x20\x00\ \x7a\x00\x75\x00\x20\x00\x53\x00\x70\x00\x65\x00\x69\x00\x63\x00\ \x68\x00\x65\x00\x72\x00\x6e\x00\x20\x00\x26\x00\x26\x00\x20\x00\ \x41\x00\x67\x00\x6f\x00\x72\x00\x64\x00\x65\x00\x6a\x00\x6f\x00\ \x20\x00\x62\x00\x65\x00\x65\x00\x6e\x00\x64\x00\x65\x00\x6e\x08\ \x00\x00\x00\x00\x06\x00\x00\x00\x25\x43\x6c\x6f\x73\x65\x20\x77\ \x69\x74\x68\x6f\x75\x74\x20\x53\x61\x76\x69\x6e\x67\x20\x26\x26\ \x20\x51\x75\x69\x74\x20\x41\x67\x6f\x72\x64\x65\x6a\x6f\x07\x00\ \x00\x00\x08\x54\x72\x61\x79\x49\x63\x6f\x6e\x01\x03\x00\x00\x00\ \x30\x00\x56\x00\x65\x00\x72\x00\x73\x00\x74\x00\x65\x00\x63\x00\ \x6b\x00\x65\x00\x2f\x00\x5a\x00\x65\x00\x69\x00\x67\x00\x65\x00\ \x20\x00\x41\x00\x67\x00\x6f\x00\x72\x00\x64\x00\x65\x00\x6a\x00\ \x6f\x08\x00\x00\x00\x00\x06\x00\x00\x00\x12\x48\x69\x64\x65\x2f\ \x53\x68\x6f\x77\x20\x41\x67\x6f\x72\x64\x65\x6a\x6f\x07\x00\x00\ \x00\x08\x54\x72\x61\x79\x49\x63\x6f\x6e\x01\x03\x00\x00\x00\x10\ \x00\x42\x00\x65\x00\x65\x00\x6e\x00\x64\x00\x65\x00\x6e\x00\x20\ \x08\x00\x00\x00\x00\x06\x00\x00\x00\x05\x51\x75\x69\x74\x20\x07\ \x00\x00\x00\x08\x54\x72\x61\x79\x49\x63\x6f\x6e\x01\x03\x00\x00\ \x00\x3c\x00\x41\x00\x62\x00\x62\x00\x72\x00\x65\x00\x63\x00\x68\ \x00\x65\x00\x6e\x00\x20\x00\x75\x00\x6e\x00\x64\x00\x20\x00\x41\ \x00\x67\x00\x6f\x00\x72\x00\x64\x00\x65\x00\x6a\x00\x6f\x00\x20\ \x00\x42\x00\x65\x00\x65\x00\x6e\x00\x64\x00\x65\x00\x6e\x08\x00\ \x00\x00\x00\x06\x00\x00\x00\x15\x53\x61\x76\x65\x20\x26\x26\x20\ \x51\x75\x69\x74\x20\x41\x67\x6f\x72\x64\x65\x6a\x6f\x07\x00\x00\ \x00\x08\x54\x72\x61\x79\x49\x63\x6f\x6e\x01\x03\x00\x00\x00\x2e\ \x00\x53\x00\x69\x00\x63\x00\x68\x00\x74\x00\x62\x00\x61\x00\x72\ \x00\x6b\x00\x65\x00\x69\x00\x74\x00\x20\x00\x75\x00\x6d\x00\x73\ \x00\x63\x00\x68\x00\x61\x00\x6c\x00\x74\x00\x65\x00\x6e\x08\x00\ \x00\x00\x00\x06\x00\x00\x00\x18\x54\x6f\x67\x67\x6c\x65\x20\x43\ \x6c\x69\x65\x6e\x74\x20\x56\x69\x73\x69\x62\x69\x6c\x69\x74\x79\ \x07\x00\x00\x00\x08\x54\x72\x61\x79\x49\x63\x6f\x6e\x01\x03\x00\ \x00\x00\x76\x00\x42\x00\x69\x00\x74\x00\x74\x00\x65\x00\x20\x00\ \x62\x00\x65\x00\x73\x00\x74\x00\xe4\x00\x74\x00\x69\x00\x67\x00\ \x65\x00\x6e\x00\x20\x00\x53\x00\x69\x00\x65\x00\x20\x00\x64\x00\ \x75\x00\x72\x00\x63\x00\x68\x00\x20\x00\x65\x00\x69\x00\x6e\x00\ \x65\x00\x6e\x00\x20\x00\x4b\x00\x6c\x00\x69\x00\x63\x00\x6b\x00\ \x20\x00\x61\x00\x75\x00\x66\x00\x20\x00\x64\x00\x65\x00\x6e\x00\ \x20\x00\x4b\x00\x6e\x00\x6f\x00\x70\x00\x66\x00\x20\x00\x75\x00\ \x6e\x00\x74\x00\x65\x00\x6e\x00\x2e\x08\x00\x00\x00\x00\x06\x00\ \x00\x00\x38\x50\x6c\x65\x61\x73\x65\x20\x63\x6f\x6e\x66\x69\x72\ \x6d\x20\x77\x69\x74\x68\x20\x61\x20\x63\x6c\x69\x63\x6b\x20\x6f\ \x6e\x20\x74\x68\x65\x20\x62\x75\x74\x74\x6f\x6e\x20\x61\x74\x20\ \x74\x68\x65\x20\x62\x6f\x74\x74\x6f\x6d\x2e\x07\x00\x00\x00\x0a\ \x57\x61\x69\x74\x44\x69\x61\x6c\x6f\x67\x01\x03\x00\x00\x00\x26\ \x00\x41\x00\x67\x00\x6f\x00\x72\x00\x64\x00\x65\x00\x6a\x00\x6f\ \x00\x20\x00\x69\x00\x73\x00\x74\x00\x20\x00\x62\x00\x65\x00\x72\ \x00\x65\x00\x69\x00\x74\x08\x00\x00\x00\x00\x06\x00\x00\x00\x0e\ \x41\x67\x6f\x72\x64\x65\x6a\x6f\x20\x72\x65\x61\x64\x79\x07\x00\ \x00\x00\x0a\x6d\x61\x69\x6e\x57\x69\x6e\x64\x6f\x77\x01\x03\x00\ \x00\x00\x5c\x00\x45\x00\x73\x00\x20\x00\x77\x00\x75\x00\x72\x00\ \x64\x00\x65\x00\x20\x00\x76\x00\x65\x00\x72\x00\x73\x00\x75\x00\ \x63\x00\x68\x00\x74\x00\x20\x00\x65\x00\x69\x00\x6e\x00\x65\x00\ \x20\x00\x77\x00\x65\x00\x69\x00\x74\x00\x65\x00\x72\x00\x65\x00\ \x20\x00\x47\x00\x55\x00\x49\x00\x20\x00\x7a\x00\x75\x00\x20\x00\ \x73\x00\x74\x00\x61\x00\x72\x00\x74\x00\x65\x00\x6e\x00\x2e\x08\ \x00\x00\x00\x00\x06\x00\x00\x00\x1c\x41\x6e\x6f\x74\x68\x65\x72\ \x20\x47\x55\x49\x20\x74\x72\x69\x65\x64\x20\x74\x6f\x20\x6c\x61\ \x75\x6e\x63\x68\x2e\x07\x00\x00\x00\x0a\x6d\x61\x69\x6e\x57\x69\ \x6e\x64\x6f\x77\x01\x03\x00\x00\x01\x7e\x00\x50\x00\x72\x00\x6f\ \x00\x67\x00\x72\x00\x61\x00\x6d\x00\x6d\x00\x64\x00\x61\x00\x74\ \x00\x65\x00\x6e\x00\x62\x00\x61\x00\x6e\x00\x6b\x00\x20\x00\x77\ \x00\x69\x00\x72\x00\x64\x00\x20\x00\x61\x00\x6b\x00\x74\x00\x75\ \x00\x61\x00\x6c\x00\x69\x00\x73\x00\x69\x00\x65\x00\x72\x00\x74\ \x00\x2e\x00\x0a\x00\x56\x00\x69\x00\x65\x00\x6c\x00\x65\x00\x6e\ \x00\x20\x00\x44\x00\x61\x00\x6e\x00\x6b\x00\x20\x00\x66\x00\xfc\ \x00\x72\x00\x20\x00\x69\x00\x68\x00\x72\x00\x65\x00\x20\x00\x47\ \x00\x65\x00\x64\x00\x75\x00\x6c\x00\x64\x00\x2e\x00\x0a\x00\x57\ \x00\x65\x00\x6e\x00\x6e\x00\x20\x00\x64\x00\x65\x00\x72\x00\x20\ \x00\x50\x00\x72\x00\x6f\x00\x7a\x00\x65\x00\x73\x00\x73\x00\x20\ \x00\x65\x00\x69\x00\x6e\x00\x67\x00\x65\x00\x66\x00\x6f\x00\x72\ \x00\x65\x00\x6e\x00\x20\x00\x69\x00\x73\x00\x74\x00\x20\x00\x65\ \x00\x72\x00\x7a\x00\x77\x00\x69\x00\x6e\x00\x67\x00\x65\x00\x6e\ \x00\x20\x00\x53\x00\x69\x00\x65\x00\x20\x00\x62\x00\x69\x00\x74\ \x00\x74\x00\x65\x00\x20\x00\x64\x00\x69\x00\x65\x00\x20\x00\x42\ \x00\x65\x00\x65\x00\x6e\x00\x64\x00\x75\x00\x6e\x00\x67\x00\x20\ \x00\x64\x00\x65\x00\x73\x00\x20\x00\x50\x00\x72\x00\x6f\x00\x67\ \x00\x72\x00\x61\x00\x6d\x00\x6d\x00\x73\x00\x20\x00\x75\x00\x6e\ \x00\x64\x00\x20\x00\x73\x00\x74\x00\x61\x00\x72\x00\x74\x00\x65\ \x00\x6e\x00\x20\x00\x65\x00\x73\x00\x20\x00\x65\x00\x72\x00\x6e\ \x00\x65\x00\x75\x00\x74\x00\x2e\x00\x20\x00\x49\x00\x68\x00\x72\ \x00\x65\x00\x20\x00\x44\x00\x61\x00\x74\x00\x65\x00\x6e\x00\x20\ \x00\x73\x00\x69\x00\x6e\x00\x64\x00\x20\x00\x73\x00\x69\x00\x63\ \x00\x68\x00\x65\x00\x72\x00\x2e\x08\x00\x00\x00\x00\x06\x00\x00\ \x00\x76\x55\x70\x64\x61\x74\x69\x6e\x67\x20\x50\x72\x6f\x67\x72\ \x61\x6d\x20\x44\x61\x74\x61\x62\x61\x73\x65\x2e\x0a\x54\x68\x61\ \x6e\x6b\x20\x79\x6f\x75\x20\x66\x6f\x72\x20\x79\x6f\x75\x72\x20\ \x70\x61\x74\x69\x65\x6e\x63\x65\x2e\x0a\x49\x66\x20\x70\x72\x6f\ \x67\x72\x65\x73\x73\x20\x66\x72\x65\x65\x7a\x65\x73\x20\x70\x6c\ \x65\x61\x73\x65\x20\x6b\x69\x6c\x6c\x20\x61\x6e\x64\x20\x72\x65\ \x73\x74\x61\x72\x74\x20\x74\x68\x65\x20\x77\x68\x6f\x6c\x65\x20\ \x70\x72\x6f\x67\x72\x61\x6d\x2e\x07\x00\x00\x00\x0a\x6d\x61\x69\ \x6e\x57\x69\x6e\x64\x6f\x77\x01\x88\x00\x00\x00\x02\x01\x01\ " qt_resource_name = b"\ \x00\x09\ \x0d\xc5\xb4\x07\ \x00\x70\ \x00\x6f\x00\x77\x00\x65\x00\x72\x00\x2e\x00\x73\x00\x76\x00\x67\ \x00\x0b\ \x06\x42\x36\x27\ \x00\x73\ \x00\x74\x00\x6f\x00\x70\x00\x70\x00\x65\x00\x64\x00\x2e\x00\x73\x00\x76\x00\x67\ \x00\x0b\ \x00\xbd\xd0\x67\ \x00\x72\ \x00\x75\x00\x6e\x00\x6e\x00\x69\x00\x6e\x00\x67\x00\x2e\x00\x73\x00\x76\x00\x67\ \x00\x08\ \x06\x63\x59\x27\ \x00\x6c\ \x00\x6f\x00\x6f\x00\x70\x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x0d\ \x02\xc6\x5b\x87\ \x00\x70\ \x00\x6c\x00\x61\x00\x79\x00\x70\x00\x61\x00\x75\x00\x73\x00\x65\x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x0a\ \x0a\xcc\x85\x87\ \x00\x68\ \x00\x69\x00\x64\x00\x64\x00\x65\x00\x6e\x00\x2e\x00\x73\x00\x76\x00\x67\ \x00\x08\ \x0a\x61\x5a\xa7\ \x00\x69\ \x00\x63\x00\x6f\x00\x6e\x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x0c\ \x0d\xfc\x11\x13\ \x00\x74\ \x00\x72\x00\x61\x00\x6e\x00\x73\x00\x6c\x00\x61\x00\x74\x00\x69\x00\x6f\x00\x6e\x00\x73\ \x00\x0b\ \x0c\x46\xd2\x07\ \x00\x72\ \x00\x65\x00\x6d\x00\x6f\x00\x76\x00\x65\x00\x64\x00\x2e\x00\x73\x00\x76\x00\x67\ \x00\x0b\ \x08\x61\x82\x07\ \x00\x74\ \x00\x6f\x00\x73\x00\x74\x00\x61\x00\x72\x00\x74\x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x05\ \x00\x70\x75\x7d\ \x00\x69\ \x00\x74\x00\x2e\x00\x71\x00\x6d\ \x00\x05\ \x00\x6a\x85\x7d\ \x00\x64\ \x00\x65\x00\x2e\x00\x71\x00\x6d\ " qt_resource_struct_v1 = b"\ \x00\x00\x00\x00\x00\x02\x00\x00\x00\x0a\x00\x00\x00\x01\ \x00\x00\x00\x34\x00\x00\x00\x00\x00\x01\x00\x00\x02\xd8\ \x00\x00\x00\x66\x00\x00\x00\x00\x00\x01\x00\x00\x08\x90\ \x00\x00\x00\x18\x00\x00\x00\x00\x00\x01\x00\x00\x02\x74\ \x00\x00\x00\x50\x00\x00\x00\x00\x00\x01\x00\x00\x03\x90\ \x00\x00\x00\xf0\x00\x00\x00\x00\x00\x01\x00\x00\x14\xbb\ \x00\x00\x00\xa0\x00\x00\x00\x00\x00\x01\x00\x00\x0c\xa2\ \x00\x00\x00\x86\x00\x00\x00\x00\x00\x01\x00\x00\x0b\x4f\ \x00\x00\x00\xd4\x00\x00\x00\x00\x00\x01\x00\x00\x13\x92\ \x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ \x00\x00\x00\xb6\x00\x02\x00\x00\x00\x02\x00\x00\x00\x0b\ \x00\x00\x01\x1c\x00\x00\x00\x00\x00\x01\x00\x00\x18\x20\ \x00\x00\x01\x0c\x00\x00\x00\x00\x00\x01\x00\x00\x17\xfb\ " qt_resource_struct_v2 = b"\ \x00\x00\x00\x00\x00\x02\x00\x00\x00\x0a\x00\x00\x00\x01\ \x00\x00\x00\x00\x00\x00\x00\x00\ \x00\x00\x00\x34\x00\x00\x00\x00\x00\x01\x00\x00\x02\xd8\ \x00\x00\x01\x83\xce\x2c\x09\x0e\ \x00\x00\x00\x66\x00\x00\x00\x00\x00\x01\x00\x00\x08\x90\ \x00\x00\x01\x83\xce\x2c\x09\x0e\ \x00\x00\x00\x18\x00\x00\x00\x00\x00\x01\x00\x00\x02\x74\ \x00\x00\x01\x83\xce\x2c\x09\x0e\ \x00\x00\x00\x50\x00\x00\x00\x00\x00\x01\x00\x00\x03\x90\ \x00\x00\x01\x83\xce\x2c\x09\x0e\ \x00\x00\x00\xf0\x00\x00\x00\x00\x00\x01\x00\x00\x14\xbb\ \x00\x00\x01\x83\xce\x2c\x09\x0e\ \x00\x00\x00\xa0\x00\x00\x00\x00\x00\x01\x00\x00\x0c\xa2\ \x00\x00\x01\x83\xce\x2c\x09\x0e\ \x00\x00\x00\x86\x00\x00\x00\x00\x00\x01\x00\x00\x0b\x4f\ \x00\x00\x01\x83\xce\x2c\x09\x0e\ \x00\x00\x00\xd4\x00\x00\x00\x00\x00\x01\x00\x00\x13\x92\ \x00\x00\x01\x83\xce\x2c\x09\x0e\ \x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ \x00\x00\x01\x83\xce\x2c\x09\x0e\ \x00\x00\x00\xb6\x00\x02\x00\x00\x00\x02\x00\x00\x00\x0b\ \x00\x00\x00\x00\x00\x00\x00\x00\ \x00\x00\x01\x1c\x00\x00\x00\x00\x00\x01\x00\x00\x18\x20\ \x00\x00\x01\x83\xce\x2c\x09\x0e\ \x00\x00\x01\x0c\x00\x00\x00\x00\x00\x01\x00\x00\x17\xfb\ \x00\x00\x01\x83\xce\x2c\x09\x0e\ " qt_version = [int(v) for v in QtCore.qVersion().split('.')] if qt_version < [5, 8, 0]: rcc_version = 1 qt_resource_struct = qt_resource_struct_v1 else: rcc_version = 2 qt_resource_struct = qt_resource_struct_v2 def qInitResources(): QtCore.qRegisterResourceData(rcc_version, qt_resource_struct, qt_resource_name, qt_resource_data) def qCleanupResources(): QtCore.qUnregisterResourceData(rcc_version, qt_resource_struct, qt_resource_name, qt_resource_data) qInitResources() ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1665611335.9505882 agordejo-0.4.2/qtgui/resources/buildresources.sh0000644000175000017500000000042314321633110020565 0ustar00nilsnils#!/bin/bash #https://doc.qt.io/qt-5/resources.html #Resources are kept up-to-date upstream. They are not part of the make and build process. pyrcc5 -no-compress resources.qrc -o ../resources.py #put them into the gui directly. Engine does not need any translation or images. ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1665611335.9505882 agordejo-0.4.2/qtgui/resources/hidden.svg0000644000175000017500000000051714321633110017157 0ustar00nilsnils././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1665611335.9505882 agordejo-0.4.2/qtgui/resources/icon.png0000644000175000017500000000335414321633110016643 0ustar00nilsnils‰PNG  IHDR@@ªiqÞsBIT|dˆ pHYsMMˆoûtEXtSoftwarewww.inkscape.org›î<iIDATxœí›ÝK]ǿۮû–ÞthÈš¥äã_¢HâÒËMàE7aP¬„”D˜JBÈšu×ÕBðÔ…+Fôv£w¹/ÊL%º»®eÖu9/»;ó\Ä.û¬®{Þ´`Ÿ/ˆ;3¿ó=g?;çüΜ™€' f™ýžXd@yʰà œ|Xˆ—¦ibzz}}}"ìJêñãǸté, ·—. ( ¢Ñ¨+"E£Q(Š"ÄK€L&I’DXI’$d2!^B¸\.Ȳ,ŠH²,Ãår ñ@UU|ûöM„‘677¡iš/!VWWEØü–:……B"l¨´°° ćÀÖÖÂá°ˆ¶P)‰ÉÜÒéô¾€YɲŒT*Åíà `¿3@V²,ÃívsûpH¥Røúõ+SÙÎÎNtvv2•ÝØØ’ ¸§Â+++Låìv;¦¦¦`µZÑÒÒÂôeb±ZZZ˜êÏŠû `ÑÔÔ„††\¿~}_ëÎEQ˜Q[[‹[·nå¶oß¾ºº:jŸp8Ì ¸¤R)¦ðÁƒ8xð`nÛívcbb‚ÚGD&àÀ’ºººpîܹmûÏŸ?žž*/×\Òé4666ˆã¦¦¦ŠŸœœ„Óé$öûüù3Òé4qüNâ°ºº Ó4‰ã‡††ÐØØXôø±cÇpãÆ ª6Äb1ªøBqˆD"ı‡èË ¡¾¾žØ—70P…ê"èáÇD37—Ë…Gû†Ãa¨ªJ_(f4àìÙ³ðz½ÄÞ§OŸÆ™3gˆbeY†®ëÄÞ…bàt:‰–Áü~?µÿää$GÉ8I’¸23Ã0ðåË—’qš¦1 TÉd’è—åÍÌb±q¸rå Õ\_×uô÷÷ù›¦‰D"Aì](f4`yy™ªLLLàÇ{Ò–B1PU•zìîÝ»D¿Ôúú:FGG©¼C¡s&` ë:õxkk ƒƒƒ%ãðãÇ*ožLÀ€4êÙ³g˜-züÕ«Wxþü9µ¯$ITSè|10MëëëLú|¾OW]×qíÚ5&Ïd2I5%Ï€x<Î\áÊÊ îß¿¿mÿøø8>}úÄäÉ“ ˜ðÎ¿ÇÆÆÇsÛÉdããã\ž¬™€€¦iÜEÁÀÀ@nûêÕ«øùó'—'k& ^Õ4MÈàééiÌÌÌÀf³! rûI’]שCj‡CØ}ŸÏ'Äø• Y25‹Å‚d2I]ÑNâ]ÌÈ×ÚÚS9ê1 ‘HÀ0 ¦ÊöR†a0A À3ïÞk±´ €¦i¿åV8©B¡õ&jûù,­$IÚ["3À^H–e¢U¤|Q°X,\‹ùª¯¯ÇÌÌ ^¿~¦¦&!žñxœúÙA*kkkÜÀn·ãæÍ›$ ===èèè@4Ž{÷˜¯è²2 ƒ:ESà}²Ø—-„£ÅÅEªxbº®3?˜täÈ’§{¶[ƒA=z”©®÷ïßS „ÄTU¥+**àóùðñãG\¼x‘¸œ×ë…$I¡î²,ï »ÝN ½½‘H~¿•••Äå²r¹\¸sç–––ÐÝÝM\Ž6°Ùlÿ¹†/¦šš¼}û'Nœ nH1?~³³³ƒðx<%ãc± Úˆ#‰Ä®(³žî¤òz½e###»þ™L†*XZZ*zìÔ©S‡Ãðûý¨ªª"®œVn·;×-ºººŠÆíÖÖB(–ª««ðîÝ;477WÊ«††¼|ùÁ`µµµÛŽ/,,/“­¨ªŠºº:\¾|9·¯±±.\@UUEÓé¤ê{¼RUmmm˜ŸŸÇÓ§O±¼¼œ;æñx ª*ìv{I"n·½½½èííÝñ¸iš¹§µ²SÑbÿ‹íËúä¯6çoî«ÕŠC‡¡¿¿[›H3›Í›MØûU”þm.ûáÍ›7ÂÞÂøÓ577—û\ö/O[üàoü‚QN2üó/è1×ËIEND®B`‚././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1665611335.9505882 agordejo-0.4.2/qtgui/resources/loop.png0000644000175000017500000000237414321633110016665 0ustar00nilsnils‰PNG  IHDR&@lnsBIT|dˆ pHYs´´Ø9ˆ¿tEXtSoftwarewww.inkscape.org›î< tEXtTitleShape&bIDATX…µ—]L[uÆŸ÷œò=Öv cèÂZ¬q”B4ÄÌiÄhbb0ŽH0:ãâ…Þ¸hŒº¹ ½ñ3.LŒf?’uÙ„í‚Ì‘¹ÄŒ}@I-lŽ9–+PzÎã…€Ýé)=”î¹ëûÞ÷ý5ÿ¯ód@þÀìfM[(†j+P5NÎäÎŒïܺun=5%¤®.ÚŠË»@*hÁtœQž$ÿoߪ OPÏÌÕæ"Yj‘¢I… žP$·[³?æõJÔlЖ$©Ft\ñ;ÄJàSe.ú^,'/ˆmñ¹7\_›p| 8û®ÆÅyÞ¤F­–~ ÀGf S';›Ä£$š|nÇ[^ï³6èuÈoþ‘Ð.£ÑëÚðW•Û¹[€7M;û®„7­Ì\‚½Õ原 ÑŠüÔ72õÉ„š>·ã@ZLª9´Eýõõ1NU¹_$E¼í†÷ŒçÕ¹û\2Ik\˜À¢¾7:¿kMôLö|þ™Á@¨,>èõJ”àû&þíG¦ïMl O {s6…O_†¶­â«Ö =}Pm|0_ÓHØ…¢hYc*:Ú(gûGCõ«xŠéì †^\Ž-ø} º”ZKu¶’xv~Âq:…7”¯{¡ƒm¤ºT~Üh¢0,Ù9¯€3v(š~ÄwOÑ•øÁKa %â$´%̉(‹VÁNzG¾æìôxdamÝW´á+Õ.ç7+â.£‰äµ”`Ì›µº¤äfš +`‚`c•ËÙ½ûï~Ô*¼”«)Á2 WŸªp;ÇâƒYªÞL@5xI=Ömˆ¥qò§ÖÑÜÈN#ÔÐÐB‚Ü‚óÕž¢?o'A|èsÙ›kJK#· ÍÎú Àæ.â°Y1+»ò–þÀt ÿT¹íñ§øM!_ð•;sººhë ·h2ù+Wò"Ÿ›õ²üiÝFª÷†¿±g)õ˜h|CuFUb[*ÝÎ^cÎÅÑÐýª®´|Ä´9ù’¯Üy(m°áaæÌ«á/Ü(ÁNiW„ÃB„bº)ŠxI6¨GòåÒæsÙ›EÄô¦±¶ôU»ÇŠ×’ç¢9‘ZãZŒ—¥Åo“¬œËVw,¦>±`¬b[Á55­ø=,\òI¤Ò29fôOáTæ5¿Ä{ƒáB~`‡Õ´+ª²Ï[V8h='MùGg¶S×>  ÿ¿†t€×I°Ct9–ê ™Q°x‘ÿå°C4[öÄXÁd}½ÄÖ[ó_. ½EÂÕ%GIEND®B`‚././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1665611335.9505882 agordejo-0.4.2/qtgui/resources/loop.svg0000644000175000017500000000556014321633110016700 0ustar00nilsnils image/svg+xml Shape Shape ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1665611335.9505882 agordejo-0.4.2/qtgui/resources/playpause.png0000644000175000017500000000127314321633110017714 0ustar00nilsnils‰PNG  IHDR&@lnsBIT|dˆ pHYs´´Ø9ˆ¿tEXtSoftwarewww.inkscape.org›î< tEXtTitleShape&!IDATX…Í×=hqÇñßó¿ˆÛ&± "ñBMÒ*uºXpq‰‚ƒ®]ìà&¸XœÄÙ Ž"7qé àk ‹¹K§4—HA¹æÎ”6wCÏæOh//w—ø]’p/ùÜ“g i†õt{3¾03C;˜nX—Xñ9¥”SÙµjã„ÃÍ~÷æûõ¶¸¾u<( ù%ò^üÏó``ð, çËײy6\( é½*¥ Uëù¡i¤DÛçQ0½,Vê‹ÌÜqÜQÖvWà~ɰ—?olŒ \äµ À„£;cŸôuóÔ Aÿ:æuŽE¬PªØ¢‘êÀ']â÷ºaߊžÓª àƒŸië!3w{M zùá®^³_—Ëæxd"¯ÞŸžqå·¢|,Ö¬Óxöê÷gÉ’‹ÕR­~)TT}™p]ZÑ ûvh©Xðëù±fX¹íÍø`‡‚‚M,Ò‚N¬ М_ݰB íf Á×3©Ä»Ð4RýÂtÈgRÉj¨©>vŒßŒ8ÎìT* èmb Æ£¬š¸GDnd"¯na4ŸMÇŸGª‘êFßãj&_žÓª¬±kgRG}ÿjEÑËOŒC[sÃ@ûOŒ™ùA.\4F®ÖñÍ)5ùj(©ŒPq›N~zrbmˆž½¼£ͦrñA€ à‰ùm|îüäØÏacäþœõ£»–£:‡IEND®B`‚././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1665611335.9505882 agordejo-0.4.2/qtgui/resources/playpause.svg0000644000175000017500000000525114321633110017727 0ustar00nilsnils image/svg+xml Shape Shape ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1665611335.9505882 agordejo-0.4.2/qtgui/resources/power.svg0000644000175000017500000000116014321633110017053 0ustar00nilsnils././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1665611335.9505882 agordejo-0.4.2/qtgui/resources/removed.svg0000644000175000017500000000044514321633110017365 0ustar00nilsnilsShape + Shape././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1665611335.9505882 agordejo-0.4.2/qtgui/resources/resources.qrc0000644000175000017500000000061114321633110017717 0ustar00nilsnils icon.png hidden.svg power.svg running.svg stopped.svg removed.svg translations/de.qm translations/it.qm loop.png playpause.png tostart.png ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1665611335.9505882 agordejo-0.4.2/qtgui/resources/running.svg0000644000175000017500000000026414321633110017403 0ustar00nilsnils././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1665611335.9505882 agordejo-0.4.2/qtgui/resources/stopped.svg0000644000175000017500000000014014321633110017372 0ustar00nilsnils././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1665611335.9505882 agordejo-0.4.2/qtgui/resources/tostart.png0000644000175000017500000000147414321633110017414 0ustar00nilsnils‰PNG  IHDR&@lnsBIT|dˆ pHYsµµ ~j[tEXtSoftwarewww.inkscape.org›î< tEXtTitleShape&¢IDATX…½—ÍKTQ‡Ÿ÷^#ìëŽJ¡`Ú(Ö˜‰²BŠra›HjY—ÕNÿ€6.Z´‰ ´I2"«…‹JjáÊB‡ŒJüÊB™s‹šJï}[è„DÌ9:sý-gžÃ}Îï=œ;#©‰´’#®UÔÇ·ÌæbLÿrD·2÷zLìÀ€•V§¯9&PD$©ÔdºÝç™;LìèŒ_ZVí÷ r©(Ÿ‡æÊÀ€•UùWQ:løáñ¯»ƒ…ð!° ±Ñ¿4XðïÇmø‘I¿E4ì¼ìgÆQ®6K;×A{©ùË¢úx¥¸±åßb&vlL7fœ¯7Q½ð¿ï ÖØŠ¥ÞL}¯È¸þsäÿRP€ÆL;ÿ7£“óûÃ…> *——Ø«±oÛ3®8jç&ÒmÒ l2±k{=1Ÿ ú€j«Ê  °º×tÆRé¶[)@ÑÓ¶R«SUIM¦;€,Æ‘O¬G92;»95åßåL”BÙX‰ŒÍUJÆ}€r j¡lŒ£ CÄu‡ÖS "x%*F1ÇaHƒ áåzý}® ”¬+û¨Å?©p?j¡l¬G™,/ÿžØåEè€Už1ÑD<Öœ~D£´”5þDM¬×A¦m×òÈùÿ"o1€½5%#Aà^X-žç°l:¯ëbÝÖOÅw•n>QëuE›€‘ŠÔÕɯD­wQE¯‰¯— /n†¦ vÁ&ã%×U¤H›X›¦ zó'ã^¨Îaà­‰55]ðWÒ¾ÚmïÝ Ò<µáW4íG*P¿Óû27íµ tÙÉyý¡:‡€w‘Š47Ëb¢6Ö ´¿MürÓ,7ù¯‹DMì–ˆžÒÏ&6Û´À?Ý–ñ%JÓ—ÃIEND®B`‚././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1665611335.9505882 agordejo-0.4.2/qtgui/resources/tostart.svg0000644000175000017500000000507014321633110017423 0ustar00nilsnils image/svg+xml Shape Shape ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1665611335.9505882 agordejo-0.4.2/qtgui/resources/translations/config.pro0000644000175000017500000000064414321633110021714 0ustar00nilsnilsSOURCES = ../../addclientprompt.py ../../changelog.py ../../descriptiontextwidget.py ../../mainwindow.py ../../projectname.py ../../sessiontreecontroller.py ../../opensessioncontroller.py ../../systemtray.py ../../waitdialog.py ../../usermanual.py ../../designer/mainwindow.py ../../designer/newsession.py ../../designer/projectname.py ../../designer/settings.py ../../designer/usermanual.py TRANSLATIONS = de.ts it.ts ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1665611335.9505882 agordejo-0.4.2/qtgui/resources/translations/de.qm0000644000175000017500000003700014321633110020650 0ustar00nilsnils<¸dÊÍ!¿`¡½Ý§de_DEBXA È’TÔ"áTÇvsþ ‡›2VÖ%Ö2å{Kö52¸H5 JH5#6H50jh¨ sh¨0–ŒÝ˜Å)˜Å¡1«` Ž•Ò)Fˆa?KO3±C“1>G–Ä èH6ÿµH7ÙH7ýH7!H7ERx¼#XÀ`4°j+~#Ó€€ƒ(¨‡Ôí­A1·åx©!ïC§~ l“=q˜?4Q™@d§ž£E!–Ιe×qî‹á—^&n íg0Hþ&ïLdmƒn'çÚ6î'vå‚u¿"\ÜP˜½b A&Wn5³g´þ±«©«Î«t¯ƒW!qÿoÙÃ,ªÿ8[|í?/.%±F¤î¬G/מTÄé…lÇ<2÷‚®Ž0ÂŒFÅ}ŒH5´Œ¬53ŒàDl¦zñ1ª‹Ã#bæ=®/N%ef9uÕyùÔ${‡”ÑŽ™î-_ÿ nZy”uѺ{€³$Eq‰5PKž9 ”LƒQÝN ·1v”2·Ä›â æÛµó B,äv Mgþ hã> |Ït¡ ŸÊÓ] Ïç- èMƒ"¦ /Åó.y e¹5 eÊE0 udi ´¡î6Ó º¯4â ÂÊ1/š ×ÛÅ8 á•ä™ ÷{B u”G“ €WeÒ —{­- ü¦Ž+ |ˆÿ ¤"®7j »s P »sü Ñ^/Ñ üEÎ(¢ ùu#” ¦ó “ DZó DZ DZI lå.® æÅ¤ia>ãh°~N4UEhµ],,Ã` œ96€SóÅ%Fk„S*{&.ú,w•·oÙ·oÙ3Ii9}8Programm soll beendet werden About to quit AskBeforeQuitxProgramm soll beendet werden, aber Session {} ist noch offen'About to quit but session {} still open AskBeforeQuit(Änderungen VerwerfenDiscard Changes AskBeforeQuit,Möchten Sie speichern?Do you want to save? AskBeforeQuitNicht Beenden Don't Quit AskBeforeQuitSpeichernSave AskBeforeQuit $PATH$PATHDialogEinn absoluter Pfad pro Zeile (z.B. /home/user/audio-bin). Schrägstrich am Ende spielt keine Rolle. Keine Wildcards wie * oder ..xAdd one absolute path to a directory (e.g. /home/user/audio-bin) per line. No wildcards. Trailing slashes/ don't matter.DialogBlacklist - Ausführbare Dateinamen (keine ganzen Pfade) hinzufügen um diese aus dem Programmstarter zu entfernen. Ein Programm pro Zeile.dBlacklist - Exclude executable names (not paths) from the program launcher. One executable per line.DialogpNur für fortgeschrittene Benutzer! Hier absolute Pfade hinzufügen, in denen sich ausführbare Dateien befinden. Diese Einstellungen gelten nur für Agordejo/NSM. Änderungen benötigen einen kompletten Programmneustart. Um Programme zum Schnellstarter hinzuzufügen bitte den Tab &quot;Programmstarter&quot; benutzen.ÓFor advanced users only! Add executable paths to the environment, just for Agordejo and NSM. Changes need a program restart afterwards. If you want your programs in the application launcher use the launcher tab.DialogProgrammstarterLauncherDialogEinstellungenSettingsDialogWhitelist - Ausführbare Dateinamen (keine ganzen Pfade) hinzufügen um diese im Programmstarter anzuzeigen. Ein Programm pro Zeile.^Whitelist - Add executable names (not paths) to the program launcher. One executable per line.DialogBeschreibung DescriptionLauncherNameNameLauncherPfadPathLauncherêUm hier zu schreiben doppelklicken Sie auf dieses Feld (startet nsm-data) Für Notizen, TODO, Referenzen, Quellen etc.cDouble click to add the client nsm-data to write here. Use it for notes, TODO, references etc…LoadedSessionDescription min min MainWindow" time-placeholder time-placeholder MainWindowÿÿÿÿ/home/usr/NSM Sessions MainWindowÿÿÿÿA MainWindowÜberAbout MainWindow8Programm hinzufügen (Prompt)Add Client (Prompt) MainWindowAgordejoAgordejo MainWindowÿÿÿÿAlt+O MainWindowÿÿÿÿAlt+R MainWindowÿÿÿÿAlt+S MainWindowÿÿÿÿAlt+T MainWindowÿÿÿÿAlt+X MainWindowÿÿÿÿ ClientNameId MainWindowfSchließen ohne zu Speichern (&quot;Abbrechen&quot;)Close without Save ("Abort") MainWindowSteuerungControl MainWindowKopieren Copy Selected MainWindowÿÿÿÿCtrl+Q MainWindowÿÿÿÿCtrl+S MainWindowÿÿÿÿ Ctrl+Shift+Q MainWindowÿÿÿÿ Ctrl+Shift+S MainWindowÿÿÿÿ Ctrl+Shift+W MainWindowÿÿÿÿCtrl+W MainWindowLöschenDelete Selected MainWindowDDoppelklick um Programm zu startenDouble-click to load program MainWindowÿÿÿÿF2 MainWindowVolle Ansicht Full View MainWindow<Globaler Zugriff aufs PlaybackGlobal Playback Controls MainWindow,Verstecke alle ClientsHide All Clients MainWindow&Minimieren zum TrayHide in System Tray MainWindowpIch verstehe, dass ich dieses Problem selbst lösen muss!@I understand that I will need to resolve this problem on my own! MainWindow.Momentan in der SessionIn current session MainWindowInformation Information MainWindowJACKJACK MainWindowÿÿÿÿ Last Updated MainWindow Lade Ausgewählte Load Selected MainWindowHandbuchManual MainWindowÿÿÿÿNSM Server Mode MainWindowÿÿÿÿNSM Url MainWindowNeuNew MainWindow$News und ChangelogNews and Changelog MainWindowPlayPause PlayPause MainWindowArbeitet Processing MainWindowÿÿÿÿProgram Database MainWindow Schnellstart Neu Quick New MainWindowBeendenQuit MainWindow8Programmdatenbank generierenRebuild Program Database MainWindowEntfernenRemove MainWindowUmbenennenRename MainWindowNamen ändernRename Selected MainWindowFortführenResume MainWindow RewindRewind MainWindowSpeichernSave MainWindowTSpeichern und mit anderem Namen neu öffnen#Save and Clone under different name MainWindow.Speichern und SchließenSave and Close MainWindow"Seperat speichernSave separately MainWindowÿÿÿÿ+Self-started, connected to, environment var MainWindowSession Notizen Session Notes MainWindowÿÿÿÿ Session Root MainWindowÿÿÿÿ SessionName MainWindowEinstellungenSettings MainWindow$Zeige alle ClientsShow All Clients MainWindow>Sessionfenster anders aufteilen Split Session View the other way MainWindowStopStop MainWindow.Sichtbarkeit umschaltenToggle Visible MainWindowBaumansicht Tree View MainWindowÿÿÿÿosc.upd ip port MainWindowÿÿÿÿversion and running MainWindowERROR! Kopierte Daten der Session unterscheiden sich von der Ursprünlinglichen! Bitte überprüfen Sie die Integrität der kopierten Daten!SERROR! Copied session data is different from source session. Please check you data!NOOPEngineStrings‚Überprüfe Integrität der Dateien. Bitte haben Sie etwas Geduld...2Veryfying file-integrity. This may take a while...NOOPEngineStrings„Umbenennen und Session-Notitzen aktivieren (mit Client 'nsm-data'):Client Renaming and Session Notes (adds client 'nsm-data') NewSessionÿÿÿÿDialog NewSession*Name für neue SessionNew Session Name NewSessionhSpeichere JACK-Verbindungen (mit Client 'jackpatch')0Save JACK Connections (adds clients 'jackpatch') NewSessionÄnderungenChanges OpenSessionIDID OpenSession LabelLabel OpenSessionNameName OpenSession StatusStatus OpenSessionSichtbarkeitVisible OpenSession cleanclean OpenSessionungespeichert not saved OpenSession²Wählen Sie einen Namen für die Session. Benutzen Sie / um Unterverzeichnisse einzurichten/Choose a project name. Use / for subdirectories ProjectNameFehlermeldung Error Message ProjectNameÿÿÿÿForm ProjectNamepDer Zugriff auf das Elternverzeichnis ist nicht erlaubt.'Moving to parent directory not allowed.ProjectNameWidgetDDer Name ist bereits in Benutzung.Name is already in use.ProjectNameWidgetDName muss ein relativer Pfad sein.Name must be a relative path.ProjectNameWidget4Name darf nicht leer sein.Name must not be empty.ProjectNameWidgetjSie haben keine Schreibrechte für dieses Verzeichnis.+Writing in this directory is not permitted.ProjectNameWidget¸Befehl nicht gefunden oder nicht akzeptiert!&lt;br&gt;Parameter, --schalter und relative Pfade sind nicht erlaubt.&lt;br&gt;Benutzen Sie stattdessen ein Programm wie nsm-proxy oder schreiben sie selbst ein Starterscript.”Command not found or not accepted!
Parameters, --switches and relative paths are not allowed.
Use nsm-proxy or write a starter-script instead. PromptWidget¾Keine Programm-Datenbank gefunden. Bitte benutzen Sie das Steuerungsmenü um diese zu erstellen.>No program database found. Please update through Control menu. PromptWidget„Schreiben Sie den Namen einer ausführbaren Datei von ihrem System.6Type in the name of an executable file on your system. PromptWidget>Session {} soll gelöscht werdenAbout to delete Session {} SessionTreešAlle Dateien aus diesem Projektverzeichnis werden unwiederbringlich gelöscht.@All files in the project directory will be irreversibly deleted. SessionTreeClientsClients SessionTree Session kopieren Copy Session SessionTree$Kopiere {} nach {}Copying {} to {} SessionTreeSession löschenDelete Session SessionTreeLöschen!Delete! SessionTree Session behalten Keep Session SessionTree$Letzte Speicherung Last Save SessionTreeNameName SessionTreePfadPath SessionTree Session umbennenRename Session SessionTree GrößeSize SessionTreeSymlinksSymlinks SessionTreeChangelog ChangelogTemplateChangelogNDen Changelog gibt es nur auf Englisch.+The Changelog is only available in English.TemplateChangelog ZurückBackTemplateUserManualÿÿÿÿFormTemplateUserManualStartseiteHomeTemplateUserManual Benutzerhandbuch User ManualTemplateUserManual8Programm hinzufügen (Prompt)Add Client (Prompt)TrayIcon^Schließen ohne zu Speichern && Agordejo beenden%Close without Saving && Quit AgordejoTrayIcon0Verstecke/Zeige AgordejoHide/Show AgordejoTrayIconBeenden Quit TrayIcon<Abbrechen und Agordejo BeendenSave && Quit AgordejoTrayIcon.Sichtbarkeit umschaltenToggle Client VisibilityTrayIconvBitte bestätigen Sie durch einen Klick auf den Knopf unten.8Please confirm with a click on the button at the bottom. WaitDialog&Agordejo ist bereitAgordejo ready mainWindow\Es wurde versucht eine weitere GUI zu starten.Another GUI tried to launch. mainWindow~Programmdatenbank wird aktualisiert. Vielen Dank für ihre Geduld. Wenn der Prozess eingeforen ist erzwingen Sie bitte die Beendung des Programms und starten es erneut. Ihre Daten sind sicher.vUpdating Program Database. Thank you for your patience. If progress freezes please kill and restart the whole program. mainWindowˆ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1665611335.9505882 agordejo-0.4.2/qtgui/resources/translations/de.ts0000644000175000017500000007747214321633110020702 0ustar00nilsnils AskBeforeQuit About to quit but session {} still open Programm soll beendet werden, aber Session {} ist noch offen Do you want to save? Möchten Sie speichern? About to quit Programm soll beendet werden Don't Quit Nicht Beenden Save Speichern Discard Changes Änderungen Verwerfen Dialog Settings Einstellungen Whitelist - Add executable names (not paths) to the program launcher. One executable per line. Whitelist - Ausführbare Dateinamen (keine ganzen Pfade) hinzufügen um diese im Programmstarter anzuzeigen. Ein Programm pro Zeile. Blacklist - Exclude executable names (not paths) from the program launcher. One executable per line. Blacklist - Ausführbare Dateinamen (keine ganzen Pfade) hinzufügen um diese aus dem Programmstarter zu entfernen. Ein Programm pro Zeile. Launcher Programmstarter For advanced users only! Add executable paths to the environment, just for Agordejo and NSM. Changes need a program restart afterwards. If you want your programs in the application launcher use the launcher tab. Nur für fortgeschrittene Benutzer! Hier absolute Pfade hinzufügen, in denen sich ausführbare Dateien befinden. Diese Einstellungen gelten nur für Agordejo/NSM. Änderungen benötigen einen kompletten Programmneustart. Um Programme zum Schnellstarter hinzuzufügen bitte den Tab &quot;Programmstarter&quot; benutzen. Add one absolute path to a directory (e.g. /home/user/audio-bin) per line. No wildcards. Trailing slashes/ don't matter. Einn absoluter Pfad pro Zeile (z.B. /home/user/audio-bin). Schrägstrich am Ende spielt keine Rolle. Keine Wildcards wie * oder .. $PATH $PATH Launcher Name Name Description Beschreibung Path Pfad LoadedSessionDescription Double click to add the client nsm-data to write here. Use it for notes, TODO, references etc… Um hier zu schreiben doppelklicken Sie auf dieses Feld (startet nsm-data) Für Notizen, TODO, Referenzen, Quellen etc. MainWindow Agordejo Agordejo Start New Session Neue Session starten Save Speichern Save and Close Speichern und Schließen Session Notes Session Notizen Quick View Schnelle Ansicht New Neu Load Selected Lade Ausgewählte Tree View Baumansicht Double-click to load program Doppelklick um Programm zu starten In current session Momentan in der Session Full View Volle Ansicht JACK JACK version and running NSM Server Mode Self-started, connected to, environment var NSM Url osc.upd ip port Session Root /home/usr/NSM Sessions Program Database Last Updated Information Information Processing Arbeitet Control Steuerung SessionName ClientNameId Quit Beenden Ctrl+Shift+Q About Über Manual Handbuch Hide in System Tray Minimieren zum Tray Ctrl+Q Add Client (Prompt) Programm hinzufügen (Prompt) A Ctrl+S Save As Speichern unter Ctrl+Shift+S Ctrl+W Abort Abbrechen Ctrl+Shift+W Stop Stop Alt+O Resume Fortführen Alt+R Save separately Seperat speichern Alt+S Remove Entfernen Alt+X Toggle Visible Sichtbarkeit umschalten Alt+T Show All Clients Zeige alle Clients Hide All Clients Verstecke alle Clients Rebuild Program Database Programmdatenbank generieren Rename Umbenennen F2 Settings Einstellungen Save and Clone under different name Speichern und mit anderem Namen neu öffnen Close without Save ("Abort") Schließen ohne zu Speichern (&quot;Abbrechen&quot;) Global Playback Controls Globaler Zugriff aufs Playback PlayPause PlayPause Rewind Rewind time-placeholder time-placeholder min min Quick New Schnellstart Neu Rename Selected Namen ändern Copy Selected Kopieren Delete Selected Löschen Remove Lock Lock aufheben I understand that I will need to resolve this problem on my own! Ich verstehe, dass ich dieses Problem selbst lösen muss! News and Changelog News und Changelog Split Session View the other way Sessionfenster anders aufteilen NOOPEngineStrings ERROR! Copied session data is different from source session. Please check you data! ERROR! Kopierte Daten der Session unterscheiden sich von der Ursprünlinglichen! Bitte überprüfen Sie die Integrität der kopierten Daten! Veryfying file-integrity. This may take a while... Überprüfe Integrität der Dateien. Bitte haben Sie etwas Geduld... NewSession Dialog New Session Name Name für neue Session Save JACK Connections (adds clients 'jackpatch') Speichere JACK-Verbindungen (mit Client 'jackpatch') Client Renaming and Session Notes (adds client 'nsm-data') Umbenennen und Session-Notitzen aktivieren (mit Client 'nsm-data') OpenSession not saved ungespeichert clean clean (command not found) (Befehl nicht gefunden) Name Name Label Label Status Status Visible Sichtbarkeit Changes Änderungen ID ID ? ✖ ProjectName Form Error Message Fehlermeldung Choose a project name. Use / for subdirectories Wählen Sie einen Namen für die Session. Benutzen Sie / um Unterverzeichnisse einzurichten ProjectNameWidget Name must not be empty. Name darf nicht leer sein. Name must be a relative path. Name muss ein relativer Pfad sein. Moving to parent directory not allowed. Der Zugriff auf das Elternverzeichnis ist nicht erlaubt. Writing in this directory is not permitted. Sie haben keine Schreibrechte für dieses Verzeichnis. Name is already in use. Der Name ist bereits in Benutzung. PromptWidget Type in the name of an executable file on your system. Schreiben Sie den Namen einer ausführbaren Datei von ihrem System. No program database found. Please update through Control menu. Keine Programm-Datenbank gefunden. Bitte benutzen Sie das Steuerungsmenü um diese zu erstellen. Command not accepted!<br>Parameters, --switches and relative paths are not allowed.<br>Use nsm-proxy or write a starter-script instead. Befehl nicht akzeptiert!&lt;br&gt;Parameter, --schalter und relative Pfade sind nicht erlaubt.&lt;br&gt;Benutzen Sie stattdessen ein Programm wie nsm-proxy oder schreiben sie selbst ein Starterscript. Command not found or not accepted!<br>Parameters, --switches and relative paths are not allowed.<br>Use nsm-proxy or write a starter-script instead. Befehl nicht gefunden oder nicht akzeptiert!&lt;br&gt;Parameter, --schalter und relative Pfade sind nicht erlaubt.&lt;br&gt;Benutzen Sie stattdessen ein Programm wie nsm-proxy oder schreiben sie selbst ein Starterscript. SessionTree Name Name Last Save Letzte Speicherung Clients Clients Size Größe Symlinks Symlinks Path Pfad About to delete Session {} Session {} soll gelöscht werden All files in the project directory will be irreversibly deleted. Alle Dateien aus diesem Projektverzeichnis werden unwiederbringlich gelöscht. Keep Session Session behalten Delete! Löschen! Copy Session Session kopieren Force Lock Removal Lockdatei Aufhebung erzwingen Rename Session Session umbennen Delete Session Session löschen Copying {} to {} Kopiere {} nach {} TemplateChangelog Changelog Changelog The Changelog is only available in English. Den Changelog gibt es nur auf Englisch. TemplateUserManual User Manual Benutzerhandbuch Form Home Startseite Back Zurück TrayIcon Hide/Show Agordejo Verstecke/Zeige Agordejo Save && Quit Agordejo Abbrechen und Agordejo Beenden Quit Beenden Add Client (Prompt) Programm hinzufügen (Prompt) Close without Saving && Quit Agordejo Schließen ohne zu Speichern && Agordejo beenden Toggle Client Visibility Sichtbarkeit umschalten WaitDialog Please confirm with a click on the button at the bottom. Bitte bestätigen Sie durch einen Klick auf den Knopf unten. mainWindow Agordejo ready Agordejo ist bereit Another GUI tried to launch. Es wurde versucht eine weitere GUI zu starten. Updating Program Database. Thank you for your patience. Programmdatenbank wird aktualisiert. Vielen Dank für ihre Geduld. Updating Program Database. Thank you for your patience. If progress freezes please kill and restart the whole program. Programmdatenbank wird aktualisiert. Vielen Dank für ihre Geduld. Wenn der Prozess eingeforen ist erzwingen Sie bitte die Beendung des Programms und starten es erneut. Ihre Daten sind sicher. ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1665611335.9505882 agordejo-0.4.2/qtgui/resources/translations/it.qm0000644000175000017500000000004114321633110020667 0ustar00nilsnils<¸dÊÍ!¿`¡½Ý§it_ITˆ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1665611335.9505882 agordejo-0.4.2/qtgui/resources/translations/it.ts0000644000175000017500000007031614321633110020714 0ustar00nilsnils AskBeforeQuit About to quit but session {} still open Do you want to save? About to quit Don't Quit Save Discard Changes Dialog Settings Whitelist - Add executable names (not paths) to the program launcher. One executable per line. Blacklist - Exclude executable names (not paths) from the program launcher. One executable per line. Launcher For advanced users only! Add executable paths to the environment, just for Agordejo and NSM. Changes need a program restart afterwards. If you want your programs in the application launcher use the launcher tab. Add one absolute path to a directory (e.g. /home/user/audio-bin) per line. No wildcards. Trailing slashes/ don't matter. $PATH Launcher Name Description Path LoadedSessionDescription Double click to add the client nsm-data to write here. Use it for notes, TODO, references etc… MainWindow Agordejo Save Save and Close Session Notes New Load Selected Tree View Double-click to load program In current session Full View JACK version and running NSM Server Mode Self-started, connected to, environment var NSM Url osc.upd ip port Session Root /home/usr/NSM Sessions Program Database Last Updated Information Processing Control SessionName ClientNameId Quit Ctrl+Shift+Q About Hide in System Tray Ctrl+Q Add Client (Prompt) A Ctrl+S Save and Clone under different name Ctrl+Shift+S Ctrl+W Close without Save ("Abort") Ctrl+Shift+W Stop Alt+O Resume Alt+R Save separately Alt+S Remove Alt+X Toggle Visible Alt+T Show All Clients Hide All Clients Rebuild Program Database Rename F2 Settings Manual Global Playback Controls PlayPause Rewind time-placeholder min Quick New Rename Selected Copy Selected Delete Selected I understand that I will need to resolve this problem on my own! News and Changelog Split Session View the other way NOOPEngineStrings ERROR! Copied session data is different from source session. Please check you data! Veryfying file-integrity. This may take a while... NewSession Dialog New Session Name Save JACK Connections (adds clients 'jackpatch') Client Renaming and Session Notes (adds client 'nsm-data') OpenSession not saved clean Name Label Status Visible Changes ID ProjectName Form Error Message Choose a project name. Use / for subdirectories ProjectNameWidget Name must not be empty. Name must be a relative path. Moving to parent directory not allowed. Writing in this directory is not permitted. Name is already in use. PromptWidget Type in the name of an executable file on your system. No program database found. Please update through Control menu. Command not found or not accepted!<br>Parameters, --switches and relative paths are not allowed.<br>Use nsm-proxy or write a starter-script instead. SessionTree Name Last Save Clients Size Symlinks Path About to delete Session {} All files in the project directory will be irreversibly deleted. Keep Session Delete! Copy Session Rename Session Delete Session Copying {} to {} TemplateChangelog Changelog The Changelog is only available in English. TemplateUserManual User Manual Form Home Back TrayIcon Hide/Show Agordejo Add Client (Prompt) Save && Quit Agordejo Close without Saving && Quit Agordejo Quit Toggle Client Visibility WaitDialog Please confirm with a click on the button at the bottom. mainWindow Agordejo ready Another GUI tried to launch. Updating Program Database. Thank you for your patience. If progress freezes please kill and restart the whole program. ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1665611335.9505882 agordejo-0.4.2/qtgui/resources/translations/it2.ts0000644000175000017500000006315214321633110020776 0ustar00nilsnils AskBeforeQuit About to quit but session {} still open Sto per chiudere ma la sessione {} è ancora aperta Do you want to save? Vuoi salvare la sessione? About to quit Chiusura Don't Quit Non Uscire Save Salva Discard Changes Scarta Modifiche Dialog Settings Impostazioni Whitelist - Add executable names (not paths) to the program launcher. One executable per line. Lista Bianca - Aggiungi eseguibili (non percorsi) all'avviatore. Un nome per ogni linea. Blacklist - Exclude executable names (not paths) from the program launcher. One executable per line. Lista Nera - Escludi eseguibili (non percorsi) dall'avviatore. Un nome per ogni linea. Launcher Avviatore For advanced users only! Add executable paths to the environment, just for Agordejo and NSM. Changes need a program restart afterwards. If you want your programs in the application launcher use the launcher tab. Solamente per utenti avanzati! Aggiungi percorsi degli eseguibili all'ambiente, solo per Agordejo e NSM. Per rendere effettive le modifiche sarà necessario il riavvio dell'applicazione. Se vuoi aggiungere un programma all'avviatore, usa la relativa scheda. Add one absolute path to a directory (e.g. /home/user/audio-bin) per line. No wildcards. Trailing slashes/ don't matter. Aggiungi un percorso assoluto di una directory (es.: /home/user/audio-bin) per linea. No caratteri jolly. Le barre finali non sono richieste. $PATH Launcher Name Nome Description Descrizione Path Percorso LoadedSessionDescription Double click to add the client nsm-data to write here. Use it for notes, TODO, references etc… Doppio click per aggiugere il client nsm-data da scrivere qui. Usalo per note, lista TODO, riferimenti ecc… MainWindow Agordejo Start New Session Avvia Nuova Sessione Session Name Goes Here Inserire Qui Nome Sessione Save Salva Save and Close Salva e Chiudi Session Notes Note Sessione Quick View Vista Rapida New Nuova Load Selected Carica Selezionata Tree View Vista Albero Double-click to load program Doppio click per caricare un programma In current session Nella sessione corrente Full View Vista Completa JACK version and running versione ed esecuzione NSM Server Mode Modalità Server NSM Self-started, connected to, environment var Auto-avvio, connesso a, var ambiente NSM Url osc.upd ip port Session Root Root Sessione /home/usr/NSM Sessions Program Database Database Programma Last Updated Ultimo Aggiornamento Information Informazioni Processing Elaborazione Control Controllo SessionName ClientNameId Quit Esci Ctrl+Shift+Q Ctrl+Maiusc+Q About Informazioni Hide in System Tray Nascondi nell'Area di Notifica Ctrl+Q Add Client (Prompt) Aggiungi Client (Prompt) A Ctrl+S Save and Clone under different name Salva e Clona con un nome diverso Ctrl+Shift+S Ctrl+Maiusc+S Ctrl+W Close without Save ("Abort") Chiudi senza Salvare ("Abort") Ctrl+Shift+W Ctrl+Maiusc+W Stop Ferma Alt+O Resume Riprendi Alt+R Save separately Salva separatamente Alt+S Remove Rimuovi Alt+X Toggle Visible Alterna Visibilità Alt+T Show All Clients Mostra Tutti Client Hide All Clients Nascondi Tutti Client Rebuild Program Database Ricostruisci Database Programma Rename Rinomina F2 Settings Impostazioni Manual Manuale NewSession Dialog Finestra di dialogo New Session Name Nome Nuova Sessione Save JACK Connections (adds clients 'jackpatch') Salva Connessioni JACK (aggiunge client 'jackpatch') Client Renaming and Session Notes (adds client 'nsm-data') Rinomina Client e Note Sessione (aggiunge client 'nsm-data') OpenSession not saved non salvato clean pulisci Name Nome Label Etichetta Status Stato Visible VIsibile Changes Modifiche ID ProjectName Form Error Message Messaggio Errore Choose a project name. Use / for subdirectories Scegli un nome per il progetto. Usa / per le sottodirectory ProjectNameWidget Name must not be empty. Il nome non può rimanere vuoto. Name must be a relative path. Il nome dev'essere un percorso relativo. Moving to parent directory not allowed. Lo spostamento alla directory superiore non è consentito. Writing in this directory is not permitted. Non è concesso scrivere in questa directory. Name is already in use. Nome già in uso. PromptWidget Type in the name of an executable file on your system. Digita il nome di un file eseguibile nel tuo sistema. No program database found. Please update through Control menu. Database del programma non trovato. Si prega di aggiornarlo tramite menu Controllo. Command not found or not accepted!<br>Parameters, --switches and relative paths are not allowed.<br>Use nsm-proxy or write a starter-script instead. Comando non trovato o non accettato!<br>Parametri, --argomenti e percorsi relativi non sono ammessi. <br>Usa nsm-proxy oppure crea uno script di avvio. SessionTree Name Nome Last Save Ultimo Salvataggio Clients Client Size Dimensione Symlinks Collegamenti Simbolici Path Percorso About to delete Session {} Eliminazione Sessione {} All files in the project directory will be irreversibly deleted. Tutti i file nella directory del progetto verranno eliminati irreversibilmente. Keep Session Mantieni Sessione Delete! Elimina! Copy Session Copia Sessione Force Lock Removal Rimozione Forzata Blocco Rename Session Rinomina Sessione Delete Session Elimina Sessione TemplateUserManual User Manual Manuale Utente Form Home Principale Back Indietro TrayIcon Hide/Show Agordejo Nascondi/Mostra Agordejo Add Client (Prompt) Aggiungi Client (Prompt) Save && Quit Agordejo Salva e Chiudi Agordejo Close without Saving && Quit Agordejo Chiudi senza Salvare e Chiudi Agordejo Quit Esci mainWindow Agordejo ready Agordejo pronto Another GUI tried to launch. Un'altra GUI ha tentato di avviarsi. Updating Program Database. Thank you for your patience. Aggiornamento Database in corso. Si prega di attendere. ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1665611335.9505882 agordejo-0.4.2/qtgui/resources/translations/update.sh0000644000175000017500000000034214321633110021536 0ustar00nilsnils#!/bin/sh set -e pylupdate5 config.pro echo "linguist-qt5 de.ts" echo "linguist-qt5 it.ts" echo "Release from inside qt-linguist and then cd .. && sh buildresources.sh. Also read the manual, which has a translation chapter" ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1665611335.9539216 agordejo-0.4.2/qtgui/sessiontreecontroller.py0000644000175000017500000004302614321633110020214 0ustar00nilsnils#! /usr/bin/env python3 # -*- coding: utf-8 -*- """ Copyright 2022, Nils Hilbricht, Germany ( https://www.hilbricht.net ) This file is part of the Laborejo Software Suite ( https://www.laborejo.org ). This application is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . """ import logging; logger = logging.getLogger(__name__); logger.info("import") #Standard Library #Third Party from PyQt5 import QtCore, QtGui, QtWidgets #Engine import engine.api as api #Qt from .helper import sizeof_fmt from .projectname import ProjectNameWidget from .projectname import NewSessionDialog from .waitdialog import WaitDialog class DirectoryItem(QtWidgets.QTreeWidgetItem): """A plain directory with no session content""" def __lt__(self, other): """Treeview uses only less than. less equal, both greater and equal are not used. We always want to be on top, no matter what column or sort order """ if type(other) is SessionItem: if self.treeWidget().header().sortIndicatorOrder(): #descending? return False else: return True else: return QtWidgets.QTreeWidgetItem.__lt__(self, other) class SessionItem(QtWidgets.QTreeWidgetItem): """Subclass to enable sorting of size by actual value, not by human readable display. entry["nsmSessionName"] = projectName entry["name"] = os.path.basename(projectName) entry["lastSavedDate"] = "2016-05-21 16:36" entry["fullPath"] = actual path entry["sizeInBytes"] = 623623 entry["numberOfClients"] = 3 entry["hasSymlinks"] = True entry["parents"] = [] Also: entry["locked"] = True """ allItems = {} #nsmSessionName : SessionItem def __init__(self, sessionDict): SessionItem.allItems[sessionDict["nsmSessionName"]] = self self.sessionDict = sessionDict symlinks = "Yes" if sessionDict["hasSymlinks"] else "No" #TODO: Translate parameterList = [sessionDict["name"], sessionDict["lastSavedDate"], str(sessionDict["numberOfClients"]), sizeof_fmt(sessionDict["sizeInBytes"]), symlinks, sessionDict["fullPath"], ] super().__init__(parameterList, type=1000) #type 0 is default qt type. 1000 is subclassed user type) self.setTextAlignment(2, QtCore.Qt.AlignHCenter) #clients self.setTextAlignment(4, QtCore.Qt.AlignHCenter) #symlinks self.setLocked(sessionDict["locked"]) def updateData(self): """Actively queries the api for new data""" sessionDict = api.sessionQuery(self.sessionDict["nsmSessionName"]) self.sessionDict = sessionDict self.setText(0, sessionDict["name"]) self.setText(1, sessionDict["lastSavedDate"]) self.setText(2, str(sessionDict["numberOfClients"])) self.setText(3, sizeof_fmt(sessionDict["sizeInBytes"])) self.setText(4, "Yes" if sessionDict["hasSymlinks"] else "No") #TODO: Translate self.setText(5, sessionDict["fullPath"]) def setLocked(self, state:bool): """Number of clients, symlinks and size change frequently while a session is open/locked. We deactivate the display of these values while locked This is also used for nsmd lockfiles and locked sessions, aka session open by another nsmd. """ self.updateData() self.setDisabled(state) def updateTimestamp(self, timestamp:str): #Column 1 "Last Save" self.setText(1, timestamp) def __lt__(self, other): """Treeview uses only less than. less equal, both greater and equal are not used. There is no check between two directory-items here because these are standard WidgetItems """ if type(other) is DirectoryItem: #Just a dir return False #we are "greater"=later column = self.treeWidget().sortColumn() if column == 3: #bytes return self.sessionDict["sizeInBytes"] > other.sessionDict["sizeInBytes"] elif column == 2: #number of clients return self.sessionDict["numberOfClients"] > other.sessionDict["numberOfClients"] else: return QtWidgets.QTreeWidgetItem.__lt__(self, other) class SessionTreeController(object): """Controls a treeWidget, but does not subclass""" def __init__(self, mainWindow): self.mainWindow = mainWindow self.treeWidget = mainWindow.ui.session_tree self._cachedSessionDicts = None self.mainWindow.ui.checkBoxNested.stateChanged.connect(self._reactSignal_nestedFlatChanged) self._reactSignal_nestedFlatChanged(self.mainWindow.ui.checkBoxNested.isChecked()) #initial state #Configure the treewidget #columns: name, path (relative from session dir), number of programs, disk size (links resolved?) self.treeWidget.setColumnCount(4) self.headerLabels = [ QtCore.QCoreApplication.translate("SessionTree", "Name"), QtCore.QCoreApplication.translate("SessionTree", "Last Save"), QtCore.QCoreApplication.translate("SessionTree", "Clients"), QtCore.QCoreApplication.translate("SessionTree", "Size"), QtCore.QCoreApplication.translate("SessionTree", "Symlinks"), QtCore.QCoreApplication.translate("SessionTree", "Path"), ] self.treeWidget.setHeaderLabels(self.headerLabels) self.treeWidget.setSortingEnabled(True) self.treeWidget.setAlternatingRowColors(True) self.treeWidget.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) #TODO: save sorting in user-wide qt application settings #We remember sorting via signals layoutAboutToBeChanged and restore via layoutChanged self.sortByColumnValue = 0 #by name self.sortDescendingValue = 0 # Qt::SortOrder which is 0 for ascending and 1 for descending self.treeWidget.header().setSortIndicator(0,0) #Hack/Workaround. On startup it is not enough to set sorting. New items will be added in a random position. Maybe that is our async network adding. #self.treeWidget.sortByColumn(self.sortByColumnValue, self.sortDescendingValue) api.callbacks.sessionsChanged.append(self._reactCallback_sessionsChanged) api.callbacks.sessionLocked.append(self._reactCallback_sessionLocked) api.callbacks.sessionFileChanged.append(self._reactCallback_sessionFileChanged) self.treeWidget.currentItemChanged.connect(self._reactSelectionChanged) #click anywhere self.treeWidget.itemDoubleClicked.connect(self._reactSignal_itemDoubleClicked) self.treeWidget.customContextMenuRequested.connect(self.contextMenu) self.treeWidget.itemExpanded.connect(self._reactSignal_itemExpanded) self.treeWidget.itemCollapsed.connect(self._reactSignal_itemExpanded) self.treeWidget.model().layoutAboutToBeChanged.connect(self._reactSignal_rememberSorting) #self.treeWidget.model().layoutChanged.connect(self._reactSignal_restoreSorting) self.mainWindow.ui.button_new_session.clicked.connect(self._reactSignal_newSession) self.mainWindow.ui.button_new_quick_session.clicked.connect(api.sessionNewTimestamped) #The next ones are only available if a session item is currently selected. The connected lambda functions do not test for this, but we only enable the buttons when such an item is selected. self.mainWindow.ui.button_load_selected_session.clicked.connect(self._reactSignal_openSelected) self.mainWindow.ui.button_copy_selected_session.clicked.connect(lambda: self._askForCopyAndCopy(self.treeWidget.currentItem().sessionDict["nsmSessionName"])) self.mainWindow.ui.button_rename_selected_session.clicked.connect(lambda: self._askForNameAndRenameSession(self.treeWidget.currentItem().sessionDict["nsmSessionName"])) self.mainWindow.ui.button_delete_selected_session.clicked.connect(lambda: self.deleteSessionItem(self.treeWidget.currentItem())) logger.info("Full View Session Chooser ready") def _reactCallback_sessionFileChanged(self, name:str, timestamp:str): """Timestamp of "last saved" changed""" SessionItem.allItems[name].updateTimestamp(timestamp) def _reactCallback_sessionLocked(self, name:str, state:bool): SessionItem.allItems[name].setLocked(state) def _reactCallback_sessionsChanged(self, sessionDicts:list): """Main callback for new, added, removed, moved sessions etc. We also get this for every client change so we can update our numbers""" self.treeWidget.clear() self._cachedSessionDicts = sessionDicts #in case we change the flat/nested mode. for sessionDict in sessionDicts: self.addSessionItem(sessionDict) for i in range(len(self.headerLabels)): self.treeWidget.resizeColumnToContents(i) #Make the name column a few pixels wider self.treeWidget.setColumnWidth(0, self.treeWidget.columnWidth(0) + 25) self.treeWidget.sortItems(self.sortByColumnValue, self.sortDescendingValue) def _addItemNested(self, sessionDict:dict): assert sessionDict, sessionDict item = SessionItem(sessionDict) if sessionDict["parents"]: #These are already a hirarchy, sorted from parents to children last = None #first is toplevel for parentDir in sessionDict["parents"]: alreadyExist = self.treeWidget.findItems(parentDir, QtCore.Qt.MatchExactly|QtCore.Qt.MatchRecursive, column=0) if alreadyExist: directoryItem = alreadyExist[0] else: directoryItem = DirectoryItem([parentDir]) #directoryItem = QtWidgets.QTreeWidgetItem([parentDir], 0) #type 0 is qt default if last: last.addChild(directoryItem) else: self.treeWidget.addTopLevelItem(directoryItem) last = directoryItem #After the loop: All subdirs built. Now add the item to the last one last.addChild(item) else: self.treeWidget.addTopLevelItem(item) def _addItemFlat(self, sessionDict:dict): assert sessionDict, sessionDict sessionDict["name"] = sessionDict["nsmSessionName"] item = SessionItem(sessionDict) self.treeWidget.addTopLevelItem(item) def addSessionItem(self, sessionDict:dict): if self.mode == "nested": self._addItemNested(sessionDict) elif self.mode == "flat": self._addItemFlat(sessionDict) else: raise ValueError("Unknown SessionTree display mode") def deleteSessionItem(self, item:SessionItem): """Instruct the engine to fully delete a complete session item. Will show a warning before.""" text = QtCore.QCoreApplication.translate("SessionTree", "About to delete Session {}").format(item.sessionDict["nsmSessionName"]) informativeText = QtCore.QCoreApplication.translate("SessionTree", "All files in the project directory will be irreversibly deleted.") title = QtCore.QCoreApplication.translate("SessionTree", "All files in the project directory will be irreversibly deleted.") title = QtCore.QCoreApplication.translate("SessionTree", "About to delete Session {}").format(item.sessionDict["nsmSessionName"]) box = QtWidgets.QMessageBox(self.treeWidget) box.setIcon(box.Warning) box.setText(text) box.setWindowTitle(title) box.setInformativeText(informativeText) keep = box.addButton(QtCore.QCoreApplication.translate("SessionTree", "Keep Session"), box.RejectRole) box.addButton(QtCore.QCoreApplication.translate("SessionTree", "Delete!"), box.AcceptRole) box.setDefaultButton(keep) ret = box.exec() #0 or 1. Return values are NOT the button roles. if ret: #Delete api.sessionDelete(item.sessionDict["nsmSessionName"]) def contextMenu(self, qpoint): item = self.treeWidget.itemAt(qpoint) if not type(item) is SessionItem: return menu = QtWidgets.QMenu() listOfLabelsAndFunctions = [ (QtCore.QCoreApplication.translate("SessionTree", "Copy Session"), lambda: self._askForCopyAndCopy(item.sessionDict["nsmSessionName"])) ] if not item.isDisabled() and not item.sessionDict["locked"]: listOfLabelsAndFunctions.append((QtCore.QCoreApplication.translate("SessionTree", "Rename Session"), lambda: self._askForNameAndRenameSession(item.sessionDict["nsmSessionName"]))) #Delete should be the bottom item. listOfLabelsAndFunctions.append((QtCore.QCoreApplication.translate("SessionTree", "Delete Session"), lambda: self.deleteSessionItem(item))) for text, function in listOfLabelsAndFunctions: if function is None: l = QtWidgets.QLabel(text) l.setAlignment(QtCore.Qt.AlignCenter) a = QtWidgets.QWidgetAction(menu) a.setDefaultWidget(l) menu.addAction(a) else: a = QtWidgets.QAction(text, menu) menu.addAction(a) a.triggered.connect(function) pos = QtGui.QCursor.pos() pos.setY(pos.y() + 5) menu.exec_(pos) #GUI Signals def _reactSelectionChanged(self, item, previous): """User clicks on an entry in the session chooser, or in the empty space. in any case, the selection changes and we can decide if we activate/deactivate certain buttons""" if not item or not type(item) is SessionItem or item.sessionDict["locked"] == True: sessionSelectedState = False else: sessionSelectedState = True self.mainWindow.ui.button_load_selected_session.setEnabled(sessionSelectedState) self.mainWindow.ui.button_copy_selected_session.setEnabled(sessionSelectedState) self.mainWindow.ui.button_rename_selected_session.setEnabled(sessionSelectedState) self.mainWindow.ui.button_delete_selected_session.setEnabled(sessionSelectedState) def _reactSignal_itemDoubleClicked(self, item:QtWidgets.QTreeWidgetItem, column:int): if not item.isDisabled() and type(item) is SessionItem: api.sessionOpen(item.sessionDict["nsmSessionName"]) def _reactSignal_itemExpanded(self, item:QtWidgets.QTreeWidgetItem): """Also for collapsed!""" for i in range(len(self.headerLabels)): self.treeWidget.resizeColumnToContents(i) def _reactSignal_openSelected(self): item = self.treeWidget.currentItem() if item: self._reactSignal_itemDoubleClicked(item, column=0) def _reactSignal_newSession(self): widget = NewSessionDialog(parent=self.treeWidget, startwith="") #widget = ProjectNameWidget(parent=self.treeWidget, startwith="") if widget.result: #result = {"name":str, "startclients":list} api.sessionNew(widget.result["name"], widget.result["startclients"]) def _askForCopyAndCopy(self, nsmSessionName:str): """Called by button and context menu""" copyString = QtCore.QCoreApplication.translate("SessionTree", "Copying {} to {}") widget = ProjectNameWidget(parent=self.treeWidget, startwith=nsmSessionName+"-copy") if widget.result: logger.info("Asking api to copy a session while waiting") copyString = copyString.format(nsmSessionName, widget.result) def longrunningfunction(progressHook): api.sessionCopy(nsmSessionName, widget.result, progressHook) diag = WaitDialog(self.mainWindow, copyString, longrunningfunction) #save in local var to keep alive #Somehow session list is wrong, symlinks are not calculated. Force update. api.requestSessionList() def _askForNameAndRenameSession(self, nsmSessionName:str): """Only for non-locked sessions. Context menu is only available if not locked.""" widget = ProjectNameWidget(parent=self.treeWidget, startwith=nsmSessionName) if widget.result and not widget.result == nsmSessionName: api.sessionRename(nsmSessionName, widget.result) def _reactSignal_rememberSorting(self, *args): self.sortByColumnValue = self.treeWidget.header().sortIndicatorSection() self.sortDescendingValue = self.treeWidget.header().sortIndicatorOrder() def _reactSignal_restoreSorting(self, *args): """Do not use as signal!!! Will lead to infinite recursion since Qt 5.12.2""" #self.treeWidget.sortItems(self.sortByColumnValue, self.sortDescendingValue) raise RuntimeError() def _reactSignal_nestedFlatChanged(self, checkStatus:bool): """#flat does not create directory items but changes the session name to dir/foo/bar""" if checkStatus: self.mode = "nested" else: self.mode = "flat" #And rebuild the items without fetching new data. if self._cachedSessionDicts: #not startup self._reactCallback_sessionsChanged(self._cachedSessionDicts) self.treeWidget.sortItems(self.sortByColumnValue, self.sortDescendingValue) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1665611335.9539216 agordejo-0.4.2/qtgui/settings.py0000644000175000017500000001127314321633110015404 0ustar00nilsnils#! /usr/bin/env python3 # -*- coding: utf-8 -*- """ Copyright 2022, Nils Hilbricht, Germany ( https://www.hilbricht.net ) This file is part of the Laborejo Software Suite ( https://www.laborejo.org ), This application is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . """ import logging; logger = logging.getLogger(__name__); logger.info("import") #Standard Library import pathlib import os #Third Party from PyQt5 import QtCore, QtWidgets #Engine import engine.api as api from engine.config import METADATA #includes METADATA only. No other environmental setup is executed. #QtGui from .designer.settings import Ui_Dialog class SettingsDialog(QtWidgets.QDialog): def __init__(self, mainWindow): super().__init__() self.ui = Ui_Dialog() self.ui.setupUi(self) self.mainWindow = mainWindow self.success = False logger.info("Init settings dialog") settings = QtCore.QSettings("LaborejoSoftwareSuite", METADATA["shortName"]) if settings.contains("launcherBlacklistPlainTextEdit"): self.ui.launcherBlacklistPlainTextEdit.setPlainText(settings.value("launcherBlacklistPlainTextEdit", type=str)) else: self.ui.launcherBlacklistPlainTextEdit.setPlainText("") if settings.contains("launcherWhitelistPlainTextEdit"): self.ui.launcherWhitelistPlainTextEdit.setPlainText(settings.value("launcherWhitelistPlainTextEdit", type=str)) else: self.ui.launcherWhitelistPlainTextEdit.setPlainText("") if settings.contains("programPathsPlainTextEdit"): self.ui.programPathsPlainTextEdit.setPlainText(settings.value("programPathsPlainTextEdit", type=str)) else: self.ui.programPathsPlainTextEdit.setPlainText("") #self.ui.name.textEdited.connect(self.check) #not called when text is changed programatically self.ui.buttonBox.accepted.connect(self.process) self.ui.buttonBox.rejected.connect(self.reject) self.setWindowFlag(QtCore.Qt.Popup, True) self.setModal(True) self.setFocus(True) logger.info("Show settings dialog") self.exec_() @staticmethod def loadFromSettingsAndSendToEngine(): """Called on program start and in self.process, which has a bit overhead because it is saving to file and then reloading from file (qsettings)""" settings = QtCore.QSettings("LaborejoSoftwareSuite", METADATA["shortName"]) if settings.contains("launcherBlacklistPlainTextEdit"): bl = settings.value("launcherBlacklistPlainTextEdit", type=str) else: bl = None if settings.contains("launcherWhitelistPlainTextEdit"): wl = settings.value("launcherWhitelistPlainTextEdit", type=str) else: wl = None if settings.contains("programPathsPlainTextEdit"): pth = settings.value("programPathsPlainTextEdit", type=str) else: pth = None blacklist = bl.split("\n") if bl else [] whitelist = wl.split("\n") if wl else [] api.systemProgramsSetBlacklist(blacklist) api.systemProgramsSetWhitelist(whitelist) #Depends on SettingsDialog: More executable paths for the engine. We do this in mainwindow because it has access to the qsettings safe file and is started before engine, program-database or nsmd. additionalExecutablePaths = pth.split("\n") if pth else [] if additionalExecutablePaths: os.environ["PATH"] = os.pathsep.join(additionalExecutablePaths) + os.pathsep + os.environ["PATH"] logger.info(f"Binary search paths: {os.environ['PATH']}") def process(self): settings = QtCore.QSettings("LaborejoSoftwareSuite", METADATA["shortName"]) settings.setValue("launcherBlacklistPlainTextEdit", self.ui.launcherBlacklistPlainTextEdit.toPlainText()) settings.setValue("launcherWhitelistPlainTextEdit", self.ui.launcherWhitelistPlainTextEdit.toPlainText()) settings.setValue("programPathsPlainTextEdit", self.ui.programPathsPlainTextEdit.toPlainText()) SettingsDialog.loadFromSettingsAndSendToEngine() self.success = True self.done(True) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1665611335.9539216 agordejo-0.4.2/qtgui/startchooserunningnsmd.py0000644000175000017500000001347414321633110020372 0ustar00nilsnils#! /usr/bin/env python3 # -*- coding: utf-8 -*- """ Copyright 2022, Nils Hilbricht, Germany ( https://www.hilbricht.net ) This file is part of the Laborejo Software Suite ( https://www.laborejo.org ). This application is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . """ import logging; logger = logging.getLogger(__name__); logger.info("import") #Standard Library import pathlib from os import getenv, listdir from sys import argv as sysargv #Third Party from PyQt5 import QtCore, QtGui, QtWidgets #Our own files from .movesessionroot import nsmVersionGreater160 def checkForRunningNsmd(qtApp, PATHS:dict): """Before we start the engine with our own nsmd server: nsmd >= 1.6.0 introduced run file discovery of sessions and empty nsmd. Ask the user if they want to connect to one of these. Injects the path into PATHS["url"], which is used by engine.api.startEngine() """ if PATHS["url"] or any("url" in param for param in sysargv): #this is really the same test twice... #This is not our problem anymore. return if not nsmVersionGreater160(): return #see docstring parent = qtApp.desktop() if not getenv("XDG_RUNTIME_DIR"): logger.warning("Your system has no environment variable $XDG_RUNTIME_DIR. That is unusual for Linux. Automatic detection of already running sessions deactivated.") return #On Linux that should exist. But if not, no reason to crash. logger.info("Detecting if there are already nsmd or sessions running under this user") session_rundir = pathlib.Path(getenv("XDG_RUNTIME_DIR"), "nsm") logger.info(f"Supposed nsmd session rundir: {session_rundir}") nsmd_rundir = pathlib.Path(getenv("XDG_RUNTIME_DIR"), "nsm", "d") logger.info(f"Supposed nsmd server rundir: {nsmd_rundir}") if not session_rundir.exists() or not session_rundir.is_dir(): logger.info("nsmd rundir does not exist. Continue.") return existing_daemons = listdir(nsmd_rundir) existing_sessions = listdir(session_rundir) if existing_sessions: existing_sessions.remove("d") #Remove the daemon subdir if not existing_daemons: logger.info("nsmd rundir exist, but is empty. Continue.") return #if there are no daemons there are no sessions. #There are nsmd running under this user, maybe even open sessions. #Now build a list for the user to choose from. #If a session is running on an nsmd show this, otherwise show the empty nsmd sessions = [] nsmd_pids = set() for s in existing_sessions: res = {} sessions.append(res) with open(pathlib.Path(session_rundir, s), "r") as f: res["hashName"] = s #name of the lockfile. has hash postfix. res["path"] = pathlib.Path(f.readline().strip("\n")) #file path in ~/.local/share/nsm res["name"] = res["path"].name #present this to the user. res["url"] = f.readline().strip("\n") #nsmd url res["pid"] = int(f.readline().strip("\n")) #nsmd pid nsmd_pids.add(res["pid"]) daemons = [] for d in existing_daemons: #d is a file with a PID as name. e.g. 9627 #inside is the NSM_URL. if int(d) in nsmd_pids: #we already have a running session on this server continue else: #empty nsmd. res = {} daemons.append(res) with open(pathlib.Path(nsmd_rundir, d), "r") as f: res["pid"] = d res["url"] = f.readline().strip("\n") #just the first line res["name"] = QtCore.QCoreApplication.translate("StartChooseRunningSession", "Empty Server") + " " + res["url"] #We now have all empty nsmd in var daemons assert sessions or daemons PATHS["url"] = ChooseSessionWidget(qtApp, sessions+daemons).url #"Global Variable" class ChooseSessionWidget(QtWidgets.QDialog): """return value in self.url. Might be None""" def __init__(self, qtApp, sessionDicts:list): super().__init__() #QDialog can't parent to qtApp self.qtApp = qtApp self.setModal(True) #block until closed self.layout = QtWidgets.QVBoxLayout() self.setLayout(self.layout) self.label = QtWidgets.QLabel(self) self.label.setText(QtCore.QCoreApplication.translate("StartChooseRunningSession", "Select session or server to connect to.\nCancel to start our own server.")) self.layout.addWidget(self.label) self.comboBox = QtWidgets.QComboBox(self) self.layout.addWidget(self.comboBox) for s in sessionDicts: self.comboBox.addItem(s["name"], s["url"]) #Show name, use url as data. self.buttonBox = QtWidgets.QDialogButtonBox(self) self.buttonBox.setOrientation(QtCore.Qt.Horizontal) self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok) self.buttonBox.setObjectName("buttonBox") self.layout.addWidget(self.buttonBox) self.buttonBox.accepted.connect(self.accept) self.buttonBox.rejected.connect(self.reject) self.exec() def accept(self): self.url = self.comboBox.currentData() #easy abstraction so that the caller does not need to know our widget name super().accept() def reject(self): self.url = None super().reject() ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1665611335.9539216 agordejo-0.4.2/qtgui/systemtray.py0000644000175000017500000001410314321633110015763 0ustar00nilsnils#! /usr/bin/env python3 # -*- coding: utf-8 -*- """ Copyright 2022, Nils Hilbricht, Germany ( https://www.hilbricht.net ) This file is part of the Laborejo Software Suite ( https://www.laborejo.org ), This is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . """ import logging; logger = logging.getLogger(__name__); logger.info("import") #Third Party from PyQt5 import QtCore, QtGui, QtWidgets #Engine import engine.api as api #This loads the engine and starts a session. #QtGui from .addclientprompt import askForExecutable from .resources import * class SystemTray(QtWidgets.QSystemTrayIcon): def __init__(self, mainWindow): super().__init__(QtGui.QIcon(":icon.png")) self.mainWindow = mainWindow self.available = self.isSystemTrayAvailable() self.show() #self.showMessage("Title", "Helllo!", QtWidgets.QSystemTrayIcon.Information) #title, message, icon, timeout. #has messageClicked() signal. #Don't build the context menu here. The engine is not ready to provide us with session information. Let the callbacks do it. #self.messageClicked.connect(self._reactSignal_messageClicked) self.activated.connect(self._reactSignal_activated) #Connect to api callbacks to rebuild context menu when session changes api.callbacks.sessionClosed.append(self.buildContextMenu) api.callbacks.sessionOpenReady.append(self.buildContextMenu) api.callbacks.sessionsChanged.append(self.buildContextMenu) #api.callbacks.clientStatusChanged.append(self.buildContextMenu) #too much. We deal with the client list separately. self.toggleVisMenu = None def updateToggleVisMenu(self): if not self.toggleVisMenu or not api.currentSession(): return #program start or not in a session for a in self.toggleVisMenu.findChildren(QtWidgets.QAction): if not a.menu(): self.toggleVisMenu.removeAction(a) del a for clientItem in self.mainWindow.sessionController.openSessionController.allSessionItems(): if clientItem.clientDict["hasOptionalGUI"]: a = QtWidgets.QAction(clientItem.clientDict["reportedName"], self.toggleVisMenu) self.toggleVisMenu.addAction(a) def createToggleLambda(clientId): return lambda: api.clientToggleVisible(clientId) command = createToggleLambda(clientItem.clientDict["clientId"]) a.triggered.connect(command) def buildContextMenu(self, *args): """In a function for readability. It gets rebuild everytime a session is opened or closed or the session list changed """ menu = QtWidgets.QMenu() def _add(text, function): a = QtWidgets.QAction(text, menu) menu.addAction(a) a.triggered.connect(function) nsmSessionName = api.currentSession() _add(QtCore.QCoreApplication.translate("TrayIcon", "Hide/Show Agordejo"), lambda: self.mainWindow.toggleVisible(force=None)) #explicit force=None because the qt signal is sending a bool menu.addSeparator() #Add other pre-defined actions if nsmSessionName: #We are in a loaded session menu.addAction(self.mainWindow.ui.actionShow_All_Clients) _add(QtCore.QCoreApplication.translate("TrayIcon", "Add Client (Prompt)"), lambda: askForExecutable(self.mainWindow.qtApp.desktop())) menu.addSeparator() menu.addAction(self.mainWindow.ui.actionShow_All_Clients) menu.addAction(self.mainWindow.ui.actionHide_All_Clients) #Create a submenu to toggle visibility for all supported clients. self.toggleVisMenu = menu.addMenu(QtCore.QCoreApplication.translate("TrayIcon", "Toggle Client Visibility")) #Updated on each trayIcon show event with updateToggleVisMenu menu.addSeparator() _add(QtCore.QCoreApplication.translate("TrayIcon", "Save {}".format(nsmSessionName)), api.sessionSave) _add(QtCore.QCoreApplication.translate("TrayIcon", "Save && Close {}".format(nsmSessionName)), api.sessionClose) _add(QtCore.QCoreApplication.translate("TrayIcon", "Close without Saving {}".format(nsmSessionName)), api.sessionAbort) menu.addSeparator() _add(QtCore.QCoreApplication.translate("TrayIcon", "Save && Quit Agordejo"), self.mainWindow.closeAndQuit) _add(QtCore.QCoreApplication.translate("TrayIcon", "Close without Saving && Quit Agordejo"), self.mainWindow.abortAndQuit) menu.addSeparator() else: for recentName in self.mainWindow.recentlyOpenedSessions.get(): _add(f"Session: {recentName}", lambda: api.sessionOpen(recentName)) _add(QtCore.QCoreApplication.translate("TrayIcon", "Quit "), self.mainWindow.menuRealQuit) self.setContextMenu(menu) def _reactSignal_activated(self, qActivationReason): """ QtWidgets.QSystemTrayIcon.Unknown QtWidgets.QSystemTrayIcon.Context QtWidgets.QSystemTrayIcon.DoubleClick QtWidgets.QSystemTrayIcon.Trigger QtWidgets.QSystemTrayIcon.MiddleClick """ logger.info(f"System tray activated with reason {qActivationReason}") self.updateToggleVisMenu() if qActivationReason == QtWidgets.QSystemTrayIcon.Trigger: self.mainWindow.toggleVisible() #def _reactSignal_messageClicked(self): # """this signal is emitted when the message displayed using # showMessage() was clicked by the user.""" # print ("clicky") ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1665611335.9539216 agordejo-0.4.2/qtgui/usermanual.py0000644000175000017500000000442114321633110015715 0ustar00nilsnils#! /usr/bin/env python3 # -*- coding: utf-8 -*- """ Copyright 2022, Nils Hilbricht, Germany ( https://www.hilbricht.net ) This file is part of the Laborejo Software Suite ( https://www.laborejo.org ), This application is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . """ import logging; logger = logging.getLogger(__name__); logger.info("import") #System Wide Modules from PyQt5 import QtCore, QtWidgets, QtGui #Local Modules from engine.config import * #imports METADATA from .designer.usermanual import Ui_TemplateUserManual from engine.start import PATHS class UserManual(QtWidgets.QWidget): """A modal window that is hidden""" def __init__(self, mainWindow): super().__init__() self.mainWindow = mainWindow self.ui = Ui_TemplateUserManual() self.ui.setupUi(self) self.text = self.ui.textBrowser self.text.setSearchPaths([PATHS["doc"]]) #it's a list! self.text.setSource(QtCore.QUrl("index.html")) self.text.highlighted.connect(self.blockLink) self.ui.back.clicked.connect(self.text.backward) self.ui.home.clicked.connect(self.text.home) self.setWindowTitle(METADATA["name"] + " " + QtCore.QCoreApplication.translate("TemplateUserManual", "User Manual")) self.hide() def blockLink(self, qurl): """The browser displays binary "text" when following a link to an image. prevent that""" if qurl.url().endswith(".png") or qurl.url().startswith("http") or qurl.url().startswith("mailto"): #About to click an url. self.text.setOpenLinks(False) return False else: self.text.setOpenLinks(True) return True def closeEvent(self, event): self.hide() ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1665611335.9539216 agordejo-0.4.2/qtgui/waitdialog.py0000644000175000017500000001023514321633110015665 0ustar00nilsnils#! /usr/bin/env python3 # -*- coding: utf-8 -*- """ Copyright 2022, Nils Hilbricht, Germany ( https://www.hilbricht.net ) This file is part of the Laborejo Software Suite ( https://www.laborejo.org ) This application is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . """ import logging; logger = logging.getLogger(__name__); logger.info("import") #Standard Library #Third Party from PyQt5 import QtCore, QtGui, QtWidgets class WaitThread(QtCore.QThread): def __init__(self, mainWindow, longRunningFunction): self.longRunningFunction = longRunningFunction self.mainWindow = mainWindow self.finished = False QtCore.QThread.__init__(self) def __del__(self): """This gets called rather early in the objects life but is in the main thread!""" self.wait() #This is after run self.finished = True logger.info(f"Thread done {self.longRunningFunction}") def run(self): """This is in the side-thread. We can't access qt widgets here""" logger.info(f"Thread running {self.longRunningFunction}") self.longRunningFunction() self.finished = True class WaitDialog(object): """An information text that closes itself once a task is done. Executes and shows on construction""" def __init__(self, mainWindow, text, longRunningFunction): super().__init__() self.text = text self._errorText = None logger.info(f"Starting blocking message for {longRunningFunction}") self.mainWindow = mainWindow self.mainWindow.ui.messageLabel.setText(text) #self.mainWindow.ui.messageLabel.setWordWrap(True) #qt segfault! maybe something with threads... don't care. We truncate now, see below. self.mainWindow.ui.mainPageSwitcher.setCurrentIndex(1) #1 is messageLabel 0 is the tab widget self.mainWindow.ui.menubar.setEnabled(False) #TODO: this will leave the options in the TrayIcon menu available.. but well, who cares... self.mainWindow.ui.waitDialogErrorButton.hide() self.mainWindow.ui.waitDialogErrorButton.setEnabled(False) self.mainWindow.ui.waitDialogErrorButton.clicked.connect(lambda: self.weAreDone()) #for some reason this does not trigger if we don't use lambda self.buttonErrorText = QtCore.QCoreApplication.translate("WaitDialog", "Please confirm with a click on the button at the bottom.") def wrap(): longRunningFunction(self.progressInfo) wt = WaitThread(mainWindow, wrap) #wt.finished.connect(self.threadDone) #does NOT trigger wt.start() while not wt.finished: self.mainWindow.qtApp.processEvents() if self._errorText: #progressInfo activated it self.mainWindow.ui.messageLabel.setText(self._errorText + "\n" + self.buttonErrorText) self.mainWindow.ui.waitDialogErrorButton.show() self.mainWindow.ui.waitDialogErrorButton.setEnabled(True) else: self.weAreDone() def weAreDone(self): self.mainWindow.ui.menubar.setEnabled(True) self.mainWindow.ui.waitDialogErrorButton.setEnabled(False) self.mainWindow.ui.waitDialogErrorButton.hide() self.mainWindow.ui.mainPageSwitcher.setCurrentIndex(0) #1 is messageLabel 0 is the tab widget def progressInfo(self, updateText:str): """ProcessEvents is already called above in init""" #updateText can be very long. They will destroy our complete layout if unchecked. we truncate to -80 self.mainWindow.ui.messageLabel.setText(self.text + "\n\n" + updateText[-80:]) if "ERROR!" in updateText: self._errorText = updateText ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1665611335.9539216 agordejo-0.4.2/tools/nsm-data0000755000175000017500000003656714321633110014650 0ustar00nilsnils#! /usr/bin/env python3 # -*- coding: utf-8 -*- """ Copyright 2022, Nils Hilbricht, Germany ( https://www.hilbricht.net ) This file is part of the Laborejo Software Suite ( https://www.laborejo.org ), more specifically its template base application. The Template Base Application is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . """ import logging; logger = logging.getLogger("nsm-data"); logger.info("import") URL="https://www.laborejo.org/agordejo/nsm-data" HARD_LIMIT = 512 # no single message longer than this VERSION= 1.1 #In case the user tries to run this standalone. import argparse parser = argparse.ArgumentParser(description="nsm-data is a module for Agordejo. It only communicates over OSC in an NSM-Session and has no standalone functionality.") parser.add_argument("-v", "--version", action='version', version=str(VERSION)) args = parser.parse_args() import json import pathlib from time import sleep from sys import exit as sysexit from nsmclient import NSMClient from nsmclient import NSMNotRunningError def chunkstring(string): return [string[0+i:HARD_LIMIT+i] for i in range(0, len(string), HARD_LIMIT)] class DataClient(object): """ Keys are strings, While nsmd OSC support int, str and float we use json exclusively. We expect a json string and will parse it here. All message consist of two arguments maximum: a key and, if a create-function, a json string. Rule: all client-keys are send as strings, even in replies. All client-values are send as json-string, even if originally just a string. Description is a multi-part message, a string. DataClient will register itself as Data-Storage. All other communication is done via osc. In theory every application can read and write us (like a book!) We listen to OSC paths and reply to the sender, which must give its address explicitly. /agordejo/datastorage/readall s:request-host i:request-port #Request all data /agordejo/datastorage/read s:key s:request-host i:request-port #Request one value The write functions have no reply. They will print out to stdout/err but not send an error message back. /agordejo/datastorage/create s:key any:value #Write/Create one value /agordejo/datastorage/update s:kecy any:value #Update a value, but only if it exists /agordejo/datastorage/delete s:key #Remove a key/value completely """ def __init__(self): self.data = None #Dict. created in openOrNewCallbackFunction, saved as json self.absoluteJsonFilePath = None #pathlib.Path set by openOrNewCallbackFunction self._descriptionStringArray = {"identifier":None} #int:str self._descriptionId = None self.nsmClient = NSMClient(prettyName = "Data-Storage", #will raise an error and exit if this example is not run from NSM. saveCallback = self.saveCallbackFunction, openOrNewCallback = self.openOrNewCallbackFunction, supportsSaveStatus = True, # Change this to True if your program announces it's save status to NSM exitProgramCallback = self.exitCallbackFunction, broadcastCallback = None, hideGUICallback = None, #replace with your hiding function. You need to answer in your function with nsmClient.announceGuiVisibility(False) showGUICallback = None, #replace with your showing function. You need to answer in your function with nsmClient.announceGuiVisibility(True) sessionIsLoadedCallback = self.sessionIsLoadedCallback, #no parametersd loggingLevel = "error", #"info" for development or debugging, "error" for production. default is error. ) #Add custom callbacks. They all receive _IncomingMessage(data) self.nsmClient.reactions["/agordejo/datastorage/setclientoverridename"] = self.setClientOverrideName self.nsmClient.reactions["/agordejo/datastorage/getclientoverridename"] = self.getClientOverrideName self.nsmClient.reactions["/agordejo/datastorage/getall"] = self.getAll self.nsmClient.reactions["/agordejo/datastorage/getdescription"] = self.getDescription self.nsmClient.reactions["/agordejo/datastorage/setdescription"] = self.setDescription self.nsmClient.reactions["/agordejo/datastorage/gettimelinemaximum"] = self.getTimelineMaximum self.nsmClient.reactions["/agordejo/datastorage/settimelinemaximum"] = self.setTimelineMaximum #self.nsmClient.reactions["/agordejo/datastorage/read"] = self.reactRead #generic key/value storage #self.nsmClient.reactions["/agordejo/datastorage/readall"] = self.reactReadAll #self.nsmClient.reactions["/agordejo/datastorage/create"] = self.reactCreate #self.nsmClient.reactions["/agordejo/datastorage/update"] = self.reactUpdate #self.nsmClient.reactions["/agordejo/datastorage/delete"] = self.reactDelete #NsmClients only returns from init when it has a connection, and on top (for us) when session is ready. It is safe to announce now. self.nsmClient.broadcast("/agordejo/datastorage/announce", [self.nsmClient.ourClientId, HARD_LIMIT, self.nsmClient.ourOscUrl]) while True: self.nsmClient.reactToMessage() sleep(0.05) #20fps update cycle def getAll(self, msg): """A complete data dump, intended to use once after startup. Will split into multiple reply messages, if needed. Our mirror datastructure in nsmservercontrol.py calls that on init. """ senderHost, senderPort = msg.params path = "/agordejo/datastorage/reply/getall" encoded = json.dumps(self.data) chunks = chunkstring(encoded) l = len(chunks) for index, chunk in enumerate(chunks): listOfParameters = [index+0, l-1, chunk] self.nsmClient.send(path, listOfParameters, host=senderHost, port=senderPort) def getDescription(self, msg)->str: """Returns a normal string, not json""" senderHost, senderPort = msg.params path = "/agordejo/datastorage/reply/getdescription" chunks = chunkstring(self.data["description"]) l = len(chunks) for index, chunk in enumerate(chunks): listOfParameters = [index+0, l-1, chunk] self.nsmClient.send(path, listOfParameters, host=senderHost, port=senderPort) def setDescription(self, msg): """ Answers with descriptionId and index when data was received and saved. The GUI needs to buffer this a bit. Don't send every char as single message. This is for multi-part messages Index is 0 based, chunk is part of a simple string, not json. The descriptionId:int indicates the message the chunks belong to. If we see a new one we reset our storage. """ descriptionId, index, chunk, senderHost, senderPort = msg.params #str, int, str, str, int if not self._descriptionId == descriptionId: self._descriptionId = descriptionId self._descriptionStringArray.clear() self._descriptionStringArray[index] = chunk buildString = "".join([v for k,v in sorted(self._descriptionStringArray.items())]) self.data["description"] = buildString self.nsmClient.announceSaveStatus(False) def getClientOverrideName(self, msg): """Answers with empty string if clientId does not exist or has not data. This is a signal for the GUI/host to use the original name!""" clientId, senderHost, senderPort = msg.params path = "/agordejo/datastorage/reply/getclient" if clientId in self.data["clientOverrideNames"]: name = self.data["clientOverrideNames"][clientId] else: logger.info(f"We were instructed to read client {clientId}, but it does not exist") name = "" listOfParameters = [clientId, json.dumps(name)] self.nsmClient.send(path, listOfParameters, host=senderHost, port=senderPort) def setClientOverrideName(self, msg): """We accept empty string as a name to remove the name override. """ clientId, jsonValue = msg.params name = json.loads(jsonValue)[:HARD_LIMIT] if name: self.data["clientOverrideNames"][clientId] = name else: #It is possible that a client not present in our storage will send an empty string. Protect. if clientId in self.data["clientOverrideNames"]: del self.data["clientOverrideNames"][clientId] self.nsmClient.announceSaveStatus(False) def getTimelineMaximum(self, msg): """ In minutes If the GUI supports global jack transport controls this can be used to remember the users setting for the maximum timeline duration. JACKs own data is without an upper bound.""" senderHost, senderPort = msg.params path = "/agordejo/datastorage/reply/gettimelinemaximum" if "timelineMaximumDuration" in self.data: numericValue = self.data["timelineMaximumDuration"] else: logger.info(f"We were instructed to read the timeline maximum duration, but it does not exist yet") numericValue = 5# minutes. listOfParameters = [json.dumps(numericValue)] self.nsmClient.send(path, listOfParameters, host=senderHost, port=senderPort) def setTimelineMaximum(self, msg): """In minutes""" jsonValue = msg.params[0] #list of 1 numericValue = json.loads(jsonValue) if numericValue <= 1: numericValue = 1 self.data["timelineMaximumDuration"] = numericValue self.nsmClient.announceSaveStatus(False) #Generic Functions. Not in use and not ready. #Callback Reactions to OSC. They all receive _IncomingMessage(data) def reactReadAll(self, msg): senderHost, senderPort = msg.params path = "/agordejo/datastorage/reply/readall" encoded = json.dumps("") chunks = chunkstring(encoded, 512) l = len(chunks) for index, chunk in enumerate(chunks): listOfParameters = [index+0, l-1, chunk] self.nsmClient.send(path, listOfParameters, host=senderHost, port=senderPort) def reactRead(self, msg): key, senderHost, senderPort = msg.params if key in self.data: path = "/agordejo/datastorage/reply/read" listOfParameters = [key, json.dumps(self.data[key])] self.nsmClient.send(path, listOfParameters, host=senderHost, port=senderPort) else: logger.warning(f"We were instructed to read key {key}, but it does not exist") def reactCreate(self, msg): key, jsonValue = msg.params value = json.loads(jsonValue) self.data[key] = value self.nsmClient.announceSaveStatus(False) def reactUpdate(self, msg): key, jsonValue = msg.params value = json.loads(jsonValue) if key in self.data: self.data[key] = value self.nsmClient.announceSaveStatus(False) else: logger.warning(f"We were instructed to update key {key} with value {value}, but it does not exist") def reactDelete(self, msg): key = msg.params[0] if key in self.data: del self.data[key] self.nsmClient.announceSaveStatus(False) else: logger.warning(f"We were instructed to delete key {key}, but it does not exist") #NSM Callbacks and File Handling def saveCallbackFunction(self, ourPath, sessionName, ourClientNameUnderNSM): result = self.data result["origin"] = URL result["version"] = VERSION jsonData = json.dumps(result, indent=2) try: with open(self.absoluteJsonFilePath, "w", encoding="utf-8") as f: f.write(jsonData) except Exception as e: logging.error("Will not load or save because: " + e.__repr__()) return self.absoluteJsonFilePath #nsmclient.py will send save status clean def openOrNewCallbackFunction(self, ourPath, sessionName, ourClientNameUnderNSM): self.absoluteJsonFilePath = pathlib.Path(ourPath) try: self.data = self.openFromJson(self.absoluteJsonFilePath) except FileNotFoundError: self.data = None #This makes debugging output nicer. If we init Data() here all errors will be presented as follow-up error "while handling exception FileNotFoundError". except (NotADirectoryError, PermissionError) as e: self.data = None logger.error("Will not load or save because: " + e.__repr__()) #Version 1.1 save file updates if self.data: if not "timelineMaximumDuration" in self.data: self.data["timelineMaximumDuration"] = 5 #5 minutes as sensible default else: self.data = {"clientOverrideNames":{}, "description":"", "timelineMaximumDuration":5} #5 minutes as sensible default logger.info("New/Open complete") #Data is not send here. Instead the gui calls the getAll message later. def openFromJson(self, absoluteJsonFilePath): with open(absoluteJsonFilePath, "r", encoding="utf-8") as f: try: text = f.read() result = json.loads(text) except Exception as error: result = None logger.error(error) if result and "version" in result and "origin" in result and result["origin"] == URL: if result["version"] <= VERSION: assert type(result) is dict, (result, type(result)) logger.info("Loading file from json complete") return result else: logger.error(f"""{absoluteJsonFilePath} was saved with {result["version"]} but we need {VERSION}""") #self.nsmClient.setLabel... We cannot use nsm client here because at this point we are still in the open/new callback. and self.nsmClient does not exist yet. sysexit() else: logger.error(f"""Error. {absoluteJsonFilePath} not loaded. Not a sane agordejo/nsm-data file in json format""") sysexit() def sessionIsLoadedCallback(self): """At one point I thought we could send our data when session is ready, so the GUI actually has clients to rename. However, that turned out impossible or impractical. Instead the GUI now just fails if nameOverrides that we send are not available yet and tries again later. Leave that in for documentation. """ pass #def broadcastCallbackFunction(self, ourPath, sessionName, ourClientNameUnderNSM, messagePath, listOfArguments): # print (__file__, "broadcast") def exitCallbackFunction(self, ourPath, sessionName, ourClientNameUnderNSM): sysexit(0) if __name__ == '__main__': """Creating an instance starts the client and does not return""" try: DataClient() except NSMNotRunningError: parser.print_help() ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1665611335.9539216 agordejo-0.4.2/tools/nsmclient.py0000644000175000017500000010261114321633110015544 0ustar00nilsnils#! /usr/bin/env python3 # -*- coding: utf-8 -*- """ PyNSMClient - A New Session Manager Client-Library in one file. The Non-Session-Manager by Jonathan Moore Liles : http://non.tuxfamily.org/nsm/ New Session Manager by Nils Hilbricht et al https://new-session-manager.jackaudio.org With help from code fragments from https://github.com/attwad/python-osc ( DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE v2 ) MIT License Copyright (c) since 2014: Laborejo Software Suite , All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ import logging; logger: logging.Logger #filled by init with client logger. import struct import socket from os import getenv, getpid, kill import os import os.path import shutil from uuid import uuid4 from sys import argv from signal import signal, SIGTERM, SIGINT, SIGKILL #react to exit signals to close the client gracefully. Or kill if the client fails to do so. from urllib.parse import urlparse class _IncomingMessage(object): """Representation of a parsed datagram representing an OSC message. An OSC message consists of an OSC Address Pattern followed by an OSC Type Tag String followed by zero or more OSC Arguments. """ def __init__(self, dgram): #NSM Broadcasts are bundles, but very simple ones. We only need to care about the single message it contains. #Therefore we can strip the bundle prefix and handle it as normal message. if b"#bundle" in dgram: bundlePrefix, singleMessage = dgram.split(b"/", maxsplit=1) dgram = b"/" + singleMessage # / eaten by split self.isBroadcast = True else: self.isBroadcast = False self.LENGTH = 4 #32 bit self._dgram = dgram self._parameters = [] self.parse_datagram() def get_int(self, dgram, start_index): """Get a 32-bit big-endian two's complement integer from the datagram. Args: dgram: A datagram packet. start_index: An index where the integer starts in the datagram. Returns: A tuple containing the integer and the new end index. Raises: ValueError if the datagram could not be parsed. """ try: if len(dgram[start_index:]) < self.LENGTH: raise ValueError('Datagram is too short') return ( struct.unpack('>i', dgram[start_index:start_index + self.LENGTH])[0], start_index + self.LENGTH) except (struct.error, TypeError) as e: raise ValueError('Could not parse datagram %s' % e) def get_string(self, dgram, start_index): """Get a python string from the datagram, starting at pos start_index. We receive always the full string, but handle only the part from the start_index internally. In the end return the offset so it can be added to the index for the next parameter. Each subsequent call handles less of the same string, starting further to the right. According to the specifications, a string is: "A sequence of non-null ASCII characters followed by a null, followed by 0-3 additional null characters to make the total number of bits a multiple of 32". Args: dgram: A datagram packet. start_index: An index where the string starts in the datagram. Returns: A tuple containing the string and the new end index. Raises: ValueError if the datagram could not be parsed. """ #First test for empty string, which is nothing, followed by a terminating \x00 padded by three additional \x00. if dgram[start_index:].startswith(b"\x00\x00\x00\x00"): return "", start_index + 4 #Otherwise we have a non-empty string that must follow the rules of the docstring. offset = 0 try: while dgram[start_index + offset] != 0: offset += 1 if offset == 0: raise ValueError('OSC string cannot begin with a null byte: %s' % dgram[start_index:]) # Align to a byte word. if (offset) % self.LENGTH == 0: offset += self.LENGTH else: offset += (-offset % self.LENGTH) # Python slices do not raise an IndexError past the last index, # do it ourselves. if offset > len(dgram[start_index:]): raise ValueError('Datagram is too short') data_str = dgram[start_index:start_index + offset] return data_str.replace(b'\x00', b'').decode('utf-8'), start_index + offset except IndexError as ie: raise ValueError('Could not parse datagram %s' % ie) except TypeError as te: raise ValueError('Could not parse datagram %s' % te) def get_float(self, dgram, start_index): """Get a 32-bit big-endian IEEE 754 floating point number from the datagram. Args: dgram: A datagram packet. start_index: An index where the float starts in the datagram. Returns: A tuple containing the float and the new end index. Raises: ValueError if the datagram could not be parsed. """ try: return (struct.unpack('>f', dgram[start_index:start_index + self.LENGTH])[0], start_index + self.LENGTH) except (struct.error, TypeError) as e: raise ValueError('Could not parse datagram %s' % e) def parse_datagram(self): try: self._address_regexp, index = self.get_string(self._dgram, 0) if not self._dgram[index:]: # No params is legit, just return now. return # Get the parameters types. type_tag, index = self.get_string(self._dgram, index) if type_tag.startswith(','): type_tag = type_tag[1:] # Parse each parameter given its type. for param in type_tag: if param == "i": # Integer. val, index = self.get_int(self._dgram, index) elif param == "f": # Float. val, index = self.get_float(self._dgram, index) elif param == "s": # String. val, index = self.get_string(self._dgram, index) else: logger.warning("Unhandled parameter type: {0}".format(param)) continue self._parameters.append(val) except ValueError as pe: #raise ValueError('Found incorrect datagram, ignoring it', pe) # Raising an error is not ignoring it! logger.warning("Found incorrect datagram, ignoring it. {}".format(pe)) @property def oscpath(self): """Returns the OSC address regular expression.""" return self._address_regexp @staticmethod def dgram_is_message(dgram): """Returns whether this datagram starts as an OSC message.""" return dgram.startswith(b'/') @property def size(self): """Returns the length of the datagram for this message.""" return len(self._dgram) @property def dgram(self): """Returns the datagram from which this message was built.""" return self._dgram @property def params(self): """Convenience method for list(self) to get the list of parameters.""" return list(self) def __iter__(self): """Returns an iterator over the parameters of this message.""" return iter(self._parameters) class _OutgoingMessage(object): def __init__(self, oscpath): self.LENGTH = 4 #32 bit self.oscpath = oscpath self._args = [] def write_string(self, val): dgram = val.encode('utf-8') diff = self.LENGTH - (len(dgram) % self.LENGTH) dgram += (b'\x00' * diff) return dgram def write_int(self, val): return struct.pack('>i', val) def write_float(self, val): return struct.pack('>f', val) def add_arg(self, argument): t = {str:"s", int:"i", float:"f"}[type(argument)] self._args.append((t, argument)) def build(self): dgram = b'' #OSC Path dgram += self.write_string(self.oscpath) if not self._args: dgram += self.write_string(',') return dgram # Write the parameters. arg_types = "".join([arg[0] for arg in self._args]) dgram += self.write_string(',' + arg_types) for arg_type, value in self._args: f = {"s":self.write_string, "i":self.write_int, "f":self.write_float}[arg_type] dgram += f(value) return dgram class NSMNotRunningError(Exception): """Error raised when environment variable $NSM_URL was not found.""" class NSMClient(object): """The representation of the host programs as NSM sees it. Technically consists of an udp server and a udp client. Does not run an event loop itself and depends on the host loop. E.g. a Qt timer or just a simple while True: sleep(0.1) in Python.""" def __init__(self, prettyName, supportsSaveStatus, saveCallback, openOrNewCallback, exitProgramCallback, hideGUICallback=None, showGUICallback=None, broadcastCallback=None, sessionIsLoadedCallback=None, loggingLevel = "info"): self.nsmOSCUrl = self.getNsmOSCUrl() #this fails and raises NSMNotRunningError if NSM is not available. Host programs can ignore it or exit their program. self.realClient = True self.cachedSaveStatus = None #save status checks for this. global logger logger = logging.getLogger(prettyName) logger.info("import") if loggingLevel == "info" or loggingLevel == 20: logging.basicConfig(level=logging.INFO) #development logger.info("Starting PyNSM2 Client with logging level INFO. Switch to 'error' for a release!") #the NSM name is not ready yet so we just use the pretty name elif loggingLevel == "error" or loggingLevel == 40: logging.basicConfig(level=logging.ERROR) #production else: logging.warning("Unknown logging level: {}. Choose 'info' or 'error'".format(loggingLevel)) logging.basicConfig(level=logging.INFO) #development #given parameters, self.prettyName = prettyName #keep this consistent! Settle for one name. self.supportsSaveStatus = supportsSaveStatus self.saveCallback = saveCallback self.exitProgramCallback = exitProgramCallback self.openOrNewCallback = openOrNewCallback #The host needs to: Create a jack client with ourClientNameUnderNSM - Open the saved file and all its resources self.broadcastCallback = broadcastCallback self.hideGUICallback = hideGUICallback self.showGUICallback = showGUICallback self.sessionIsLoadedCallback = sessionIsLoadedCallback #Reactions get the raw _IncomingMessage OSC object #A client can add to reactions. self.reactions = { "/nsm/client/save" : self._saveCallback, "/nsm/client/show_optional_gui" : lambda msg: self.showGUICallback(), "/nsm/client/hide_optional_gui" : lambda msg: self.hideGUICallback(), "/nsm/client/session_is_loaded" : self._sessionIsLoadedCallback, #Hello source-code reader. You can add your own reactions here by nsmClient.reactions[oscpath]=func, where func gets the raw _IncomingMessage OSC object as argument. #broadcast is handled directly by the function because it has more parameters } #self.discardReactions = set(["/nsm/client/session_is_loaded"]) self.discardReactions = set() #Networking and Init self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) #internet, udp self.sock.bind(('', 0)) #pick a free port on localhost. ip, port = self.sock.getsockname() self.ourOscUrl = f"osc.udp://{ip}:{port}/" self.executableName = self.getExecutableName() #UNIX Signals. Used for quit. signal(SIGTERM, self.sigtermHandler) #NSM sends only SIGTERM. #TODO: really? pynsm version 1 handled sigkill as well. signal(SIGINT, self.sigtermHandler) #The following instance parameters are all set in announceOurselves self.serverFeatures = None self.sessionName = None self.ourPath = None self.ourClientNameUnderNSM = None self.ourClientId = None # the "file extension" of ourClientNameUnderNSM self.isVisible = None #set in announceGuiVisibility self.saveStatus = True # true is clean. false means we need saving. self.announceOurselves() assert self.serverFeatures, self.serverFeatures assert self.sessionName, self.sessionName assert self.ourPath, self.ourPath assert self.ourClientNameUnderNSM, self.ourClientNameUnderNSM self.sock.setblocking(False) #We have waited for tha handshake. Now switch blocking off because we expect sock.recvfrom to be empty in 99.99...% of the time so we shouldn't wait for the answer. #After this point the host must include self.reactToMessage in its event loop #We assume we are save at startup. self.announceSaveStatus(isClean = True) logger.info("NSMClient client init complete. Going into listening mode.") def reactToMessage(self): """This is the main loop message. It is added to the clients event loop.""" try: data, addr = self.sock.recvfrom(4096) #4096 is quite big. We don't expect nsm messages this big. Better safe than sorry. However, messages will crash the program if they are bigger than 4096. except BlockingIOError: #happens while no data is received. Has nothing to do with blocking or not. return None msg = _IncomingMessage(data) if msg.oscpath in self.reactions: self.reactions[msg.oscpath](msg) elif msg.oscpath in self.discardReactions: pass elif msg.oscpath == "/reply" and msg.params == ["/nsm/server/open", "Loaded."]: #NSM sends that all programs of the session were loaded. logger.info ("Got /reply Loaded from NSM Server") elif msg.oscpath == "/reply" and msg.params == ["/nsm/server/save", "Saved."]: #NSM sends that all program-states are saved. Does only happen from the general save instruction, not when saving our client individually logger.info ("Got /reply Saved from NSM Server") elif msg.isBroadcast: if self.broadcastCallback: logger.info (f"Got broadcast with messagePath {msg.oscpath} and listOfArguments {msg.params}") self.broadcastCallback(self.ourPath, self.sessionName, self.ourClientNameUnderNSM, msg.oscpath, msg.params) else: logger.info (f"No callback for broadcast! Got messagePath {msg.oscpath} and listOfArguments {msg.params}") elif msg.oscpath == "/error": logger.warning("Got /error from NSM Server. Path: {} , Parameter: {}".format(msg.oscpath, msg.params)) else: logger.warning("Reaction not implemented:. Path: {} , Parameter: {}".format(msg.oscpath, msg.params)) def send(self, path:str, listOfParameters:list, host=None, port=None): """Send any osc message. Defaults to nsmd URL. Will not wait for an answer but return None.""" if host and port: url = (host, port) else: url = self.nsmOSCUrl msg = _OutgoingMessage(path) for arg in listOfParameters: msg.add_arg(arg) #type is auto-determined by outgoing message self.sock.sendto(msg.build(), url) def getNsmOSCUrl(self): """Return and save the nsm osc url or raise an error""" nsmOSCUrl = getenv("NSM_URL") if not nsmOSCUrl: raise NSMNotRunningError("New-Session-Manager environment variable $NSM_URL not found.") else: #osc.udp://hostname:portnumber/ o = urlparse(nsmOSCUrl) #return o.hostname, o.port #this always make the hostname lowercase. usually it does not matter, but we got crash reports. Alternative: return o.netloc.split(":")[0], o.port def getExecutableName(self): """Finding the actual executable name can be a bit hard in Python. NSM wants the real starting point, even if it was a bash script. """ #TODO: I really don't know how to find out the name of the bash script fullPath = argv[0] assert os.path.dirname(fullPath) in os.environ["PATH"], (fullPath, os.path.dirname(fullPath), os.environ["PATH"]) #NSM requires the executable to be in the path. No excuses. This will never happen since the reference NSM server-GUI already checks for this. executableName = os.path.basename(fullPath) assert not "/" in executableName, executableName #see above. return executableName def announceOurselves(self): """Say hello to NSM and tell it we are ready to receive instructions /nsm/server/announce s:application_name s:capabilities s:executable_name i:api_version_major i:api_version_minor i:pid""" def buildClientFeaturesString(): #:dirty:switch:progress: result = [] if self.supportsSaveStatus: result.append("dirty") if self.hideGUICallback and self.showGUICallback: result.append("optional-gui") if result: return ":".join([""] + result + [""]) else: return "" logger.info("Sending our NSM-announce message") announce = _OutgoingMessage("/nsm/server/announce") announce.add_arg(self.prettyName) #s:application_name announce.add_arg(buildClientFeaturesString()) #s:capabilities announce.add_arg(self.executableName) #s:executable_name announce.add_arg(1) #i:api_version_major announce.add_arg(2) #i:api_version_minor announce.add_arg(int(getpid())) #i:pid hostname, port = self.nsmOSCUrl assert hostname, self.nsmOSCUrl assert port, self.nsmOSCUrl self.sock.sendto(announce.build(), self.nsmOSCUrl) #Wait for /reply (aka 'Howdy, what took you so long?) data, addr = self.sock.recvfrom(1024) msg = _IncomingMessage(data) if msg.oscpath == "/error": originalMessage, errorCode, reason = msg.params logger.error("Code {}: {}".format(errorCode, reason)) quit() elif msg.oscpath == "/reply": nsmAnnouncePath, welcomeMessage, managerName, self.serverFeatures = msg.params assert nsmAnnouncePath == "/nsm/server/announce", nsmAnnouncePath logger.info("Got /reply " + welcomeMessage) #Wait for /nsm/client/open data, addr = self.sock.recvfrom(1024) msg = _IncomingMessage(data) assert msg.oscpath == "/nsm/client/open", msg.oscpath self.ourPath, self.sessionName, self.ourClientNameUnderNSM = msg.params self.ourClientId = os.path.splitext(self.ourClientNameUnderNSM)[1][1:] logger.info("Got '/nsm/client/open' from NSM. Telling our client to load or create a file with name {}".format(self.ourPath)) self.openOrNewCallback(self.ourPath, self.sessionName, self.ourClientNameUnderNSM) #Host function to either load an existing session or create a new one. logger.info("Our client should be done loading or creating the file {}".format(self.ourPath)) replyToOpen = _OutgoingMessage("/reply") replyToOpen.add_arg("/nsm/client/open") replyToOpen.add_arg("{} is opened or created".format(self.prettyName)) self.sock.sendto(replyToOpen.build(), self.nsmOSCUrl) else: raise ValueError("Unexpected message path after announce: {}".format((msg.oscpath, msg.params))) def announceGuiVisibility(self, isVisible): message = "/nsm/client/gui_is_shown" if isVisible else "/nsm/client/gui_is_hidden" self.isVisible = isVisible guiVisibility = _OutgoingMessage(message) logger.info("Telling NSM that our clients switched GUI visibility to: {}".format(message)) self.sock.sendto(guiVisibility.build(), self.nsmOSCUrl) def announceSaveStatus(self, isClean): """Only send to the NSM Server if there was really a change""" if not self.supportsSaveStatus: return if not isClean == self.cachedSaveStatus: message = "/nsm/client/is_clean" if isClean else "/nsm/client/is_dirty" self.cachedSaveStatus = isClean saveStatus = _OutgoingMessage(message) logger.info("Telling NSM that our clients save state is now: {}".format(message)) self.sock.sendto(saveStatus.build(), self.nsmOSCUrl) def _saveCallback(self, msg): logger.info("Telling our client to save as {}".format(self.ourPath)) self.saveCallback(self.ourPath, self.sessionName, self.ourClientNameUnderNSM) replyToSave = _OutgoingMessage("/reply") replyToSave.add_arg("/nsm/client/save") replyToSave.add_arg("{} saved".format(self.prettyName)) self.sock.sendto(replyToSave.build(), self.nsmOSCUrl) #it is assumed that after saving the state is clear self.announceSaveStatus(isClean = True) def _sessionIsLoadedCallback(self, msg): if self.sessionIsLoadedCallback: logger.info("Received 'Session is Loaded'. Our client supports it. Forwarding message...") self.sessionIsLoadedCallback() else: logger.info("Received 'Session is Loaded'. Our client does not support it, which is the default. Discarding message...") def sigtermHandler(self, signal, frame): """Wait for the user to quit the program The user function does not need to exit itself. Just shutdown audio engines etc. It is possible, that the client does not implement quit properly. In that case NSM protocol demands that we quit anyway. No excuses. Achtung GDB! If you run your program with gdb --args python foo.py the Python signal handler will not work. This has nothing to do with this library. """ logger.info("Telling our client to quit.") self.exitProgramCallback(self.ourPath, self.sessionName, self.ourClientNameUnderNSM) #There is a chance that exitProgramCallback will hang and the program won't quit. However, this is broken design and bad programming. We COULD place a timeout here and just kill after 10s or so, but that would make quitting our responsibility and fixing a broken thing. #If we reach this point we have reached the point of no return. Say goodbye. logger.warning("Client did not quit on its own. Sending SIGKILL.") kill(getpid(), SIGKILL) logger.error("SIGKILL did nothing. Do it manually.") def debugResetDataAndExit(self): """This is solely meant for debugging and testing. The user way of action should be to remove the client from the session and add a new instance, which will get a different NSM-ID. Afterwards we perform a clean exit.""" logger.warning("debugResetDataAndExit will now delete {} and then request an exit.".format(self.ourPath)) if os.path.exists(self.ourPath): if os.path.isfile(self.ourPath): try: os.remove(self.ourPath) except Exception as e: logger.info(e) elif os.path.isdir(self.ourPath): try: shutil.rmtree(self.ourPath) except Exception as e: logger.info(e) else: logger.info("{} does not exist.".format(self.ourPath)) self.serverSendExitToSelf() def serverSendExitToSelf(self): """If you want a very strict client you can block any non-NSM quit-attempts, like ignoring a qt closeEvent, and instead send the NSM Server a request to close this client. This method is a shortcut to do just that. """ logger.info("Sending SIGTERM to ourselves to trigger the exit callback.") #if "server-control" in self.serverFeatures: # message = _OutgoingMessage("/nsm/server/stop") # message.add_arg("{}".format(self.ourClientId)) # self.sock.sendto(message.build(), self.nsmOSCUrl) #else: kill(getpid(), SIGTERM) #this calls the exit callback def serverSendSaveToSelf(self): """Some clients want to offer a manual Save function, mostly for psychological reasons. We offer a clean solution in calling this function which will trigger a round trip over the NSM server so our client thinks it received a Save instruction. This leads to a clean state with a good saveStatus and no required extra functionality in the client.""" logger.info("instructing the NSM-Server to send Save to ourselves.") if "server-control" in self.serverFeatures: #message = _OutgoingMessage("/nsm/server/save") # "Save All" Command. message = _OutgoingMessage("/nsm/gui/client/save") message.add_arg("{}".format(self.ourClientId)) self.sock.sendto(message.build(), self.nsmOSCUrl) else: logger.warning("...but the NSM-Server does not support server control. Server only supports: {}".format(self.serverFeatures)) def changeLabel(self, label:str): """This function is implemented because it is provided by NSM. However, it does not much. The message gets received but is not saved. The official NSM GUI uses it but then does not save it. We would have to send it every startup ourselves. This is fine for us as clients, but you need to provide a GUI field to enter that label.""" logger.info("Telling the NSM-Server that our label is now " + label) message = _OutgoingMessage("/nsm/client/label") message.add_arg(label) #s:label self.sock.sendto(message.build(), self.nsmOSCUrl) def broadcast(self, path:str, arguments:list): """/nsm/server/broadcast s:path [arguments...] We, as sender, will not receive the broadcast back. Broadcasts starting with /nsm are not allowed and will get discarded by the server """ if path.startswith("/nsm"): logger.warning("Attempted broadbast starting with /nsm. Not allwoed") else: logger.info("Sending broadcast " + path + repr(arguments)) message = _OutgoingMessage("/nsm/server/broadcast") message.add_arg(path) for arg in arguments: message.add_arg(arg) #type autodetect self.sock.sendto(message.build(), self.nsmOSCUrl) def importResource(self, filePath): """aka. import into session ATTENTION! You will still receive an absolute path from this function. You need to make sure yourself that this path will not be saved in your save file, but rather use a place- holder that gets replaced by the actual session path each time. A good point is after serialisation. search&replace for the session prefix ("ourPath") and replace it with a tag e.g. . The opposite during load. Only such a behaviour will make your session portable. Do not use the following pattern: An alternative that comes to mind is to only work with relative paths and force your programs workdir to the session directory. Better work with absolute paths internally . Symlinks given path into session dir and returns the linked path relative to the ourPath. It can handles single files as well as whole directories. if filePath is already a symlink we do not follow it. os.path.realpath or os.readlink will not be used. Multilayer links may indicate a users ordering system that depends on abstractions. e.g. with mounted drives under different names which get symlinked to a reliable path. Basically do not question the type of our input filePath. tar with the follow symlink option has os.path.realpath behaviour and therefore is able to follow multiple levels of links anyway. A hardlink does not count as a link and will be detected and treated as real file. Cleaning up a session directory is either responsibility of the user or of our client program. We do not provide any means to unlink or delete files from the session directory. """ #Even if the project was not saved yet now it is time to make our directory in the NSM dir. if not os.path.exists(self.ourPath): os.makedirs(self.ourPath) filePath = os.path.abspath(filePath) #includes normalisation if not os.path.exists(self.ourPath):raise FileNotFoundError(self.ourPath) if not os.path.isdir(self.ourPath): raise NotADirectoryError(self.ourPath) if not os.access(self.ourPath, os.W_OK): raise PermissionError("not writable", self.ourPath) if not os.path.exists(filePath):raise FileNotFoundError(filePath) if os.path.isdir(filePath): raise IsADirectoryError(filePath) if not os.access(filePath, os.R_OK): raise PermissionError("not readable", filePath) filePathInOurSession = os.path.commonprefix([filePath, self.ourPath]) == self.ourPath linkedPath = os.path.join(self.ourPath, os.path.basename(filePath)) linkedPathAlreadyExists = os.path.exists(linkedPath) if not os.access(os.path.dirname(linkedPath), os.W_OK): raise PermissionError("not writable", os.path.dirname(linkedPath)) if filePathInOurSession: #loadResource from our session dir. Portable session, manually copied beforehand or just loading a link again. linkedPath = filePath #we could return here, but we continue to get the tests below. logger.info(f"tried to import external resource {filePath} but this is already in our session directory. We use this file directly instead. ") elif linkedPathAlreadyExists and os.readlink(linkedPath) == filePath: #the imported file already exists as link in our session dir. We do not link it again but simply report the existing link. #We only check for the first target of the existing link and do not follow it through to a real file. #This way all user abstractions and file structures will be honored. linkedPath = linkedPath logger.info(f"tried to import external resource {filePath} but this was already linked to our session directory before. We use the old link: {linkedPath} ") elif linkedPathAlreadyExists: #A new file shall be imported but it would create a linked name which already exists in our session dir. #Because we already checked for a new link to the same file above this means actually linking a different file so we need to differentiate with a unique name firstpart, extension = os.path.splitext(linkedPath) uniqueLinkedPath = firstpart + "." + uuid4().hex + extension assert not os.path.exists(uniqueLinkedPath) os.symlink(filePath, uniqueLinkedPath) logger.info(self.ourClientNameUnderNSM + f":pysm2: tried to import external resource {filePath} but potential target link {linkedPath} already exists. Linked to {uniqueLinkedPath} instead.") linkedPath = uniqueLinkedPath else: #this is the "normal" case. External resources will be linked. assert not os.path.exists(linkedPath) os.symlink(filePath, linkedPath) logger.info(f"imported external resource {filePath} as link {linkedPath}") assert os.path.exists(linkedPath), linkedPath return linkedPath class NullClient(object): """Use this as a drop-in replacement if your program has a mode without NSM but you don't want to change the code itself. This was originally written for programs that have a core-engine and normal mode of operations is a GUI with NSM but they also support commandline-scripts and batch processing. For these you don't want NSM.""" def __init__(self, *args, **kwargs): self.realClient = False self.ourClientNameUnderNSM = "NSM Null Client" def announceSaveStatus(self, *args): pass def announceGuiVisibility(self, *args): pass def reactToMessage(self): pass def importResource(self): return "" def serverSendExitToSelf(self): quit()