From 76f6315d39e7119aad45e3a8cfec2eba4b2475f3 Mon Sep 17 00:00:00 2001 From: nadrad Date: Tue, 30 Aug 2022 17:54:49 +0200 Subject: [PATCH] Hello world! --- LICENSE | 674 ++++++++++++++++ h-m-m | 2208 +++++++++++++++++++++++++++++++++++++++++++++++++++++ h-m-m.png | Bin 0 -> 50254 bytes readme.md | 123 +++ 4 files changed, 3005 insertions(+) create mode 100644 LICENSE create mode 100755 h-m-m create mode 100644 h-m-m.png create mode 100644 readme.md diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + 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 +. diff --git a/h-m-m b/h-m-m new file mode 100755 index 0000000..88b36ea --- /dev/null +++ b/h-m-m @@ -0,0 +1,2208 @@ +#!/usr/bin/env php +$data) + echo + ($i++ % 4 == 0 ? "\n" : ''). + str_pad($id,3,' ',STR_PAD_LEFT). + str_pad($data['parent'],5,' ',STR_PAD_LEFT). + str_pad($data['x'] ?? 'x',5,' ',STR_PAD_LEFT). + str_pad($data['w'] ?? 'x',5,' ',STR_PAD_LEFT). + str_pad($data['y'] ?? 'x',5,' ',STR_PAD_LEFT). + str_pad($data['yo'] ?? 'x',5,' ',STR_PAD_LEFT). + str_pad($data['h'] ?? 'x',5,' ',STR_PAD_LEFT). + str_pad($data['lh'] ?? 'x',5,' ',STR_PAD_LEFT). + str_pad($data['clh'] ?? 'x',5,' ',STR_PAD_LEFT). + ' '. + substr($data['title'],0,70). + "\n"; + +} + + +// }}} +// {{{ decode tree + +function decode_tree($lines, $root_id, $start_id){ + + + // calculating the indentation shift and cleaning up the special characters + + $indentation_shift = 9999999; + + foreach ($lines as $lid=>$line) { + + $lines[$lid] = + str_replace( + [ "\t", "\n", "\r"], + [ " ", " ", " "], + $lines[$lid] + ); + + if (trim($lines[$lid])!='') + $indentation_shift = + min( + $indentation_shift, + mb_strlen($lines[$lid]) - mb_strlen(ltrim($lines[$lid])) + ); + + } + + + // building the tree + + $nodes = []; + $id = $start_id; + + $previous_level = 1; + $level = 1; + $previous_indentation = 0; + + $level_parent[1] = $root_id; + $level_indentation[1] = 0; + + foreach ($lines as $line) + if (trim($line)!='') { + + // warming up for this round! + $indentation = mb_strlen($line) - mb_strlen(ltrim($line)) - $indentation_shift; + + // going one level down + if ($indentation > $previous_indentation ){ + $level = $previous_level + 1; + $level_indentation[$level] = $indentation; + } + + // going one or more levels up + if ($indentation < $previous_indentation ) + foreach ($level_indentation as $plevel=>$pindentation) + if ($pindentation == $indentation) + $level = $plevel; + + // saving the latest level_parent + if ($level > $previous_level) + $level_parent[$level] = $id-1; + + // done! saving the data + $nodes[$id] = [ + 'title' => trim($line), + 'parent' => $level_parent[$level] + ]; + + // getting ready for the next round! + $previous_indentation = $indentation; + $previous_level = $level; + $id++; + + } + + + // setting a few properties that simplify future calculations + + foreach ($nodes as $id=>$node) { + $nodes[$id]['collapsed'] = false; + $nodes[$id]['is_leaf'] = true; + $nodes[$id]['children'] = []; + } + + foreach ($nodes as $id=>$node) + if (isset($nodes[ $node['parent'] ])) { + $nodes[ $node['parent'] ]['is_leaf'] = false; + $nodes[ $node['parent'] ]['children'][] = $id; + } + + return($nodes); + +} + + +// }}} +// {{{ load_file + +function load_file(&$mm){ + + global $argv; + + if (!isset($argv[1])){ + load_empty_map($mm); + return; + } + + $mm['filename']=$argv[1]; + + if (!file_exists($argv[1])) { + load_empty_map($mm); + return; + } + + $lines = file($argv[1], FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); + + // starting from 2 instead of 1, in case the files doesn't have + // a single root and we have to inject one. leaving "1" empty + // won't cause any problems. + $new_nodes = decode_tree($lines, 0, 2); + + + // checking to see how many first-level nodes we have + $first_level_nodes = []; + foreach ($new_nodes as $id=>$node) + if ($id!=0 && $node['parent']==0) + $first_level_nodes[] = $id; + + $mm['nodes'][0]['parent'] = -1; + $mm['nodes'][0]['title'] = 'X'; + $mm['nodes'][0]['is_leaf'] = false; + + if (count($first_level_nodes)>1) { + + $mm['root'] = 1; + + $mm['nodes'][1]['parent'] = 0; + $mm['nodes'][1]['title'] = 'root'; + $mm['nodes'][1]['is_leaf'] = false; + $mm['nodes'][1]['children'] = $first_level_nodes; + $mm['nodes'][0]['children'] = [1]; + + foreach ($new_nodes as $id=>$node) + if ($node['parent']==0) + $new_nodes[$id]['parent'] = 1; + + } else { + + $mm['nodes'][0]['children'] = $first_level_nodes; + + } + + $mm['nodes'] = $mm['nodes'] + $new_nodes; + + if (isset($mm['nodes'][1])) + $mm['active_node']=1; + else + $mm['active_node']=2; + +} + + + +// }}} +// {{{ calculate w, x, lh, and clh + + +function calculate_x_and_lh(&$mm, $id){ + + + $node = $mm['nodes'][$id]; + + $mm['nodes'][$id]['x'] = + $mm['nodes'][ $node['parent'] ]['x'] + + $mm['nodes'][ $node['parent'] ]['w'] + + conn_left_len + + conn_right_len + + 1 + + ( $node['parent']==0 ? 1 - conn_right_len - conn_left_len : 0 ) + ; + + $max_width = + ($node['is_leaf'] || ($node['collapsed'] ?? false)) * $mm['max_leaf_width'] + + !($node['is_leaf'] || ($node['collapsed'] ?? false)) * $mm['max_parent_width']; + + if ( mb_strlen($node['title']) > width_tolerance * $max_width ) { + + $lines = explode( + "\n", + wordwrap( + $node['title'], + $max_width + ) + ); + + $mm['nodes'][$id]['w'] = 0; + foreach ($lines as $line) + $mm['nodes'][$id]['w'] = + max($mm['nodes'][$id]['w'], trim(mb_strlen($line))); + + $mm['nodes'][$id]['lh'] = count($lines); + + } else { + + $mm['nodes'][$id]['w'] = mb_strlen($node['title']); + $mm['nodes'][$id]['lh'] = 1; + + } + + + $mm['map_width'] = + max( + $mm['map_width'], + $mm['nodes'][$id]['x'] + + $mm['nodes'][$id]['w'] + ); + + + $mm['nodes'][$id]['clh'] = 0; + if (($mm['nodes'][$id]['collapsed'] ?? false) || $mm['nodes'][$id]['is_leaf']) + $mm['nodes'][$id]['clh'] = $mm['nodes'][$id]['lh']; + + foreach ($node['children'] as $cid) { + calculate_x_and_lh($mm, $cid); + $mm['nodes'][$id]['clh'] += $mm['nodes'][$cid]['clh']; + } + + +} + + +// }}} +// {{{ calculate h + +function calculate_h(&$mm){ + + $unfinished = true; + while ($unfinished) { + + $unfinished = false; + + foreach ($mm['nodes'] as $id=>$node) + + if ($node['is_leaf'] || ($node['collapsed'] ?? false)) + + $mm['nodes'][$id]['h'] = + $mm['line_spacing'] + + $mm['nodes'][$id]['lh']; + + else { + + $h = 0; + $unready = false; + + foreach ($node['children'] as $cid) + if ($mm['nodes'][$cid]['h']>=0) + $h += $mm['nodes'][$cid]['h']; + else { + $unready = true; + break; + } + + if ($unready) + $unfinished = true; + else + $mm['nodes'][$id]['h'] = + max( $h, $node['lh'] + $mm['line_spacing'] ); + } + } + +} + + + + +// }}} +// {{{ calculate y and yo + +function calculate_y(&$mm){ + + $mm['map_top'] = 0; + $mm['map_bottom'] = 0; + $mm['map_height'] = $mm['nodes'][0]['h']; + + $mm['nodes'][0]['y'] = 0; + calculate_children_y($mm, 0); + +} + +function calculate_children_y(&$mm,$pid){ + + $y = $mm['nodes'][$pid]['y']; + + $mm['nodes'][$pid]['yo'] = + round( + ( + $mm['nodes'][$pid]['h'] - + $mm['nodes'][$pid]['lh'] + )/2 + ); + + if (!($mm['nodes'][$pid]['collapsed'] ?? false)) + foreach ($mm['nodes'][$pid]['children'] as $cid) { + + $mm['nodes'][$cid]['y'] = $y; + + $mm['map_bottom'] = max( + $mm['map_bottom'], + $mm['nodes'][$cid]['lh']+ + $mm['line_spacing']+ + $y + ); + + $mm['map_top'] = min($mm['map_top'],$y); + + $y += $mm['nodes'][$cid]['h']; + + calculate_children_y($mm,$cid); + + } + +} + +// }}} +// {{{ calculate top-down height shift + +function calculate_height_shift(&$mm, $id, $shift = 0){ + + $mm['nodes'][$id]['yo'] += $shift; + $shift += max(0, round( ($mm['nodes'][$id]['lh'] - $mm['nodes'][$id]['clh'])/2 - 0.5 )); + + foreach ($mm['nodes'][$id]['children'] as $cid) + if (!$mm['nodes'][$id]['collapsed']) + calculate_height_shift($mm, $cid, $shift); + +} + + + +// }}} +// {{{ draw connections on the map + +function draw_connections(&$mm, $id){ + + $node = $mm['nodes'][$id]; + + // if there's no child + + if ($node['is_leaf']) return; + + + // if the node is collapsed + + if ($node['collapsed'] ?? false) { + + mput( + $mm, + $node['x'] + $node['w']+1, + $node['y'] + $node['yo'], + ' [+]' + ); + + return; + } + + + // if there's only one child + + if (count($node['children'])==1) { + + $child = $mm['nodes'][ $node['children'][0] ]; + + $y1 = round($node['y']+$node['yo']) + round($node['lh']/2-0.6); + $y2 = round($child['y']+$child['yo']) + round($child['lh']/2-0.6); + + mput( + $mm, + $mm['nodes'][ $node['children'][0] ]['x'] - conn_left_len - conn_right_len, + min($y1,$y2), + $mm['conn_single'] + ); + + draw_connections($mm, $node['children'][0]); + return; + + } + + + // if there's more than one child + + $bottom = 0; + $bottom_child = 0; + $top = $mm['map_height']; + $top_child = 0; + + foreach ($node['children'] as $cid) { + + if ($mm['nodes'][$cid]['y'] + $mm['nodes'][$cid]['yo'] > $bottom) { + $bottom = $mm['nodes'][$cid]['y'] + $mm['nodes'][$cid]['yo']; + $bottom_child = $cid; + } + + if ($mm['nodes'][$cid]['y'] + $mm['nodes'][$cid]['yo'] < $top) { + $top = $mm['nodes'][$cid]['y'] + $mm['nodes'][$cid]['yo']; + $top_child = $cid; + } + + } + + $middle = round($node['y']+$node['yo']) + round($node['lh']/2-0.6); + + mput( + $mm, + $mm['nodes'][$top_child]['x'] - conn_left_len - conn_right_len, + $middle, + $mm['conn_left'] + ); + + for ( $i = $top ; $i < $bottom ; $i++ ) + mput( + $mm, + $mm['nodes'][$top_child]['x'] - conn_right_len, + $i, + '│'); + + mput( + $mm, + $mm['nodes'][$top_child]['x'] - conn_right_len, + $top, + '╭'. + $mm['conn_right']); + + mput( + $mm, + $mm['nodes'][$top_child]['x']-conn_right_len, + $bottom, + '╰'. + $mm['conn_right']); + + if (count($node['children'])>2) + foreach ($node['children'] as $cid) + if ($cid!=$top_child && $cid!=$bottom_child) + mput( + $mm, + $mm['nodes'][$cid]['x']-conn_right_len, + $mm['nodes'][$cid]['y']+ + $mm['nodes'][$cid]['lh']/2-0.2+ + $mm['nodes'][$cid]['yo'], + '├'. + $mm['conn_right']); + + + $existing_char = + mb_substr( + $mm['map'][$middle], + $mm['nodes'][$top_child]['x'] - conn_right_len, + 1); + + if ($existing_char=='│') + mput( + $mm, + $mm['nodes'][$top_child]['x'] - conn_right_len, + $middle, + '┤'); + + if ($existing_char=='╭') + mput( + $mm, + $mm['nodes'][$top_child]['x'] - conn_right_len, + $middle, + '┬'); + + if ($existing_char=='├') + mput( + $mm, + $mm['nodes'][$top_child]['x'] - conn_right_len, + $middle, + '┼'); + + + foreach ($node['children'] as $cid) + draw_connections($mm, $cid); + +} + + +// }}} +// {{{ add content to the map + +function add_content_to_the_map(&$mm, $id){ + + $node = $mm['nodes'][$id]; + + $max_width = + ($node['is_leaf'] || ($node['collapsed'] ?? false)) * $mm['max_leaf_width'] + + !($node['is_leaf'] || ($node['collapsed'] ?? false)) * $mm['max_parent_width']; + + if ( mb_strlen($node['title']) > width_tolerance * $max_width) + $lines = explode("\n",wordwrap( + $node['title'], + $max_width + )); + else + $lines = [$node['title']]; + + $num_lines = count($lines); + for ( $i=0 ; $i<$num_lines ; $i++ ) + mput( + $mm, + $node['x'], + round($node['y']+$node['yo'])+$i, + $lines[$i].' ' + ); + + if (!($node['collapsed'] ?? false)) + foreach ($node['children'] as $cid) + add_content_to_the_map($mm,$cid); + + +} + + + +// }}} +// {{{ build map + +function build_map(&$mm){ + + // resetting the global values + $mm['map_width'] = 0; + $mm['map_height'] = 0; + $mm['map_top'] = 0; + $mm['map_bottom'] = 0; + + // resetting the coordinates + foreach ($mm['nodes'] as $id=>$node) { + $mm['nodes'][$id]['x'] = -1; + $mm['nodes'][$id]['y'] = -1; + $mm['nodes'][$id]['h'] = -1; + $mm['nodes'][$id]['lh'] = -1; + } + + $mm['nodes'][0]['x'] = 0; + $mm['nodes'][0]['w'] = left_padding; + $mm['nodes'][0]['lh'] = 1; + + // resetting the map, 1/2 + $mm['map']=[]; + $mm['map_width']=0; + $mm['map_height']=0; + $mm['map_top']=0; + $mm['map_bottom']=0; + + // calculating the new coordinates + calculate_x_and_lh($mm,$mm['root']); + calculate_h($mm); + calculate_y($mm); + calculate_height_shift($mm, $mm['root']); + + // in case it was resized! + $mm['terminal_width'] = exec('tput cols'); + $mm['terminal_height'] = exec('tput lines'); + + // resetting the map, 2/2 + $height = max($mm['map_bottom'],$mm['terminal_height']); + $blank = str_repeat(' ', $mm['map_width']+$mm['terminal_width']); + + for ($i=$mm['map_top'] ; $i<=$height ; $i++) + $mm['map'][$i] = $blank; + + // building the new map + draw_connections($mm, $mm['root']); + add_content_to_the_map($mm, $mm['root']); + +} + + + + +// }}} +// {{{ toggle + +function toggle(&$mm){ + + if ($mm['nodes'][ $mm['active_node'] ]['is_leaf']) return; + + $mm['nodes'][ $mm['active_node'] ]['collapsed'] = + !($mm['nodes'][ $mm['active_node'] ]['collapsed'] ?? false); + + build_map($mm); + display($mm); + +} + + + +// }}} +// {{{ spacing adjuster + +function adjust_spacing(&$mm, $task){ + + if ($task==spacing_wider) $mm['line_spacing']++; + if ($task==spacing_narrower) $mm['line_spacing'] = max(0, $mm['line_spacing']-1); + if ($task==spacing_default) $mm['line_spacing'] = default_spacing; + + build_map($mm); + center_active_node($mm); + display($mm); + message($mm,'Spacing: '.$mm['line_spacing']); + +} + + +// }}} +// {{{ width adjuster + +function adjust_width(&$mm, $task){ + + if ($task==width_wider) { + $mx = $mm['terminal_width'] - max_width_padding; + $mm['max_parent_width'] = round(min($mx, max( width_min, $mm['max_parent_width'] * width_change_factor ))); + $mm['max_leaf_width'] = round(min($mx, max( width_min, $mm['max_leaf_width'] * width_change_factor ))); + } + + if ($task==width_narrower) { + $mm['max_parent_width'] = round(max( width_min, $mm['max_parent_width'] / width_change_factor )); + $mm['max_leaf_width'] = round(max( width_min, $mm['max_leaf_width'] / width_change_factor )); + } + + if ($task==width_default) { + $mm['max_parent_width'] = default_parent_width; + $mm['max_leaf_width'] = default_leaf_width; + } + + build_map($mm); + center_active_node($mm); + display($mm); + + message($mm,'Width: '.$mm['max_parent_width'].' / '.$mm['max_leaf_width']); + +} + + + +// }}} +// {{{ move nodes + +function push_node_down(&$mm, $id){ + + if ($id==0) return; + + $mm['modified'] = true; + + if (isset($mm['nodes'][$id+1])) + push_node_down($mm,$id+1); + + $mm['nodes'][$id+1] = $mm['nodes'][$id]; + unset($mm['nodes'][$id]); + + $mm['nodes'][ $mm['nodes'][$id+1]['parent'] ]['children'] = + array_diff( + $mm['nodes'][ $mm['nodes'][$id+1]['parent'] ]['children'], + [$id] + ); + + $mm['nodes'][ $mm['nodes'][$id+1]['parent'] ]['children'] = + array_push( + $mm['nodes'][ $mm['nodes'][$id+1]['parent'] ]['children'], + [$id+1] + ); + + foreach($mm['nodes'][$id+1]['children'] as $cid=>$cdata) + $mm['nodes'][$cid]['parent'] = $id+1; + +} + + + +// }}} +// {{{ insert node + + +function insert_node(&$mm, $type){ + + if ($mm['active_node']==$mm['root']) + $type=insert_child; + + $mm['modified'] = true; + + if ($type==insert_sibling) + $parent_id = $mm['nodes'][ $mm['active_node'] ]['parent']; + else + $parent_id = $mm['active_node']; + + $mm['nodes'][$parent_id]['is_leaf'] = false; + $mm['nodes'][$parent_id]['collapsed'] = false; + + $new_id = max(array_keys($mm['nodes'])) + 1; + + $mm['nodes'][$new_id] = [ + + 'title' => 'NEW', + 'is_leaf' => true, + 'collapsed' => false, + 'children' => [], + 'parent' => $parent_id + + ]; + + if ($type==insert_sibling) { + + $children = []; + foreach ($mm['nodes'][$parent_id]['children'] as $child) { + $children[] = $child; + if ($child==$mm['active_node']) + $children[] = $new_id; + } + + $mm['nodes'][$parent_id]['children'] = $children; + + } else { + + $mm['nodes'][$parent_id]['children'][] = $new_id; + + } + + + $mm['active_node'] = $new_id; + + build_map($mm); + display($mm); + + $mm['nodes'][ $mm['active_node'] ]['title']=''; + edit_node($mm); + +} + + + +// }}} +// {{{ edit node + +function show_line(&$mm, $title, $cursor, $shift){ + + $output = + str_pad( + substr($title,$shift,$mm['terminal_width']-1), + $mm['terminal_width'] + ); + + $output = substr_replace( $output, invert_off, $cursor-$shift , 0); + $output = substr_replace( $output, invert_on, $cursor-$shift-1, 0); + + put(0,$mm['terminal_height'],$mm['active_node_color'].$output); + +} + + +function edit_node(&$mm, $rewrite = false){ + + $title = $rewrite ? '' : $mm['nodes'][ $mm['active_node'] ]['title']; + if ($mm['active_node']==0 && $title=='root') $title=''; + + $in = ''; + $cursor = strlen($title)+1; + $shift = max( 0, $cursor - $mm['terminal_width'] ); + + show_line($mm, $title, $cursor, $shift); + + while(true){ + + usleep(10000); + $in = fread(STDIN, 9); + + if ($in != '') { + + + // Esc. + if ($in=="\033") { + display($mm); + message($mm, 'Editing cancelled'); + return; + } + + + // up arrow and home + elseif ($in=="\033\133\101" || $in=="\033\133\110") $cursor = 1; + + // right arrow + elseif ($in=="\033\133\103") $cursor = min( strlen($title)+1, $cursor+1); + + // down arrow and end + elseif ($in=="\033\133\102" || $in=="\033\133\106") $cursor = strlen($title)+1; + + // left arrow + elseif ($in=="\033\133\104") $cursor = max(1, $cursor-1); + + + // ctrl+left and shift+left + elseif ($in=="\033\133\061\073\065\104" || $in=="\033\133\061\073\062\104") + $cursor = + $cursor < 3 + ? 1 + : max( + 1, + ( + strrpos($title,' ',$cursor-strlen($title)-3) !== false + ? strrpos($title,' ',$cursor-strlen($title)-3) + 2 + : 1 + ) + ); + + + // ctrl+right and shift+right + elseif ($in=="\033\133\061\073\065\103" || $in=="\033\133\061\073\062\103") + $cursor = + $cursor > strlen($title) -2 + ? strlen($title) + 1 + : min( + strlen($title)+1, + ( + strpos($title,' ',$cursor+1) !== false + ? strpos($title,' ',$cursor+1) + 2 + : strlen($title) + 1 + ) + ); + + + // ctrl+backspace + elseif ($in=="\010") { + $from = mb_strrpos($title, ' ', min(1,$cursor-mb_strlen($title)-3)); + if ($from === false) $from=1; + $title = mb_substr($title, 0, $from) . mb_substr($title,$cursor-1); + $cursor = $from+1; + } + + + // backspace + elseif ($in=="\177") { + if ($cursor>1) { + $title = substr_replace($title, '', $cursor-2, 1); + $cursor--; + } + } + + + // ctrl+delete + elseif ($in=="\033\133\63\073\065\176"){ + $title = ''; + $cursor = 1; + } + + + // delete + elseif ($in=="\033\133\63\176") { + $title = substr_replace($title, '', $cursor-1, 1); + } + + + // enter + elseif ($in=="\012") { + $title = trim($title); + $mm['nodes'][ $mm['active_node'] ]['title'] = $title; + $original['nodes'][ $mm['active_node'] ]['title'] = $title; + $mm['modified'] = true; + build_map($mm); + display($mm); + return; + } + + // normal characters + elseif (strlen($in)==1) { + if ($in=="\011") $in=' '; + $title = substr_replace($title, $in, $cursor-1, 0); + $cursor++; + } + + // adjusting the position and shift + + $shift = max( 0, $shift, $cursor - $mm['terminal_width'] ); + $shift = min( $shift, $cursor-1 ); + + show_line($mm, $title, $cursor, $shift); + + } + + } + +} + + + +// }}} +// {{{ center active node + +function center_active_node(&$mm, $only_vertically = false){ + + $node = $mm['nodes'][ $mm['active_node'] ]; + + $midx = $node['w']/2 + $node['x']; + $midy = $node['lh']/2 + $node['y'] + $node['yo']; + + if (!$only_vertically) + $mm['win_left'] = max(0, round( $midx - $mm['terminal_width']/2 ) ); + + $mm['win_top'] = round( $midy - $mm['terminal_height']/2 ); + +} + + +// }}} +// {{{ goto's + +function go_to_root(&$mm){ + + $mm['active_node']=$mm['root']; + display($mm, true); + +} + + +function go_to_top(&$mm){ + + $yid = 0; + $y = $mm['map_height']; + + foreach ($mm['nodes'] as $id=>$node) + if ($node['y']>=0 && $node['y']+$node['yo'] < $y) { + $y = $node['y']+$node['yo']; + $yid = $id; + } + + $mm['active_node'] = $yid; + display($mm, true); + +} + + +function go_to_bottom(&$mm){ + + $yid = 0; + $y = 0; + + foreach ($mm['nodes'] as $id=>$node) + if ($node['y']>=0 && $node['y']+$node['yo'] > $y) { + $y = $node['y']+$node['yo']+$node['lh']; + $yid = $id; + } + + $mm['active_node'] = $yid; + display($mm, true); + +} + + + +// }}} +// {{{ search + +function search(&$mm){ + + put(0,$mm['terminal_height'],$mm['active_node_color'].str_repeat(' ',$mm['terminal_width'])); + move(0,$mm['terminal_height']); + + system("stty sane"); + $mm['query'] = readline('Search for: '); + system('stty cbreak -echo'); + + if (empty($mm['query'])) { + display($mm); + return; + } + + next_search_result($mm); + +} + +function previous_search_result(&$mm){ + + $cy = $mm['nodes'][ $mm['active_node'] ]['y'] + + $mm['nodes'][ $mm['active_node'] ]['yo']; + + $ny = -1; + $nid = -1; + + foreach ($mm['nodes'] as $id=>$node) + if ( + $id != 0 && + $node['y'] > -1 && + $node['y']+$node['yo'] < $cy && + $node['y']+$node['yo'] > $ny && + mb_stripos($node['title'],$mm['query'])!==false + ) + { + $ny = $node['y'] + $node['yo']; + $nid = $id; + } + + if ($nid<0) { + display($mm); + return; + } + + $mm['active_node'] = $nid; + display($mm); + +} + +function next_search_result(&$mm){ + + $cy = $mm['nodes'][ $mm['active_node'] ]['y'] + + $mm['nodes'][ $mm['active_node'] ]['yo']; + + $ny = $mm['map_height']+1; + $nid = -1; + + foreach ($mm['nodes'] as $id=>$node) + if ( + $id != 0 && + $node['y'] > -1 && + $node['y']+$node['yo'] > $cy && + $node['y']+$node['yo'] < $ny && + mb_stripos($node['title'],$mm['query'])!==false + ) + { + $ny = $node['y'] + $node['yo']; + $nid = $id; + } + + if ($nid<0) { + display($mm); + return; + } + + $mm['active_node'] = $nid; + display($mm); + +} + + +// }}} +// {{{ move active node + +function move_active_node_down(&$mm){ + + if ($mm['active_node']==0) return; + + $mm['modified'] = true; + + $parent_id = $mm['nodes'][ $mm['active_node'] ]['parent']; + $children = []; + $now = false; + + foreach ($mm['nodes'][ $parent_id ]['children'] as $child) { + + if ($child!=$mm['active_node']) + $children[] = $child; + + if ($now) { + $children[] = $mm['active_node']; + $now = false; + } + + if ($child==$mm['active_node']) + $now = true; + + } + + if ($now) + $children[] = $mm['active_node']; + + $mm['nodes'][ $parent_id ]['children'] = $children; + + build_map($mm); + display($mm); + +} + + +function move_active_node_up(&$mm){ + + if ($mm['active_node']==0) return; + + $mm['modified'] = true; + + $parent_id = $mm['nodes'][ $mm['active_node'] ]['parent']; + $children = []; + $now = false; + $rev_children = array_reverse($mm['nodes'][$parent_id]['children']); + + foreach ($rev_children as $child) { + + if ($child!=$mm['active_node']) + $children[] = $child; + + if ($now) { + $children[] = $mm['active_node']; + $now = false; + } + + if ($child==$mm['active_node']) + $now = true; + + } + + + if ($now) + $children[] = $mm['active_node']; + + $mm['nodes'][ $parent_id ]['children'] = array_reverse($children); + + build_map($mm); + display($mm); +} + + + +// }}} +// {{{ save + +function save(&$mm, $new_name = false){ + + if (empty($mm['filename'])) + $new_name = true; + + if ($new_name) { + + $path = exec('pwd'); + put(0,$mm['terminal_height'],$mm['active_node_color'].str_repeat(' ',$mm['terminal_width'])); + put(0,$mm['terminal_height']," $path -- new path and file name: "); + + system("stty sane"); + $mm['filename'] = trim(readline()); + system('stty cbreak -echo'); + + if ($mm['filename']=='') { + display($mm); + message($mm, 'Saving cancelled'); + return; + } + + $ext = mb_substr( $mm['filename'], mb_strrpos($mm['filename'],'.') + 1); + + if ($ext!='hmm') + $mm['filename'] .= '.hmm'; + + display($mm); + + } + + if (file_exists($mm['filename']) && !is_writable($mm['filename'])) { + message($mm, "ERROR! I don't have access to write into \"$mm[filename]\". Use shift+s and set another path and filename."); + sleep(1); + return; + } + + $file = fopen($mm['filename'], "w"); + + $mm['modified'] = false; + + if ($file===false) { + message($mm, 'ERROR! Could not save the file'); + $mm['modified'] = true; + return; + } + + fwrite($file, encode_tree($mm, $mm['root'])); + fclose($file); + + display($mm); + message($mm, 'Saved '.$mm['filename']); + +} + + + +// }}} +// {{{ message + +function message(&$mm, $text){ + + put( + $mm['terminal_width'] - strlen($text) - 1, + $mm['terminal_height'], + $mm['message_color'].' '.$text.' '.reset_color + ); + + usleep(200000); + +} + + +// }}} +// {{{ quit + +function quit(&$mm){ + + if (($mm['modified'] ?? false) === false) exit; + message($mm, "You have unsaved changes. Save them, or use shift+Q to quit without saving."); + +} + + +// }}} +// {{{ move_window + +function move_window(&$mm){ + + $node = $mm['nodes'][ $mm['active_node'] ]; + + $x1 = max(0, $node['x'] - conn_right_len - 2); + $x2 = $node['x'] + $node['w'] + 2; + $y1 = max(0, $node['y'] + $node['yo'] - vertical_offset); + $y2 = $y1 + $node['lh'] + vertical_offset*2; + + $mm['win_left'] = min( $mm['win_left'], $x1); + $mm['win_left'] = max( $mm['win_left'], $x2 - $mm['terminal_width']); + + $mm['win_top'] = min( $mm['win_top'], $y1); + $mm['win_top'] = max( $mm['win_top'], $y2 - $mm['terminal_height']); + +} + + +// }}} +// {{{ change active node + +function change_active_node(&$mm, $direction) { + + $node = $mm['nodes'][ $mm['active_node'] ]; + + + // we go to the child that is closest to the parent node; + // i.e., closest to the middle. + + if ($direction==move_right){ + + if ($node['is_leaf'] || ($node['collapsed'] ?? false) ) return; + + $distance = []; + foreach ($node['children'] as $cid) + $distance[$cid] = + abs( + + $node['y'] + + $node['yo'] + + $node['lh']/2 + - $mm['nodes'][$cid]['y'] + - $mm['nodes'][$cid]['yo'] + - $mm['nodes'][$cid]['lh']/2 + ); + + asort($distance); + + $mm['active_node'] = array_keys($distance)[0]; + + display($mm); + return; + + } + + + // no other movement applies to the root element + + if ( $mm['active_node']==0 ) return; + + + // it can't be easier than moving to the left! + + if ($direction==move_left) { + if ($mm['active_node']==$mm['root']) return; + $mm['active_node'] = $node['parent']; + display($mm); + return; + } + + + // for up and down, we'll try to move between siblings first, + // considering that their order is set based on their list + // in the parent item. + + if ($direction==move_up){ + + $rchildren = array_reverse($mm['nodes'][ $node['parent'] ]['children']); + foreach ($rchildren as $cid) + if ($mm['nodes'][$cid]['y']+$mm['nodes'][$cid]['yo'] < $node['y']+$node['yo']) { + $mm['active_node'] = $cid; + display($mm); + return; + } + + } + + if ($direction==move_down){ + + foreach ($mm['nodes'][ $node['parent'] ]['children'] as $cid) + if ($mm['nodes'][$cid]['y']+$mm['nodes'][$cid]['yo'] > $node['y']+$node['yo']) { + $mm['active_node'] = $cid; + display($mm); + return; + } + + } + + + // if it's not possible to move up or down between siblings, + // we'll measure distance and move to the nearest node. + // because the goal is to move vertically, and also because + // the aspect ratio of characters is not 1, there's a factor + // for y to give it more importance. I've set the amount by + // trial and erros and doesn't follow an exact logic. It may + // need refinements in the future. + + $distance = []; + + foreach ($mm['nodes'] as $id=>$nd) + if ($id != $mm['active_node'] && $nd['y']!=-1) { + + $dy = $nd['y'] + $nd['yo'] + $nd['lh']/2 - $node['y'] - $node['yo'] - $node['lh']/2; + + if ( ($direction==move_down && $dy>0) || ($direction==move_up && $dy<0) ) { + + $dx = $nd['x'] + $nd['w']/2 - $node['x'] - $node['w']/2; + $distance[$id] = pow($dy*15,2) + pow($dx,2); + + } + + } + + if ($distance==[]) return; + asort($distance); + + $mm['active_node'] = array_keys($distance)[0]; + + display($mm); + return; + + +} + + + +// }}} +// {{{ expand + +function expand(&$mm, $id){ + + $mm['nodes'][$id]['collapsed'] = false; + + foreach ($mm['nodes'][$id]['children'] as $cid) + expand($mm, $cid); + +} + + +function expand_all(&$mm){ + + foreach ($mm['nodes'] as $id=>$node) + $mm['nodes'][$id]['collapsed'] = false; + + $mm['show_logo'] = 0; + build_map($mm); + center_active_node($mm); + display($mm); + +} + + +// }}} +// {{{ encode tree + +function encode_tree(&$mm, $id, $exclude_parent = false, $base = 0){ + + if (!$exclude_parent) + $output = str_repeat("\t",$base).$mm['nodes'][$id]['title']."\n"; + else + $output = ''; + + foreach ($mm['nodes'][$id]['children'] as $cid) + $output .= encode_tree($mm, $cid, false, $base+1-$exclude_parent); + + return $output; + +} + + +// }}} +// {{{ paste sub-tree + +function paste_sub_tree(&$mm, $as_sibling ){ + + if ($as_sibling && $mm['active_node']==$mm['root']) return; + + $mm['modified'] = true; + + if ($as_sibling) + $parent_id = $mm['nodes'][ $mm['active_node'] ]['parent']; + else + $parent_id = $mm['active_node']; + + $mm['nodes'][$parent_id]['collapsed'] = false; + $mm['nodes'][ $parent_id ]['is_leaf'] = false; + + $new_id = 1 + max(array_keys($mm['nodes'])); + + $st = + decode_tree( + explode("\n",get_from_clipboard()), + $parent_id, + $new_id + ); + + $mm['nodes'] += $st; + + + // doing it like this, in case the sub-tree has more than + // one top-level element. + + foreach ($st as $cid=>$cdata) + if ($cdata['parent'] == $parent_id) + $mm['nodes'][ $parent_id ]['children'][] = $cid; + + + // rearranging the items only if they are pasted as siblings + // (for pasting as children, it makes sense to have them + // at the end) + + if ($as_sibling) { + + $sub_roots = []; + foreach ($st as $cid=>$cdata) + if ($cdata['parent']==$parent_id) + $sub_roots[] = $cid; + + $children = []; + + foreach ($mm['nodes'][ $parent_id ]['children'] as $child_id) { + + if (!in_array($child_id, $sub_roots)) + $children[] = $child_id; + + if ($child_id == $mm['active_node']) + $children = array_merge($children, $sub_roots); + + } + + $mm['nodes'][ $parent_id ]['children'] = $children; + + } + + + $mm['active_node'] = $new_id; + + build_map($mm); + display($mm); + +} + + + +// }}} +// {{{ clipboard + +function copy_to_clipboard($text){ + + if (PHP_OS_FAMILY === "Linux") $clip = popen('xclip -selection clipboard','wb'); + elseif (PHP_OS_FAMILY === "Windows") $clip = popen("clip","wb"); + elseif (PHP_OS_FAMILY === "Darwin") $clip = popen('pbcopy','wb'); + + if (!isset($clip)) return; + + fwrite($clip,$text); + pclose($clip); + +} + +function get_from_clipboard(){ + + if (PHP_OS_FAMILY === "Linux") + return shell_exec('xclip -out -selection clipboard'); + + elseif (PHP_OS_FAMILY === "Windows") + return shell_exec('powershell -sta "add-type -as System.Windows.Forms; [windows.forms.clipboard]::GetText()"'); + + elseif (PHP_OS_FAMILY === "Darwin") + return shell_exec('pbpaste'); + +} + + +// }}} +// {{{ load empty map + +function load_empty_map(&$mm){ + + if (isset($mm['nodes'])) + unset($mm['nodes']); + + $mm['nodes'][0] = [ 'title'=>'X', 'is_leaf'=>false, 'children'=>[1], 'collapsed'=>false, 'parent'=>-1 ]; + $mm['nodes'][1] = [ 'title'=>'root', 'is_leaf'=>true, 'children'=>[], 'collapsed'=>false, 'parent'=>0 ]; + + $mm['active_node']=1; + $mm['root']=1; + +} + + +// }}} +// {{{ yank + + +function yank_node(&$mm, $exclude_parent = false ){ + + copy_to_clipboard(encode_tree($mm, $mm['active_node'], $exclude_parent)); + message($mm, 'Item(s) are copied to the clipboard.'); + +} + +// }}} +// {{{ delete + +function delete_node(&$mm, $exclude_parent = false ){ + + if ($mm['active_node']==$mm['root']) $exclude_parent = true; + + copy_to_clipboard( encode_tree($mm, $mm['active_node'], $exclude_parent) ); + + $mm['modified'] = true; + + delete_node_internal($mm, $mm['active_node'], $exclude_parent); + + build_map($mm); + display($mm); + + message($mm, 'Item(s) are cut and placed into the clipboard.'); + +} + + +function delete_node_internal(&$mm, $active_node, $exclude_parent = false ){ + + + // taking a shorter approach if it's for the whole tree + + if ($active_node==$mm['root'] && !$exclude_parent) { + + load_empty_map($mm); + display($mm, true); + return; + + } + + + // if it's for a sub-tree, then... + + delete_children($mm, $active_node); + + if ($exclude_parent) { + + $mm['nodes'][ $active_node ]['is_leaf'] = true; + $mm['nodes'][ $active_node ]['children'] = []; + + } else { + + $parent_id = $mm['nodes'][ $active_node ]['parent']; + + $previous_sibling = 0; + $passed = false; + foreach ($mm['nodes'][$parent_id]['children'] as $cid) + if ($cid==$active_node){ + if ($previous_sibling!=0) break; + $passed = true; + } + else { + $previous_sibling = $cid; + if ($passed) break; + } + + $mm['nodes'][$parent_id]['children'] = + array_diff( + $mm['nodes'][$parent_id]['children'], + [$active_node] + ); + + if (count($mm['nodes'][$parent_id]['children'])==0) + $mm['nodes'][$parent_id]['is_leaf'] = true; + + unset($mm['nodes'][ $active_node ]); + + if ($mm['nodes'][$parent_id]['is_leaf']) + $mm['active_node'] = $parent_id; + else + $mm['active_node'] = $previous_sibling; + + } + +} + + +function delete_children(&$mm,$id){ + + foreach (($mm['nodes'][$id]['children'] ?? []) as $cid) { + delete_children($mm, $cid); + unset($mm['nodes'][$cid]); + } + +} + + +// }}} +// {{{ focus + +function toggle_focus(&$mm){ + + $mm['focus_lock'] = !$mm['focus_lock']; + + message($mm, $mm['focus_lock'] ? 'Focus locked' : 'Focus unlocked'); + + build_map($mm); + display($mm); + +} + +function focus(&$mm){ + + collapse_siblings($mm, $mm['active_node']); + expand_siblings($mm, $mm['active_node']); + +} + +function collapse_siblings(&$mm, $id){ + + if ($id <= $mm['root']) return; + + $parent_id = $mm['nodes'][$id]['parent']; + + foreach ($mm['nodes'][$parent_id]['children'] as $cid) + if ($cid!=$id) + $mm['nodes'][$cid]['collapsed'] = true; + + collapse_siblings($mm, $parent_id); + +} + + +function expand_siblings(&$mm, $id){ + + if ($mm['nodes'][$id]['is_leaf']) return; + + $mm['nodes'][$id]['collapsed'] = false; + foreach ($mm['nodes'][$id]['children'] as $cid) + expand_siblings($mm, $cid); + +} + + + +// }}} +// {{{ collapse + + +function collapse_all(&$mm){ + + foreach ($mm['nodes'] as $id=>$node) + if (!$node['is_leaf'] && $id!=0 && $id!=$mm['root']) + $mm['nodes'][$id]['collapsed'] = true; + + $mm['active_node'] = $mm['root']; + + build_map($mm); + center_active_node($mm); + display($mm); +} + + +function collapse(&$mm, $id, $keep){ + + if ($mm['nodes'][$id]['is_leaf']) return; + + if ($keep<=0) { + $mm['nodes'][$id]['collapsed'] = true; + } else { + $mm['nodes'][$id]['collapsed'] = false; + foreach ($mm['nodes'][$id]['children'] as $cid) + collapse($mm, $cid, $keep-1); + } + +} + + + +function collapse_level(&$mm, $level, $no_display = false){ + + collapse($mm, $mm['root'], $level); + + $id_collapsed = []; + $current = $mm['active_node']; + while ($current != $mm['root']) { + $id_collapsed[$current] = $mm['nodes'][$current]['collapsed']; + $current = $mm['nodes'][$current]['parent']; + } + + $id_collapsed = array_reverse( $id_collapsed, true); + foreach ($id_collapsed as $id=>$collapsed) + if ($collapsed) { + $mm['active_node'] = $id; + break; + } + + if ($no_display) return; + + build_map($mm); + center_active_node($mm); + display($mm); + +} + + + + +// }}} +// {{{ display + +function display(&$mm, $force_center = false){ + + if ($mm['focus_lock']) { + focus($mm); + build_map($mm); + } + + if ($mm['center_lock'] || $force_center) + center_active_node($mm); + else + move_window($mm); + + $mm['terminal_width'] = exec('tput cols'); + $mm['terminal_height'] = exec('tput lines'); + + $blank = str_repeat(' ', $mm['terminal_width']); + + + // calculating the coordinates of the active node + + $x1 = + max( + 0, + $mm['nodes'][ $mm['active_node'] ]['x'] - + 1 - + $mm['win_left'] + ); + + $x2 = + $mm['nodes'][ $mm['active_node'] ]['w'] + + $x1 + + 2 - + ($mm['active_node']==0 && left_padding==0); + + $y1 = + max( + 0, + $mm['nodes'][ $mm['active_node'] ]['y'] + + $mm['nodes'][ $mm['active_node'] ]['yo'] - + $mm['win_top'] + ); + + $y2 = + $mm['nodes'][ $mm['active_node'] ]['lh'] + + $y1; + + + // building the output + + $output = ''; + + for ( $y = 0 ; $y < $mm['terminal_height'] ; $y++ ) { + + if (isset($mm['map'][$y+$mm['win_top']])) + $line = + mb_substr( + $mm['map'][$y+$mm['win_top']], + $mm['win_left'], + $mm['terminal_width'] + ); + else + $line = $blank; + + // this one really depends on (x,y), but after this, the + // coordinates are not reliable anymore because of the + // added escape codes. + + if ( $y >= $y1 && $y < $y2 ) + $line = + mb_substr($line, 0, $x1). + $mm['active_node_color']. + mb_substr($line, $x1, $x2-$x1). + reset_color. + mb_substr($line, $x2); + + + // this is here, because it doesn't need coordinates. + // only inverting instead of giving another color, + // because, otherwise, it would conflict with the active + // node's highlight and it's a little complicated to fix. + + if ($mm['query'] ?? '' != '') + $line = + str_ireplace( + $mm['query'], + invert_on.$mm['query'].invert_off, + $line); + + // styling the collapsed symbol + + $line = + str_replace( + ' [+]', + ' '. + collapsed_symbol_on. + '[+]'. + collapsed_symbol_off, + $line + ); + + // styling the lines + + mb_regex_encoding("UTF-8"); + mb_internal_encoding("UTF-8"); + $line = + mb_ereg_replace( + '([─-╱]+)', + line_on.'\\1'.line_off, + $line + ); + + // adding the logo + + if ($mm['show_logo'] > 0) { + + if ($y==2) $line = mb_substr($line, 0, $mm['terminal_width']-19).logo_color. ' ╭────────────╮ '.default_color; + elseif ($y==3) $line = mb_substr($line, 0, $mm['terminal_width']-19).logo_color. ' │ ┏━ m │ '.default_color; + elseif ($y==4) $line = mb_substr($line, 0, $mm['terminal_width']-19).logo_color. ' │ h ━━┫ │ '.default_color; + elseif ($y==5) $line = mb_substr($line, 0, $mm['terminal_width']-19).logo_color. ' │ ┗━━━ m │ '.default_color; + elseif ($y==6) $line = mb_substr($line, 0, $mm['terminal_width']-19).logo_color. ' ╰────────────╯ '.default_color; + elseif ($y==7) $line = mb_substr($line, 0, $mm['terminal_width']-19).logo_color. ' '.default_color; + elseif ($y==8) $line = mb_substr($line, 0, $mm['terminal_width']-19).logo_color. ' hackers '.default_color; + elseif ($y==9) $line = mb_substr($line, 0, $mm['terminal_width']-19).logo_color. ' mind '.default_color; + elseif ($y==10) $line = mb_substr($line, 0, $mm['terminal_width']-19).logo_color. ' map '.default_color; + + } + + + // done! + + $output .= $line; + + } + + + echo reset_page.reset_color.$output; + + $mm['show_logo']--; + +} + + + +// }}} +// {{{ monitor key presses + +function monitor_key_presses(&$mm){ + + stream_set_blocking(STDIN,false); + + while(true){ + + usleep(20000); + $in = fread(STDIN, 16); + if (empty($in)) continue; + + // --- easier than :wq? ;) + + if ($in=='q') quit($mm); + if ($in=="\003") quit($mm); // ctrl+c + if ($in=='Q') exit; + + + // --- only changing the view --- + + if ($in=='m') go_to_root($mm); + if ($in=='~') go_to_root($mm); + if ($in=='g') go_to_top($mm); + if ($in=='G') go_to_bottom($mm); + + if ($in=="\033\133\102") change_active_node($mm, move_down); + if ($in=="\033\133\103") change_active_node($mm, move_right); + if ($in=="\033\133\101") change_active_node($mm, move_up); + if ($in=="\033\133\104") change_active_node($mm, move_left); + + if ($in=='j') change_active_node($mm, move_down); + if ($in=='l') change_active_node($mm, move_right); + if ($in=='k') change_active_node($mm, move_up); + if ($in=='h') change_active_node($mm, move_left); + + if ($in=='w') adjust_width($mm, width_wider); + if ($in=='W') adjust_width($mm, width_narrower); + + if ($in=='Z') adjust_spacing($mm, spacing_wider); + if ($in=='z') adjust_spacing($mm, spacing_narrower); + + if ($in=='y') yank_node($mm); + if ($in=='Y') yank_node($mm, true); + + if ($in=='f') { focus($mm); build_map($mm); display($mm,true); } + if ($in=='F') toggle_focus($mm); + + if ($in=='c') { center_active_node($mm); display($mm); } + if ($in=='C') { $mm['center_lock'] = !$mm['center_lock']; display($mm); } + + if ($in=='/') search($mm); + if ($in=='n') next_search_result($mm); + if ($in=='N') previous_search_result($mm); + + if ($in=='s') save($mm); + if ($in=='S') save($mm, true); + + if ($in==' ') toggle($mm); + + if ($in=='b') expand_all($mm); + if ($in=='v') collapse_all($mm); + + if ($in=='1') collapse_level($mm, 1); + if ($in=='2') collapse_level($mm, 2); + if ($in=='3') collapse_level($mm, 3); + if ($in=='4') collapse_level($mm, 4); + if ($in=='5') collapse_level($mm, 5); + if ($in=='6') collapse_level($mm, 6); + if ($in=='7') collapse_level($mm, 7); + if ($in=='8') collapse_level($mm, 8); + if ($in=='9') collapse_level($mm, 9); + + if ($in=='d') delete_node($mm); + if ($in=='D') delete_node($mm, true); + + if ($in=='p') paste_sub_tree($mm, false); + if ($in=='P') paste_sub_tree($mm, true); + + if ($in=='J') move_active_node_down($mm); + if ($in=='K') move_active_node_up($mm); + + if ($in=='i' || $in=="\n") insert_node($mm, insert_sibling); + if ($in=='I' || $in=="\t") insert_node($mm, insert_child); + + if ($in=='e') edit_node($mm); + if ($in=='E') edit_node($mm, true); + + if ($in=='U') debug($mm['nodes']); + + // move(1,1); + // for ($i=0; $iAm-+0wP^NL~4}YdkF-HQl(2T zp(7na2|Wq6W3CXE6vXkHyJ-wPZ5|R^v77rdgmVfZzs=cF~sfD!(3CWEppD4*kjrW+E4b>|i zaxvdxV~hSGrbBhbVStPJi*)uCU68C5RF0(mZA>TY;cN;=V6}(j>Ec;FevsM?v zaV^!;#a{*3O~r)dm>@J1Hu>duxDyz*t~qE7I=9pE^X4U0-L=Z#vYL!R-u}=Z7YA2y zqB?u)`Nc~zH?m)zx~@T$rpehX9kMsaa(tqdFG>+Ie1=YR;mO-Gqg(!jZyUA+xF&ca zb;zk?zDoR^bHBO1mhGxUM%o}8P9DHsbJxxD4ZmJi?X%+H@%$~I>UHSq$IP`8apLy~ zF_6Sk_CpKuLy1ja*{KhwJ?&njHLI!?q*Pd63A)7Ap15$~b<|0dB9B_}kx3NNECG)v z5u~Pv2F}Y_`$sxXBqY4-#D6E;GoY^EMN(&Z<%gsTWGs|)!tHOIioi?s&JUkD zKd`g0F|lZTqTj0xv$|qwl?A6t*&`=uoV;V zp*%ffx&DD-M_)Ma^PI=`zDiSkk4Ot6vrTJc>5JG0Vy|&8(7~WYZ<5 z9^I#@Ije8}MNe0E%-OE*`XT;e&Xt58Z{K`|?RU@?{roxUl!6^cdBVn>?5=_R)+IjQs~|qVvOGcjHTY}e++Uk&P!M1F>l;Y^`#=9L_V+*f zxc|}q{^Q}>GQ@|5Qs|J_ufA8vh>E&s>9X9lW;Pn5cyt4s1ofNYu%2G(z2jusBjLC+ zA{56Y$}hHuKAWYQHTe$rX{)opgNd2R(b0(qJ(EETL%5-n4i4mL$;r96cqhvJTXFDV zr2YbCiXBe@q2cxZ(cNA6`Sbg!GbFab1iqM)BvsUWd_AWrrR-OCFp5!a##;_CuImal zhp5=7c%8@Y`dPVlwTFAN*E?2LR%{*nIwQUCC=)4yWFM01JBUWhK4ktrgyWMQhbl(?f8l)XvvT9ZGPY=!n4*&N-mE-)2|TI*VR3$ zLLsgCECZ?d>Flaidffm8|^j>o4kXD*z3oTu26+_9=4 z%+}D@ozzv6f+K_cbUX}koAPjL#`b4T&CRXr>*-;;p;|kuivmt96eVF_L6?=E?>37oV15qM(=Y7oik`5iMY4!b4h?B( zJd@Zs#;PWZnG`&h*xr0#HC%{rFBw;YW`@ySymIN%&hC(nxa-mpO$jP&&Ag_Wbm_%hRNBf%Fq_|8sP;ds^aT-ef5_UF%^{rt9b^Ziq-UOO|%i0De|$po0+WYnIgCFmwRi*H2 zO~Em9=$el1Zeeb2l#&!`^r>EJ7)ym&&pu9hUD?FN<+A$0#UUanOb*EvO!)Zm<8}yn zMa{tT+ZtM$V|iC}7Y#F>dz%N^haxu*kCly$va&K}Z%kaIl98piMEH%!{+`N>D8t9ei^d!_MA-n##<`^4o4RYZ=an zLpE#YDVp%dr==Om%Li9g4ewa&yL)KP$e=D=&B8)wbpCj-khgnbPB4X|va-^^&o_YT^y$;u`KfkRm!-=qDwfZmfyn43 zPOj*3W+vpadi7hn2n!3h3}zf|sWM|*5&(6>P&}Ixi(ZeG!e$RGmXJ^U%PCECN zbF%c8$6Q$IxDRwwA3uC}sz-c?2nofvn~BdA#&yi|@bKU&Pk`1ly?1#9ZJYJ@T9Ro0 zk8$s`R!b}J>9x3RtMx1-^C$|18XZ-*c1cbysI)Y60deHHTFESa2{mO|Q3I1g%zC}> zzt0vWvA4udPfri7?Yz9A$aiEg0k6!h#?;hVZ37?48#iv>w*TIDJ@goH+%eax`YEKp zc~pn2cbRrC)4XDAXh_(~YIU_IlBxj=-uX+xhdsMz@K-t#+ElAPZOC!qw;13`9AH4q zdNsVN=*0BOvMN_sY*B=DK^XJsQ`FM#5%2n468N`YXBW@Rq!tolE0gnMXl!Wf*<-RV zz2@b*1u&Rhmf3R*>nFE^%_tu%rNmvrKJ#!*4J$;(MY*dvbbg&M=nStIn3(7~XlQKI zFB;a?(i|Nb$=29I#Ow_W4pTsC>*}sCtE_^?07~954#RhWS?m%uwK<21vWttNVrUQo zJO6qd5gjxe{nAaOYPB=c#92&Vd*SP+9#z%dx%qhu@0o>_p|LU3S}&9mBHp3MRy!`+ z=jhOxJ4GcjPX#+{S+TpjYmkx>7s*Xopk-C-b(}Ch{!DSe(%(dgiU?^Ann$1N3_ji2 zNwmuCGvD#_c{=jA`kC*cmC*yWzI_2xCCamBT^$|C$;rJBW&&FRH|o8-VN$|VzE$iH zNQrilK@3N8!g6cvk00-U{Gg(xUG&OSY5V?tz_Q}}`SUI=E@?)xIU0MQ%{H>JUm*|N zYH}FJjsln>BI3g2{Fjs>uu+D=WkS@RsftGD+@2my;}M`QB20 zroqHS{c~Z#(#(wWj!)M&CEu`+kdQPqU7^}2cdUXyWBdl^-5WPnB4*HN+Zaw&1r>P< zSiDj0L?>L^Zg{c|yFn)-*41G{8Zo-6`Fa2D)CHEo3Vjo{1H8?cuX8@ z3gNZ^moK@@5r$cx+Gsd@lfP!IN*PJINbv1KLJM+p`RUG`J{^cbBRAGYZ_VAps9^7y zG`-7?X=`IoP32IH=rLRD0pz)Zb`ozTe1y(80h#kAk*er7M%sM~S+YwI8 z$*@ywv-3hPFHe7a04Bq_BpePucWx633iQ2KejAf@0s?+?!nd|(n;qvKJP)K==~J6J z#+!D3V)5D!V3&)qs2I!67U$Kk`2h1XAe&iUzBmniPI&Eay4~+PB=u5epxk0VAc%6J zituXmC>`UQnwG}T@Ar<3GEvZC7F-4dRB;>JP^lu3b$?~O#(7!I!{abuYi@Ry<2(c1 z``B0;+$mbq%8pJ47Z<|e;Uf4NnM(0YSA-iFzWAT0V#F_D3% zUnM@c#KfdTL*hJK`~5q5GCvIMx%B1qp~vAuPnq9Dim&DJ^1x1=*S<>$ei*`G`tv>H z!BGBT0P6{os*|Q{C%*DnqAKkYJg#-ysah$n4jbCBQAB#P&&StfpK9({)Ou zPzy4jl%$FbyrK9vfhZD^1D@v`mx%cO{|7MtAHnxuxx@bf%>VtLk-Ghv%09TQ)RWcc6l~o7;b7_F698aE ztE(ABYzqquT^(GHI}8oH=Ld)La&rTy)N*#ZM2?T<3Bs8f859sEjSH$!e0o*fa3IBL z!tqaNczC!pS@kn1v+n$No3|k{x}Gg>&nG4%CI(W0`teuX9NtmnzMt%-T?_R&l9Za9 ztf;8iz!^Q%*H2c*4`M4b>10qm@_q~ z)0cjip-2NT{e^R6n_FAFH=p?JjV=xf33YdO7uDjTOR6-vr!Gq;NhaW z+1D)nZbIG*05RV#_g@2I&-h8m>TO)yB9_%VyMH-=NNr%$Qr>Fd4rSs+aSwwdscQbLpN?Ab3Svr7?h6csf)`$*(QJr<4M>F)`k z+H$x|O3850+iJDriGM@7sG7o5<+3Ua2`DIVoSMW3K5W*~+OBpo?Me`uL#`sNDHkTa z7Oh9Ocfpv}*W-6>Y^2}m2xOr~o+5XvkO(+jqI~mPbo3=mqJa62uYnW`c9Yw?@vPl< z#l((|c10@PYV=zoTO#N&$E4icT)dK7O-5kgg>A>40Yc*5#t%5cyq~A(Rlffx@9*p2=Bo6;*qA>mmi|H|E%R3pMn^04 zSA4ld*HdD|Sz6`jR^-XS|VNVI%dDGqcUxVPT!>S?WdA59eAZTxN~8U!Nl7 z)6_Cu{CSN7&5r~VVW7Ocudj4yXlP(S(9Uk7@aK96AYlK*l!Z;FNWKojD@ndeW7ybv z^c;K?LgH2Nqz9)@Uuf&-uroIotOSF|MIG`mlw||{sr|lYMsIq0Qi7;cO=6Bou256stj)c=ax`>xwOp(HD)K_hws!x4A>))3RS7?1z>G;rVOge+*S5}y zU%w=6o-5cwQ=nyLWYizqWNa4ou(#pfD3yV3fX zi_7lr#fvjQ5?F&9tE=}9IvC&s>!@Z|3sA0WkF5Rj23LGT=5yz}tmCMp?S8nCRVe1H z3U;fwFc4>r5yz!hdWugRQb`W8-I&5Eadsqpw9E z61M__f)EBpMftgX{{?bTlI6NKMn-9%k|>xU#Vu6zed?$hP$yJQv4xDN43XW~H; z87t+@n^~o$rHd1OFd4Qxbm!#dr+Pl!k-T6^%6xSP-ksS|q!(Dms;#Ot zxa!={)){I#fSaYHjBbrfN=h~kSNT18;?O$i#@dn}8%v$NuqjF6y zm^+SAE$5n~G zwVndnh2C-gGhPD5(4e5OQ1}XQ_Pqa%lr-iv$%GIe|Mr=Qs^_n-D;~Q`5gMwdt`{4U zkSytR%AB-kEUTrIz^!Kdv=cG72~V;MUVT)B@XKRRwQ6I7T1!ds$lkONBNRysj{baLiM`zLM+1o-2%+Qlj@91Hh>cpr zEPlCvV{2=#u%N(ak(C?Z(rcI6KHS{uWlzn|&j<3Lh%L~;^mziv&YuqsPo7>_$sJto z>+3^K9P7tup(gI@CnXcM<7f>%c6W1gxgY@=Tt1j^r0*dz+`?ixQ_EwuYuLudCXh;% zM~^Guds~}Ow+#Q4NF|O$0WZ_1Pd@<|r=v3(M?gK+&{$hr!?kzt(ThK;*p`Gejh5-K z79M|U{9VW8nDE+2;?b+GxBv2WxC&WcpiFkhM8R9eeS{O{J2a5a44_jGO}C$0GpRJ0 zp5AjfT@

