commit 76f6315d39e7119aad45e3a8cfec2eba4b2475f3 Author: nadrad Date: Tue Aug 30 17:54:49 2022 +0200 Hello world! 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; $i