src}Qb(71BgO zbHwUmDVY+)u1}U;1mrRzB7$FwBI@D{P-!c5ow?uSxHBcdDkYYoxtTtNn)#x3`wfij@Mg(ZVn zT3Yf@+`K+N&>7!9G5!p|hQPOPao8Bb)1k;5_h@w$o;rb}>cy0-~&&Zz{dD#A`^8RIK$7qBl zcT!B6k)vbCx97!IRWMtE!on#M@!s3UrIqrG)YKyeJmnRYXCdioIR<|7JmjYX11~{l zKrO(%fw!EW)-tyJO_}=ut7a>>fX!ZlvU+mKGEg92gu7kD$*`8x4B<_FQO~ z#tmv_)gPh%qPFc3%&uc8|JG4%cFYQ1?#X__4jxR*%s@cE<4Lm>2iC1L|4{nZ!PpsZ zrE=2nc-9&LA4Vj;v$;9&7RgfKW6H>|zP=Pf?6nx>1Uj$H=`Tk|M-l5DhlhYKMI=&- zzi7A%_#bZ)-}m-z8fj|U*f2)7;dJ>DpY^q&A_6~$sj;rEr@p?vm*(w%Cx7=^EoW9- z>kKx~3z?5^)E_ROcxA;c)YgJ8a|$sp$i;`Wc6F_z5rAYT(6L-}UFaO$xa3-HC~t0K zGiX_%&e5uxC|u!(^yO6LsM@EYzMEcOUq6@JPeL+o96de#yp-fKV&S3eBpgfQdsFcv zZp-^P&p{i1&Z22Trk=YLbQ{})yM|Eet`nMZq*#6FK+!u`hvI(cqs=~JQ?+CP?c za<(^uh1scf^Sd7oBk9WY%aO8$#|vb#LVdTtzV_!SC@=0({B^IH^#>gQlx|JGaQ86)ANx|3RI<#xC-3abUm=vCc)oud-Lbv0R)WSj)wkW0e zORSP??d|PcoFZ;>EnS0ygVJQ`>zM%HG_|PMJGl(5ID^_eWlJcO2QcTBe(4Rc;q!a1 zfDkSpd{ovWRaBUBQTj$~`ypi~mcsCMI*W)@CJL9ihWZ?*Sq}Vf3{6gKy zsf=oNIqT!X{5%V%#mU#Mp{o{AzQiah9X`cz0K%>!%^vMH*p2Ufa~rGADpm3-#sYBAKhM5`OPv zm9z%9P^#IwD}<-jmM#Vc$5&vJ|KqM;I;LDwp@~Kzkj2>}?Fv$E-T69YrZ_iz+}~5F zTLGVhoNB_uclG?Rh|tiE2NbNgZr;3u)c+4;Z%!^vHe7dW5^G{Y%bG;M>Ij&5*Q1dO za~pyz>O|LMbNUCtd}D z(x6*8d-Q?2L+jR!8waB~#s4n9$L4Dnsi5)%8Pp}cyu6fQu8~<;S!$zlWKY!9QO8l4 zE3Fmfy2*ewP)VEE@x>}AcC?4BTULw>HjgTekOxZVtf_k+9z6hxJkS^_DqPgnW2jP7 zQyWGMbaZaUv}tg8`QQH(OjvR6e54{;vD3Y@nBpZYvj0q0*3=eVZPn7hi=%)5GS?@- zUv%q<(i*M6A?YviwW8zz&@k*EdE( z3rud)-9nMiIe*@hzpu6&IqVKx1FDASx2yFR=9V@7LnrKP>UT&RJ0%e72MPE_x78CL z31)-W3&6inv9f8smKDCf_4?K#%`Gjm8KqOnr%$Gq+Mn33PTgPgvtB7{h&`>1+eCQTC10$okwwN}cZ#I8< zAou3Y$*$J^k%58M%$|AoXN9BHH8Sh{J&R%(N+R@lmGbwkQm}thhi~VfSpZvw2M-?T z>rX6wdIC%ebMx=T-ya1^zsP)UZeam@ADEcXa~!EK zOIV*k5F%z8jb6%%iHV(~n|9j|*rNFf#*v%IV^JgB`iKm*YDjT3b;1;Q0LpmMQzxX)1Z@B#D34L#0$>5_3hAs|3T zFJn8nm2fpyp|4+{UU8)Nxc5t=TuTJ=IvSl46N5T_o(hIo!O6+VdhIt_GqVDfI8QMi zi=O?3IZ^xR_k>p`RHdrn#l_}}-TFZFFsUb$>FBHu`COzGaTw~PZ{f_|v&NCUGEBO5 z_wFYmHt>jw5+T$F8p5(*x}IV}J{yxh=_dgqeEILP=fpQ33Sbt_@G+etv2}Q+M{J<~ z$3|)2;jyr60=m4LTS0G{QHqc0=sKgHwx4c#9~j=EvY0pnZ`;B4eXRm-zT5U>t&GG@ z{dN5syFl6J;`ayQ^Yio7)89x7%VI<(y}f`<46Fqv(B-FxS`Ez_d)CHcWpLm)KIrK|T18)g%6w))l9UQpF zeaMDl*Dxyy43L07NsFDN7N*bCx1jiezS>&ct5+muW{V9C4L!p?PYkD?XVxLM6t6{( zmYK@R$$^hqA4?Vl-N8gv(u&I$Y^iPF+jigyzIFhWS&yj?{KyCr@VCbK7zmo!vUkeV ztdEXy?XOP|_T47&2XjJ`1}SBxUGSmp$_>9_E-1m<{_%8PQC(rF_TkV<6__zbbNTW8HrDx5N2n>cYv{0G`q99<;;h3;IM8p7 zkbH#whEP9Xi6;qR7}h7k35AH3fWY(m_+F$GzGqDS@`f<0_wSVD__!MyNEI4)ORv^A zzC?pcx~Wx}Ny2KqZVhVx9b9XN z^LhWhrK|lXOZP9mW^IS5E_5y-82U$#m)Kj%-MD=rz;Vg0!|o-E1D{%>+->SaD2A1p zIr$Tun~#rxJEQ=)gN-nj7sP{{7H&{VPn6?`ee30UBqpZYW;~(q2K2t1NPxF9RS~WI z{pvYu>;c&zD=-yHB#ga7LzyCU0aP|No_QT+Gnqu)jaIjy8`M#m)}wIK*`mKXt%a?80rsx0T$%)GqzP8z%i|ldJ|T(1+NepL~w8rNLDOpbW0UF&Tz*XXJC-hLTg)S zTToyF0OJA*!GZ+Mb2d7tHSz_by4uwwwsNR4q9TM!I&W}jc-YJ1m@YCXC`c>M#@bqN z(Dr`PFMy6bE3Bm@lnK)^j(_|1ZB0#0@0~QjK7pwEA}WeJaMqLj?QH7o^yjN0BHH5O z;sW^cKU<31s*$a%Ow`mat@dtiRc$ex*R#^WMxX39JaN+Rj9McinFTz*k|2Sx7tSDB z%H6B=FGgjDWODecRru-9H4S`|Z1J~w8hZ*E(X2o}x>xav(PVv$52TI^k2@5NWLxuf zFu_V3nBtv8-#Jm+Cr^5vI$%U4V{aWb+!@U%Y~5$+!Y6_BcJvJ~dFzd3Y`ol`QsrsV2qjsKckz!C%#IB#caDjtDq!qYm zdL9f%1q%Rjr>H0cOVf}RRn%Ji{ax^7qbYZcU3&5yJqd|(lYaL>xTC%OCI-{h-5sTn zF~=FEDC!bIJV(GxmL`Kly*oSC+Jzvv0YGVyvkWBCn9E?Ea;wIF3U=*dImATSM~Z|ZA8TRLs)0$n}!C)foh;{ zz9D6WyQo=mq%bA~>DhbPt>WDgt!-0|XTZC{LB{FPNsnJ#?;!Sl$Gp=*X9yYPW$E+` zGwYG!oa*Y)gC|c%et--bZ+ADC?FiTF%#yCPb0I*<*vFs&^U$eYVNX_}{d2z=`TKq| zw^C--6&4YZTTp2#!c4r#pqhds5mn;W}v7Sf&4bp*<63k{>yYAa!L3HVAN zv^0dn_OGm18WD(QROCBAlG@w4Cg2WbB@-XOWiv8z7MOIp$-F!~c+~ookVQr2_H&LD z70mSVay3p>6-sD;B_<>UPz{kc>)F+n%+~Ewkdq@AvvRx>#v=cmOM_!;qzdELg`99W zHf-;JgHj~Qd0c>LdZ7MO$B;o!Zlo?0I_O(#~AMC!~?o6w0ffmt8xOCaAx~5uKNXW!& zv3#HPii_N%N8kOV^srqbQ?jye5!E%v_6zw`FUR2}ZnAO&Y>cWZN7lHwn-(z0FT0`n zuecf+8G!^)C#x@==yo?y=dQ0klJMZKt|NfKWDZ8`t&K9WumDHr{b3_Lq-h{0hP|P2 z$oIJUwSQ1uU7c)rSbgeP^&2GNRjrf9S#j4Csbo(xXAPvU6tHXlG4zkMwME!gA`%jG zD|Brnl7U%|++DSyX|mvgW$w-SAQP?ZruG{LS%HtH3=^&>EJT0rHJV@^?%YdY*pC~F z%#6@e$2%veI-#smSph?~c$u#Xh1t~~?&TF#Qq#r_XlPfJmyZXqTB3=tP2{n&v-8W@ zLV#Q_i+tDC9@PqX29)^xm21J0+g0N)L5(-3frKd{EX<&H?uu0x z0?-2B_d-$S!O@XewuY$~QM~+9a%@R~rO=d%GACylNIbZMKo#>xmX^6D4hijMS=cAM zadNV<)>A3=TQ5CiWfyY#bD$}Px`MV<5imnbsA~bHQqqT+xxly-U5Jpno}t&lO--Irs_Gygyk+z6%r7e|Xscs4?~rnnf}ANj zE^g|_j|>b%Ikx2hG%%3wp)&;iCpNbutSEd6t_KXXR$m4N##n_6DnKose0h9JcgMZ8 zS>r-3S9psC=j5b8f7Zm@@-jfB7o`UV1{hFXKfmay_4j?`)wvbd0eb%Q`){m1wJoK0 z#k;a{@aB0M3K_ttgl(?@EU^Wj5cBoT!H+B-^G~T}wVGuW(yQLT|E-Gz6`BJX6_EC|C_1D4jLUsFkE_16)Ob03DLybGpx0pT|**F2FW@pHW| z?k-2#8BY|2ih<-7BGu2Rqk|7Q)ZA|vEVDh57HMp3oUe~41R^XOn>PrqwnTPxgRspf}aLpM?4k@hv@nZDXp+@1&E&bv*784HFJST5)#}r=vm8`H? z-k5%`rs3?baQc_)Mht_E9UXb{sjANDQBo4KOG(etf7x`)LL}A&afY^QwkBw=kh=a_ZQ;tjemaJz8S= zyErL4Rjb=!Rorw3gqQHxT}B~C zW9>IZ|4M7w9yS z4M@^3FgS_{4VK>RT5^W|C5q)3)ja1~Q zJN?!NwSh}x=(A2Z3%PXZl1*$oCkw$}gG*ku=KzG29NPXwJ)am%px%Zj8hCCUetN=A z$s{r0i>3ozaeUis-tiDd*v^d)(nn8JO|^|$^s>UT3kryJ!Fp|C_*{KXj>YPbO_^!8 zyZMYiGpu$rCmG?Yn9=98*saACtEs8EygUrwe-2RCLU*#H>)IzA4r4XsUFv-(0C4Gb z%L?LI&+3;*5|0CdL%NzyD`&i`h>E(>dh2;cVae^y1`yLM)QUroSBqb}_I=hte8NC| z?pO^pw&RtY_4OS5p#L?nNC0Pv5Cy`M>;A&+R{$PCk;8URjhmW+OF%$fWKZgc{x5Qj zl)iA`0?<`PM@50ch6`}GnWYkS`B(nu9vS4?r8^+!>#`;Xj#=e|W?HI$-pl-teD3t}5vdWd8f^ zjsVEZwX*tk4rKivH#%T+(Yh9Ufsw}J;T!-y#Auu9`8)2LpS2ASG3}IEKsjC-b`tiO z!?@zN#slVBU#7{mN?)iIIVE zG?oeaYY3%z<9gx+xY;C>$f7=`l)PxnTv#xjllyCkXe)vn#2`Q(x@U0EWIi_%Bv0^xo^^f!E1@yX|IGPYJUn`s4b{B7yl^~d z%6|~#TU^yAJuxmINdsw8@Aya#addQqK0O4hqkY1HhsK}$_dyqr7)V$CwtLb}k;fVRLeQh{ z(wO^N8XC6@N;_Lyy}*&{)c$Oz|9R(5+7Mw1Oz3T~kZHY3*pKJj0dVv|^l zQH5Gr_C>Jz4L_h*L?UKS2|TFXoHY!f+9)_^ZP&~LYa@!FP;DY4{N_*U2R}-C`&JgD zIxYn_mEvD=!H^o0);6)&Jv84fuFBHro@4>5p?rGaOJ@9I6tmLPWzu>{7h=ZXQAXdLG$Uc#@g$gEeR;VvMq3YrXqh!~6Fms&qlKg4Bh zyB=)zf($!2;O6G*z@gsWXML{YtHccb9o*3w0w***GgG(LPrOSn%j09mJO~S;syLt3 zK23>=@>p?RiCmnQbbGdYwGgbw+DsJS8%!qr*)5wFR4%9>_b z{Q2{!655JOJm+B|1OQ zC!PlLLXVD*JJHj}S646IVjR|x^2@39__M!YWxZkR+nF@Wr(gP%Hx8^$_zLld9=V4% zWA|5TCppxZ9z-xxm zNM5`wumZfj;WaIgq@qi0wC`h``fgmhdKOVoupV+8&I9sWx;-f^F4v*VNai>Nzl3XQ z8XCCvydu)mr_Y?ZK&%BIv_M?PLeU}=(~}4j`FmUkUHG=M)z1UM%i-bY{YfeNs+JQI zi8(&KTEC+BD(-FX-M|P&=V$tyL=gRoC9Qw?r=tf}@6XOacuXirdBc3Al3mp_o`Ij* z<-D%`EM*sG=iB{A`YTuBBqYYei$Hb&;p~bUb8Wr}zX_Jgk!j}yz9L5=<(yvCUd%KSH_EL|B|gAQ?2M?f||}&A02?S*77nv zU=MHKp2^BEq3fBp+c_TmMTgws1b6QmL*HKYIW;h)Ug-hz#9@HBQIpGSuJbaa2?UQg zl|yzh7(mBZSw)t=M;W#30k3@Oj<>r~=OeI+;lwE%I>mn8a9crMG&VL?5-d&bI31D~t0KZjLRY(GfOYm5!1CRaO_Rxeh(`a!{jm4Si(7Qfn;$8`IM__u`CBH#7qj z&1>ZKS2yHixYcXMgsT4>O*4&P71(n2#Qi%SoLs^qK;M^RF*sRqAQ?Gn;zHhs-qecN|`>G1?u6*HJpt4ueIbUyI)R`}>21 zBCf6x8eGiK7th*r)PMf=YYi!tG+D5W5*^fb5|6Ypw^mYAR#iAkN%|dBNs~R%(b=u4 znu1V%bs?Iy;PE>Qb0!72d)zv4!6f=O+)9^D4?F1z9D}_L5gq;^gD@P4r6Cz->3&x3 zakJN~)~{zh%GeD(lYAlc$wMMLeA@i=T$AX1upox1nu6d1g?pa z3S}jx?0CWKBM#${Qi&anBoQ%~WV7bzrFnINNv^sP1>w}`&BWjL>C3gAD&8T1UGc0n zGbAl2@)w3E#_E_ltygH_64s z<#ekBzrf(u_6FVNM+o~LVr(`l&1CcN$>lTw={B*xo(=O)zZ4ko1#jm1-C9Yt*$@_( zvrO>x@oW%E>y#blaw)S(Vbx)o8T~PF97rV~#cKU*`stVTupUy>Uj5!VSd%r@uKTXW zKu7XXc?;zb-6H1G3Rsrr79Ub3%Z*)2U(Jb@;<1T-y#JOnm2XV1iX5`HxIeMypIbhp zU`KU|$t6-of5+)ZLfhiT5GA!V&dwmhA-_IksA50mvb49?&`kejUW%Z&tQ@TI$I+3S zW5*17)3(DeEyvMlT8NB#=uUOOab5ZmQk8e0m@sw&cEtVB4c&i9T7%>90bj2Tb+Pu|-C?;+63{ABy12z z%7r0o^6K#v%Le|?^;l)>3(NVo=1M#74U^k|LpeIIRx5=uKNe`O;zdYr?HQ>0&x~Pv zKe*iN9#k%MI3BzpT)^X*6YqdP09a?@vYl#~htUjaZ{bF2l||EguensmQET^vo_b}- z3Lr~=j`B9z@%^7Jv1(#`R!@=F*t?F59;RwKmh?>&9!})YUX#9s zZQ1X9m`IT@iPscUpQ9Om+Lr!S2ckK-Ck6>9)O_hcFyqx_3r; zq9$!zY7EA-EOe3w<3opNR|^WOS(Ue(e$-uYyU%e&`kioHQzv|4j87J?Su^@2)-h*N zbJTG+OT|aGNBd@OqNnJt#Z1)+V!{Zn=4oM(jli1w`90*L@C|o9u1cy~Ph`|fr(K<| zj>`|Ex^jM7EVC)SQlgf6)i#N@IP2rQ=1BY-;hl=X5m5Wj? ziG9f*Z87UveG{Wawz`i^diLxT9Kzox$b9){7C>{uz|$z_YQSc(m2f{2>@vqe+~t11 z9+|_zn~50;S7vsmpX22sJ-&*W59(BgCJnM;YDRVMK8Bmst!+od%AYrFSs(Jf6aA>E zc9UEkrrWk7O=gorCDjhN0N8^W@A=3&RCe4qn)*wISln5cV8GT@iW?uHn8l^je8XEhM{k%}L_V74|kNF%4hLVm-Dxafbmx|)ZSP)szGQ$-hW{^ zo`?CNKM6jV%&OY=Td|{2$7y9d5!A;IFEgmv2I?HpKNRZjm-04O8#(k@NL{Jhsi-kH z-_^J6$sO9dj`p>1s~iWV@+@Y_xG=e}n_Axt}k;-3u@B*CLy-M6m^o#HZyB?nWz~r<{5* z<0L_P*Yf(MExagpLD*u$;7A7&%i`Mgg5`Y9id2vF24CzuaNUC%yJy9m9D!ZfPYE%2 z>&sPfQwKvg|YLJJf+W&sB5AYJhOgqQUISwzrP}Jcg%Ss z$^D(;993MVh6^mKertCvk2|ldJx>GYwO8%SRiMGvr(h$TA%4Z&Gj6!!VGxCE@)~UeIS{5O`atFg31T58Irr!ijZCt(v^*;kM#o7zA7Yz+h7py1TvP>vGA~ z@ysUUS0WOS4l@kGiVK~09t!E0qG8l&+wrR{8>m+8_=a&s^<*np`r=AO2kIcM&nqUc zy3UVq8jtEplq2*pxbNFiS~gR#RRp`DW?t-BND8w$e@oGV+JmByGlwH5Dmq$^1u?n( z4(yVQyh1w)-_@9TiYZ*|+}Ub6ai2|(Ha}aKlWPY4xvEowuqf&*Fal_CCrMEFyb=aHDpAmbU=S-T@^xPX~Zc#9iG08fkm6NXr$kZD=TfpO;K1NDpL!NCERQEHrbl85;0x8W`yVt+sD&tIG9gf|+ zMgCXiG7AF^`L6}4cJC$N-w4$u*R^pzt5Ba|WQ8)$Y2`E~PJAIB=|b3VCq$AsGZS@7 zc=Wfc#~bA_0MWeb=urp+!wNcP{I^)OOvni$Je#hva4W0(LHEsvbtGTwtln=lzwkBT=S82oS9y;N9DIY%y3%haNPgRwA>Mi9`B@|n34GLc z(W@D-mzIOyq2TDRo063Nx+#e8FCv>U7+haYZPr!+g>Xr_N;IAJ z--DQXK~eqD(LhfB`RkHiJD;R{#yThrVmjtyIOT%&E?ktpc0J2_{6N{prhp1aaeR6; z#>=JibFI*|^?6MLgZiJa4hFwiun6f`a5s{dS6|Efz(H9#9EqG-(-8=u@_M;%J6c-t zWh_7bj>Yn_Bn}V!w`~l?Pyx1lZT2CigY%*4Im!(^1A`JT)wK_8y-o(7P$63)qeW$9 z7)A<1Av-_#>?ugoa=)3BZgu;!mu6Pj@iIU3XcVSwzR;qaR%ZMDXm! z@G!VP2alvre`XiBsR3AnUv%peQiK;oCcr8zHgdR{33BRK!*X^r|^e>fZsZd*JtN0w}dlaW=c0GbDgk5GBb)9KLXA{ zcar=SX&?}Q8)(!bJP8MLm3BxxFG#(-)X@Ps?tAxoz_RnER!*e^!QL#rVM?fO#_(z& zm2QD6Sfs;S$P0XIptzrB-d)E&fBg6d=jsq~AH~I!g}l5-m>5q0otU#dVRMs^uj6$~ zYNq#Q@BW0_6o|p;-8tsK4lX;R+}0!xu^SgJ;>|jf%6wY8jap|JTP`y(l^ZRh0h8WE zGtVw9*>CTwYf00xOloJS;mx|nc=RfBPi`QgSFhf@Sv@m4)A*KyGwZ{LB&37}5*<$; z5B4hNz*getK2@k+2l7OfY*NalrG|4YlGJ13#`r84>%shIVCcVkx7jEc!Eo-3vccp) zD~q@<4qhM)?oy*#U`QCPc52od`^Fr!N@Q!h?NVMt{T&a;E>429;PH$!8J}uTnOP4x z1gzf&xxD1~_}FA0S8PNwz7Z?rI}^o@%*f0f92iIvjnM@fne61TEA zT<5Z70WhS7nECUcnaYVk%LTV;5%I$&M@B~atKAIv-q|VUe6R`2l-g@;WdV-JQ(av# zyCB3lZelH5R61`ppKMvL^vJwaLPYMe=v8U5ix)5QUW#r^#G^Vvj#p_`%RG(J8aEFG&^2vg9@6|!5}6#}Z-f{mG3R>K=M zj4Lptc4=;fBVC?wan%gi#j2Ru+4*%HUB7;PA)JDi_q#EFMyZp7LvThr$P3M}dW#-VEsr1~{oqYDI%0-wh zry~bJau4{eKpW{b7o-aV_6u;)w5O8f*rEh+Q~3}Z^Zd+A)5C2_qn9a>VNCYZU-^dD zCd~PTS5lw2^(j&s^XnvrA>+@Tc@Q&KBEuCc%#sxgy91Ul86M-56cpNzc43+tT5sR( zrAS!3%$w`)lhlQ8@;G_g2X+IMU~L54y#nZl)K`+{@GfyLu7f4xa0Ab}`s}6g>2Lmh zef+@OeS3~xLrpEUe;M3y!NepZD=4Ts0f7MP8Hbk(w?bcY@EOL$?RQlrwnF*4dwXX> zWEzg7lh0hg4wlFB6`XW*aj}>2k77>&hwi8nAQQUHHaVRJf5v=Dr3h`ZETl>)7 z4eFqlh88)bwpQXg!Ru%Wcks01eBl^hfmYt|s*jIPg3|L$NvU0MTL7$|)MisTRLECT zubK4sag2+J>4(-%#S7$s6c3p0Ca}8lrlrlOv^3d=het;T2hNNFo&zA$n=txZx?xMe zOf3sct`^QFj|7Pdh5410z7iw3;K7YH;P{KV|M-fY@Ikn&i(O?)CC+ub94#wBh6mD~ zZS&h!$KEdjbu(75)2D203n?W9xuhUpK(xVeHoa{Xyr^Jeh6%|8Nm^h`ypB2rzSy^K z_k9`~sL3C$&KfljVnOivrO_OR5*;gbI=EGZTFyzBuV+p5_PdLXV^vkCz5a-Glv}0M zugAV>$rf>ITbUKCV%?==>RfPjdAbQKWk zQbR|nBE5G)Zz1#+Aj#cfX3m*&#`8b_?|%1Y&B|J%lJINqyZ5u7f_Z8IP{~jtaArbp zU+6b(jc;z?r&BBzjf_~aKvi4@HVk$qKJM6>hq`)?@}RR|WphwW1xTGP8Qg1o?QS9% zja{4Ob(^t{6LZX69q`;mC@IJVW6^8MQrK-EHUjNgN7#!Cr>>!aAmAcq{b?e2^j2Z} zuyUOBl5{ay9(}ep)7*_VuON8IPoxyWVx*d)1g=qaLy^<(J6-Iy zK?fq?ky)Af>H7Krwk`ACMM;p2xjFnu>ecrYbsOI%pW!28mKYkA4WYyK&lvP{cb_@x z+giD8RS+ebG{+5Oy-h z^Zc%;3f$SKB1zkD;$VSXR73u|ySIOBLZ z_(fr4@Q#Fd-v9&5c1&ijl%Q$18)83~i{RHPirfD{lHY&!_kTt5ecw^-HNdUV$q}mp zlByT&s3B3UYGp$Sb4rP3^U`qW&l#G`4L(EcmiBrPU+fF1+}{g{0ou zc9v2R@yPIS%m{<~_V;^LWW?Y0sZQ#rii+|e#ru|)gIB<_>RsCivw2TedR7k={guho zxy}gQU5z_;8eT=&1NU?qhmwaf%T%}0xRtRpA+``(%A|zz`T^pOdL+H-!69Ew|6ieG zWL?|Cl9rXn4I-@@*AAyTw0-@q7<0uK^dl!vTH!avnV6VJlRJLM`%*Bh4(4nhB=r(S zjLDd34*&r%if;5ujWt}$A)Pnm0h&jy8IGvo-E(0spcN_o%2cGA=f7 zut!n;{$$-cdTYXeZ;u9xwe0`&?VPtJER9@q-)I$N$in>RNUZELh*fK^JV?Sf+zbp7 zKH2-by#T>AE+H@Y`Q9%}StAj2>+9>l(Pa7Zm4_YOkxYKCNc8#eu<8+V|ASD+UI>?y z4SvN5iW#~fDXC#bFBj05`A?_JqtSj;^x6)KLm*MUaNNJ)(Qz3yb#)l$7jfzVDImPG zR0nQ*Amqp!vkUY?p@;5W`X-sBOy1W{cm$!X6A|Nid^inlTjtmOvp_*GPiNZ>d0+VU%i; zy#Gu`;iG!Ge^oQT5O-5Y?L)Z3zgoT|)*2)v|AU+LKRqq~yNBtozrcTaIBn4REFhN~ zB2)2C!g)qkhIjU!K_6w~+L#jXm+>n0x*ybTB5>>$335B}6*b8vkapQE%G*b);yVKE z>!bvyNBo=O3MwO}S9S(X1t@>-IK(enS<)s<&(6D`K4@I0!^d>hH@M*re^3fS?~0-7 zXTTC57qc(Z9`jxI0V7~21s z)aPAkDCM{-bVtn!(wKF=Adv~1+g*&3dq9%}A2WJR9nP(-tr$sjUur|;g%K!cue@!I zJ`)xZKT?fC4GwZiS3VdnQEuebKS6XDywgroUA-F-sj)CZBmUTKJ@(Xeq2V&9srx%v zA1p+U}UK^ssTh|=2pwi+8dq*G7{ zJMF!D)p~lm>q0`X3Jn(G)iQf^=6X9kv^pLQX^SmEVP(zj(Orfc3!w6*PEk(4noOm| z*u-j@r8Hm*at$KJ&xJLxDMz0^e%#Q=NEMj(W9T3{Mg5pcR;Km2b4H?YwM!L9qnRv_l$l2uKYG*>jT>K~@z+2~yR^Nxt{C21 zecn4yu>rqfv(I(kqel+U6|7DmvT=`apF>%)>`yUzI;<*7W8M)~(+w>9pcM3`CiY$! zhTJEZlC4PI-nq1dXsKr1Jg9RU2^-+PSg1|AJ*{KSCC=qp;z%R&>Wrkod*hH0jZD1B z6ldk)Fq4}}KJ|GpZbyE7YsFKdf$q6J2>oL63?1M@Ks&$D+tHi9^j88dacJ^@3 zZh@ddgl2$F;BDV)uXWps$#Lytw22Jg}G8ZC~MQ+?!ry?WU zHkqnNAdFW=iad||ds9&pAM(W~P{V?wPO7m=YwR6IJG+IBS}ii>$j0g_8Kl*)*P^bi z+>ZuddvWj zE4r#JzaiBnAu9Puyb-xdw`FtJ|yu)?xctRtuPI3 z?daDw62cmtzSeasAA;pSZSzKr4>sQ!!+gGE9YW{Z(#y|Y_SIkYe@XgKTWm|{* z&<5?D0X~}J+>*2>lEmDvq{yYv$UHJmxSxLC)^;~MP8_IWRUhShKb$;qqR7Gaq>Lsf zQt59!OCv_t*aj{woivbSL72_Y-zvnbCU((>dfI>y=&#t{-v0KY{T8pr#S@#Gn6*_t z9-7v_v_?=BMuVY+F0XKu!m}+>k2153%wYrYQdkL*SO5o1YZPoDD~844UOHm%l=Nij zm8v(n%NN`q%Cf5O*mL@Vw6?3{uKl2v9;7THm5$in!`gRtURF_2`jldv|M1sehB3De zanLiuToWwkf(=FwQbXDF)SS%89{u^Qg|uTDFpDT+;F$GFop2BOL7yWKl`(Rc=@b&R z_codDl_F1Bduh7^R!TnTl-2E|NXGBl@dVv(9(vafzW9CH&Q!_V42ZKFuhrmT3<0u72(zT<;d~`kmF=8{lEsW#J6{{-m zm220`*xA{`IhW^BQgjdvV=S_=K>@298!IbwSDvhYzU~(+C;^QC$L&m5Zp1?0xtMPu zq3`}YN0gS9S;2gbe@zg5 zC5Qy)$o1nj?%pjj?~Q-|Uf$dVE@5mKc6YZqL0E$~vT}1LW;#aciYW@85EmO~h~M#W z#nu8hMfN7H6=MbDsq(V2*x0kJaa>lzS(t%xS0ipf`}#sQzyw zciO9EVEu0_RUXyn+@un`=_~e#Y&?qwrwHGg~jD&OzW(ChnqrMk^FJtC4 zs5Msxe|bv(9q!rQUUnfNMNS}EN(qi_uhg|}0)htuq+y47k8I3Rv;Sb6l?qeJ9nV;A z>cGM&D-mtOQQw6j>4qGcE^al~|U&E~aM zJVHEVwQ|mXo*T_h$0OYFwnsCY$+?6(aeFnWay7r;EIo-8PF9dB)QV31{IdyS38_`Y zzHg!TPh|Sf=I*cc=znO>NdBeb{r{&MylP$mweHi>7`5}J2-K8y{kmhF*oy`^&tip- zuMM&c?i&e4D|jCx_Ncn60F@694S7>@N#nOtRa0UdWk59OyBQp!T(pxKsUI5_9bJe? z!632v$Oe(6B9WtKPh&?9J|uV$UWjwG@N{&qR4^H*(=qZ6M3j!1_=oS%$i(R*`gT{V zX)BFSps||!S7GrHu<$3#Yl(Zin!dj6bW_w4Vmk*`s~_^v!c#HVjNh~tt~{lz9KxR9 zQr3_+`8JH5oIKEqaJ2xw_PNN=FH2R(N5^g{fj60yo~!v8$ko7BNnf9*{GwV(|vmvkj$x>QyDs2rv0^n5p|}? zD7Jdj;MSyB|FJR8#zrouCI2`9QMVRy`$0}h^*ZZR)XC<+@J$Q`lOicqAWKC67qnNe z6wtE4+L_TJW5?pMv{M)M8na2OcQ?clV|zcs30F=Zg@S!`au&LHnU_hEZ(&JG1wqoy zSop}ywfD-TXOJLMCf|Pg>mm=7yGM?eNwW+0?%BXu`A`lPm3mwFjwvy{4b{#RErhEd z<>u}dZg1pGa=Lov=IZ*(;P`Xi7dbeh`0ZQV^%3(8P`+gJ^D#2V4gQM480HJFQhArvBU9vYRkOE3vxGSPc%r&o>U&zR~ zW$taG%%!!Y%_d(K2E7Ph(V0iyTr*6@&H7JZ7A#Ix?v4blT^#|yhr!k&zuTypp=N=AvZ2{zz3Ap+BinS+%( zSV!bYYQrM|x}Z<#i|k?V>Z8AF{K*CQYt5PCH~~{6Hdfk`C%a)4WMczcOD!hjFr4-o zA^Lwnpp|5uoTe7<|p z&@kRyS8?t=cefdj(D>ApC_dCrQbJ+@Gf2)%^sF1cOB416tkIzUS7cKi#T{VE)*dcd zA!E)@j*JIn5!|jaLh>5CGKp$p?ji?Ct83S;#ngZ6*yh<73~((j@q9ef0!{drS-V|c zz1U2oHL)}L?Zr0_mU}P0nXp8H@X395(-j*Tu{$#}0~Rsx0z4Bb*kVcRck-|UlAhK< z^pudbxuc`>*~+ge)Wm)m$%kggWjc+?q}~dP1XWa?ama`3q}GFl!w-Ol14jRiJ991v zfoK{jEgkmeO|BQtm^M88!-sNMY5GdO!60GY;r@XGl0m;2fvp#`V%SIdHz$f6&YKHd zp&Jvb>wi?)+h?9yQ^p+DFd#vx7>rV>%#1lDkBP}zW1==0>OuImFB8;jusaiNDuIQj zCoMb4n5Eo5r6_QI%2JxDCIz=2C_P3D1)?@FC?*(k@J5>dQT-XE+QIx#m)F|G1m+cL zP9rp>$d9=yWMflP9HtK1#-D}Bh_|WGAU_og7!Yb?gCUWlib?>sX(M{Vl#Oe`BG-Ux zf}2BhX>sw%h!ddmYa*H*k18|!P;91+KL8UR8s;rn+C&9Kbf7nYA{q}$LBGYtMYnSO zl+J?lEtbbgiKyq(E7VK~UA8dEHuk1R0^q`-`0kR_#oL(4$t0CSQn>Z?Iw7IxvI;7x z_i9dsNaQIQHNdsh)Q-M73x5DL+dR!l@Lr-*QjjmuPHaDYyhENh(qKi%CZ0_>Qw1^$ z92WX=KH&XplFk)~iHOYJNW6*Zsqa+isdsg(RIhWL{NDcU#cxhai-Vcwy$6mRYc{P+ zwlz@+!xdT~vf&2!*HSe2R;K;-_x5{9qgFKeR$`R2_2Nfod{1g;GJPL3w$Tb~scJpjl<{y}9_qxKCZev`n$YTh zr-2{giEm(M0^j4?;3XDQ|KU!=j%6+Wis?k1_4-aUjFQ|r6gZ>V*~(^S(a?=w@i3`cnQ(Tlbavij z->{ADs=q^)W1_R@o5-qerbOoGhI?oojMxX!%|T1#(btw5g8LwsA}^;9928Vqx?8R@ zVQ^P{ttPtBJV-EEs0Upi$J=-@%gW_lO6F8Kd?!xzyIfL?FD=`s2uwztPi7IN&i{9K z{C_Fg{>xwg4@$tlBA_OjPTnLWiw=Kak+}AsW06;B5QbTm{;>VSDE-#OXX3+OpM}~R zMBCpSb0=tar6~6~dj7bb9H9;J@_jZ$qSSEo{MNLG{%{-4Ohu!9m$IC$V;PksrPPB~ zCUZ2?MfGB(;8b4?1BykF20(VTfg@JES;J-6?9)MYPb%j7a=h1AXR=caelNr%q@>2? zQ~m%eukD&Aqv$cdi;=XpDUB5(y4>F1MYECm_K5k~1?3KJ>aZP=puKw&u9%#K0I-`lY9D!rwQ*}w`0a_v z-jk8NN0@)xb{@|>KvFc;|2!@eW8aD|hIs)@pwkI?g-~#*C>Cf7*;cWL=@qE-V479E68ZmvZT1>0uM%8cT*#QOAhxP2Ja?OYpo@={0xeN|xy(ya z#@WkmYF(%i{R>2eQ5d`8(Zh#xa&kC0IR}zk$(1+_ktB497dXhxgEy08@LCo`3ri##sTgqpxjg3pUhqs&-hG@^6X)nT$J4Zqx zHxqE8>dniS_yY$HFhL!*w8T9^k5oP%O2#ZFC-i4oz%0fnJOqj*%GaB35iK-tUan>m$ z(lpqWE|{&Z0&8RebaCZ&sp>jltF5b|0#pToDBidFvl$CRz8eR0o1qEq?iSimm`bt*=IEwSCi zY+g#@U+5DA$*9F%A>ci9CW!bFg5L*hWQQ{@HC^mW+o;k-m7Vw zUf}01*2q4$<^=GCq2b4HTyB@wW`7wp2e)ORHBBpDmY?heQfnOJgVVzqE}|IKFuk)|;Wl?tO{w zs0`VQZx1RSCVYF4AKIvMuksG;4e!*3=CnOeBcdLx1dO~t*WI5d3P39u_wQzCX9ffg zW-knBafYA7Z{c*n8M33c*io;Z{cJ+Fv(~oz;M#yVlUd9yF&lG=}lZjJkM}(%G09Wp_kQ~nd*+1fsGfB2`7j=Be(0Gx?JtgwZLE&|6nzs(}7zMB4mG(JU+!25Gedb>gkgba8}>L?e%!&w(J#@q^F}* zI}@N`%zsSulUlU0N~HzLvpFxz37Ofn^ufUbVe8To2W&!$LkUopLKp&jHPH9dzWQ2x zyK8+Z<)j9xu;A0DTUOYVsyaEj7T|BhNnlH; zT2>Y>MD4uQ#5dn1v;A8&*l*5-)AyWbqNf*rShfBBTtQe^m>li)dS_>L|6hvA@$g=E z-5dk;f!2nGIi``Nrl_v`u66tvF1K~srsx+EE3Q3v@~^|nnZ&3oVGn|(K$JjUE=;aq zic2r-wbsH-Ym&$*ErFN3?j?v&`San>w9ze!a#w0UmNrEDl{dZKac6f z#%%}*`Ln+<+^Q2QODZYZ*x;hT4$$2HK+j-{QErwq7qmeKc-fD^%0#v6y>)t zg#Lzpj_K^y$VdYT;8Uq}y)<7e2=~e^G~Z%ORoj_;duMZV6BeU}2KMZJuYZDrSLWy8 zn&IIYhR!7qB)m}yw-PzF>V~LI2k3gv9U#%Z^N)$|f>TAtFK5{mqs6y;u9H$RiFr(J zo>BP+^MY4a{vZ>3ZAjjIR9%8W?sf2!$`tOV&(sS(A9?45T`KiBmf0#~%Q@=H_|_{! z$%ns^=mw!qk_b9p;fREu4AK=g*O!~RO{}mkXV0F68eB!S6PlDSW7lbdnT&(8uB3)^ zLh&N=^5&qO@L8T56?>V_ooFg|8vgxOqvy1U%~$W05SVOB=Hx&o2hv00xc_M>i`lMv z{N3bER#vw9x;lQ@AlkRx6743%p`Y^W+;9LQovH1npjyi>sG%llTTghPetuXG`r7)# zKpM%jQNBQ&ZSPY-{@;>3eya)mD2z3+Uz*=axpVyhw}9)OiNb!`%~Q*p&qsXTuexpgLvQIBb(&BS)NQXG<@5V#fkdmjgD+ zC=S5y^R>A%Q>Ry!k#Potfw?PUbqiIwJ*(rpd7mCd`D1?}o^0X4ckPLSbj`lm z!ck10l9OYEA$eu8p&igEZ|XY@9PofbP{UxrCiEyDKl+xI6wR8Oy?OHnX^BhK&ORkm zwq2Fc)WnT|38wK3q}C`)@0R7}%8^0S{`V2ZFpw7g18w-9b9R}% zZ=8=hA;~XMX6@=)yC|8X$|l?D`p}B2&NS82-F??IL>bdwUHSg~B^H*oXxrESGm>rE zhMPRFmTgC7T?$)XNHIJbGCWAKNbT|9;FkB(r%!3v-$eg7f9tpPg_V`GZl+A#sowJ4 z>r_Ejdb*LPJ+>Ab02rp9=z9cZkh ziQ!BHTwJaD#O9p~$L)z;HNPWvE{>=ETLl+FCpzv1?d)zlO+BP%@xs`PLEQP~ty{Wz zxE!N;?@dZA;Qa2&N1`3Nl@$}%U@ghS?FI5NF$x2J@Y}Ui50{}LzKZQ19-G!2QkFt3 z^bE@T%Yb-%rp!b0^h*d=51+rya(lAwiv5IbI+j|Kl=S?$3_uJ>YlMl3b+In5w~fQy zh*-XMlU0bfpgA5qGi}xsuE-V!3``+M=~(!Is;ZCRG+k43fsc>R{^7r&a5ixhWgk6@ zbs0~dEOA6{+Rp^2swiD?ZNW(=r2Hp*kox@P%jmYTYDDx-9~0!?0UwK^5~CTQtP`|$ zt3K~$ou3#Ci%TB`SPQfJAQP*~B&E7SoiCCMwE#Pr;&(0ow&ZE#6SMX7*r> zxBmpbLjDE#$~XVp;MTQTm6bKp%GZ9Z>JX4g)SSS|%Kz_^THQQy%haB-552v8{ryo$ z0M?I?^XgSXrDr2KQTFdhoZ#X=0l?32KFt76w;U|wb8*!#b;|8d@4b@M_Q=C(R%Gx} zJkRl2D`C#t{mX6tsM*|zt)opjz;J+sXZE7SolELA5~ruGQUpGahE`&4sI~1yHVTYNUL<4VyZPmqCr>i}C_G}BtDr>co^udg{W;ecD zVLA6gg6x^^Ol;~%Dgo*i?Ky$?V^I7v>TBNd1bWGq!T@7=p1)0Wg~dsR@Wg9?gBBN> z+ocg^jkoBHogY8sl&PrJt~)zBm+NPD&e3s#AJV&1b-H5#c2Mvp=dQ)~l>=x9yllSK zIu5^-ndD92rl8RjP)SVtL7GWl&NgHU?e!W^rK&wsA3%O24t+}piT^A^qfn|P{a?^H ziolIO$G?w03Sd@Jfm=C1SbN6)375)z-9$jjQmTdi#P82ZX$?T%B~Bao2__~?Po_?5 zykxwGU3Br@Pbjf;m2!!HQdeQ>z%>OZ9W5>GK^D@B;c>8V?s)rfoCxrx(Jhec-ugxg z>o)L8)I4NlU~)}u`1R?_FJJD;%96r!0z8RBSVG<`t8!S1dMd;vh&e5o;BH{QhbF>` zu`n`>)alJScs|RPHh0687>U8l&*3^$#1EnfwY0Bo=^dHrdAWUkMTcqXNDrmP#m2&) zrX<8$d70I`e25twgpI6z6C*xYU@mN?rdFMv6|5W+vi4go&Hmhp7sm9N+cwED>5U6o z{GACNA1kt2bfMdC0oK$^g1ghLrZtE}B@R%x@9TIC^+4l{9Puy$?pH=6n0B~=Q6S8q ziHwqsc_E-5jHNFcP%+X;xQy<(+HdScFIA!pdBNKfMy2?HbaMPouQ4=rMo`IF5#YSi z1wFx+6$g%Bb=cF%KW}0PqWm19P_#kkH353@)bw;DBPoEd9d$t{;G4tXc&qJQd#Mwq z_(`lYW(0h(KggyixM8{@6WN0+ZSc>jtEz`R*FT3Qc;X+!x&HQGdoM8i)=?(#Eqlx= zWhvf5f2y@7s*o-G@of^Dl-HlT%|6=LD^QS|uWihZiWE1O!|q1o->|!hsXXktahRMz zRA4^t=F;&59Y*D!mU8OMKP%-Dej~9FALTSAalQmdCvYGkaR|~JEVkRg>c+nTL(>W) zS)i;Pm*M_nE*Sm$Trg()Ah9mWXZ62vl;FK)&3`7# zcxG$J9#L$IX^oP!@B7014+&9c#}!h_SFyVtv1n^Rut|hZ5X9)SO+1hTfH;y} ziT?pzBP8W=5)I?vMi_*;1SA7Grv2CEmd6A0$|WR$z>RHhimQIoT4O!*o^>t0`%6v2!1lPbWB;)Rvp;fwwOKx;5nNRto zOd|uKnUm9IRaI^=1|50ER8b0>yGtn<$e~I^Wl{Mq%qx}I!tg60A<^V;{~I@Zt!(?( zq@$@A1!rSmuv<1-06n!@*Rp(djL1k!i_=Q!5~i=13-T{M*vSu`5qNa>_U&vaF5EHf zXxX5%?RQ1be?(lp`c)S2pI!05tvXED?F(m#bN5H&)YC)II1)7~f`}FHIwz%gH3Z~k z2+Vr;aBSMTa&2YB_AaLnH857T4wDO2a&qTyL1zZyl^%L!CDEvNZ*_H5a;NGozg;sU z2EAS)c0lcq1oZTz|CMhh|2KkKUGcTRfnzf3=s{;lm^gDL3LbD2{vbs-1H)U^8F|6z zGRS}N_MRv%rVEBRnWyL)0H)2%G&Rxs+mB_Qha2Ncc0m%<+SHVm)}bfn==3KqE9=w# zW(mj?BO$R}>vLCngE+N4>xuQ_be57rEQ07hjqa`j` z#q{C5cEliu&D^+0vsbp2EZNw?f|{mHah&gumBaPM8&u$~W98;{3s`pG6GzW~Z2wdi z-OET=M-s_%t5ZyMH4;pK3x0hDDsu4FAX1f5Z=^TpJWKUe5=dw6*;^mi15!skDrB{34mkXMp{MPULY>JoaB)m0!I zqdZDo_gE%CAL9*@E%of)-JKmlEdml1>;o|@K142pNz0ish=6jfhA2`KLH&z7ma(6~ zmeaiOJr5W7NZC8v?^?gG(qNZpHU&$36}ypPTt_1)!|0@wJLiHK0tpaBhrn&TmZSat z43>=DYX>^PNGiG@TDk>WQ;wyOIOf zswe6`CB=53Hz<^+Ojo-&!@+t4bZVKpn!5iwWO`t-^qPxPFpq(`CX_`Mg7EsvORWLe zAibemq3!m!jq=d`E#3=sK<97}ZMbfPN3xLKIJ-U7t5>=Oenqeu`kAe6+~ z`Z|n?z@llWz+Cz6>oiBq!XP0pEKjw`Ac2ps(q(yc?DJ=Nx0)v3=_xVs02YfO`m0Xg0OGXp|2%`AKQl=JzA{+K#gY(C#{V3v~ zL{4)kNvfIb(^NkT7ZDcXpuLImt)EHq9yN5{f za{!)KN)QELW`uq~9)yI_5@P-B?fj4iX22y?Vmz@A1=wtX5YZ+<>LHL{6YdIj)*PM) z0TCLgt{gWuZh(nAHL&}>X0nIg2J=YhD5O7_NU^im!@|uOmsMC8VM07KP$bx(Hz?;= zLvM#LNw*g^|9F%O<*To^Hw5@VVuaYye@ZqX z!qV8>WcdhQ9VxH9>Jfc?fXd|Y#Tg$zf*9_2hxz(!C_qg>v4#PRq{rmvVsmkw!I^lR zJrsfv{G?7W&Mfwn$7$20um+wgABNF4a}Bxulf%rgq&0Je{;i8PQo4}ws`5_6_=AQ( zzNQ~dO{-g5&Q4A(&CR&=yjBP`$*B3(;ljN!*x~vsu`w@7Kgu9BIdhQo)h(mJEA;

mstMGXw`j3xQ` zUAhpHoD)SPQ8O^NK zzLZ#u=H9b|5XJ>h#_Q;ap1at|r4*&u+b3ksia8WHFHxg`4AY`cMny$fsn49=dk9L; zu#fD5Ehn=WD_MIc+LC_ndzo(xRp9t4F72jM&_g(H^edpsXwD;(*S@jt5T3e5HjKR-GEQ4>?=8=SoBGGWfwpU{?gE&3o{07Wx%mrPE zo{Vy|r%$^O0LC%!C?12Xc%VvC8;(^WvqUoA-oAjqu3hHf;6V2x)bDAXp&M|A(kh5c zH?H-aH#M7Tm>xYH&jvqVY))5pvaP=HgJuY^=QA@?lIn>S5xm1$?$AperO+e7sR3*j zmyGqnkAFNI{UxFzHOVO&!%y9pj-C;iooqn0CP<@Vf9ym5Jmv-|oebh95eu4S724S< zhZrSyV${o{X$rCM0VxqGLQYfSXd&>qzY?G;P798;V?!^jA@{|Sm>I|@kQs!Bcnk@W2aDfHtIN&jS|EOKag zXqB8hzBEC~ei{Oxm?YgEt=Al$7XPfx6q4-46O@+>p7ej?9oO-e<@BdM8BMDlo*jZpi|k zPxKALRVlo!B#AT2{tnZgs#*`*CK6lu{Wp@U^oF3!9ir2=><85wD9eaA@zc_igy%4E zIsJdL3;th$)Bj&!;lDR$KmWQSqo(LHmknz!joa|y9Yf`I*AJIo!wmV>-`Bw`Np5&~ zY=mQ(vbIO7X_^{Uq>n+<}u{g5vKQdFSgL0S^{0-_>J=qzXgFA z^&fhwAvVkq$F94wt8u$DP=rsFD@q)Iy$IPCrmUcP@dUaD@hQ8<-PP4;vCID*R1NTV zz^dAsZ8w_Br2>WRF&U^)>EeIxc_8x=5?Nq^11tS`BF|6!9zbT} zP0h?)ek+8rzqTJ(qZZIof~-Hf^h;iW=VHI~)BR7KSd2QHxEVAe>QEiq!u`O#Y33rIUJ44_or-)R^AaRX$5?#% z^w+6&DH$?e<-x}9T3XJ;Q-JE2cm!Q)r*X?df1U=}%GZOQw?YDT1_uK{O|Z|IECw$k?3mdt8*@+7k&x&WD;=Rf7bUStOz@fJ_wfk8ae|U<@#`b>Hekm>_=1q1unoWfC3l;*s;gQ?6?gxN2V8 z?YA=3OHIa}r>Z7`M9|Vu@)aYl4P^GxeajC$Jzx0ABK&U{8YaSmBp;dSU~jL7tDk`) zuYZ%(Xb`g?Qr8ongLN*%V}d)oy4L$=bP~MgA+1U(yL0}DLIOTpkx4kMJIJiS-?gw& zQ*x;#p+2U5DZ;U%07vAY%#NuS8;!BjL!()jrHcZcf9a35SP%FL&+?jd0+Ya_<@euS#Cz@dv&c?BEGqCi z!N4Mt+qJ|}IXOHxHw^?VkHO~=G=xWPf(4jjb9kZW>YDXla1}^wcyPLwSKag8_)g z>0v(c1Z)hY?!+(}FOA?3_|AF#@}?_Rdqs`rsl#p~meL`0b;A6uR$A;Nw_#9-9?fl> zr>A@Kru71A!X43=oi0`71r*{-LBV~pmDR@VVH*+XTH-y%!9`%bIR@F)(|za=MU1$y z(;%fV(+I@Hi|LgPc3V#ke6RdUx|H?V#_v-~x2bgwB!62wVIcJG{aC7Y=A4a(|N8v< zCr_SWoL}EwN6_g|$&iJoEr)9uY!Nj)XL3FI-?SpF#2x3?Exl~2ok}-1|Gbo+>gRg1 z4Nw)Hpo=aj=uVc8EWLttLc1by5_I&H8PJ5X=Gl+xSkW@88D=U4- z;ZskCAZV-E2d1VFQ6PR|%hM82Y9hy>`g+OuM?+y}VKevoEj`8Nsm5eqgqc{$g_4Yn zp@zBnel|+V&0YcDlB}#25W*0;8XE3ostXGWp4Opq17CVDLrL)3>4#8T7+#A>MO&Me zkF~Y+If;u0^M9H_pcP)Rgx)?WAx79O>4Fs4(5qggSG7snDB^Vr>?Xe-14LY3|KM~>;Jwr%K%sCH)6*TIYH!a2 z6@i6?1$-}~yG0K5N5~p2EOfHyJDr?saP=wTw#^q1?s-171G8f2p8b4+3JVMAE`7D{ zxZ%YzsK?Aa)&&>_<>}FW)UapMY`gDf*x?l5#Y4oOgv5@re}IJT`DCRZWyUyaU z^8*6@MY-EXz^hlYRT4qnYx-E%g(|UoscMj$jT;{J^zi)^wJzSPmR8kvtQrEDEL&gA zt4SUhx)mKJL;~9mcRLH`@4wGoGLym1<&B*@^UlO&@R zut!YI&4orqv4+c4vn$<}viFT9AwJ&A0~O4PeMX1 z^D-dd2NJ(|#SsFM&iwAl1-Br){x|O=9sB%;|NZ;x-`xiO?cEti&`aug{d!bfux0hJ zJe%T?N)5S{p|tSMPfA#!h4oU)%ChaqQzHhIi?|K6hllUbhmFI;k8+lSRRjfpTzsEv zLm9*|@J~RvF^}JN;N@)zJSpY2^=ge=PTHBb^8M38sI?KzHDq$5I;8WCM52$(@|Ryc z0J+FR!^0p9l#}DV7qENZ!s6`B{d462^X$6Wb}CQYMvFLq5sdt4F1vSqZsXE0Goq?( zj-k(6?Vs5P({IH_G|Kyyq3G;^5JQ1eYaqnFmT1*#7%7VX4A;TN++y*^9@n3K;})5F>~{9 z$9v8DUwiRe#9QFLWru9630^@MsY{kh2n)ADHY5OY@86Gh@+5#W7J8<5GY*N;+Fi`h zx;&(-m#tg8x+x8Z+fyiB0d^qs(pfv52?P*4UG)C)x)J5E7F2*9X>R3P-rUsA@c@?3 z-Jz{g=OuoNUQLDwJ{>zLmWj3xOFq=F?A*9{Yi|i{E~bN^!3A}*pZh9(-tgm#@^bCO zJBo^frN#z{+Lj+FWnRKUa_?Rk#=;W7To?r@vBe7H7`&QOi&OOAHF88(mHYyC;?t}) znA%+t&KCAE8GKwHobZlNiYk+vfQ^ik{iiS?MXdxT2IlD5-wZ+_rlU>;uI|{G{smfmW_?=FtZ2F0~EG`~97ZuR*E^?8q~?N0wYD=X3w8fuT8JZ6wM zZEjfUFx!J3(nC{UO<5lPg~Wk7GwH(DcH4}|$TE!-LSC_uXupHs1iMl&1^rrrP7$?J z`^Q7yu2mmZR)qW?pQxxPmHzDfbu)P{FFb=<9*6CdP^b0N{i9HW+TqB4wr>no_WiUf4Smny-ro9p|(Y*5k8-97Of)u^w*-QQ{Y;b9;Mnx(#L^Ec zY+LevR^=^q^A9qtNJ)LUv!#B*dvVAAg@e~bC)4t zx1umhiEIpf#U6Td@p3xZJC|OUrqt8)W1Dxf$BCC_ku2N)LQ+{aQLnUp^h^h)n(acY zo6At&frAperKKTfBnR5O1jf|BGCw87&rg=XaW52D?6Xv^6XRZ>s{|WIV^})T=(Rp0 z4Zc^5m!CM;*t{+%fYDTXWbadmmZTKc)4HGBZFcaW-;YNE)xC5WDd-2DHgW)pZxcap0MM{rQV2yW+X8Hh-%{YZmu{ zEZ_{(%{yVb|5*f@4Tm z<_5Qxr2Aw)xIpYA}!6RWGd5FBT^F+US~mLV`8=*gRZEmJAehD)u)ODTi86|J|B z?f^e552R;<6;#>I0D-t0dQDyM^72yqVBADSxwvd7`_%C{ibp5(t(trW^&6`s?2=^GV)1711ZovHbogxcA2(SOmJFG|W`rp5kfUE^x z`|npZHa`AJ(x#Vm{LT_-7J=tgiPqpy+U0hGCv2gOSzFn?CIw;)kAEeBO7Q(bqk7+< z(~Nr|1f5g@slS#Bc8hi^i=~+iX(IpZ{K=|vy|#cFJq1vRCfxwlNZKuUT&8b z`#zm~#;P|Vi@=eh)|^F3{IKcadI-C=A{6VeEA>s$_{c@5=_grM7 zbbr~+;m@GRoZA|GQ8{e}P~LF+3ZH?gT-jZB``r#3_RFc0!`E;6Ms#o~CTl?4rBNBv zqwrsdWxv+}fhY?Y=r3jHIjG<`TN7H4Y9ebTK)Y8%gZ7B1TYFvu>(fqBsK z81uc*V}#uuI$|tuZ@`qi54GQT1*+UV#ETkOyAU^hkFX8(yf^hstFgzIu|zA>F&2rP zcedt-Nxn+@5Nr=PpHh^Ux3*tfWXJ=6s<^meun|2SxwC%ntfnua z4>gcspaS#q(rB&R+E|0i394G^zm$mBO6L(v6O%ot5kCnJFK)Y0h0t$x_lgrwQ(=DL zjT;JG!gWpYk0DfkS_c7jQYjn~gI7Msy)Ymj^pux+fw=D2u`scPgs`wukJ_}$1kaP| zu%2{0<|5!XA!rVOykdv`AL7SofqZ^yW%w5x=LQ}=rp4(^)jM~zJUyL*Qak4gJ##$q zOJ0|sG|j$x^$KQzpf*_NW03ahXv3K7<;-<{nq`6%CJKi6gm>>Qc$1MWE8n?ujF@j^ zYg8?Tcd^$WImBEOob8OqEm>=Ggooe1Uw6gQ!h`vSoLL%v{Mp;LU4RjgGlOfWjZGUV zlZOM=ZVs>zb{Cwh z2*4Z=hX;CZ^^0!g%w}d;rzxkvH<=UMXT{=LAwSY9{~obU`U8K%e3#==HT~ANO+?eu zmsHY)AU;io>~WFRz&Xsg(zR_8AVF5Bk5TGyb9 zpJ&n#5)6Ze5SFmG&b01$9!@bGwJr=xudpBy29uKtv%9j9niAg!$N;3#{-iePac*hl zGcozh1gWis_XoSWk_ZAH{shgUUXohERNH8RoH<|2GA6zk<%lNomFOfRb1~=GN;X~z zx!*V6O!KDJVPty5M!P2bB)8=N$<~o99TG3cz{knOe{ul;6-`$0FD@>I8(O7*3EC|% zvXUXw&^XmlQ0)|_1YxRD4klNxJ~c2iE5VPJrlrYcs=K?n4UPLMyh3HA&Ci3#ja^n2 zr&@1L^~%)3A_FKhb^`0pm&&^K0VpIs=SBH2g>{#Spg`0@U2IQ>oA3=l4d5cW4cMov z2cH!MxU9C({Ogb_Kj(dbrHL;~OHAJve!0h$Pe}-`M;{dHT8X@uj(Ib`Gy~zlP(0L9 zGmrnRGznC+=vvg!ES4%^C&S#?69`rt)b4e9D4O%i`-w1Zgj`2eh%$$#zo# zkAZL@jd$U`aN#zJ5wZf!zdohqia2@VbHy(273fI{m&BrMehdzxeg2>3t~?y-wf$>3 zM`cNMN?CHIg(AFJB1A`)vSx|wm1XQhwybkbAu6emWGidNmKaMIim2@SGWKGKu@1v9 zGw(e*=Y4xS&h@*l-+BLdU0p7hYw(??=Xvh?{(L_7=k92#vwBs5BLPDFG7Ng0J&E^Y zc&WGFvCK5{>T^1d@En25Ol5>XP~GcOvo>^P4|wbpYl7?pMozWRaX}V$ZilVm_u?!B zqAL8)9u2g$Z^7DXngt`oBi>up^9G30-~0Txa*A*Bk#94L zag2E+Q*r7Q;fJJvA)nUvR1iaRTnKv1p$F`RUG`ex+}_X5AYGZaSrZ)F6ek zzw)*V#xJH%qCXzLlwS#}VYi9=vZp0qD_-qhI6V)Jv6C;Q#0}K>iM zU>Y{;w@46xsJAl8%R}M&kX14}a{^Hv#}Rvclfendm(Chjo@uJ7kr9$D#XTPmUV;R+ z)IOo2-B0BI$Oz0JM{3<1aPqg`&Lt=D<9ir>7|_SEn|FI;tRc*3Wom&k*MzjBJyg&sbG6MJC~xOE!t7z-y{Nu_bg0I z5s2pN$v_>fIV@Da4Gh`7%PHKQw-B3%ru2Xepenxt_ZQ4tHhN;%_0xbq%HPOnMZ8Jw zz^;?7tA?_H9B5jdx?+fj13iyMWh*K14xR&Fp6J}mSlgDH+=a5>#ttwFxBObVN*k&A z(_NHnVYr-_7oia)VqQ<49nW=g+WQxLBk)$=p-a2&HKgY!a>Dbc1g%;u2hOGMi5U5W z2J^yoC}RSz8pgFqEb=AQQepI9+@3v7(mrsJb|jJjCXu}7 z1^H8WbyM6)qJd590~Z-_arJEv!8itWI$$kjxFdP4HcdOW6}#fzMIc80bwIZUD7SU9 z3^KsQrE8!Vu##@|#zrl>34*h2WZmcv<(aohndvP6XhFLdd`K`-NQ_`6iD-|lfl1E} zdatn92ZBg{|k9K>r)!BhurZSBD_ zZ*?a#vy06yVLa(zfaY{Fp}gm5IR+xHVu*0~PO*qPE8OBnpFyb@@HQxhT((XJ6>)QQ z*n5Dy{%(FgEdzQ)jkBGLC$v)CH8U-5L$W=m9i_H+_wM$={{C9z zd6)CdNOLd@>gwzSBr92eiDldD&h6|_k_1;?{CD;Ls?frsKsIrjS_aR<5f|#tC!P2( z&$g3Eb%tnMraeQ{1hV&?rxcK%9!CI`x!oml<8!6D}aH{Z>I*kwmIQM-% z1vohu2F2xRPr82HBm}qCv5Wp>Ge_9WX{rNdEBW3FV{&V=#bsqI+)~cQ<&@I`s9FO6Sxt;-=|-lQ>^0E4{J#GRX`zyyQ@#b{J0_cBVS1n%K(?z_+sZ_XO7)G65iLV zxg7>xhkGS%7!pMeZ4?p%K$~4z9yGJBS=qqA02Um;N8JnwzmdSt;>ZBDL?5k?hByvx zMD^41AMjdHG2;h#trl8Yr8=QS^k?CruJea>?Wq!|r}v|PHPzK?g*2zQLILH9wYeJG z7N#G?)kZ~2Tg2=6P=CA_bV)Po0Qhas@XovP>xP$vH!<9!EB!-#*XJRB8>9YzUw>Qu z{;jVWJkeLSh>7VnhVy7;uEa6*BjtR>36DD(ddCdnLuq2-p{AbY=yI5BIdSap`eG!U z1lBQR_ziuz!h@IRn0hK;y3kIELf-{6ozigF4a%zge76A;`Y$_q$GvLT`m_0#J%e(G zC;nJmzV*c|n$+&1N9tj0KCB0-X{p}>qp;moPg0g%`tDDk(2mY_C7c9CX40hncn4y> zPF9+v@W{w*c+f~OHEM(TW!{vowl_V#oqPT)unuVIGaYaCPiy*D7h^aNrHk(d!5$PKr3@^aQ_ zet8z5WN%D~wg`Y}8!0G@)kZwf4qh-y*EuK0Fvrw;mCvESzu!i+$P&5QIIc!lC$tr0e-OBlfD)m9mp}+NwcJ` za$k2_U?u7$@k86jY*qLv7+|l=!l@C@g@w@rRhb(_wKhUS(SZ5vxSxVWqM?N^HMN5F zE*VXa>+5T5uC0@%=$EZcS zCzqShc0B)Pma6ZWV}awS!_nuf@e6X${o!VhJFWKy`e5N9`ivHC-6~zG$^JmJXvv)F z(iV(*Z2CF{ZER}lc%J1?wda!oCFE)mSWhv;7rFiwI{NxnR~a8I-Q6!xADWsfhJ`7p z{l+FHi&96NoI0M?S@laMHf|KSyyEh1(eNpy6E$?`8rmQ##qNKePenacI&^X^0~x}` zTSjcGPd^8BVdvDP?TA7-`S%)vW2kzomd#ART8N8$E-Sip$q=Yz+0!y#5@pPN|Ey{t zB77axGr^F)97on9S9sWCztb_mo%X_MI?fpfaG;(w_^K- z5oF8sM~q;$0r!4sef|hwDLlF>^nj`ZejkLRv$Nw0M+N7rLjy%vTYFYu*&&zq^n+Z| z9y`{AHa_E_*6uaPD^>t>y%BWw)QOf$J3Dqm9adjo&uZ}ta7W zQ-jA>$$a%6KL!B0`&h)AUsMzY%{FGMrt$G;vhmB~5p56PJ^UL9#hvRCe#Ro__<1mA zmMW96M%*IE7!R4FocTNcmjWqkjjTN7fj^0ocwMa{61rge28%x)sIYfabcE_?8=`Yc zxZ4v`uhcaDb8NItu?|=b0QESHe%J9dC^je2;hWlMQp#O0%RVW1{le)>ln{O`&T#ju zD9b4+%ZYgnT1<<*zgtoLFO%u+x#p2Wv{_)zAqx7Ei51yX$D)4xfeqwXxTr=^+UO4; zqDNE*@1(=TVuGyWOpP}LuxH5G{z0NY%$~X`^D{HS^fkmo_l0HWDcAs-y7^}(hTk`glg3V*MDgMD%XbyB( zeQT{CiYZ?mpa4>iOz;*vbZulQ*K->}B|HGep1k36Tz-key{SsBP&%|rG4fBz`!8Qn z#)7k#Y@J17rKEt@r7UFQeXb;YPf$PMWn=8Cv*{B#B`c}Ghyp;Y81fZ|o8->Uj?jMyb8<0r^-#;!6-`LQ`Yo1#N}s_;^bnjz1^hHhU?B_jAu zs!R^vLgBu5Kpgya-uL!`POk&_Vb_+$2tUD0Oo6e}m7$B!^iH2#18pko)x4{gkskv# zZK5IGyt!UdMqCDzYdmCF%~~NyvBguUrWfU(0HNsMK`GBlrY1n7ELFm7IN_2b$%#W_ zH>O#9GdSa@t4%i#fx-MiYClr7>CO|0x$4lZSYuu;nWVBZInfzt-|gx7fOn{DS>_Ak zfJTG}{vcIlt%Li1T85)VCMrDZFh8^FMQW;Ywt;r6jP+Rk1n8vp?1>2E2*M;m3$&_= zid=Wm3|LRSeXEk5mUbXu<*C{w>FZYG^)ceXY}`JdZT;{C&CaXYO$*loa9Hf()VL80 z7JxBhZ>p+3H6sEH;hE2dKU!W{(Fsa8sUTf^JJ+Nb6}XjIGl6HW=4lboMNDV4M+A|5 zsbCriaxgLR{1jcgLXNv4c3o`V23$sk~PRRL{w4Lu7kT;rC?%WVz{BEhXnz=EAXQ--S|w&r|s~5Hool$pfL?jSO^A6|3s!v_xtuXwyD`+?K^fDaz@|fT%cno z2H}jRtrlhW(`v>nVU}R4I@)`*qYHA%byY|MH@%d2mk6CoC%EAxvd8rF^uX@gHS{^s zTo;IJb}oro$b0|=btc?51LmKHhj$&C0625|EE!Ll6G+z<(Auv6sIELDdN6kFunMci z!q^}f3-tG!4*E^bL>xW@1T-)g$tpo`WN8jaoYa>t-Bb9O!FcunsyA zplHl8J6`<^XF#UfgN0lMysx$Z|GQfHWpkZ}F;O0~SF&*a?Jsmd1q~^zS+R>N{kK~? z!?-d~xmg%{XJ-)@$tWTMKKpy4_EICMB+=BAw0)e>w%A@icE0U9xcDPJ1ag2@QA150 z3@3~)Uv`(EJ&EpOYFmJxfv(x~w*EC;V@gS6djaIfpdw_OCBr<*v{f(9HP55`{6Slp zC-|6|*N zW`+m^QeG_dsV8ACIfz6|lmdV?!KbgU|0E{ncobDly$%c>z}FXgkFze4_9?cvn@-Hv zik2*OsKH=7$QZF$j~48KfzzPY2A{>zp(_>!1_wEY=((D78(v5b9_*V|;b*6mY6t|5 zkBjz#1Z%j-)1s4ZB3Mo_W2Bq``ry9{qjJ*I(@_Iusf9Tr$85X%PUoXgIZlaRe4dQ~ zGXgsZpmJ!I2fa9@jR8k9-swbwp{f4V*tF-lWaI1{AV6F_I~AbMR5!DOl6gnd#kHh& z@Xf&U)Kow;dcTKp)ba0IdRu!>Sr(+W*jjbrUj4(x-M#n}pK7*&iH?!oYcvuc)<7Ul zR1bly?%7osH1qmk=&|)HqHixdpFALQvp@}Yb{cH?@rKsZ{W&Ow7#Be?vA1n7ts$8n z`QrT^f!#7ZADgVoTc#g_Q+7Q-?ZTwNnz@Zcm4TNxL~rSUl;csIUL|*>3}| zrkJ;i2qj<=Rhe(wwWS3Ivp2ne-?!K>4R1-nS?WfRf=&m0I9bp1;A9c{Mv?0fj}&lH@Z$dht?@^=V*Iwh^Yw->5ploVjpoeU zH4mz<12C`GSChkdxtG@5gcidMb1v^YC{J6ejXUi|Y13BtkN_)Ke4CWCm3;eKUL(jJ z9t;xvNuU_>U*%77b1~8hpb?9|G)U%>C)!lE%}sqARSW@(OW;+0hB16c+R`)+NQ$-h z^~njzf@e1`WNQ4qdw0CMsY=+%!(eVfmx%Y=W1yCKDboOY6HUvWFs=(|{^d~Ix^-Dq zNyGhRk)eYGL?2)Bl!JceA{7KqD$1IL`B<=%fi@=D7#9rD7OHRZ0@?wEDxy($N^-IJ zaxfm1={ZQfot(`F+uO^lXe*+k+Yz1F)LIHz3N%m$deb#ycT1dbX{$|?IEAUIss&*3 zrdKBpnRLcx>4duaU8zAzX?%P?-#*b3(6-9D*nIA=0(3u*j652o(~2sn#KU~(wNQ?v z4W*l!+fLdxSH8QuZ>oExp-F-22!!fz4?TE$ZM9Nqu{3zqGb_RJ{aVUnc$ys7H$Ok@ z>|6-LW~&3D)M~@n@F2qgy*prSycRQ1e!tWe48sDi2t8?Me3vY6rcVlU*mo?X(ZHxhA+ITwvSw?#k`0$ktXvh#C7N>t=lX_@s-DTwB&Elwz9N{Q>g)8FYwZ&Jv!PH7!(#7 zPJeh5{(wvLCykUg?E# zfY1lMDL8FcoxIpmlhSxNm3%Pes+FvFQ)3Bmk;_VGGowNE@)zzVU;=!~69#8Qt*7`EI3ClodakmQhnKu!g+GIp-%uIdv7}L)gnZn=w{P&6KufO5HaIb$Qt-rm|i_g&c Yjy>dze1I=&WW3P}%34Y}idSy_6S2h=4*&oF literal 0 HcmV?d00001 diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..4ab5a34 --- /dev/null +++ b/readme.md @@ -0,0 +1,123 @@ + +# h-m-m (hackers mind map) + +**h-m-m** is a simple, fast, keyboard-centric terminal tool for working with mind maps. + +![screenshot](h-m-m.png) + + +# Key bindings + +Adding, removing, and editing nodes: + +* `i` and `enter` - create a new sibling to the active node +* `I` and `tab` - create a new child for the active node +* `y` - yanks (copies) the active node and its descendants +* `Y` - yanks (copies) the descendants of the active node +* `d` - deletes (cuts) the active node and its descendants +* `D` - deletes (cuts) the descendants of the active node +* `p` - pastes as descendants of the active node +* `P` - pastes as siblings of the active node +* `e` - edits the active node +* `E` - edits the active node, ignoring the existing text + +Relative navigating and moving: + +* `h` and `←` - activate the parent of the previously active node +* `l` and `→` - activate the middle child of the previously active node +* `j` and `↓` - activate the lower sibling (or the nearest lower node if there's no lower sibling) +* `k` and `↑` - activate the higher sibling (or the nearest higher node if there's no higher sibling) +* `J` - moves the current node down among its siblings +* `K` - moves the current node up among its siblings + +Adjusting the view: + +* `c` - centers the active node on the screen +* `C` - locks and always keeps active nodes on the center +* `~` and `m` - activate the root element +* `g` - goes to the highest element +* `G` - goes to the lowest element +* `w` - increases the maximum node width +* `W` - decreases the maximum node width +* `z` - decreases line spacing +* `Z` - increases line spacing + +Collapsing and expanding: + +* `space` - toggles the active node +* `v` - collapses everything other than the first-level nodes +* `b` - expands all nodes +* `1` to `9` - collapse the nth level and expand those before +* `f` - focuses by collapsing all, but the ancestors and descendants of the active node +* `F` - locks focus as the active node changes (try it with the center lock) + +Search: + +* `/` - searches for a phrase +* `n` - goes to the next search result +* `N` - goes to the previous search result + +Save and quit: + +* `s` - saves with the previous file name (or asks for one if there's none) +* `S` - saves with a new file name +* `q` - quits (if the changes were already saved) +* `Q` - quits, ignoring the changes + + + +# Configuration + +You can create an `h-m-m.conf` file in the same directory as the application and use it to change some or all of the following default values: + + max_parent_width = 25 + max_leaf_width = 55 + line_spacing = 1 + initial_depth = 1 + active_node_color = "\033[38;5;0m\033[48;5;172m\033[1m" + message_color = "\033[38;5;0m\033[48;5;141m\033[1m" + center_lock = false + focus_lock = false + +The colors are ASCII escape codes. + + +# Data format + +Mind maps are stored in plain text files (with `hmm` file extension by default) without metadata. The tree structure is represented by tab indentations; e.g., + + root (level 0) + item A (level 1) + item B (level 1) + item Ba (level 2) + item Bb (level 2) + item Bc (level 2) + item BaX (level 3) + item BaY (level 3) + item Bd (level 2) + item C (level 1) + +When you yank (copy) or delete (cut) a subtree, the data will be put into your clipboard with a similar structure, and when pasting, the data will be interpreted as such. + +Most mind mapping applications use a similar format for copying and pasting. As a result, if you want to import a map from another application, you can probably select everything in that application, copy it, come to **h-m-m**, and paste it. The same usually works well when copying from HTML/PDF/Doc lists, spreadsheets (e.g., Calc and Excel), etc. + + +# Installing + +**h-m-m** is a single php file. You can download it from here, or clone it on your computer using git and add a scheduled job to update it once a week. + +After downloading or cloning, you can run `php h-m-m` in your terminal to run the program with a blank map or `php h-m-m filename` to open an existing file. If you don't already have a php interpreter installed, you would need to install it as well. Note: You don't need to set up a "web server" to run it because it's not a web application, but rather a terminal application that works like those written in Python, Bash, etc. + +Optionally, you can make the file executable by running the `chmod +x h-m-m` in your terminal, and afterward, you can run it as `h-m-m filename` (assuming that **h-m-m** is in your path). + + +# Compatibility + +I think the method I've used in this program to interact with the terminal emulator is general and standard enough to be cross-platform, but I've developed it in Linux and I don't have any other operating system to test it on. If you run into a problem in Windows or Mac, let me know, especially if you know how to fix it, and I'll try to make it work. + + +# Feedback + +Programming is not my career, but rather a hobby, and I developed **h-m-m** because I wanted to have something like this and couldn't find one. Therefore, what I've done here may have a lot of room for improvement. If you see an embarrassing problem in the program or have an idea for improvement, feel free to contact me; I'd be happy to receive your feedback. + +