diff --git a/Debian Package Folder/DEBIAN/control b/Debian Package Folder/DEBIAN/control new file mode 100644 index 0000000..2cd398d --- /dev/null +++ b/Debian Package Folder/DEBIAN/control @@ -0,0 +1,13 @@ +Package: birdbrain-robot-server +Version: 1.0.1 +Section: education +Priority: optional +Architecture: i386 +Description: Package for using Finch and Hummingbird with Snap! + Installs a server utility, the BirdBrain Robot Server, that connects the Finch + and Hummingbird robots with browser-based drag and drop programming + environments like Snap! and Scratch 2.0. +Maintainer: Tom Lauwers +Homepage: http://www.hummingbirdkit.com/learning/software/snap +Installed-Size: 28000 +Depends: libgnome2-0 diff --git a/Debian Package Folder/lib/udev/rules.d/55-finch.rules b/Debian Package Folder/lib/udev/rules.d/55-finch.rules new file mode 100644 index 0000000..fa790dc --- /dev/null +++ b/Debian Package Folder/lib/udev/rules.d/55-finch.rules @@ -0,0 +1 @@ +SUBSYSTEM=="usb", ATTR{idVendor}=="2354", ATTR{idProduct}=="1111", MODE="0660", GROUP="plugdev" diff --git a/Debian Package Folder/lib/udev/rules.d/55-hummingbird.rules b/Debian Package Folder/lib/udev/rules.d/55-hummingbird.rules new file mode 100644 index 0000000..b2d22ab --- /dev/null +++ b/Debian Package Folder/lib/udev/rules.d/55-hummingbird.rules @@ -0,0 +1 @@ +SUBSYSTEM=="usb", ATTR{idVendor}=="2354", ATTR{idProduct}=="2222", MODE="0660", GROUP="plugdev" diff --git a/Debian Package Folder/usr/lib/birdbrainrobotserver/BirdBrainLogo.png b/Debian Package Folder/usr/lib/birdbrainrobotserver/BirdBrainLogo.png new file mode 100644 index 0000000..e4e21c0 Binary files /dev/null and b/Debian Package Folder/usr/lib/birdbrainrobotserver/BirdBrainLogo.png differ diff --git a/Debian Package Folder/usr/lib/birdbrainrobotserver/BirdBrainRobotServer.jar b/Debian Package Folder/usr/lib/birdbrainrobotserver/BirdBrainRobotServer.jar new file mode 100644 index 0000000..fc535da Binary files /dev/null and b/Debian Package Folder/usr/lib/birdbrainrobotserver/BirdBrainRobotServer.jar differ diff --git a/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/FinchHummingbirdSnapBlocks.xml b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/FinchHummingbirdSnapBlocks.xml new file mode 100644 index 0000000..ea1d0c8 --- /dev/null +++ b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/FinchHummingbirdSnapBlocks.xml @@ -0,0 +1 @@ +Hello!0000044050044050010101015010100011111111 \ No newline at end of file diff --git a/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/FinchSnapBlocks.xml b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/FinchSnapBlocks.xml new file mode 100644 index 0000000..3b5ed9c --- /dev/null +++ b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/FinchSnapBlocks.xml @@ -0,0 +1 @@ +Hello!00000440500440500 \ No newline at end of file diff --git a/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/HummingbirdSnapBlocks.xml b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/HummingbirdSnapBlocks.xml new file mode 100644 index 0000000..ed92a89 --- /dev/null +++ b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/HummingbirdSnapBlocks.xml @@ -0,0 +1 @@ +Hello!10101015010100011111111 \ No newline at end of file diff --git a/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/META-INF/MANIFEST.MF b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/META-INF/MANIFEST.MF new file mode 100644 index 0000000..254272e --- /dev/null +++ b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Class-Path: + diff --git a/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SayThisBlock.xml b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SayThisBlock.xml new file mode 100644 index 0000000..8b30180 --- /dev/null +++ b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SayThisBlock.xml @@ -0,0 +1 @@ +Hello! \ No newline at end of file diff --git a/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/agpl.txt b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/agpl.txt new file mode 100644 index 0000000..dba13ed --- /dev/null +++ b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/agpl.txt @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 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 Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are 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. + + 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. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + 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 Affero 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. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + 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 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 work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero 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 Affero 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 Affero 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 Affero 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 Affero 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + 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 AGPL, see +. diff --git a/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/blocks.js b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/blocks.js new file mode 100644 index 0000000..4e85416 --- /dev/null +++ b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/blocks.js @@ -0,0 +1,10280 @@ +/* + + blocks.js + + a programming construction kit + based on morphic.js + inspired by Scratch + + written by Jens Mönig + jens@moenig.org + + Copyright (C) 2013 by Jens Mönig + + This file is part of Snap!. + + Snap! is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + + + prerequisites: + -------------- + needs morphic.js + + + hierarchy + --------- + the following tree lists all constructors hierarchically, + indentation indicating inheritance. Refer to this list to get a + contextual overview: + + Morph* + ArrowMorph + BlockHighlightMorph + ScriptsMorph + SymbolMorph + SyntaxElementMorph + ArgMorph + ArgLabelMorph + BooleanSlotMorph + ColorSlotMorph + CommandSlotMorph + CSlotMorph + RingCommandSlotMorph + FunctionSlotMorph + ReporterSlotMorph + RingReporterSlotMorph + InputSlotMorph + TextSlotMorph + MultiArgMorph + TemplateSlotMorph + BlockMorph + CommandBlockMorph + HatBlockMorph + ReporterBlockMorph + RingMorph + BoxMorph* + CommentMorph + + * from morphic.js + + + toc + --- + the following list shows the order in which all constructors are + defined. Use this list to locate code in this document: + + SyntaxElementMorph + BlockMorph + CommandBlockMorph + HatBlockMorph + ReporterBlockMorph + RingMorph + ScriptsMorph + ArgMorph + CommandSlotMorph + RingCommandSlotMorph + CSlotMorph + InputSlotMorph + BooleanSlotMorph + ArrowMorph + TextSlotMorph + SymbolMorph + ColorSlotMorph + TemplateSlotMorph + BlockHighlightMorph + MultiArgMorph + ArgLabelMorph + FunctionSlotMorph + ReporterSlotMorph + RingReporterSlotMorph + CommentMorph + + + structure of syntax elements + ---------------------------- + the structure of syntax elements is identical with their morphic + tree. There are, however, accessor methods to get (only) the + parts which are relevant for evaluation wherever appropriate. + + In Scratch/BYOB every sprite and the stage has its own "blocks bin", + an instance of ScriptsMorph (we're going to name it differently in + Snap, probably just "scripts"). + + At the top most level blocks are assembled into stacks in ScriptsMorph + instances. A ScriptsMorph contains nothing but blocks, therefore + every child of a ScriptsMorph is expected to be a block. + + Each block contains: + + selector - indicating the name of the function it triggers, + + Its arguments are first evaluated and then passed along as the + selector is called. Arguments can be either instances of ArgMorph + or ReporterBlockMorph. The getter method for a block's arguments is + + inputs() - gets an array of arg morphs and/or reporter blocks + + in addition to inputs, command blocks also know their + + nextBlock() - gets the block attached to the receiver's bottom + + and the block they're attached to - if any: Their parent. + + please also refer to the high-level comment at the beginning of each + constructor for further details. +*/ + +/*global Array, BlinkerMorph, BouncerMorph, BoxMorph, CircleBoxMorph, +Color, ColorPaletteMorph, ColorPickerMorph, CursorMorph, Date, +FrameMorph, Function, GrayPaletteMorph, HandMorph, HandleMorph, +InspectorMorph, ListMorph, Math, MenuItemMorph, MenuMorph, Morph, +MorphicPreferences, MouseSensorMorph, Node, Object, PenMorph, Point, +Rectangle, ScrollFrameMorph, ShadowMorph, SliderButtonMorph, +SliderMorph, String, StringFieldMorph, StringMorph, TextMorph, +TriggerMorph, WorldMorph, clone, contains, copy, degrees, detect, +document, getDocumentPositionOf, isNaN, isObject, isString, newCanvas, +nop, parseFloat, radians, standardSettings, touchScreenSettings, +useBlurredShadows, version, window, SpeechBubbleMorph, modules, StageMorph, +fontHeight*/ + +/*global SpriteMorph, Context, ListWatcherMorph, CellMorph, +DialogBoxMorph, BlockInputFragmentMorph, PrototypeHatBlockMorph*/ + +/*global IDE_Morph, BlockDialogMorph, BlockEditorMorph, localize, isNil*/ + +// Global stuff //////////////////////////////////////////////////////// + +modules.blocks = '2013-August-02'; + +var SyntaxElementMorph; +var BlockMorph; +var CommandBlockMorph; +var ReporterBlockMorph; +var ScriptsMorph; +var ArgMorph; +var CommandSlotMorph; +var CSlotMorph; +var InputSlotMorph; +var BooleanSlotMorph; +var ArrowMorph; +var ColorSlotMorph; +var HatBlockMorph; +var BlockHighlightMorph; +var MultiArgMorph; +var TemplateSlotMorph; +var FunctionSlotMorph; +var ReporterSlotMorph; +var RingMorph; +var RingCommandSlotMorph; +var RingReporterSlotMorph; +var SymbolMorph; +var CommentMorph; +var ArgLabelMorph; +var TextSlotMorph; + +WorldMorph.prototype.customMorphs = function () { + // add examples to the world's demo menu + + return []; + +/* + return [ + new SymbolMorph( + 'pipette', + 50, + new Color(250, 250, 250), + new Point(-1, -1), + new Color(20, 20, 20) + ) + ]; +*/ +/* + var sm = new ScriptsMorph(); + sm.setExtent(new Point(800, 600)); + + return [ + new SymbolMorph(), + new HatBlockMorph(), + new CommandBlockMorph(), + sm, + new CommandSlotMorph(), + new CSlotMorph(), + new InputSlotMorph(), + new InputSlotMorph(null, true), + new BooleanSlotMorph(), + new ColorSlotMorph(), + new TemplateSlotMorph('foo'), + new ReporterBlockMorph(), + new ReporterBlockMorph(true), + new ArrowMorph(), + new MultiArgMorph(), + new FunctionSlotMorph(), + new ReporterSlotMorph(), + new ReporterSlotMorph(true), +// new DialogBoxMorph('Dialog Box'), +// new InputFieldMorph('Input Field') + new RingMorph(), + new RingCommandSlotMorph(), + new RingReporterSlotMorph(), + new RingReporterSlotMorph(true) + ]; +*/ +}; + + +// SyntaxElementMorph ////////////////////////////////////////////////// + +// I am the ancestor of all blocks and input slots + +// SyntaxElementMorph inherits from Morph: + +SyntaxElementMorph.prototype = new Morph(); +SyntaxElementMorph.prototype.constructor = SyntaxElementMorph; +SyntaxElementMorph.uber = Morph.prototype; + +// SyntaxElementMorph preferences settings: + +/* + the following settings govern the appearance of all syntax elements + (blocks and slots) where applicable: + + outline: + + corner - radius of command block rounding + rounding - radius of reporter block rounding + edge - width of 3D-ish shading box + hatHeight - additional top space for hat blocks + hatWidth - minimum width for hat blocks + rfBorder - pixel width of reification border (grey outline) + minWidth - minimum width for any syntax element's contents + + jigsaw shape: + + inset - distance from indentation to left edge + dent - width of indentation bottom + + paddings: + + bottomPadding - adds to the width of the bottom most c-slot + cSlotPadding - adds to the width of the open "C" in c-slots + typeInPadding - adds pixels between text and edge in input slots + labelPadding - adds left/right pixels to block labels + + label: + + labelFontName - specific font family name + labelFontStyle - generic font family name, cascaded + fontSize - duh + embossing - offset for embossing effect + labelWidth - column width, used for word wrapping + labelWordWrap - if true labels can break after each word + dynamicInputLabels - if true inputs can have dynamic labels + + snapping: + + feedbackColor - for displaying drop feedbacks + feedbackMinHeight - height of white line for command block snaps + minSnapDistance - threshold when commands start snapping + reporterDropFeedbackPadding - increases reporter drop feedback + + color gradients: + + contrast - 3D-ish shading gradient contrast + labelContrast - 3D-ish label shading contrast + activeHighlight - for stack highlighting when active + errorHighlight - for error highlighting + activeBlur - shadow for blurred activeHighlight + activeBorder - unblurred activeHighlight + rfColor - for reified outlines and slot backgrounds +*/ + +SyntaxElementMorph.prototype.setScale = function (num) { + var scale = Math.min(Math.max(num, 1), 25); + this.scale = scale; + this.corner = 3 * scale; + this.rounding = 9 * scale; + this.edge = 1.000001 * scale; + this.inset = 6 * scale; + this.hatHeight = 12 * scale; + this.hatWidth = 70 * scale; + this.rfBorder = 3 * scale; + this.minWidth = 0; + this.dent = 8 * scale; + this.bottomPadding = 3 * scale; + this.cSlotPadding = 4 * scale; + this.typeInPadding = scale; + this.labelPadding = 4 * scale; + this.labelFontName = 'Verdana'; + this.labelFontStyle = 'sans-serif'; + this.fontSize = 10 * scale; + this.embossing = new Point( + -1 * Math.max(scale / 2, 1), + -1 * Math.max(scale / 2, 1) + ); + this.labelWidth = 450 * scale; + this.labelWordWrap = true; + this.dynamicInputLabels = true; + this.feedbackColor = new Color(255, 255, 255); + this.feedbackMinHeight = 5; + this.minSnapDistance = 20; + this.reporterDropFeedbackPadding = 10 * scale; + this.contrast = 65; + this.labelContrast = 25; + this.activeHighlight = new Color(153, 255, 213); + this.errorHighlight = new Color(173, 15, 0); + this.activeBlur = 20; + this.activeBorder = 4; + this.rfColor = new Color(120, 120, 120); +}; + +SyntaxElementMorph.prototype.setScale(1); + +// SyntaxElementMorph instance creation: + +function SyntaxElementMorph() { + this.init(); +} + +SyntaxElementMorph.prototype.init = function () { + this.cachedClr = null; + this.cachedClrBright = null; + this.cachedClrDark = null; + this.isStatic = false; // if true, I cannot be exchanged + + SyntaxElementMorph.uber.init.call(this); + + this.defaults = []; +}; + +// SyntaxElementMorph accessing: + +SyntaxElementMorph.prototype.parts = function () { + // answer my non-crontrol submorphs + var nb = null; + if (this.nextBlock) { // if I am a CommandBlock or a HatBlock + nb = this.nextBlock(); + } + return this.children.filter(function (child) { + return (child !== nb) + && !(child instanceof ShadowMorph) + && !(child instanceof BlockHighlightMorph); + }); +}; + +SyntaxElementMorph.prototype.inputs = function () { + // answer my arguments and nested reporters + return this.parts().filter(function (part) { + return part instanceof SyntaxElementMorph; + }); + +}; + +SyntaxElementMorph.prototype.allInputs = function () { + // answer arguments and nested reporters of all children + var myself = this; + return this.allChildren().slice(0).reverse().filter( + function (child) { + return (child instanceof ArgMorph) || + (child instanceof ReporterBlockMorph && + child !== myself); + } + ); +}; + +SyntaxElementMorph.prototype.allEmptySlots = function () { +/* + answer empty input slots of all children excluding myself, + but omit those in nested rings (lambdas) +*/ + var empty = []; + if (!(this instanceof RingMorph)) { + this.children.forEach(function (morph) { + if (morph.isEmptySlot && morph.isEmptySlot()) { + empty.push(morph); + } else if (morph.allEmptySlots) { + empty = empty.concat(morph.allEmptySlots()); + } + }); + } + return empty; +}; + +SyntaxElementMorph.prototype.replaceInput = function (oldArg, newArg) { + var scripts = this.parentThatIsA(ScriptsMorph), + replacement = newArg, + idx = this.children.indexOf(oldArg), + i = 0, + nb; + + // try to find the ArgLabel embedding the newArg, + // used for the undrop() feature + if (idx === -1 && newArg instanceof MultiArgMorph) { + this.children.forEach(function (morph) { + if (morph instanceof ArgLabelMorph && + morph.argMorph() === oldArg) { + idx = i; + } + i += 1; + }); + } + + if ((idx === -1) || (scripts === null)) { + return null; + } + this.startLayout(); + if (newArg.parent) { + newArg.parent.removeChild(newArg); + } + if (oldArg instanceof MultiArgMorph) { + oldArg.inputs().forEach(function (inp) { // preserve nested reporters + oldArg.replaceInput(inp, new InputSlotMorph()); + }); + if (this.dynamicInputLabels) { + replacement = new ArgLabelMorph(newArg); + } + } + replacement.parent = this; + this.children[idx] = replacement; + if (oldArg instanceof ReporterBlockMorph) { + if (!(oldArg instanceof RingMorph) + || (oldArg instanceof RingMorph && oldArg.contents())) { + scripts.add(oldArg); + oldArg.moveBy(replacement.extent()); + oldArg.fixBlockColor(); + } + } else if (oldArg instanceof CommandSlotMorph) { + nb = oldArg.nestedBlock(); + if (nb) { + scripts.add(nb); + nb.moveBy(replacement.extent()); + nb.fixBlockColor(); + } + } + if (replacement instanceof MultiArgMorph + || replacement instanceof ArgLabelMorph + || replacement.constructor === CommandSlotMorph) { + replacement.fixLayout(); + if (this.fixLabelColor) { // special case for variadic continuations + this.fixLabelColor(); + } + } else { + replacement.drawNew(); + this.fixLayout(); + } + this.endLayout(); +}; + +SyntaxElementMorph.prototype.silentReplaceInput = function (oldArg, newArg) { + // used by the Serializer or when programatically + // changing blocks + var i = this.children.indexOf(oldArg), + replacement; + + if (i === -1) { + return; + } + + if (newArg.parent) { + newArg.parent.removeChild(newArg); + } + if (oldArg instanceof MultiArgMorph && this.dynamicInputLabels) { + replacement = new ArgLabelMorph(newArg); + } else { + replacement = newArg; + } + replacement.parent = this; + this.children[i] = replacement; + + if (replacement instanceof MultiArgMorph + || replacement instanceof ArgLabelMorph + || replacement.constructor === CommandSlotMorph) { + replacement.fixLayout(); + if (this.fixLabelColor) { // special case for variadic continuations + this.fixLabelColor(); + } + } else { + replacement.drawNew(); + this.fixLayout(); + } +}; + +SyntaxElementMorph.prototype.revertToDefaultInput = function (arg, noValues) { + var idx = this.parts().indexOf(arg), + inp = this.inputs().indexOf(arg), + deflt = new InputSlotMorph(); + + if (idx !== -1) { + if (this instanceof BlockMorph) { + deflt = this.labelPart(this.parseSpec(this.blockSpec)[idx]); + } else if (this instanceof MultiArgMorph) { + deflt = this.labelPart(this.slotSpec); + } else if (this instanceof ReporterSlotMorph) { + deflt = this.emptySlot(); + } + } + // set default value + if (!noValues) { + if (inp !== -1) { + if (deflt instanceof MultiArgMorph) { + deflt.setContents(this.defaults); + deflt.defaults = this.defaults; + } else if (!isNil(this.defaults[inp])) { + deflt.setContents(this.defaults[inp]); + } + } + } + this.silentReplaceInput(arg, deflt); + if (deflt instanceof MultiArgMorph) { + deflt.refresh(); + } else if (deflt instanceof RingMorph) { + deflt.fixBlockColor(); + } +}; + +SyntaxElementMorph.prototype.isLocked = function () { + // answer true if I can be exchanged by a dropped reporter + return this.isStatic; +}; + +// SyntaxElementMorph enumerating: + +SyntaxElementMorph.prototype.topBlock = function () { + if (this.parent && this.parent.topBlock) { + return this.parent.topBlock(); + } + return this; +}; + +// SyntaxElementMorph drag & drop: + +SyntaxElementMorph.prototype.reactToGrabOf = function (grabbedMorph) { + var topBlock = this.topBlock(), + affected; + if (grabbedMorph instanceof CommandBlockMorph) { + affected = this.parentThatIsA(CommandSlotMorph); + if (affected) { + this.startLayout(); + affected.fixLayout(); + this.endLayout(); + } + } + if (topBlock) { + topBlock.allComments().forEach(function (comment) { + comment.align(topBlock); + }); + if (topBlock.getHighlight()) { + topBlock.addHighlight(topBlock.removeHighlight()); + } + } +}; + +// SyntaxElementMorph 3D - border color rendering: + +SyntaxElementMorph.prototype.bright = function () { + return this.color.lighter(this.contrast).toString(); +}; + +SyntaxElementMorph.prototype.dark = function () { + return this.color.darker(this.contrast).toString(); +}; + +// SyntaxElementMorph color changing: + +SyntaxElementMorph.prototype.setColor = function (aColor) { + if (aColor) { + if (!this.color.eq(aColor)) { + this.color = aColor; + this.drawNew(); + this.children.forEach(function (child) { + child.drawNew(); + child.changed(); + }); + this.changed(); + } + } +}; + +SyntaxElementMorph.prototype.setLabelColor = function ( + textColor, + shadowColor, + shadowOffset +) { + this.children.forEach(function (morph) { + if (morph instanceof StringMorph && !morph.isProtectedLabel) { + morph.shadowOffset = shadowOffset || morph.shadowOffset; + morph.shadowColor = shadowColor || morph.shadowColor; + morph.setColor(textColor); + } else if (morph instanceof MultiArgMorph + || morph instanceof ArgLabelMorph + || (morph instanceof SymbolMorph && !morph.isProtectedLabel) + || (morph instanceof InputSlotMorph + && morph.isReadOnly)) { + morph.setLabelColor(textColor, shadowColor, shadowOffset); + } + }); +}; + + +// SyntaxElementMorph zebra coloring + +SyntaxElementMorph.prototype.fixBlockColor = function ( + nearestBlock, + isForced +) { + this.children.forEach(function (morph) { + if (morph instanceof SyntaxElementMorph) { + morph.fixBlockColor(nearestBlock, isForced); + } + }); +}; + +// SyntaxElementMorph label parts: + +SyntaxElementMorph.prototype.labelPart = function (spec) { + var part; + if (spec[0] === '%' && + spec.length > 1 && + this.selector !== 'reportGetVar') { + // check for variable multi-arg-slot: + if ((spec.length > 5) && (spec.slice(0, 5) === '%mult')) { + part = new MultiArgMorph(spec.slice(5)); + part.addInput(); + return part; + } + + // single-arg and specialized multi-arg slots: + switch (spec) { + case '%inputs': + part = new MultiArgMorph('%s', 'with inputs'); + part.isStatic = false; + part.canBeEmpty = false; + break; + case '%scriptVars': + part = new MultiArgMorph('%t', null, 1, spec); + part.canBeEmpty = false; + break; + case '%parms': + part = new MultiArgMorph('%t', 'Input Names:', 0, spec); + part.canBeEmpty = false; + break; + case '%ringparms': + part = new MultiArgMorph( + '%t', + 'input names:', + 0, + spec + ); + break; + case '%cmdRing': + part = new RingMorph(); + part.color = SpriteMorph.prototype.blockColor.other; + part.selector = 'reifyScript'; + part.setSpec('%rc %ringparms'); + part.isDraggable = true; + break; + case '%repRing': + part = new RingMorph(); + part.color = SpriteMorph.prototype.blockColor.other; + part.selector = 'reifyReporter'; + part.setSpec('%rr %ringparms'); + part.isDraggable = true; + part.isStatic = true; + break; + case '%predRing': + part = new RingMorph(true); + part.color = SpriteMorph.prototype.blockColor.other; + part.selector = 'reifyPredicate'; + part.setSpec('%rp %ringparms'); + part.isDraggable = true; + part.isStatic = true; + break; + case '%words': + part = new MultiArgMorph('%s', null, 0); + part.addInput(); // allow for default value setting + part.addInput(); // allow for default value setting + part.isStatic = false; + break; + case '%exp': + part = new MultiArgMorph('%s', null, 0); + part.addInput(); + part.isStatic = true; + part.canBeEmpty = false; + break; + case '%br': + part = new Morph(); + part.setExtent(new Point(0, 0)); + part.isBlockLabelBreak = true; + part.getSpec = function () { + return '%br'; + }; + break; + case '%inputName': + part = new ReporterBlockMorph(); + part.category = 'variables'; + part.color = SpriteMorph.prototype.blockColor.variables; + part.setSpec(localize('Input name')); + break; + case '%s': + part = new InputSlotMorph(); + break; + case '%anyUE': + part = new InputSlotMorph(); + part.isUnevaluated = true; + break; + case '%txt': + part = new InputSlotMorph(); + part.minWidth = part.height() * 1.7; // "landscape" + part.fixLayout(); + break; + case '%mlt': + part = new TextSlotMorph(); + part.fixLayout(); + break; + case '%code': + part = new TextSlotMorph(); + part.contents().fontName = 'monospace'; + part.contents().fontStyle = 'monospace'; + part.fixLayout(); + break; + case '%obj': + part = new ArgMorph('object'); + break; + case '%n': + part = new InputSlotMorph(null, true); + break; + case '%dir': + part = new InputSlotMorph( + null, + true, + { + '(90) right' : 90, + '(-90) left' : -90, + '(0) up' : '0', + '(180) down' : 180 + } + ); + part.setContents(90); + break; + case '%inst': + part = new InputSlotMorph( + null, + true, + { + '(1) Acoustic Grand' : 1, + '(2) Bright Acoustic' : 2, + '(3) Electric Grand' : 3, + '(4) Honky Tonk' : 4, + '(5) Electric Piano 1' : 5, + '(6) Electric Piano 2' : 6, + '(7) Harpsichord' : 7 + } + ); + part.setContents(1); + break; + case '%month': + part = new InputSlotMorph( + null, // text + false, // numeric? + { + 'January' : ['January'], + 'February' : ['February'], + 'March' : ['March'], + 'April' : ['April'], + 'May' : ['May'], + 'June' : ['June'], + 'July' : ['July'], + 'August' : ['August'], + 'September' : ['September'], + 'October' : ['October'], + 'November' : ['November'], + 'December' : ['December'] + }, + true // read-only + ); + break; + case '%delim': + part = new InputSlotMorph( + null, // text + false, // numeric? + { + 'whitespace' : ['whitespace'], + 'line' : ['line'], + 'tab' : ['tab'], + 'cr' : ['cr'] + }, + false // read-only + ); + break; + case '%ida': + part = new InputSlotMorph( + null, + true, + { + '1' : 1, + last : ['last'], + '~' : null, + all : ['all'] + } + ); + part.setContents(1); + break; + case '%idx': + part = new InputSlotMorph( + null, + true, + { + '1' : 1, + last : ['last'], + any : ['any'] + } + ); + part.setContents(1); + break; + case '%spr': + part = new InputSlotMorph( + null, + false, + 'objectsMenu', + true + ); + break; + case '%col': // collision detection + part = new InputSlotMorph( + null, + false, + 'collidablesMenu', + true + ); + break; + case '%dst': // distance measuring + part = new InputSlotMorph( + null, + false, + 'distancesMenu', + true + ); + break; + case '%cln': // clones + part = new InputSlotMorph( + null, + false, + 'clonablesMenu', + true + ); + break; + case '%cst': + part = new InputSlotMorph( + null, + false, + 'costumesMenu', + true + ); + break; + case '%eff': + part = new InputSlotMorph( + null, + false, + { + /* + color : 'color', + fisheye : 'fisheye', + whirl : 'whirl', + pixelate : 'pixelate', + mosaic : 'mosaic', + brightness : 'brightness', + */ + ghost : ['ghost'] + }, + true + ); + part.setContents(['ghost']); + break; + case '%snd': + part = new InputSlotMorph( + null, + false, + 'soundsMenu', + true + ); + break; + case '%key': + part = new InputSlotMorph( + null, + false, + { + 'up arrow': ['up arrow'], + 'down arrow': ['down arrow'], + 'right arrow': ['right arrow'], + 'left arrow': ['left arrow'], + space : ['space'], + a : ['a'], + b : ['b'], + c : ['c'], + d : ['d'], + e : ['e'], + f : ['f'], + g : ['g'], + h : ['h'], + i : ['i'], + j : ['j'], + k : ['k'], + l : ['l'], + m : ['m'], + n : ['n'], + o : ['o'], + p : ['p'], + q : ['q'], + r : ['r'], + s : ['s'], + t : ['t'], + u : ['u'], + v : ['v'], + w : ['w'], + x : ['x'], + y : ['y'], + z : ['z'], + '0' : ['0'], + '1' : ['1'], + '2' : ['2'], + '3' : ['3'], + '4' : ['4'], + '5' : ['5'], + '6' : ['6'], + '7' : ['7'], + '8' : ['8'], + '9' : ['9'] + }, + true + ); + part.setContents(['space']); + break; + case '%keyHat': + part = this.labelPart('%key'); + part.isStatic = true; + break; + case '%msg': + part = new InputSlotMorph( + null, + false, + 'messagesMenu', + true + ); + break; + case '%msgHat': + part = new InputSlotMorph( + null, + false, + 'messagesReceivedMenu', + true + ); + part.isStatic = true; + break; + case '%att': + part = new InputSlotMorph( + null, + false, + 'attributesMenu', + true + ); + part.isStatic = true; + break; + case '%fun': + part = new InputSlotMorph( + null, + false, + { + abs : ['abs'], + floor : ['floor'], + sqrt : ['sqrt'], + sin : ['sin'], + cos : ['cos'], + tan : ['tan'], + asin : ['asin'], + acos : ['acos'], + atan : ['atan'], + ln : ['ln'], + // log : 'log', + 'e^' : ['e^'] + // '10^' : '10^' + }, + true + ); + part.setContents(['sqrt']); + break; + case '%txtfun': + part = new InputSlotMorph( + null, + false, + { + 'encode URI' : ['encode URI'], + 'decode URI' : ['decode URI'], + 'encode URI component' : ['encode URI component'], + 'decode URI component' : ['decode URI component'], + 'XML escape' : ['XML escape'], + 'XML unescape' : ['XML unescape'], + 'hex sha512 hash' : ['hex sha512 hash'] + }, + true + ); + part.setContents(['encode URI']); + break; + case '%typ': + part = new InputSlotMorph( + null, + false, + { + number : ['number'], + text : ['text'], + Boolean : ['Boolean'], + list : ['list'], + command : ['command'], + reporter : ['reporter'], + predicate : ['predicate'] + // ring : 'ring' + // object : 'object' + }, + true + ); + part.setContents(['number']); + break; + case '%var': + part = new InputSlotMorph( + null, + false, + 'getVarNamesDict', + true + ); + part.isStatic = true; + break; + case '%lst': + part = new InputSlotMorph( + null, + false, + { + list1 : 'list1', + list2 : 'list2', + list3 : 'list3' + }, + true + ); + break; + case '%codeKind': + part = new InputSlotMorph( + null, + false, + { + code : ['code'], + header : ['header'] + }, + true + ); + part.setContents(['code']); + break; + case '%l': + part = new ArgMorph('list'); + break; + case '%b': + case '%boolUE': + part = new BooleanSlotMorph(null, true); + break; + case '%cmd': + part = new CommandSlotMorph(); + break; + case '%rc': + part = new RingCommandSlotMorph(); + part.isStatic = true; + break; + case '%rr': + part = new RingReporterSlotMorph(); + part.isStatic = true; + break; + case '%rp': + part = new RingReporterSlotMorph(true); + part.isStatic = true; + break; + case '%c': + part = new CSlotMorph(); + part.isStatic = true; + break; + case '%cs': + part = new CSlotMorph(); // non-static + break; + case '%clr': + part = new ColorSlotMorph(); + part.isStatic = true; + break; + case '%t': + part = new TemplateSlotMorph('a'); + break; + case '%upvar': + part = new TemplateSlotMorph('\u2191'); // up-arrow + break; + case '%f': + part = new FunctionSlotMorph(); + break; + case '%r': + part = new ReporterSlotMorph(); + break; + case '%p': + part = new ReporterSlotMorph(true); + break; + + // code mapping (experimental) + + case '%codeListPart': + part = new InputSlotMorph( + null, // text + false, // numeric? + { + 'list' : ['list'], + 'item' : ['item'], + 'delimiter' : ['delimiter'] + }, + true // read-only + ); + break; + case '%codeListKind': + part = new InputSlotMorph( + null, // text + false, // numeric? + { + 'collection' : ['collection'], + 'variables' : ['variables'], + 'parameters' : ['parameters'] + }, + true // read-only + ); + break; + + + // symbols: + + case '%turtle': + part = new SymbolMorph('turtle'); + part.size = this.fontSize * 1.2; + part.color = new Color(255, 255, 255); + part.shadowColor = this.color.darker(this.labelContrast); + part.shadowOffset = MorphicPreferences.isFlat ? + new Point() : this.embossing; + part.drawNew(); + break; + case '%turtleOutline': + part = new SymbolMorph('turtleOutline'); + part.size = this.fontSize; + part.color = new Color(255, 255, 255); + part.isProtectedLabel = true; // doesn't participate in zebraing + part.shadowColor = this.color.darker(this.labelContrast); + part.shadowOffset = MorphicPreferences.isFlat ? + new Point() : this.embossing; + part.drawNew(); + break; + case '%clockwise': + part = new SymbolMorph('turnRight'); + part.size = this.fontSize * 1.5; + part.color = new Color(255, 255, 255); + part.isProtectedLabel = false; // zebra colors + part.shadowColor = this.color.darker(this.labelContrast); + part.shadowOffset = MorphicPreferences.isFlat ? + new Point() : this.embossing; + part.drawNew(); + break; + case '%counterclockwise': + part = new SymbolMorph('turnLeft'); + part.size = this.fontSize * 1.5; + part.color = new Color(255, 255, 255); + part.isProtectedLabel = false; // zebra colors + part.shadowColor = this.color.darker(this.labelContrast); + part.shadowOffset = MorphicPreferences.isFlat ? + new Point() : this.embossing; + part.drawNew(); + break; + case '%greenflag': + part = new SymbolMorph('flag'); + part.size = this.fontSize * 1.5; + part.color = new Color(0, 200, 0); + part.isProtectedLabel = true; // doesn't participate in zebraing + part.shadowColor = this.color.darker(this.labelContrast); + part.shadowOffset = MorphicPreferences.isFlat ? + new Point() : this.embossing; + part.drawNew(); + break; + case '%stop': + part = new SymbolMorph('octagon'); + part.size = this.fontSize * 1.5; + part.color = new Color(200, 0, 0); + part.isProtectedLabel = true; // doesn't participate in zebraing + part.shadowColor = this.color.darker(this.labelContrast); + part.shadowOffset = MorphicPreferences.isFlat ? + new Point() : this.embossing; + part.drawNew(); + break; + case '%pause': + part = new SymbolMorph('pause'); + part.size = this.fontSize; + part.color = new Color(160, 80, 0); + part.isProtectedLabel = true; // doesn't participate in zebraing + part.shadowColor = this.color.darker(this.labelContrast); + part.shadowOffset = MorphicPreferences.isFlat ? + new Point() : this.embossing; + part.drawNew(); + break; + default: + // nop(); + } + } else { + part = new StringMorph(spec); + part.fontName = this.labelFontName; + part.fontStyle = this.labelFontStyle; + part.fontSize = this.fontSize; + part.color = new Color(255, 255, 255); + part.isBold = true; + part.shadowColor = this.color.darker(this.labelContrast); + part.shadowOffset = MorphicPreferences.isFlat ? + new Point() : this.embossing; + part.drawNew(); + } + return part; +}; + +// SyntaxElementMorph layout: + +SyntaxElementMorph.prototype.fixLayout = function () { + var nb, + parts = this.parts(), + myself = this, + x = 0, + y, + lineHeight = 0, + maxX = 0, + blockWidth = this.minWidth, + blockHeight, + affected, + l = [], + lines = [], + space = this.isPrototype ? + 1 : Math.floor(fontHeight(this.fontSize) / 3), + bottomCorrection, + initialExtent = this.extent(); + + if ((this instanceof MultiArgMorph) && (this.slotSpec !== '%c')) { + blockWidth += this.arrows().width(); + } else if (this instanceof ReporterBlockMorph) { + blockWidth += (this.rounding * 2) + (this.edge * 2); + } else { + blockWidth += (this.corner * 4) + + (this.edge * 2) + + (this.inset * 3) + + this.dent; + } + + if (this.nextBlock) { + nb = this.nextBlock(); + } + + // determine lines + parts.forEach(function (part) { + if ((part instanceof CSlotMorph) + || (part.slotSpec === '%c')) { + if (l.length > 0) { + lines.push(l); + lines.push([part]); + l = []; + x = 0; + } else { + lines.push([part]); + } + } else if (part instanceof BlockHighlightMorph) { + nop(); // should be redundant now + // myself.fullChanged(); + // myself.removeChild(part); + } else { + if (part.isVisible) { + x += part.fullBounds().width() + space; + } + if ((x > myself.labelWidth) || part.isBlockLabelBreak) { + if (l.length > 0) { + lines.push(l); + l = []; + x = part.fullBounds().width() + space; + } + } + l.push(part); + if (part.isBlockLabelBreak) { + x = 0; + } + } + }); + if (l.length > 0) { + lines.push(l); + } + + // distribute parts on lines + if (this instanceof CommandBlockMorph) { + y = this.top() + this.corner + this.edge; + if (this instanceof HatBlockMorph) { + y += this.hatHeight; + } + } else if (this instanceof ReporterBlockMorph) { + y = this.top() + (this.edge * 2); + } else if (this instanceof MultiArgMorph + || this instanceof ArgLabelMorph) { + y = this.top(); + } + lines.forEach(function (line) { + x = myself.left() + myself.edge + myself.labelPadding; + if (myself instanceof RingMorph) { + x = myself.left() + space; //myself.labelPadding; + } else if (myself.isPredicate) { + x = myself.left() + myself.rounding; + } else if (myself instanceof MultiArgMorph + || myself instanceof ArgLabelMorph) { + x = myself.left(); + } + y += lineHeight; + lineHeight = 0; + line.forEach(function (part) { + if (part instanceof CSlotMorph) { + x -= myself.labelPadding; + if (myself.isPredicate) { + x = myself.left() + myself.rounding; + } + part.setColor(myself.color); + part.setPosition(new Point(x, y)); + lineHeight = part.height(); + } else { + part.setPosition(new Point(x, y)); + if (!part.isBlockLabelBreak) { + if (part.slotSpec === '%c') { + x += part.width(); + } else if (part.isVisible) { + x += part.fullBounds().width() + space; + } + } + maxX = Math.max(maxX, x); + lineHeight = Math.max( + lineHeight, + part instanceof StringMorph ? + part.rawHeight() : part.height() + ); + } + }); + + // center parts vertically on each line: + line.forEach(function (part) { + part.moveBy(new Point( + 0, + Math.floor((lineHeight - part.height()) / 2) + )); + }); + }); + + // determine my height: + y += lineHeight; + if (this.children.some(function (any) { + return any instanceof CSlotMorph; + })) { + bottomCorrection = this.bottomPadding; + if (this instanceof ReporterBlockMorph && !this.isPredicate) { + bottomCorrection = Math.max( + this.bottomPadding, + this.rounding - this.bottomPadding + ); + } + y += bottomCorrection; + } + if (this instanceof CommandBlockMorph) { + blockHeight = y - this.top() + (this.corner * 2); + } else if (this instanceof ReporterBlockMorph) { + blockHeight = y - this.top() + (this.edge * 2); + } else if (this instanceof MultiArgMorph + || this instanceof ArgLabelMorph) { + blockHeight = y - this.top(); + } + + // determine my width: + if (this.isPredicate) { + blockWidth = Math.max( + blockWidth, + maxX - this.left() + this.rounding + ); + } else if (this instanceof MultiArgMorph + || this instanceof ArgLabelMorph) { + blockWidth = Math.max( + blockWidth, + maxX - this.left() - space + ); + } else { + blockWidth = Math.max( + blockWidth, + maxX - this.left() + this.labelPadding - this.edge + ); + // adjust right padding if rightmost input has arrows + if (parts[parts.length - 1] instanceof MultiArgMorph + && (lines.length === 1)) { + blockWidth -= space; + } + // adjust width to hat width + if (this instanceof HatBlockMorph) { + blockWidth = Math.max(blockWidth, this.hatWidth * 1.5); + } + } + + // set my extent: + this.setExtent(new Point(blockWidth, blockHeight)); + + // adjust CSlots + parts.forEach(function (part) { + if (part instanceof CSlotMorph) { + if (myself.isPredicate) { + part.setWidth(blockWidth - myself.rounding * 2); + } else { + part.setWidth(blockWidth - myself.edge); + } + } + }); + + // redraw in order to erase CSlot backgrounds + this.drawNew(); + + // position next block: + if (nb) { + nb.setPosition( + new Point( + this.left(), + this.bottom() - (this.corner) + ) + ); + } + + // find out if one of my parents needs to be fixed + if (this instanceof CommandBlockMorph) { + if (this.height() !== initialExtent.y) { + affected = this.parentThatIsA(CommandSlotMorph); + if (affected) { + affected.fixLayout(); + } + } + if (this.width() !== initialExtent.x) { + affected = this.parentThatIsAnyOf( + [ReporterBlockMorph, CommandSlotMorph, RingCommandSlotMorph] + ); + if (affected) { + affected.fixLayout(); + } + } + if (affected) { + return; + } + } else if (this instanceof ReporterBlockMorph) { + if (this.parent) { + if (this.parent.fixLayout) { + return this.parent.fixLayout(); + } + } + } + + this.fixHighlight(); +}; + +SyntaxElementMorph.prototype.fixHighlight = function () { + var top = this.topBlock(); + if (top.getHighlight && top.getHighlight()) { + top.addHighlight(top.removeHighlight()); + } +}; + +// SyntaxElementMorph evaluating: + +SyntaxElementMorph.prototype.evaluate = function () { + // responsibility of my children, default is to answer null + return null; +}; + +SyntaxElementMorph.prototype.isEmptySlot = function () { + // responsibility of my children, default is to answer false + return false; +}; + +// SyntaxElementMorph speech bubble feedback: + +SyntaxElementMorph.prototype.showBubble = function (value) { + var bubble, + txt, + img, + morphToShow, + isClickable = false, + wrrld = this.world(); + + if ((value === undefined) || !wrrld) { + return null; + } + if (value instanceof ListWatcherMorph) { + morphToShow = value; + morphToShow.update(true); + morphToShow.step = value.update; + morphToShow.isDraggable = false; + isClickable = true; + } else if (value instanceof Morph) { + img = value.fullImage(); + morphToShow = new Morph(); + morphToShow.silentSetWidth(img.width); + morphToShow.silentSetHeight(img.height); + morphToShow.image = img; + } else if (value instanceof Context) { + img = value.image(); + morphToShow = new Morph(); + morphToShow.silentSetWidth(img.width); + morphToShow.silentSetHeight(img.height); + morphToShow.image = img; + } else if (typeof value === 'boolean') { + morphToShow = SpriteMorph.prototype.booleanMorph.call( + null, + value + ); + } else if (isString(value)) { + txt = value.length > 500 ? value.slice(0, 500) + '...' : value; + morphToShow = new TextMorph( + txt, + this.fontSize, + null, + false, + false, + 'center' + ); + } else if (value === null) { + morphToShow = new TextMorph( + '', + this.fontSize, + null, + false, + false, + 'center' + ); + } else if (value === 0) { + morphToShow = new TextMorph( + '0', + this.fontSize, + null, + false, + false, + 'center' + ); + } else if (value.toString) { + morphToShow = new TextMorph( + value.toString(), + this.fontSize, + null, + false, + false, + 'center' + ); + } + bubble = new SpeechBubbleMorph( + morphToShow, + null, + Math.max(this.rounding - 2, 6), + 0 + ); + bubble.popUp( + wrrld, + this.rightCenter().add(new Point(2, 0)), + isClickable + ); +}; + +// SyntaxElementMorph code mapping + +/* + code mapping lets you use blocks to generate arbitrary text-based + source code that can be exported and compiled / embedded elsewhere, + it's not part of Snap's evaluator and not needed for Snap itself +*/ + +SyntaxElementMorph.prototype.mappedCode = function (definitions) { + var result = this.evaluate(); + if (result instanceof BlockMorph) { + return result.mappedCode(definitions); + } + return result; +}; + +// SyntaxElementMorph layout update optimization + +SyntaxElementMorph.prototype.startLayout = function () { + this.topBlock().fullChanged(); + Morph.prototype.trackChanges = false; +}; + +SyntaxElementMorph.prototype.endLayout = function () { + Morph.prototype.trackChanges = true; + this.topBlock().fullChanged(); +}; + + +// BlockMorph ////////////////////////////////////////////////////////// + +/* + I am an abstraction of all blocks (commands, reporters, hats). + + Aside from the visual settings inherited from Morph and + SyntaxElementMorph my most important attributes and public + accessors are: + + selector - (string) name of method to be triggered + receiver() - answer the object (sprite) to which I apply + inputs() - answer an array with my arg slots and nested reporters + defaults - an optional Array containing default input values + topBlock() - answer the top block of the stack I'm attached to + blockSpec - a formalized description of my label parts + setSpec() - force me to change my label structure + evaluate() - answer the result of my evaluation + isUnevaluated() - answer whether I am part of a special form + + Zebra coloring provides a mechanism to alternate brightness of nested, + same colored blocks (of the same category). The deviation of alternating + brightness is set in the preferences setting: + + zebraContrast - percentage of brightness deviation + + attribute. If the attribute is set to zero, zebra coloring is turned + off. If it is a positive number, nested blocks will be colored in + a brighter shade of the same hue and the label color (for texts) + alternates between white and black. If the attribute is set to a negative + number, nested blocks are colored in a darker shade of the same hue + with no alternating label colors. + + Note: Some of these methods are inherited from SyntaxElementMorph + for technical reasons, because they are shared among Block and + MultiArgMorph (e.g. topBlock()). + + blockSpec is a formatted string consisting of plain words and + reserved words starting with the percent character (%), which + represent the following pre-defined input slots and/or label + features: + + arity: single + + %br - user-forced line break + %s - white rectangular type-in slot ("string-type") + %txt - white rectangular type-in slot ("text-type") + %mlt - white rectangular type-in slot ("multi-line-text-type") + %code - white rectangular type-in slot, monospaced font + %n - white roundish type-in slot ("numerical") + %dir - white roundish type-in slot with drop-down for directions + %inst - white roundish type-in slot with drop-down for instruments + %ida - white roundish type-in slot with drop-down for list indices + %idx - white roundish type-in slot for indices incl. "any" + %obj - specially drawn slot for object reporters + %spr - chameleon colored rectangular drop-down for object-names + %col - chameleon colored rectangular drop-down for collidables + %dst - chameleon colored rectangular drop-down for distances + %cst - chameleon colored rectangular drop-down for costume-names + %eff - chameleon colored rectangular drop-down for graphic effects + %snd - chameleon colored rectangular drop-down for sound names + %key - chameleon colored rectangular drop-down for keyboard keys + %msg - chameleon colored rectangular drop-down for messages + %att - chameleon colored rectangular drop-down for attributes + %fun - chameleon colored rectangular drop-down for math functions + %typ - chameleon colored rectangular drop-down for data types + %var - chameleon colored rectangular drop-down for variable names + %lst - chameleon colored rectangular drop-down for list names + %b - chameleon colored hexagonal slot (for predicates) + %l - list icon + %c - C-shaped command slot + %clr - interactive color slot + %t - inline variable reporter template + %anyUE - white rectangular type-in slot, unevaluated if replaced + %boolUE - chameleon colored hexagonal slot, unevaluated if replaced + %f - round function slot, unevaluated if replaced, + %r - round reporter slot + %p - hexagonal predicate slot + + rings: + + %cmdRing - command slotted ring with %ringparms + %repRing - round slotted ringn with %ringparms + %predRing - diamond slotted ring with %ringparms + + arity: multiple + + %mult%x - where %x stands for any of the above single inputs + %inputs - for an additional text label 'with inputs' + %words - for an expandable list of default 2 (used in JOIN) + %exp - for a static expandable list of minimum 0 (used in LIST) + %scriptVars - for an expandable list of variable reporter templates + %parms - for an expandable list of formal parameters + %ringparms - the same for use inside Rings + + special form: upvar + + %upvar - same as %t (inline variable reporter template) + + special form: input name + + %inputName - variable blob (used in input type dialog) + + examples: + + 'if %b %c else %c' - creates Scratch's If/Else block + 'set pen color to %clr' - creates Scratch's Pen color block + 'list %mult%s' - creates BYOB's list reporter block + 'call %n %inputs' - creates BYOB's Call block + 'the script %parms %c' - creates BYOB's THE SCRIPT block +*/ + +// BlockMorph inherits from SyntaxElementMorph: + +BlockMorph.prototype = new SyntaxElementMorph(); +BlockMorph.prototype.constructor = BlockMorph; +BlockMorph.uber = SyntaxElementMorph.prototype; + +// BlockMorph preferences settings: + +BlockMorph.prototype.zebraContrast = 40; // alternating color brightness + +// BlockMorph sound feedback: + +BlockMorph.prototype.snapSound = null; + +BlockMorph.prototype.toggleSnapSound = function () { + if (this.snapSound !== null) { + this.snapSound = null; + } else { + BlockMorph.prototype.snapSound = document.createElement('audio'); + BlockMorph.prototype.snapSound.src = 'click.wav'; + } + CommentMorph.prototype.snapSound = BlockMorph.prototype.snapSound; +}; + +// BlockMorph instance creation: + +function BlockMorph() { + this.init(); +} + +BlockMorph.prototype.init = function () { + this.selector = null; // name of method to be triggered + this.blockSpec = ''; // formal description of label and arguments + this.comment = null; // optional "sticky" comment morph + + // not to be persisted: + this.instantiationSpec = null; // spec to set upon fullCopy() of template + this.category = null; // for zebra coloring (non persistent) + + BlockMorph.uber.init.call(this); + this.color = new Color(0, 17, 173); +}; + +BlockMorph.prototype.receiver = function () { + // answer the object to which I apply (whose method I represent) + var up = this.parent; + while (!!up) { + if (up.owner) { + return up.owner; + } + up = up.parent; + } + return null; +}; + +BlockMorph.prototype.toString = function () { + return 'a ' + + (this.constructor.name || + this.constructor.toString().split(' ')[1].split('(')[0]) + + ' ("' + + this.blockSpec.slice(0, 30) + '...")'; +}; + +// BlockMorph spec: + +BlockMorph.prototype.parseSpec = function (spec) { + var result = [], + words, + word = ''; + + words = isString(spec) ? spec.split(' ') : []; + if (words.length === 0) { + words = [spec]; + } + if (this.labelWordWrap) { + return words; + } + + function addWord(w) { + if ((w[0] === '%') && (w.length > 1)) { + if (word !== '') { + result.push(word); + word = ''; + } + result.push(w); + } else { + if (word !== '') { + word += ' ' + w; + } else { + word = w; + } + } + } + + words.forEach(function (each) { + addWord(each); + }); + if (word !== '') { + result.push(word); + } + return result; +}; + +BlockMorph.prototype.setSpec = function (spec) { + var myself = this, + part; + + if (!spec) {return; } + this.parts().forEach(function (part) { + part.destroy(); + }); + if (this.isPrototype) { + this.add(this.placeHolder()); + } + this.parseSpec(spec).forEach(function (word) { + part = myself.labelPart(word); + myself.add(part); + if (!(part instanceof CommandSlotMorph)) { + part.drawNew(); + } + if (part instanceof RingMorph) { + part.fixBlockColor(); + } + if (part instanceof MultiArgMorph + || contains( + [CommandSlotMorph, RingCommandSlotMorph], + part.constructor + )) { + part.fixLayout(); + } + if (myself.isPrototype) { + myself.add(myself.placeHolder()); + } + }); + this.blockSpec = spec; + this.fixLayout(); +}; + +BlockMorph.prototype.buildSpec = function () { + // create my blockSpec from my parts - for demo purposes only + var myself = this; + this.blockSpec = ''; + this.parts().forEach(function (part) { + if (part instanceof StringMorph) { + myself.blockSpec += part.text; + } else if (part instanceof ArgMorph) { + myself.blockSpec += part.getSpec(); + } else if (part.isBlockLabelBreak) { + myself.blockSpec += part.getSpec(); + } else { + myself.blockSpec += '[undefined]'; + } + myself.blockSpec += ' '; + }); + this.blockSpec = this.blockSpec.trim(); +}; + +BlockMorph.prototype.rebuild = function (contrast) { + // rebuild my label fragments, for use in ToggleElementMorphs + this.setSpec(this.blockSpec); + if (contrast) { + this.inputs().forEach(function (input) { + if (input instanceof ReporterBlockMorph) { + input.setColor(input.color.lighter(contrast)); + input.setSpec(input.blockSpec); + } + }); + } +}; + +// BlockMorph menu: + +BlockMorph.prototype.userMenu = function () { + var menu = new MenuMorph(this), + world = this.world(), + myself = this, + blck; + + menu.addItem( + "help...", + 'showHelp' + ); + if (this.isTemplate) { + if (this.selector !== 'evaluateCustomBlock') { + menu.addItem( + "hide", + 'hidePrimitive' + ); + } + if (StageMorph.prototype.enableCodeMapping) { + menu.addLine(); + menu.addItem( + 'header mapping...', + 'mapToHeader' + ); + menu.addItem( + 'code mapping...', + 'mapToCode' + ); + } + return menu; + } + menu.addLine(); + if (this.selector === 'reportGetVar') { + blck = this.fullCopy(); + blck.addShadow(); + menu.addItem( + 'rename...', + function () { + new DialogBoxMorph( + myself, + myself.setSpec, + myself + ).prompt( + "Variable name", + myself.blockSpec, + world, + blck.fullImage(), // pic + InputSlotMorph.prototype.getVarNamesDict.call(myself) + ); + } + ); + } else if (SpriteMorph.prototype.blockAlternatives[this.selector]) { + menu.addItem( + 'relabel...', + function () { + myself.relabel( + SpriteMorph.prototype.blockAlternatives[myself.selector] + ); + } + ); + } + + menu.addItem( + "duplicate", + function () { + this.fullCopy().pickUp(world); + }, + 'make a copy\nand pick it up' + ); + if (this instanceof CommandBlockMorph && this.nextBlock()) { + menu.addItem( + this.thumbnail(0.5, 60, false), + function () { + var cpy = this.fullCopy(), + nb = cpy.nextBlock(); + if (nb) {nb.destroy(); } + cpy.pickUp(world); + }, + 'only duplicate this block' + ); + } + menu.addItem( + "delete", + 'userDestroy' + ); + menu.addItem( + "script pic...", + function () { + window.open(myself.topBlock().fullImage().toDataURL()); + }, + 'open a new window\nwith a picture of this script' + ); + if (this.parentThatIsA(RingMorph)) { + menu.addLine(); + menu.addItem("unringify", 'unringify'); + return menu; + } + if (this.parent instanceof ReporterSlotMorph + || (this.parent instanceof CommandSlotMorph) + || (this instanceof HatBlockMorph) + || (this instanceof CommandBlockMorph + && (this.topBlock() instanceof HatBlockMorph))) { + return menu; + } + menu.addLine(); + menu.addItem("ringify", 'ringify'); + if (StageMorph.prototype.enableCodeMapping) { + menu.addLine(); + menu.addItem( + 'header mapping...', + 'mapToHeader' + ); + menu.addItem( + 'code mapping...', + 'mapToCode' + ); + } + return menu; +}; + +BlockMorph.prototype.developersMenu = function () { + var menu = BlockMorph.uber.developersMenu.call(this); + menu.addLine(); + menu.addItem("delete block", 'deleteBlock'); + menu.addItem("spec...", function () { + + new DialogBoxMorph( + this, + this.setSpec, + this + ).prompt( + menu.title + '\nspec', + this.blockSpec, + this.world() + ); + }); + return menu; +}; + +BlockMorph.prototype.hidePrimitive = function () { + var ide = this.parentThatIsA(IDE_Morph), + cat; + if (!ide) {return; } + StageMorph.prototype.hiddenPrimitives[this.selector] = true; + cat = { + doWarp: 'control', + reifyScript: 'operators', + reifyReporter: 'operators', + reifyPredicate: 'operators', + doDeclareVariables: 'variables' + }[this.selector] || this.category; + if (cat === 'lists') {cat = 'variables'; } + ide.flushBlocksCache(cat); + ide.refreshPalette(); +}; + +BlockMorph.prototype.deleteBlock = function () { + // delete just this one block, keep inputs and next block around + var scripts = this.parentThatIsA(ScriptsMorph), + nb = this.nextBlock ? this.nextBlock() : null, + tobefixed, + isindef; + if (scripts) { + if (nb) { + scripts.add(nb); + } + this.inputs().forEach(function (inp) { + if (inp instanceof BlockMorph) { + scripts.add(inp); + } + }); + } + if (this instanceof ReporterBlockMorph) { + if (this.parent instanceof BlockMorph) { + this.parent.revertToDefaultInput(this); + } + } else { // CommandBlockMorph + if (this.parent) { + if (this.parent.fixLayout) { + tobefixed = this.parentThatIsA(ArgMorph); + } + } else { // must be in a custom block definition + isindef = true; + } + } + this.destroy(); + if (isindef) { + /* + since the definition's body still points to this block + even after it has been destroyed, mark it to be deleted + later. + */ + this.isCorpse = true; + } + if (tobefixed) { + tobefixed.fixLayout(); + } +}; + +BlockMorph.prototype.ringify = function () { + // wrap a Ring around me + var ring = new RingMorph(), + top = this.topBlock(), + center = top.fullBounds().center(); + + if (this.parent === null) {return null; } + if (this.parent instanceof SyntaxElementMorph) { + if (this instanceof ReporterBlockMorph) { + this.parent.silentReplaceInput(this, ring); + ring.embed(this); + } else if (top) { // command + top.parent.add(ring); + ring.embed(top); + ring.setCenter(center); + } + } else { + this.parent.add(ring); + ring.embed(this); + ring.setCenter(center); + } + this.fixBlockColor(null, true); +}; + +BlockMorph.prototype.unringify = function () { + // remove a Ring around me, if any + var ring = this.parentThatIsA(RingMorph), + scripts = this.parentThatIsA(ScriptsMorph), + block, + center; + + if (ring === null) {return null; } + block = ring.contents(); + center = ring.center(); + + if (ring.parent instanceof SyntaxElementMorph) { + if (block instanceof ReporterBlockMorph) { + ring.parent.silentReplaceInput(ring, block); + } else if (scripts) { + scripts.add(block); + block.setFullCenter(center); + block.moveBy(20); + ring.parent.revertToDefaultInput(ring); + } + } else { + ring.parent.add(block); + block.setFullCenter(center); + ring.destroy(); + } + this.fixBlockColor(null, true); +}; + +BlockMorph.prototype.relabel = function (alternativeSelectors) { + var menu = new MenuMorph(this), + oldInputs = this.inputs(), + myself = this; + alternativeSelectors.forEach(function (sel) { + var block = SpriteMorph.prototype.blockForSelector(sel); + block.restoreInputs(oldInputs); + block.addShadow(new Point(3, 3)); + menu.addItem( + block, + function () { + myself.setSelector(sel); + } + ); + }); + menu.popup(this.world(), this.bottomLeft().subtract(new Point( + 8, + this instanceof CommandBlockMorph ? this.corner : 0 + ))); +}; + +BlockMorph.prototype.setSelector = function (aSelector) { + // private - used only for relabel() + var oldInputs = this.inputs(), + info; + info = SpriteMorph.prototype.blocks[aSelector]; + this.category = info.category; + this.selector = aSelector; + this.setSpec(localize(info.spec)); + this.restoreInputs(oldInputs); + this.fixLabelColor(); +}; + +BlockMorph.prototype.restoreInputs = function (oldInputs) { + // private - used only for relabel() + // try to restore my previous inputs when my spec has been changed + var i = 0, + old, + myself = this; + + this.inputs().forEach(function (inp) { + old = oldInputs[i]; + if (old instanceof ReporterBlockMorph) { + myself.silentReplaceInput(inp, old.fullCopy()); + } else if (old && inp instanceof InputSlotMorph) { + inp.setContents(old.evaluate()); + } + i += 1; + }); +}; + +BlockMorph.prototype.showHelp = function () { + var myself = this, + pic = new Image(), + help, + comment, + block, + isCustomBlock = this.selector === 'evaluateCustomBlock', + spec = isCustomBlock ? + this.definition.helpSpec() : this.selector, + ctx; + + pic.onload = function () { + help = newCanvas(new Point(pic.width, pic.height)); + ctx = help.getContext('2d'); + ctx.drawImage(pic, 0, 0); + new DialogBoxMorph().inform( + 'Help', + null, + myself.world(), + help + ); + }; + + if (isCustomBlock && this.definition.comment) { + block = this.fullCopy(); + block.addShadow(); + comment = this.definition.comment.fullCopy(); + comment.contents.parse(); + help = ''; + comment.contents.lines.forEach(function (line) { + help = help + '\n' + line; + }); + new DialogBoxMorph().inform( + 'Help', + help.substr(1), + myself.world(), + block.fullImage() + ); + } else { + pic.src = 'help/' + spec + '.png'; + } +}; + +// BlockMorph code mapping + +/* + code mapping lets you use blocks to generate arbitrary text-based + source code that can be exported and compiled / embedded elsewhere, + it's not part of Snap's evaluator and not needed for Snap itself +*/ + +BlockMorph.prototype.mapToHeader = function () { + // open a dialog box letting the user map header code via the GUI + var key = this.selector.substr(0, 5) === 'reify' ? + 'reify' : this.selector, + block = this.codeDefinitionHeader(), + myself = this, + help, + pic; + block.addShadow(new Point(3, 3)); + pic = block.fullImageClassic(); + if (this.definition) { + help = 'Enter code that corresponds to the block\'s definition. ' + + 'Use the formal parameter\nnames as shown and to ' + + 'reference the definition body\'s generated text code.'; + } else { + help = 'Enter code that corresponds to the block\'s definition. ' + + 'Choose your own\nformal parameter names (ignoring the ones ' + + 'shown .'; + } + new DialogBoxMorph( + this, + function (code) { + if (key === 'evaluateCustomBlock') { + myself.definition.codeHeader = code; + } else { + StageMorph.prototype.codeHeaders[key] = code; + } + }, + this + ).promptCode( + 'Header mapping', + key === 'evaluateCustomBlock' ? this.definition.codeHeader || '' + : StageMorph.prototype.codeHeaders[key] || '', + this.world(), + pic, + help + ); +}; + +BlockMorph.prototype.mapToCode = function () { + // open a dialog box letting the user map code via the GUI + var key = this.selector.substr(0, 5) === 'reify' ? + 'reify' : this.selector, + block = this.codeMappingHeader(), + myself = this, + pic; + block.addShadow(new Point(3, 3)); + pic = block.fullImageClassic(); + new DialogBoxMorph( + this, + function (code) { + if (key === 'evaluateCustomBlock') { + myself.definition.codeMapping = code; + } else { + StageMorph.prototype.codeMappings[key] = code; + } + }, + this + ).promptCode( + 'Code mapping', + key === 'evaluateCustomBlock' ? this.definition.codeMapping || '' + : StageMorph.prototype.codeMappings[key] || '', + this.world(), + pic, + 'Enter code that corresponds to the block\'s operation ' + + '(usually a single\nfunction invocation). Use <#n> to ' + + 'reference actual arguments as shown.' + ); +}; + +BlockMorph.prototype.mapHeader = function (aString, key) { + // primitive for programatically mapping header code + var sel = key || this.selector.substr(0, 5) === 'reify' ? + 'reify' : this.selector; + if (aString) { + if (this.definition) { // custom block + this.definition.codeHeader = aString; + } else { + StageMorph.prototype.codeHeaders[sel] = aString; + } + } +}; + +BlockMorph.prototype.mapCode = function (aString, key) { + // primitive for programatically mapping code + var sel = key || this.selector.substr(0, 5) === 'reify' ? + 'reify' : this.selector; + if (aString) { + if (this.definition) { // custom block + this.definition.codeMapping = aString; + } else { + StageMorph.prototype.codeMappings[sel] = aString; + } + } +}; + +BlockMorph.prototype.mappedCode = function (definitions) { + var key = this.selector.substr(0, 5) === 'reify' ? + 'reify' : this.selector, + code, + codeLines, + count = 1, + header, + headers, + headerLines, + body, + bodyLines, + defKey = this.definition ? this.definition.spec : key, + defs = definitions || {}, + parts = []; + code = key === 'reportGetVar' ? this.blockSpec + : this.definition ? this.definition.codeMapping || '' + : StageMorph.prototype.codeMappings[key] || ''; + + // map header + if (key !== 'reportGetVar' && !defs.hasOwnProperty(defKey)) { + defs[defKey] = null; // create the property for recursive definitions + if (this.definition) { + header = this.definition.codeHeader || ''; + if (header.indexOf(''), + bodyLines.join('\n' + prefix) + ); + headerLines[idx] = headerLines[idx].replace( + new RegExp('', 'g'), + bodyLines.join('\n') + ); + }); + header = headerLines.join('\n'); + } + defs[defKey] = header; + } else { + defs[defKey] = StageMorph.prototype.codeHeaders[defKey]; + } + } + + codeLines = code.split('\n'); + this.inputs().forEach(function (input) { + parts.push(input.mappedCode(defs).toString()); + }); + parts.forEach(function (part) { + var partLines = part.split('\n'), + placeHolder = '<#' + count + '>', + rx = new RegExp(placeHolder, 'g'); + codeLines.forEach(function (codeLine, idx) { + var prefix = '', + indent; + if (codeLine.trimLeft().indexOf(placeHolder) === 0) { + indent = codeLine.indexOf(placeHolder); + prefix = codeLine.slice(0, indent); + } + codeLines[idx] = codeLine.replace( + new RegExp(placeHolder), + partLines.join('\n' + prefix) + ); + codeLines[idx] = codeLines[idx].replace(rx, partLines.join('\n')); + }); + count += 1; + }); + code = codeLines.join('\n'); + if (this.nextBlock && this.nextBlock()) { // Command + code += ('\n' + this.nextBlock().mappedCode(defs)); + } + if (!definitions) { // top-level, add headers + headers = []; + Object.keys(defs).forEach(function (each) { + if (defs[each]) { + headers.push(defs[each]); + } + }); + if (headers.length) { + return headers.join('\n\n') + + '\n\n' + + code; + } + } + return code; +}; + +BlockMorph.prototype.codeDefinitionHeader = function () { + var block = this.definition ? new PrototypeHatBlockMorph(this.definition) + : SpriteMorph.prototype.blockForSelector(this.selector), + hat = new HatBlockMorph(), + count = 1; + + if (this.definition) {return block; } + block.inputs().forEach(function (input) { + var part = new TemplateSlotMorph('#' + count); + block.silentReplaceInput(input, part); + count += 1; + }); + block.isPrototype = true; + hat.setCategory("control"); + hat.setSpec('%s'); + hat.silentReplaceInput(hat.inputs()[0], block); + if (this.category === 'control') { + hat.alternateBlockColor(); + } + return hat; +}; + +BlockMorph.prototype.codeMappingHeader = function () { + var block = this.definition ? this.definition.blockInstance() + : SpriteMorph.prototype.blockForSelector(this.selector), + hat = new HatBlockMorph(), + count = 1; + + block.inputs().forEach(function (input) { + var part = new TemplateSlotMorph('<#' + count + '>'); + block.silentReplaceInput(input, part); + count += 1; + }); + block.isPrototype = true; + hat.setCategory("control"); + hat.setSpec('%s'); + hat.silentReplaceInput(hat.inputs()[0], block); + if (this.category === 'control') { + hat.alternateBlockColor(); + } + return hat; +}; + +// BlockMorph drawing + +BlockMorph.prototype.eraseHoles = function (context) { + var myself = this, + isReporter = this instanceof ReporterBlockMorph, + shift = this.edge * 0.5, + gradient, + rightX, + holes = this.parts().filter(function (part) { + return part.isHole; + }); + + if (this.isPredicate && (holes.length > 0)) { + rightX = this.width() - this.rounding; + context.clearRect( + rightX, + 0, + this.width(), + this.height() + ); + + // draw a 3D-ish vertical right edge + gradient = context.createLinearGradient( + rightX - this.edge, + 0, + this.width(), + 0 + ); + gradient.addColorStop(0, this.color.toString()); + gradient.addColorStop(1, this.dark()); + context.lineWidth = this.edge; + context.lineJoin = 'round'; + context.lineCap = 'round'; + context.strokeStyle = gradient; + context.beginPath(); + context.moveTo(rightX - shift, this.edge + shift); + context.lineTo(rightX - shift, this.height() - this.edge - shift); + context.stroke(); + } + holes.forEach(function (hole) { + var w = hole.width(), + h = Math.floor(hole.height()) - 2; // Opera needs this + context.clearRect( + Math.floor(hole.bounds.origin.x - myself.bounds.origin.x) + 1, + Math.floor(hole.bounds.origin.y - myself.bounds.origin.y) + 1, + isReporter ? w - 1 : w + 1, + h + ); + }); +}; + +// BlockMorph highlighting + +BlockMorph.prototype.addHighlight = function (oldHighlight) { + var isHidden = !this.isVisible, + highlight; + + if (isHidden) {this.show(); } + highlight = this.highlight( + oldHighlight ? oldHighlight.color : this.activeHighlight, + this.activeBlur, + this.activeBorder + ); + this.addBack(highlight); + this.fullChanged(); + if (isHidden) {this.hide(); } + return highlight; +}; + +BlockMorph.prototype.addErrorHighlight = function () { + var isHidden = !this.isVisible, + highlight; + + if (isHidden) {this.show(); } + this.removeHighlight(); + highlight = this.highlight( + this.errorHighlight, + this.activeBlur, + this.activeBorder + ); + this.addBack(highlight); + this.fullChanged(); + if (isHidden) {this.hide(); } + return highlight; +}; + +BlockMorph.prototype.removeHighlight = function () { + var highlight = this.getHighlight(); + if (highlight !== null) { + this.fullChanged(); + this.removeChild(highlight); + } + return highlight; +}; + +BlockMorph.prototype.toggleHighlight = function () { + if (this.getHighlight()) { + this.removeHighlight(); + } else { + this.addHighlight(); + } +}; + +BlockMorph.prototype.highlight = function (color, blur, border) { + var highlight = new BlockHighlightMorph(), + fb = this.fullBounds(), + edge = useBlurredShadows && !MorphicPreferences.isFlat ? + blur : border; + highlight.setExtent(fb.extent().add(edge * 2)); + highlight.color = color; + highlight.image = useBlurredShadows && !MorphicPreferences.isFlat ? + this.highlightImageBlurred(color, blur) + : this.highlightImage(color, border); + highlight.setPosition(fb.origin.subtract(new Point(edge, edge))); + return highlight; +}; + +BlockMorph.prototype.highlightImage = function (color, border) { + var fb, img, hi, ctx, out; + fb = this.fullBounds().extent(); + img = this.fullImage(); + + hi = newCanvas(fb.add(border * 2)); + ctx = hi.getContext('2d'); + + ctx.drawImage(img, 0, 0); + ctx.drawImage(img, border, 0); + ctx.drawImage(img, border * 2, 0); + ctx.drawImage(img, border * 2, border); + ctx.drawImage(img, border * 2, border * 2); + ctx.drawImage(img, border, border * 2); + ctx.drawImage(img, 0, border * 2); + ctx.drawImage(img, 0, border); + + ctx.globalCompositeOperation = 'destination-out'; + ctx.drawImage(img, border, border); + + out = newCanvas(fb.add(border * 2)); + ctx = out.getContext('2d'); + ctx.drawImage(hi, 0, 0); + ctx.globalCompositeOperation = 'source-atop'; + ctx.fillStyle = color.toString(); + ctx.fillRect(0, 0, out.width, out.height); + + return out; +}; + +BlockMorph.prototype.highlightImageBlurred = function (color, blur) { + var fb, img, hi, ctx; + fb = this.fullBounds().extent(); + img = this.fullImage(); + + hi = newCanvas(fb.add(blur * 2)); + ctx = hi.getContext('2d'); + ctx.shadowBlur = blur; + ctx.shadowColor = color.toString(); + ctx.drawImage(img, blur, blur); + + ctx.shadowBlur = 0; + ctx.globalCompositeOperation = 'destination-out'; + ctx.drawImage(img, blur, blur); + return hi; +}; + +BlockMorph.prototype.getHighlight = function () { + var highlights; + highlights = this.children.slice(0).reverse().filter( + function (child) { + return child instanceof BlockHighlightMorph; + } + ); + if (highlights.length !== 0) { + return highlights[0]; + } + return null; +}; + +// BlockMorph zebra coloring + +BlockMorph.prototype.fixBlockColor = function (nearestBlock, isForced) { + var nearest = nearestBlock, + clr, + cslot; + + if (!this.zebraContrast && !isForced) { + return; + } + if (!this.zebraContrast && isForced) { + return this.forceNormalColoring(); + } + + if (!nearest) { + if (this.parent) { + if (this.isPrototype) { + nearest = null; // this.parent; // the PrototypeHatBlockMorph + } else if (this instanceof ReporterBlockMorph) { + nearest = this.parent.parentThatIsA(BlockMorph); + } else { // command + cslot = this.parentThatIsA(CommandSlotMorph); + if (cslot) { + nearest = cslot.parentThatIsA(BlockMorph); + } + } + } + } + if (!nearest) { // top block + clr = SpriteMorph.prototype.blockColor[this.category]; + if (!this.color.eq(clr)) { + this.alternateBlockColor(); + } + } else if (nearest.category === this.category) { + if (nearest.color.eq(this.color)) { + this.alternateBlockColor(); + } + } else if (this.category && !this.color.eq( + SpriteMorph.prototype.blockColor[this.category] + )) { + this.alternateBlockColor(); + } + if (isForced) { + this.fixChildrensBlockColor(true); + } +}; + +BlockMorph.prototype.forceNormalColoring = function () { + var clr = SpriteMorph.prototype.blockColor[this.category]; + this.setColor(clr); + this.setLabelColor( + new Color(255, 255, 255), + clr.darker(this.labelContrast), + new Point(-1, -1) + ); + this.fixChildrensBlockColor(true); +}; + +BlockMorph.prototype.alternateBlockColor = function () { + var clr = SpriteMorph.prototype.blockColor[this.category]; + + if (this.color.eq(clr)) { + this.setColor( + this.zebraContrast < 0 ? clr.darker(Math.abs(this.zebraContrast)) + : clr.lighter(this.zebraContrast) + ); + } else { + this.setColor(clr); + } + this.fixLabelColor(); + this.fixChildrensBlockColor(true); // has issues if not forced +}; + +BlockMorph.prototype.fixLabelColor = function () { + if (this.zebraContrast > 0 && this.category) { + var clr = SpriteMorph.prototype.blockColor[this.category]; + if (this.color.eq(clr)) { + this.setLabelColor( + new Color(255, 255, 255), + clr.darker(this.labelContrast), + MorphicPreferences.isFlat ? null : new Point(-1, -1) + ); + } else { + this.setLabelColor( + new Color(0, 0, 0), + clr.lighter(this.zebraContrast) + .lighter(this.labelContrast * 2), + MorphicPreferences.isFlat ? null : new Point(1, 1) + ); + } + } +}; + +BlockMorph.prototype.fixChildrensBlockColor = function (isForced) { + var myself = this; + this.children.forEach(function (morph) { + if (morph instanceof CommandBlockMorph) { + morph.fixBlockColor(null, isForced); + } else if (morph instanceof SyntaxElementMorph) { + morph.fixBlockColor(myself, isForced); + } + }); +}; + +BlockMorph.prototype.setCategory = function (aString) { + this.category = aString; + this.startLayout(); + this.fixBlockColor(); + this.endLayout(); +}; + +// BlockMorph copying + +BlockMorph.prototype.fullCopy = function () { + var ans = BlockMorph.uber.fullCopy.call(this); + ans.removeHighlight(); + ans.isDraggable = true; + if (this.instantiationSpec) { + ans.setSpec(this.instantiationSpec); + } + ans.allChildren().filter(function (block) { + return !isNil(block.comment); + }).forEach(function (block) { + var cmnt = block.comment.fullCopy(); + block.comment = cmnt; + cmnt.block = block; + //block.comment = null; + + }); + return ans; +}; + +// BlockMorph events + +BlockMorph.prototype.mouseClickLeft = function () { + var top = this.topBlock(), + receiver = top.receiver(), + stage; + if (top instanceof PrototypeHatBlockMorph) { + return top.mouseClickLeft(); + } + if (receiver) { + stage = receiver.parentThatIsA(StageMorph); + if (stage) { + stage.threads.toggleProcess(top); + } + } +}; + +// BlockMorph thumbnail + +BlockMorph.prototype.thumbnail = function (scale, clipWidth, noShadow) { + var block = this.fullCopy(), + nb = block.nextBlock(), + fadeout = 12, + ext, + trgt, + ctx, + gradient; + if (nb) {nb.destroy(); } + if (!noShadow) {block.addShadow(); } + ext = block.fullBounds().extent(); + if (!noShadow) { + ext = ext.subtract(this.shadowBlur * + (useBlurredShadows && !MorphicPreferences.isFlat ? 1 : 2)); + } + trgt = newCanvas(new Point( + Math.min(ext.x * scale, clipWidth || ext.x), + ext.y * scale + )); + ctx = trgt.getContext('2d'); + ctx.scale(scale, scale); + ctx.drawImage(block.fullImage(), 0, 0); + // draw fade-out + if (trgt.width === clipWidth) { + gradient = ctx.createLinearGradient( + trgt.width / scale - fadeout, + 0, + trgt.width / scale, + 0 + ); + gradient.addColorStop(0, new Color(255, 255, 255, 0).toString()); + gradient.addColorStop(1, 'white'); + ctx.fillStyle = gradient; + ctx.fillRect( + trgt.width / scale - fadeout, + 0, + trgt.width / scale, + trgt.height / scale + ); + } + return trgt; +}; + +// BlockMorph dragging and dropping + +BlockMorph.prototype.rootForGrab = function () { + return this; +}; + +/* + for demo purposes, allows you to drop arg morphs onto + blocks and forces a layout update. This section has + no relevance in end user mode. +*/ + +BlockMorph.prototype.wantsDropOf = function (aMorph) { + // override the inherited method + return (aMorph instanceof ArgMorph + || aMorph instanceof StringMorph + || aMorph instanceof TextMorph + ) && !this.isTemplate; +}; + +BlockMorph.prototype.reactToDropOf = function (droppedMorph) { + droppedMorph.isDraggable = false; + if (droppedMorph instanceof InputSlotMorph) { + droppedMorph.drawNew(); + } else if (droppedMorph instanceof MultiArgMorph) { + droppedMorph.fixLayout(); + } + this.fixLayout(); + this.buildSpec(); +}; + +BlockMorph.prototype.situation = function () { + // answer a dictionary specifying where I am right now, so + // I can slide back to it if I'm dropped somewhere else + var scripts = this.parentThatIsA(ScriptsMorph); + if (scripts) { + return { + origin: scripts, + position: this.position().subtract(scripts.position()) + }; + } + return BlockMorph.uber.situation.call(this); +}; + +// BlockMorph sticky comments + +BlockMorph.prototype.prepareToBeGrabbed = function () { + var myself = this; + this.allComments().forEach(function (comment) { + comment.startFollowing(myself); + }); +}; + +BlockMorph.prototype.justDropped = function () { + this.allComments().forEach(function (comment) { + comment.stopFollowing(); + }); +}; + +BlockMorph.prototype.allComments = function () { + return this.allChildren().filter(function (block) { + return !isNil(block.comment); + }).map(function (block) { + return block.comment; + }); +}; + +BlockMorph.prototype.destroy = function () { + this.allComments().forEach(function (comment) { + comment.destroy(); + }); + BlockMorph.uber.destroy.call(this); +}; + +BlockMorph.prototype.stackHeight = function () { + var fb = this.fullBounds(), + commentsBottom = Math.max(this.allComments().map( + function (comment) {return comment.bottom(); } + )) || this.bottom(); + return Math.max(fb.bottom(), commentsBottom) - fb.top(); +}; + +BlockMorph.prototype.snap = function () { + var top = this.topBlock(); + top.allComments().forEach(function (comment) { + comment.align(top); + }); + // fix highlights, if any + if (this.getHighlight() && (this !== top)) { + this.removeHighlight(); + } + if (top.getHighlight()) { + top.addHighlight(top.removeHighlight()); + } +}; + +// CommandBlockMorph /////////////////////////////////////////////////// + +/* + I am a stackable jigsaw-shaped block. + + I inherit from BlockMorph adding the following most important + public accessors: + + nextBlock() - set / get the block attached to my bottom + bottomBlock() - answer the bottom block of my stack + blockSequence() - answer an array of blocks starting with myself +*/ + +// CommandBlockMorph inherits from BlockMorph: + +CommandBlockMorph.prototype = new BlockMorph(); +CommandBlockMorph.prototype.constructor = CommandBlockMorph; +CommandBlockMorph.uber = BlockMorph.prototype; + +// CommandBlockMorph instance creation: + +function CommandBlockMorph() { + this.init(); +} + +CommandBlockMorph.prototype.init = function () { + CommandBlockMorph.uber.init.call(this); + this.setExtent(new Point(200, 100)); +}; + +// CommandBlockMorph enumerating: + +CommandBlockMorph.prototype.blockSequence = function () { + var nb = this.nextBlock(), + result = [this]; + if (nb) { + result = result.concat(nb.blockSequence()); + } + return result; +}; + +CommandBlockMorph.prototype.bottomBlock = function () { + // topBlock() also exists - inherited from SyntaxElementMorph + if (this.nextBlock()) { + return this.nextBlock().bottomBlock(); + } + return this; +}; + +CommandBlockMorph.prototype.nextBlock = function (block) { + // set / get the block attached to my bottom + if (block) { + var nb = this.nextBlock(), + affected = this.parentThatIsA(CommandSlotMorph); + this.add(block); + if (nb) { + block.bottomBlock().nextBlock(nb); + } + this.fixLayout(); + if (affected) { + affected.fixLayout(); + } + } else { + return detect( + this.children, + function (child) { + return child instanceof CommandBlockMorph + && !child.isPrototype; + } + ); + } +}; + +// CommandBlockMorph attach targets: + +CommandBlockMorph.prototype.topAttachPoint = function () { + return new Point( + this.dentCenter(), + this.top() + ); +}; + +CommandBlockMorph.prototype.bottomAttachPoint = function () { + return new Point( + this.dentCenter(), + this.bottom() + ); +}; + +CommandBlockMorph.prototype.dentLeft = function () { + return this.left() + + this.corner + + this.inset; +}; + +CommandBlockMorph.prototype.dentCenter = function () { + return this.dentLeft() + + this.corner + + (this.dent * 0.5); +}; + +CommandBlockMorph.prototype.attachTargets = function () { + var answer = []; + if (!(this instanceof HatBlockMorph)) { + if (!(this.parent instanceof SyntaxElementMorph)) { + answer.push({ + point: this.topAttachPoint(), + element: this, + loc: 'top', + type: 'block' + }); + } + } + if (!this.isStop()) { + answer.push({ + point: this.bottomAttachPoint(), + element: this, + loc: 'bottom', + type: 'block' + }); + } + return answer; +}; + +CommandBlockMorph.prototype.allAttachTargets = function (newParent) { + var myself = this, + target = newParent || this.parent, + answer = [], + topBlocks; + + topBlocks = target.children.filter(function (child) { + return (child !== myself) && + child instanceof SyntaxElementMorph && + !child.isTemplate; + }); + topBlocks.forEach(function (block) { + block.forAllChildren(function (child) { + if (child.attachTargets) { + child.attachTargets().forEach(function (at) { + answer.push(at); + }); + } + }); + }); + return answer; +}; + +CommandBlockMorph.prototype.closestAttachTarget = function (newParent) { + var target = newParent || this.parent, + bottomBlock = this.bottomBlock(), + answer = null, + thresh = Math.max( + this.corner * 2 + this.dent, + this.minSnapDistance + ), + dist, + ref = [], + minDist = 1000; + + if (!(this instanceof HatBlockMorph)) { + ref.push( + { + point: this.topAttachPoint(), + loc: 'top' + } + ); + } + if (!this.isStop()) { + ref.push( + { + point: bottomBlock.bottomAttachPoint(), + loc: 'bottom' + } + ); + } + + this.allAttachTargets(target).forEach(function (eachTarget) { + ref.forEach(function (eachRef) { + if (eachRef.loc !== eachTarget.loc) { + dist = eachRef.point.distanceTo(eachTarget.point); + if ((dist < thresh) && (dist < minDist)) { + minDist = dist; + answer = eachTarget; + } + } + }); + }); + return answer; +}; + +CommandBlockMorph.prototype.snap = function () { + var target = this.closestAttachTarget(), + scripts = this.parentThatIsA(ScriptsMorph), + next, + offsetY, + affected; + + scripts.clearDropHistory(); + scripts.lastDroppedBlock = this; + + if (target === null) { + this.startLayout(); + this.fixBlockColor(); + this.endLayout(); + CommandBlockMorph.uber.snap.call(this); // align stuck comments + return; + } + + scripts.lastDropTarget = target; + + this.startLayout(); + if (target.loc === 'bottom') { + if (target.type === 'slot') { + this.removeHighlight(); + scripts.lastNextBlock = target.element.nestedBlock(); + target.element.nestedBlock(this); + } else { + scripts.lastNextBlock = target.element.nextBlock(); + target.element.nextBlock(this); + } + if (this.isStop()) { + next = this.nextBlock(); + if (next) { + scripts.add(next); + next.moveBy(this.extent().floorDivideBy(2)); + affected = this.parentThatIsA(CommandSlotMorph); + if (affected) { + affected.fixLayout(); + } + } + } + } else if (target.loc === 'top') { + target.element.removeHighlight(); + offsetY = this.bottomBlock().bottom() - this.bottom(); + this.setBottom(target.element.top() + this.corner - offsetY); + this.setLeft(target.element.left()); + this.bottomBlock().nextBlock(target.element); + } + this.fixBlockColor(); + this.endLayout(); + CommandBlockMorph.uber.snap.call(this); // align stuck comments + if (this.snapSound) { + this.snapSound.play(); + } +}; + +CommandBlockMorph.prototype.isStop = function () { + return ([ + 'doStop', + 'doStopBlock', + 'doStopAll', + 'doForever', + 'doReport', + 'removeClone' + ].indexOf(this.selector) > -1); +}; + +// CommandBlockMorph deleting + +CommandBlockMorph.prototype.userDestroy = function () { + var cslot = this.parentThatIsA(CSlotMorph); + this.destroy(); + if (cslot) { + cslot.fixLayout(); + } +}; + +// CommandBlockMorph drawing: + +CommandBlockMorph.prototype.drawNew = function () { + var context; + this.cachedClr = this.color.toString(); + this.cachedClrBright = this.bright(); + this.cachedClrDark = this.dark(); + this.image = newCanvas(this.extent()); + context = this.image.getContext('2d'); + context.fillStyle = this.cachedClr; + + // draw the 'flat' shape: + this.drawTop(context); + this.drawBody(context); + this.drawBottom(context); + + // add 3D-Effect: + if (!MorphicPreferences.isFlat) { + this.drawTopDentEdge(context, 0, 0); + this.drawBottomDentEdge(context, 0, this.height() - this.corner); + this.drawLeftEdge(context); + this.drawRightEdge(context); + this.drawTopLeftEdge(context); + this.drawBottomRightEdge(context); + } else { + nop(); + /* + this.drawFlatBottomDentEdge( + context, 0, this.height() - this.corner + ); + */ + } + + // erase CommandSlots + this.eraseHoles(context); +}; + +CommandBlockMorph.prototype.drawBody = function (context) { + context.fillRect( + 0, + Math.floor(this.corner), + this.width(), + this.height() - Math.floor(this.corner * 3) + 1 + ); +}; + +CommandBlockMorph.prototype.drawTop = function (context) { + context.beginPath(); + + // top left: + context.arc( + this.corner, + this.corner, + this.corner, + radians(-180), + radians(-90), + false + ); + + // dent: + this.drawDent(context, 0, 0); + + // top right: + context.arc( + this.width() - this.corner, + this.corner, + this.corner, + radians(-90), + radians(-0), + false + ); + + context.closePath(); + context.fill(); +}; + +CommandBlockMorph.prototype.drawBottom = function (context) { + var y = this.height() - (this.corner * 2); + + context.beginPath(); + + // bottom left: + context.arc( + this.corner, + y, + this.corner, + radians(180), + radians(90), + true + ); + + if (!this.isStop()) { + this.drawDent(context, 0, this.height() - this.corner); + } + + // bottom right: + context.arc( + this.width() - this.corner, + y, + this.corner, + radians(90), + radians(0), + true + ); + + context.closePath(); + context.fill(); +}; + +CommandBlockMorph.prototype.drawDent = function (context, x, y) { + var indent = x + this.corner * 2 + this.inset; + + context.lineTo(x + this.corner + this.inset, y); + context.lineTo(indent, y + this.corner); + context.lineTo(indent + this.dent, y + this.corner); + context.lineTo(x + this.corner * 3 + this.inset + this.dent, y); + context.lineTo(this.width() - this.corner, y); +}; + +CommandBlockMorph.prototype.drawTopDentEdge = function (context, x, y) { + var shift = this.edge * 0.5, + indent = x + this.corner * 2 + this.inset, + upperGradient, + lowerGradient, + leftGradient, + lgx; + + context.lineWidth = this.edge; + context.lineJoin = 'round'; + context.lineCap = 'round'; + + upperGradient = context.createLinearGradient( + 0, + y, + 0, + y + this.edge + ); + upperGradient.addColorStop(0, this.cachedClrBright); + upperGradient.addColorStop(1, this.cachedClr); + + context.strokeStyle = upperGradient; + context.beginPath(); + context.moveTo(this.corner, y + shift); + context.lineTo(x + this.corner + this.inset, y + shift); + context.stroke(); + + context.strokeStyle = upperGradient; + context.beginPath(); + context.moveTo( + x + this.corner * 3 + this.inset + this.dent + shift, + y + shift + ); + context.lineTo(this.width() - this.corner, y + shift); + context.stroke(); + + lgx = x + this.corner + this.inset; + leftGradient = context.createLinearGradient( + lgx - this.edge, + y + this.edge, + lgx, + y + ); + leftGradient.addColorStop(0, this.cachedClr); + leftGradient.addColorStop(1, this.cachedClrBright); + + context.strokeStyle = leftGradient; + context.beginPath(); + context.moveTo(x + this.corner + this.inset, y + shift); + context.lineTo(indent, y + this.corner + shift); + context.stroke(); + + lowerGradient = context.createLinearGradient( + 0, + y + this.corner, + 0, + y + this.corner + this.edge + ); + lowerGradient.addColorStop(0, this.cachedClrBright); + lowerGradient.addColorStop(1, this.cachedClr); + + context.strokeStyle = lowerGradient; + context.beginPath(); + context.moveTo(indent, y + this.corner + shift); + context.lineTo(indent + this.dent, y + this.corner + shift); + context.stroke(); +}; + +CommandBlockMorph.prototype.drawBottomDentEdge = function (context, x, y) { + var shift = this.edge * 0.5, + indent = x + this.corner * 2 + this.inset, + upperGradient, + lowerGradient, + rightGradient; + + context.lineWidth = this.edge; + context.lineJoin = 'round'; + context.lineCap = 'round'; + + upperGradient = context.createLinearGradient( + 0, + y - this.edge, + 0, + y + ); + upperGradient.addColorStop(0, this.cachedClr); + upperGradient.addColorStop(1, this.cachedClrDark); + + context.strokeStyle = upperGradient; + context.beginPath(); + context.moveTo(this.corner, y - shift); + if (this.isStop()) { + context.lineTo(this.width() - this.corner, y - shift); + } else { + context.lineTo(x + this.corner + this.inset - shift, y - shift); + } + context.stroke(); + + if (this.isStop()) { // draw straight bottom edge + return null; + } + + lowerGradient = context.createLinearGradient( + 0, + y + this.corner - this.edge, + 0, + y + this.corner + ); + lowerGradient.addColorStop(0, this.cachedClr); + lowerGradient.addColorStop(1, this.cachedClrDark); + + context.strokeStyle = lowerGradient; + context.beginPath(); + context.moveTo(indent + shift, y + this.corner - shift); + context.lineTo(indent + this.dent, y + this.corner - shift); + context.stroke(); + + rightGradient = context.createLinearGradient( + x + indent + this.dent - this.edge, + y + this.corner - this.edge, + x + indent + this.dent, + y + this.corner + ); + rightGradient.addColorStop(0, this.cachedClr); + rightGradient.addColorStop(1, this.cachedClrDark); + + context.strokeStyle = rightGradient; + context.beginPath(); + context.moveTo(x + indent + this.dent, y + this.corner - shift); + context.lineTo( + x + this.corner * 3 + this.inset + this.dent, + y - shift + ); + context.stroke(); + + context.strokeStyle = upperGradient; + context.beginPath(); + context.moveTo( + x + this.corner * 3 + this.inset + this.dent, + y - shift + ); + context.lineTo(this.width() - this.corner, y - shift); + context.stroke(); +}; + +CommandBlockMorph.prototype.drawFlatBottomDentEdge = function (context) { + if (!this.isStop()) { + context.fillStyle = this.color.darker(this.contrast / 2).toString(); + context.beginPath(); + this.drawDent(context, 0, this.height() - this.corner); + context.closePath(); + context.fill(); + } +}; + +CommandBlockMorph.prototype.drawLeftEdge = function (context) { + var shift = this.edge * 0.5, + gradient = context.createLinearGradient(0, 0, this.edge, 0); + + gradient.addColorStop(0, this.cachedClrBright); + gradient.addColorStop(1, this.cachedClr); + + context.lineWidth = this.edge; + context.lineJoin = 'round'; + context.lineCap = 'round'; + + context.strokeStyle = gradient; + context.beginPath(); + context.moveTo(shift, this.corner); + context.lineTo(shift, this.height() - this.corner * 2 - shift); + context.stroke(); +}; + +CommandBlockMorph.prototype.drawRightEdge = function (context) { + var shift = this.edge * 0.5, + x = this.width(), + gradient; + + gradient = context.createLinearGradient(x - this.edge, 0, x, 0); + gradient.addColorStop(0, this.cachedClr); + gradient.addColorStop(1, this.cachedClrDark); + + context.lineWidth = this.edge; + context.lineJoin = 'round'; + context.lineCap = 'round'; + + context.strokeStyle = gradient; + context.beginPath(); + context.moveTo(x - shift, this.corner + shift); + context.lineTo(x - shift, this.height() - this.corner * 2); + context.stroke(); +}; + +CommandBlockMorph.prototype.drawTopLeftEdge = function (context) { + var shift = this.edge * 0.5, + gradient; + + gradient = context.createRadialGradient( + this.corner, + this.corner, + this.corner, + this.corner, + this.corner, + this.corner - this.edge + ); + gradient.addColorStop(0, this.cachedClrBright); + gradient.addColorStop(1, this.cachedClr); + + context.lineWidth = this.edge; + context.lineJoin = 'round'; + context.lineCap = 'round'; + + context.strokeStyle = gradient; + + context.beginPath(); + context.arc( + this.corner, + this.corner, + this.corner - shift, + radians(-180), + radians(-90), + false + ); + context.stroke(); +}; + +CommandBlockMorph.prototype.drawBottomRightEdge = function (context) { + var shift = this.edge * 0.5, + x = this.width() - this.corner, + y = this.height() - this.corner * 2, + gradient; + + gradient = context.createRadialGradient( + x, + y, + this.corner, + x, + y, + this.corner - this.edge + ); + gradient.addColorStop(0, this.cachedClrDark); + gradient.addColorStop(1, this.cachedClr); + + context.lineWidth = this.edge; + context.lineJoin = 'round'; + context.lineCap = 'round'; + + context.strokeStyle = gradient; + + context.beginPath(); + context.arc( + x, + y, + this.corner - shift, + radians(90), + radians(0), + true + ); + context.stroke(); +}; + +// HatBlockMorph /////////////////////////////////////////////////////// + +/* + I am a script's top most block. I can attach command blocks at my + bottom, but not on top. + +*/ + +// HatBlockMorph inherits from CommandBlockMorph: + +HatBlockMorph.prototype = new CommandBlockMorph(); +HatBlockMorph.prototype.constructor = HatBlockMorph; +HatBlockMorph.uber = CommandBlockMorph.prototype; + +// HatBlockMorph instance creation: + +function HatBlockMorph() { + this.init(); +} + +HatBlockMorph.prototype.init = function () { + HatBlockMorph.uber.init.call(this); + this.setExtent(new Point(300, 150)); +}; + +// HatBlockMorph enumerating: + +HatBlockMorph.prototype.blockSequence = function () { + // override my inherited method so that I am not part of my sequence + var result = HatBlockMorph.uber.blockSequence.call(this); + result.shift(); + return result; +}; + +// HatBlockMorph drawing: + +HatBlockMorph.prototype.drawTop = function (context) { + var s = this.hatWidth, + h = this.hatHeight, + r = ((4 * h * h) + (s * s)) / (8 * h), + a = degrees(4 * Math.atan(2 * h / s)), + sa = a / 2, + sp = Math.min(s * 1.7, this.width() - this.corner); + + context.beginPath(); + + context.moveTo(0, h + this.corner); + + // top arc: + context.arc( + s / 2, + r, + r, + radians(-sa - 90), + radians(-90), + false + ); + context.bezierCurveTo( + s, + 0, + s, + h, + sp, + h + ); + + // top right: + context.arc( + this.width() - this.corner, + h + this.corner, + this.corner, + radians(-90), + radians(-0), + false + ); + + context.closePath(); + context.fill(); +}; + +HatBlockMorph.prototype.drawBody = function (context) { + context.fillRect( + 0, + this.hatHeight + Math.floor(this.corner) - 1, + this.width(), + this.height() - Math.floor(this.corner * 3) - this.hatHeight + 2 + ); +}; + +HatBlockMorph.prototype.drawLeftEdge = function (context) { + var shift = this.edge * 0.5, + gradient = context.createLinearGradient(0, 0, this.edge, 0); + + gradient.addColorStop(0, this.cachedClrBright); + gradient.addColorStop(1, this.cachedClr); + + context.lineWidth = this.edge; + context.lineJoin = 'round'; + context.lineCap = 'round'; + + context.strokeStyle = gradient; + context.beginPath(); + context.moveTo(shift, this.hatHeight + shift); + context.lineTo(shift, this.height() - this.corner * 2 - shift); + context.stroke(); +}; + +HatBlockMorph.prototype.drawRightEdge = function (context) { + var shift = this.edge * 0.5, + x = this.width(), + gradient; + + gradient = context.createLinearGradient(x - this.edge, 0, x, 0); + gradient.addColorStop(0, this.cachedClr); + gradient.addColorStop(1, this.cachedClrDark); + + context.lineWidth = this.edge; + context.lineJoin = 'round'; + context.lineCap = 'round'; + + context.strokeStyle = gradient; + context.beginPath(); + context.moveTo(x - shift, this.corner + this.hatHeight + shift); + context.lineTo(x - shift, this.height() - this.corner * 2); + context.stroke(); +}; + +HatBlockMorph.prototype.drawTopDentEdge = function () { + return null; +}; + +HatBlockMorph.prototype.drawTopLeftEdge = function (context) { + var shift = this.edge * 0.5, + s = this.hatWidth, + h = this.hatHeight, + r = ((4 * h * h) + (s * s)) / (8 * h), + a = degrees(4 * Math.atan(2 * h / s)), + sa = a / 2, + sp = Math.min(s * 1.7, this.width() - this.corner), + gradient; + + gradient = context.createRadialGradient( + s / 2, + r, + r - this.edge, + s / 2, + r, + r + ); + gradient.addColorStop(1, this.cachedClrBright); + gradient.addColorStop(0, this.cachedClr); + + context.lineWidth = this.edge; + context.lineJoin = 'round'; + context.lineCap = 'round'; + + context.strokeStyle = gradient; + context.beginPath(); + context.arc( + Math.round(s / 2), + r, + r - shift, + radians(-sa - 90), + radians(-90), + false + ); + context.moveTo(s / 2, shift); + context.bezierCurveTo( + s, + shift, + s, + h + shift, + sp, + h + shift + ); + context.lineTo(this.width() - this.corner, h + shift); + context.stroke(); +}; + +// ReporterBlockMorph ////////////////////////////////////////////////// + +/* + I am a block with a return value, either round-ish or diamond shaped + I inherit all my important accessors from BlockMorph +*/ + +// ReporterBlockMorph inherits from BlockMorph: + +ReporterBlockMorph.prototype = new BlockMorph(); +ReporterBlockMorph.prototype.constructor = ReporterBlockMorph; +ReporterBlockMorph.uber = BlockMorph.prototype; + +// ReporterBlockMorph instance creation: + +function ReporterBlockMorph(isPredicate) { + this.init(isPredicate); +} + +ReporterBlockMorph.prototype.init = function (isPredicate) { + ReporterBlockMorph.uber.init.call(this); + this.isPredicate = isPredicate || false; + this.setExtent(new Point(200, 80)); +}; + +// ReporterBlockMorph drag & drop: + +ReporterBlockMorph.prototype.snap = function (hand) { + // passing the hand is optional (for when blocks are dragged & dropped) + var scripts = this.parent, + target; + + if (!scripts instanceof ScriptsMorph) { + return null; + } + + scripts.clearDropHistory(); + scripts.lastDroppedBlock = this; + + target = scripts.closestInput(this, hand); + if (target !== null) { + scripts.lastReplacedInput = target; + scripts.lastDropTarget = target.parent; + if (target instanceof MultiArgMorph) { + scripts.lastPreservedBlocks = target.inputs(); + scripts.lastReplacedInput = target.fullCopy(); + } + target.parent.replaceInput(target, this); + if (this.snapSound) { + this.snapSound.play(); + } + } + this.startLayout(); + this.fixBlockColor(); + this.endLayout(); + ReporterBlockMorph.uber.snap.call(this); +}; + +ReporterBlockMorph.prototype.prepareToBeGrabbed = function (handMorph) { + var oldPos = this.position(); + + nop(handMorph); + if ((this.parent instanceof BlockMorph) + || (this.parent instanceof MultiArgMorph) + || (this.parent instanceof ReporterSlotMorph)) { + this.parent.revertToDefaultInput(this); + this.setPosition(oldPos); + } + ReporterBlockMorph.uber.prepareToBeGrabbed.call(this, handMorph); +}; + +// ReporterBlockMorph enumerating + +ReporterBlockMorph.prototype.blockSequence = function () { + // reporters don't have a sequence, answer myself + return this; +}; + +// ReporterBlockMorph evaluating + +ReporterBlockMorph.prototype.isUnevaluated = function () { +/* + answer whether my parent block's slot is designated to be of an + 'unevaluated' kind, denoting a spedial form +*/ + return contains(['%anyUE', '%boolUE', '%f'], this.getSlotSpec()); +}; + +ReporterBlockMorph.prototype.isLocked = function () { + // answer true if I can be exchanged by a dropped reporter + return this.isStatic || (this.getSlotSpec() === '%t'); +}; + +ReporterBlockMorph.prototype.getSlotSpec = function () { + // answer the spec of the slot I'm in, if any + var parts, idx; + if (this.parent instanceof BlockMorph) { + parts = this.parent.parts().filter( + function (part) { + return !(part instanceof BlockHighlightMorph); + } + ); + idx = parts.indexOf(this); + if (idx !== -1) { + if (this.parent.blockSpec) { + return this.parseSpec(this.parent.blockSpec)[idx]; + } + } + } + if (this.parent instanceof MultiArgMorph) { + return this.parent.slotSpec; + } + if (this.parent instanceof TemplateSlotMorph) { + return this.parent.getSpec(); + } + return null; +}; + +// ReporterBlockMorph events + +ReporterBlockMorph.prototype.mouseClickLeft = function (pos) { + var isRing; + if (this.parent instanceof BlockInputFragmentMorph) { + return this.parent.mouseClickLeft(); + } + if (this.parent instanceof TemplateSlotMorph) { + isRing = this.parent.parent && this.parent.parent.parent && + this.parent.parent.parent instanceof RingMorph; + new DialogBoxMorph( + this, + this.setSpec, + this + ).prompt( + isRing ? "Input name" : "Script variable name", + this.blockSpec, + this.world() + ); + } else { + ReporterBlockMorph.uber.mouseClickLeft.call(this, pos); + } +}; + +// ReporterBlockMorph deleting + +ReporterBlockMorph.prototype.userDestroy = function () { + this.prepareToBeGrabbed(); // restore default slot of parent block + this.destroy(); +}; + +// ReporterBlockMorph drawing: + +ReporterBlockMorph.prototype.drawNew = function () { + var context; + this.cachedClr = this.color.toString(); + this.cachedClrBright = this.bright(); + this.cachedClrDark = this.dark(); + this.image = newCanvas(this.extent()); + context = this.image.getContext('2d'); + context.fillStyle = this.cachedClr; + + if (this.isPredicate) { + this.drawDiamond(context); + } else { + this.drawRounded(context); + } + + // erase CommandSlots + this.eraseHoles(context); +}; + +ReporterBlockMorph.prototype.drawRounded = function (context) { + var h = this.height(), + r = Math.min(this.rounding, h / 2), + w = this.width(), + shift = this.edge / 2, + gradient; + + // draw the 'flat' shape: + context.fillStyle = this.cachedClr; + context.beginPath(); + + // top left: + context.arc( + r, + r, + r, + radians(-180), + radians(-90), + false + ); + + // top right: + context.arc( + w - r, + r, + r, + radians(-90), + radians(-0), + false + ); + + // bottom right: + context.arc( + w - r, + h - r, + r, + radians(0), + radians(90), + false + ); + + // bottom left: + context.arc( + r, + h - r, + r, + radians(90), + radians(180), + false + ); + + context.closePath(); + context.fill(); + + if (MorphicPreferences.isFlat) {return; } + + // add 3D-Effect: + context.lineWidth = this.edge; + context.lineJoin = 'round'; + context.lineCap = 'round'; + + // half-tone edges + // bottem left corner + gradient = context.createRadialGradient( + r, + h - r, + r - this.edge, + r, + h - r, + r + this.edge + ); + gradient.addColorStop(0, this.cachedClr); + gradient.addColorStop(1, this.cachedClrBright); + context.strokeStyle = gradient; + context.beginPath(); + context.arc( + r, + h - r, + r - shift, + radians(90), + radians(180), + false + ); + context.stroke(); + + // top right corner + gradient = context.createRadialGradient( + w - r, + r, + r - this.edge, + w - r, + r, + r + this.edge + ); + gradient.addColorStop(0, this.cachedClr); + gradient.addColorStop(1, this.cachedClrDark); + context.strokeStyle = gradient; + context.beginPath(); + context.arc( + w - r, + r, + r - shift, + radians(-90), + radians(0), + false + ); + context.stroke(); + + // normal gradient edges + + // top edge: straight line + gradient = context.createLinearGradient( + 0, + 0, + 0, + this.edge + ); + gradient.addColorStop(0, this.cachedClrBright); + gradient.addColorStop(1, this.cachedClr); + context.strokeStyle = gradient; + context.beginPath(); + context.moveTo(r - shift, shift); + context.lineTo(w - r + shift, shift); + context.stroke(); + + // top edge: left corner + gradient = context.createRadialGradient( + r, + r, + r - this.edge, + r, + r, + r + ); + gradient.addColorStop(0, this.cachedClr); + gradient.addColorStop(1, this.cachedClrBright); + context.strokeStyle = gradient; + context.beginPath(); + context.arc( + r, + r, + r - shift, + radians(180), + radians(270), + false + ); + context.stroke(); + + // bottom edge: right corner + gradient = context.createRadialGradient( + w - r, + h - r, + r - this.edge, + w - r, + h - r, + r + ); + gradient.addColorStop(0, this.cachedClr); + gradient.addColorStop(1, this.cachedClrDark); + context.strokeStyle = gradient; + context.beginPath(); + context.arc( + w - r, + h - r, + r - shift, + radians(0), + radians(90), + false + ); + context.stroke(); + + // bottom edge: straight line + gradient = context.createLinearGradient( + 0, + h - this.edge, + 0, + h + ); + gradient.addColorStop(0, this.cachedClr); + gradient.addColorStop(1, this.cachedClrDark); + context.strokeStyle = gradient; + context.beginPath(); + context.moveTo(r - shift, h - shift); + context.lineTo(w - r + shift, h - shift); + context.stroke(); + + // left edge: straight vertical line + gradient = context.createLinearGradient(0, 0, this.edge, 0); + gradient.addColorStop(0, this.cachedClrBright); + gradient.addColorStop(1, this.cachedClr); + context.strokeStyle = gradient; + context.beginPath(); + context.moveTo(shift, r); + context.lineTo(shift, h - r); + context.stroke(); + + // right edge: straight vertical line + gradient = context.createLinearGradient(w - this.edge, 0, w, 0); + gradient.addColorStop(0, this.cachedClr); + gradient.addColorStop(1, this.cachedClrDark); + context.strokeStyle = gradient; + context.beginPath(); + context.moveTo(w - shift, r + shift); + context.lineTo(w - shift, h - r); + context.stroke(); + +}; + +ReporterBlockMorph.prototype.drawDiamond = function (context) { + var w = this.width(), + h = this.height(), + h2 = Math.floor(h / 2), + r = this.rounding, + shift = this.edge / 2, + gradient; + + // draw the 'flat' shape: + context.fillStyle = this.cachedClr; + context.beginPath(); + + context.moveTo(0, h2); + context.lineTo(r, 0); + context.lineTo(w - r, 0); + context.lineTo(w, h2); + context.lineTo(w - r, h); + context.lineTo(r, h); + + context.closePath(); + context.fill(); + + if (MorphicPreferences.isFlat) {return; } + + // add 3D-Effect: + context.lineWidth = this.edge; + context.lineJoin = 'round'; + context.lineCap = 'round'; + + // half-tone edges + // bottom left corner + gradient = context.createLinearGradient( + -r, + 0, + r, + 0 + ); + gradient.addColorStop(1, this.cachedClr); + gradient.addColorStop(0, this.cachedClrBright); + context.strokeStyle = gradient; + context.beginPath(); + context.moveTo(shift, h2); + context.lineTo(r, h - shift); + context.closePath(); + context.stroke(); + + // top right corner + gradient = context.createLinearGradient( + w - r, + 0, + w + r, + 0 + ); + gradient.addColorStop(0, this.cachedClr); + gradient.addColorStop(1, this.cachedClrDark); + context.strokeStyle = gradient; + context.beginPath(); + context.moveTo(w - shift, h2); + context.lineTo(w - r, shift); + context.closePath(); + context.stroke(); + + // normal gradient edges + // top edge: left corner + gradient = context.createLinearGradient( + 0, + 0, + r, + 0 + ); + gradient.addColorStop(0, this.cachedClrBright); + gradient.addColorStop(1, this.cachedClr); + context.strokeStyle = gradient; + context.beginPath(); + context.moveTo(shift, h2); + context.lineTo(r, shift); + context.closePath(); + context.stroke(); + + // top edge: straight line + gradient = context.createLinearGradient( + 0, + 0, + 0, + this.edge + ); + gradient.addColorStop(0, this.cachedClrBright); + gradient.addColorStop(1, this.cachedClr); + context.strokeStyle = gradient; + context.beginPath(); + context.moveTo(r, shift); + context.lineTo(w - r, shift); + context.closePath(); + context.stroke(); + + // bottom edge: right corner + gradient = context.createLinearGradient( + w - r, + 0, + w, + 0 + ); + gradient.addColorStop(0, this.cachedClr); + gradient.addColorStop(1, this.cachedClrDark); + context.strokeStyle = gradient; + context.beginPath(); + context.moveTo(w - r, h - shift); + context.lineTo(w - shift, h2); + context.closePath(); + context.stroke(); + + // bottom edge: straight line + gradient = context.createLinearGradient( + 0, + h - this.edge, + 0, + h + ); + gradient.addColorStop(0, this.cachedClr); + gradient.addColorStop(1, this.cachedClrDark); + context.strokeStyle = gradient; + context.beginPath(); + context.moveTo(r + shift, h - shift); + context.lineTo(w - r - shift, h - shift); + context.closePath(); + context.stroke(); +}; + +// RingMorph ///////////////////////////////////////////////////////////// + +/* + I am a reporter block which reifies its contents, my outer shape is + always roundish (never diamond) +*/ + +// RingMorph inherits from ReporterBlockMorph: + +RingMorph.prototype = new ReporterBlockMorph(); +RingMorph.prototype.constructor = RingMorph; +RingMorph.uber = ReporterBlockMorph.prototype; + +// RingMorph preferences settings: + +// RingMorph.prototype.edge = 2; +// RingMorph.prototype.rounding = 9; +// RingMorph.prototype.alpha = 0.8; +// RingMorph.prototype.contrast = 85; + +// RingMorph instance creation: + +function RingMorph() { + this.init(); +} + +RingMorph.prototype.init = function () { + RingMorph.uber.init.call(this); + this.category = 'other'; + this.alpha = RingMorph.prototype.alpha; + this.contrast = RingMorph.prototype.contrast; + this.setExtent(new Point(200, 80)); +}; + +// RingMorph dragging and dropping + +RingMorph.prototype.rootForGrab = function () { + if (this.isDraggable) { + return this; + } + return BlockMorph.uber.rootForGrab.call(this); +}; + +// RingMorph ops - Note: these assume certain layouts defined elsewhere - + +RingMorph.prototype.embed = function (aBlock, inputNames) { + var slot; + + // set my color + this.color = SpriteMorph.prototype.blockColor.other; + this.isDraggable = true; + + // set my type, selector, and nested block: + if (aBlock instanceof CommandBlockMorph) { + this.isStatic = false; + this.setSpec('%rc %ringparms'); + this.selector = 'reifyScript'; + slot = this.parts()[0]; + slot.nestedBlock(aBlock); + } else if (aBlock.isPredicate) { + this.isStatic = true; + this.setSpec('%rp %ringparms'); + this.selector = 'reifyPredicate'; + slot = this.parts()[0]; + slot.silentReplaceInput(slot.contents(), aBlock); + } else { // reporter + this.isStatic = false; + this.setSpec('%rr %ringparms'); + this.selector = 'reifyReporter'; + slot = this.parts()[0]; + slot.silentReplaceInput(slot.contents(), aBlock); + } + + // set my inputs, if any + slot = this.parts()[1]; + if (inputNames) { + inputNames.forEach(function (name) { + slot.addInput(name); + }); + } + + // ensure zebra coloring + this.fixBlockColor(null, true); +}; + +RingMorph.prototype.vanishForSimilar = function () { + // let me disappear if I am nesting a variable getter or Ring + // but only if I'm not already inside another ring + var slot = this.parts()[0], + block = slot.nestedBlock(); + + if (!block) {return null; } + if (!(this.parent instanceof SyntaxElementMorph)) {return null; } + if (this.parent instanceof RingReporterSlotMorph + || (this.parent instanceof RingCommandSlotMorph)) { + return null; + } + if (block.selector === 'reportGetVar' || (block instanceof RingMorph)) { + this.parent.silentReplaceInput(this, block); + } +}; + +RingMorph.prototype.contents = function () { + return this.parts()[0].nestedBlock(); +}; + +RingMorph.prototype.inputNames = function () { + return this.parts()[1].evaluate(); +}; + +RingMorph.prototype.dataType = function () { + switch (this.selector) { + case 'reifyScript': + return 'command'; + case 'reifyPredicate': + return 'predicate'; + default: + return 'reporter'; + } +}; + +// RingMorph zebra coloring + +RingMorph.prototype.fixBlockColor = function (nearest, isForced) { + var slot = this.parts()[0]; + RingMorph.uber.fixBlockColor.call(this, nearest, isForced); + if (slot instanceof RingCommandSlotMorph) { + slot.fixLayout(); + } +}; + +// ScriptsMorph //////////////////////////////////////////////////////// + +/* + I give feedback about possible drop targets and am in charge + of actually snapping blocks together. + + My children are the top blocks of scripts. + + I store a back-pointer to my owner, i.e. the object (sprite) + to whom my scripts apply. +*/ + +// ScriptsMorph inherits from FrameMorph: + +ScriptsMorph.prototype = new FrameMorph(); +ScriptsMorph.prototype.constructor = ScriptsMorph; +ScriptsMorph.uber = FrameMorph.prototype; + +// ScriptsMorph preference settings + +ScriptsMorph.prototype.cleanUpMargin = 20; +ScriptsMorph.prototype.cleanUpSpacing = 15; +ScriptsMorph.prototype.isPreferringEmptySlots = true; + +// ScriptsMorph instance creation: + +function ScriptsMorph(owner) { + this.init(owner); +} + +ScriptsMorph.prototype.init = function (owner) { + this.owner = owner || null; + this.feedbackColor = SyntaxElementMorph.prototype.feedbackColor; + this.feedbackMorph = new BoxMorph(); + + // "undrop" attributes: + this.lastDroppedBlock = null; + this.lastReplacedInput = null; + this.lastDropTarget = null; + this.lastPreservedBlocks = null; + this.lastNextBlock = null; + + ScriptsMorph.uber.init.call(this); + this.setColor(new Color(70, 70, 70)); +}; + +// ScriptsMorph deep copying: + +ScriptsMorph.prototype.fullCopy = function () { + var cpy = new ScriptsMorph(), + pos = this.position(), + child; + this.children.forEach(function (morph) { + if (!morph.block) { // omit anchored comments + child = morph.fullCopy(); + child.setPosition(morph.position().subtract(pos)); + cpy.add(child); + if (child instanceof BlockMorph) { + child.allComments().forEach(function (comment) { + comment.align(child); + }); + } + } + }); + cpy.adjustBounds(); + return cpy; +}; + +// ScriptsMorph stepping: + +ScriptsMorph.prototype.step = function () { + var hand = this.world().hand, + block; + + if (this.feedbackMorph.parent) { + this.feedbackMorph.destroy(); + this.feedbackMorph.parent = null; + } + if (hand.children.length === 0) { + return null; + } + if (!this.bounds.containsPoint(hand.bounds.origin)) { + return null; + } + block = hand.children[0]; + if (!(block instanceof BlockMorph) && !(block instanceof CommentMorph)) { + return null; + } + if (!contains(hand.morphAtPointer().allParents(), this)) { + return null; + } + if (block instanceof CommentMorph) { + this.showCommentDropFeedback(block, hand); + } else if (block instanceof ReporterBlockMorph) { + this.showReporterDropFeedback(block, hand); + } else { + this.showCommandDropFeedback(block); + } +}; + +ScriptsMorph.prototype.showReporterDropFeedback = function (block, hand) { + var target = this.closestInput(block, hand); + + if (target === null) { + return null; + } + this.feedbackMorph.bounds = target.fullBounds() + .expandBy(Math.max( + block.edge * 2, + block.reporterDropFeedbackPadding + )); + this.feedbackMorph.edge = SyntaxElementMorph.prototype.rounding; + this.feedbackMorph.border = Math.max( + SyntaxElementMorph.prototype.edge, + 3 + ); + this.add(this.feedbackMorph); + if (target instanceof MultiArgMorph) { + this.feedbackMorph.color = + SpriteMorph.prototype.blockColor.lists.copy(); + this.feedbackMorph.borderColor = + SpriteMorph.prototype.blockColor.lists; + } else { + this.feedbackMorph.color = this.feedbackColor.copy(); + this.feedbackMorph.borderColor = this.feedbackColor; + } + this.feedbackMorph.color.a = 0.5; + this.feedbackMorph.drawNew(); + this.feedbackMorph.changed(); +}; + +ScriptsMorph.prototype.showCommandDropFeedback = function (block) { + var y, target; + + target = block.closestAttachTarget(this); + if (!target) { + return null; + } + this.add(this.feedbackMorph); + this.feedbackMorph.border = 0; + this.feedbackMorph.edge = 0; + this.feedbackMorph.alpha = 1; + this.feedbackMorph.setExtent(new Point( + target.element.width(), + Math.max( + SyntaxElementMorph.prototype.corner, + SyntaxElementMorph.prototype.feedbackMinHeight + ) + )); + this.feedbackMorph.color = this.feedbackColor; + this.feedbackMorph.drawNew(); + this.feedbackMorph.changed(); + y = target.point.y; + if (target.loc === 'bottom') { + if (target.type === 'block') { + if (target.element.nextBlock()) { + y -= SyntaxElementMorph.prototype.corner; + } + } else if (target.type === 'slot') { + if (target.element.nestedBlock()) { + y -= SyntaxElementMorph.prototype.corner; + } + } + } + this.feedbackMorph.setPosition(new Point( + target.element.left(), + y + )); +}; + +ScriptsMorph.prototype.showCommentDropFeedback = function (comment, hand) { + var target = this.closestBlock(comment, hand); + if (!target) { + return null; + } + + this.feedbackMorph.bounds = target.bounds + .expandBy(Math.max( + BlockMorph.prototype.edge * 2, + BlockMorph.prototype.reporterDropFeedbackPadding + )); + this.feedbackMorph.edge = SyntaxElementMorph.prototype.rounding; + this.feedbackMorph.border = Math.max( + SyntaxElementMorph.prototype.edge, + 3 + ); + this.add(this.feedbackMorph); + this.feedbackMorph.color = comment.color.copy(); + this.feedbackMorph.color.a = 0.25; + this.feedbackMorph.borderColor = comment.titleBar.color; + this.feedbackMorph.drawNew(); + this.feedbackMorph.changed(); +}; + +ScriptsMorph.prototype.closestInput = function (reporter, hand) { + // passing the hand is optional (when dragging reporters) + var fb = reporter.fullBoundsNoShadow(), + stacks = this.children.filter(function (child) { + return (child instanceof BlockMorph) && + (child.fullBounds().intersects(fb)); + }), + blackList = reporter.allInputs(), + handPos, + target, + all; + + all = []; + stacks.forEach(function (stack) { + all = all.concat(stack.allInputs()); + }); + if (all.length === 0) {return null; } + + function touchingVariadicArrowsIfAny(inp, point) { + if (inp instanceof MultiArgMorph) { + if (point) { + return inp.arrows().bounds.containsPoint(point); + } + return inp.arrows().bounds.intersects(fb); + } + return true; + } + + if (this.isPreferringEmptySlots) { + if (hand) { + handPos = hand.position(); + target = detect( + all, + function (input) { + return (input instanceof InputSlotMorph + || input instanceof ArgMorph + || (input instanceof RingMorph + && !input.contents()) + || input.isEmptySlot()) + && !input.isLocked() + && input.bounds.containsPoint(handPos) + && !contains(blackList, input) + && touchingVariadicArrowsIfAny(input, handPos); + } + ); + if (target) { + return target; + } + } + target = detect( + all, + function (input) { + return (input instanceof InputSlotMorph + || input instanceof ArgMorph + || (input instanceof RingMorph + && !input.contents()) + || input.isEmptySlot()) + && !input.isLocked() + && input.bounds.intersects(fb) + && !contains(blackList, input) + && touchingVariadicArrowsIfAny(input); + } + ); + if (target) { + return target; + } + } + + if (hand) { + handPos = hand.position(); + target = detect( + all, + function (input) { + return (input !== reporter) + && !input.isLocked() + && input.bounds.containsPoint(handPos) + && !(input.parent instanceof PrototypeHatBlockMorph) + && !contains(blackList, input); + } + ); + if (target) { + return target; + } + } + return detect( + all, + function (input) { + return (input !== reporter) + && !input.isLocked() + && input.fullBounds().intersects(fb) + && !(input.parent instanceof PrototypeHatBlockMorph) + && !contains(blackList, input); + } + ); +}; + +ScriptsMorph.prototype.closestBlock = function (comment, hand) { + // passing the hand is optional (when dragging comments) + var fb = comment.bounds, + stacks = this.children.filter(function (child) { + return (child instanceof BlockMorph) && + (child.fullBounds().intersects(fb)); + }), + handPos, + target, + all; + + all = []; + stacks.forEach(function (stack) { + all = all.concat(stack.allChildren().slice(0).reverse().filter( + function (child) {return child instanceof BlockMorph && + !child.isTemplate; } + )); + }); + if (all.length === 0) {return null; } + + if (hand) { + handPos = hand.position(); + target = detect( + all, + function (block) { + return !block.comment + && !block.isPrototype + && block.bounds.containsPoint(handPos); + } + ); + if (target) { + return target; + } + } + return detect( + all, + function (block) { + return !block.comment + && !block.isPrototype + && block.bounds.intersects(fb); + } + ); +}; + +// ScriptsMorph user menu + +ScriptsMorph.prototype.userMenu = function () { + var menu = new MenuMorph(this), + ide = this.parentThatIsA(IDE_Morph), + blockEditor, + myself = this, + obj = this.owner, + stage = obj.parentThatIsA(StageMorph); + + if (!ide) { + blockEditor = this.parentThatIsA(BlockEditorMorph); + if (blockEditor) { + ide = blockEditor.target.parentThatIsA(IDE_Morph); + } + } + menu.addItem('clean up', 'cleanUp', 'arrange scripts\nvertically'); + menu.addItem('add comment', 'addComment'); + if (this.lastDroppedBlock) { + menu.addItem( + 'undrop', + 'undrop', + 'undo the last\nblock drop\nin this pane' + ); + } + menu.addItem( + 'scripts pic...', + 'exportScriptsPicture', + 'open a new window\nwith a picture of all scripts' + ); + if (ide) { + menu.addLine(); + menu.addItem( + 'make a block...', + function () { + new BlockDialogMorph( + null, + function (definition) { + if (definition.spec !== '') { + if (definition.isGlobal) { + stage.globalBlocks.push(definition); + } else { + obj.customBlocks.push(definition); + } + ide.flushPaletteCache(); + ide.refreshPalette(); + new BlockEditorMorph(definition, obj).popUp(); + } + }, + myself + ).prompt( + 'Make a block', + null, + myself.world() + ); + } + ); + } + return menu; +}; + +// ScriptsMorph user menu features: + +ScriptsMorph.prototype.cleanUp = function () { + var origin = this.topLeft(), + y = this.cleanUpMargin, + myself = this; + this.children.sort(function (a, b) { + // make sure the prototype hat block always stays on top + return a instanceof PrototypeHatBlockMorph ? 0 : a.top() - b.top(); + }).forEach(function (child) { + if (child instanceof CommentMorph && child.block) { + return; // skip anchored comments + } + child.setPosition(origin.add(new Point(myself.cleanUpMargin, y))); + if (child instanceof BlockMorph) { + child.allComments().forEach(function (comment) { + comment.align(child, true); // ignore layer + }); + } + y += child.stackHeight() + myself.cleanUpSpacing; + }); + if (this.parent) { + this.setPosition(this.parent.topLeft()); + } + this.adjustBounds(); +}; + +ScriptsMorph.prototype.exportScriptsPicture = function () { + var boundingBox, pic, ctx; + if (this.children.length === 0) {return; } + boundingBox = this.children[0].fullBounds(); + this.children.forEach(function (child) { + if (child.isVisible) { + boundingBox = boundingBox.merge(child.fullBounds()); + } + }); + pic = newCanvas(boundingBox.extent()); + ctx = pic.getContext('2d'); + this.children.forEach(function (child) { + var pos = child.position(); + if (child.isVisible) { + ctx.drawImage( + child.fullImageClassic(), + pos.x - boundingBox.origin.x, + pos.y - boundingBox.origin.y + ); + } + }); + window.open(pic.toDataURL()); +}; + +ScriptsMorph.prototype.addComment = function () { + new CommentMorph().pickUp(this.world()); +}; + +ScriptsMorph.prototype.undrop = function () { + if (!this.lastDroppedBlock) {return; } + if (this.lastDroppedBlock instanceof CommandBlockMorph) { + if (this.lastNextBlock) { + this.add(this.lastNextBlock); + } + if (this.lastDropTarget) { + if (this.lastDropTarget.loc === 'bottom') { + if (this.lastDropTarget.type === 'slot') { + if (this.lastNextBlock) { + this.lastDropTarget.element.nestedBlock( + this.lastNextBlock + ); + } + } else { // 'block' + if (this.lastNextBlock) { + this.lastDropTarget.element.nextBlock( + this.lastNextBlock + ); + } + } + } else if (this.lastDropTarget.loc === 'top') { + this.add(this.lastDropTarget.element); + } + } + } else { // ReporterBlockMorph + if (this.lastDropTarget) { + this.lastDropTarget.replaceInput( + this.lastDroppedBlock, + this.lastReplacedInput + ); + this.lastDropTarget.fixBlockColor(null, true); + if (this.lastPreservedBlocks) { + this.lastPreservedBlocks.forEach(function (morph) { + morph.destroy(); + }); + } + } + } + this.lastDroppedBlock.pickUp(this.world()); + this.clearDropHistory(); +}; + + +ScriptsMorph.prototype.clearDropHistory = function () { + this.lastDroppedBlock = null; + this.lastReplacedInput = null; + this.lastDropTarget = null; + this.lastPreservedBlocks = null; + this.lastNextBlock = null; +}; + +// ScriptsMorph blocks layout fix + +ScriptsMorph.prototype.fixMultiArgs = function () { + var oldFlag = Morph.prototype.trackChanges; + + Morph.prototype.trackChanges = false; + this.forAllChildren(function (morph) { + if (morph instanceof MultiArgMorph) { + morph.fixLayout(); + } + }); + Morph.prototype.trackChanges = oldFlag; +}; + +// ScriptsMorph drag & drop: + +ScriptsMorph.prototype.wantsDropOf = function (aMorph) { + // override the inherited method + return aMorph instanceof SyntaxElementMorph || + aMorph instanceof CommentMorph; +}; + +ScriptsMorph.prototype.reactToDropOf = function (droppedMorph, hand) { + if (droppedMorph instanceof BlockMorph || + droppedMorph instanceof CommentMorph) { + droppedMorph.snap(hand); + } + this.adjustBounds(); +}; + +// ArgMorph ////////////////////////////////////////////////////////// + +/* + I am a syntax element and the ancestor of all block inputs. + I am present in block labels. + Usually I am just a receptacle for inherited methods and attributes, + however, if my 'type' attribute is set to one of the following + values, I act as an iconic slot myself: + + 'list' - a list symbol +*/ + +// ArgMorph inherits from SyntaxElementMorph: + +ArgMorph.prototype = new SyntaxElementMorph(); +ArgMorph.prototype.constructor = ArgMorph; +ArgMorph.uber = SyntaxElementMorph.prototype; + +// ArgMorph instance creation: + +function ArgMorph(type) { + this.init(type); +} + +ArgMorph.prototype.init = function (type) { + this.type = type || null; + this.isHole = false; + ArgMorph.uber.init.call(this); + this.color = new Color(0, 17, 173); + this.setExtent(new Point(50, 50)); +}; + +// ArgMorph drag & drop: for demo puposes only + +ArgMorph.prototype.justDropped = function () { + if (!(this instanceof CommandSlotMorph)) { + this.drawNew(); + this.changed(); + } +}; + +// ArgMorph spec extrapolation (for demo purposes) + +ArgMorph.prototype.getSpec = function () { + return '%s'; // default +}; + +// ArgMorph drawing + +ArgMorph.prototype.drawNew = function () { + if (this.type === 'list') { + this.image = this.listIcon(); + this.silentSetExtent(new Point( + this.image.width, + this.image.height + )); + } else if (this.type === 'object') { + this.image = this.objectIcon(); + this.silentSetExtent(new Point( + this.image.width, + this.image.height + )); + } else { + ArgMorph.uber.drawNew.call(this); + } +}; + +ArgMorph.prototype.listIcon = function () { + var frame = new Morph(), + first = new CellMorph(), + second = new CellMorph(), + source, + icon, + context, + ratio; + + frame.color = new Color(255, 255, 255); + second.setPosition(first.bottomLeft().add(new Point( + 0, + this.fontSize / 3 + ))); + first.add(second); + first.setPosition(frame.position().add(this.fontSize)); + frame.add(first); + frame.bounds.corner = second.bounds.corner.add(this.fontSize); + frame.drawNew(); + source = frame.fullImage(); + ratio = (this.fontSize + this.edge) / source.height; + icon = newCanvas(new Point( + Math.ceil(source.width * ratio) + 1, + Math.ceil(source.height * ratio) + 1 + )); + context = icon.getContext('2d'); + context.fillStyle = 'black'; + context.fillRect(0, 0, icon.width, icon.height); + context.scale(ratio, ratio); + context.drawImage(source, 1 / ratio, 1 / ratio); + return icon; +}; + +ArgMorph.prototype.objectIcon = function () { + return this.labelPart('%turtle').image; +}; + +// ArgMorph evaluation + +ArgMorph.prototype.isEmptySlot = function () { + return this.type !== null; +}; + +// CommandSlotMorph //////////////////////////////////////////////////// + +/* + I am a CommandBlock-shaped input slot. I can nest command blocks + and also accept reporters (containing reified scripts). + + my most important accessor is + + nestedBlock() - answer the command block I encompass, if any + + My command spec is %cmd + + evaluate() returns my nested block or null +*/ + +// CommandSlotMorph inherits from ArgMorph: + +CommandSlotMorph.prototype = new ArgMorph(); +CommandSlotMorph.prototype.constructor = CommandSlotMorph; +CommandSlotMorph.uber = ArgMorph.prototype; + +// CommandSlotMorph instance creation: + +function CommandSlotMorph() { + this.init(); +} + +CommandSlotMorph.prototype.init = function () { + CommandSlotMorph.uber.init.call(this); + this.color = new Color(0, 17, 173); + this.setExtent(new Point(230, this.corner * 4 + this.cSlotPadding)); +}; + +CommandSlotMorph.prototype.getSpec = function () { + return '%cmd'; +}; + +// CommandSlotMorph enumerating: + +CommandSlotMorph.prototype.topBlock = function () { + if (this.parent.topBlock) { + return this.parent.topBlock(); + } + return this.nestedBlock(); +}; + +// CommandSlotMorph nesting: + +CommandSlotMorph.prototype.nestedBlock = function (block) { + if (block) { + var nb = this.nestedBlock(); + this.add(block); + if (nb) { + block.bottomBlock().nextBlock(nb); + } + this.fixLayout(); + } else { + return detect( + this.children, + function (child) { + return child instanceof CommandBlockMorph; + } + ); + } +}; + +// CommandSlotMorph attach targets: + +CommandSlotMorph.prototype.slotAttachPoint = function () { + return new Point( + this.dentCenter(), + this.top() + this.corner * 2 + ); +}; + +CommandSlotMorph.prototype.dentLeft = function () { + return this.left() + + this.corner + + this.inset * 2; +}; + +CommandSlotMorph.prototype.dentCenter = function () { + return this.dentLeft() + + this.corner + + (this.dent * 0.5); +}; + +CommandSlotMorph.prototype.attachTargets = function () { + var answer = []; + answer.push({ + point: this.slotAttachPoint(), + element: this, + loc: 'bottom', + type: 'slot' + }); + return answer; +}; + +// CommandSlotMorph layout: + +CommandSlotMorph.prototype.fixLayout = function () { + var nb = this.nestedBlock(); + if (this.parent) { + if (!this.color.eq(this.parent.color)) { + this.setColor(this.parent.color); + } + } + if (nb) { + nb.setPosition( + new Point( + this.left() + this.edge + this.rfBorder, + this.top() + this.edge + this.rfBorder + ) + ); + this.setWidth(nb.fullBounds().width() + + (this.edge + this.rfBorder) * 2 + ); + this.setHeight(nb.fullBounds().height() + + this.edge + (this.rfBorder * 2) - (this.corner - this.edge) + ); + } else { + this.setHeight(this.corner * 4); + this.setWidth( + this.corner * 4 + + this.inset + + this.dent + ); + } + if (this.parent.fixLayout) { + this.parent.fixLayout(); + } +}; + +// CommandSlotMorph evaluating: + +CommandSlotMorph.prototype.evaluate = function () { + return this.nestedBlock(); +}; + +CommandSlotMorph.prototype.isEmptySlot = function () { + return this.nestedBlock() === null; +}; + +// CommandSlotMorph context menu ops + +CommandSlotMorph.prototype.attach = function () { + // for context menu demo and testing purposes + // override inherited version to adjust new owner's layout + var choices = this.overlappedMorphs(), + menu = new MenuMorph(this, 'choose new parent:'), + myself = this; + + choices.forEach(function (each) { + menu.addItem(each.toString().slice(0, 50), function () { + each.add(myself); + myself.isDraggable = false; + if (each.fixLayout) { + each.fixLayout(); + } + }); + }); + if (choices.length > 0) { + menu.popUpAtHand(this.world()); + } +}; + +// CommandSlotMorph drawing: + +CommandSlotMorph.prototype.drawNew = function () { + var context; + this.cachedClr = this.color.toString(); + this.cachedClrBright = this.bright(); + this.cachedClrDark = this.dark(); + this.image = newCanvas(this.extent()); + context = this.image.getContext('2d'); + context.fillStyle = this.cachedClr; + context.fillRect(0, 0, this.width(), this.height()); + + // draw the 'flat' shape: + context.fillStyle = this.rfColor.toString(); + this.drawFlat(context); + + if (MorphicPreferences.isFlat) {return; } + + // add 3D-Effect: + this.drawEdges(context); +}; + +CommandSlotMorph.prototype.drawFlat = function (context) { + var isFilled = this.nestedBlock() !== null, + ins = (isFilled ? this.inset : this.inset / 2), + dent = (isFilled ? this.dent : this.dent / 2), + indent = this.corner * 2 + ins, + edge = this.edge, + rf = (isFilled ? this.rfBorder : 0), + y = this.height() - this.corner - edge; + + context.beginPath(); + + // top left: + context.arc( + this.corner + edge, + this.corner + edge, + this.corner, + radians(-180), + radians(-90), + false + ); + + // dent: + context.lineTo(this.corner + ins + edge + rf * 2, edge); + context.lineTo(indent + edge + rf * 2, this.corner + edge); + context.lineTo( + indent + edge + rf * 2 + (dent - rf * 2), + this.corner + edge + ); + context.lineTo( + indent + edge + rf * 2 + (dent - rf * 2) + this.corner, + edge + ); + context.lineTo(this.width() - this.corner - edge, edge); + + // top right: + context.arc( + this.width() - this.corner - edge, + this.corner + edge, + this.corner, + radians(-90), + radians(-0), + false + ); + + // bottom right: + context.arc( + this.width() - this.corner - edge, + y, + this.corner, + radians(0), + radians(90), + false + ); + + // bottom left: + context.arc( + this.corner + edge, + y, + this.corner, + radians(90), + radians(180), + false + ); + + context.closePath(); + context.fill(); + +}; + +CommandSlotMorph.prototype.drawEdges = function (context) { + var isFilled = this.nestedBlock() !== null, + ins = (isFilled ? this.inset : this.inset / 2), + dent = (isFilled ? this.dent : this.dent / 2), + indent = this.corner * 2 + ins, + edge = this.edge, + rf = (isFilled ? this.rfBorder : 0), + shift = this.edge * 0.5, + gradient, + upperGradient, + lowerGradient, + rightGradient; + + context.lineWidth = this.edge; + context.lineJoin = 'round'; + context.lineCap = 'round'; + + + // bright: + // bottom horizontal line + gradient = context.createLinearGradient( + 0, + this.height(), + 0, + this.height() - this.edge + ); + gradient.addColorStop(0, this.cachedClr); + gradient.addColorStop(1, this.cachedClrBright); + + context.strokeStyle = gradient; + context.beginPath(); + context.moveTo(this.corner + edge, this.height() - shift); + context.lineTo( + this.width() - this.corner - edge, + this.height() - shift + ); + context.stroke(); + + // bottom right corner + gradient = context.createRadialGradient( + this.width() - (this.corner + edge), + this.height() - (this.corner + edge), + this.corner, + this.width() - (this.corner + edge), + this.height() - (this.corner + edge), + this.corner + edge + ); + gradient.addColorStop(0, this.cachedClrBright); + gradient.addColorStop(1, this.cachedClr); + + context.strokeStyle = gradient; + context.beginPath(); + context.arc( + this.width() - (this.corner + edge), + this.height() - (this.corner + edge), + this.corner + shift, + radians(0), + radians(90), + false + ); + context.stroke(); + + // right vertical line + gradient = context.createLinearGradient( + this.width(), + 0, + this.width() - this.edge, + 0 + ); + gradient.addColorStop(0, this.cachedClr); + gradient.addColorStop(1, this.cachedClrBright); + + context.strokeStyle = gradient; + context.beginPath(); + context.moveTo( + this.width() - shift, + this.height() - this.corner - this.edge + ); + context.lineTo(this.width() - shift, edge + this.corner); + context.stroke(); + + context.shadowOffsetY = shift; + context.shadowBlur = this.edge; + context.shadowColor = this.rfColor.darker(80).toString(); + + // left vertical side + gradient = context.createLinearGradient( + 0, + 0, + edge, + 0 + ); + gradient.addColorStop(0, this.cachedClr); + gradient.addColorStop(1, this.cachedClrDark); + + context.strokeStyle = gradient; + context.beginPath(); + context.moveTo(shift, edge + this.corner); + context.lineTo(shift, this.height() - edge - this.corner); + context.stroke(); + + // upper left corner + gradient = context.createRadialGradient( + this.corner + edge, + this.corner + edge, + this.corner, + this.corner + edge, + this.corner + edge, + this.corner + edge + ); + gradient.addColorStop(0, this.cachedClrDark); + gradient.addColorStop(1, this.cachedClr); + + context.strokeStyle = gradient; + context.beginPath(); + context.arc( + this.corner + edge, + this.corner + edge, + this.corner + shift, + radians(-180), + radians(-90), + false + ); + context.stroke(); + + // upper edge (left side) + upperGradient = context.createLinearGradient( + 0, + 0, + 0, + this.edge + ); + upperGradient.addColorStop(0, this.cachedClr); + upperGradient.addColorStop(1, this.cachedClrDark); + + context.strokeStyle = upperGradient; + context.beginPath(); + context.moveTo(this.corner + edge, shift); + context.lineTo( + this.corner + ins + edge + rf * 2 - shift, + shift + ); + context.stroke(); + + // dent bottom + lowerGradient = context.createLinearGradient( + 0, + this.corner, + 0, + this.corner + edge + ); + lowerGradient.addColorStop(0, this.cachedClr); + lowerGradient.addColorStop(1, this.cachedClrDark); + + context.strokeStyle = lowerGradient; + context.beginPath(); + context.moveTo(indent + edge + rf * 2 + shift, this.corner + shift); + context.lineTo( + indent + edge + rf * 2 + (dent - rf * 2), + this.corner + shift + ); + context.stroke(); + + // dent right edge + rightGradient = context.createLinearGradient( + indent + edge + rf * 2 + (dent - rf * 2) - shift, + this.corner, + indent + edge + rf * 2 + (dent - rf * 2) + shift * 0.7, + this.corner + shift + shift * 0.7 + ); + rightGradient.addColorStop(0, this.cachedClr); + rightGradient.addColorStop(1, this.cachedClrDark); + + context.strokeStyle = rightGradient; + context.beginPath(); + context.moveTo( + indent + edge + rf * 2 + (dent - rf * 2), + this.corner + shift + ); + context.lineTo( + indent + edge + rf * 2 + (dent - rf * 2) + this.corner, + shift + ); + context.stroke(); + + // upper edge (right side) + context.strokeStyle = upperGradient; + context.beginPath(); + context.moveTo( + indent + edge + rf * 2 + (dent - rf * 2) + this.corner, + shift + ); + context.lineTo(this.width() - this.corner - edge, shift); + context.stroke(); +}; + +// RingCommandSlotMorph /////////////////////////////////////////////////// + +/* + I am a CommandBlock-shaped input slot for use in RingMorphs. + I can only nest command blocks, not reporters. + + My command spec is %rc + + evaluate() returns my nested block or null + (inherited from CommandSlotMorph) +*/ + +// RingCommandSlotMorph inherits from CommandSlotMorph: + +RingCommandSlotMorph.prototype = new CommandSlotMorph(); +RingCommandSlotMorph.prototype.constructor = RingCommandSlotMorph; +RingCommandSlotMorph.uber = CommandSlotMorph.prototype; + +// RingCommandSlotMorph preferences settings + +RingCommandSlotMorph.prototype.rfBorder = 0; +RingCommandSlotMorph.prototype.edge = RingMorph.prototype.edge; + +// RingCommandSlotMorph instance creation: + +function RingCommandSlotMorph() { + this.init(); +} + +RingCommandSlotMorph.prototype.init = function () { + RingCommandSlotMorph.uber.init.call(this); + this.isHole = true; + this.noticesTransparentClick = true; + this.color = new Color(0, 17, 173); + this.alpha = RingMorph.prototype.alpha; + this.contrast = RingMorph.prototype.contrast; +}; + +RingCommandSlotMorph.prototype.getSpec = function () { + return '%rc'; +}; + +// RingCommandSlotMorph drawing: + +RingCommandSlotMorph.prototype.drawNew = function () { + var context; + this.cachedClr = this.color.toString(); + this.cachedClrBright = this.bright(); + this.cachedClrDark = this.dark(); + this.image = newCanvas(this.extent()); + context = this.image.getContext('2d'); + context.fillStyle = this.cachedClr; + + // draw the 'flat' shape: + this.drawFlat(context); + + if (MorphicPreferences.isFlat) {return; } + + // add 3D-Effect: + this.drawEdges(context); +}; + +RingCommandSlotMorph.prototype.drawFlat = function (context) { + var isFilled = this.nestedBlock() !== null, + ins = (isFilled ? this.inset : this.inset / 2), + dent = (isFilled ? this.dent : this.dent / 2), + indent = this.corner * 2 + ins, + edge = this.edge, + w = this.width(), + h = this.height(), + rf = (isFilled ? this.rfBorder : 0), + y = h - this.corner - edge; + + // top half: + + context.beginPath(); + context.moveTo(0, h / 2); + context.lineTo(edge, h / 2); + + // top left: + context.arc( + this.corner + edge, + this.corner + edge, + this.corner, + radians(-180), + radians(-90), + false + ); + + // dent: + context.lineTo(this.corner + ins + edge + rf * 2, edge); + context.lineTo(indent + edge + rf * 2, this.corner + edge); + context.lineTo( + indent + edge + rf * 2 + (dent - rf * 2), + this.corner + edge + ); + context.lineTo( + indent + edge + rf * 2 + (dent - rf * 2) + this.corner, + edge + ); + context.lineTo(this.width() - this.corner - edge, edge); + + // top right: + context.arc( + w - this.corner - edge, + this.corner + edge, + this.corner, + radians(-90), + radians(-0), + false + ); + + context.lineTo(w - this.edge, h / 2); + context.lineTo(w, h / 2); + context.lineTo(w, 0); + context.lineTo(0, 0); + context.closePath(); + context.fill(); + + // bottom half: + context.beginPath(); + context.moveTo(w, h / 2); + context.lineTo(w - edge, h / 2); + + // bottom right: + context.arc( + this.width() - this.corner - edge, + y, + this.corner, + radians(0), + radians(90), + false + ); + + // bottom left: + context.arc( + this.corner + edge, + y, + this.corner, + radians(90), + radians(180), + false + ); + + context.lineTo(edge, h / 2); + context.lineTo(0, h / 2); + context.lineTo(0, h); + context.lineTo(w, h); + context.closePath(); + context.fill(); + +}; + +// CSlotMorph //////////////////////////////////////////////////// + +/* + I am a C-shaped input slot. I can nest command blocks and also accept + reporters (containing reified scripts). + + my most important accessor is + + nestedBlock() - the command block I encompass, if any (inherited) + + My command spec is %c + + evaluate() returns my nested block or null +*/ + +// CSlotMorph inherits from CommandSlotMorph: + +CSlotMorph.prototype = new CommandSlotMorph(); +CSlotMorph.prototype.constructor = CSlotMorph; +CSlotMorph.uber = CommandSlotMorph.prototype; + +// CSlotMorph instance creation: + +function CSlotMorph() { + this.init(); +} + +CSlotMorph.prototype.init = function () { + CommandSlotMorph.uber.init.call(this); + this.isHole = true; + this.color = new Color(0, 17, 173); + this.setExtent(new Point(230, this.corner * 4 + this.cSlotPadding)); +}; + +CSlotMorph.prototype.getSpec = function () { + return '%c'; +}; + +CSlotMorph.prototype.mappedCode = function (definitions) { + var code = StageMorph.prototype.codeMappings.reify || '<#1>', + codeLines = code.split('\n'), + nested = this.nestedBlock(), + part = nested ? nested.mappedCode(definitions) : '', + partLines = (part.toString()).split('\n'), + rx = new RegExp('<#1>', 'g'); + + codeLines.forEach(function (codeLine, idx) { + var prefix = '', + indent; + if (codeLine.trimLeft().indexOf('<#1>') === 0) { + indent = codeLine.indexOf('<#1>'); + prefix = codeLine.slice(0, indent); + } + codeLines[idx] = codeLine.replace( + new RegExp('<#1>'), + partLines.join('\n' + prefix) + ); + codeLines[idx] = codeLines[idx].replace(rx, partLines.join('\n')); + }); + + return codeLines.join('\n'); +}; + + +// CSlotMorph layout: + +CSlotMorph.prototype.fixLayout = function () { + var nb = this.nestedBlock(); + if (nb) { + nb.setPosition( + new Point( + this.left() + this.inset, + this.top() + this.corner + ) + ); + this.setHeight(nb.fullBounds().height() + this.corner); + this.setWidth(nb.fullBounds().width() + (this.cSlotPadding * 2)); + } else { + this.setHeight(this.corner * 4 + this.cSlotPadding); // default + this.setWidth( + this.corner * 4 + + (this.inset * 2) + + this.dent + + (this.cSlotPadding * 2) + ); // default + } + if (this.parent.fixLayout) { + this.parent.fixLayout(); + } +}; + +// CSlotMorph drawing: + +CSlotMorph.prototype.drawNew = function () { + var context; + this.cachedClr = this.color.toString(); + this.cachedClrBright = this.bright(); + this.cachedClrDark = this.dark(); + this.image = newCanvas(this.extent()); + context = this.image.getContext('2d'); + context.fillStyle = this.cachedClr; + + // draw the 'flat' shape: + this.drawFlat(context); + + if (MorphicPreferences.isFlat) {return; } + + // add 3D-Effect: + this.drawTopRightEdge(context); + this.drawTopEdge(context, this.inset, this.corner); + this.drawTopLeftEdge(context); + this.drawBottomEdge(context); + this.drawRightEdge(context); +}; + +CSlotMorph.prototype.drawFlat = function (context) { + context.beginPath(); + + // top line: + context.moveTo(0, 0); + context.lineTo(this.width(), 0); + + // top right: + context.arc( + this.width() - this.corner, + 0, + this.corner, + radians(90), + radians(0), + true + ); + + // jigsaw shape: + context.lineTo(this.width() - this.corner, this.corner); + context.lineTo( + (this.inset * 2) + (this.corner * 3) + this.dent, + this.corner + ); + context.lineTo( + (this.inset * 2) + (this.corner * 2) + this.dent, + this.corner * 2 + ); + context.lineTo( + (this.inset * 2) + (this.corner * 2), + this.corner * 2 + ); + context.lineTo( + (this.inset * 2) + this.corner, + this.corner + ); + context.lineTo( + this.inset + this.corner, + this.corner + ); + context.arc( + this.inset + this.corner, + this.corner * 2, + this.corner, + radians(270), + radians(180), + true + ); + + // bottom: + context.lineTo( + this.inset, + this.height() - (this.corner * 2) + ); + context.arc( + this.inset + this.corner, + this.height() - (this.corner * 2), + this.corner, + radians(180), + radians(90), + true + ); + context.lineTo( + this.width() - this.corner, + this.height() - this.corner + ); + context.arc( + this.width() - this.corner, + this.height(), + this.corner, + radians(-90), + radians(-0), + false + ); + context.lineTo(0, this.height()); + + // fill: + context.closePath(); + context.fill(); +}; + +CSlotMorph.prototype.drawTopRightEdge = function (context) { + var shift = this.edge * 0.5, + x = this.width() - this.corner, + y = 0, + gradient; + + gradient = context.createRadialGradient( + x, + y, + this.corner, + x, + y, + this.corner - this.edge + ); + gradient.addColorStop(0, this.cachedClrDark); + gradient.addColorStop(1, this.cachedClr); + + context.lineWidth = this.edge; + context.lineJoin = 'round'; + context.lineCap = 'round'; + + context.strokeStyle = gradient; + + context.beginPath(); + context.arc( + x, + y, + this.corner - shift, + radians(90), + radians(0), + true + ); + context.stroke(); +}; + +CSlotMorph.prototype.drawTopEdge = function (context, x, y) { + var shift = this.edge * 0.5, + indent = x + this.corner * 2 + this.inset, + upperGradient, + lowerGradient, + rightGradient; + + context.lineWidth = this.edge; + context.lineJoin = 'round'; + context.lineCap = 'round'; + + upperGradient = context.createLinearGradient( + 0, + y - this.edge, + 0, + y + ); + upperGradient.addColorStop(0, this.cachedClr); + upperGradient.addColorStop(1, this.cachedClrDark); + + context.strokeStyle = upperGradient; + context.beginPath(); + context.moveTo(x + this.corner, y - shift); + context.lineTo(x + this.corner + this.inset - shift, y - shift); + context.stroke(); + + lowerGradient = context.createLinearGradient( + 0, + y + this.corner - this.edge, + 0, + y + this.corner + ); + lowerGradient.addColorStop(0, this.cachedClr); + lowerGradient.addColorStop(1, this.cachedClrDark); + + context.strokeStyle = lowerGradient; + context.beginPath(); + context.moveTo(indent + shift, y + this.corner - shift); + context.lineTo(indent + this.dent, y + this.corner - shift); + context.stroke(); + + rightGradient = context.createLinearGradient( + (x + this.inset + (this.corner * 2) + this.dent) - shift, + (y + this.corner - shift) - shift, + (x + this.inset + (this.corner * 2) + this.dent) + (shift * 0.7), + (y + this.corner - shift) + (shift * 0.7) + ); + rightGradient.addColorStop(0, this.cachedClr); + rightGradient.addColorStop(1, this.cachedClrDark); + + + context.strokeStyle = rightGradient; + context.beginPath(); + context.moveTo( + x + this.inset + (this.corner * 2) + this.dent, + y + this.corner - shift + ); + context.lineTo( + x + this.corner * 3 + this.inset + this.dent, + y - shift + ); + context.stroke(); + + context.strokeStyle = upperGradient; + context.beginPath(); + context.moveTo( + x + this.corner * 3 + this.inset + this.dent, + y - shift + ); + context.lineTo(this.width() - this.corner, y - shift); + context.stroke(); +}; + +CSlotMorph.prototype.drawTopLeftEdge = function (context) { + var shift = this.edge * 0.5, + gradient; + + gradient = context.createRadialGradient( + this.corner + this.inset, + this.corner * 2, + this.corner, + this.corner + this.inset, + this.corner * 2, + this.corner + this.edge + ); + gradient.addColorStop(0, this.cachedClrDark); + gradient.addColorStop(1, this.cachedClr); + + context.lineWidth = this.edge; + context.lineJoin = 'round'; + context.lineCap = 'round'; + + context.strokeStyle = gradient; + + context.beginPath(); + context.arc( + this.corner + this.inset, + this.corner * 2, + this.corner + shift, + radians(-180), + radians(-90), + false + ); + context.stroke(); +}; + +CSlotMorph.prototype.drawRightEdge = function (context) { + var shift = this.edge * 0.5, + x = this.inset, + gradient; + + gradient = context.createLinearGradient(x - this.edge, 0, x, 0); + gradient.addColorStop(0, this.cachedClr); + gradient.addColorStop(1, this.cachedClrDark); + + context.lineWidth = this.edge; + context.lineJoin = 'round'; + context.lineCap = 'round'; + + context.strokeStyle = gradient; + context.beginPath(); + context.moveTo(x - shift, this.corner * 2); + context.lineTo(x - shift, this.height() - this.corner * 2); + context.stroke(); +}; + +CSlotMorph.prototype.drawBottomEdge = function (context) { + var shift = this.edge * 0.5, + gradient, + upperGradient; + + context.lineWidth = this.edge; + context.lineJoin = 'round'; + context.lineCap = 'round'; + + upperGradient = context.createRadialGradient( + this.corner + this.inset, + this.height() - (this.corner * 2), + this.corner, /*- this.edge*/ // uncomment for half-tone + this.corner + this.inset, + this.height() - (this.corner * 2), + this.corner + this.edge + ); + upperGradient.addColorStop(0, this.cachedClrBright); + upperGradient.addColorStop(1, this.cachedClr); + context.strokeStyle = upperGradient; + context.beginPath(); + context.arc( + this.corner + this.inset, + this.height() - (this.corner * 2), + this.corner + shift, + radians(180), + radians(90), + true + ); + context.stroke(); + + gradient = context.createLinearGradient( + 0, + this.height() - this.corner, + 0, + this.height() - this.corner + this.edge + ); + gradient.addColorStop(0, this.cachedClrBright); + gradient.addColorStop(1, this.cachedClr); + + context.strokeStyle = gradient; + context.beginPath(); + context.moveTo( + this.inset + this.corner, + this.height() - this.corner + shift + ); + context.lineTo( + this.width() - this.corner, + this.height() - this.corner + shift + ); + + context.stroke(); +}; + +// InputSlotMorph ////////////////////////////////////////////////////// + +/* + I am an editable text input slot. I can be either rectangular or + rounded, and can have an optional drop-down menu. If I'm set to + read-only I must have a drop-down menu and will assume a darker + shade of my parent's color. + + my most important public attributes and accessors are: + + setContents(str/float) - display the argument (string or float) + contents().text - get the displayed string + choices - a key/value list for my optional drop-down + isReadOnly - governs whether I am editable or not + isNumeric - governs my outer shape (round or rect) + + my block specs are: + + %s - string input, rectangular + %n - numerical input, semi-circular vertical edges + %anyUE - any unevaluated + + evaluate() returns my displayed string, cast to float if I'm numerical + + there are also a number of specialized drop-down menu presets, refer + to BlockMorph for details. +*/ + +// InputSlotMorph inherits from ArgMorph: + +InputSlotMorph.prototype = new ArgMorph(); +InputSlotMorph.prototype.constructor = InputSlotMorph; +InputSlotMorph.uber = ArgMorph.prototype; + +// InputSlotMorph preferences settings: + +InputSlotMorph.prototype.executeOnSliderEdit = false; + +// InputSlotMorph instance creation: + +function InputSlotMorph(text, isNumeric, choiceDict, isReadOnly) { + this.init(text, isNumeric, choiceDict, isReadOnly); +} + +InputSlotMorph.prototype.init = function ( + text, + isNumeric, + choiceDict, + isReadOnly +) { + var contents = new StringMorph(''), + arrow = new ArrowMorph( + 'down', + 0, + Math.max(Math.floor(this.fontSize / 6), 1) + ); + + contents.fontSize = this.fontSize; + contents.isShowingBlanks = true; + contents.drawNew(); + + this.isUnevaluated = false; + this.choices = choiceDict || null; // object, function or selector + this.oldContentsExtent = contents.extent(); + this.isNumeric = isNumeric || false; + this.isReadOnly = isReadOnly || false; + this.minWidth = 0; // can be chaged for text-type inputs ("landscape") + this.constant = null; + + InputSlotMorph.uber.init.call(this); + this.color = new Color(255, 255, 255); + this.add(contents); + this.add(arrow); + contents.isEditable = true; + contents.isDraggable = false; + contents.enableSelecting(); + this.setContents(text); +}; + +// InputSlotMorph accessing: + +InputSlotMorph.prototype.getSpec = function () { + if (this.isNumeric) { + return '%n'; + } + return '%s'; // default +}; + +InputSlotMorph.prototype.contents = function () { + return detect( + this.children, + function (child) { + return (child instanceof StringMorph); + } + ); +}; + +InputSlotMorph.prototype.arrow = function () { + return detect( + this.children, + function (child) { + return (child instanceof ArrowMorph); + } + ); +}; + +InputSlotMorph.prototype.setContents = function (aStringOrFloat) { + var cnts = this.contents(), + dta = aStringOrFloat, + isConstant = dta instanceof Array; + if (isConstant) { + dta = localize(dta[0]); + cnts.isItalic = !this.isReadOnly; + } else { // assume dta is a localizable choice if it's a key in my choices + cnts.isItalic = false; + if (this.choices !== null && this.choices[dta] instanceof Array) { + return this.setContents(this.choices[dta]); + } + } + cnts.text = dta; + if (isNil(dta)) { + cnts.text = ''; + } else if (dta.toString) { + cnts.text = dta.toString(); + } + cnts.drawNew(); + + // adjust to zebra coloring: + if (this.isReadOnly && (this.parent instanceof BlockMorph)) { + this.parent.fixLabelColor(); + } + + // remember the constant, if any + this.constant = isConstant ? aStringOrFloat : null; +}; + +// InputSlotMorph drop-down menu: + +InputSlotMorph.prototype.dropDownMenu = function () { + var choices = this.choices, + key, + menu = new MenuMorph( + this.setContents, + null, + this, + this.fontSize + ); + + if (choices instanceof Function) { + choices = choices.call(this); + } else if (isString(choices)) { + choices = this[choices](); + } + if (!choices) { + return null; + } + menu.addItem(' ', null); + for (key in choices) { + if (Object.prototype.hasOwnProperty.call(choices, key)) { + if (key[0] === '~') { + menu.addLine(); + } else { + menu.addItem(key, choices[key]); + } + } + } + if (menu.items.length > 0) { + menu.popUpAtHand(this.world()); + } else { + return null; + } +}; + +InputSlotMorph.prototype.messagesMenu = function () { + var dict = {}, + rcvr = this.parentThatIsA(BlockMorph).receiver(), + stage = rcvr.parentThatIsA(StageMorph), + myself = this, + allNames = []; + + stage.children.concat(stage).forEach(function (morph) { + if (morph instanceof SpriteMorph || morph instanceof StageMorph) { + allNames = allNames.concat(morph.allMessageNames()); + } + }); + allNames.forEach(function (name) { + dict[name] = name; + }); + if (allNames.length > 0) { + dict['~'] = null; + } + dict['new...'] = function () { + + new DialogBoxMorph( + myself, + myself.setContents, + myself + ).prompt( + 'Message name', + null, + myself.world() + ); + }; + + return dict; +}; + +InputSlotMorph.prototype.messagesReceivedMenu = function () { + var dict = {'any message': ['any message']}, + rcvr = this.parentThatIsA(BlockMorph).receiver(), + stage = rcvr.parentThatIsA(StageMorph), + myself = this, + allNames = []; + + stage.children.concat(stage).forEach(function (morph) { + if (morph instanceof SpriteMorph || morph instanceof StageMorph) { + allNames = allNames.concat(morph.allMessageNames()); + } + }); + allNames.forEach(function (name) { + dict[name] = name; + }); + dict['~'] = null; + dict['new...'] = function () { + + new DialogBoxMorph( + myself, + myself.setContents, + myself + ).prompt( + 'Message name', + null, + myself.world() + ); + }; + + return dict; +}; + +InputSlotMorph.prototype.collidablesMenu = function () { + var dict = { + 'mouse-pointer' : ['mouse-pointer'], + edge : ['edge'], + 'pen trails' : ['pen trails'] + }, + rcvr = this.parentThatIsA(BlockMorph).receiver(), + stage = rcvr.parentThatIsA(StageMorph), + allNames = []; + + stage.children.forEach(function (morph) { + if (morph instanceof SpriteMorph) { + if (morph.name !== rcvr.name) { + allNames = allNames.concat(morph.name); + } + } + }); + if (allNames.length > 0) { + dict['~'] = null; + allNames.forEach(function (name) { + dict[name] = name; + }); + } + return dict; +}; + +InputSlotMorph.prototype.distancesMenu = function () { + var dict = { + 'mouse-pointer' : ['mouse-pointer'] + }, + rcvr = this.parentThatIsA(BlockMorph).receiver(), + stage = rcvr.parentThatIsA(StageMorph), + allNames = []; + + stage.children.forEach(function (morph) { + if (morph instanceof SpriteMorph) { + if (morph.name !== rcvr.name) { + allNames = allNames.concat(morph.name); + } + } + }); + if (allNames.length > 0) { + dict['~'] = null; + allNames.forEach(function (name) { + dict[name] = name; + }); + } + return dict; +}; + +InputSlotMorph.prototype.clonablesMenu = function () { + var dict = {}, + rcvr = this.parentThatIsA(BlockMorph).receiver(), + stage = rcvr.parentThatIsA(StageMorph), + allNames = []; + + if (rcvr instanceof SpriteMorph) { + dict.myself = ['myself']; + } + stage.children.forEach(function (morph) { + if (morph instanceof SpriteMorph && !morph.isClone) { + allNames = allNames.concat(morph.name); + } + }); + if (allNames.length > 0) { + dict['~'] = null; + allNames.forEach(function (name) { + dict[name] = name; + }); + } + return dict; +}; + +InputSlotMorph.prototype.objectsMenu = function () { + var rcvr = this.parentThatIsA(BlockMorph).receiver(), + stage = rcvr.parentThatIsA(StageMorph), + dict = {}, + allNames = []; + + dict[stage.name] = stage.name; + stage.children.forEach(function (morph) { + if (morph instanceof SpriteMorph) { + allNames.push(morph.name); + } + }); + if (allNames.length > 0) { + dict['~'] = null; + allNames.forEach(function (name) { + dict[name] = name; + }); + } + return dict; +}; + +InputSlotMorph.prototype.attributesMenu = function () { + var block = this.parentThatIsA(BlockMorph), + objName = block.inputs()[1].evaluate(), + rcvr = block.receiver(), + stage = rcvr.parentThatIsA(StageMorph), + obj, + dict = {}, + varNames = []; + + if (objName === stage.name) { + obj = stage; + } else { + obj = detect( + stage.children, + function (morph) { + return morph.name === objName; + } + ); + } + if (!obj) { + return dict; + } + if (obj instanceof SpriteMorph) { + dict = { + 'x position' : ['x position'], + 'y position' : ['y position'], + 'direction' : ['direction'], + 'costume #' : ['costume #'], + 'costume name' : ['costume name'], + 'size' : ['size'] + }; + } else { // the stage + dict = { + 'costume #' : ['costume #'], + 'costume name' : ['costume name'] + }; + } + varNames = obj.variables.names(); + if (varNames.length > 0) { + dict['~'] = null; + varNames.forEach(function (name) { + dict[name] = name; + }); + } + return dict; +}; + +InputSlotMorph.prototype.costumesMenu = function () { + var rcvr = this.parentThatIsA(BlockMorph).receiver(), + dict, + allNames = []; + if (rcvr instanceof SpriteMorph) { + dict = {Turtle : ['Turtle']}; + } else { // stage + dict = {Empty : ['Empty']}; + } + rcvr.costumes.asArray().forEach(function (costume) { + allNames = allNames.concat(costume.name); + }); + if (allNames.length > 0) { + dict['~'] = null; + allNames.forEach(function (name) { + dict[name] = name; + }); + } + return dict; +}; + +InputSlotMorph.prototype.soundsMenu = function () { + var rcvr = this.parentThatIsA(BlockMorph).receiver(), + allNames = [], + dict = {}; + + rcvr.sounds.asArray().forEach(function (sound) { + allNames = allNames.concat(sound.name); + }); + if (allNames.length > 0) { + allNames.forEach(function (name) { + dict[name] = name; + }); + } + return dict; +}; + +InputSlotMorph.prototype.getVarNamesDict = function () { + var block = this.parentThatIsA(BlockMorph), + rcvr, + proto, + rings, + declarations, + tempVars = [], + dict; + + if (!block) { + return {}; + } + rcvr = block.receiver(); + + proto = detect(block.allParents(), function (morph) { + return morph instanceof PrototypeHatBlockMorph; + }); + if (proto) { + tempVars = proto.inputs()[0].inputFragmentNames(); + } + + rings = block.allParents().filter(function (block) { + return block instanceof RingMorph; + }); + rings.forEach(function (block) { + tempVars = tempVars.concat(block.inputs()[1].evaluate()); + }); + + declarations = block.allParents().filter(function (block) { + return block.selector === 'doDeclareVariables'; + }); + declarations.forEach(function (block) { + tempVars = tempVars.concat(block.inputs()[0].evaluate()); + }); + + if (rcvr) { + dict = rcvr.variables.allNamesDict(); + tempVars.forEach(function (name) { + dict[name] = name; + }); + return dict; + } + return {}; +}; + +// InputSlotMorph layout: + +InputSlotMorph.prototype.fixLayout = function () { + var contents = this.contents(), + arrow = this.arrow(); + + contents.isNumeric = this.isNumeric; + contents.isEditable = (!this.isReadOnly); + if (this.isReadOnly) { + contents.disableSelecting(); + contents.color = new Color(254, 254, 254); + } else { + contents.enableSelecting(); + contents.color = new Color(0, 0, 0); + } + + if (this.choices) { + arrow.setSize(this.fontSize); + arrow.show(); + } else { + arrow.setSize(0); + arrow.hide(); + } + this.setHeight( + contents.height() + + this.edge * 2 + // + this.typeInPadding * 2 + ); + if (this.isNumeric) { + this.setWidth(contents.width() + + Math.floor(arrow.width() * 0.5) + + this.height() + + this.typeInPadding * 2 + ); + } else { + this.setWidth(Math.max( + contents.width() + + arrow.width() + + this.edge * 2 + + this.typeInPadding * 2, + contents.rawHeight ? // single vs. multi-line contents + contents.rawHeight() + arrow.width() + : contents.height() / 1.2 + arrow.width(), + this.minWidth // for text-type slots + )); + } + if (this.isNumeric) { + contents.setPosition(new Point( + Math.floor(this.height() / 2), + this.edge + ).add(new Point(this.typeInPadding, 0)).add(this.position())); + } else { + contents.setPosition(new Point( + this.edge, + this.edge + ).add(new Point(this.typeInPadding, 0)).add(this.position())); + } + + arrow.setPosition(new Point( + this.right() - arrow.width() - this.edge, + contents.top() + )); + + if (this.parent) { + if (this.parent.fixLayout) { + if (this.world()) { + this.startLayout(); + this.parent.fixLayout(); + this.endLayout(); + } else { + this.parent.fixLayout(); + } + } + } +}; + +// InputSlotMorph events: + +InputSlotMorph.prototype.mouseClickLeft = function (pos) { + if (this.arrow().bounds.containsPoint(pos)) { + this.dropDownMenu(); + } else if (this.isReadOnly) { + this.dropDownMenu(); + } else { + this.contents().edit(); + this.contents().selectAll(); + } +}; + +InputSlotMorph.prototype.reactToKeystroke = function () { + var cnts; + if (this.constant) { + cnts = this.contents(); + this.constant = null; + cnts.isItalic = false; + cnts.drawNew(); + } +}; + +InputSlotMorph.prototype.reactToEdit = function () { + this.contents().clearSelection(); +}; + +InputSlotMorph.prototype.reactToSliderEdit = function () { +/* + directly execute the stack of blocks I'm part of if my + "executeOnSliderEdit" setting is turned on, obeying the stage's + thread safety setting. This feature allows for "Bret Victor" style + interactive coding. +*/ + var block, top, receiver, stage; + if (!this.executeOnSliderEdit) {return; } + block = this.parentThatIsA(BlockMorph); + if (block) { + top = block.topBlock(); + receiver = top.receiver(); + if (top instanceof PrototypeHatBlockMorph) { + return; + } + if (receiver) { + stage = receiver.parentThatIsA(StageMorph); + if (stage && stage.isThreadSafe) { + stage.threads.startProcess(top, stage.isThreadSafe); + } else { + top.mouseClickLeft(); + } + } + } +}; + +// InputSlotMorph menu: + +InputSlotMorph.prototype.userMenu = function () { + var menu = new MenuMorph(this); + if (!StageMorph.prototype.enableCodeMapping || this.isNumeric) { + return this.parent.userMenu(); + } + menu.addItem( + 'code string mapping...', + 'mapToCode' + ); + return menu; +}; + +// InputSlotMorph code mapping + +/* + code mapping lets you use blocks to generate arbitrary text-based + source code that can be exported and compiled / embedded elsewhere, + it's not part of Snap's evaluator and not needed for Snap itself +*/ + +InputSlotMorph.prototype.mapToCode = function () { + // private - open a dialog box letting the user map code via the GUI + new DialogBoxMorph( + this, + function (code) { + StageMorph.prototype.codeMappings.string = code; + }, + this + ).promptCode( + 'Code mapping - String <#1>', + StageMorph.prototype.codeMappings.string || '', + this.world() + ); +}; + +InputSlotMorph.prototype.mappedCode = function () { + var code = StageMorph.prototype.codeMappings.string || '<#1>', + block = this.parentThatIsA(BlockMorph), + val = this.evaluate(); + + if (this.isNumeric) {return val; } + if (!isNaN(parseFloat(val))) {return val; } + if (!isString(val)) {return val; } + if (block && contains( + ['doSetVar', 'doChangeVar', 'doShowVar', 'doHideVar'], + block.selector + )) { + return val; + } + return code.replace(/<#1>/g, val); +}; + +// InputSlotMorph evaluating: + +InputSlotMorph.prototype.evaluate = function () { +/* + answer my content's text string. If I am numerical convert that + string to a number. If the conversion fails answer the string + (e.g. for special choices like 'any', 'all' or 'last') otherwise + the numerical value. +*/ + var num, + contents = this.contents(); + if (this.constant) { + return this.constant; + } + if (this.isNumeric) { + num = parseFloat(contents.text || '0'); + if (!isNaN(num)) { + return num; + } + } + return contents.text; +}; + +InputSlotMorph.prototype.isEmptySlot = function () { + return this.contents().text === ''; +}; + +// InputSlotMorph drawing: + +InputSlotMorph.prototype.drawNew = function () { + var context, borderColor, r; + + // initialize my surface property + this.image = newCanvas(this.extent()); + context = this.image.getContext('2d'); + if (this.parent) { + borderColor = this.parent.color; + } else { + borderColor = new Color(120, 120, 120); + } + context.fillStyle = this.color.toString(); + if (this.isReadOnly) { + context.fillStyle = borderColor.darker().toString(); + } + + // cache my border colors + this.cachedClr = borderColor.toString(); + this.cachedClrBright = borderColor.lighter(this.contrast) + .toString(); + this.cachedClrDark = borderColor.darker(this.contrast).toString(); + + if (!this.isNumeric) { + context.fillRect( + this.edge, + this.edge, + this.width() - this.edge * 2, + this.height() - this.edge * 2 + ); + if (!MorphicPreferences.isFlat) { + this.drawRectBorder(context); + } + } else { + r = (this.height() - (this.edge * 2)) / 2; + context.fillStyle = this.color.toString(); + context.beginPath(); + context.arc( + r + this.edge, + r + this.edge, + r, + radians(90), + radians(-90), + false + ); + context.arc( + this.width() - r - this.edge, + r + this.edge, + r, + radians(-90), + radians(90), + false + ); + context.closePath(); + context.fill(); + if (!MorphicPreferences.isFlat) { + this.drawRoundBorder(context); + } + } +}; + +InputSlotMorph.prototype.drawRectBorder = function (context) { + var shift = this.edge * 0.5, + gradient; + + context.lineWidth = this.edge; + context.lineJoin = 'round'; + context.lineCap = 'round'; + + context.shadowOffsetY = shift; + context.shadowBlur = this.edge; + context.shadowColor = this.color.darker(80).toString(); + + gradient = context.createLinearGradient( + 0, + 0, + 0, + this.edge + ); + gradient.addColorStop(0, this.cachedClr); + gradient.addColorStop(1, this.cachedClrDark); + context.strokeStyle = gradient; + context.beginPath(); + context.moveTo(this.edge, shift); + context.lineTo(this.width() - this.edge - shift, shift); + context.stroke(); + + context.shadowOffsetY = 0; + + gradient = context.createLinearGradient( + 0, + 0, + this.edge, + 0 + ); + gradient.addColorStop(0, this.cachedClr); + gradient.addColorStop(1, this.cachedClrDark); + context.strokeStyle = gradient; + context.beginPath(); + context.moveTo(shift, this.edge); + context.lineTo(shift, this.height() - this.edge - shift); + context.stroke(); + + context.shadowOffsetX = 0; + context.shadowOffsetY = 0; + context.shadowBlur = 0; + + gradient = context.createLinearGradient( + 0, + this.height() - this.edge, + 0, + this.height() + ); + gradient.addColorStop(0, this.cachedClrBright); + gradient.addColorStop(1, this.cachedClr); + context.strokeStyle = gradient; + context.beginPath(); + context.moveTo(this.edge, this.height() - shift); + context.lineTo(this.width() - this.edge, this.height() - shift); + context.stroke(); + + gradient = context.createLinearGradient( + this.width() - this.edge, + 0, + this.width(), + 0 + ); + gradient.addColorStop(0, this.cachedClrBright); + gradient.addColorStop(1, this.cachedClr); + context.strokeStyle = gradient; + context.beginPath(); + context.moveTo(this.width() - shift, this.edge); + context.lineTo(this.width() - shift, this.height() - this.edge); + context.stroke(); + +}; + +InputSlotMorph.prototype.drawRoundBorder = function (context) { + var shift = this.edge * 0.5, + r = (this.height() - (this.edge * 2)) / 2, + start, + end, + gradient; + + context.lineWidth = this.edge; + context.lineJoin = 'round'; + context.lineCap = 'round'; + + // straight top edge: + start = r + this.edge; + end = this.width() - r - this.edge; + if (end > start) { + + context.shadowOffsetX = shift; + context.shadowOffsetY = shift; + context.shadowBlur = this.edge; + context.shadowColor = this.color.darker(80).toString(); + + gradient = context.createLinearGradient( + 0, + 0, + 0, + this.edge + ); + gradient.addColorStop(0, this.cachedClr); + gradient.addColorStop(1, this.cachedClrDark); + context.strokeStyle = gradient; + context.beginPath(); + + context.moveTo(start, shift); + context.lineTo(end, shift); + context.stroke(); + + context.shadowOffsetX = 0; + context.shadowOffsetY = 0; + context.shadowBlur = 0; + } + + // straight bottom edge: + gradient = context.createLinearGradient( + 0, + this.height() - this.edge, + 0, + this.height() + ); + gradient.addColorStop(0, this.cachedClrBright); + gradient.addColorStop(1, this.cachedClr); + context.strokeStyle = gradient; + context.beginPath(); + context.moveTo(r + this.edge, this.height() - shift); + context.lineTo(this.width() - r - this.edge, this.height() - shift); + context.stroke(); + + r = this.height() / 2; + + context.shadowOffsetX = shift; + context.shadowOffsetY = shift; + context.shadowBlur = this.edge; + context.shadowColor = this.color.darker(80).toString(); + + // top edge: left corner + gradient = context.createRadialGradient( + r, + r, + r - this.edge, + r, + r, + r + ); + gradient.addColorStop(1, this.cachedClr); + gradient.addColorStop(0, this.cachedClrDark); + context.strokeStyle = gradient; + context.beginPath(); + context.arc( + r, + r, + r - shift, + radians(180), + radians(270), + false + ); + + context.stroke(); + + context.shadowOffsetX = 0; + context.shadowOffsetY = 0; + context.shadowBlur = 0; + + // bottom edge: right corner + gradient = context.createRadialGradient( + this.width() - r, + r, + r - this.edge, + this.width() - r, + r, + r + ); + gradient.addColorStop(1, this.cachedClr); + gradient.addColorStop(0, this.cachedClrBright); + context.strokeStyle = gradient; + context.beginPath(); + context.arc( + this.width() - r, + r, + r - shift, + radians(0), + radians(90), + false + ); + context.stroke(); +}; + +// TemplateSlotMorph /////////////////////////////////////////////////// + +/* + I am a reporter block template sitting on a pedestal. + My block spec is + + %t - template + + evaluate returns the embedded reporter template's label string +*/ + +// TemplateSlotMorph inherits from ArgMorph: + +TemplateSlotMorph.prototype = new ArgMorph(); +TemplateSlotMorph.prototype.constructor = TemplateSlotMorph; +TemplateSlotMorph.uber = ArgMorph.prototype; + +// TemplateSlotMorph instance creation: + +function TemplateSlotMorph(name) { + this.init(name); +} + +TemplateSlotMorph.prototype.init = function (name) { + var template = new ReporterBlockMorph(); + this.labelString = name || ''; + template.isDraggable = false; + template.isTemplate = true; + if (modules.objects !== undefined) { + template.color = SpriteMorph.prototype.blockColor.variables; + template.category = 'variables'; + } else { + template.color = new Color(243, 118, 29); + template.category = null; + } + template.setSpec(this.labelString); + template.selector = 'reportGetVar'; + TemplateSlotMorph.uber.init.call(this); + this.add(template); + this.fixLayout(); + this.isDraggable = false; + this.isStatic = true; // I cannot be exchanged +}; + +// TemplateSlotMorph accessing: + +TemplateSlotMorph.prototype.getSpec = function () { + return '%t'; +}; + +TemplateSlotMorph.prototype.template = function () { + return this.children[0]; +}; + +TemplateSlotMorph.prototype.contents = function () { + return this.template().blockSpec; +}; + +TemplateSlotMorph.prototype.setContents = function (aString) { + var tmp = this.template(); + tmp.setSpec(aString); + tmp.fixBlockColor(); // fix zebra coloring + tmp.fixLabelColor(); +}; + +// TemplateSlotMorph evaluating: + +TemplateSlotMorph.prototype.evaluate = function () { + return this.contents(); +}; + +// TemplateSlotMorph layout: + +TemplateSlotMorph.prototype.fixLayout = function () { + var template = this.template(); + this.setExtent(template.extent().add(this.edge * 2 + 2)); + template.setPosition(this.position().add(this.edge + 1)); + if (this.parent) { + if (this.parent.fixLayout) { + this.parent.fixLayout(); + } + } +}; + +// TemplateSlotMorph drawing: + +TemplateSlotMorph.prototype.drawNew = function () { + var context; + if (this.parent instanceof Morph) { + this.color = this.parent.color.copy(); + } + this.cachedClr = this.color.toString(); + this.cachedClrBright = this.bright(); + this.cachedClrDark = this.dark(); + this.image = newCanvas(this.extent()); + context = this.image.getContext('2d'); + context.fillStyle = this.cachedClr; + this.drawRounded(context); +}; + +TemplateSlotMorph.prototype.drawRounded = ReporterBlockMorph + .prototype.drawRounded; + +// BooleanSlotMorph //////////////////////////////////////////////////// + +/* + I am a diamond-shaped argument slot. + My block spec is + + %b - Boolean + %boolUE - Boolean unevaluated + + evaluate returns null +*/ + +// BooleanSlotMorph inherits from ArgMorph: + +BooleanSlotMorph.prototype = new ArgMorph(); +BooleanSlotMorph.prototype.constructor = BooleanSlotMorph; +BooleanSlotMorph.uber = ArgMorph.prototype; + +// BooleanSlotMorph instance creation: + +function BooleanSlotMorph() { + this.init(); +} + +BooleanSlotMorph.prototype.init = function () { + BooleanSlotMorph.uber.init.call(this); +}; + +BooleanSlotMorph.prototype.getSpec = function () { + return '%b'; +}; + +// BooleanSlotMorph drawing: + +BooleanSlotMorph.prototype.drawNew = function () { + var context; + this.silentSetExtent(new Point( + (this.fontSize + this.edge * 2) * 2, + this.fontSize + this.edge * 2 + )); + if (this.parent) { + this.color = this.parent.color; + } + this.cachedClr = this.color.toString(); + this.cachedClrBright = this.bright(); + this.cachedClrDark = this.dark(); + this.image = newCanvas(this.extent()); + context = this.image.getContext('2d'); + this.drawDiamond(context, true); +}; + +BooleanSlotMorph.prototype.drawDiamond = function (context) { + var w = this.width(), + h = this.height(), + r = h / 2, + shift = this.edge / 2, + gradient; + + // draw the 'flat' shape: + context.fillStyle = this.color.darker(25).toString(); + context.beginPath(); + + context.moveTo(0, r); + context.lineTo(r, 0); + context.lineTo(w - r, 0); + context.lineTo(w, r); + context.lineTo(w - r, h); + context.lineTo(r, h); + + context.closePath(); + context.fill(); + + if (MorphicPreferences.isFlat) {return; } + + // add 3D-Effect: + context.lineWidth = this.edge; + context.lineJoin = 'round'; + context.lineCap = 'round'; + + context.shadowOffsetX = shift; + context.shadowBlur = shift; + context.shadowColor = 'black'; + + // top edge: left corner + gradient = context.createLinearGradient( + 0, + r, + this.edge * 0.6, + r + (this.edge * 0.6) + ); + gradient.addColorStop(1, this.cachedClrDark); + gradient.addColorStop(0, this.cachedClr); + context.strokeStyle = gradient; + context.beginPath(); + context.moveTo(shift, r); + context.lineTo(r, shift); + context.closePath(); + context.stroke(); + + // top edge: straight line + context.shadowOffsetX = 0; + context.shadowOffsetY = shift; + context.shadowBlur = this.edge; + + gradient = context.createLinearGradient( + 0, + 0, + 0, + this.edge + ); + gradient.addColorStop(1, this.cachedClrDark); + gradient.addColorStop(0, this.cachedClr); + context.strokeStyle = gradient; + context.beginPath(); + context.moveTo(r, shift); + context.lineTo(w - r, shift); + context.closePath(); + context.stroke(); + + context.shadowOffsetY = 0; + context.shadowBlur = 0; + + // bottom edge: right corner + gradient = context.createLinearGradient( + w - r - (this.edge * 0.6), + h - (this.edge * 0.6), + w - r, + h + ); + gradient.addColorStop(1, this.cachedClr); + gradient.addColorStop(0, this.cachedClrBright); + context.strokeStyle = gradient; + context.beginPath(); + context.moveTo(w - r, h - shift); + context.lineTo(w - shift, r); + context.closePath(); + context.stroke(); + + // bottom edge: straight line + gradient = context.createLinearGradient( + 0, + h - this.edge, + 0, + h + ); + gradient.addColorStop(1, this.cachedClr); + gradient.addColorStop(0, this.cachedClrBright); + context.strokeStyle = gradient; + context.beginPath(); + context.moveTo(r, h - shift); + context.lineTo(w - r - shift, h - shift); + context.closePath(); + context.stroke(); +}; + +// BooleanSlotMorph implicit formal parameters: + +BooleanSlotMorph.prototype.isEmptySlot = function () { + return true; +}; + +// ArrowMorph ////////////////////////////////////////////////////////// + +/* + I am a triangular arrow shape, for use in drop-down menus etc. + My orientation is governed by my 'direction' property, which can be + 'down', 'up', 'left' or 'right'. +*/ + +// ArrowMorph inherits from Morph: + +ArrowMorph.prototype = new Morph(); +ArrowMorph.prototype.constructor = ArrowMorph; +ArrowMorph.uber = Morph.prototype; + +// ArrowMorph instance creation: + +function ArrowMorph(direction, size, padding, color) { + this.init(direction, size, padding, color); +} + +ArrowMorph.prototype.init = function (direction, size, padding, color) { + this.direction = direction || 'down'; + this.size = size || ((size === 0) ? 0 : 50); + this.padding = padding || 0; + + ArrowMorph.uber.init.call(this); + this.color = color || new Color(0, 0, 0); + this.setExtent(new Point(this.size, this.size)); +}; + +ArrowMorph.prototype.setSize = function (size) { + var min = Math.max(size, 1); + this.size = size; + this.setExtent(new Point(min, min)); +}; + +// ArrowMorph displaying: + +ArrowMorph.prototype.drawNew = function () { + // initialize my surface property + this.image = newCanvas(this.extent()); + var context = this.image.getContext('2d'), + pad = this.padding, + h = this.height(), + h2 = Math.floor(h / 2), + w = this.width(), + w2 = Math.floor(w / 2); + + context.fillStyle = this.color.toString(); + context.beginPath(); + if (this.direction === 'down') { + context.moveTo(pad, h2); + context.lineTo(w - pad, h2); + context.lineTo(w2, h - pad); + } else if (this.direction === 'up') { + context.moveTo(pad, h2); + context.lineTo(w - pad, h2); + context.lineTo(w2, pad); + } else if (this.direction === 'left') { + context.moveTo(pad, h2); + context.lineTo(w2, pad); + context.lineTo(w2, h - pad); + } else { // 'right' + context.moveTo(w2, pad); + context.lineTo(w - pad, h2); + context.lineTo(w2, h - pad); + } + context.closePath(); + context.fill(); +}; + +// TextSlotMorph ////////////////////////////////////////////////////// + +/* + I am a multi-line input slot, primarily used in Snap's code-mapping + blocks. +*/ + +// TextSlotMorph inherits from InputSlotMorph: + +TextSlotMorph.prototype = new InputSlotMorph(); +TextSlotMorph.prototype.constructor = TextSlotMorph; +TextSlotMorph.uber = InputSlotMorph.prototype; + +// TextSlotMorph instance creation: + +function TextSlotMorph(text, isNumeric, choiceDict, isReadOnly) { + this.init(text, isNumeric, choiceDict, isReadOnly); +} + +TextSlotMorph.prototype.init = function ( + text, + isNumeric, + choiceDict, + isReadOnly +) { + var contents = new TextMorph(''), + arrow = new ArrowMorph( + 'down', + 0, + Math.max(Math.floor(this.fontSize / 6), 1) + ); + + contents.fontSize = this.fontSize; + contents.drawNew(); + + this.isUnevaluated = false; + this.choices = choiceDict || null; // object, function or selector + this.oldContentsExtent = contents.extent(); + this.isNumeric = isNumeric || false; + this.isReadOnly = isReadOnly || false; + this.minWidth = 0; // can be chaged for text-type inputs ("landscape") + this.constant = null; + + InputSlotMorph.uber.init.call(this); + this.color = new Color(255, 255, 255); + this.add(contents); + this.add(arrow); + contents.isEditable = true; + contents.isDraggable = false; + contents.enableSelecting(); + this.setContents(text); + +}; + +// TextSlotMorph accessing: + +TextSlotMorph.prototype.getSpec = function () { + if (this.isNumeric) { + return '%mln'; + } + return '%mlt'; // default +}; + +TextSlotMorph.prototype.contents = function () { + return detect( + this.children, + function (child) { + return (child instanceof TextMorph); + } + ); +}; + +// TextSlotMorph events: + +TextSlotMorph.prototype.layoutChanged = function () { + this.fixLayout(); +}; + +// SymbolMorph ////////////////////////////////////////////////////////// + +/* + I display graphical symbols, such as special letters. I have been + called into existence out of frustration about not being able to + consistently use Unicode characters to the same ends. + */ + +// SymbolMorph inherits from Morph: + +SymbolMorph.prototype = new Morph(); +SymbolMorph.prototype.constructor = SymbolMorph; +SymbolMorph.uber = Morph.prototype; + +// SymbolMorph instance creation: + +function SymbolMorph(name, size, color, shadowOffset, shadowColor) { + this.init(name, size, color, shadowOffset, shadowColor); +} + +SymbolMorph.prototype.init = function ( + name, + size, + color, + shadowOffset, + shadowColor +) { + this.isProtectedLabel = false; // participate in zebraing + this.isReadOnly = true; + this.name = name || 'square'; + this.size = size || ((size === 0) ? 0 : 50); + this.shadowOffset = shadowOffset || new Point(0, 0); + this.shadowColor = shadowColor || null; + + SymbolMorph.uber.init.call(this); + this.color = color || new Color(0, 0, 0); + this.drawNew(); +}; + +// SymbolMorph zebra coloring: + +SymbolMorph.prototype.setLabelColor = function ( + textColor, + shadowColor, + shadowOffset +) { + this.shadowOffset = shadowOffset; + this.shadowColor = shadowColor; + this.setColor(textColor); +}; + +// SymbolMorph displaying: + +SymbolMorph.prototype.drawNew = function () { + var ctx, x, y, sx, sy; + this.image = newCanvas(new Point( + this.symbolWidth() + Math.abs(this.shadowOffset.x), + this.size + Math.abs(this.shadowOffset.y) + )); + this.silentSetWidth(this.image.width); + this.silentSetHeight(this.image.height); + ctx = this.image.getContext('2d'); + sx = this.shadowOffset.x < 0 ? 0 : this.shadowOffset.x; + sy = this.shadowOffset.y < 0 ? 0 : this.shadowOffset.y; + x = this.shadowOffset.x < 0 ? Math.abs(this.shadowOffset.x) : 0; + y = this.shadowOffset.y < 0 ? Math.abs(this.shadowOffset.y) : 0; + if (this.shadowColor) { + ctx.drawImage( + this.symbolCanvasColored(this.shadowColor), + sx, + sy + ); + } + ctx.drawImage( + this.symbolCanvasColored(this.color), + x, + y + ); +}; + +SymbolMorph.prototype.symbolCanvasColored = function (aColor) { + // private + var canvas = newCanvas(new Point(this.symbolWidth(), this.size)); + switch (this.name) { + case 'square': + return this.drawSymbolStop(canvas, aColor); + case 'pointRight': + return this.drawSymbolPointRight(canvas, aColor); + case 'gears': + return this.drawSymbolGears(canvas, aColor); + case 'file': + return this.drawSymbolFile(canvas, aColor); + case 'fullScreen': + return this.drawSymbolFullScreen(canvas, aColor); + case 'normalScreen': + return this.drawSymbolNormalScreen(canvas, aColor); + case 'smallStage': + return this.drawSymbolSmallStage(canvas, aColor); + case 'normalStage': + return this.drawSymbolNormalStage(canvas, aColor); + case 'turtle': + return this.drawSymbolTurtle(canvas, aColor); + case 'stage': + return this.drawSymbolStop(canvas, aColor); + case 'turtleOutline': + return this.drawSymbolTurtleOutline(canvas, aColor); + case 'pause': + return this.drawSymbolPause(canvas, aColor); + case 'flag': + return this.drawSymbolFlag(canvas, aColor); + case 'octagon': + return this.drawSymbolOctagon(canvas, aColor); + case 'cloud': + return this.drawSymbolCloud(canvas, aColor); + case 'cloudOutline': + return this.drawSymbolCloudOutline(canvas, aColor); + case 'cloudGradient': + return this.drawSymbolCloudGradient(canvas, aColor); + case 'turnRight': + return this.drawSymbolTurnRight(canvas, aColor); + case 'turnLeft': + return this.drawSymbolTurnLeft(canvas, aColor); + case 'storage': + return this.drawSymbolStorage(canvas, aColor); + case 'poster': + return this.drawSymbolPoster(canvas, aColor); + case 'flash': + return this.drawSymbolFlash(canvas, aColor); + case 'brush': + return this.drawSymbolBrush(canvas, aColor); + case 'rectangle': + return this.drawSymbolRectangle(canvas, aColor); + case 'rectangleSolid': + return this.drawSymbolRectangleSolid(canvas, aColor); + case 'circle': + return this.drawSymbolCircle(canvas, aColor); + case 'circleSolid': + return this.drawSymbolCircleSolid(canvas, aColor); + case 'line': + return this.drawSymbolLine(canvas, aColor); + case 'crosshairs': + return this.drawSymbolCrosshairs(canvas, aColor); + case 'paintbucket': + return this.drawSymbolPaintbucket(canvas, aColor); + case 'eraser': + return this.drawSymbolEraser(canvas, aColor); + case 'pipette': + return this.drawSymbolPipette(canvas, aColor); + default: + return canvas; + } +}; + +SymbolMorph.prototype.symbolWidth = function () { + // private + var size = this.size; + switch (this.name) { + case 'pointRight': + return Math.sqrt(size * size - Math.pow(size / 2, 2)); + case 'flash': + case 'file': + return size * 0.8; + case 'smallStage': + case 'normalStage': + return size * 1.2; + case 'turtle': + case 'turtleOutline': + case 'stage': + return size * 1.3; + case 'cloud': + case 'cloudGradient': + case 'cloudOutline': + return size * 1.6; + case 'turnRight': + case 'turnLeft': + return size / 3 * 2; + default: + return this.size; + } +}; + +SymbolMorph.prototype.drawSymbolStop = function (canvas, color) { + // answer a canvas showing a vertically centered square + var ctx = canvas.getContext('2d'); + + ctx.fillStyle = color.toString(); + ctx.fillRect(0, 0, canvas.width, canvas.height); + return canvas; +}; + +SymbolMorph.prototype.drawSymbolPointRight = function (canvas, color) { + // answer a canvas showing a right-pointing, equilateral triangle + var ctx = canvas.getContext('2d'); + + ctx.fillStyle = color.toString(); + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(canvas.width, Math.round(canvas.height / 2)); + ctx.lineTo(0, canvas.height); + ctx.lineTo(0, 0); + ctx.closePath(); + ctx.fill(); + return canvas; +}; + +SymbolMorph.prototype.drawSymbolGears = function (canvas, color) { + // answer a canvas showing gears + var ctx = canvas.getContext('2d'), + w = canvas.width, + r = w / 2, + e = w / 6; + + ctx.strokeStyle = color.toString(); + ctx.lineWidth = canvas.width / 7; + + ctx.beginPath(); + ctx.arc(r, r, w, radians(0), radians(360), true); + ctx.arc(r, r, e * 1.5, radians(0), radians(360), false); + ctx.closePath(); + ctx.clip(); + + ctx.moveTo(0, r); + ctx.lineTo(w, r); + ctx.stroke(); + + ctx.moveTo(r, 0); + ctx.lineTo(r, w); + ctx.stroke(); + + ctx.moveTo(e, e); + ctx.lineTo(w - e, w - e); + ctx.stroke(); + + ctx.moveTo(w - e, e); + ctx.lineTo(e, w - e); + ctx.stroke(); + + return canvas; +}; + +SymbolMorph.prototype.drawSymbolFile = function (canvas, color) { + // answer a canvas showing a page symbol + var ctx = canvas.getContext('2d'), + w = Math.min(canvas.width, canvas.height) / 2; + + ctx.fillStyle = color.toString(); + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(w, 0); + ctx.lineTo(w, w); + ctx.lineTo(canvas.width, w); + ctx.lineTo(canvas.width, canvas.height); + ctx.lineTo(0, canvas.height); + ctx.closePath(); + ctx.fill(); + + ctx.fillStyle = color.darker(25).toString(); + ctx.beginPath(); + ctx.moveTo(w, 0); + ctx.lineTo(canvas.width, w); + ctx.lineTo(w, w); + ctx.lineTo(w, 0); + ctx.closePath(); + ctx.fill(); + + return canvas; +}; + +SymbolMorph.prototype.drawSymbolFullScreen = function (canvas, color) { + // answer a canvas showing two arrows pointing diagonally outwards + var ctx = canvas.getContext('2d'), + h = canvas.height, + c = canvas.width / 2, + off = canvas.width / 20, + w = canvas.width / 2; + + ctx.strokeStyle = color.toString(); + ctx.lineWidth = canvas.width / 5; + ctx.moveTo(c - off, c + off); + ctx.lineTo(0, h); + ctx.stroke(); + + ctx.strokeStyle = color.toString(); + ctx.lineWidth = canvas.width / 5; + ctx.moveTo(c + off, c - off); + ctx.lineTo(h, 0); + ctx.stroke(); + + ctx.fillStyle = color.toString(); + ctx.beginPath(); + ctx.moveTo(0, h); + ctx.lineTo(0, h - w); + ctx.lineTo(w, h); + ctx.closePath(); + ctx.fill(); + + ctx.fillStyle = color.toString(); + ctx.beginPath(); + ctx.moveTo(h, 0); + ctx.lineTo(h - w, 0); + ctx.lineTo(h, w); + ctx.closePath(); + ctx.fill(); + + return canvas; +}; + +SymbolMorph.prototype.drawSymbolNormalScreen = function (canvas, color) { + // answer a canvas showing two arrows pointing diagonally inwards + var ctx = canvas.getContext('2d'), + h = canvas.height, + c = canvas.width / 2, + off = canvas.width / 20, + w = canvas.width; + + ctx.strokeStyle = color.toString(); + ctx.lineWidth = canvas.width / 5; + ctx.moveTo(c - off * 3, c + off * 3); + ctx.lineTo(0, h); + ctx.stroke(); + + ctx.strokeStyle = color.toString(); + ctx.lineWidth = canvas.width / 5; + ctx.moveTo(c + off * 3, c - off * 3); + ctx.lineTo(h, 0); + ctx.stroke(); + + ctx.fillStyle = color.toString(); + ctx.beginPath(); + ctx.moveTo(c + off, c - off); + ctx.lineTo(w, c - off); + ctx.lineTo(c + off, 0); + ctx.closePath(); + ctx.fill(); + + ctx.fillStyle = color.toString(); + ctx.beginPath(); + ctx.moveTo(c - off, c + off); + ctx.lineTo(0, c + off); + ctx.lineTo(c - off, w); + ctx.closePath(); + ctx.fill(); + + return canvas; +}; + +SymbolMorph.prototype.drawSymbolSmallStage = function (canvas, color) { + // answer a canvas showing a stage toggling symbol + var ctx = canvas.getContext('2d'), + w = canvas.width, + h = canvas.height, + w2 = w / 2, + h2 = h / 2; + + ctx.fillStyle = color.darker(40).toString(); + ctx.fillRect(0, 0, w, h); + + ctx.fillStyle = color.toString(); + ctx.fillRect(w2, 0, w2, h2); + + return canvas; +}; + +SymbolMorph.prototype.drawSymbolNormalStage = function (canvas, color) { + // answer a canvas showing a stage toggling symbol + var ctx = canvas.getContext('2d'), + w = canvas.width, + h = canvas.height, + w2 = w / 2, + h2 = h / 2; + + ctx.fillStyle = color.toString(); + ctx.fillRect(0, 0, w, h); + + ctx.fillStyle = color.darker(25).toString(); + ctx.fillRect(w2, 0, w2, h2); + + return canvas; +}; + +SymbolMorph.prototype.drawSymbolTurtle = function (canvas, color) { + // answer a canvas showing a turtle + var ctx = canvas.getContext('2d'); + + ctx.fillStyle = color.toString(); + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(canvas.width, canvas.height / 2); + ctx.lineTo(0, canvas.height); + ctx.lineTo(canvas.height / 2, canvas.height / 2); + ctx.closePath(); + ctx.fill(); + return canvas; +}; + +SymbolMorph.prototype.drawSymbolTurtleOutline = function (canvas, color) { + // answer a canvas showing a turtle + var ctx = canvas.getContext('2d'); + + ctx.strokeStyle = color.toString(); + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(canvas.width, canvas.height / 2); + ctx.lineTo(0, canvas.height); + ctx.lineTo(canvas.height / 2, canvas.height / 2); + ctx.closePath(); + ctx.stroke(); + + return canvas; +}; + +SymbolMorph.prototype.drawSymbolPause = function (canvas, color) { + // answer a canvas showing two parallel rectangles + var ctx = canvas.getContext('2d'), + w = canvas.width / 5; + + ctx.fillStyle = color.toString(); + ctx.fillRect(0, 0, w * 2, canvas.height); + ctx.fillRect(w * 3, 0, w * 2, canvas.height); + return canvas; +}; + +SymbolMorph.prototype.drawSymbolFlag = function (canvas, color) { + // answer a canvas showing a flag + var ctx = canvas.getContext('2d'), + w = canvas.width, + l = Math.max(w / 12, 1), + h = canvas.height; + + ctx.lineWidth = l; + ctx.strokeStyle = color.toString(); + ctx.beginPath(); + ctx.moveTo(l / 2, 0); + ctx.lineTo(l / 2, canvas.height); + ctx.stroke(); + + ctx.lineWidth = h / 2; + ctx.beginPath(); + ctx.moveTo(0, h / 4); + ctx.bezierCurveTo( + w * 0.8, + h / 4, + w * 0.1, + h * 0.5, + w, + h * 0.5 + ); + ctx.stroke(); + + return canvas; +}; + +SymbolMorph.prototype.drawSymbolOctagon = function (canvas, color) { + // answer a canvas showing an octagon + var ctx = canvas.getContext('2d'), + side = canvas.width, + vert = (side - (side * 0.383)) / 2; + + ctx.fillStyle = color.toString(); + ctx.beginPath(); + ctx.moveTo(vert, 0); + ctx.lineTo(side - vert, 0); + ctx.lineTo(side, vert); + ctx.lineTo(side, side - vert); + ctx.lineTo(side - vert, side); + ctx.lineTo(vert, side); + ctx.lineTo(0, side - vert); + ctx.lineTo(0, vert); + ctx.closePath(); + ctx.fill(); + + return canvas; +}; + +SymbolMorph.prototype.drawSymbolCloud = function (canvas, color) { + // answer a canvas showing an cloud + var ctx = canvas.getContext('2d'), + w = canvas.width, + h = canvas.height, + r1 = h * 2 / 5, + r2 = h / 4, + r3 = h * 3 / 10, + r4 = h / 5; + + ctx.fillStyle = color.toString(); + ctx.beginPath(); + ctx.arc(r2, h - r2, r2, radians(90), radians(259), false); + ctx.arc(w / 20 * 5, h / 9 * 4, r4, radians(165), radians(300), false); + ctx.arc(w / 20 * 11, r1, r1, radians(200), radians(357), false); + ctx.arc(w - r3, h - r3, r3, radians(269), radians(90), false); + ctx.closePath(); + ctx.fill(); + + return canvas; +}; + +SymbolMorph.prototype.drawSymbolCloudGradient = function (canvas, color) { + // answer a canvas showing an cloud + var ctx = canvas.getContext('2d'), + gradient, + w = canvas.width, + h = canvas.height, + r1 = h * 2 / 5, + r2 = h / 4, + r3 = h * 3 / 10, + r4 = h / 5; + + gradient = ctx.createRadialGradient( + 0, + 0, + 0, + 0, + 0, + w + ); + gradient.addColorStop(0, color.lighter(25).toString()); + gradient.addColorStop(1, color.darker(25).toString()); + ctx.fillStyle = gradient; + ctx.beginPath(); + ctx.arc(r2, h - r2, r2, radians(90), radians(259), false); + ctx.arc(w / 20 * 5, h / 9 * 4, r4, radians(165), radians(300), false); + ctx.arc(w / 20 * 11, r1, r1, radians(200), radians(357), false); + ctx.arc(w - r3, h - r3, r3, radians(269), radians(90), false); + ctx.closePath(); + ctx.fill(); + + return canvas; +}; + +SymbolMorph.prototype.drawSymbolCloudOutline = function (canvas, color) { + // answer a canvas showing an cloud + var ctx = canvas.getContext('2d'), + w = canvas.width, + h = canvas.height, + r1 = h * 2 / 5, + r2 = h / 4, + r3 = h * 3 / 10, + r4 = h / 5; + + ctx.strokeStyle = color.toString(); + ctx.beginPath(); + ctx.arc(r2 + 1, h - r2 - 1, r2, radians(90), radians(259), false); + ctx.arc(w / 20 * 5, h / 9 * 4, r4, radians(165), radians(300), false); + ctx.arc(w / 20 * 11, r1 + 1, r1, radians(200), radians(357), false); + ctx.arc(w - r3 - 1, h - r3 - 1, r3, radians(269), radians(90), false); + ctx.closePath(); + ctx.stroke(); + + return canvas; +}; + +SymbolMorph.prototype.drawSymbolTurnRight = function (canvas, color) { + // answer a canvas showing a right-turning arrow + var ctx = canvas.getContext('2d'), + w = canvas.width, + l = Math.max(w / 10, 1), + r = w / 2; + + ctx.lineWidth = l; + ctx.strokeStyle = color.toString(); + ctx.beginPath(); + ctx.arc(r, r * 2, r - l / 2, radians(0), radians(-90), false); + ctx.stroke(); + + ctx.fillStyle = color.toString(); + ctx.beginPath(); + ctx.moveTo(w, r); + ctx.lineTo(r, 0); + ctx.lineTo(r, r * 2); + ctx.closePath(); + ctx.fill(); + + return canvas; +}; + +SymbolMorph.prototype.drawSymbolTurnLeft = function (canvas, color) { + // answer a canvas showing a left-turning arrow + var ctx = canvas.getContext('2d'), + w = canvas.width, + l = Math.max(w / 10, 1), + r = w / 2; + + ctx.lineWidth = l; + ctx.strokeStyle = color.toString(); + ctx.beginPath(); + ctx.arc(r, r * 2, r - l / 2, radians(180), radians(-90), true); + ctx.stroke(); + + ctx.fillStyle = color.toString(); + ctx.beginPath(); + ctx.moveTo(0, r); + ctx.lineTo(r, 0); + ctx.lineTo(r, r * 2); + ctx.closePath(); + ctx.fill(); + + return canvas; +}; + +SymbolMorph.prototype.drawSymbolStorage = function (canvas, color) { + // answer a canvas showing a stack of three disks + var ctx = canvas.getContext('2d'), + w = canvas.width, + h = canvas.height, + r = canvas.height, + unit = canvas.height / 11; + + function drawDisk(bottom, fillTop) { + ctx.fillStyle = color.toString(); + ctx.beginPath(); + ctx.arc(w / 2, bottom - h, r, radians(60), radians(120), false); + ctx.lineTo(0, bottom - unit * 2); + ctx.arc( + w / 2, + bottom - h - unit * 2, + r, + radians(120), + radians(60), + true + ); + ctx.closePath(); + ctx.fill(); + + ctx.fillStyle = color.darker(25).toString(); + ctx.beginPath(); + + if (fillTop) { + ctx.arc( + w / 2, + bottom - h - unit * 2, + r, + radians(120), + radians(60), + true + ); + } + + ctx.arc( + w / 2, + bottom + unit * 6 + 1, + r, + radians(60), + radians(120), + true + ); + ctx.closePath(); + + if (fillTop) { + ctx.fill(); + } else { + ctx.stroke(); + } + } + + ctx.strokeStyle = color.toString(); + drawDisk(h); + drawDisk(h - unit * 3); + drawDisk(h - unit * 6, false); + return canvas; +}; + +SymbolMorph.prototype.drawSymbolPoster = function (canvas, color) { + // answer a canvas showing a poster stand + var ctx = canvas.getContext('2d'), + w = canvas.width, + h = canvas.height, + bottom = h * 0.75, + edge = canvas.height / 5; + + ctx.fillStyle = color.toString(); + ctx.strokeStyle = color.toString(); + + ctx.lineWidth = w / 15; + ctx.moveTo(w / 2, h / 3); + ctx.lineTo(w / 6, h); + ctx.stroke(); + + ctx.moveTo(w / 2, h / 3); + ctx.lineTo(w / 2, h); + ctx.stroke(); + + ctx.moveTo(w / 2, h / 3); + ctx.lineTo(w * 5 / 6, h); + ctx.stroke(); + + ctx.fillRect(0, 0, w, bottom); + ctx.clearRect(0, bottom, w, w / 20); + + ctx.clearRect(w - edge, bottom - edge, edge + 1, edge + 1); + + ctx.fillStyle = color.darker(25).toString(); + ctx.beginPath(); + ctx.moveTo(w, bottom - edge); + ctx.lineTo(w - edge, bottom - edge); + ctx.lineTo(w - edge, bottom); + ctx.closePath(); + ctx.fill(); + + return canvas; +}; + +SymbolMorph.prototype.drawSymbolFlash = function (canvas, color) { + // answer a canvas showing a flash + var ctx = canvas.getContext('2d'), + w = canvas.width, + w3 = w / 3, + h = canvas.height, + h3 = h / 3, + off = h3 / 3; + + ctx.fillStyle = color.toString(); + ctx.beginPath(); + ctx.moveTo(w3, 0); + ctx.lineTo(0, h3); + ctx.lineTo(w3, h3); + ctx.lineTo(0, h3 * 2); + ctx.lineTo(w3, h3 * 2); + ctx.lineTo(0, h); + ctx.lineTo(w, h3 * 2 - off); + ctx.lineTo(w3 * 2, h3 * 2 - off); + ctx.lineTo(w, h3 - off); + ctx.lineTo(w3 * 2, h3 - off); + ctx.lineTo(w, 0); + ctx.closePath(); + ctx.fill(); + return canvas; +}; + +SymbolMorph.prototype.drawSymbolBrush = function (canvas, color) { + // answer a canvas showing a paintbrush + var ctx = canvas.getContext('2d'), + w = canvas.width, + h = canvas.height, + l = Math.max(w / 30, 0.5); + + ctx.fillStyle = color.toString(); + ctx.lineWidth = l * 2; + ctx.beginPath(); + ctx.moveTo(w / 8 * 3, h / 2); + ctx.quadraticCurveTo(0, h / 2, l, h - l); + ctx.quadraticCurveTo(w / 2, h, w / 2, h / 8 * 5); + ctx.closePath(); + ctx.fill(); + + ctx.lineJoin = 'round'; + ctx.lineCap = 'round'; + ctx.strokeStyle = color.toString(); + + ctx.moveTo(w / 8 * 3, h / 2); + ctx.lineTo(w * 0.75, l); + ctx.quadraticCurveTo(w, 0, w - l, h * 0.25); + ctx.stroke(); + + ctx.moveTo(w / 2, h / 8 * 5); + ctx.lineTo(w - l, h * 0.25); + ctx.stroke(); + + return canvas; +}; + +SymbolMorph.prototype.drawSymbolRectangle = function (canvas, color) { + // answer a canvas showing a rectangle + var ctx = canvas.getContext('2d'), + w = canvas.width, + h = canvas.width, + l = Math.max(w / 20, 0.5); + + ctx.strokeStyle = color.toString(); + ctx.lineWidth = l * 2; + ctx.beginPath(); + ctx.moveTo(l, l); + ctx.lineTo(w - l, l); + ctx.lineTo(w - l, h - l); + ctx.lineTo(l, h - l); + ctx.closePath(); + ctx.stroke(); + return canvas; +}; + +SymbolMorph.prototype.drawSymbolRectangleSolid = function (canvas, color) { + // answer a canvas showing a solid rectangle + var ctx = canvas.getContext('2d'), + w = canvas.width, + h = canvas.width; + + ctx.fillStyle = color.toString(); + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(w, 0); + ctx.lineTo(w, h); + ctx.lineTo(0, h); + ctx.closePath(); + ctx.fill(); + return canvas; +}; + +SymbolMorph.prototype.drawSymbolCircle = function (canvas, color) { + // answer a canvas showing a circle + var ctx = canvas.getContext('2d'), + w = canvas.width, + l = Math.max(w / 20, 0.5); + + ctx.strokeStyle = color.toString(); + ctx.lineWidth = l * 2; + ctx.arc(w / 2, w / 2, w / 2 - l, radians(0), radians(360), false); + ctx.stroke(); + return canvas; +}; + +SymbolMorph.prototype.drawSymbolCircleSolid = function (canvas, color) { + // answer a canvas showing a solid circle + var ctx = canvas.getContext('2d'), + w = canvas.width; + + ctx.fillStyle = color.toString(); + ctx.arc(w / 2, w / 2, w / 2, radians(0), radians(360), false); + ctx.fill(); + return canvas; +}; + +SymbolMorph.prototype.drawSymbolLine = function (canvas, color) { + // answer a canvas showing a diagonal line + var ctx = canvas.getContext('2d'), + w = canvas.width, + h = canvas.height, + l = Math.max(w / 20, 0.5); + + ctx.strokeStyle = color.toString(); + ctx.lineWidth = l * 2; + ctx.lineCap = 'round'; + ctx.moveTo(l, l); + ctx.lineTo(w - l, h - l); + ctx.stroke(); + return canvas; +}; + +SymbolMorph.prototype.drawSymbolCrosshairs = function (canvas, color) { + // answer a canvas showing a crosshairs + var ctx = canvas.getContext('2d'), + w = canvas.width, + h = canvas.height, + l = 0.5; + + ctx.strokeStyle = color.toString(); + ctx.lineWidth = l * 2; + ctx.moveTo(l, h / 2); + ctx.lineTo(w - l, h / 2); + ctx.stroke(); + ctx.moveTo(w / 2, l); + ctx.lineTo(w / 2, h - l); + ctx.stroke(); + ctx.moveTo(w / 2, h / 2); + ctx.arc(w / 2, w / 2, w / 3 - l, radians(0), radians(360), false); + ctx.stroke(); + return canvas; +}; + +SymbolMorph.prototype.drawSymbolPaintbucket = function (canvas, color) { + // answer a canvas showing a paint bucket + var ctx = canvas.getContext('2d'), + w = canvas.width, + h = canvas.height, + n = canvas.width / 5, + l = Math.max(w / 30, 0.5); + + ctx.strokeStyle = color.toString(); + ctx.lineWidth = l * 2; + ctx.beginPath(); + ctx.moveTo(n * 2, n); + ctx.lineTo(n * 4, n * 3); + ctx.lineTo(n * 3, n * 4); + ctx.quadraticCurveTo(n * 2, h, n, n * 4); + ctx.quadraticCurveTo(0, n * 3, n, n * 2); + ctx.closePath(); + ctx.stroke(); + + ctx.lineWidth = l; + ctx.moveTo(n * 2, n * 2.5); + ctx.arc(n * 2, n * 2.5, l, radians(0), radians(360), false); + ctx.stroke(); + + ctx.moveTo(n * 2, n * 2.5); + ctx.lineTo(n * 2, n / 2 + l); + ctx.stroke(); + + ctx.arc(n * 1.5, n / 2 + l, n / 2, radians(0), radians(180), true); + ctx.stroke(); + + ctx.moveTo(n, n / 2 + l); + ctx.lineTo(n, n * 2); + ctx.stroke(); + + ctx.fillStyle = color.toString(); + ctx.beginPath(); + ctx.moveTo(n * 3.5, n * 3.5); + ctx.quadraticCurveTo(w, n * 3.5, w - l, h); + ctx.lineTo(w, h); + ctx.quadraticCurveTo(w, n * 2, n * 2.5, n * 1.5); + ctx.lineTo(n * 4, n * 3); + ctx.closePath(); + ctx.fill(); + + return canvas; +}; + +SymbolMorph.prototype.drawSymbolEraser = function (canvas, color) { + // answer a canvas showing an eraser + var ctx = canvas.getContext('2d'), + w = canvas.width, + h = canvas.height, + n = canvas.width / 4, + l = Math.max(w / 20, 0.5); + + ctx.strokeStyle = color.toString(); + ctx.lineWidth = l * 2; + ctx.beginPath(); + ctx.moveTo(n * 3, l); + ctx.lineTo(l, n * 3); + ctx.quadraticCurveTo(n, h, n * 2, n * 3); + ctx.lineTo(w - l, n); + ctx.closePath(); + ctx.stroke(); + + ctx.fillStyle = color.toString(); + ctx.beginPath(); + ctx.moveTo(n * 3, 0); + ctx.lineTo(n * 1.5, n * 1.5); + ctx.lineTo(n * 2.5, n * 2.5); + ctx.lineTo(w, n); + ctx.closePath(); + ctx.fill(); + + return canvas; +}; + +SymbolMorph.prototype.drawSymbolPipette = function (canvas, color) { + // answer a canvas showing an eyedropper + var ctx = canvas.getContext('2d'), + w = canvas.width, + h = canvas.height, + n = canvas.width / 4, + n2 = n / 2, + l = Math.max(w / 20, 0.5); + + ctx.strokeStyle = color.toString(); + ctx.lineWidth = l * 2; + ctx.beginPath(); + ctx.moveTo(l, h - l); + ctx.quadraticCurveTo(n2, h - n2, n2, h - n); + ctx.lineTo(n * 2, n * 1.5); + ctx.stroke(); + + ctx.beginPath(); + ctx.moveTo(l, h - l); + ctx.quadraticCurveTo(n2, h - n2, n, h - n2); + ctx.lineTo(n * 2.5, n * 2); + ctx.stroke(); + + ctx.fillStyle = color.toString(); + ctx.arc(n * 3, n, n - l, radians(0), radians(360), false); + ctx.fill(); + + ctx.beginPath(); + ctx.moveTo(n * 2, n); + ctx.lineTo(n * 3, n * 2); + ctx.stroke(); + + return canvas; +}; + +// ColorSlotMorph ////////////////////////////////////////////////////// + +/* + I am an editable input slot for a color. Users can edit my color by + clicking on me, in which case a display a color gradient palette + and let the user select another color. Note that the user isn't + restricted to selecting a color from the palette, any color from + anywhere within the World can be chosen. + + my block spec is %clr + + evaluate() returns my color +*/ + +// ColorSlotMorph inherits from ArgMorph: + +ColorSlotMorph.prototype = new ArgMorph(); +ColorSlotMorph.prototype.constructor = ColorSlotMorph; +ColorSlotMorph.uber = ArgMorph.prototype; + +// ColorSlotMorph instance creation: + +function ColorSlotMorph(clr) { + this.init(clr); +} + +ColorSlotMorph.prototype.init = function (clr) { + ColorSlotMorph.uber.init.call(this); + this.setColor(clr || new Color(145, 26, 68)); +}; + +ColorSlotMorph.prototype.getSpec = function () { + return '%clr'; +}; + +// ColorSlotMorph color sensing: + +ColorSlotMorph.prototype.getUserColor = function () { + var myself = this, + world = this.world(), + hand = world.hand, + posInDocument = getDocumentPositionOf(world.worldCanvas), + mouseMoveBak = hand.processMouseMove, + mouseDownBak = hand.processMouseDown, + mouseUpBak = hand.processMouseUp, + pal = new ColorPaletteMorph(null, new Point( + this.fontSize * 16, + this.fontSize * 10 + )); + world.add(pal); + pal.setPosition(this.bottomLeft().add(new Point(0, this.edge))); + + hand.processMouseMove = function (event) { + hand.setPosition(new Point( + event.pageX - posInDocument.x, + event.pageY - posInDocument.y + )); + myself.setColor(world.getGlobalPixelColor(hand.position())); + }; + + hand.processMouseDown = nop; + + hand.processMouseUp = function () { + pal.destroy(); + hand.processMouseMove = mouseMoveBak; + hand.processMouseDown = mouseDownBak; + hand.processMouseUp = mouseUpBak; + }; +}; + +// ColorSlotMorph events: + +ColorSlotMorph.prototype.mouseClickLeft = function () { + this.getUserColor(); +}; + +// ColorSlotMorph evaluating: + +ColorSlotMorph.prototype.evaluate = function () { + return this.color; +}; + +// ColorSlotMorph drawing: + +ColorSlotMorph.prototype.drawNew = function () { + var context, borderColor, side; + + side = this.fontSize + this.edge * 2 + this.typeInPadding * 2; + this.silentSetExtent(new Point(side, side)); + + // initialize my surface property + this.image = newCanvas(this.extent()); + context = this.image.getContext('2d'); + if (this.parent) { + borderColor = this.parent.color; + } else { + borderColor = new Color(120, 120, 120); + } + context.fillStyle = this.color.toString(); + + // cache my border colors + this.cachedClr = borderColor.toString(); + this.cachedClrBright = borderColor.lighter(this.contrast) + .toString(); + this.cachedClrDark = borderColor.darker(this.contrast).toString(); + + context.fillRect( + this.edge, + this.edge, + this.width() - this.edge * 2, + this.height() - this.edge * 2 + ); + if (!MorphicPreferences.isFlat) { + this.drawRectBorder(context); + } +}; + +ColorSlotMorph.prototype.drawRectBorder = + InputSlotMorph.prototype.drawRectBorder; + +// BlockHighlightMorph ///////////////////////////////////////////////// + +// BlockHighlightMorph inherits from Morph: + +BlockHighlightMorph.prototype = new Morph(); +BlockHighlightMorph.prototype.constructor = BlockHighlightMorph; +BlockHighlightMorph.uber = Morph.prototype; + +// BlockHighlightMorph instance creation: + +function BlockHighlightMorph() { + this.init(); +} + +// MultiArgMorph /////////////////////////////////////////////////////// + +/* + I am an arity controlled list of input slots + + my block specs are + + %mult%x - where x is any single input slot + %inputs - for an additional text label 'with inputs' + + evaluation is handles by the interpreter +*/ + +// MultiArgMorph inherits from ArgMorph: + +MultiArgMorph.prototype = new ArgMorph(); +MultiArgMorph.prototype.constructor = MultiArgMorph; +MultiArgMorph.uber = ArgMorph.prototype; + +// MultiArgMorph instance creation: + +function MultiArgMorph( + slotSpec, + labelTxt, + min, + eSpec, + arrowColor, + labelColor, + shadowColor, + shadowOffset, + isTransparent +) { + this.init( + slotSpec, + labelTxt, + min, + eSpec, + arrowColor, + labelColor, + shadowColor, + shadowOffset, + isTransparent + ); +} + +MultiArgMorph.prototype.init = function ( + slotSpec, + labelTxt, + min, + eSpec, + arrowColor, + labelColor, + shadowColor, + shadowOffset, + isTransparent +) { + var label, + arrows = new FrameMorph(), + leftArrow, + rightArrow, + i; + + this.slotSpec = slotSpec || '%s'; + this.labelText = localize(labelTxt || ''); + this.minInputs = min || 0; + this.elementSpec = eSpec || null; + this.labelColor = labelColor || null; + this.shadowColor = shadowColor || null; + this.shadowOffset = shadowOffset || null; + + this.canBeEmpty = true; + MultiArgMorph.uber.init.call(this); + + // MultiArgMorphs are transparent by default b/c of zebra coloring + this.alpha = isTransparent === false ? 1 : 0; + arrows.alpha = isTransparent === false ? 1 : 0; + arrows.noticesTransparentClick = true; + this.noticesTransparentclick = true; + + // label text: + label = this.labelPart(this.labelText); + this.add(label); + label.hide(); + + // left arrow: + leftArrow = new ArrowMorph( + 'left', + this.fontSize, + Math.max(Math.floor(this.fontSize / 6), 1), + arrowColor + ); + + // right arrow: + rightArrow = new ArrowMorph( + 'right', + this.fontSize, + Math.max(Math.floor(this.fontSize / 6), 1), + arrowColor + ); + + // control panel: + arrows.add(leftArrow); + arrows.add(rightArrow); + arrows.drawNew(); + arrows.acceptsDrops = false; + + this.add(arrows); + + // create the minimum number of inputs + for (i = 0; i < this.minInputs; i += 1) { + this.addInput(); + } +}; + +MultiArgMorph.prototype.label = function () { + return this.children[0]; +}; + +MultiArgMorph.prototype.arrows = function () { + return this.children[this.children.length - 1]; +}; + +MultiArgMorph.prototype.getSpec = function () { + return '%mult' + this.slotSpec; +}; + +// MultiArgMorph defaults: + +MultiArgMorph.prototype.setContents = function (anArray) { + var inputs = this.inputs(), i; + for (i = 0; i < anArray.length; i += 1) { + if (anArray[i] !== null && (inputs[i])) { + inputs[i].setContents(anArray[i]); + } + } +}; + +// MultiArgMorph hiding and showing: + +/* + override the inherited behavior to recursively hide/show all + children, so that my instances get restored correctly when + switching back out of app mode. +*/ + +MultiArgMorph.prototype.hide = function () { + this.isVisible = false; + this.changed(); +}; + +MultiArgMorph.prototype.show = function () { + this.isVisible = true; + this.changed(); +}; + +// MultiArgMorph coloring: + +MultiArgMorph.prototype.setLabelColor = function ( + textColor, + shadowColor, + shadowOffset +) { + this.textColor = textColor; + this.shadowColor = shadowColor; + this.shadowOffset = shadowOffset; + MultiArgMorph.uber.setLabelColor.call( + this, + textColor, + shadowColor, + shadowOffset + ); +}; + +// MultiArgMorph layout: + +MultiArgMorph.prototype.fixLayout = function () { + if (this.slotSpec === '%t') { + this.isStatic = true; // in this case I cannot be exchanged + } + if (this.parent) { + var label = this.label(), shadowColor, shadowOffset; + this.color = this.parent.color; + shadowColor = this.shadowColor || + this.parent.color.darker(this.labelContrast); + shadowOffset = this.shadowOffset || label.shadowOffset; + this.arrows().color = this.color; + + if (this.labelText !== '') { + if (!label.shadowColor.eq(shadowColor)) { + label.shadowColor = shadowColor; + label.shadowOffset = shadowOffset; + label.drawNew(); + } + } + + } + this.fixArrowsLayout(); + MultiArgMorph.uber.fixLayout.call(this); + if (this.parent) { + this.parent.fixLayout(); + } +}; + +MultiArgMorph.prototype.fixArrowsLayout = function () { + var label = this.label(), + arrows = this.arrows(), + leftArrow = arrows.children[0], + rightArrow = arrows.children[1], + dim = new Point(rightArrow.width() / 2, rightArrow.height()); + if (this.inputs().length < (this.minInputs + 1)) { + label.hide(); + leftArrow.hide(); + rightArrow.setPosition( + arrows.position().subtract(new Point(dim.x, 0)) + ); + arrows.setExtent(dim); + } else { + if (this.labelText !== '') { + label.show(); + } + leftArrow.show(); + rightArrow.setPosition(leftArrow.topCenter()); + arrows.bounds.corner = rightArrow.bottomRight().copy(); + } + arrows.drawNew(); +}; + +MultiArgMorph.prototype.refresh = function () { + this.inputs().forEach(function (input) { + input.drawNew(); + }); +}; + +MultiArgMorph.prototype.drawNew = function () { + MultiArgMorph.uber.drawNew.call(this); + this.refresh(); +}; + +// MultiArgMorph arity control: + +MultiArgMorph.prototype.addInput = function (contents) { + var i, name, + newPart = this.labelPart(this.slotSpec), + idx = this.children.length - 1; + // newPart.alpha = this.alpha ? 1 : (1 - this.alpha) / 2; + if (contents) { + newPart.setContents(contents); + } else if (this.elementSpec === '%scriptVars') { + name = ''; + i = idx; + while (i > 0) { + name = String.fromCharCode(97 + (i - 1) % 26) + name; + i = Math.floor((i - 1) / 26); + } + newPart.setContents(name); + } else if (contains(['%parms', '%ringparms'], this.elementSpec)) { + newPart.setContents('#' + idx); + } + newPart.parent = this; + this.children.splice(idx, 0, newPart); + newPart.drawNew(); + this.fixLayout(); +}; + +MultiArgMorph.prototype.removeInput = function () { + var oldPart, scripts; + if (this.children.length > 1) { + oldPart = this.children[this.children.length - 2]; + this.removeChild(oldPart); + if (oldPart instanceof BlockMorph) { + scripts = this.parentThatIsA(ScriptsMorph); + if (scripts) { + scripts.add(oldPart); + } + } + } + this.fixLayout(); +}; + +// MultiArgMorph events: + +MultiArgMorph.prototype.mouseClickLeft = function (pos) { + // if the key is pressed, repeat action 5 times + var arrows = this.arrows(), + leftArrow = arrows.children[0], + rightArrow = arrows.children[1], + repetition = this.world().currentKey === 16 ? 3 : 1, + i; + + this.startLayout(); + if (rightArrow.bounds.containsPoint(pos)) { + for (i = 0; i < repetition; i += 1) { + if (rightArrow.isVisible) { + this.addInput(); + } + } + } else if (leftArrow.bounds.containsPoint(pos)) { + for (i = 0; i < repetition; i += 1) { + if (leftArrow.isVisible) { + this.removeInput(); + } + } + } else { + this.escalateEvent('mouseClickLeft', pos); + } + this.endLayout(); +}; + +// MultiArgMorph menu: + +MultiArgMorph.prototype.userMenu = function () { + var menu = new MenuMorph(this), + block = this.parentThatIsA(BlockMorph), + key = '', + myself = this; + if (!StageMorph.prototype.enableCodeMapping) { + return this.parent.userMenu(); + } + if (block) { + if (block instanceof RingMorph) { + key = 'parms_'; + } else if (block.selector === 'doDeclareVariables') { + key = 'tempvars_'; + } + } + menu.addItem( + 'code list mapping...', + function () {myself.mapCodeList(key); } + ); + menu.addItem( + 'code item mapping...', + function () {myself.mapCodeItem(key); } + ); + menu.addItem( + 'code delimiter mapping...', + function () {myself.mapCodeDelimiter(key); } + ); + return menu; +}; + +// MultiArgMorph code mapping + +/* + code mapping lets you use blocks to generate arbitrary text-based + source code that can be exported and compiled / embedded elsewhere, + it's not part of Snap's evaluator and not needed for Snap itself +*/ + +MultiArgMorph.prototype.mapCodeDelimiter = function (key) { + this.mapToCode(key + 'delim', 'list item delimiter'); +}; + +MultiArgMorph.prototype.mapCodeList = function (key) { + this.mapToCode(key + 'list', 'list contents <#1>'); +}; + +MultiArgMorph.prototype.mapCodeItem = function (key) { + this.mapToCode(key + 'item', 'list item <#1>'); +}; + +MultiArgMorph.prototype.mapToCode = function (key, label) { + // private - open a dialog box letting the user map code via the GUI + new DialogBoxMorph( + this, + function (code) { + StageMorph.prototype.codeMappings[key] = code; + }, + this + ).promptCode( + 'Code mapping - ' + label, + StageMorph.prototype.codeMappings[key] || '', + this.world() + ); +}; + +MultiArgMorph.prototype.mappedCode = function (definitions) { + var block = this.parentThatIsA(BlockMorph), + key = '', + code, + items = '', + itemCode, + delim, + count = 0, + parts = []; + + if (block) { + if (block instanceof RingMorph) { + key = 'parms_'; + } else if (block.selector === 'doDeclareVariables') { + key = 'tempvars_'; + } + } + + code = StageMorph.prototype.codeMappings[key + 'list'] || '<#1>'; + itemCode = StageMorph.prototype.codeMappings[key + 'item'] || '<#1>'; + delim = StageMorph.prototype.codeMappings[key + 'delim'] || ' '; + + this.inputs().forEach(function (input) { + parts.push(itemCode.replace(/<#1>/g, input.mappedCode(definitions))); + }); + parts.forEach(function (part) { + if (count) { + items += delim; + } + items += part; + count += 1; + }); + code = code.replace(/<#1>/g, items); + return code; +}; + +// MultiArgMorph arity evaluating: + +MultiArgMorph.prototype.evaluate = function () { +/* + this is usually overridden by the interpreter. This method is only + called (and needed) for the variables menu. +*/ + var result = []; + this.inputs().forEach(function (slot) { + result.push(slot.evaluate()); + }); + return result; +}; + +MultiArgMorph.prototype.isEmptySlot = function () { + return this.canBeEmpty ? this.inputs().length === 0 : false; +}; + +// ArgLabelMorph /////////////////////////////////////////////////////// + +/* + I am a label string that is wrapped around an ArgMorph, usually + a MultiArgMorph, so to indicate that it has been replaced entirely + for an embedded reporter block + + I don't have a block spec, I get embedded automatically by the parent + block's argument replacement mechanism + + My evaluation method is the identity function, i.e. I simply pass my + input's value along. +*/ + +// ArgLabelMorph inherits from ArgMorph: + +ArgLabelMorph.prototype = new ArgMorph(); +ArgLabelMorph.prototype.constructor = ArgLabelMorph; +ArgLabelMorph.uber = ArgMorph.prototype; + +// MultiArgMorph instance creation: + +function ArgLabelMorph(argMorph, labelTxt) { + this.init(argMorph, labelTxt); +} + +ArgLabelMorph.prototype.init = function (argMorph, labelTxt) { + var label; + + this.labelText = localize(labelTxt || 'input list:'); + ArgLabelMorph.uber.init.call(this); + + this.isStatic = true; // I cannot be exchanged + + // ArgLabelMorphs are transparent + this.alpha = 0; + this.noticesTransparentclick = true; + + // label text: + label = this.labelPart(this.labelText); + this.add(label); + + // argMorph + this.add(argMorph); +}; + +ArgLabelMorph.prototype.label = function () { + return this.children[0]; +}; + +ArgLabelMorph.prototype.argMorph = function () { + return this.children[1]; +}; + +// ArgLabelMorph layout: + +ArgLabelMorph.prototype.fixLayout = function () { + var label = this.label(), + shadowColor, + shadowOffset; + + if (this.parent) { + this.color = this.parent.color; + shadowOffset = label.shadowOffset || new Point(); + + // determine the shadow color for zebra coloring: + if (shadowOffset.x < 0) { + shadowColor = this.parent.color.darker(this.labelContrast); + } else { + shadowColor = this.parent.color.lighter(this.labelContrast); + } + + if (this.labelText !== '') { + if (!label.shadowColor.eq(shadowColor)) { + label.shadowColor = shadowColor; + label.shadowOffset = shadowOffset; + label.drawNew(); + } + } + } + ArgLabelMorph.uber.fixLayout.call(this); + if (this.parent) { + this.parent.fixLayout(); + } +}; + +ArgLabelMorph.prototype.refresh = function () { + this.inputs().forEach(function (input) { + input.drawNew(); + }); +}; + +ArgLabelMorph.prototype.drawNew = function () { + ArgLabelMorph.uber.drawNew.call(this); + this.refresh(); +}; + +// ArgLabelMorph label color: + +ArgLabelMorph.prototype.setLabelColor = function ( + textColor, + shadowColor, + shadowOffset +) { + if (this.labelText !== '') { + var label = this.label(); + label.color = textColor; + label.shadowColor = shadowColor; + label.shadowOffset = shadowOffset; + label.drawNew(); + } +}; + +// ArgLabelMorph events: + +ArgLabelMorph.prototype.reactToGrabOf = function () { + if (this.parent instanceof SyntaxElementMorph) { + this.parent.revertToDefaultInput(this); + } +}; + +// ArgLabelMorph evaluating: + +ArgLabelMorph.prototype.evaluate = function () { +/* + this is usually overridden by the interpreter. This method is only + called (and needed) for the variables menu. +*/ + return this.argMorph().evaluate(); +}; + +ArgLabelMorph.prototype.isEmptySlot = function () { + return false; +}; + +// FunctionSlotMorph /////////////////////////////////////////////////// + +/* + I am an unevaluated, non-editable, rf-colored, rounded or diamond + input slot. My current (only) use is in the THE BLOCK block. + + My command spec is %f +*/ + +// FunctionSlotMorph inherits from ArgMorph: + +FunctionSlotMorph.prototype = new ArgMorph(); +FunctionSlotMorph.prototype.constructor = FunctionSlotMorph; +FunctionSlotMorph.uber = ArgMorph.prototype; + +// FunctionSlotMorph instance creation: + +function FunctionSlotMorph(isPredicate) { + this.init(isPredicate); +} + +FunctionSlotMorph.prototype.init = function (isPredicate) { + FunctionSlotMorph.uber.init.call(this); + this.isPredicate = isPredicate || false; + this.color = this.rfColor; + this.setExtent(new Point( + (this.fontSize + this.edge * 2) * 2, + this.fontSize + this.edge * 2 + )); +}; + +FunctionSlotMorph.prototype.getSpec = function () { + return '%f'; +}; + +// FunctionSlotMorph drawing: + +FunctionSlotMorph.prototype.drawNew = function () { + var context, borderColor; + + // initialize my surface property + this.image = newCanvas(this.extent()); + context = this.image.getContext('2d'); + if (this.parent) { + borderColor = this.parent.color; + } else { + borderColor = new Color(120, 120, 120); + } + + // cache my border colors + this.cachedClr = borderColor.toString(); + this.cachedClrBright = borderColor.lighter(this.contrast) + .toString(); + this.cachedClrDark = borderColor.darker(this.contrast).toString(); + + if (this.isPredicate) { + this.drawDiamond(context); + } else { + this.drawRounded(context); + } +}; + +FunctionSlotMorph.prototype.drawRounded = function (context) { + var h = this.height(), + r = Math.min(this.rounding, h / 2), + w = this.width(), + shift = this.edge / 2, + gradient; + + // draw the 'flat' shape: + context.fillStyle = this.color.toString(); + context.beginPath(); + + // top left: + context.arc( + r, + r, + r, + radians(-180), + radians(-90), + false + ); + + // top right: + context.arc( + w - r, + r, + r, + radians(-90), + radians(-0), + false + ); + + // bottom right: + context.arc( + w - r, + h - r, + r, + radians(0), + radians(90), + false + ); + + // bottom left: + context.arc( + r, + h - r, + r, + radians(90), + radians(180), + false + ); + + context.closePath(); + context.fill(); + + if (MorphicPreferences.isFlat) {return; } + + // add 3D-Effect: + context.lineWidth = this.edge; + context.lineJoin = 'round'; + context.lineCap = 'round'; + + // bottom left corner + context.strokeStyle = this.cachedClr; //gradient; + context.beginPath(); + context.arc( + r, + h - r, + r - shift, + radians(90), + radians(180), + false + ); + context.stroke(); + + // top right corner + context.strokeStyle = this.cachedClr; //gradient; + context.beginPath(); + context.arc( + w - r, + r, + r - shift, + radians(-90), + radians(0), + false + ); + context.stroke(); + + // normal gradient edges + + context.shadowOffsetX = shift; + context.shadowOffsetY = shift; + context.shadowBlur = this.edge; + context.shadowColor = this.color.darker(80).toString(); + + // top edge: straight line + gradient = context.createLinearGradient( + 0, + 0, + 0, + this.edge + ); + gradient.addColorStop(1, this.cachedClrDark); + gradient.addColorStop(0, this.cachedClr); + context.strokeStyle = gradient; + context.beginPath(); + context.moveTo(r - shift, shift); + context.lineTo(w - r + shift, shift); + context.stroke(); + + // top edge: left corner + gradient = context.createRadialGradient( + r, + r, + r - this.edge, + r, + r, + r + ); + gradient.addColorStop(1, this.cachedClr); + gradient.addColorStop(0, this.cachedClrDark); + context.strokeStyle = gradient; + context.beginPath(); + context.arc( + r, + r, + r - shift, + radians(180), + radians(270), + false + ); + context.stroke(); + + // left edge: straight vertical line + gradient = context.createLinearGradient(0, 0, this.edge, 0); + gradient.addColorStop(1, this.cachedClrDark); + gradient.addColorStop(0, this.cachedClr); + context.strokeStyle = gradient; + context.beginPath(); + context.moveTo(shift, r); + context.lineTo(shift, h - r); + context.stroke(); + + context.shadowOffsetX = 0; + context.shadowOffsetY = 0; + context.shadowBlur = 0; + + // bottom edge: right corner + gradient = context.createRadialGradient( + w - r, + h - r, + r - this.edge, + w - r, + h - r, + r + ); + gradient.addColorStop(1, this.cachedClr); + gradient.addColorStop(0, this.cachedClrBright); + context.strokeStyle = gradient; + context.beginPath(); + context.arc( + w - r, + h - r, + r - shift, + radians(0), + radians(90), + false + ); + context.stroke(); + + // bottom edge: straight line + gradient = context.createLinearGradient( + 0, + h - this.edge, + 0, + h + ); + gradient.addColorStop(1, this.cachedClr); + gradient.addColorStop(0, this.cachedClrBright); + context.strokeStyle = gradient; + context.beginPath(); + context.moveTo(r - shift, h - shift); + context.lineTo(w - r + shift, h - shift); + context.stroke(); + + // right edge: straight vertical line + gradient = context.createLinearGradient(w - this.edge, 0, w, 0); + gradient.addColorStop(1, this.cachedClr); + gradient.addColorStop(0, this.cachedClrBright); + context.strokeStyle = gradient; + context.beginPath(); + context.moveTo(w - shift, r + shift); + context.lineTo(w - shift, h - r); + context.stroke(); + +}; + +FunctionSlotMorph.prototype.drawDiamond = function (context) { + var w = this.width(), + h = this.height(), + h2 = Math.floor(h / 2), + r = Math.min(this.rounding, h2), + shift = this.edge / 2, + gradient; + + // draw the 'flat' shape: + context.fillStyle = this.color.toString(); + context.beginPath(); + + context.moveTo(0, h2); + context.lineTo(r, 0); + context.lineTo(w - r, 0); + context.lineTo(w, h2); + context.lineTo(w - r, h); + context.lineTo(r, h); + + context.closePath(); + context.fill(); + + if (MorphicPreferences.isFlat) {return; } + + // add 3D-Effect: + context.lineWidth = this.edge; + context.lineJoin = 'round'; + context.lineCap = 'round'; + + // half-tone edges + // bottom left corner + context.strokeStyle = this.cachedClr; + context.beginPath(); + context.moveTo(shift, h2); + context.lineTo(r, h - shift); + context.stroke(); + + // top right corner + context.strokeStyle = this.cachedClr; + context.beginPath(); + context.moveTo(w - shift, h2); + context.lineTo(w - r, shift); + context.stroke(); + + // normal gradient edges + // top edge: left corner + + context.shadowOffsetX = shift; + context.shadowOffsetY = shift; + context.shadowBlur = this.edge; + context.shadowColor = this.color.darker(80).toString(); + + gradient = context.createLinearGradient( + 0, + 0, + r, + 0 + ); + gradient.addColorStop(1, this.cachedClrDark); + gradient.addColorStop(0, this.cachedClr); + context.strokeStyle = gradient; + context.beginPath(); + context.moveTo(shift, h2); + context.lineTo(r, shift); + context.stroke(); + + // top edge: straight line + gradient = context.createLinearGradient( + 0, + 0, + 0, + this.edge + ); + gradient.addColorStop(1, this.cachedClrDark); + gradient.addColorStop(0, this.cachedClr); + context.strokeStyle = gradient; + context.beginPath(); + context.moveTo(r, shift); + context.lineTo(w - r, shift); + context.stroke(); + + context.shadowOffsetX = 0; + context.shadowOffsetY = 0; + context.shadowBlur = 0; + + // bottom edge: right corner + gradient = context.createLinearGradient( + w - r, + 0, + w, + 0 + ); + gradient.addColorStop(1, this.cachedClr); + gradient.addColorStop(0, this.cachedClrBright); + context.strokeStyle = gradient; + context.beginPath(); + context.moveTo(w - r, h - shift); + context.lineTo(w - shift, h2); + context.stroke(); + + // bottom edge: straight line + gradient = context.createLinearGradient( + 0, + h - this.edge, + 0, + h + ); + gradient.addColorStop(1, this.cachedClr); + gradient.addColorStop(0, this.cachedClrBright); + context.strokeStyle = gradient; + context.beginPath(); + context.moveTo(r + shift, h - shift); + context.lineTo(w - r - shift, h - shift); + context.stroke(); +}; + +// ReporterSlotMorph /////////////////////////////////////////////////// + +/* + I am a ReporterBlock-shaped input slot. I can nest as well as + accept reporter blocks (containing reified scripts). + + my most important accessor is + + nestedBlock() - answer the reporter block I encompass, if any + + My command spec is %r for reporters (round) and %p for + predicates (diamond) + + evaluate() returns my nested block or null +*/ + +// ReporterSlotMorph inherits from FunctionSlotMorph: + +ReporterSlotMorph.prototype = new FunctionSlotMorph(); +ReporterSlotMorph.prototype.constructor = ReporterSlotMorph; +ReporterSlotMorph.uber = FunctionSlotMorph.prototype; + +// ReporterSlotMorph instance creation: + +function ReporterSlotMorph(isPredicate) { + this.init(isPredicate); +} + +ReporterSlotMorph.prototype.init = function (isPredicate) { + ReporterSlotMorph.uber.init.call(this, isPredicate); + this.add(this.emptySlot()); + this.fixLayout(); +}; + +ReporterSlotMorph.prototype.emptySlot = function () { + var empty = new ArgMorph(), + shrink = this.rfBorder * 2 + this.edge * 2; + empty.color = this.rfColor; + empty.alpha = 0; + empty.setExtent(new Point( + (this.fontSize + this.edge * 2) * 2 - shrink, + this.fontSize + this.edge * 2 - shrink + )); + return empty; +}; + +// ReporterSlotMorph accessing: + +ReporterSlotMorph.prototype.getSpec = function () { + return '%r'; +}; + +ReporterSlotMorph.prototype.contents = function () { + return this.children[0]; +}; + +ReporterSlotMorph.prototype.nestedBlock = function () { + var contents = this.contents(); + return contents instanceof ReporterBlockMorph ? contents : null; +}; + +// ReporterSlotMorph evaluating: + +ReporterSlotMorph.prototype.evaluate = function () { + return this.nestedBlock(); +}; + +ReporterSlotMorph.prototype.isEmptySlot = function () { + return this.nestedBlock() === null; +}; + +// ReporterSlotMorph layout: + +ReporterSlotMorph.prototype.fixLayout = function () { + var contents = this.contents(); + this.setExtent(contents.extent().add( + this.edge * 2 + this.rfBorder * 2 + )); + contents.setCenter(this.center()); + if (this.parent) { + if (this.parent.fixLayout) { + this.parent.fixLayout(); + } + } +}; + +// RingReporterSlotMorph /////////////////////////////////////////////////// + +/* + I am a ReporterBlock-shaped input slot for use in RingMorphs. + I can only nest reporter blocks (both round and diamond). + + My command spec is %rr for reporters (round) and %rp for + predicates (diamond) + + evaluate() returns my nested block or null + (inherited from ReporterSlotMorph +*/ + +// ReporterSlotMorph inherits from FunctionSlotMorph: + +RingReporterSlotMorph.prototype = new ReporterSlotMorph(); +RingReporterSlotMorph.prototype.constructor = RingReporterSlotMorph; +RingReporterSlotMorph.uber = ReporterSlotMorph.prototype; + +// ReporterSlotMorph preferences settings: + +RingReporterSlotMorph.prototype.rfBorder + = RingCommandSlotMorph.prototype.rfBorder; + +RingReporterSlotMorph.prototype.edge + = RingCommandSlotMorph.prototype.edge; + +// RingReporterSlotMorph instance creation: + +function RingReporterSlotMorph(isPredicate) { + this.init(isPredicate); +} + +RingReporterSlotMorph.prototype.init = function (isPredicate) { + RingReporterSlotMorph.uber.init.call(this, isPredicate); + this.alpha = RingMorph.prototype.alpha; + this.contrast = RingMorph.prototype.contrast; + this.isHole = true; +}; + +// RingReporterSlotMorph accessing: + +RingReporterSlotMorph.prototype.getSpec = function () { + return '%rr'; +}; + +RingReporterSlotMorph.prototype.replaceInput = function (source, target) { + RingReporterSlotMorph.uber.replaceInput.call(this, source, target); + if (this.parent instanceof RingMorph) { + this.parent.vanishForSimilar(); + } +}; + +// RingReporterSlotMorph drawing: + +RingReporterSlotMorph.prototype.drawRounded = function (context) { + var h = this.height(), + r = Math.min(this.rounding, h / 2), + w = this.width(), + shift = this.edge / 2, + gradient; + + // draw the 'flat' shape: + context.fillStyle = this.cachedClr; //this.color.toString(); + + // top half: + context.beginPath(); + context.moveTo(0, h / 2); + + // top left: + context.arc( + r, + r, + r, + radians(-180), + radians(-90), + false + ); + + // top right: + context.arc( + w - r, + r, + r, + radians(-90), + radians(-0), + false + ); + + context.lineTo(w, h / 2); + context.lineTo(w, 0); + context.lineTo(0, 0); + context.closePath(); + context.fill(); + + // bottom half: + context.beginPath(); + context.moveTo(w, h / 2); + + // bottom right: + context.arc( + w - r, + h - r, + r, + radians(0), + radians(90), + false + ); + + // bottom left: + context.arc( + r, + h - r, + r, + radians(90), + radians(180), + false + ); + + context.lineTo(0, h / 2); + context.lineTo(0, h); + context.lineTo(w, h); + context.closePath(); + context.fill(); + + if (MorphicPreferences.isFlat) {return; } + + // add 3D-Effect: + context.lineWidth = this.edge; + context.lineJoin = 'round'; + context.lineCap = 'round'; + + // bottom left corner + context.strokeStyle = this.cachedClr; //gradient; + context.beginPath(); + context.arc( + r, + h - r, + r - shift, + radians(90), + radians(180), + false + ); + context.stroke(); + + // top right corner + context.strokeStyle = this.cachedClr; //gradient; + context.beginPath(); + context.arc( + w - r, + r, + r - shift, + radians(-90), + radians(0), + false + ); + context.stroke(); + + // normal gradient edges + + context.shadowOffsetX = shift; + context.shadowOffsetY = shift; + context.shadowBlur = this.edge; + context.shadowColor = this.color.darker(80).toString(); + + // top edge: straight line + gradient = context.createLinearGradient( + 0, + 0, + 0, + this.edge + ); + gradient.addColorStop(1, this.cachedClrDark); + gradient.addColorStop(0, this.cachedClr); + context.strokeStyle = gradient; + context.beginPath(); + context.moveTo(r - shift, shift); + context.lineTo(w - r + shift, shift); + context.stroke(); + + // top edge: left corner + gradient = context.createRadialGradient( + r, + r, + r - this.edge, + r, + r, + r + ); + gradient.addColorStop(1, this.cachedClr); + gradient.addColorStop(0, this.cachedClrDark); + context.strokeStyle = gradient; + context.beginPath(); + context.arc( + r, + r, + r - shift, + radians(180), + radians(270), + false + ); + context.stroke(); + + // left edge: straight vertical line + gradient = context.createLinearGradient(0, 0, this.edge, 0); + gradient.addColorStop(1, this.cachedClrDark); + gradient.addColorStop(0, this.cachedClr); + context.strokeStyle = gradient; + context.beginPath(); + context.moveTo(shift, r); + context.lineTo(shift, h - r); + context.stroke(); + + context.shadowOffsetX = 0; + context.shadowOffsetY = 0; + context.shadowBlur = 0; + + // bottom edge: right corner + gradient = context.createRadialGradient( + w - r, + h - r, + r - this.edge, + w - r, + h - r, + r + ); + gradient.addColorStop(1, this.cachedClr); + gradient.addColorStop(0, this.cachedClrBright); + context.strokeStyle = gradient; + context.beginPath(); + context.arc( + w - r, + h - r, + r - shift, + radians(0), + radians(90), + false + ); + context.stroke(); + + // bottom edge: straight line + gradient = context.createLinearGradient( + 0, + h - this.edge, + 0, + h + ); + gradient.addColorStop(1, this.cachedClr); + gradient.addColorStop(0, this.cachedClrBright); + context.strokeStyle = gradient; + context.beginPath(); + context.moveTo(r - shift, h - shift); + context.lineTo(w - r + shift, h - shift); + context.stroke(); + + // right edge: straight vertical line + gradient = context.createLinearGradient(w - this.edge, 0, w, 0); + gradient.addColorStop(1, this.cachedClr); + gradient.addColorStop(0, this.cachedClrBright); + context.strokeStyle = gradient; + context.beginPath(); + context.moveTo(w - shift, r + shift); + context.lineTo(w - shift, h - r); + context.stroke(); +}; + +RingReporterSlotMorph.prototype.drawDiamond = function (context) { + var w = this.width(), + h = this.height(), + h2 = Math.floor(h / 2), + r = Math.min(this.rounding, h2), + shift = this.edge / 2, + gradient; + + // draw the 'flat' shape: + context.fillStyle = this.cachedClr; + context.beginPath(); + + context.moveTo(0, 0); + context.lineTo(0, h2); + context.lineTo(r, 0); + context.lineTo(w - r, 0); + context.lineTo(w, h2); + context.lineTo(w, 0); + + context.closePath(); + context.fill(); + + context.moveTo(w, h2); + context.lineTo(w - r, h); + context.lineTo(r, h); + context.lineTo(0, h2); + context.lineTo(0, h); + context.lineTo(w, h); + + context.closePath(); + context.fill(); + + if (MorphicPreferences.isFlat) {return; } + + // add 3D-Effect: + context.lineWidth = this.edge; + context.lineJoin = 'round'; + context.lineCap = 'round'; + + // half-tone edges + // bottom left corner + context.strokeStyle = this.cachedClr; + context.beginPath(); + context.moveTo(shift, h2); + context.lineTo(r, h - shift); + context.stroke(); + + // top right corner + context.strokeStyle = this.cachedClr; + context.beginPath(); + context.moveTo(w - shift, h2); + context.lineTo(w - r, shift); + context.stroke(); + + // normal gradient edges + // top edge: left corner + + context.shadowOffsetX = shift; + context.shadowOffsetY = shift; + context.shadowBlur = this.edge; + context.shadowColor = this.color.darker(80).toString(); + + gradient = context.createLinearGradient( + 0, + 0, + r, + 0 + ); + gradient.addColorStop(1, this.cachedClrDark); + gradient.addColorStop(0, this.cachedClr); + context.strokeStyle = gradient; + context.beginPath(); + context.moveTo(shift, h2); + context.lineTo(r, shift); + context.stroke(); + + // top edge: straight line + gradient = context.createLinearGradient( + 0, + 0, + 0, + this.edge + ); + gradient.addColorStop(1, this.cachedClrDark); + gradient.addColorStop(0, this.cachedClr); + context.strokeStyle = gradient; + context.beginPath(); + context.moveTo(r, shift); + context.lineTo(w - r, shift); + context.stroke(); + + context.shadowOffsetX = 0; + context.shadowOffsetY = 0; + context.shadowBlur = 0; + + // bottom edge: right corner + gradient = context.createLinearGradient( + w - r, + 0, + w, + 0 + ); + gradient.addColorStop(1, this.cachedClr); + gradient.addColorStop(0, this.cachedClrBright); + context.strokeStyle = gradient; + context.beginPath(); + context.moveTo(w - r, h - shift); + context.lineTo(w - shift, h2); + context.stroke(); + + // bottom edge: straight line + gradient = context.createLinearGradient( + 0, + h - this.edge, + 0, + h + ); + gradient.addColorStop(1, this.cachedClr); + gradient.addColorStop(0, this.cachedClrBright); + context.strokeStyle = gradient; + context.beginPath(); + context.moveTo(r + shift, h - shift); + context.lineTo(w - r - shift, h - shift); + context.stroke(); +}; + +// CommentMorph ////////////////////////////////////////////////////////// + +/* + I am an editable, multi-line non-scrolling text window. I can be collapsed + to a single abbreviated line or expanded to full. My width can be adjusted + by the user, by height is determined by the size of my text body. I can be + either placed in a scripting area or "stuck" to a block. +*/ + +// CommentMorph inherits from BoxMorph: + +CommentMorph.prototype = new BoxMorph(); +CommentMorph.prototype.constructor = CommentMorph; +CommentMorph.uber = BoxMorph.prototype; + +// CommentMorph preferences settings (pseudo-inherited from SyntaxElement): + +CommentMorph.prototype.refreshScale = function () { + CommentMorph.prototype.fontSize = SyntaxElementMorph.prototype.fontSize; + CommentMorph.prototype.padding = 5 * SyntaxElementMorph.prototype.scale; + CommentMorph.prototype.rounding = 8 * SyntaxElementMorph.prototype.scale; +}; + +CommentMorph.prototype.refreshScale(); + +// CommentMorph instance creation: + +function CommentMorph(contents) { + this.init(contents); +} + +CommentMorph.prototype.init = function (contents) { + var myself = this, + scale = SyntaxElementMorph.prototype.scale; + this.block = null; // optional anchor block + this.stickyOffset = null; // not to be persisted + this.isCollapsed = false; + this.titleBar = new BoxMorph( + this.rounding, + 1.000001 * scale, // shadow bug in Chrome, + new Color(255, 255, 180) + ); + this.titleBar.color = new Color(255, 255, 180); + this.titleBar.setHeight(fontHeight(this.fontSize) + this.padding); + this.title = null; + this.arrow = new ArrowMorph( + 'down', + this.fontSize + ); + this.arrow.noticesTransparentClick = true; + this.arrow.mouseClickLeft = function () {myself.toggleExpand(); }; + this.contents = new TextMorph( + contents || localize('add comment here...'), + this.fontSize + ); + this.contents.isEditable = true; + this.contents.enableSelecting(); + this.contents.maxWidth = 90 * scale; + this.contents.drawNew(); + this.handle = new HandleMorph( + this.contents, + 80, + this.fontSize * 2, + -2, + -2 + ); + this.handle.setExtent(new Point(11 * scale, 11 * scale)); + this.anchor = null; + + CommentMorph.uber.init.call( + this, + this.rounding, + 1.000001 * scale, // shadow bug in Chrome, + new Color(255, 255, 180) + ); + this.color = new Color(255, 255, 220); + this.isDraggable = true; + this.add(this.titleBar); + this.add(this.arrow); + this.add(this.contents); + this.add(this.handle); + + this.fixLayout(); +}; + +// CommentMorph ops: + +CommentMorph.prototype.fullCopy = function () { + var cpy = new CommentMorph(this.contents.text); + cpy.isCollapsed = this.isCollapsed; + cpy.setTextWidth(this.textWidth()); + return cpy; +}; + +CommentMorph.prototype.setTextWidth = function (pixels) { + this.contents.maxWidth = pixels; + this.contents.drawNew(); + this.fixLayout(); +}; + +CommentMorph.prototype.textWidth = function () { + return this.contents.maxWidth; +}; + +CommentMorph.prototype.text = function () { + return this.contents.text; +}; + +CommentMorph.prototype.toggleExpand = function () { + this.isCollapsed = !this.isCollapsed; + this.fixLayout(); + this.align(); +}; + +// CommentMorph layout: + +CommentMorph.prototype.layoutChanged = function () { + // react to a change of the contents area + this.fixLayout(); + this.align(); +}; + +CommentMorph.prototype.fixLayout = function () { + var label, + tw = this.contents.width() + 2 * this.padding, + myself = this, + oldFlag = Morph.prototype.trackChanges; + + Morph.prototype.trackChanges = false; + + if (this.title) { + this.title.destroy(); + this.title = null; + } + if (this.isCollapsed) { + this.contents.hide(); + this.title = new FrameMorph(); + this.title.alpha = 0; + this.title.acceptsDrops = false; + label = new StringMorph( + this.contents.text, + this.fontSize, + null, // style (sans-serif) + true // bold + ); + label.rootForGrab = function () { + return myself; + }; + this.title.add(label); + this.title.setHeight(label.height()); + this.title.setWidth( + tw - this.arrow.width() - this.padding * 2 - this.rounding + ); + this.add(this.title); + } else { + this.contents.show(); + } + this.titleBar.setWidth(tw); + this.contents.setLeft(this.titleBar.left() + this.padding); + this.contents.setTop(this.titleBar.bottom() + this.padding); + this.arrow.direction = this.isCollapsed ? 'right' : 'down'; + this.arrow.drawNew(); + this.arrow.setCenter(this.titleBar.center()); + this.arrow.setLeft(this.titleBar.left() + this.padding); + if (this.title) { + this.title.setPosition( + this.arrow.topRight().add(new Point(this.padding, 0)) + ); + } + Morph.prototype.trackChanges = oldFlag; + this.changed(); + this.silentSetHeight( + this.titleBar.height() + + (this.isCollapsed ? 0 : + this.padding + + this.contents.height() + + this.padding) + ); + this.silentSetWidth(this.titleBar.width()); + this.drawNew(); + this.handle.drawNew(); + this.changed(); +}; + +// CommentMorph menu: + +CommentMorph.prototype.userMenu = function () { + var menu = new MenuMorph(this), + myself = this; + + menu.addItem( + "duplicate", + function () { + this.fullCopy().pickUp(this.world()); + }, + 'make a copy\nand pick it up' + ); + menu.addItem("delete", 'destroy'); + menu.addItem( + "picture...", + function () { + window.open(myself.fullImage().toDataURL()); + }, + 'open a new window\nwith a picture of this comment' + ); + return menu; +}; + +// CommentMorph hiding and showing: + +/* + override the inherited behavior to recursively hide/show all + children, so that my instances get restored correctly when + switching back out of app mode. +*/ + +CommentMorph.prototype.hide = function () { + this.isVisible = false; + this.changed(); +}; + +CommentMorph.prototype.show = function () { + this.isVisible = true; + this.changed(); +}; + +// CommentMorph dragging & dropping + +CommentMorph.prototype.prepareToBeGrabbed = function () { + // disassociate from the block I'm posted to + if (this.block) { + this.block.comment = null; + this.block = null; + } + if (this.anchor) { + this.anchor.destroy(); + this.anchor = null; + // fix shadow, because it was added earlier + this.removeShadow(); + this.addShadow(); + } +}; + +CommentMorph.prototype.snap = function (hand) { + // passing the hand is optional (for when blocks are dragged & dropped) + var scripts = this.parent, + target; + + if (!scripts instanceof ScriptsMorph) { + return null; + } + + scripts.clearDropHistory(); + scripts.lastDroppedBlock = this; + target = scripts.closestBlock(this, hand); + + if (target !== null) { + target.comment = this; + this.block = target; + if (this.snapSound) { + this.snapSound.play(); + } + } + this.align(); +}; + +// CommentMorph sticking to blocks + +CommentMorph.prototype.align = function (topBlock, ignoreLayer) { + if (this.block) { + var top = topBlock || this.block.topBlock(), + affectedBlocks, + tp, + bottom, + rightMost, + scripts = top.parentThatIsA(ScriptsMorph); + this.setTop(this.block.top() + this.block.corner); + tp = this.top(); + bottom = this.bottom(); + affectedBlocks = top.allChildren().filter(function (child) { + return child instanceof BlockMorph && + child.bottom() > tp && + child.top() < bottom; + }); + rightMost = Math.max.apply( + null, + affectedBlocks.map(function (block) {return block.right(); }) + ); + + this.setLeft(rightMost + 5); + if (!ignoreLayer && scripts) { + scripts.addBack(this); // push to back and show + } + + if (!this.anchor) { + this.anchor = new Morph(); + this.anchor.color = this.titleBar.color; + } + this.anchor.silentSetPosition(new Point( + this.block.right(), + this.top() + this.edge + )); + this.anchor.bounds.corner = new Point( + this.left(), + this.top() + this.edge + 1 + ); + this.anchor.drawNew(); + this.addBack(this.anchor); + this.anchor.changed(); + } +}; + +CommentMorph.prototype.startFollowing = function (topBlock) { + var myself = this; + this.align(topBlock); + this.world().add(this); + this.addShadow(); + this.stickyOffset = this.position().subtract(this.block.position()); + this.step = function () { + myself.setPosition(this.block.position().add(myself.stickyOffset)); + }; +}; + +CommentMorph.prototype.stopFollowing = function () { + this.removeShadow(); + delete this.step; +}; + +CommentMorph.prototype.destroy = function () { + if (this.block) { + this.block.comment = null; + } + CommentMorph.uber.destroy.call(this); +}; + +CommentMorph.prototype.stackHeight = function () { + return this.height(); +}; diff --git a/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/byob.js b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/byob.js new file mode 100644 index 0000000..8954c6a --- /dev/null +++ b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/byob.js @@ -0,0 +1,3122 @@ +/* + + byob.js + + "build your own blocks" for SNAP! + based on morphic.js, widgets.js blocks.js, threads.js and objects.js + inspired by Scratch + + written by Jens Mönig + jens@moenig.org + + Copyright (C) 2013 by Jens Mönig + + This file is part of Snap!. + + Snap! is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + + + prerequisites: + -------------- + needs blocks.js, threads.js, objects.js, widgets.js and morphic.js + + + hierarchy + --------- + the following tree lists all constructors hierarchically, + indentation indicating inheritance. Refer to this list to get a + contextual overview: + + BlockLabelFragment + CustomBlockDefinition + + CommandBlockMorph*** + CustomCommandBlockMorph + HatBlockMorph*** + PrototypeHatBlockMorph + + DialogBoxMorph** + BlockDialogMorph + BlockEditorMorph + BlockExportDialogMorph + BlockImportDialogMorph + InputSlotDialogMorph + VariableDialogMorph + + ReporterBlockMorph*** + CustomReporterBlockMorph + JaggedBlockMorph + + + StringMorph* + BlockLabelFragmentMorph + BlockLabelPlaceHolderMorph + + TemplateSlotMorph*** + BlockInputFragmentMorph + + * from morphic.js + ** from widgets.js + *** from blocks.js + + + toc + --- + the following list shows the order in which all constructors are + defined. Use this list to locate code in this document: + + CustomBlockDefinition + CustomCommandBlockMorph + CustomReporterBlockMorph + JaggedBlockMorph + BlockDialogMorph + BlockEditorMorph + PrototypeHatBlockMorph + BlockLabelFragmentMorph + BlockLabelPlaceHolderMorph + BlockInputFragmentMorph + InputSlotDialogMorph + VariableDialogMorph + BlockExportDialogMorph + BlockImportDialogMorph + +*/ + +/*global modules, CommandBlockMorph, SpriteMorph, TemplateSlotMorph, +StringMorph, Color, DialogBoxMorph, ScriptsMorph, ScrollFrameMorph, +Point, HandleMorph, HatBlockMorph, BlockMorph, detect, List, Process, +AlignmentMorph, ToggleMorph, InputFieldMorph, ReporterBlockMorph, +Context, StringMorph, nop, newCanvas, radians, BoxMorph, +ArrowMorph, PushButtonMorph, contains, InputSlotMorph, ShadowMorph, +ToggleButtonMorph, IDE_Morph, MenuMorph, copy, ToggleElementMorph, +Morph, fontHeight, StageMorph, SyntaxElementMorph, SnapSerializer, +CommentMorph, localize, CSlotMorph, SpeechBubbleMorph, MorphicPreferences*/ + +// Global stuff //////////////////////////////////////////////////////// + +modules.byob = '2013-July-04'; + +// Declarations + +var CustomBlockDefinition; +var CustomCommandBlockMorph; +var CustomReporterBlockMorph; +var BlockDialogMorph; +var BlockEditorMorph; +var PrototypeHatBlockMorph; +var BlockLabelFragment; +var BlockLabelFragmentMorph; +var BlockInputFragmentMorph; +var BlockLabelPlaceHolderMorph; +var InputSlotDialogMorph; +var VariableDialogMorph; +var JaggedBlockMorph; +var BlockExportDialogMorph; +var BlockImportDialogMorph; + +// CustomBlockDefinition /////////////////////////////////////////////// + +// CustomBlockDefinition instance creation: + +function CustomBlockDefinition(spec, receiver) { + this.body = null; // a Context (i.e. a reified top block) + this.scripts = []; + this.category = null; + this.isGlobal = false; + this.type = 'command'; + this.spec = spec || ''; + this.declarations = {}; // {'inputName' : [type, default]} + this.comment = null; + this.codeMapping = null; // experimental, generate text code + this.codeHeader = null; // experimental, generate text code + + // don't serialize (not needed for functionality): + this.receiver = receiver || null; // for serialization only (pointer) +} + +// CustomBlockDefinition instantiating blocks + +CustomBlockDefinition.prototype.blockInstance = function () { + var block; + if (this.type === 'command') { + block = new CustomCommandBlockMorph(this); + } else { + block = new CustomReporterBlockMorph( + this, + this.type === 'predicate' + ); + } + block.isDraggable = true; + return block; +}; + +CustomBlockDefinition.prototype.templateInstance = function () { + var block; + block = this.blockInstance(); + block.refreshDefaults(); + block.isDraggable = false; + block.isTemplate = true; + return block; +}; + +CustomBlockDefinition.prototype.prototypeInstance = function () { + var block, slot, myself = this; + + // make a new block instance and mark it as prototype + if (this.type === 'command') { + block = new CustomCommandBlockMorph(this, true); + } else { + block = new CustomReporterBlockMorph( + this, + this.type === 'predicate', + true + ); + } + + // assign slot declarations to prototype inputs + block.parts().forEach(function (part) { + if (part instanceof BlockInputFragmentMorph) { + slot = myself.declarations[part.fragment.labelString]; + if (slot) { + part.fragment.type = slot[0]; + part.fragment.defaultValue = slot[1]; + } + } + }); + + return block; +}; + +// CustomBlockDefinition duplicating + +CustomBlockDefinition.prototype.copyAndBindTo = function (sprite) { + var c = copy(this); + + c.receiver = sprite; // only for (kludgy) serialization + c.declarations = copy(this.declarations); // might have to go deeper + c.body = Process.prototype.reify.call( + null, + this.body.expression, + new List(this.inputNames()) + ); + c.body.outerContext = null; + + return c; +}; + +// CustomBlockDefinition accessing + +CustomBlockDefinition.prototype.blockSpec = function () { + var myself = this, + ans = [], + parts = this.parseSpec(this.spec), + spec; + parts.forEach(function (part) { + if (part[0] === '%') { + spec = myself.typeOf(part.slice(1)); + } else { + spec = part; + } + ans.push(spec); + ans.push(' '); + }); + return ''.concat.apply('', ans).trim(); +}; + +CustomBlockDefinition.prototype.helpSpec = function () { + var ans = [], + parts = this.parseSpec(this.spec); + parts.forEach(function (part) { + if (part[0] !== '%') { + ans.push(part); + } + }); + return ''.concat.apply('', ans).replace(/\?/g, ''); +}; + +CustomBlockDefinition.prototype.typeOf = function (inputName) { + if (this.declarations[inputName]) { + return this.declarations[inputName][0]; + } + return '%s'; +}; + +CustomBlockDefinition.prototype.defaultValueOf = function (inputName) { + if (this.declarations[inputName]) { + return this.declarations[inputName][1]; + } + return ''; +}; + +CustomBlockDefinition.prototype.defaultValueOfInputIdx = function (idx) { + var inputName = this.inputNames()[idx]; + return this.defaultValueOf(inputName); +}; + +CustomBlockDefinition.prototype.inputNames = function () { + var vNames = [], + parts = this.parseSpec(this.spec); + parts.forEach(function (part) { + if (part[0] === '%') { + vNames.push(part.slice(1)); + } + }); + return vNames; +}; + +CustomBlockDefinition.prototype.parseSpec = function (spec) { + // private + var parts = [], word = '', i, quoted = false, c; + for (i = 0; i < spec.length; i += 1) { + c = spec[i]; + if (c === "'") { + quoted = !quoted; + } else if (c === ' ' && !quoted) { + parts.push(word); + word = ''; + } else { + word = word.concat(c); + } + } + parts.push(word); + return parts; +}; + +// CustomCommandBlockMorph ///////////////////////////////////////////// + +// CustomCommandBlockMorph inherits from CommandBlockMorph: + +CustomCommandBlockMorph.prototype = new CommandBlockMorph(); +CustomCommandBlockMorph.prototype.constructor = CustomCommandBlockMorph; +CustomCommandBlockMorph.uber = CommandBlockMorph.prototype; + +// CustomCommandBlockMorph instance creation: + +function CustomCommandBlockMorph(definition, isProto) { + this.init(definition, isProto); +} + +CustomCommandBlockMorph.prototype.init = function (definition, isProto) { + this.definition = definition; // mandatory + this.isPrototype = isProto || false; // optional + + CustomCommandBlockMorph.uber.init.call(this); + + this.category = definition.category; + this.selector = 'evaluateCustomBlock'; + if (definition) { // needed for de-serializing + this.refresh(); + } +}; + +CustomCommandBlockMorph.prototype.refresh = function () { + var def = this.definition, + newSpec = this.isPrototype ? + def.spec : def.blockSpec(), + oldInputs; + + this.setCategory(def.category); + if (this.blockSpec !== newSpec) { + oldInputs = this.inputs(); + this.setSpec(newSpec); + this.fixBlockColor(); + this.fixLabelColor(); + this.restoreInputs(oldInputs); + } + + // find unnahmed upvars and label them + // to their internal definition (default) + this.inputs().forEach(function (inp, idx) { + if (inp instanceof TemplateSlotMorph && inp.contents() === '\u2191') { + inp.setContents(def.inputNames()[idx]); + } + }); +}; + +CustomCommandBlockMorph.prototype.restoreInputs = function (oldInputs) { + // try to restore my previous inputs when my spec has been changed + var i = 0, + old, + myself = this; + + if (this.isPrototype) {return; } + this.inputs().forEach(function (inp) { + old = oldInputs[i]; + if (old instanceof ReporterBlockMorph && + (!(inp instanceof TemplateSlotMorph))) { + myself.silentReplaceInput(inp, old); + } else if (old instanceof InputSlotMorph + && inp instanceof InputSlotMorph) { + inp.setContents(old.evaluate()); + } else if (old instanceof TemplateSlotMorph + && inp instanceof TemplateSlotMorph) { + inp.setContents(old.evaluate()); + } else if (old instanceof CSlotMorph + && inp instanceof CSlotMorph) { + inp.nestedBlock(old.evaluate()); + } + i += 1; + }); +}; + +CustomCommandBlockMorph.prototype.refreshDefaults = function () { + // fill my editable slots with the defaults specified in my definition + var inputs = this.inputs(), idx = 0, myself = this; + + inputs.forEach(function (inp) { + if (inp instanceof InputSlotMorph) { + inp.setContents(myself.definition.defaultValueOfInputIdx(idx)); + } + idx += 1; + }); +}; + +CustomCommandBlockMorph.prototype.refreshPrototype = function () { + // create my label parts from my (edited) fragments only + var hat, + protoSpec, + frags = [], + myself = this, + words, + newFrag, + i = 0; + + if (!this.isPrototype) {return null; } + + hat = this.parentThatIsA(PrototypeHatBlockMorph); + + // remember the edited fragments + this.parts().forEach(function (part) { + if (!part.fragment.isDeleted) { + // take into consideration that a fragment may spawn others + // if it isn't an input label consisting of several words + if (part.fragment.type) { // marked as input, take label as is + frags.push(part.fragment); + } else { // not an input, devide into several non-input fragments + words = myself.definition.parseSpec( + part.fragment.labelString + ); + words.forEach(function (word) { + newFrag = part.fragment.copy(); + newFrag.labelString = word; + frags.push(newFrag); + }); + } + } + }); + + // remember the edited prototype spec + protoSpec = this.specFromFragments(); + + + // update the prototype's type + // and possibly exchange 'this' for 'myself' + if (this instanceof CustomCommandBlockMorph + && ((hat.type === 'reporter') || (hat.type === 'predicate'))) { + myself = new CustomReporterBlockMorph( + this.definition, + hat.type === 'predicate', + true + ); + hat.silentReplaceInput(this, myself); + } else if (this instanceof CustomReporterBlockMorph) { + if (hat.type === 'command') { + myself = new CustomCommandBlockMorph( + this.definition, + true + ); + hat.silentReplaceInput(this, myself); + } else { + this.isPredicate = (hat.type === 'predicate'); + this.drawNew(); + } + } + myself.setCategory(hat.blockCategory || 'other'); + hat.fixBlockColor(); + + // update the (new) prototype's appearance + myself.setSpec(protoSpec); + + // update the (new) prototype's (new) fragments + // with the previously edited ones + + myself.parts().forEach(function (part) { + if (!(part instanceof BlockLabelPlaceHolderMorph)) { + if (frags[i]) { // don't delete the default fragment + part.fragment = frags[i]; + } + i += 1; + } + }); + + // refresh slot type indicators + this.refreshPrototypeSlotTypes(); + + hat.fixLayout(); +}; + +CustomCommandBlockMorph.prototype.refreshPrototypeSlotTypes = function () { + this.parts().forEach(function (part) { + if (part instanceof BlockInputFragmentMorph) { + part.template().instantiationSpec = part.contents(); + part.setContents(part.fragment.defTemplateSpecFragment()); + } + }); + this.fixBlockColor(null, true); // enforce zebra coloring of templates +}; + + +CustomCommandBlockMorph.prototype.inputFragmentNames = function () { + // for the variable name slot drop-down menu (in the block editor) + var ans = []; + + this.parts().forEach(function (part) { + if (!part.fragment.isDeleted && (part.fragment.type)) { + ans.push(part.fragment.labelString); + } + }); + return ans; +}; + +CustomCommandBlockMorph.prototype.upvarFragmentNames = function () { + // for the variable name slot drop-down menu (in the block editor) + var ans = []; + + this.parts().forEach(function (part) { + if (!part.fragment.isDeleted && (part.fragment.type === '%upvar')) { + ans.push(part.fragment.labelString); + } + }); + return ans; +}; + +CustomCommandBlockMorph.prototype.upvarFragmentName = function (idx) { + // for block prototypes while they are being edited + return this.upvarFragmentNames()[idx] || '\u2191'; +}; + +CustomCommandBlockMorph.prototype.specFromFragments = function () { + // for block prototypes while they are being edited + var ans = ''; + + this.parts().forEach(function (part) { + if (!part.fragment.isDeleted) { + ans = ans + part.fragment.defSpecFragment() + ' '; + } + }); + return ans.trim(); +}; + +CustomCommandBlockMorph.prototype.blockSpecFromFragments = function () { + // for block instances while their prototype is being edited + var ans = ''; + + this.parts().forEach(function (part) { + if (!part.fragment.isDeleted) { + ans = ans + part.fragment.blockSpecFragment() + ' '; + } + }); + return ans.trim(); +}; + +CustomCommandBlockMorph.prototype.declarationsFromFragments = function () { + // format for type declarations: {inputName : [type, default]} + var ans = {}; + + this.parts().forEach(function (part) { + if (part instanceof BlockInputFragmentMorph) { + ans[part.fragment.labelString] = + [part.fragment.type, part.fragment.defaultValue]; + } + }); + return ans; +}; + +CustomCommandBlockMorph.prototype.parseSpec = function (spec) { + if (!this.isPrototype) { + return CustomCommandBlockMorph.uber.parseSpec.call(this, spec); + } + return this.definition.parseSpec.call(this, spec); +}; + +CustomCommandBlockMorph.prototype.mouseClickLeft = function () { + if (!this.isPrototype) { + return CustomCommandBlockMorph.uber.mouseClickLeft.call(this); + } + this.edit(); +}; + +CustomCommandBlockMorph.prototype.edit = function () { + var myself = this, block, hat; + + if (this.isPrototype) { + block = this.definition.blockInstance(); + block.addShadow(); + hat = this.parentThatIsA(PrototypeHatBlockMorph); + new BlockDialogMorph( + null, + function (definition) { + if (definition) { // temporarily update everything + hat.blockCategory = definition.category; + hat.type = definition.type; + myself.refreshPrototype(); + } + }, + myself + ).openForChange( + 'Change block', + hat.blockCategory, + hat.type, + myself.world(), + block.fullImage(), + myself.isInUse() + ); + } else { + new BlockEditorMorph(this.definition, this.receiver()).popUp(); + } +}; + +CustomCommandBlockMorph.prototype.labelPart = function (spec) { + var part; + + if (!this.isPrototype) { + return CustomCommandBlockMorph.uber.labelPart.call(this, spec); + } + if ((spec[0] === '%') && (spec.length > 1)) { + part = new BlockInputFragmentMorph(spec.slice(1)); + } else { + part = new BlockLabelFragmentMorph(spec); + part.fontSize = this.fontSize; + part.color = new Color(255, 255, 255); + part.isBold = true; + part.shadowColor = this.color.darker(this.labelContrast); + part.shadowOffset = this.embossing; + part.drawNew(); + } + return part; +}; + +CustomCommandBlockMorph.prototype.placeHolder = function () { + var part; + + part = new BlockLabelPlaceHolderMorph(); + part.fontSize = this.fontSize * 1.4; + part.color = new Color(45, 45, 45); + part.drawNew(); + return part; +}; + +CustomCommandBlockMorph.prototype.attachTargets = function () { + if (this.isPrototype) { + return []; + } + return CustomCommandBlockMorph.uber.attachTargets.call(this); +}; + +CustomCommandBlockMorph.prototype.isInUse = function () { + // anser true if an instance of my definition is found + // in any of my receiver's scripts or block definitions + return this.receiver().usesBlockInstance(this.definition); +}; + +// CustomCommandBlockMorph menu: + +CustomCommandBlockMorph.prototype.userMenu = function () { + var menu; + + if (this.isPrototype) { + menu = new MenuMorph(this); + menu.addItem( + "script pic...", + function () { + window.open(this.topBlock().fullImage().toDataURL()); + }, + 'open a new window\nwith a picture of this script' + ); + } else { + menu = this.constructor.uber.userMenu.call(this); + if (!menu) { + menu = new MenuMorph(this); + } else { + menu.addLine(); + } + // menu.addItem("export definition...", 'exportBlockDefinition'); + menu.addItem("delete block definition...", 'deleteBlockDefinition'); + } + menu.addItem("edit...", 'edit'); // works also for prototypes + return menu; +}; + +CustomCommandBlockMorph.prototype.exportBlockDefinition = function () { + var xml = new SnapSerializer().serialize(this.definition); + window.open('data:text/xml,' + encodeURIComponent(xml)); +}; + +CustomCommandBlockMorph.prototype.deleteBlockDefinition = function () { + var idx, rcvr, stage, ide, myself = this, block; + if (this.isPrototype) { + return null; // under construction... + } + block = myself.definition.blockInstance(); + block.addShadow(); + new DialogBoxMorph( + this, + function () { + rcvr = myself.receiver(); + rcvr.deleteAllBlockInstances(myself.definition); + if (myself.definition.isGlobal) { + stage = rcvr.parentThatIsA(StageMorph); + idx = stage.globalBlocks.indexOf(myself.definition); + if (idx !== -1) { + stage.globalBlocks.splice(idx, 1); + } + } else { + idx = rcvr.customBlocks.indexOf(myself.definition); + if (idx !== -1) { + rcvr.customBlocks.splice(idx, 1); + } + } + ide = rcvr.parentThatIsA(IDE_Morph); + if (ide) { + ide.flushPaletteCache(); + ide.refreshPalette(); + } + }, + this + ).askYesNo( + 'Delete Custom Block', + localize('block deletion dialog text'), // long string lookup + myself.world(), + block.fullImage() + ); +}; + +// CustomCommandBlockMorph events: + +CustomCommandBlockMorph.prototype.mouseEnter = function () { + var comment, help; + if (this.isTemplate && this.definition.comment) { + comment = this.definition.comment.fullCopy(); + comment.contents.parse(); + help = ''; + comment.contents.lines.forEach(function (line) { + help = help + '\n' + line; + }); + this.bubbleHelp( + help.substr(1), + this.definition.comment.color + ); + } +}; + +CustomCommandBlockMorph.prototype.mouseLeave = function () { + if (this.isTemplate && this.definition.comment) { + this.world().hand.destroyTemporaries(); + } +}; + +// CustomCommandBlockMorph bubble help: + +CustomCommandBlockMorph.prototype.bubbleHelp = function (contents, color) { + var myself = this; + this.fps = 2; + this.step = function () { + if (this.bounds.containsPoint(this.world().hand.position())) { + myself.popUpbubbleHelp(contents, color); + } + myself.fps = 0; + delete myself.step; + }; +}; + +CustomCommandBlockMorph.prototype.popUpbubbleHelp = function ( + contents, + color +) { + new SpeechBubbleMorph( + contents, + color, + null, + 1 + ).popUp(this.world(), this.rightCenter().add(new Point(-8, 0))); +}; + +// CustomReporterBlockMorph //////////////////////////////////////////// + +// CustomReporterBlockMorph inherits from ReporterBlockMorph: + +CustomReporterBlockMorph.prototype = new ReporterBlockMorph(); +CustomReporterBlockMorph.prototype.constructor = CustomReporterBlockMorph; +CustomReporterBlockMorph.uber = ReporterBlockMorph.prototype; + +// CustomReporterBlockMorph instance creation: + +function CustomReporterBlockMorph(definition, isPredicate, isProto) { + this.init(definition, isPredicate, isProto); +} + +CustomReporterBlockMorph.prototype.init = function ( + definition, + isPredicate, + isProto +) { + this.definition = definition; // mandatory + this.isPrototype = isProto || false; // optional + + CustomReporterBlockMorph.uber.init.call(this, isPredicate); + + this.category = definition.category; + this.selector = 'evaluateCustomBlock'; + if (definition) { // needed for de-serializing + this.refresh(); + } +}; + +CustomReporterBlockMorph.prototype.refresh = function () { + CustomCommandBlockMorph.prototype.refresh.call(this); + if (!this.isPrototype) { + this.isPredicate = (this.definition.type === 'predicate'); + } + this.drawNew(); +}; + +CustomReporterBlockMorph.prototype.mouseClickLeft = function () { + if (!this.isPrototype) { + return CustomReporterBlockMorph.uber.mouseClickLeft.call(this); + } + this.edit(); +}; + +CustomReporterBlockMorph.prototype.placeHolder + = CustomCommandBlockMorph.prototype.placeHolder; + +CustomReporterBlockMorph.prototype.parseSpec + = CustomCommandBlockMorph.prototype.parseSpec; + +CustomReporterBlockMorph.prototype.edit + = CustomCommandBlockMorph.prototype.edit; + +CustomReporterBlockMorph.prototype.labelPart + = CustomCommandBlockMorph.prototype.labelPart; + +CustomReporterBlockMorph.prototype.upvarFragmentNames + = CustomCommandBlockMorph.prototype.upvarFragmentNames; + +CustomReporterBlockMorph.prototype.upvarFragmentName + = CustomCommandBlockMorph.prototype.upvarFragmentName; + +CustomReporterBlockMorph.prototype.inputFragmentNames + = CustomCommandBlockMorph.prototype.inputFragmentNames; + +CustomReporterBlockMorph.prototype.specFromFragments + = CustomCommandBlockMorph.prototype.specFromFragments; + +CustomReporterBlockMorph.prototype.blockSpecFromFragments + = CustomCommandBlockMorph.prototype.blockSpecFromFragments; + +CustomReporterBlockMorph.prototype.declarationsFromFragments + = CustomCommandBlockMorph.prototype.declarationsFromFragments; + +CustomReporterBlockMorph.prototype.refreshPrototype + = CustomCommandBlockMorph.prototype.refreshPrototype; + +CustomReporterBlockMorph.prototype.refreshPrototypeSlotTypes + = CustomCommandBlockMorph.prototype.refreshPrototypeSlotTypes; + +CustomReporterBlockMorph.prototype.restoreInputs + = CustomCommandBlockMorph.prototype.restoreInputs; + +CustomReporterBlockMorph.prototype.refreshDefaults + = CustomCommandBlockMorph.prototype.refreshDefaults; + +CustomReporterBlockMorph.prototype.isInUse + = CustomCommandBlockMorph.prototype.isInUse; + +// CustomReporterBlockMorph menu: + +CustomReporterBlockMorph.prototype.userMenu + = CustomCommandBlockMorph.prototype.userMenu; + +CustomReporterBlockMorph.prototype.deleteBlockDefinition + = CustomCommandBlockMorph.prototype.deleteBlockDefinition; + +// CustomReporterBlockMorph events: + +CustomReporterBlockMorph.prototype.mouseEnter + = CustomCommandBlockMorph.prototype.mouseEnter; + +CustomReporterBlockMorph.prototype.mouseLeave + = CustomCommandBlockMorph.prototype.mouseLeave; + +// CustomReporterBlockMorph bubble help: + +CustomReporterBlockMorph.prototype.bubbleHelp + = CustomCommandBlockMorph.prototype.bubbleHelp; + +CustomReporterBlockMorph.prototype.popUpbubbleHelp + = CustomCommandBlockMorph.prototype.popUpbubbleHelp; + +// JaggedBlockMorph //////////////////////////////////////////////////// + +/* + I am a reporter block with jagged left and right edges conveying the + appearance of having the broken out of a bigger block. I am used to + display input types in the long form input dialog. +*/ + +// JaggedBlockMorph inherits from ReporterBlockMorph: + +JaggedBlockMorph.prototype = new ReporterBlockMorph(); +JaggedBlockMorph.prototype.constructor = JaggedBlockMorph; +JaggedBlockMorph.uber = ReporterBlockMorph.prototype; + +// JaggedBlockMorph preferences settings: + +JaggedBlockMorph.prototype.jag = 5; + +// JaggedBlockMorph instance creation: + +function JaggedBlockMorph(spec) { + this.init(spec); +} + +JaggedBlockMorph.prototype.init = function (spec) { + JaggedBlockMorph.uber.init.call(this); + if (spec) {this.setSpec(spec); } + if (spec === '%cs') { + this.minWidth = 25; + this.fixLayout(); + } +}; + +// JaggedBlockMorph drawing: + +JaggedBlockMorph.prototype.drawNew = function () { + var context; + + this.cachedClr = this.color.toString(); + this.cachedClrBright = this.bright(); + this.cachedClrDark = this.dark(); + this.image = newCanvas(this.extent()); + context = this.image.getContext('2d'); + context.fillStyle = this.cachedClr; + + this.drawBackground(context); + if (!MorphicPreferences.isFlat) { + this.drawEdges(context); + } + + // erase holes + this.eraseHoles(context); +}; + +JaggedBlockMorph.prototype.drawBackground = function (context) { + var w = this.width(), + h = this.height(), + jags = Math.round(h / this.jag), + delta = h / jags, + i, + y; + + context.fillStyle = this.cachedClr; + context.beginPath(); + + context.moveTo(0, 0); + context.lineTo(w, 0); + + y = 0; + for (i = 0; i < jags; i += 1) { + y += delta / 2; + context.lineTo(w - this.jag / 2, y); + y += delta / 2; + context.lineTo(w, y); + } + + context.lineTo(0, h); + y = h; + for (i = 0; i < jags; i += 1) { + y -= delta / 2; + context.lineTo(this.jag / 2, y); + y -= delta / 2; + context.lineTo(0, y); + } + + context.closePath(); + context.fill(); +}; + +JaggedBlockMorph.prototype.drawEdges = function (context) { + var w = this.width(), + h = this.height(), + jags = Math.round(h / this.jag), + delta = h / jags, + shift = this.edge / 2, + gradient, + i, + y; + + context.lineWidth = this.edge; + context.lineJoin = 'round'; + context.lineCap = 'round'; + + gradient = context.createLinearGradient( + 0, + 0, + 0, + this.edge + ); + gradient.addColorStop(0, this.cachedClrBright); + gradient.addColorStop(1, this.cachedClr); + context.strokeStyle = gradient; + + context.beginPath(); + context.moveTo(shift, shift); + context.lineTo(w - shift, shift); + context.stroke(); + + y = 0; + for (i = 0; i < jags; i += 1) { + context.strokeStyle = this.cachedClrDark; + context.beginPath(); + context.moveTo(w - shift, y); + y += delta / 2; + context.lineTo(w - this.jag / 2 - shift, y); + context.stroke(); + y += delta / 2; + } + + gradient = context.createLinearGradient( + 0, + h - this.edge, + 0, + h + ); + gradient.addColorStop(0, this.cachedClr); + gradient.addColorStop(1, this.cachedClrDark); + context.strokeStyle = gradient; + context.beginPath(); + context.moveTo(w - shift, h - shift); + context.lineTo(shift, h - shift); + context.stroke(); + + y = h; + for (i = 0; i < jags; i += 1) { + context.strokeStyle = this.cachedClrBright; + context.beginPath(); + context.moveTo(shift, y); + y -= delta / 2; + context.lineTo(this.jag / 2 + shift, y); + context.stroke(); + y -= delta / 2; + } +}; + +// BlockDialogMorph //////////////////////////////////////////////////// + +// BlockDialogMorph inherits from DialogBoxMorph: + +BlockDialogMorph.prototype = new DialogBoxMorph(); +BlockDialogMorph.prototype.constructor = BlockDialogMorph; +BlockDialogMorph.uber = DialogBoxMorph.prototype; + +// BlockDialogMorph instance creation: + +function BlockDialogMorph(target, action, environment) { + this.init(target, action, environment); +} + +BlockDialogMorph.prototype.init = function (target, action, environment) { + // additional properties: + this.blockType = 'command'; + this.category = 'other'; + this.isGlobal = true; + this.types = null; + this.categories = null; + + // initialize inherited properties: + BlockDialogMorph.uber.init.call( + this, + target, + action, + environment + ); + + // override inherited properites: + this.key = 'makeABlock'; + + this.types = new AlignmentMorph('row', this.padding); + this.add(this.types); + this.scopes = new AlignmentMorph('row', this.padding); + this.add(this.scopes); + + this.categories = new BoxMorph(); + this.categories.color = SpriteMorph.prototype.paletteColor.lighter(8); + this.categories.borderColor = this.categories.color.lighter(40); + this.createCategoryButtons(); + this.fixCategoriesLayout(); + this.add(this.categories); + + this.createTypeButtons(); + this.createScopeButtons(); + this.fixLayout(); +}; + +BlockDialogMorph.prototype.openForChange = function ( + title, + category, + type, + world, + pic, + preventTypeChange // +) { + var clr = SpriteMorph.prototype.blockColor[category]; + this.key = 'changeABlock'; + this.category = category; + this.blockType = type; + + this.categories.children.forEach(function (each) { + each.refresh(); + }); + this.types.children.forEach(function (each) { + each.setColor(clr); + each.refresh(); + }); + + this.labelString = title; + this.createLabel(); + if (pic) {this.setPicture(pic); } + this.addButton('ok', 'OK'); + this.addButton('cancel', 'Cancel'); + if (preventTypeChange) { + this.types.destroy(); + this.types = null; + } + this.scopes.destroy(); + this.scopes = null; + this.fixLayout(); + this.drawNew(); + this.popUp(world); +}; + +// category buttons + +BlockDialogMorph.prototype.createCategoryButtons = function () { + var myself = this, + oldFlag = Morph.prototype.trackChanges; + + Morph.prototype.trackChanges = false; + SpriteMorph.prototype.categories.forEach(function (cat) { + myself.addCategoryButton(cat); + }); + Morph.prototype.trackChanges = oldFlag; +}; + +BlockDialogMorph.prototype.addCategoryButton = function (category) { + var labelWidth = 75, + myself = this, + colors = [ + SpriteMorph.prototype.paletteColor, + SpriteMorph.prototype.paletteColor.darker(50), + SpriteMorph.prototype.blockColor[category] + ], + button; + + button = new ToggleButtonMorph( + colors, + this, // this block dialog box is the target + function () { + myself.category = category; + myself.categories.children.forEach(function (each) { + each.refresh(); + }); + if (myself.types) { + myself.types.children.forEach(function (each) { + each.setColor(colors[2]); + }); + } + myself.edit(); + }, + category[0].toUpperCase().concat(category.slice(1)), // UCase label + function () {return myself.category === category; }, // query + null, // env + null, // hint + null, // template cache + labelWidth, // minWidth + true // has preview + ); + + button.corner = 8; + button.padding = 0; + button.labelShadowOffset = new Point(-1, -1); + button.labelShadowColor = colors[1]; + button.labelColor = IDE_Morph.prototype.buttonLabelColor; + button.contrast = this.buttonContrast; + button.fixLayout(); + button.refresh(); + this.categories.add(button); + return button; +}; + +BlockDialogMorph.prototype.fixCategoriesLayout = function () { + var buttonWidth = this.categories.children[0].width(), // all the same + buttonHeight = this.categories.children[0].height(), // all the same + xPadding = 15, + yPadding = 2, + border = 10, // this.categories.border, + rows = Math.ceil((this.categories.children.length) / 2), + l = this.categories.left(), + t = this.categories.top(), + i = 0, + row, + col, + oldFlag = Morph.prototype.trackChanges; + + Morph.prototype.trackChanges = false; + + this.categories.children.forEach(function (button) { + i += 1; + row = Math.ceil(i / 2); + col = 2 - (i % 2); + button.setPosition(new Point( + l + (col * xPadding + ((col - 1) * buttonWidth)), + t + (row * yPadding + ((row - 1) * buttonHeight) + border) + )); + }); + + if (MorphicPreferences.isFlat) { + this.categories.corner = 0; + this.categories.border = 0; + this.categories.edge = 0; + } + this.categories.setExtent(new Point( + 3 * xPadding + 2 * buttonWidth, + (rows + 1) * yPadding + rows * buttonHeight + 2 * border + )); + + Morph.prototype.trackChanges = oldFlag; + this.categories.changed(); +}; + +// type radio buttons + +BlockDialogMorph.prototype.createTypeButtons = function () { + var block, + myself = this, + clr = SpriteMorph.prototype.blockColor[this.category]; + + + block = new CommandBlockMorph(); + block.setColor(clr); + block.setSpec(localize('Command')); + this.addBlockTypeButton( + function () {myself.setType('command'); }, + block, + function () {return myself.blockType === 'command'; } + ); + + block = new ReporterBlockMorph(); + block.setColor(clr); + block.setSpec(localize('Reporter')); + this.addBlockTypeButton( + function () {myself.setType('reporter'); }, + block, + function () {return myself.blockType === 'reporter'; } + ); + + block = new ReporterBlockMorph(true); + block.setColor(clr); + block.setSpec(localize('Predicate')); + this.addBlockTypeButton( + function () {myself.setType('predicate'); }, + block, + function () {return myself.blockType === 'predicate'; } + ); +}; + +BlockDialogMorph.prototype.addBlockTypeButton = function ( + action, + element, + query +) { + var button = new ToggleElementMorph( + this, + action, + element, + query, + null, + null, + 'rebuild' + ); + button.refresh(); + this.types.add(button); + return button; +}; + +BlockDialogMorph.prototype.addTypeButton = function (action, label, query) { + var button = new ToggleMorph( + 'radiobutton', + this, + action, + label, + query + ); + button.edge = this.buttonEdge / 2; + button.outline = this.buttonOutline / 2; + button.outlineColor = this.buttonOutlineColor; + button.outlineGradient = this.buttonOutlineGradient; + button.contrast = this.buttonContrast; + + button.drawNew(); + button.fixLayout(); + this.types.add(button); + return button; +}; + +BlockDialogMorph.prototype.setType = function (blockType) { + this.blockType = blockType || this.blockType; + this.types.children.forEach(function (c) { + c.refresh(); + }); + this.edit(); +}; + +// scope radio buttons + +BlockDialogMorph.prototype.createScopeButtons = function () { + var myself = this; + + this.addScopeButton( + function () {myself.setScope('gobal'); }, + "for all sprites", + function () {return myself.isGlobal; } + ); + this.addScopeButton( + function () {myself.setScope('local'); }, + "for this sprite only", + function () {return !myself.isGlobal; } + ); +}; + +BlockDialogMorph.prototype.addScopeButton = function (action, label, query) { + var button = new ToggleMorph( + 'radiobutton', + this, + action, + label, + query + ); + button.edge = this.buttonEdge / 2; + button.outline = this.buttonOutline / 2; + button.outlineColor = this.buttonOutlineColor; + button.outlineGradient = this.buttonOutlineGradient; + button.contrast = this.buttonContrast; + + button.drawNew(); + button.fixLayout(); + this.scopes.add(button); + return button; +}; + + +BlockDialogMorph.prototype.setScope = function (varType) { + this.isGlobal = (varType === 'gobal'); + this.scopes.children.forEach(function (c) { + c.refresh(); + }); + this.edit(); +}; + +// other ops + +BlockDialogMorph.prototype.getInput = function () { + var spec, def, body; + if (this.body instanceof InputFieldMorph) { + spec = this.normalizeSpaces(this.body.getValue()); + } + def = new CustomBlockDefinition(spec); + def.type = this.blockType; + def.category = this.category; + def.isGlobal = this.isGlobal; + if (def.type === 'reporter' || def.type === 'predicate') { + body = Process.prototype.reify.call( + null, + SpriteMorph.prototype.blockForSelector('doReport'), + new List(), + true // ignore empty slots for custom block reification + ); + body.outerContext = null; + def.body = body; + } + return def; +}; + +BlockDialogMorph.prototype.fixLayout = function () { + var th = fontHeight(this.titleFontSize) + this.titlePadding * 2; + + if (this.body) { + this.body.setPosition(this.position().add(new Point( + this.padding, + th + this.padding + ))); + this.silentSetWidth(this.body.width() + this.padding * 2); + this.silentSetHeight( + this.body.height() + + this.padding * 2 + + th + ); + if (this.categories) { + this.categories.setCenter(this.body.center()); + this.categories.setTop(this.body.top()); + this.body.setTop(this.categories.bottom() + this.padding); + this.silentSetHeight( + this.height() + + this.categories.height() + + this.padding + ); + } + } else if (this.head) { // when changing an existing prototype + if (this.types) { + this.types.fixLayout(); + this.silentSetWidth( + Math.max(this.types.width(), this.head.width()) + + this.padding * 2 + ); + } else { + this.silentSetWidth( + Math.max(this.categories.width(), this.head.width()) + + this.padding * 2 + ); + } + this.head.setCenter(this.center()); + this.head.setTop(th + this.padding); + this.silentSetHeight( + this.head.height() + + this.padding * 2 + + th + ); + if (this.categories) { + this.categories.setCenter(this.center()); + this.categories.setTop(this.head.bottom() + this.padding); + this.silentSetHeight( + this.height() + + this.categories.height() + + this.padding + ); + } + } + + if (this.label) { + this.label.setCenter(this.center()); + this.label.setTop(this.top() + (th - this.label.height()) / 2); + } + + if (this.types) { + this.types.fixLayout(); + this.silentSetHeight( + this.height() + + this.types.height() + + this.padding + ); + this.silentSetWidth(Math.max( + this.width(), + this.types.width() + this.padding * 2 + )); + this.types.setCenter(this.center()); + if (this.body) { + this.types.setTop(this.body.bottom() + this.padding); + } else if (this.categories) { + this.types.setTop(this.categories.bottom() + this.padding); + } + } + + if (this.scopes) { + this.scopes.fixLayout(); + this.silentSetHeight( + this.height() + + this.scopes.height() + + (this.padding / 3) + ); + this.silentSetWidth(Math.max( + this.width(), + this.scopes.width() + this.padding * 2 + )); + this.scopes.setCenter(this.center()); + if (this.types) { + this.scopes.setTop(this.types.bottom() + (this.padding / 3)); + } + } + + if (this.buttons && (this.buttons.children.length > 0)) { + this.buttons.fixLayout(); + this.silentSetHeight( + this.height() + + this.buttons.height() + + this.padding + ); + this.buttons.setCenter(this.center()); + this.buttons.setBottom(this.bottom() - this.padding); + } +}; + +// BlockEditorMorph //////////////////////////////////////////////////// + +// BlockEditorMorph inherits from DialogBoxMorph: + +BlockEditorMorph.prototype = new DialogBoxMorph(); +BlockEditorMorph.prototype.constructor = BlockEditorMorph; +BlockEditorMorph.uber = DialogBoxMorph.prototype; + +// BlockEditorMorph instance creation: + +function BlockEditorMorph(definition, target) { + this.init(definition, target); +} + +BlockEditorMorph.prototype.init = function (definition, target) { + var scripts, proto, scriptsFrame, block, comment, myself = this; + + // additional properties: + this.definition = definition; + this.handle = null; + + // initialize inherited properties: + BlockEditorMorph.uber.init.call( + this, + target, + function () {myself.updateDefinition(); }, + target + ); + + // override inherited properites: + this.key = 'editBlock' + definition.spec; + this.labelString = 'Block Editor'; + this.createLabel(); + + // create scripting area + scripts = new ScriptsMorph(target); + scripts.isDraggable = false; + scripts.color = IDE_Morph.prototype.groupColor; + scripts.texture = IDE_Morph.prototype.scriptsPaneTexture; + scripts.cleanUpMargin = 10; + + proto = new PrototypeHatBlockMorph(this.definition); + proto.setPosition(scripts.position().add(10)); + + if (definition.comment !== null) { + comment = definition.comment.fullCopy(); + proto.comment = comment; + comment.block = proto; + } + + if (definition.body !== null) { + proto.nextBlock(definition.body.expression.fullCopy()); + } + + scripts.add(proto); + proto.fixBlockColor(null, true); + + this.definition.scripts.forEach(function (element) { + block = element.fullCopy(); + block.setPosition(scripts.position().add(element.position())); + scripts.add(block); + if (block instanceof BlockMorph) { + block.allComments().forEach(function (comment) { + comment.align(block); + }); + } + }); + proto.allComments().forEach(function (comment) { + comment.align(proto); + }); + + scriptsFrame = new ScrollFrameMorph(scripts); + scriptsFrame.padding = 10; + scriptsFrame.growth = 50; + scriptsFrame.isDraggable = false; + scriptsFrame.acceptsDrops = false; + scriptsFrame.contents.acceptsDrops = true; + scripts.scrollFrame = scriptsFrame; + + this.addBody(scriptsFrame); + this.addButton('ok', 'OK'); + this.addButton('updateDefinition', 'Apply'); + this.addButton('cancel', 'Cancel'); + + this.setExtent(new Point(375, 300)); + this.fixLayout(); + proto.children[0].fixLayout(); + scripts.fixMultiArgs(); +}; + +BlockEditorMorph.prototype.popUp = function () { + var world = this.target.world(); + + if (world) { + BlockEditorMorph.uber.popUp.call(this, world); + this.handle = new HandleMorph( + this, + 280, + 220, + this.corner, + this.corner + ); + } +}; + +// BlockEditorMorph ops + +BlockEditorMorph.prototype.accept = function () { + // check DialogBoxMorph comment for accept() + if (this.action) { + if (typeof this.target === 'function') { + if (typeof this.action === 'function') { + this.target.call(this.environment, this.action.call()); + } else { + this.target.call(this.environment, this.action); + } + } else { + if (typeof this.action === 'function') { + this.action.call(this.target, this.getInput()); + } else { // assume it's a String + this.target[this.action](this.getInput()); + } + } + } + this.close(); +}; + +BlockEditorMorph.prototype.cancel = function () { + //this.refreshAllBlockInstances(); + this.close(); +}; + +BlockEditorMorph.prototype.close = function () { + // allow me to disappear only when name collisions + // have been resolved + var doubles, block, + myself = this; + doubles = this.target.doubleDefinitionsFor(this.definition); + if (doubles.length > 0) { + block = doubles[0].blockInstance(); + block.addShadow(); + new DialogBoxMorph(this, 'consolidateDoubles', this).askYesNo( + 'Same Named Blocks', + 'Another custom block with this name exists.\n' + + 'Would you like to replace it?', + myself.world(), + block.fullImage() + ); + return; + } + this.destroy(); +}; + +BlockEditorMorph.prototype.consolidateDoubles = function () { + this.target.replaceDoubleDefinitionsFor(this.definition); + this.destroy(); +}; + +BlockEditorMorph.prototype.refreshAllBlockInstances = function () { + var template = this.target.paletteBlockInstance(this.definition); + + this.target.allBlockInstances(this.definition).forEach( + function (block) { + block.refresh(); + } + ); + if (template) { + template.refreshDefaults(); + } +}; + +BlockEditorMorph.prototype.updateDefinition = function () { + var head, ide, + pos = this.body.contents.position(), + element, + myself = this; + + this.definition.receiver = this.target; // only for serialization + this.definition.spec = this.prototypeSpec(); + this.definition.declarations = this.prototypeSlots(); + this.definition.scripts = []; + + this.body.contents.children.forEach(function (morph) { + if (morph instanceof PrototypeHatBlockMorph) { + head = morph; + } else if (morph instanceof BlockMorph || + (morph instanceof CommentMorph && !morph.block)) { + element = morph.fullCopy(); + element.parent = null; + element.setPosition(morph.position().subtract(pos)); + myself.definition.scripts.push(element); + } + }); + + if (head) { + this.definition.category = head.blockCategory; + this.definition.type = head.type; + if (head.comment) { + this.definition.comment = head.comment.fullCopy(); + this.definition.comment.block = true; // serialize in short form + } else { + this.definition.comment = null; + } + } + + this.definition.body = this.context(head); + this.refreshAllBlockInstances(); + + ide = this.target.parentThatIsA(IDE_Morph); + ide.flushPaletteCache(); + ide.refreshPalette(); +}; + +BlockEditorMorph.prototype.context = function (prototypeHat) { + // answer my script reified for deferred execution + // if no prototypeHat is given, my body is scanned + var head, topBlock, stackFrame; + + head = prototypeHat || detect( + this.body.contents.children, + function (c) {return c instanceof PrototypeHatBlockMorph; } + ); + topBlock = head.nextBlock(); + if (topBlock === null) { + return null; + } + stackFrame = Process.prototype.reify.call( + null, + topBlock, + new List(this.definition.inputNames()), + true // ignore empty slots for custom block reification + ); + stackFrame.outerContext = null; //; + return stackFrame; +}; + +BlockEditorMorph.prototype.prototypeSpec = function () { + // answer the spec represented by my (edited) block prototype + return detect( + this.body.contents.children, + function (c) {return c instanceof PrototypeHatBlockMorph; } + ).parts()[0].specFromFragments(); +}; + +BlockEditorMorph.prototype.prototypeSlots = function () { + // answer the slot declarations from my (edited) block prototype + return detect( + this.body.contents.children, + function (c) {return c instanceof PrototypeHatBlockMorph; } + ).parts()[0].declarationsFromFragments(); +}; + +// BlockEditorMorph layout + +BlockEditorMorph.prototype.fixLayout = function () { + var th = fontHeight(this.titleFontSize) + this.titlePadding * 2; + + if (this.buttons && (this.buttons.children.length > 0)) { + this.buttons.fixLayout(); + } + + if (this.body) { + this.body.setPosition(this.position().add(new Point( + this.padding, + th + this.padding + ))); + this.body.setExtent(new Point( + this.width() - this.padding * 2, + this.height() - this.padding * 3 - th - this.buttons.height() + )); + } + + if (this.label) { + this.label.setCenter(this.center()); + this.label.setTop(this.top() + (th - this.label.height()) / 2); + } + + if (this.buttons && (this.buttons.children.length > 0)) { + this.buttons.setCenter(this.center()); + this.buttons.setBottom(this.bottom() - this.padding); + } +}; + +// PrototypeHatBlockMorph ///////////////////////////////////////////// + +// PrototypeHatBlockMorph inherits from HatBlockMorph: + +PrototypeHatBlockMorph.prototype = new HatBlockMorph(); +PrototypeHatBlockMorph.prototype.constructor = PrototypeHatBlockMorph; +PrototypeHatBlockMorph.uber = HatBlockMorph.prototype; + +// PrototypeHatBlockMorph instance creation: + +function PrototypeHatBlockMorph(definition) { + this.init(definition); +} + +PrototypeHatBlockMorph.prototype.init = function (definition) { + var proto = definition.prototypeInstance(); + + this.definition = definition; + + // additional attributes to store edited data + this.blockCategory = definition ? definition.category : null; + this.type = definition ? definition.type : null; + + // init inherited stuff + HatBlockMorph.uber.init.call(this); + this.color = SpriteMorph.prototype.blockColor.control; + this.category = 'control'; + this.add(proto); + proto.refreshPrototypeSlotTypes(); // show slot type indicators + this.fixLayout(); +}; + +PrototypeHatBlockMorph.prototype.mouseClickLeft = function () { + // relay the mouse click to my prototype block to + // pop-up a Block Dialog + + this.children[0].mouseClickLeft(); +}; + +PrototypeHatBlockMorph.prototype.userMenu = function () { + return this.children[0].userMenu(); +}; + +// PrototypeHatBlockMorph zebra coloring + +PrototypeHatBlockMorph.prototype.fixBlockColor = function ( + nearestBlock, + isForced +) { + var nearest = this.children[0] || nearestBlock; + + if (!this.zebraContrast && !isForced) { + return; + } + if (!this.zebraContrast && isForced) { + return this.forceNormalColoring(); + } + + if (nearest.category === this.category) { + if (nearest.color.eq(this.color)) { + this.alternateBlockColor(); + } + } else if (this.category && !this.color.eq( + SpriteMorph.prototype.blockColor[this.category] + )) { + this.alternateBlockColor(); + } + if (isForced) { + this.fixChildrensBlockColor(true); + } +}; + +// BlockLabelFragment ////////////////////////////////////////////////// + +// BlockLabelFragment instance creation: + +function BlockLabelFragment(labelString) { + this.labelString = labelString || ''; + this.type = '%s'; // null for label, a spec for an input + this.defaultValue = ''; + this.isDeleted = false; +} + +// accessing + +BlockLabelFragment.prototype.defSpecFragment = function () { + // answer a string representing my prototype's spec + var pref = this.type ? '%\'' : ''; + return this.isDeleted ? + '' : pref + this.labelString + (this.type ? '\'' : ''); +}; + +BlockLabelFragment.prototype.defTemplateSpecFragment = function () { + // answer a string representing my prototype's spec + // which also indicates my type, default value or arity + var suff = ''; + if (!this.type) {return this.defSpecFragment(); } + if (this.isUpvar()) { + suff = ' \u2191'; + } else if (this.isMultipleInput()) { + suff = '...'; + } else if (this.type === '%cs') { + suff = ' \u03BB'; // ' [\u03BB' + } else if (this.type === '%b') { + suff = ' ?'; + } else if (this.type === '%l') { + suff = ' \uFE19'; + } else if (this.type === '%obj') { + suff = ' %turtleOutline'; + } else if (contains( + ['%cmdRing', '%repRing', '%predRing', '%anyUE', '%boolUE'], + this.type + )) { + suff = ' \u03BB'; + } else if (this.defaultValue) { + suff = ' = ' + this.defaultValue.toString(); + } + return this.labelString + suff; +}; + +BlockLabelFragment.prototype.blockSpecFragment = function () { + // answer a string representing my block spec + return this.isDeleted ? '' : this.type || this.labelString; +}; + +BlockLabelFragment.prototype.copy = function () { + var ans = new BlockLabelFragment(this.labelString); + ans.type = this.type; + ans.defaultValue = this.defaultValue; + return ans; +}; + +// arity + +BlockLabelFragment.prototype.isSingleInput = function () { + return !this.isMultipleInput() && + (this.type !== '%upvar'); +}; + +BlockLabelFragment.prototype.isMultipleInput = function () { + // answer true if the type begins with '%mult' + if (!this.type) { + return false; // not an input at all + } + return this.type.indexOf('%mult') > -1; +}; + +BlockLabelFragment.prototype.isUpvar = function () { + if (!this.type) { + return false; // not an input at all + } + return this.type === '%upvar'; +}; + +BlockLabelFragment.prototype.setToSingleInput = function () { + if (!this.type) {return null; } // not an input at all + if (this.type === '%upvar') { + this.type = '%s'; + } else { + this.type = this.singleInputType(); + } +}; + +BlockLabelFragment.prototype.setToMultipleInput = function () { + if (!this.type) {return null; } // not an input at all + if (this.type === '%upvar') { + this.type = '%s'; + } + this.type = '%mult'.concat(this.singleInputType()); +}; + +BlockLabelFragment.prototype.setToUpvar = function () { + if (!this.type) {return null; } // not an input at all + this.type = '%upvar'; +}; + +BlockLabelFragment.prototype.singleInputType = function () { + // answer the type of my input withtou any preceding '%mult' + if (!this.type) { + return null; // not an input at all + } + if (this.isMultipleInput()) { + return this.type.substr(5); // everything following '%mult' + } + return this.type; +}; + +BlockLabelFragment.prototype.setSingleInputType = function (type) { + if (!this.type || !this.isMultipleInput()) { + this.type = type; + } else { + this.type = '%mult'.concat(type); + } +}; + +// BlockLabelFragmentMorph /////////////////////////////////////////////// + +/* + I am a single word in a custom block prototype's label. I can be clicked + to edit my contents and to turn me into an input placeholder. +*/ + +// BlockLabelFragmentMorph inherits from StringMorph: + +BlockLabelFragmentMorph.prototype = new StringMorph(); +BlockLabelFragmentMorph.prototype.constructor = BlockLabelFragmentMorph; +BlockLabelFragmentMorph.uber = StringMorph.prototype; + +// BlockLabelFragmentMorph instance creation: + +function BlockLabelFragmentMorph(text) { + this.init(text); +} + +BlockLabelFragmentMorph.prototype.init = function (text) { + this.fragment = new BlockLabelFragment(text); + this.fragment.type = null; + this.sO = null; // temporary backup for shadowOffset + BlockLabelFragmentMorph.uber.init.call( + this, + text, + null, // font size + SyntaxElementMorph.prototype.labelFontStyle, + null, // bold + null, // italic + null, // numeric + null, // shadow offset + null, // shadow color + null, // color + SyntaxElementMorph.prototype.labelFontName + ); +}; + +// BlockLabelFragmentMorph events: + +BlockLabelFragmentMorph.prototype.mouseEnter = function () { + this.sO = this.shadowOffset; + this.shadowOffset = this.sO.neg(); + this.drawNew(); + this.changed(); +}; + +BlockLabelFragmentMorph.prototype.mouseLeave = function () { + this.shadowOffset = this.sO; + this.drawNew(); + this.changed(); +}; + +BlockLabelFragmentMorph.prototype.mouseClickLeft = function () { +/* + make a copy of my fragment object and open an InputSlotDialog on it. + If the user acknowledges the DialogBox, assign the - edited - copy + of the fragment object to be my new fragment object and update the + custom block'label (the prototype in the block editor). Do not yet update + the definition and every block instance, as this happens only after + the user acknowledges and closes the block editor +*/ + var frag = this.fragment.copy(), + myself = this, + isPlaceHolder = this instanceof BlockLabelPlaceHolderMorph, + isOnlyElement = this.parent.parseSpec(this.parent.blockSpec).length + < 2; + + new InputSlotDialogMorph( + frag, + null, + function () {myself.updateBlockLabel(frag); }, + this, + this.parent.definition.category + ).open( + this instanceof BlockLabelFragmentMorph ? + 'Edit label fragment' : + isPlaceHolder ? 'Create input name' : 'Edit input name', + frag.labelString, + this.world(), + null, + isPlaceHolder || isOnlyElement + ); +}; + +BlockLabelFragmentMorph.prototype.updateBlockLabel = function (newFragment) { + var prot = this.parentThatIsA(BlockMorph); + + this.fragment = newFragment; + if (prot) { + prot.refreshPrototype(); + } +}; + +// BlockLabelPlaceHolderMorph /////////////////////////////////////////////// + +/* + I am a space between words or inputs in a custom block prototype's label. + When I am moused over I display a plus sign on a colored background + circle. I can be clicked to add a new word or input to the prototype. +*/ + +// BlockLabelPlaceHolderMorph inherits from StringMorph: + +BlockLabelPlaceHolderMorph.prototype = new StringMorph(); +BlockLabelPlaceHolderMorph.prototype.constructor = BlockLabelPlaceHolderMorph; +BlockLabelPlaceHolderMorph.uber = StringMorph.prototype; + +// BlockLabelPlaceHolderMorph instance creation: + +function BlockLabelPlaceHolderMorph() { + this.init(); +} + +BlockLabelPlaceHolderMorph.prototype.init = function () { + this.fragment = new BlockLabelFragment(''); + this.fragment.type = '%s'; + this.fragment.isDeleted = true; + this.isHighlighted = false; + this.isProtectedLabel = true; // doesn't participate in zebra coloring + BlockLabelFragmentMorph.uber.init.call(this, '+'); +}; + +// BlockLabelPlaceHolderMorph drawing + +BlockLabelPlaceHolderMorph.prototype.drawNew = function () { + var context, width, x, y, cx, cy; + + // initialize my surface property + this.image = newCanvas(); + context = this.image.getContext('2d'); + context.font = this.font(); + + // set my extent + width = Math.max( + context.measureText(this.text).width + + Math.abs(this.shadowOffset.x), + 1 + ); + this.bounds.corner = this.bounds.origin.add( + new Point( + width, + fontHeight(this.fontSize) + Math.abs(this.shadowOffset.y) + ) + ); + this.image.width = width; + this.image.height = this.height(); + + // draw background, if any + if (this.isHighlighted) { + cx = Math.floor(width / 2); + cy = Math.floor(this.height() / 2); + context.fillStyle = this.color.toString(); + context.beginPath(); + context.arc( + cx, + cy * 1.2, + Math.min(cx, cy), + radians(0), + radians(360), + false + ); + context.closePath(); + context.fill(); + } + + // prepare context for drawing text + context.font = this.font(); + context.textAlign = 'left'; + context.textBaseline = 'bottom'; + + // first draw the shadow, if any + if (this.shadowColor) { + x = Math.max(this.shadowOffset.x, 0); + y = Math.max(this.shadowOffset.y, 0); + context.fillStyle = this.shadowColor.toString(); + context.fillText(this.text, x, fontHeight(this.fontSize) + y); + } + + // now draw the actual text + x = Math.abs(Math.min(this.shadowOffset.x, 0)); + y = Math.abs(Math.min(this.shadowOffset.y, 0)); + context.fillStyle = this.isHighlighted ? + 'white' : this.color.toString(); + context.fillText(this.text, x, fontHeight(this.fontSize) + y); + + // notify my parent of layout change + if (this.parent) { + if (this.parent.fixLayout) { + this.parent.fixLayout(); + } + } +}; + +// BlockLabelPlaceHolderMorph events: + +BlockLabelPlaceHolderMorph.prototype.mouseEnter = function () { + this.isHighlighted = true; + this.drawNew(); + this.changed(); +}; + +BlockLabelPlaceHolderMorph.prototype.mouseLeave = function () { + this.isHighlighted = false; + this.drawNew(); + this.changed(); +}; + +BlockLabelPlaceHolderMorph.prototype.mouseClickLeft + = BlockLabelFragmentMorph.prototype.mouseClickLeft; + +BlockLabelPlaceHolderMorph.prototype.updateBlockLabel + = BlockLabelFragmentMorph.prototype.updateBlockLabel; + +// BlockInputFragmentMorph /////////////////////////////////////////////// + +/* + I am a variable blob in a custom block prototype's label. I can be clicked + to edit my contents and to turn me into an part of the block's label text. +*/ + +// BlockInputFragmentMorph inherits from TemplateSlotMorph: + +BlockInputFragmentMorph.prototype = new TemplateSlotMorph(); +BlockInputFragmentMorph.prototype.constructor = BlockInputFragmentMorph; +BlockInputFragmentMorph.uber = TemplateSlotMorph.prototype; + +// BlockInputFragmentMorph instance creation: + +function BlockInputFragmentMorph(text) { + this.init(text); +} + +BlockInputFragmentMorph.prototype.init = function (text) { + this.fragment = new BlockLabelFragment(text); + this.fragment.type = '%s'; + BlockInputFragmentMorph.uber.init.call(this, text); +}; + +// BlockInputFragmentMorph events: + +BlockInputFragmentMorph.prototype.mouseClickLeft + = BlockLabelFragmentMorph.prototype.mouseClickLeft; + +BlockInputFragmentMorph.prototype.updateBlockLabel + = BlockLabelFragmentMorph.prototype.updateBlockLabel; + +// InputSlotDialogMorph //////////////////////////////////////////////// + +// ... "inherits" some methods from BlockDialogMorph + +// InputSlotDialogMorph inherits from DialogBoxMorph: + +InputSlotDialogMorph.prototype = new DialogBoxMorph(); +InputSlotDialogMorph.prototype.constructor = InputSlotDialogMorph; +InputSlotDialogMorph.uber = DialogBoxMorph.prototype; + +// InputSlotDialogMorph preferences settings: + +// if "isLaunchingExpanded" is true I always open in the long form +InputSlotDialogMorph.prototype.isLaunchingExpanded = false; + +// InputSlotDialogMorph instance creation: + +function InputSlotDialogMorph( + fragment, + target, + action, + environment, + category +) { + this.init(fragment, target, action, environment, category); +} + +InputSlotDialogMorph.prototype.init = function ( + fragment, + target, + action, + environment, + category +) { + var scale = SyntaxElementMorph.prototype.scale, + fh = fontHeight(10) / 1.2 * scale; // "raw height" + + // additional properties: + this.fragment = fragment || new BlockLabelFragment(); + this.types = null; + this.slots = null; + this.isExpanded = false; + this.category = category || 'other'; + this.cachedRadioButton = null; // "template" for radio button backgrounds + + // initialize inherited properties: + BlockDialogMorph.uber.init.call( + this, + target, + action, + environment + ); + + // override inherited properites: + this.types = new AlignmentMorph('row', this.padding); + this.types.respectHiddens = true; // prevent the arrow from flipping + this.add(this.types); + this.slots = new BoxMorph(); + this.slots.color = new Color(55, 55, 55); // same as palette + this.slots.borderColor = this.slots.color.lighter(50); + this.slots.setExtent(new Point((fh + 10) * 24, (fh + 10 * scale) * 10.4)); + this.add(this.slots); + this.createSlotTypeButtons(); + this.fixSlotsLayout(); + this.createTypeButtons(); + this.fixLayout(); +}; + +InputSlotDialogMorph.prototype.createTypeButtons = function () { + var block, + arrow, + myself = this, + clr = SpriteMorph.prototype.blockColor[this.category]; + + + block = new JaggedBlockMorph(localize('Title text')); + block.setColor(clr); + this.addBlockTypeButton( + function () {myself.setType(null); }, + block, + function () {return myself.fragment.type === null; } + ); + + block = new JaggedBlockMorph('%inputName'); + block.setColor(clr); + this.addBlockTypeButton( + function () {myself.setType('%s'); }, + block, + function () {return myself.fragment.type !== null; } + ); + + // add an arrow button for long form/short form toggling + arrow = new ArrowMorph( + 'right', + PushButtonMorph.prototype.fontSize + 4, + 2 + ); + arrow.noticesTransparentClick = true; + this.types.add(arrow); + this.types.fixLayout(); + + // configure arrow button + arrow.refresh = function () { + if (myself.fragment.type === null) { + myself.isExpanded = false; + arrow.hide(); + myself.drawNew(); + } else { + arrow.show(); + if (myself.isExpanded) { + arrow.direction = 'down'; + } else { + arrow.direction = 'right'; + } + arrow.drawNew(); + arrow.changed(); + } + }; + + arrow.mouseClickLeft = function () { + if (arrow.isVisible) { + myself.isExpanded = !myself.isExpanded; + myself.types.children.forEach(function (c) { + c.refresh(); + }); + myself.drawNew(); + myself.edit(); + } + }; + + arrow.refresh(); +}; + +InputSlotDialogMorph.prototype.addTypeButton + = BlockDialogMorph.prototype.addTypeButton; + +InputSlotDialogMorph.prototype.addBlockTypeButton + = BlockDialogMorph.prototype.addBlockTypeButton; + +InputSlotDialogMorph.prototype.setType = function (fragmentType) { + this.fragment.type = fragmentType || null; + this.types.children.forEach(function (c) { + c.refresh(); + }); + this.slots.children.forEach(function (c) { + c.refresh(); + }); + this.edit(); +}; + +InputSlotDialogMorph.prototype.getInput = function () { + var lbl; + if (this.body instanceof InputFieldMorph) { + lbl = this.normalizeSpaces(this.body.getValue()); + } + if (lbl) { + this.fragment.labelString = lbl; + this.fragment.defaultValue = this.slots.defaultInputField.getValue(); + return lbl; + } + this.fragment.isDeleted = true; + return null; +}; + +InputSlotDialogMorph.prototype.fixLayout = function () { + var maxWidth, + left = this.left(), + th = fontHeight(this.titleFontSize) + this.titlePadding * 2; + + if (!this.isExpanded) { + if (this.slots) { + this.slots.hide(); + } + return BlockDialogMorph.prototype.fixLayout.call(this); + } + + this.slots.show(); + maxWidth = this.slots.width(); + + // arrange panes : + // body (input field) + this.body.setPosition(this.position().add(new Point( + this.padding + (maxWidth - this.body.width()) / 2, + th + this.padding + ))); + + // label + this.label.setLeft( + left + this.padding + (maxWidth - this.label.width()) / 2 + ); + this.label.setTop(this.top() + (th - this.label.height()) / 2); + + // types + this.types.fixLayout(); + this.types.setTop(this.body.bottom() + this.padding); + this.types.setLeft( + left + this.padding + (maxWidth - this.types.width()) / 2 + ); + + // slots + this.slots.setPosition(new Point( + this.left() + this.padding, + this.types.bottom() + this.padding + )); + this.slots.children.forEach(function (c) { + c.refresh(); + }); + + // buttons + this.buttons.fixLayout(); + this.buttons.setTop(this.slots.bottom() + this.padding); + this.buttons.setLeft( + left + this.padding + (maxWidth - this.buttons.width()) / 2 + ); + + // set dialog box dimensions: + this.silentSetHeight(this.buttons.bottom() - this.top() + this.padding); + this.silentSetWidth(this.slots.right() - this.left() + this.padding); +}; + +InputSlotDialogMorph.prototype.open = function ( + title, + defaultString, + world, + pic, + noDeleteButton +) { + var txt = new InputFieldMorph(defaultString), + oldFlag = Morph.prototype.trackChanges; + + Morph.prototype.trackChanges = false; + this.isExpanded = this.isLaunchingExpanded; + txt.setWidth(250); + this.labelString = title; + this.createLabel(); + if (pic) {this.setPicture(pic); } + this.addBody(txt); + txt.drawNew(); + this.addButton('ok', 'OK'); + if (!noDeleteButton) { + this.addButton('deleteFragment', 'Delete'); + } + this.addButton('cancel', 'Cancel'); + this.fixLayout(); + this.drawNew(); + this.fixLayout(); + this.popUp(world); + this.add(this.types); // make the types come to front + Morph.prototype.trackChanges = oldFlag; + this.changed(); +}; + +InputSlotDialogMorph.prototype.deleteFragment = function () { + this.fragment.isDeleted = true; + this.accept(); +}; + +InputSlotDialogMorph.prototype.createSlotTypeButtons = function () { + // populate my 'slots' area with radio buttons, labels and input fields + var myself = this, defLabel, defInput, + oldFlag = Morph.prototype.trackChanges; + + Morph.prototype.trackChanges = false; + + // slot types + this.addSlotTypeButton('Object', '%obj'); + this.addSlotTypeButton('Text', '%txt'); + this.addSlotTypeButton('List', '%l'); + this.addSlotTypeButton('Number', '%n'); + this.addSlotTypeButton('Any type', '%s'); + this.addSlotTypeButton('Boolean (T/F)', '%b'); + this.addSlotTypeButton('Command\n(inline)', '%cmdRing'); //'%cmd'); + this.addSlotTypeButton('Reporter', '%repRing'); //'%r'); + this.addSlotTypeButton('Predicate', '%predRing'); //'%p'); + this.addSlotTypeButton('Command\n(C-shape)', '%cs'); + this.addSlotTypeButton('Any\n(unevaluated)', '%anyUE'); + this.addSlotTypeButton('Boolean\n(unevaluated)', '%boolUE'); + + // arity and upvars + this.slots.radioButtonSingle = this.addSlotArityButton( + function () {myself.setSlotArity('single'); }, + "Single input.", + function () {return myself.fragment.isSingleInput(); } + ); + this.addSlotArityButton( + function () {myself.setSlotArity('multiple'); }, + "Multiple inputs (value is list of inputs)", + function () {return myself.fragment.isMultipleInput(); } + ); + this.addSlotArityButton( + function () {myself.setSlotArity('upvar'); }, + "Upvar - make internal variable visible to caller", + function () {return myself.fragment.isUpvar(); } + ); + + // default values + defLabel = new StringMorph(localize('Default Value:')); + defLabel.fontSize = this.slots.radioButtonSingle.fontSize; + defLabel.setColor(new Color(255, 255, 255)); + defLabel.refresh = function () { + if (myself.isExpanded && contains( + ['%s', '%n', '%txt', '%anyUE'], + myself.fragment.type + )) { + defLabel.show(); + } else { + defLabel.hide(); + } + }; + this.slots.defaultInputLabel = defLabel; + this.slots.add(defLabel); + + defInput = new InputFieldMorph(this.fragment.defaultValue); + defInput.contents().fontSize = defLabel.fontSize; + defInput.contrast = 90; + defInput.contents().drawNew(); + defInput.setWidth(50); + defInput.refresh = function () { + if (defLabel.isVisible) { + defInput.show(); + if (myself.fragment.type === '%n') { + defInput.setIsNumeric(true); + } else { + defInput.setIsNumeric(false); + } + } else { + defInput.hide(); + } + }; + this.slots.defaultInputField = defInput; + this.slots.add(defInput); + defInput.drawNew(); + + Morph.prototype.trackChanges = oldFlag; +}; + +InputSlotDialogMorph.prototype.setSlotType = function (type) { + this.fragment.setSingleInputType(type); + this.slots.children.forEach(function (c) { + c.refresh(); + }); + this.edit(); +}; + +InputSlotDialogMorph.prototype.setSlotArity = function (arity) { + if (arity === 'single') { + this.fragment.setToSingleInput(); + } else if (arity === 'multiple') { + this.fragment.setToMultipleInput(); + } else if (arity === 'upvar') { + this.fragment.setToUpvar(); + // hide other options - under construction + } + this.slots.children.forEach(function (c) { + c.refresh(); + }); + this.edit(); +}; + +InputSlotDialogMorph.prototype.addSlotTypeButton = function ( + label, + spec +) { +/* + this method produces a radio button with a picture of the + slot type indicated by "spec" and the "label" text to + its right. + Note that you can make the slot picture interactive (turn + it into a ToggleElementMorph by changing the + + element.fullImage() + + line to just + + element + + I've opted for the simpler representation because it reduces + the duration of time it takes for the InputSlotDialog to load + and show. But in the future computers and browsers may be + faster. +*/ + var myself = this, + action = function () {myself.setSlotType(spec); }, + query, + element = new JaggedBlockMorph(spec), + button; + + query = function () { + return myself.fragment.singleInputType() === spec; + }; + element.setCategory(this.category); + element.rebuild(); + button = new ToggleMorph( + 'radiobutton', + this, + action, + label, + query, + null, + null, + this.cachedRadioButton, + element.fullImage(), // delete the "fullImage()" part for interactive + 'rebuild' + ); + button.edge = this.buttonEdge / 2; + button.outline = this.buttonOutline / 2; + button.outlineColor = this.buttonOutlineColor; + button.outlineGradient = this.buttonOutlineGradient; + button.drawNew(); + button.fixLayout(); + button.label.isBold = false; + button.label.setColor(new Color(255, 255, 255)); + if (!this.cachedRadioButton) { + this.cachedRadioButton = button; + } + this.slots.add(button); + return button; +}; + +InputSlotDialogMorph.prototype.addSlotArityButton = function ( + action, + label, + query +) { + var button = new ToggleMorph( + 'radiobutton', + this, + action, + label, + query, + null, + null, + this.cachedRadioButton + ); + button.edge = this.buttonEdge / 2; + button.outline = this.buttonOutline / 2; + button.outlineColor = this.buttonOutlineColor; + button.outlineGradient = this.buttonOutlineGradient; + + button.drawNew(); + button.fixLayout(); + // button.label.isBold = false; + button.label.setColor(new Color(255, 255, 255)); + this.slots.add(button); + if (!this.cachedRadioButton) { + this.cachedRadioButton = button; + } + return button; +}; + +InputSlotDialogMorph.prototype.fixSlotsLayout = function () { + var slots = this.slots, + scale = SyntaxElementMorph.prototype.scale, + xPadding = 10 * scale, + ypadding = 14 * scale, + bh = (fontHeight(10) / 1.2 + 15) * scale, // slot type button height + ah = (fontHeight(10) / 1.2 + 10) * scale, // arity button height + size = 12, // number slot type radio buttons + cols = [ + slots.left() + xPadding, + slots.left() + slots.width() / 3, + slots.left() + slots.width() * 2 / 3 + ], + rows = [ + slots.top() + ypadding, + slots.top() + ypadding + bh, + slots.top() + ypadding + bh * 2, + slots.top() + ypadding + bh * 3, + slots.top() + ypadding + bh * 4, + slots.top() + ypadding + bh * 5, + + slots.top() + ypadding + bh * 5 + ah, + slots.top() + ypadding + bh * 5 + ah * 2 + ], + idx, + row = -1, + col, + oldFlag = Morph.prototype.trackChanges; + + Morph.prototype.trackChanges = false; + + // slot types: + + for (idx = 0; idx < size; idx += 1) { + col = idx % 3; + if (idx % 3 === 0) {row += 1; } + slots.children[idx].setPosition(new Point( + cols[col], + rows[row] + )); + } + + // arity: + + col = 0; + row = 5; + for (idx = size; idx < size + 3; idx += 1) { + slots.children[idx].setPosition(new Point( + cols[col], + rows[row + idx - size] + )); + } + + // default input + + this.slots.defaultInputLabel.setPosition( + this.slots.radioButtonSingle.label.topRight().add(new Point(5, 0)) + ); + this.slots.defaultInputField.setCenter( + this.slots.defaultInputLabel.center().add(new Point( + this.slots.defaultInputField.width() / 2 + + this.slots.defaultInputLabel.width() / 2 + 5, + 0 + )) + ); + Morph.prototype.trackChanges = oldFlag; + this.slots.changed(); +}; + +// InputSlotDialogMorph hiding and showing: + +/* + override the inherited behavior to recursively hide/show all + children, so that my instances get restored correctly when + hiding/showing my parent. +*/ + +InputSlotDialogMorph.prototype.hide = function () { + this.isVisible = false; + this.changed(); +}; + +InputSlotDialogMorph.prototype.show = function () { + this.isVisible = true; + this.changed(); +}; + +// VariableDialogMorph //////////////////////////////////////////////////// + +// VariableDialogMorph inherits from DialogBoxMorph: + +VariableDialogMorph.prototype = new DialogBoxMorph(); +VariableDialogMorph.prototype.constructor = VariableDialogMorph; +VariableDialogMorph.uber = DialogBoxMorph.prototype; + +// ... and some behavior from BlockDialogMorph + +// VariableDialogMorph instance creation: + +function VariableDialogMorph(target, action, environment) { + this.init(target, action, environment); +} + +VariableDialogMorph.prototype.init = function (target, action, environment) { + // additional properties: + this.types = null; + this.isGlobal = true; + + // initialize inherited properties: + BlockDialogMorph.uber.init.call( + this, + target, + action, + environment + ); + + // override inherited properites: + this.types = new AlignmentMorph('row', this.padding); + this.add(this.types); + this.createTypeButtons(); +}; + +VariableDialogMorph.prototype.createTypeButtons = function () { + var myself = this; + + this.addTypeButton( + function () {myself.setType('gobal'); }, + "for all sprites", + function () {return myself.isGlobal; } + ); + this.addTypeButton( + function () {myself.setType('local'); }, + "for this sprite only", + function () {return !myself.isGlobal; } + ); +}; + +VariableDialogMorph.prototype.addTypeButton + = BlockDialogMorph.prototype.addTypeButton; + +VariableDialogMorph.prototype.setType = function (varType) { + this.isGlobal = (varType === 'gobal'); + this.types.children.forEach(function (c) { + c.refresh(); + }); + this.edit(); +}; + +VariableDialogMorph.prototype.getInput = function () { + // answer a tuple: [varName, isGlobal] + var name = this.normalizeSpaces(this.body.getValue()); + return name ? [name, this.isGlobal] : null; +}; + +VariableDialogMorph.prototype.fixLayout = function () { + var th = fontHeight(this.titleFontSize) + this.titlePadding * 2; + + if (this.body) { + this.body.setPosition(this.position().add(new Point( + this.padding, + th + this.padding + ))); + this.silentSetWidth(this.body.width() + this.padding * 2); + this.silentSetHeight( + this.body.height() + + this.padding * 2 + + th + ); + } + + if (this.label) { + this.label.setCenter(this.center()); + this.label.setTop(this.top() + (th - this.label.height()) / 2); + } + + if (this.types) { + this.types.fixLayout(); + this.silentSetHeight( + this.height() + + this.types.height() + + this.padding + ); + this.silentSetWidth(Math.max( + this.width(), + this.types.width() + this.padding * 2 + )); + this.types.setCenter(this.center()); + if (this.body) { + this.types.setTop(this.body.bottom() + this.padding); + } else if (this.categories) { + this.types.setTop(this.categories.bottom() + this.padding); + } + } + + if (this.buttons && (this.buttons.children.length > 0)) { + this.buttons.fixLayout(); + this.silentSetHeight( + this.height() + + this.buttons.height() + + this.padding + ); + this.buttons.setCenter(this.center()); + this.buttons.setBottom(this.bottom() - this.padding); + } +}; + +// BlockExportDialogMorph //////////////////////////////////////////////////// + +// BlockExportDialogMorph inherits from DialogBoxMorph: + +BlockExportDialogMorph.prototype = new DialogBoxMorph(); +BlockExportDialogMorph.prototype.constructor = BlockExportDialogMorph; +BlockExportDialogMorph.uber = DialogBoxMorph.prototype; + +// BlockExportDialogMorph constants: + +BlockExportDialogMorph.prototype.key = 'blockExport'; + +// BlockExportDialogMorph instance creation: + +function BlockExportDialogMorph(serializer, blocks) { + this.init(serializer, blocks); +} + +BlockExportDialogMorph.prototype.init = function (serializer, blocks) { + var myself = this; + + // additional properties: + this.serializer = serializer; + this.blocks = blocks.slice(0); + this.handle = null; + + // initialize inherited properties: + BlockExportDialogMorph.uber.init.call( + this, + null, // target + function () {myself.exportBlocks(); }, + null // environment + ); + + // override inherited properites: + this.labelString = 'Export blocks'; + this.createLabel(); + + // build contents + this.buildContents(); +}; + +BlockExportDialogMorph.prototype.buildContents = function () { + var palette, x, y, block, checkBox, lastCat, + myself = this, + padding = 4; + + // create plaette + palette = new ScrollFrameMorph( + null, + null, + SpriteMorph.prototype.sliderColor + ); + palette.color = SpriteMorph.prototype.paletteColor; + palette.padding = padding; + palette.isDraggable = false; + palette.acceptsDrops = false; + palette.contents.acceptsDrops = false; + + // populate palette + x = palette.left() + padding; + y = palette.top() + padding; + SpriteMorph.prototype.categories.forEach(function (category) { + myself.blocks.forEach(function (definition) { + if (definition.category === category) { + if (lastCat && (category !== lastCat)) { + y += padding; + } + lastCat = category; + block = definition.templateInstance(); + checkBox = new ToggleMorph( + 'checkbox', + myself, + function () { + var idx = myself.blocks.indexOf(definition); + if (idx > -1) { + myself.blocks.splice(idx, 1); + } else { + myself.blocks.push(definition); + } + }, + null, + function () { + return contains( + myself.blocks, + definition + ); + }, + null, + null, + null, + block.fullImage() + ); + checkBox.setPosition(new Point( + x, + y + (checkBox.top() - checkBox.toggleElement.top()) + )); + palette.addContents(checkBox); + y += checkBox.fullBounds().height() + padding; + } + }); + }); + + palette.scrollX(padding); + palette.scrollY(padding); + this.addBody(palette); + + this.addButton('ok', 'OK'); + this.addButton('cancel', 'Cancel'); + + this.setExtent(new Point(220, 300)); + this.fixLayout(); + +}; + +BlockExportDialogMorph.prototype.popUp = function (wrrld) { + var world = wrrld || this.target.world(); + if (world) { + BlockExportDialogMorph.uber.popUp.call(this, world); + this.handle = new HandleMorph( + this, + 200, + 220, + this.corner, + this.corner + ); + } +}; + +// BlockExportDialogMorph menu + +BlockExportDialogMorph.prototype.userMenu = function () { + var menu = new MenuMorph(this, 'select'); + menu.addItem('all', 'selectAll'); + menu.addItem('none', 'selectNone'); + return menu; +}; + +BlockExportDialogMorph.prototype.selectAll = function () { + this.body.contents.children.forEach(function (checkBox) { + if (!checkBox.state) { + checkBox.trigger(); + } + }); +}; + +BlockExportDialogMorph.prototype.selectNone = function () { + this.blocks = []; + this.body.contents.children.forEach(function (checkBox) { + checkBox.refresh(); + }); +}; + +// BlockExportDialogMorph ops + +BlockExportDialogMorph.prototype.exportBlocks = function () { + var str = this.serializer.serialize(this.blocks); + if (this.blocks.length > 0) { + window.open('data:text/xml,' + + str + + ''); + } else { + new DialogBoxMorph().inform( + 'Export blocks', + 'no blocks were selected', + this.world() + ); + } +}; + +// BlockExportDialogMorph layout + +BlockExportDialogMorph.prototype.fixLayout + = BlockEditorMorph.prototype.fixLayout; + +// BlockImportDialogMorph //////////////////////////////////////////////////// + +// BlockImportDialogMorph inherits from DialogBoxMorph +// and pseudo-inherits from BlockExportDialogMorph: + +BlockImportDialogMorph.prototype = new DialogBoxMorph(); +BlockImportDialogMorph.prototype.constructor = BlockImportDialogMorph; +BlockImportDialogMorph.uber = DialogBoxMorph.prototype; + +// BlockImportDialogMorph constants: + +BlockImportDialogMorph.prototype.key = 'blockImport'; + +// BlockImportDialogMorph instance creation: + +function BlockImportDialogMorph(blocks, target, name) { + this.init(blocks, target, name); +} + +BlockImportDialogMorph.prototype.init = function (blocks, target, name) { + var myself = this; + + // additional properties: + this.blocks = blocks.slice(0); + this.handle = null; + + // initialize inherited properties: + BlockExportDialogMorph.uber.init.call( + this, + target, + function () {myself.importBlocks(name); }, + null // environment + ); + + // override inherited properites: + this.labelString = localize('Import blocks') + + (name ? ': ' : '') + + name || ''; + this.createLabel(); + + // build contents + this.buildContents(); +}; + +BlockImportDialogMorph.prototype.buildContents + = BlockExportDialogMorph.prototype.buildContents; + +BlockImportDialogMorph.prototype.popUp + = BlockExportDialogMorph.prototype.popUp; + +// BlockImportDialogMorph menu + +BlockImportDialogMorph.prototype.userMenu + = BlockExportDialogMorph.prototype.userMenu; + +BlockImportDialogMorph.prototype.selectAll + = BlockExportDialogMorph.prototype.selectAll; + +BlockImportDialogMorph.prototype.selectNone + = BlockExportDialogMorph.prototype.selectNone; + +// BlockImportDialogMorph ops + +BlockImportDialogMorph.prototype.importBlocks = function (name) { + var ide = this.target.parentThatIsA(IDE_Morph); + if (!ide) {return; } + if (this.blocks.length > 0) { + this.blocks.forEach(function (def) { + def.receiver = ide.stage; + ide.stage.globalBlocks.push(def); + ide.stage.replaceDoubleDefinitionsFor(def); + }); + ide.flushPaletteCache(); + ide.refreshPalette(); + ide.showMessage( + 'Imported Blocks Module' + (name ? ': ' + name : '') + '.', + 2 + ); + } else { + new DialogBoxMorph().inform( + 'Import blocks', + 'no blocks were selected', + this.world() + ); + } +}; + +// BlockImportDialogMorph layout + +BlockImportDialogMorph.prototype.fixLayout + = BlockEditorMorph.prototype.fixLayout; diff --git a/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/click.wav b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/click.wav new file mode 100644 index 0000000..74b9214 Binary files /dev/null and b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/click.wav differ diff --git a/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/cloud.js b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/cloud.js new file mode 100644 index 0000000..243fad5 --- /dev/null +++ b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/cloud.js @@ -0,0 +1,605 @@ +/* + + cloud.js + + a backend API for SNAP! + + written by Jens Mönig + + Copyright (C) 2013 by Jens Mönig + + This file is part of Snap!. + + Snap! is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +*/ + +// Global settings ///////////////////////////////////////////////////// + +/*global modules, IDE_Morph, SnapSerializer, hex_sha512, alert, nop*/ + +modules.cloud = '2013-May-10'; + +// Global stuff + +var Cloud; + +var SnapCloud = new Cloud( + 'https://snapcloud.miosoft.com/miocon/app/login?_app=SnapCloud' + //'192.168.2.110:8087/miocon/app/login?_app=SnapCloud' + //'192.168.186.167:8087/miocon/app/login?_app=SnapCloud' + // 'localhost/miocon/app/login?_app=SnapCloud' +); + +// Cloud ///////////////////////////////////////////////////////////// + +function Cloud(url) { + this.username = null; + this.password = null; // hex_sha512 hashed + this.url = url; + this.session = null; + this.api = {}; +} + +Cloud.prototype.clear = function () { + this.username = null; + this.password = null; + this.session = null; + this.api = {}; +}; + +Cloud.prototype.hasProtocol = function () { + return this.url.toLowerCase().indexOf('http') === 0; +}; + +// Cloud: Snap! API + +Cloud.prototype.signup = function ( + username, + email, + callBack, + errorCall +) { + // both callBack and errorCall are two-argument functions + var request = new XMLHttpRequest(), + myself = this; + try { + request.open( + "GET", + (this.hasProtocol() ? '' : 'http://') + + this.url + 'SignUp' + + '&Username=' + + encodeURIComponent(username) + + '&Email=' + + email, + true + ); + request.setRequestHeader( + "Content-Type", + "application/x-www-form-urlencoded" + ); + request.withCredentials = true; + request.onreadystatechange = function () { + if (request.readyState === 4) { + if (request.responseText) { + if (request.responseText.indexOf('ERROR') === 0) { + errorCall.call( + this, + request.responseText, + 'Signup' + ); + } else { + callBack.call( + null, + request.responseText, + 'Signup' + ); + } + } else { + errorCall.call( + null, + myself.url + 'SignUp', + 'could not connect to:' + ); + } + } + }; + request.send(null); + } catch (err) { + errorCall.call(this, err.toString(), 'Snap!Cloud'); + } +}; + +Cloud.prototype.getPublicProject = function ( + id, + callBack, + errorCall +) { + // id is Username=username&projectName=projectname, + // where the values are url-component encoded + // callBack is a single argument function, errorCall take two args + var request = new XMLHttpRequest(), + responseList, + myself = this; + try { + request.open( + "GET", + (this.hasProtocol() ? '' : 'http://') + + this.url + 'Public' + + '&' + + id, + true + ); + request.setRequestHeader( + "Content-Type", + "application/x-www-form-urlencoded" + ); + request.withCredentials = true; + request.onreadystatechange = function () { + if (request.readyState === 4) { + if (request.responseText) { + if (request.responseText.indexOf('ERROR') === 0) { + errorCall.call( + this, + request.responseText + ); + } else { + responseList = myself.parseResponse( + request.responseText + ); + callBack.call( + null, + responseList[0].SourceCode + ); + } + } else { + errorCall.call( + null, + myself.url + 'Public', + 'could not connect to:' + ); + } + } + }; + request.send(null); + } catch (err) { + errorCall.call(this, err.toString(), 'Snap!Cloud'); + } +}; + +Cloud.prototype.resetPassword = function ( + username, + callBack, + errorCall +) { + // both callBack and errorCall are two-argument functions + var request = new XMLHttpRequest(), + myself = this; + try { + request.open( + "GET", + (this.hasProtocol() ? '' : 'http://') + + this.url + 'ResetPW' + + '&Username=' + + encodeURIComponent(username), + true + ); + request.setRequestHeader( + "Content-Type", + "application/x-www-form-urlencoded" + ); + request.withCredentials = true; + request.onreadystatechange = function () { + if (request.readyState === 4) { + if (request.responseText) { + if (request.responseText.indexOf('ERROR') === 0) { + errorCall.call( + this, + request.responseText, + 'Reset Password' + ); + } else { + callBack.call( + null, + request.responseText, + 'Reset Password' + ); + } + } else { + errorCall.call( + null, + myself.url + 'ResetPW', + 'could not connect to:' + ); + } + } + }; + request.send(null); + } catch (err) { + errorCall.call(this, err.toString(), 'Snap!Cloud'); + } +}; + +Cloud.prototype.connect = function ( + callBack, + errorCall +) { + // both callBack and errorCall are two-argument functions + var request = new XMLHttpRequest(), + myself = this; + try { + request.open( + "GET", + (this.hasProtocol() ? '' : 'http://') + this.url, + true + ); + request.setRequestHeader( + "Content-Type", + "application/x-www-form-urlencoded" + ); + request.withCredentials = true; + request.onreadystatechange = function () { + if (request.readyState === 4) { + if (request.responseText) { + myself.api = myself.parseAPI(request.responseText); + myself.session = request.getResponseHeader('MioCracker') + .split(';')[0]; + if (myself.api.login) { + callBack.call(null, myself.api, 'Snap!Cloud'); + } else { + errorCall.call( + null, + 'connection failed' + ); + } + } else { + errorCall.call( + null, + myself.url, + 'could not connect to:' + ); + } + } + }; + request.send(null); + } catch (err) { + errorCall.call(this, err.toString(), 'Snap!Cloud'); + } +}; + + +Cloud.prototype.login = function ( + username, + password, + callBack, + errorCall +) { + var myself = this; + this.connect( + function () { + myself.rawLogin(username, password, callBack, errorCall); + myself.disconnect(); + }, + errorCall + ); +}; + +Cloud.prototype.rawLogin = function ( + username, + password, + callBack, + errorCall +) { + // both callBack and errorCall are two-argument functions + var myself = this, + pwHash = hex_sha512("miosoft%20miocon," + + this.session.split('=')[1] + "," + + encodeURIComponent(username.toLowerCase()) + "," + + password // alreadey hex_sha512 hashed + ); + this.callService( + 'login', + function (response, url) { + if (myself.api.logout) { + myself.username = username; + myself.password = password; + callBack.call(null, response, url); + } else { + errorCall.call( + null, + 'Service catalog is not available,\nplease retry', + 'Connection Error:' + ); + } + }, + errorCall, + [username, pwHash] + ); +}; + +Cloud.prototype.reconnect = function ( + callBack, + errorCall +) { + if (!(this.username && this.password)) { + this.message('You are not logged in'); + return; + } + this.login( + this.username, + this.password, + callBack, + errorCall + ); +}; + +Cloud.prototype.saveProject = function (ide, callBack, errorCall) { + var myself = this, + pdata, + media; + + ide.serializer.isCollectingMedia = true; + pdata = ide.serializer.serialize(ide.stage); + media = ide.hasChangedMedia ? + ide.serializer.mediaXML(ide.projectName) : null; + ide.serializer.isCollectingMedia = false; + ide.serializer.flushMedia(); + + myself.reconnect( + function () { + myself.callService( + 'saveProject', + function (response, url) { + callBack.call(null, response, url); + myself.disconnect(); + ide.hasChangedMedia = false; + }, + errorCall, + [ide.projectName, pdata, media] + ); + }, + errorCall + ); +}; + +Cloud.prototype.getProjectList = function (callBack, errorCall) { + var myself = this; + this.reconnect( + function () { + myself.callService( + 'getProjectList', + function (response, url) { + callBack.call(null, response, url); + myself.disconnect(); + }, + errorCall + ); + }, + errorCall + ); +}; + +Cloud.prototype.changePassword = function ( + oldPW, + newPW, + callBack, + errorCall +) { + var myself = this; + this.reconnect( + function () { + myself.callService( + 'changePassword', + function (response, url) { + callBack.call(null, response, url); + myself.disconnect(); + }, + errorCall, + [oldPW, newPW] + ); + }, + errorCall + ); +}; + +Cloud.prototype.logout = function (callBack, errorCall) { + this.clear(); + this.callService( + 'logout', + callBack, + errorCall + ); +}; + +Cloud.prototype.disconnect = function () { + this.callService( + 'logout', + nop, + nop + ); +}; + +// Cloud: backend communication + +Cloud.prototype.callURL = function (url, callBack, errorCall) { + // both callBack and errorCall are optional two-argument functions + var request = new XMLHttpRequest(), + myself = this; + try { + request.open('GET', url, true); + request.withCredentials = true; + request.setRequestHeader( + "Content-Type", + "application/x-www-form-urlencoded" + ); + request.setRequestHeader('MioCracker', this.session); + request.onreadystatechange = function () { + if (request.readyState === 4) { + if (request.responseText) { + var responseList = myself.parseResponse( + request.responseText + ); + callBack.call(null, responseList, url); + } else { + errorCall.call( + null, + url, + 'no response from:' + ); + } + } + }; + request.send(null); + } catch (err) { + errorCall.call(this, err.toString(), url); + } +}; + +Cloud.prototype.callService = function ( + serviceName, + callBack, + errorCall, + args +) { + // both callBack and errorCall are optional two-argument functions + var request = new XMLHttpRequest(), + service = this.api[serviceName], + myself = this, + postDict; + + if (!this.session) { + errorCall.call(null, 'You are not connected', 'Cloud'); + return; + } + if (!service) { + errorCall.call( + null, + 'service ' + serviceName + ' is not available', + 'API' + ); + return; + } + if (args && args.length > 0) { + postDict = {}; + service.parameters.forEach(function (parm, idx) { + postDict[parm] = args[idx]; + }); + } + try { + request.open(service.method, service.url, true); + request.withCredentials = true; + request.setRequestHeader( + "Content-Type", + "application/x-www-form-urlencoded" + ); + request.setRequestHeader('MioCracker', this.session); + request.onreadystatechange = function () { + if (request.readyState === 4) { + var responseList = []; + if (request.responseText && + request.responseText.indexOf('ERROR') === 0) { + errorCall.call( + this, + request.responseText, + 'Service: ' + serviceName + ); + return; + } + if (serviceName === 'login') { + myself.api = myself.parseAPI(request.responseText); + } + responseList = myself.parseResponse( + request.responseText + ); + callBack.call(null, responseList, service.url); + } + }; + request.send(this.encodeDict(postDict)); + } catch (err) { + errorCall.call(this, err.toString(), service.url); + } +}; + +// Cloud: payload transformation + +Cloud.prototype.parseAPI = function (src) { + var api = {}, + services; + services = src.split(" "); + services.forEach(function (service) { + var entries = service.split("&"), + serviceDescription = {}, + parms; + entries.forEach(function (entry) { + var pair = entry.split("="), + key = decodeURIComponent(pair[0]).toLowerCase(), + val = decodeURIComponent(pair[1]); + if (key === "service") { + api[val] = serviceDescription; + } else if (key === "parameters") { + parms = val.split(","); + if (!(parms.length === 1 && !parms[0])) { + serviceDescription.parameters = parms; + } + } else { + serviceDescription[key] = val; + } + }); + }); + return api; +}; + +Cloud.prototype.parseResponse = function (src) { + var ans = [], + lines; + if (!src) {return ans; } + lines = src.split(" "); + lines.forEach(function (service) { + var entries = service.split("&"), + dict = {}; + entries.forEach(function (entry) { + var pair = entry.split("="), + key = decodeURIComponent(pair[0]), + val = decodeURIComponent(pair[1]); + dict[key] = val; + }); + ans.push(dict); + }); + return ans; +}; + +Cloud.prototype.encodeDict = function (dict) { + var str = '', + pair, + key; + if (!dict) {return null; } + for (key in dict) { + if (dict.hasOwnProperty(key)) { + pair = encodeURIComponent(key) + + '=' + + encodeURIComponent(dict[key]); + if (pair.length > 0) { + str += '&'; + } + str += pair; + } + } + return str; +}; + +// Cloud: user messages (to be overridden) + +Cloud.prototype.message = function (string) { + alert(string); +}; diff --git a/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/contributing to BYOB4.txt b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/contributing to BYOB4.txt new file mode 100644 index 0000000..8813846 --- /dev/null +++ b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/contributing to BYOB4.txt @@ -0,0 +1,173 @@ +***************************** +Contributing to BYOB4 / Snap! +by Jens Mönig +last changed: 12/09/14 +***************************** + +Attached is the current development code base for BYOB4 a.k.a. Snap. It consists of +several JavaScript, HTML and text files, and while some of it may be functional +most parts will be in flux and subject to frequent, even fundamental modifications. +This document lays out a few simple guidelines ensuring that collaborative code +contribution works out. + + +Working with changesets +----------------------- +You'll often want to change or add code in existing JS files. Please don't. Instead +use a changeset. The way I always do it myself is to copy the empty + + changeset.js + +file and rename it to something like + + JensChangesToMorphic.js + +Into this file I write all the functions I want to add to Morphic. If I want to +change a function in Morphic.js I copy it to the changeset and edit it there. Then +I always validate the changeset with + + JSLint.com + +Please check your code frequently with JSLint! + +For our Snap code set JSLint's settings to: + + assume a browser + tolerate missing 'use strict' pragma + + [4] indentation + [78] maximum line length + +If you're working on the core Morphic library you can also + + tolerate eval + tolerate unfiltered for in + +although you'll probably not ever going to need either EVAL or FORIN in your +changesets anyway. + +There are, of course, other tools - like JSHint and Firebug - that help you debug +your code. Feel free to use whichever suits you best, but let's all agree on +JSLint's (nitpicky!) formatting rules so we get code that's well readable and +easily shareable among ourselves. + + +Coding style +------------ +Snap's codebase is both big and fast moving. We'll continue to churn out several +builds per day for a long time, hunting bugs and adding features throughout the +whole application. We'll also need to be able to get our heads around the whole +codebase. Being able to read and to quickly understand the code is most important, +much more so than mathematical elegance. + +Let me really stress this point: In creating Snap we're neither playing "Code Golf" +(solving a problem using the least number of keystrokes) nor are we trying to +outsmart Knuth. Instead we're maintaining a large number of small interchangeable +code chunks, therefore: + + +Avoid +----- + * accessing the DOM + + * frameworks (e.g. JQuery) + + * modules and namespaces (e.g. IIFE) + + - all of Snap is a single "World" with unique names for everything + + - remember: Changesets contain "small interchangeable code chunks"... + + * passing "thisArg" to functions like map(), filter() or forEach() + + - except in call() + - always use "myself" to reference "this" in an outer scope instead + + * meta-class systems + + - use Morphic's way of creating class-like constructors instead + + - initialize all attributes either in the constructor or in an init() + method, so you now the object's structure by casting a single look + upon it. Avoid adding adding attributes elsewhere, or if you do, + initialize them explicitly to "null" in the constructor or init() + code + + * nested ternary operators + + * RegEx + + * overwriting existing definitions + + - except in changesets, duh :-) + - create new constructors instead + + * non-descriptive names for variables, functions, objects + + * giant functions + + - which often are "modules in disguise", + especially if they define local helper functions + + - create a new constructor instead + + +Testing your code +------------------ +(don't worry, I'm not talking about formal UnitTest Suites or other BDSM software +fetishes, just about playing with what you're creating while you're doing it) + +To test your changesets just add another line in the html stub file with your +changeset. Make sure to put your changeset /after/ Morphic.js and Blocks.js and +whichever libraries are already included, so it'll actually get used. + +In your changeset override the world's + + customMorphs + +function so it returns a list of instances of your Morphs. For "Blocks.js" that +code is: + + var BlockMorph; + var ScriptsMorph; + + WorldMorph.prototype.customMorphs = function () { + var sm = new ScriptsMorph(); + sm.setExtent(new Point(800, 600)); + return [ + new BlockMorph(), + sm + ]; + }; + +Just modify this code so it returns your list of sample Morphs instead of +BlockMorph and ScriptsMorph instances. + +Once you've added this code to your changeset you can open your sample html file +in your browser, and you'll find your sample Morphs in the World's DEMO menu. + + +Inspectors +---------- +To actually test play with your Morphs you can right-click on them and open an +inspector on them. You can open more than one inspector on each object. The +inspector pretty much works the same as in Smalltalk. It even has an evaluation +pane at the bottom, in which you can type in any JS code, mark it with your mouse +(or select all with ctrl-a), righ-click on the selection and either "do it", "show +it" or "inspect it" (again, like in Squeak). + +Needless to say, in the evaluation pane "this" always refers to the inspected +object. + + +Source Code Mgmt +----------------- +The good thing about changesets is that you can continue working on them regardless +of new dev releases that happen in the meantime. When you feel you've got something +that's finished just send me your changeset, and I'll work all the changesets into +the Snap codebase and release another dev version. That way there will always (and +frequently) be a harmonized common code base. + +Thanks! + +--Jens diff --git a/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/gui.js b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/gui.js new file mode 100644 index 0000000..c3790cc --- /dev/null +++ b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/gui.js @@ -0,0 +1,5938 @@ +/* + + gui.js + + a programming environment + based on morphic.js, blocks.js, threads.js and objects.js + inspired by Scratch + + written by Jens Mönig + jens@moenig.org + + Copyright (C) 2013 by Jens Mönig + + This file is part of Snap!. + + Snap! is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + + + prerequisites: + -------------- + needs blocks.js, threads.js, objects.js and morphic.js + + + toc + --- + the following list shows the order in which all constructors are + defined. Use this list to locate code in this document: + + IDE_Morph + ProjectDialogMorph + SpriteIconMorph + TurtleIconMorph + CostumeIconMorph + WardrobeMorph + + + credits + ------- + Nathan Dinsmore contributed saving and loading of projects, + ypr-Snap! project conversion and countless bugfixes + Ian Reynolds contributed handling and visualization of sounds + +*/ + +/*global modules, Morph, SpriteMorph, BoxMorph, SyntaxElementMorph, Color, +ListWatcherMorph, isString, TextMorph, newCanvas, useBlurredShadows, +radians, VariableFrame, StringMorph, Point, SliderMorph, MenuMorph, +morphicVersion, DialogBoxMorph, ToggleButtonMorph, contains, +ScrollFrameMorph, StageMorph, PushButtonMorph, InputFieldMorph, FrameMorph, +Process, nop, SnapSerializer, ListMorph, detect, AlignmentMorph, TabMorph, +Costume, CostumeEditorMorph, MorphicPreferences, touchScreenSettings, +standardSettings, Sound, BlockMorph, ToggleMorph, InputSlotDialogMorph, +ScriptsMorph, isNil, SymbolMorph, BlockExportDialogMorph, +BlockImportDialogMorph, SnapTranslator, localize, List, InputSlotMorph, +SnapCloud, Uint8Array, HandleMorph, SVG_Costume, fontHeight, hex_sha512, +sb, CommentMorph, CommandBlockMorph*/ + +// Global stuff //////////////////////////////////////////////////////// + +modules.gui = '2013-August-17'; + +// Declarations + +var IDE_Morph; +var ProjectDialogMorph; +var SpriteIconMorph; +var CostumeIconMorph; +var TurtleIconMorph; +var WardrobeMorph; +var SoundIconMorph; +var JukeboxMorph; + +// IDE_Morph /////////////////////////////////////////////////////////// + +// I am SNAP's top-level frame, the Editor window + +// IDE_Morph inherits from Morph: + +IDE_Morph.prototype = new Morph(); +IDE_Morph.prototype.constructor = IDE_Morph; +IDE_Morph.uber = Morph.prototype; + +// IDE_Morph preferences settings and skins + +IDE_Morph.prototype.setDefaultDesign = function () { + MorphicPreferences.isFlat = false; + SpriteMorph.prototype.paletteColor = new Color(55, 55, 55); + SpriteMorph.prototype.paletteTextColor = new Color(230, 230, 230); + StageMorph.prototype.paletteTextColor + = SpriteMorph.prototype.paletteTextColor; + StageMorph.prototype.paletteColor = SpriteMorph.prototype.paletteColor; + SpriteMorph.prototype.sliderColor + = SpriteMorph.prototype.paletteColor.lighter(30); + + IDE_Morph.prototype.buttonContrast = 30; + IDE_Morph.prototype.backgroundColor = new Color(40, 40, 40); + IDE_Morph.prototype.frameColor = SpriteMorph.prototype.paletteColor; + + IDE_Morph.prototype.groupColor + = SpriteMorph.prototype.paletteColor.lighter(8); + IDE_Morph.prototype.sliderColor = SpriteMorph.prototype.sliderColor; + IDE_Morph.prototype.buttonLabelColor = new Color(255, 255, 255); + IDE_Morph.prototype.tabColors = [ + IDE_Morph.prototype.groupColor.darker(40), + IDE_Morph.prototype.groupColor.darker(60), + IDE_Morph.prototype.groupColor + ]; + IDE_Morph.prototype.rotationStyleColors = IDE_Morph.prototype.tabColors; + IDE_Morph.prototype.appModeColor = new Color(); + IDE_Morph.prototype.scriptsPaneTexture = 'scriptsPaneTexture.gif'; + IDE_Morph.prototype.padding = 5; + + SpriteIconMorph.prototype.labelColor + = IDE_Morph.prototype.buttonLabelColor; + CostumeIconMorph.prototype.labelColor + = IDE_Morph.prototype.buttonLabelColor; + SoundIconMorph.prototype.labelColor + = IDE_Morph.prototype.buttonLabelColor; + TurtleIconMorph.prototype.labelColor + = IDE_Morph.prototype.buttonLabelColor; +}; + +IDE_Morph.prototype.setFlatDesign = function () { + MorphicPreferences.isFlat = true; + SpriteMorph.prototype.paletteColor = new Color(255, 255, 255); + SpriteMorph.prototype.paletteTextColor = new Color(70, 70, 70); + StageMorph.prototype.paletteTextColor + = SpriteMorph.prototype.paletteTextColor; + StageMorph.prototype.paletteColor = SpriteMorph.prototype.paletteColor; + SpriteMorph.prototype.sliderColor = SpriteMorph.prototype.paletteColor; + + IDE_Morph.prototype.buttonContrast = 30; + IDE_Morph.prototype.backgroundColor = new Color(200, 200, 200); + IDE_Morph.prototype.frameColor = new Color(255, 255, 255); + + IDE_Morph.prototype.groupColor = new Color(230, 230, 230); + IDE_Morph.prototype.sliderColor = SpriteMorph.prototype.sliderColor; + IDE_Morph.prototype.buttonLabelColor = new Color(70, 70, 70); + IDE_Morph.prototype.tabColors = [ + IDE_Morph.prototype.groupColor.lighter(60), + IDE_Morph.prototype.groupColor.darker(10), + IDE_Morph.prototype.groupColor + ]; + IDE_Morph.prototype.rotationStyleColors = [ + IDE_Morph.prototype.groupColor, + IDE_Morph.prototype.groupColor.darker(10), + IDE_Morph.prototype.groupColor.darker(30) + ]; + IDE_Morph.prototype.appModeColor = IDE_Morph.prototype.frameColor; + IDE_Morph.prototype.scriptsPaneTexture = null; + IDE_Morph.prototype.padding = 1; + + SpriteIconMorph.prototype.labelColor + = IDE_Morph.prototype.buttonLabelColor; + CostumeIconMorph.prototype.labelColor + = IDE_Morph.prototype.buttonLabelColor; + SoundIconMorph.prototype.labelColor + = IDE_Morph.prototype.buttonLabelColor; + TurtleIconMorph.prototype.labelColor + = IDE_Morph.prototype.buttonLabelColor; +}; + +IDE_Morph.prototype.setDefaultDesign(); + +// IDE_Morph instance creation: + +function IDE_Morph(isAutoFill) { + this.init(isAutoFill); +} + +IDE_Morph.prototype.init = function (isAutoFill) { + // global font setting + MorphicPreferences.globalFontFamily = 'Helvetica, Arial'; + + // restore saved user preferences + this.userLanguage = null; // user language preference for startup + this.applySavedSettings(); + + // additional properties: + this.cloudMsg = null; + this.source = 'local'; + this.serializer = new SnapSerializer(); + + this.globalVariables = new VariableFrame(); + this.currentSprite = new SpriteMorph(this.globalVariables); + this.sprites = new List([this.currentSprite]); + this.currentCategory = 'motion'; + this.currentTab = 'scripts'; + this.projectName = ''; + this.projectNotes = ''; + + this.logo = null; + this.controlBar = null; + this.categories = null; + this.palette = null; + this.spriteBar = null; + this.spriteEditor = null; + this.stage = null; + this.corralBar = null; + this.corral = null; + + this.isAutoFill = isAutoFill || true; + this.isAppMode = false; + this.isSmallStage = false; + this.filePicker = null; + this.hasChangedMedia = false; + + this.isAnimating = true; + this.stageRatio = 1; // for IDE animations, e.g. when zooming + + this.loadNewProject = false; // flag when starting up translated + this.shield = null; + + // initialize inherited properties: + IDE_Morph.uber.init.call(this); + + // override inherited properites: + this.color = this.backgroundColor; +}; + +IDE_Morph.prototype.openIn = function (world) { + var hash, usr, myself = this, urlLanguage = null; + + this.buildPanes(); + world.add(this); + world.userMenu = this.userMenu; + + // get persistent user data, if any + if (localStorage) { + usr = localStorage['-snap-user']; + if (usr) { + usr = SnapCloud.parseResponse(usr)[0]; + if (usr) { + SnapCloud.username = usr.username || null; + SnapCloud.password = usr.password || null; + } + } + } + + // override SnapCloud's user message with Morphic + SnapCloud.message = function (string) { + var m = new MenuMorph(null, string), + intervalHandle; + m.popUpCenteredInWorld(world); + intervalHandle = setInterval(function () { + m.destroy(); + clearInterval(intervalHandle); + }, 2000); + }; + + // prevent non-DialogBoxMorphs from being dropped + // onto the World in user-mode + world.reactToDropOf = function (morph) { + if (!(morph instanceof DialogBoxMorph)) { + if (world.hand.grabOrigin) { + morph.slideBackTo(world.hand.grabOrigin); + } else { + world.hand.grab(morph); + } + } + }; + + this.reactToWorldResize(world.bounds); + + function getURL(url) { + try { + var request = new XMLHttpRequest(); + request.open('GET', url, false); + request.send(); + if (request.status === 200) { + return request.responseText; + } + throw new Error('unable to retrieve ' + url); + } catch (err) { + return; + } + } + + // dynamic notifications from non-source text files + // has some issues, commented out for now + /* + this.cloudMsg = getURL('http://snap.berkeley.edu/cloudmsg.txt'); + motd = getURL('http://snap.berkeley.edu/motd.txt'); + if (motd) { + this.inform('Snap!', motd); + } + */ + + function interpretUrlAnchors() { + if (location.hash.substr(0, 6) === '#open:') { + hash = location.hash.substr(6); + if (hash.charAt(0) === '%' + || hash.search(/\%(?:[0-9a-f]{2})/i) > -1) { + hash = decodeURIComponent(hash); + } + if (contains( + ['project', 'blocks', 'sprites', 'snapdata'].map( + function (each) { + return hash.substr(0, 8).indexOf(each); + } + ), + 1 + )) { + this.droppedText(hash); + } else { + this.droppedText(getURL(hash)); + } + } else if (location.hash.substr(0, 5) === '#run:') { + hash = location.hash.substr(5); + if (hash.charAt(0) === '%' + || hash.search(/\%(?:[0-9a-f]{2})/i) > -1) { + hash = decodeURIComponent(hash); + } + if (hash.substr(0, 8) === '') { + this.rawOpenProjectString(hash); + } else { + this.rawOpenProjectString(getURL(hash)); + } + this.toggleAppMode(true); + this.runScripts(); + } else if (location.hash.substr(0, 9) === '#present:') { + this.shield = new Morph(); + this.shield.color = this.color; + this.shield.setExtent(this.parent.extent()); + this.parent.add(this.shield); + myself.showMessage('Fetching project\nfrom the cloud...'); + SnapCloud.getPublicProject( + location.hash.substr(9), + function (projectData) { + var msg; + myself.nextSteps([ + function () { + msg = myself.showMessage('Opening project...'); + }, + function () { + if (projectData.indexOf(' max) { + x = start; + y += icon.height(); // they're all the same + } + icon.setPosition(new Point(x, y)); + x += w; + }); + this.frame.contents.adjustBounds(); + }; + + this.corral.addSprite = function (sprite) { + this.frame.contents.add(new SpriteIconMorph(sprite)); + this.fixLayout(); + }; + + this.corral.refresh = function () { + this.stageIcon.refresh(); + this.frame.contents.children.forEach(function (icon) { + icon.refresh(); + }); + }; + + this.corral.wantsDropOf = function (morph) { + return morph instanceof SpriteIconMorph; + }; + + this.corral.reactToDropOf = function (spriteIcon) { + var idx = 1, + pos = spriteIcon.position(); + spriteIcon.destroy(); + this.frame.contents.children.forEach(function (icon) { + if (pos.gt(icon.position()) || pos.y > icon.bottom()) { + idx += 1; + } + }); + myself.sprites.add(spriteIcon.object, idx); + myself.createCorral(); + myself.fixLayout(); + }; +}; + +// IDE_Morph layout + +IDE_Morph.prototype.fixLayout = function (situation) { + // situation is a string, i.e. + // 'selectSprite' or 'refreshPalette' or 'tabEditor' + var padding = this.padding; + + Morph.prototype.trackChanges = false; + + if (situation !== 'refreshPalette') { + // controlBar + this.controlBar.setPosition(this.logo.topRight()); + this.controlBar.setWidth(this.right() - this.controlBar.left()); + this.controlBar.fixLayout(); + + // categories + this.categories.setLeft(this.logo.left()); + this.categories.setTop(this.logo.bottom()); + } + + // palette + this.palette.setLeft(this.logo.left()); + this.palette.setTop(this.categories.bottom()); + this.palette.setHeight(this.bottom() - this.palette.top()); + + if (situation !== 'refreshPalette') { + // stage + if (this.isAppMode) { + this.stage.setScale(Math.floor(Math.min( + (this.width() - padding * 2) / this.stage.dimensions.x, + (this.height() - this.controlBar.height() * 2 - padding * 2) + / this.stage.dimensions.y + ) * 10) / 10); + this.stage.setCenter(this.center()); + } else { +// this.stage.setScale(this.isSmallStage ? 0.5 : 1); + this.stage.setScale(this.isSmallStage ? this.stageRatio : 1); + this.stage.setTop(this.logo.bottom() + padding); + this.stage.setRight(this.right()); + } + + // spriteBar + this.spriteBar.setPosition(this.logo.bottomRight().add(padding)); + this.spriteBar.setExtent(new Point( + Math.max(0, this.stage.left() - padding - this.spriteBar.left()), + this.categories.bottom() - this.spriteBar.top() - padding + )); + this.spriteBar.fixLayout(); + + // spriteEditor + if (this.spriteEditor.isVisible) { + this.spriteEditor.setPosition(this.spriteBar.bottomLeft()); + this.spriteEditor.setExtent(new Point( + this.spriteBar.width(), + this.bottom() - this.spriteEditor.top() + )); + } + + // corralBar + this.corralBar.setLeft(this.stage.left()); + this.corralBar.setTop(this.stage.bottom() + padding); + this.corralBar.setWidth(this.stage.width()); + + // corral + if (!contains(['selectSprite', 'tabEditor'], situation)) { + this.corral.setPosition(this.corralBar.bottomLeft()); + this.corral.setWidth(this.stage.width()); + this.corral.setHeight(this.bottom() - this.corral.top()); + this.corral.fixLayout(); + } + } + + Morph.prototype.trackChanges = true; + this.changed(); +}; + +IDE_Morph.prototype.setProjectName = function (string) { + this.projectName = string; + this.hasChangedMedia = true; + this.controlBar.updateLabel(); +}; + +// IDE_Morph resizing + +IDE_Morph.prototype.setExtent = function (point) { + var minExt, + ext; + + // determine the minimum dimensions making sense for the current mode + if (this.isAppMode) { + minExt = StageMorph.prototype.dimensions.add( + this.controlBar.height() + 10 + ); + } else { + /* // auto-switches to small stage mode, commented out b/c I don't like it + if (point.x < 910) { + this.isSmallStage = true; + this.stageRatio = 0.5; + } + */ + minExt = this.isSmallStage ? + new Point(700, 350) : new Point(910, 490); + } + ext = point.max(minExt); + IDE_Morph.uber.setExtent.call(this, ext); + this.fixLayout(); +}; + +// IDE_Morph events + +IDE_Morph.prototype.reactToWorldResize = function (rect) { + if (this.isAutoFill) { + this.setPosition(rect.origin); + this.setExtent(rect.extent()); + } + if (this.filePicker) { + document.body.removeChild(this.filePicker); + this.filePicker = null; + } +}; + +IDE_Morph.prototype.droppedImage = function (aCanvas, name) { + var costume = new Costume( + aCanvas, + name ? name.split('.')[0] : '' // up to period + ); + this.currentSprite.addCostume(costume); + this.currentSprite.wearCostume(costume); + this.spriteBar.tabBar.tabTo('costumes'); + this.hasChangedMedia = true; +}; + +IDE_Morph.prototype.droppedSVG = function (anImage, name) { + var costume = new SVG_Costume(anImage, name.split('.')[0]); + this.currentSprite.addCostume(costume); + this.currentSprite.wearCostume(costume); + this.spriteBar.tabBar.tabTo('costumes'); + this.hasChangedMedia = true; + this.showMessage( + 'SVG costumes are\nnot yet fully supported\nin every browser', + 2 + ); +}; + +IDE_Morph.prototype.droppedAudio = function (anAudio, name) { + this.currentSprite.addSound(anAudio, name.split('.')[0]); // up to period + this.spriteBar.tabBar.tabTo('sounds'); + this.hasChangedMedia = true; +}; + +IDE_Morph.prototype.droppedText = function (aString, name) { + var lbl = name ? name.split('.')[0] : ''; + if (aString.indexOf('Snap! + var ypr = document.getElementById('ypr'), + myself = this, + suffix = name.substring(name.length - 3); + + if (suffix.toLowerCase() !== 'ypr') {return; } + + function loadYPR(buffer, lbl) { + var reader = new sb.Reader(), + pname = lbl.split('.')[0]; // up to period + reader.onload = function (info) { + myself.droppedText(new sb.XMLWriter().write(pname, info)); + }; + reader.readYPR(new Uint8Array(buffer)); + } + + if (!ypr) { + ypr = document.createElement('script'); + ypr.id = 'ypr'; + ypr.onload = function () {loadYPR(anArrayBuffer, name); }; + document.head.appendChild(ypr); + ypr.src = 'ypr.js'; + } else { + loadYPR(anArrayBuffer, name); + } +}; + +// IDE_Morph button actions + +IDE_Morph.prototype.refreshPalette = function (shouldIgnorePosition) { + var oldTop = this.palette.contents.top(); + + this.createPalette(); + this.fixLayout('refreshPalette'); + if (!shouldIgnorePosition) { + this.palette.contents.setTop(oldTop); + } +}; + +IDE_Morph.prototype.pressStart = function () { + if (this.world().currentKey === 16) { // shiftClicked + this.toggleFastTracking(); + } else { + this.runScripts(); + } +}; + +IDE_Morph.prototype.toggleFastTracking = function () { + if (this.stage.isFastTracked) { + this.stopFastTracking(); + } else { + this.startFastTracking(); + } +}; + +IDE_Morph.prototype.toggleVariableFrameRate = function () { + if (StageMorph.prototype.frameRate) { + StageMorph.prototype.frameRate = 0; + this.stage.fps = 0; + } else { + StageMorph.prototype.frameRate = 30; + this.stage.fps = 30; + } +}; + +IDE_Morph.prototype.startFastTracking = function () { + this.stage.isFastTracked = true; + this.stage.fps = 0; + this.controlBar.startButton.labelString = new SymbolMorph('flash', 14); + this.controlBar.startButton.drawNew(); + this.controlBar.startButton.fixLayout(); +}; + +IDE_Morph.prototype.stopFastTracking = function () { + this.stage.isFastTracked = false; + this.stage.fps = this.stage.frameRate; + this.controlBar.startButton.labelString = new SymbolMorph('flag', 14); + this.controlBar.startButton.drawNew(); + this.controlBar.startButton.fixLayout(); +}; + +IDE_Morph.prototype.runScripts = function () { + this.stage.fireGreenFlagEvent(); +}; + +IDE_Morph.prototype.togglePauseResume = function () { + if (this.stage.threads.isPaused()) { + this.stage.threads.resumeAll(this.stage); + } else { + this.stage.threads.pauseAll(this.stage); + } + this.controlBar.pauseButton.refresh(); +}; + +IDE_Morph.prototype.isPaused = function () { + if (!this.stage) {return false; } + return this.stage.threads.isPaused(); +}; + +IDE_Morph.prototype.stopAllScripts = function () { + this.stage.fireStopAllEvent(); +}; + +IDE_Morph.prototype.selectSprite = function (sprite) { + this.currentSprite = sprite; + this.createPalette(); + this.createSpriteBar(); + this.createSpriteEditor(); + this.corral.refresh(); + this.fixLayout('selectSprite'); + this.currentSprite.scripts.fixMultiArgs(); +}; + +// IDE_Morph skins + +IDE_Morph.prototype.defaultDesign = function () { + this.setDefaultDesign(); + this.refreshIDE(); + this.removeSetting('design'); +}; + +IDE_Morph.prototype.flatDesign = function () { + this.setFlatDesign(); + this.refreshIDE(); + this.saveSetting('design', 'flat'); +}; + +IDE_Morph.prototype.refreshIDE = function () { + var projectData; + + if (Process.prototype.isCatchingErrors) { + try { + projectData = this.serializer.serialize(this.stage); + } catch (err) { + this.showMessage('Serialization failed: ' + err); + } + } else { + projectData = this.serializer.serialize(this.stage); + } + SpriteMorph.prototype.initBlocks(); + this.buildPanes(); + this.fixLayout(); + if (this.loadNewProject) { + this.newProject(); + } else { + this.openProjectString(projectData); + } +}; + +// IDE_Morph settings persistance + +IDE_Morph.prototype.applySavedSettings = function () { + var design = this.getSetting('design'), + zoom = this.getSetting('zoom'), + language = this.getSetting('language'), + click = this.getSetting('click'), + longform = this.getSetting('longform'); + + // design + if (design === 'flat') { + this.setFlatDesign(); + } else { + this.setDefaultDesign(); + } + + // blocks zoom + if (zoom) { + SyntaxElementMorph.prototype.setScale(zoom); + CommentMorph.prototype.refreshScale(); + SpriteMorph.prototype.initBlocks(); + } + + // language + if (language && language !== 'en') { + this.userLanguage = language; + } else { + this.userLanguage = null; + } + + // click + if (click && !BlockMorph.prototype.snapSound) { + BlockMorph.prototype.toggleSnapSound(); + } + + // long form + if (longform) { + InputSlotDialogMorph.prototype.isLaunchingExpanded = true; + } +}; + +IDE_Morph.prototype.saveSetting = function (key, value) { + if (localStorage) { + localStorage['-snap-setting-' + key] = value; + } +}; + +IDE_Morph.prototype.getSetting = function (key) { + if (localStorage) { + return localStorage['-snap-setting-' + key]; + } + return null; +}; + +IDE_Morph.prototype.removeSetting = function (key) { + if (localStorage) { + delete localStorage['-snap-setting-' + key]; + } +}; + +// IDE_Morph sprite list access + +IDE_Morph.prototype.addNewSprite = function () { + var sprite = new SpriteMorph(this.globalVariables), + rnd = Process.prototype.reportRandom; + + sprite.name = sprite.name + + (this.corral.frame.contents.children.length + 1); + sprite.setCenter(this.stage.center()); + this.stage.add(sprite); + + // randomize sprite properties + sprite.setHue(rnd.call(this, 0, 100)); + sprite.setBrightness(rnd.call(this, 50, 100)); + sprite.turn(rnd.call(this, 1, 360)); + sprite.setXPosition(rnd.call(this, -220, 220)); + sprite.setYPosition(rnd.call(this, -160, 160)); + + this.sprites.add(sprite); + this.corral.addSprite(sprite); + this.selectSprite(sprite); +}; + +IDE_Morph.prototype.paintNewSprite = function () { + var sprite = new SpriteMorph(this.globalVariables), + cos = new Costume(), + myself = this; + + sprite.name = sprite.name + + (this.corral.frame.contents.children.length + 1); + sprite.setCenter(this.stage.center()); + this.stage.add(sprite); + this.sprites.add(sprite); + this.corral.addSprite(sprite); + this.selectSprite(sprite); + cos.edit( + this.world(), + this, + true, + function () {myself.removeSprite(sprite); }, + function () { + sprite.addCostume(cos); + sprite.wearCostume(cos); + } + ); +}; + +IDE_Morph.prototype.duplicateSprite = function (sprite) { + var duplicate = sprite.fullCopy(); + + duplicate.name = sprite.name + '(2)'; + duplicate.setPosition(this.world().hand.position()); + this.stage.add(duplicate); + duplicate.keepWithin(this.stage); + this.sprites.add(duplicate); + this.corral.addSprite(duplicate); + this.selectSprite(duplicate); +}; + +IDE_Morph.prototype.removeSprite = function (sprite) { + var idx = this.sprites.asArray().indexOf(sprite) + 1; + + sprite.destroy(); + this.stage.watchers().forEach(function (watcher) { + if (watcher.object() === sprite) { + watcher.destroy(); + } + }); + + if (idx < 1) {return; } + + this.currentSprite = detect( + this.stage.children, + function (morph) {return morph instanceof SpriteMorph; } + ) || this.stage; + this.sprites.remove(this.sprites.asArray().indexOf(sprite) + 1); + this.createCorral(); + this.fixLayout(); + this.selectSprite(this.currentSprite); +}; + +// IDE_Morph menus + +IDE_Morph.prototype.userMenu = function () { + var menu = new MenuMorph(this); + menu.addItem('help', 'nop'); + return menu; +}; + +IDE_Morph.prototype.snapMenu = function () { + var menu, + world = this.world(); + + menu = new MenuMorph(this); + menu.addItem('About...', 'aboutSnap'); + menu.addLine(); + menu.addItem( + 'Reference manual', + function () { + window.open('help/SnapManual.pdf', 'SnapReferenceManual'); + } + ); + menu.addItem( + 'Snap! website', + function () { + window.open('http://snap.berkeley.edu/', 'SnapWebsite'); + } + ); + menu.addItem( + 'Download source', + function () { + window.open( + 'http://snap.berkeley.edu/snapsource/snap.zip', + 'SnapSource' + ); + } + ); + if (world.isDevMode) { + menu.addLine(); + menu.addItem( + 'Switch back to user mode', + 'switchToUserMode', + 'disable deep-Morphic\ncontext menus' + + '\nand show user-friendly ones', + new Color(0, 100, 0) + ); + } else if (world.currentKey === 16) { // shift-click + menu.addLine(); + menu.addItem( + 'Switch to dev mode', + 'switchToDevMode', + 'enable Morphic\ncontext menus\nand inspectors,' + + '\nnot user-friendly!', + new Color(100, 0, 0) + ); + } + menu.popup(world, this.logo.bottomLeft()); +}; + +IDE_Morph.prototype.cloudMenu = function () { + var menu, + myself = this, + world = this.world(), + pos = this.controlBar.cloudButton.bottomLeft(), + shiftClicked = (world.currentKey === 16); + + menu = new MenuMorph(this); + if (shiftClicked) { + menu.addItem( + 'url...', + 'setCloudURL', + null, + new Color(100, 0, 0) + ); + menu.addLine(); + } + if (!SnapCloud.username) { + menu.addItem( + 'Login...', + 'initializeCloud' + ); + menu.addItem( + 'Signup...', + 'createCloudAccount' + ); + menu.addItem( + 'Reset Password...', + 'resetCloudPassword' + ); + } else { + menu.addItem( + 'Logout', + 'logout' + ); + menu.addItem( + 'Change Password...', + 'changeCloudPassword' + ); + } + if (shiftClicked) { + menu.addLine(); + menu.addItem( + 'export project media only...', + function () { + if (myself.projectName) { + myself.exportProjectMedia(myself.projectName); + } else { + myself.prompt('Export Project As...', function (name) { + myself.exportProjectMedia(name); + }, null, 'exportProject'); + } + }, + null, + this.hasChangedMedia ? new Color(100, 0, 0) : new Color(0, 100, 0) + ); + menu.addItem( + 'export project without media...', + function () { + if (myself.projectName) { + myself.exportProjectNoMedia(myself.projectName); + } else { + myself.prompt('Export Project As...', function (name) { + myself.exportProjectNoMedia(name); + }, null, 'exportProject'); + } + }, + null, + new Color(100, 0, 0) + ); + menu.addItem( + 'export project as cloud data...', + function () { + if (myself.projectName) { + myself.exportProjectAsCloudData(myself.projectName); + } else { + myself.prompt('Export Project As...', function (name) { + myself.exportProjectAsCloudData(name); + }, null, 'exportProject'); + } + }, + null, + new Color(100, 0, 0) + ); + menu.addLine(); + menu.addItem( + 'open shared project from cloud...', + function () { + myself.prompt('Author name…', function (usr) { + myself.prompt('Project name...', function (prj) { + var id = 'Username=' + + encodeURIComponent(usr) + + '&ProjectName=' + + encodeURIComponent(prj); + myself.showMessage( + 'Fetching project\nfrom the cloud...' + ); + SnapCloud.getPublicProject( + id, + function (projectData) { + var msg; + if (!Process.prototype.isCatchingErrors) { + window.open( + 'data:text/xml,' + projectData + ); + } + myself.nextSteps([ + function () { + msg = myself.showMessage( + 'Opening project...' + ); + }, + function () { + myself.rawOpenCloudDataString( + projectData + ); + }, + function () { + msg.destroy(); + } + ]); + }, + myself.cloudError() + ); + + }, 'project'); + }, 'project'); + }, + null, + new Color(100, 0, 0) + ); + } + menu.popup(world, pos); +}; + +IDE_Morph.prototype.settingsMenu = function () { + var menu, + stage = this.stage, + world = this.world(), + myself = this, + pos = this.controlBar.settingsButton.bottomLeft(), + shiftClicked = (world.currentKey === 16); + + function addPreference(label, toggle, test, onHint, offHint, hide) { + var on = '\u2611 ', + off = '\u2610 '; + if (!hide || shiftClicked) { + menu.addItem( + (test ? on : off) + localize(label), + toggle, + test ? onHint : offHint, + hide ? new Color(100, 0, 0) : null + ); + } + } + + menu = new MenuMorph(this); + menu.addItem('Language...', 'languageMenu'); + menu.addItem( + 'Zoom blocks...', + 'userSetBlocksScale' + ); + menu.addLine(); + addPreference( + 'Blurred shadows', + 'toggleBlurredShadows', + useBlurredShadows, + 'uncheck to use solid drop\nshadows and highlights', + 'check to use blurred drop\nshadows and highlights', + true + ); + addPreference( + 'Zebra coloring', + 'toggleZebraColoring', + BlockMorph.prototype.zebraContrast, + 'uncheck to disable alternating\ncolors for nested block', + 'check to enable alternating\ncolors for nested blocks', + true + ); + addPreference( + 'Dynamic input labels', + 'toggleDynamicInputLabels', + SyntaxElementMorph.prototype.dynamicInputLabels, + 'uncheck to disable dynamic\nlabels for variadic inputs', + 'check to enable dynamic\nlabels for variadic inputs', + true + ); + addPreference( + 'Prefer empty slot drops', + 'togglePreferEmptySlotDrops', + ScriptsMorph.prototype.isPreferringEmptySlots, + 'uncheck to allow dropped\nreporters to kick out others', + 'settings menu prefer empty slots hint', + true + ); + addPreference( + 'Long form input dialog', + 'toggleLongFormInputDialog', + InputSlotDialogMorph.prototype.isLaunchingExpanded, + 'uncheck to use the input\ndialog in short form', + 'check to always show slot\ntypes in the input dialog' + ); + addPreference( + 'Virtual keyboard', + 'toggleVirtualKeyboard', + MorphicPreferences.useVirtualKeyboard, + 'uncheck to disable\nvirtual keyboard support\nfor mobile devices', + 'check to enable\nvirtual keyboard support\nfor mobile devices', + true + ); + addPreference( + 'Input sliders', + 'toggleInputSliders', + MorphicPreferences.useSliderForInput, + 'uncheck to disable\ninput sliders for\nentry fields', + 'check to enable\ninput sliders for\nentry fields' + ); + if (MorphicPreferences.useSliderForInput) { + addPreference( + 'Execute on slider change', + 'toggleSliderExecute', + InputSlotMorph.prototype.executeOnSliderEdit, + 'uncheck to supress\nrunning scripts\nwhen moving the slider', + 'check to run\nthe edited script\nwhen moving the slider' + ); + } + addPreference( + 'Clicking sound', + function () { + BlockMorph.prototype.toggleSnapSound(); + if (BlockMorph.prototype.snapSound) { + myself.saveSetting('click', true); + } else { + myself.removeSetting('click'); + } + }, + BlockMorph.prototype.snapSound, + 'uncheck to turn\nblock clicking\nsound off', + 'check to turn\nblock clicking\nsound on' + ); + addPreference( + 'Animations', + function () {myself.isAnimating = !myself.isAnimating; }, + myself.isAnimating, + 'uncheck to disable\nIDE animations', + 'check to enable\nIDE animations', + true + ); + addPreference( + 'Turbo mode', + 'toggleFastTracking', + this.stage.isFastTracked, + 'uncheck to run scripts\nat normal speed', + 'check to prioritize\nscript execution' + ); + addPreference( + 'Rasterize SVGs', + function () { + MorphicPreferences.rasterizeSVGs = + !MorphicPreferences.rasterizeSVGs; + }, + MorphicPreferences.rasterizeSVGs, + 'uncheck for smooth\nscaling of vector costumes', + 'check to rasterize\nSVGs on import', + true + ); + addPreference( + 'Flat design', + function () { + if (MorphicPreferences.isFlat) { + return myself.defaultDesign(); + } + myself.flatDesign(); + }, + MorphicPreferences.isFlat, + 'uncheck for default\nGUI design', + 'check for alternative\nGUI design', + false + ); + addPreference( + 'Sprite Nesting', + function () { + SpriteMorph.prototype.enableNesting = + !SpriteMorph.prototype.enableNesting; + }, + SpriteMorph.prototype.enableNesting, + 'uncheck to disable\nsprite composition', + 'check to enable\nsprite composition', + true + ); + menu.addLine(); // everything below this line is stored in the project + addPreference( + 'Thread safe scripts', + function () {stage.isThreadSafe = !stage.isThreadSafe; }, + this.stage.isThreadSafe, + 'uncheck to allow\nscript reentrance', + 'check to disallow\nscript reentrance' + ); + addPreference( + 'Prefer smooth animations', + 'toggleVariableFrameRate', + StageMorph.prototype.frameRate, + 'uncheck for greater speed\nat variable frame rates', + 'check for smooth, predictable\nanimations across computers' + ); + addPreference( + 'Codification support', + function () { + StageMorph.prototype.enableCodeMapping = + !StageMorph.prototype.enableCodeMapping; + myself.currentSprite.blocksCache.variables = null; + myself.currentSprite.paletteCache.variables = null; + myself.refreshPalette(); + }, + StageMorph.prototype.enableCodeMapping, + 'uncheck to disable\nblock to text mapping features', + 'check for block\nto text mapping features', + false + ); + menu.popup(world, pos); +}; + +IDE_Morph.prototype.projectMenu = function () { + var menu, + myself = this, + world = this.world(), + pos = this.controlBar.projectButton.bottomLeft(), + shiftClicked = (world.currentKey === 16); + + menu = new MenuMorph(this); + menu.addItem('Project notes...', 'editProjectNotes'); + menu.addLine(); + menu.addItem( + 'New', + function () { + myself.confirm( + 'Replace the current project with a new one?', + 'New Project', + function () { + myself.newProject(); + } + ); + } + ); + menu.addItem('Open...', 'openProjectsBrowser'); + menu.addItem( + 'Save', + function () { + if (myself.projectName) { + if (myself.source === 'local') { // as well as 'examples' + myself.saveProject(myself.projectName); + } else { // 'cloud' + myself.saveProjectToCloud(myself.projectName); + } + } else { + myself.saveProjectsBrowser(); + } + } + ); + if (shiftClicked) { + menu.addItem( + 'Save to disk', + 'saveProjectToDisk', + 'experimental - store this project\nin your downloads folder', + new Color(100, 0, 0) + ); + } + menu.addItem('Save As...', 'saveProjectsBrowser'); + menu.addLine(); + menu.addItem( + 'Import...', + function () { + var inp = document.createElement('input'); + if (myself.filePicker) { + document.body.removeChild(myself.filePicker); + myself.filePicker = null; + } + inp.type = 'file'; + inp.style.color = "transparent"; + inp.style.backgroundColor = "transparent"; + inp.style.border = "none"; + inp.style.outline = "none"; + inp.style.position = "absolute"; + inp.style.top = "0px"; + inp.style.left = "0px"; + inp.style.width = "0px"; + inp.style.height = "0px"; + inp.addEventListener( + "change", + function () { + document.body.removeChild(inp); + myself.filePicker = null; + world.hand.processDrop(inp.files); + }, + false + ); + document.body.appendChild(inp); + myself.filePicker = inp; + inp.click(); + }, + 'file menu import hint' // looks up the actual text in the translator + ); + + menu.addItem( + shiftClicked ? + 'Export project as plain text...' : 'Export project...', + function () { + if (myself.projectName) { + myself.exportProject(myself.projectName, shiftClicked); + } else { + myself.prompt('Export Project As...', function (name) { + myself.exportProject(name); + }, null, 'exportProject'); + } + }, + 'show project data as XML\nin a new browser window', + shiftClicked ? new Color(100, 0, 0) : null + ); + + menu.addItem( + 'Export blocks...', + function () {myself.exportGlobalBlocks(); }, + 'show global custom block definitions as XML\nin a new browser window' + ); + + menu.addLine(); + menu.addItem( + 'Import tools', + function () { + + var url = 'http://snap.berkeley.edu/snapsource/tools.xml', + request = new XMLHttpRequest(); + request.open('GET', url, false); + request.send(); + if (request.status === 200) { + return myself.droppedText(request.responseText, 'tools'); + } + throw new Error('unable to retrieve ' + url); + + }, + 'load the official library of\npowerful blocks' + ); + menu.addItem( + 'Libraries...', + function () { + // read a list of libraries from an external file, + // this has turned out to be profoundly ugly + // we should pull it all apart into meaningful selectors + // at some time + var libMenu = new MenuMorph(this, 'Import library'), + libUrl = 'http://snap.berkeley.edu/snapsource/libraries/' + + 'LIBRARIES', + lRequest = new XMLHttpRequest(); + + function loadLib(name) { + var url = 'http://snap.berkeley.edu/snapsource/libraries/' + + name + + '.xml', + request = new XMLHttpRequest(); + request.open('GET', url, false); + request.send(); + if (request.status === 200) { + return myself.droppedText(request.responseText, name); + } + throw new Error('unable to retrieve ' + url); + } + + lRequest.open('GET', libUrl, false); + lRequest.send(); + if (lRequest.status === 200) { + lRequest.responseText.split('\n').forEach(function (line) { + if (line.length > 0) { + libMenu.addItem( + line.substring(line.indexOf('\t') + 1), + function () { + loadLib( + line.substring(0, line.indexOf('\t')) + ); + } + ); + } + }); + } else { + throw new Error('unable to retrieve ' + libUrl); + } + libMenu.popup(world, pos); + }, + 'Select categories of additional blocks to add to this project.' + ); + + menu.popup(world, pos); +}; + +// IDE_Morph menu actions + +IDE_Morph.prototype.aboutSnap = function () { + var dlg, aboutTxt, noticeTxt, creditsTxt, versions = '', translations, + module, btn1, btn2, btn3, btn4, licenseBtn, translatorsBtn, + world = this.world(); + + aboutTxt = 'Snap! 4.0\nBuild Your Own Blocks\n\n--- beta ---\n\n' + + 'Copyright \u24B8 2013 Jens M\u00F6nig and ' + + 'Brian Harvey\n' + + 'jens@moenig.org, bh@cs.berkeley.edu\n\n' + + + 'Snap! is developed by the University of California, Berkeley\n' + + ' with support from the National Science Foundation ' + + 'and MioSoft. \n' + + + 'The design of Snap! is influenced and inspired by Scratch,\n' + + 'from the Lifelong Kindergarten group at the MIT Media Lab\n\n' + + + 'for more information see http://snap.berkeley.edu\n' + + 'and http://scratch.mit.edu'; + + noticeTxt = localize('License') + + '\n\n' + + 'Snap! is free software: you can redistribute it and/or modify\n' + + 'it under the terms of the GNU Affero General Public License as\n' + + 'published by the Free Software Foundation, either version 3 of\n' + + 'the License, or (at your option) any later version.\n\n' + + + 'This program is distributed in the hope that it will be useful,\n' + + 'but WITHOUT ANY WARRANTY; without even the implied warranty of\n' + + 'MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n' + + 'GNU Affero General Public License for more details.\n\n' + + + 'You should have received a copy of the\n' + + 'GNU Affero General Public License along with this program.\n' + + 'If not, see http://www.gnu.org/licenses/'; + + creditsTxt = localize('Contributors') + + '\n\nNathan Dinsmore: Saving/Loading, Snap-Logo Design, ' + + 'countless bugfixes' + + '\nKartik Chandra: Paint Editor' + + '\nIan Reynolds: UI Design, Event Bindings, ' + + 'Sound primitives' + + '\nIvan Motyashov: Initial Squeak Porting' + + '\nDavide Della Casa: Morphic Optimizations' + + '\nAchal Dave: Web Audio' + + '\nJoe Otto: Morphic Testing and Debugging'; + + for (module in modules) { + if (Object.prototype.hasOwnProperty.call(modules, module)) { + versions += ('\n' + module + ' (' + + modules[module] + ')'); + } + } + if (versions !== '') { + versions = localize('current module versions:') + ' \n\n' + + 'morphic (' + morphicVersion + ')' + + versions; + } + translations = localize('Translations') + '\n' + SnapTranslator.credits(); + + dlg = new DialogBoxMorph(); + dlg.inform('About Snap', aboutTxt, world); + btn1 = dlg.buttons.children[0]; + translatorsBtn = dlg.addButton( + function () { + dlg.body.text = translations; + dlg.body.drawNew(); + btn1.show(); + btn2.show(); + btn3.hide(); + btn4.hide(); + licenseBtn.hide(); + translatorsBtn.hide(); + dlg.fixLayout(); + dlg.drawNew(); + dlg.setCenter(world.center()); + }, + 'Translators...' + ); + btn2 = dlg.addButton( + function () { + dlg.body.text = aboutTxt; + dlg.body.drawNew(); + btn1.show(); + btn2.hide(); + btn3.show(); + btn4.show(); + licenseBtn.show(); + translatorsBtn.hide(); + dlg.fixLayout(); + dlg.drawNew(); + dlg.setCenter(world.center()); + }, + 'Back...' + ); + btn2.hide(); + licenseBtn = dlg.addButton( + function () { + dlg.body.text = noticeTxt; + dlg.body.drawNew(); + btn1.show(); + btn2.show(); + btn3.hide(); + btn4.hide(); + licenseBtn.hide(); + translatorsBtn.hide(); + dlg.fixLayout(); + dlg.drawNew(); + dlg.setCenter(world.center()); + }, + 'License...' + ); + btn3 = dlg.addButton( + function () { + dlg.body.text = versions; + dlg.body.drawNew(); + btn1.show(); + btn2.show(); + btn3.hide(); + btn4.hide(); + licenseBtn.hide(); + translatorsBtn.hide(); + dlg.fixLayout(); + dlg.drawNew(); + dlg.setCenter(world.center()); + }, + 'Modules...' + ); + btn4 = dlg.addButton( + function () { + dlg.body.text = creditsTxt; + dlg.body.drawNew(); + btn1.show(); + btn2.show(); + translatorsBtn.show(); + btn3.hide(); + btn4.hide(); + licenseBtn.hide(); + dlg.fixLayout(); + dlg.drawNew(); + dlg.setCenter(world.center()); + }, + 'Credits...' + ); + translatorsBtn.hide(); + dlg.fixLayout(); + dlg.drawNew(); +}; + +IDE_Morph.prototype.editProjectNotes = function () { + var dialog = new DialogBoxMorph().withKey('projectNotes'), + frame = new ScrollFrameMorph(), + text = new TextMorph(this.projectNotes || ''), + ok = dialog.ok, + myself = this, + size = 250, + world = this.world(); + + frame.padding = 6; + frame.setWidth(size); + frame.acceptsDrops = false; + frame.contents.acceptsDrops = false; + + text.setWidth(size - frame.padding * 2); + text.setPosition(frame.topLeft().add(frame.padding)); + text.enableSelecting(); + text.isEditable = true; + + frame.setHeight(size); + frame.fixLayout = nop; + frame.edge = InputFieldMorph.prototype.edge; + frame.fontSize = InputFieldMorph.prototype.fontSize; + frame.typeInPadding = InputFieldMorph.prototype.typeInPadding; + frame.contrast = InputFieldMorph.prototype.contrast; + frame.drawNew = InputFieldMorph.prototype.drawNew; + frame.drawRectBorder = InputFieldMorph.prototype.drawRectBorder; + + frame.addContents(text); + text.drawNew(); + + dialog.ok = function () { + myself.projectNotes = text.text; + ok.call(this); + }; + + dialog.justDropped = function () { + text.edit(); + }; + + dialog.labelString = 'Project Notes'; + dialog.createLabel(); + dialog.addBody(frame); + frame.drawNew(); + dialog.addButton('ok', 'OK'); + dialog.addButton('cancel', 'Cancel'); + dialog.fixLayout(); + dialog.drawNew(); + dialog.popUp(world); + dialog.setCenter(world.center()); + text.edit(); +}; + +IDE_Morph.prototype.newProject = function () { + this.source = SnapCloud.username ? 'cloud' : 'local'; + if (this.stage) { + this.stage.destroy(); + } + if (location.hash.substr(0, 6) !== '#lang:') { + location.hash = ''; + } + this.globalVariables = new VariableFrame(); + this.currentSprite = new SpriteMorph(this.globalVariables); + this.sprites = new List([this.currentSprite]); + StageMorph.prototype.hiddenPrimitives = {}; + StageMorph.prototype.codeMappings = {}; + StageMorph.prototype.codeHeaders = {}; + StageMorph.prototype.enableCodeMapping = false; + this.setProjectName(''); + this.projectNotes = ''; + this.createStage(); + this.add(this.stage); + this.createCorral(); + this.selectSprite(this.stage.children[0]); + this.fixLayout(); +}; + +IDE_Morph.prototype.saveProject = function (name) { + var myself = this; + this.nextSteps([ + function () { + myself.showMessage('Saving...'); + }, + function () { + myself.rawSaveProject(name); + } + ]); +}; + +IDE_Morph.prototype.rawSaveProject = function (name) { + var str; + if (name) { + this.setProjectName(name); + if (Process.prototype.isCatchingErrors) { + try { + localStorage['-snap-project-' + name] + = str = this.serializer.serialize(this.stage); + location.hash = '#open:' + str; + this.showMessage('Saved!', 1); + } catch (err) { + this.showMessage('Save failed: ' + err); + } + } else { + localStorage['-snap-project-' + name] + = str = this.serializer.serialize(this.stage); + location.hash = '#open:' + str; + this.showMessage('Saved!', 1); + } + } +}; + +IDE_Morph.prototype.saveProjectToDisk = function () { + var data, + link = document.createElement('a'); + + if (Process.prototype.isCatchingErrors) { + try { + data = this.serializer.serialize(this.stage); + link.setAttribute('href', 'data:text/xml,' + data); + link.setAttribute('download', this.projectName + '.xml'); + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + } catch (err) { + this.showMessage('Saving failed: ' + err); + } + } else { + data = this.serializer.serialize(this.stage); + link.setAttribute('href', 'data:text/xml,' + data); + link.setAttribute('download', this.projectName + '.xml'); + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + } +}; + +IDE_Morph.prototype.exportProject = function (name, plain) { + var menu, str; + if (name) { + this.setProjectName(name); + if (Process.prototype.isCatchingErrors) { + try { + menu = this.showMessage('Exporting'); + str = encodeURIComponent( + this.serializer.serialize(this.stage) + ); + location.hash = '#open:' + str; + window.open('data:text/' + + (plain ? 'plain,' + str : 'xml,' + str)); + menu.destroy(); + this.showMessage('Exported!', 1); + } catch (err) { + this.showMessage('Export failed: ' + err); + } + } else { + menu = this.showMessage('Exporting'); + str = encodeURIComponent( + this.serializer.serialize(this.stage) + ); + location.hash = '#open:' + str; + window.open('data:text/' + + (plain ? 'plain,' + str : 'xml,' + str)); + menu.destroy(); + this.showMessage('Exported!', 1); + } + } +}; + +IDE_Morph.prototype.exportGlobalBlocks = function () { + if (this.stage.globalBlocks.length > 0) { + new BlockExportDialogMorph( + this.serializer, + this.stage.globalBlocks + ).popUp(this.world()); + } else { + this.inform( + 'Export blocks', + 'this project doesn\'t have any\n' + + 'custom global blocks yet' + ); + } +}; + +IDE_Morph.prototype.exportSprite = function (sprite) { + var str = this.serializer.serialize(sprite); + window.open('data:text/xml,' + + str + + ''); +}; + +IDE_Morph.prototype.openProjectString = function (str) { + var msg, + myself = this; + this.nextSteps([ + function () { + msg = myself.showMessage('Opening project...'); + }, + function () { + myself.rawOpenProjectString(str); + }, + function () { + msg.destroy(); + } + ]); +}; + +IDE_Morph.prototype.rawOpenProjectString = function (str) { + this.toggleAppMode(false); + this.spriteBar.tabBar.tabTo('scripts'); + StageMorph.prototype.hiddenPrimitives = {}; + StageMorph.prototype.codeMappings = {}; + StageMorph.prototype.codeHeaders = {}; + StageMorph.prototype.enableCodeMapping = false; + if (Process.prototype.isCatchingErrors) { + try { + this.serializer.openProject(this.serializer.load(str), this); + } catch (err) { + this.showMessage('Load failed: ' + err); + } + } else { + this.serializer.openProject(this.serializer.load(str), this); + } + this.stopFastTracking(); +}; + +IDE_Morph.prototype.openCloudDataString = function (str) { + var msg, + myself = this; + this.nextSteps([ + function () { + msg = myself.showMessage('Opening project...'); + }, + function () { + myself.rawOpenCloudDataString(str); + }, + function () { + msg.destroy(); + } + ]); +}; + +IDE_Morph.prototype.rawOpenCloudDataString = function (str) { + var model; + StageMorph.prototype.hiddenPrimitives = {}; + StageMorph.prototype.codeMappings = {}; + StageMorph.prototype.codeHeaders = {}; + StageMorph.prototype.enableCodeMapping = false; + if (Process.prototype.isCatchingErrors) { + try { + model = this.serializer.parse(str); + this.serializer.loadMediaModel(model.childNamed('media')); + this.serializer.openProject( + this.serializer.loadProjectModel(model.childNamed('project')), + this + ); + } catch (err) { + this.showMessage('Load failed: ' + err); + } + } else { + model = this.serializer.parse(str); + this.serializer.loadMediaModel(model.childNamed('media')); + this.serializer.openProject( + this.serializer.loadProjectModel(model.childNamed('project')), + this + ); + } + this.stopFastTracking(); +}; + +IDE_Morph.prototype.openBlocksString = function (str, name, silently) { + var msg, + myself = this; + this.nextSteps([ + function () { + msg = myself.showMessage('Opening blocks...'); + }, + function () { + myself.rawOpenBlocksString(str, name, silently); + }, + function () { + msg.destroy(); + } + ]); +}; + +IDE_Morph.prototype.rawOpenBlocksString = function (str, name, silently) { + // name is optional (string), so is silently (bool) + var blocks, + myself = this; + if (Process.prototype.isCatchingErrors) { + try { + blocks = this.serializer.loadBlocks(str, myself.stage); + } catch (err) { + this.showMessage('Load failed: ' + err); + } + } else { + blocks = this.serializer.loadBlocks(str, myself.stage); + } + if (silently) { + blocks.forEach(function (def) { + def.receiver = myself.stage; + myself.stage.globalBlocks.push(def); + }); + this.flushPaletteCache(); + this.refreshPalette(); + this.showMessage( + 'Imported Blocks Module' + (name ? ': ' + name : '') + '.', + 2 + ); + } else { + new BlockImportDialogMorph(blocks, this.stage, name).popUp(); + } +}; + +IDE_Morph.prototype.openSpritesString = function (str) { + var msg, + myself = this; + this.nextSteps([ + function () { + msg = myself.showMessage('Opening sprite...'); + }, + function () { + myself.rawOpenSpritesString(str); + }, + function () { + msg.destroy(); + } + ]); +}; + +IDE_Morph.prototype.rawOpenSpritesString = function (str) { + if (Process.prototype.isCatchingErrors) { + try { + this.serializer.loadSprites(str, this); + } catch (err) { + this.showMessage('Load failed: ' + err); + } + } else { + this.serializer.loadSprites(str, this); + } +}; + +IDE_Morph.prototype.openMediaString = function (str) { + if (Process.prototype.isCatchingErrors) { + try { + this.serializer.loadMedia(str); + } catch (err) { + this.showMessage('Load failed: ' + err); + } + } else { + this.serializer.loadMedia(str); + } + this.showMessage('Imported Media Module.', 2); +}; + +IDE_Morph.prototype.openProject = function (name) { + var str; + if (name) { + this.showMessage('opening project\n' + name); + this.setProjectName(name); + str = localStorage['-snap-project-' + name]; + this.openProjectString(str); + location.hash = '#open:' + str; + } +}; + + +IDE_Morph.prototype.switchToUserMode = function () { + var world = this.world(); + + world.isDevMode = false; + Process.prototype.isCatchingErrors = true; + this.controlBar.updateLabel(); + this.isAutoFill = true; + this.isDraggable = false; + this.reactToWorldResize(world.bounds.copy()); + this.siblings().forEach(function (morph) { + if (morph instanceof DialogBoxMorph) { + world.add(morph); // bring to front + } else { + morph.destroy(); + } + }); + this.flushBlocksCache(); + this.refreshPalette(); + // prevent non-DialogBoxMorphs from being dropped + // onto the World in user-mode + world.reactToDropOf = function (morph) { + if (!(morph instanceof DialogBoxMorph)) { + world.hand.grab(morph); + } + }; + this.showMessage('entering user mode', 1); + +}; + +IDE_Morph.prototype.switchToDevMode = function () { + var world = this.world(); + + world.isDevMode = true; + Process.prototype.isCatchingErrors = false; + this.controlBar.updateLabel(); + this.isAutoFill = false; + this.isDraggable = true; + this.setExtent(world.extent().subtract(100)); + this.setPosition(world.position().add(20)); + this.flushBlocksCache(); + this.refreshPalette(); + // enable non-DialogBoxMorphs to be dropped + // onto the World in dev-mode + delete world.reactToDropOf; + this.showMessage( + 'entering development mode.\n\n' + + 'error catching is turned off,\n' + + 'use the browser\'s web console\n' + + 'to see error messages.' + ); +}; + +IDE_Morph.prototype.flushBlocksCache = function (category) { + // if no category is specified, the whole cache gets flushed + if (category) { + this.stage.blocksCache[category] = null; + this.stage.children.forEach(function (m) { + if (m instanceof SpriteMorph) { + m.blocksCache[category] = null; + } + }); + } else { + this.stage.blocksCache = {}; + this.stage.children.forEach(function (m) { + if (m instanceof SpriteMorph) { + m.blocksCache = {}; + } + }); + } + this.flushPaletteCache(category); +}; + +IDE_Morph.prototype.flushPaletteCache = function (category) { + // if no category is specified, the whole cache gets flushed + if (category) { + this.stage.paletteCache[category] = null; + this.stage.children.forEach(function (m) { + if (m instanceof SpriteMorph) { + m.paletteCache[category] = null; + } + }); + } else { + this.stage.paletteCache = {}; + this.stage.children.forEach(function (m) { + if (m instanceof SpriteMorph) { + m.paletteCache = {}; + } + }); + } +}; + +IDE_Morph.prototype.toggleZebraColoring = function () { + var scripts = []; + + if (!BlockMorph.prototype.zebraContrast) { + BlockMorph.prototype.zebraContrast = 40; + } else { + BlockMorph.prototype.zebraContrast = 0; + } + + // select all scripts: + this.stage.children.concat(this.stage).forEach(function (morph) { + if (morph instanceof SpriteMorph || morph instanceof StageMorph) { + scripts = scripts.concat( + morph.scripts.children.filter(function (morph) { + return morph instanceof BlockMorph; + }) + ); + } + }); + + // force-update all scripts: + scripts.forEach(function (topBlock) { + topBlock.fixBlockColor(null, true); + }); +}; + +IDE_Morph.prototype.toggleDynamicInputLabels = function () { + var projectData; + SyntaxElementMorph.prototype.dynamicInputLabels = + !SyntaxElementMorph.prototype.dynamicInputLabels; + if (Process.prototype.isCatchingErrors) { + try { + projectData = this.serializer.serialize(this.stage); + } catch (err) { + this.showMessage('Serialization failed: ' + err); + } + } else { + projectData = this.serializer.serialize(this.stage); + } + SpriteMorph.prototype.initBlocks(); + this.spriteBar.tabBar.tabTo('scripts'); + this.createCategories(); + this.createCorralBar(); + this.openProjectString(projectData); +}; + +IDE_Morph.prototype.toggleBlurredShadows = function () { + window.useBlurredShadows = !useBlurredShadows; +}; + +IDE_Morph.prototype.toggleLongFormInputDialog = function () { + InputSlotDialogMorph.prototype.isLaunchingExpanded = + !InputSlotDialogMorph.prototype.isLaunchingExpanded; + if (InputSlotDialogMorph.prototype.isLaunchingExpanded) { + this.saveSetting('longform', true); + } else { + this.removeSetting('longform'); + } +}; + +IDE_Morph.prototype.togglePreferEmptySlotDrops = function () { + ScriptsMorph.prototype.isPreferringEmptySlots = + !ScriptsMorph.prototype.isPreferringEmptySlots; +}; + +IDE_Morph.prototype.toggleVirtualKeyboard = function () { + MorphicPreferences.useVirtualKeyboard = + !MorphicPreferences.useVirtualKeyboard; +}; + +IDE_Morph.prototype.toggleInputSliders = function () { + MorphicPreferences.useSliderForInput = + !MorphicPreferences.useSliderForInput; +}; + +IDE_Morph.prototype.toggleSliderExecute = function () { + InputSlotMorph.prototype.executeOnSliderEdit = + !InputSlotMorph.prototype.executeOnSliderEdit; +}; + +IDE_Morph.prototype.toggleAppMode = function (appMode) { + var world = this.world(), + elements = [ + this.logo, + this.controlBar.cloudButton, + this.controlBar.projectButton, + this.controlBar.settingsButton, + this.controlBar.stageSizeButton, + this.corral, + this.corralBar, + this.spriteEditor, + this.spriteBar, + this.palette, + this.categories + ]; + + this.isAppMode = isNil(appMode) ? !this.isAppMode : appMode; + + Morph.prototype.trackChanges = false; + if (this.isAppMode) { + this.setColor(this.appModeColor); + this.controlBar.setColor(this.color); + this.controlBar.appModeButton.refresh(); + elements.forEach(function (e) { + e.hide(); + }); + world.children.forEach(function (morph) { + if (morph instanceof DialogBoxMorph) { + morph.hide(); + } + }); + } else { + this.setColor(this.backgroundColor); + this.controlBar.setColor(this.frameColor); + elements.forEach(function (e) { + e.show(); + }); + this.stage.setScale(1); + // show all hidden dialogs + world.children.forEach(function (morph) { + if (morph instanceof DialogBoxMorph) { + morph.show(); + } + }); + // prevent scrollbars from showing when morph appears + world.allChildren().filter(function (c) { + return c instanceof ScrollFrameMorph; + }).forEach(function (s) { + s.adjustScrollBars(); + }); + } + this.setExtent(this.world().extent()); // resume trackChanges +}; + +IDE_Morph.prototype.toggleStageSize = function (isSmall) { + var myself = this, + world = this.world(); + + function zoomIn() { + myself.stageRatio = 1; + myself.step = function () { + myself.stageRatio -= (myself.stageRatio - 0.5) / 2; + myself.setExtent(world.extent()); + if (myself.stageRatio < 0.6) { + myself.stageRatio = 0.5; + myself.setExtent(world.extent()); + delete myself.step; + } + }; + } + + function zoomOut() { + myself.isSmallStage = true; + myself.stageRatio = 0.5; + myself.step = function () { + myself.stageRatio += (1 - myself.stageRatio) / 2; + myself.setExtent(world.extent()); + if (myself.stageRatio > 0.9) { + myself.isSmallStage = false; + myself.setExtent(world.extent()); + myself.controlBar.stageSizeButton.refresh(); + delete myself.step; + } + }; + } + + this.isSmallStage = isNil(isSmall) ? !this.isSmallStage : isSmall; + if (this.isAnimating) { + if (this.isSmallStage) { + zoomIn(); + } else { + zoomOut(); + } + } else { + if (this.isSmallStage) {this.stageRatio = 0.5; } + this.setExtent(world.extent()); + } +}; + +IDE_Morph.prototype.openProjectsBrowser = function () { + new ProjectDialogMorph(this, 'open').popUp(); +}; + +IDE_Morph.prototype.saveProjectsBrowser = function () { + new ProjectDialogMorph(this, 'save').popUp(); +}; + +// IDE_Morph localization + +IDE_Morph.prototype.languageMenu = function () { + var menu = new MenuMorph(this), + world = this.world(), + pos = this.controlBar.settingsButton.bottomLeft(), + myself = this; + SnapTranslator.languages().forEach(function (lang) { + menu.addItem( + (SnapTranslator.language === lang ? '\u2713 ' : ' ') + + SnapTranslator.languageName(lang), + function () {myself.setLanguage(lang); } + ); + }); + menu.popup(world, pos); +}; + +IDE_Morph.prototype.setLanguage = function (lang, callback) { + var translation = document.getElementById('language'), + src = 'lang-' + lang + '.js', + myself = this; + SnapTranslator.unload(); + if (translation) { + document.head.removeChild(translation); + } + if (lang === 'en') { + return this.reflectLanguage('en', callback); + } + translation = document.createElement('script'); + translation.id = 'language'; + translation.onload = function () { + myself.reflectLanguage(lang, callback); + }; + document.head.appendChild(translation); + translation.src = src; +}; + +IDE_Morph.prototype.reflectLanguage = function (lang, callback) { + var projectData; + SnapTranslator.language = lang; + if (!this.loadNewProject) { + if (Process.prototype.isCatchingErrors) { + try { + projectData = this.serializer.serialize(this.stage); + } catch (err) { + this.showMessage('Serialization failed: ' + err); + } + } else { + projectData = this.serializer.serialize(this.stage); + } + } + SpriteMorph.prototype.initBlocks(); + this.spriteBar.tabBar.tabTo('scripts'); + this.createCategories(); + this.createCorralBar(); + this.fixLayout(); + if (this.loadNewProject) { + this.newProject(); + } else { + this.openProjectString(projectData); + } + this.saveSetting('language', lang); + if (callback) {callback.call(this); } +}; + +// IDE_Morph blocks scaling + +IDE_Morph.prototype.userSetBlocksScale = function () { + var myself = this, + scrpt, + blck, + shield, + sample, + action; + + scrpt = new CommandBlockMorph(); + scrpt.color = SpriteMorph.prototype.blockColor.motion; + scrpt.setSpec(localize('build')); + blck = new CommandBlockMorph(); + blck.color = SpriteMorph.prototype.blockColor.sound; + blck.setSpec(localize('your own')); + scrpt.nextBlock(blck); + blck = new CommandBlockMorph(); + blck.color = SpriteMorph.prototype.blockColor.operators; + blck.setSpec(localize('blocks')); + scrpt.bottomBlock().nextBlock(blck); + /* + blck = SpriteMorph.prototype.blockForSelector('doForever'); + blck.inputs()[0].nestedBlock(scrpt); + */ + + sample = new FrameMorph(); + sample.acceptsDrops = false; + sample.texture = this.scriptsPaneTexture; + sample.setExtent(new Point(250, 180)); + scrpt.setPosition(sample.position().add(10)); + sample.add(scrpt); + + shield = new Morph(); + shield.alpha = 0; + shield.setExtent(sample.extent()); + shield.setPosition(sample.position()); + sample.add(shield); + + action = function (num) { + /* + var c; + blck.setScale(num); + blck.drawNew(); + blck.setSpec(blck.blockSpec); + c = blck.inputs()[0]; + c.setScale(num); + c.nestedBlock(scrpt); + */ + scrpt.blockSequence().forEach(function (block) { + block.setScale(num); + block.drawNew(); + block.setSpec(block.blockSpec); + }); + }; + + new DialogBoxMorph( + null, + function (num) { + myself.setBlocksScale(num); + } + ).withKey('zoomBlocks').prompt( + 'Zoom blocks', + SyntaxElementMorph.prototype.scale.toString(), + this.world(), + sample, // pic + { + 'normal (1x)' : 1, + 'demo (1.2x)' : 1.2, + 'presentation (1.4x)' : 1.4, + 'big (2x)' : 2, + 'huge (4x)' : 4, + 'giant (8x)' : 8, + 'monstrous (10x)' : 10 + }, + false, // read only? + true, // numeric + 1, // slider min + 12, // slider max + action // slider action + ); +}; + +IDE_Morph.prototype.setBlocksScale = function (num) { + var projectData; + if (Process.prototype.isCatchingErrors) { + try { + projectData = this.serializer.serialize(this.stage); + } catch (err) { + this.showMessage('Serialization failed: ' + err); + } + } else { + projectData = this.serializer.serialize(this.stage); + } + SyntaxElementMorph.prototype.setScale(num); + CommentMorph.prototype.refreshScale(); + SpriteMorph.prototype.initBlocks(); + this.spriteBar.tabBar.tabTo('scripts'); + this.createCategories(); + this.createCorralBar(); + this.fixLayout(); + this.openProjectString(projectData); + this.saveSetting('zoom', num); +}; + +// IDE_Morph cloud interface + +IDE_Morph.prototype.initializeCloud = function () { + var myself = this, + world = this.world(); + new DialogBoxMorph( + null, + function (user) { + var pwh = hex_sha512(user.password), + str; + SnapCloud.login( + user.username, + pwh, + function () { + if (user.choice) { + str = SnapCloud.encodeDict( + { + username: user.username, + password: pwh + } + ); + localStorage['-snap-user'] = str; + } + myself.source = 'cloud'; + myself.showMessage('now connected.', 2); + }, + myself.cloudError() + ); + } + ).withKey('cloudlogin').promptCredentials( + 'Sign in', + 'login', + null, + null, + null, + null, + 'stay signed in on this computer\nuntil logging out', + world, + myself.cloudIcon(), + myself.cloudMsg + ); +}; + +IDE_Morph.prototype.createCloudAccount = function () { + var myself = this, + world = this.world(); +/* + // force-logout, commented out for now: + delete localStorage['-snap-user']; + SnapCloud.clear(); +*/ + new DialogBoxMorph( + null, + function (user) { + SnapCloud.signup( + user.username, + user.email, + function (txt, title) { + new DialogBoxMorph().inform( + title, + txt + + '.\n\nAn e-mail with your password\n' + + 'has been sent to the address provided', + world, + myself.cloudIcon(null, new Color(0, 180, 0)) + ); + }, + myself.cloudError() + ); + } + ).withKey('cloudsignup').promptCredentials( + 'Sign up', + 'signup', + 'http://snap.berkeley.edu/tos.html', + 'Terms of Service...', + 'http://snap.berkeley.edu/privacy.html', + 'Privacy...', + 'I have read and agree\nto the Terms of Service', + world, + myself.cloudIcon(), + myself.cloudMsg + ); +}; + +IDE_Morph.prototype.resetCloudPassword = function () { + var myself = this, + world = this.world(); +/* + // force-logout, commented out for now: + delete localStorage['-snap-user']; + SnapCloud.clear(); +*/ + new DialogBoxMorph( + null, + function (user) { + SnapCloud.resetPassword( + user.username, + function (txt, title) { + new DialogBoxMorph().inform( + title, + txt + + '.\n\nAn e-mail with a link to\n' + + 'reset your password\n' + + 'has been sent to the address provided', + world, + myself.cloudIcon(null, new Color(0, 180, 0)) + ); + }, + myself.cloudError() + ); + } + ).withKey('cloudresetpassword').promptCredentials( + 'Reset password', + 'resetPassword', + null, + null, + null, + null, + null, + world, + myself.cloudIcon(), + myself.cloudMsg + ); +}; + +IDE_Morph.prototype.changeCloudPassword = function () { + var myself = this, + world = this.world(); + new DialogBoxMorph( + null, + function (user) { + SnapCloud.changePassword( + user.oldpassword, + user.password, + function () { + myself.logout(); + myself.showMessage('password has been changed.', 2); + }, + myself.cloudError() + ); + } + ).withKey('cloudpassword').promptCredentials( + 'Change Password', + 'changePassword', + null, + null, + null, + null, + null, + world, + myself.cloudIcon(), + myself.cloudMsg + ); +}; + +IDE_Morph.prototype.logout = function () { + var myself = this; + delete localStorage['-snap-user']; + SnapCloud.logout( + function () { + SnapCloud.clear(); + myself.showMessage('disconnected.', 2); + }, + function () { + SnapCloud.clear(); + myself.showMessage('disconnected.', 2); + } + ); +}; + +IDE_Morph.prototype.saveProjectToCloud = function (name) { + var myself = this; + if (name) { + this.showMessage('Saving project\nto the cloud...'); + this.setProjectName(name); + SnapCloud.saveProject( + this, + function () {myself.showMessage('saved.', 2); }, + this.cloudError() + ); + } +}; + +IDE_Morph.prototype.exportProjectMedia = function (name) { + var menu, media; + this.serializer.isCollectingMedia = true; + if (name) { + this.setProjectName(name); + if (Process.prototype.isCatchingErrors) { + try { + menu = this.showMessage('Exporting'); + encodeURIComponent( + this.serializer.serialize(this.stage) + ); + media = encodeURIComponent( + this.serializer.mediaXML(name) + ); + window.open('data:text/xml,' + media); + menu.destroy(); + this.showMessage('Exported!', 1); + } catch (err) { + this.serializer.isCollectingMedia = false; + this.showMessage('Export failed: ' + err); + } + } else { + menu = this.showMessage('Exporting'); + encodeURIComponent( + this.serializer.serialize(this.stage) + ); + media = encodeURIComponent( + this.serializer.mediaXML() + ); + window.open('data:text/xml,' + media); + menu.destroy(); + this.showMessage('Exported!', 1); + } + } + this.serializer.isCollectingMedia = false; + this.serializer.flushMedia(); + // this.hasChangedMedia = false; +}; + +IDE_Morph.prototype.exportProjectNoMedia = function (name) { + var menu, str; + this.serializer.isCollectingMedia = true; + if (name) { + this.setProjectName(name); + if (Process.prototype.isCatchingErrors) { + try { + menu = this.showMessage('Exporting'); + str = encodeURIComponent( + this.serializer.serialize(this.stage) + ); + window.open('data:text/xml,' + str); + menu.destroy(); + this.showMessage('Exported!', 1); + } catch (err) { + this.serializer.isCollectingMedia = false; + this.showMessage('Export failed: ' + err); + } + } else { + menu = this.showMessage('Exporting'); + str = encodeURIComponent( + this.serializer.serialize(this.stage) + ); + window.open('data:text/xml,' + str); + menu.destroy(); + this.showMessage('Exported!', 1); + } + } + this.serializer.isCollectingMedia = false; + this.serializer.flushMedia(); +}; + +IDE_Morph.prototype.exportProjectAsCloudData = function (name) { + var menu, str, media, dta; + this.serializer.isCollectingMedia = true; + if (name) { + this.setProjectName(name); + if (Process.prototype.isCatchingErrors) { + try { + menu = this.showMessage('Exporting'); + str = encodeURIComponent( + this.serializer.serialize(this.stage) + ); + media = encodeURIComponent( + this.serializer.mediaXML(name) + ); + dta = encodeURIComponent('') + + str + + media + + encodeURIComponent(''); + window.open('data:text/xml,' + dta); + menu.destroy(); + this.showMessage('Exported!', 1); + } catch (err) { + this.serializer.isCollectingMedia = false; + this.showMessage('Export failed: ' + err); + } + } else { + menu = this.showMessage('Exporting'); + str = encodeURIComponent( + this.serializer.serialize(this.stage) + ); + media = encodeURIComponent( + this.serializer.mediaXML() + ); + dta = encodeURIComponent('') + + str + + media + + encodeURIComponent(''); + window.open('data:text/xml,' + dta); + menu.destroy(); + this.showMessage('Exported!', 1); + } + } + this.serializer.isCollectingMedia = false; + this.serializer.flushMedia(); + // this.hasChangedMedia = false; +}; + +IDE_Morph.prototype.cloudAcknowledge = function () { + var myself = this; + return function (responseText, url) { + nop(responseText); + new DialogBoxMorph().inform( + 'Cloud Connection', + 'Successfully connected to:\n' + + 'http://' + + url, + myself.world(), + myself.cloudIcon(null, new Color(0, 180, 0)) + ); + }; +}; + +IDE_Morph.prototype.cloudResponse = function () { + var myself = this; + return function (responseText, url) { + var response = responseText; + if (response.length > 50) { + response = response.substring(0, 50) + '...'; + } + new DialogBoxMorph().inform( + 'Snap!Cloud', + 'http://' + + url + ':\n\n' + + 'responds:\n' + + response, + myself.world(), + myself.cloudIcon(null, new Color(0, 180, 0)) + ); + }; +}; + +IDE_Morph.prototype.cloudError = function () { + var myself = this; + + function getURL(url) { + try { + var request = new XMLHttpRequest(); + request.open('GET', url, false); + request.send(); + if (request.status === 200) { + return request.responseText; + } + return null; + } catch (err) { + return null; + } + } + + return function (responseText, url) { + // first, try to find out an explanation for the error + // and notify the user about it, + // if none is found, show an error dialog box + var response = responseText, + explanation = getURL('http://snap.berkeley.edu/cloudmsg.txt'); + if (myself.shield) { + myself.shield.destroy(); + myself.shield = null; + } + if (explanation) { + myself.showMessage(explanation); + return; + } + if (response.length > 50) { + response = response.substring(0, 50) + '...'; + } + new DialogBoxMorph().inform( + 'Snap!Cloud', + (url ? url + '\n' : '') + + response, + myself.world(), + myself.cloudIcon(null, new Color(180, 0, 0)) + ); + }; +}; + +IDE_Morph.prototype.cloudIcon = function (height, color) { + var clr = color || DialogBoxMorph.prototype.titleBarColor, + isFlat = MorphicPreferences.isFlat, + icon = new SymbolMorph( + isFlat ? 'cloud' : 'cloudGradient', + height || 50, + clr, + isFlat ? null : new Point(-1, -1), + clr.darker(50) + ); + if (!isFlat) { + icon.addShadow(new Point(1, 1), 1, clr.lighter(95)); + } + return icon; +}; + +IDE_Morph.prototype.setCloudURL = function () { + new DialogBoxMorph( + null, + function (url) { + SnapCloud.url = url; + } + ).withKey('cloudURL').prompt( + 'Cloud URL', + SnapCloud.url, + this.world(), + null, + { + 'Snap!Cloud' : + 'https://snapcloud.miosoft.com/miocon/app/' + + 'login?_app=SnapCloud', + 'local network lab' : + '192.168.2.110:8087/miocon/app/login?_app=SnapCloud', + 'local network office' : + '192.168.186.167:8087/miocon/app/login?_app=SnapCloud', + 'localhost dev' : + 'localhost/miocon/app/login?_app=SnapCloud' + } + ); +}; + +// IDE_Morph user dialog shortcuts + +IDE_Morph.prototype.showMessage = function (message, secs) { + var m = new MenuMorph(null, message), + intervalHandle; + m.popUpCenteredInWorld(this.world()); + if (secs) { + intervalHandle = setInterval(function () { + m.destroy(); + clearInterval(intervalHandle); + }, secs * 1000); + } + return m; +}; + +IDE_Morph.prototype.inform = function (title, message) { + new DialogBoxMorph().inform( + title, + localize(message), + this.world() + ); +}; + +IDE_Morph.prototype.confirm = function (message, title, action) { + new DialogBoxMorph(null, action).askYesNo( + title, + localize(message), + this.world() + ); +}; + +IDE_Morph.prototype.prompt = function (message, callback, choices, key) { + (new DialogBoxMorph(null, callback)).withKey(key).prompt( + message, + '', + this.world(), + null, + choices + ); +}; + +// ProjectDialogMorph //////////////////////////////////////////////////// + +// ProjectDialogMorph inherits from DialogBoxMorph: + +ProjectDialogMorph.prototype = new DialogBoxMorph(); +ProjectDialogMorph.prototype.constructor = ProjectDialogMorph; +ProjectDialogMorph.uber = DialogBoxMorph.prototype; + +// ProjectDialogMorph instance creation: + +function ProjectDialogMorph(ide, label) { + this.init(ide, label); +} + +ProjectDialogMorph.prototype.init = function (ide, task) { + var myself = this; + + // additional properties: + this.ide = ide; + this.task = task || 'open'; // String describing what do do (open, save) + this.source = ide.source || 'local'; // or 'cloud' or 'examples' + this.projectList = []; // [{name: , thumb: , notes:}] + + this.handle = null; + this.srcBar = null; + this.nameField = null; + this.listField = null; + this.preview = null; + this.notesText = null; + this.notesField = null; + this.deleteButton = null; + this.shareButton = null; + this.unshareButton = null; + + // initialize inherited properties: + ProjectDialogMorph.uber.init.call( + this, + this, // target + null, // function + null // environment + ); + + // override inherited properites: + this.labelString = this.task === 'save' ? 'Save Project' : 'Open Project'; + this.createLabel(); + this.key = 'project' + task; + + // build contents + this.buildContents(); + this.onNextStep = function () { // yield to show "updating" message + myself.setSource(myself.source); + }; +}; + +ProjectDialogMorph.prototype.buildContents = function () { + var thumbnail, notification; + + this.addBody(new Morph()); + this.body.color = this.color; + + this.srcBar = new AlignmentMorph('column', this.padding / 2); + + if (this.ide.cloudMsg) { + notification = new TextMorph( + this.ide.cloudMsg, + 10, + null, // style + false, // bold + null, // italic + null, // alignment + null, // width + null, // font name + new Point(1, 1), // shadow offset + new Color(255, 255, 255) // shadowColor + ); + notification.refresh = nop; + this.srcBar.add(notification); + } + + this.addSourceButton('cloud', localize('Cloud'), 'cloud'); + this.addSourceButton('local', localize('Browser'), 'storage'); + if (this.task === 'open') { + this.addSourceButton('examples', localize('Examples'), 'poster'); + } + this.srcBar.fixLayout(); + this.body.add(this.srcBar); + + if (this.task === 'save') { + this.nameField = new InputFieldMorph(this.ide.projectName); + this.body.add(this.nameField); + } + + this.listField = new ListMorph([]); + this.fixListFieldItemColors(); + this.listField.fixLayout = nop; + this.listField.edge = InputFieldMorph.prototype.edge; + this.listField.fontSize = InputFieldMorph.prototype.fontSize; + this.listField.typeInPadding = InputFieldMorph.prototype.typeInPadding; + this.listField.contrast = InputFieldMorph.prototype.contrast; + this.listField.drawNew = InputFieldMorph.prototype.drawNew; + this.listField.drawRectBorder = InputFieldMorph.prototype.drawRectBorder; + + this.body.add(this.listField); + + this.preview = new Morph(); + this.preview.fixLayout = nop; + this.preview.edge = InputFieldMorph.prototype.edge; + this.preview.fontSize = InputFieldMorph.prototype.fontSize; + this.preview.typeInPadding = InputFieldMorph.prototype.typeInPadding; + this.preview.contrast = InputFieldMorph.prototype.contrast; + this.preview.drawNew = function () { + InputFieldMorph.prototype.drawNew.call(this); + if (this.texture) { + this.drawTexture(this.texture); + } + }; + this.preview.drawCachedTexture = function () { + var context = this.image.getContext('2d'); + context.drawImage(this.cachedTexture, this.edge, this.edge); + this.changed(); + }; + this.preview.drawRectBorder = InputFieldMorph.prototype.drawRectBorder; + this.preview.setExtent( + this.ide.serializer.thumbnailSize.add(this.preview.edge * 2) + ); + + this.body.add(this.preview); + this.preview.drawNew(); + if (this.task === 'save') { + thumbnail = this.ide.stage.thumbnail( + SnapSerializer.prototype.thumbnailSize + ); + this.preview.texture = null; + this.preview.cachedTexture = thumbnail; + this.preview.drawCachedTexture(); + } + + this.notesField = new ScrollFrameMorph(); + this.notesField.fixLayout = nop; + + this.notesField.edge = InputFieldMorph.prototype.edge; + this.notesField.fontSize = InputFieldMorph.prototype.fontSize; + this.notesField.typeInPadding = InputFieldMorph.prototype.typeInPadding; + this.notesField.contrast = InputFieldMorph.prototype.contrast; + this.notesField.drawNew = InputFieldMorph.prototype.drawNew; + this.notesField.drawRectBorder = InputFieldMorph.prototype.drawRectBorder; + + this.notesField.acceptsDrops = false; + this.notesField.contents.acceptsDrops = false; + + if (this.task === 'open') { + this.notesText = new TextMorph(''); + } else { // 'save' + this.notesText = new TextMorph(this.ide.projectNotes); + this.notesText.isEditable = true; + this.notesText.enableSelecting(); + } + + this.notesField.isTextLineWrapping = true; + this.notesField.padding = 3; + this.notesField.setContents(this.notesText); + this.notesField.setWidth(this.preview.width()); + + this.body.add(this.notesField); + + if (this.task === 'open') { + this.addButton('openProject', 'Open'); + this.action = 'openProject'; + } else { // 'save' + this.addButton('saveProject', 'Save'); + this.action = 'saveProject'; + } + this.shareButton = this.addButton('shareProject', 'Share'); + this.unshareButton = this.addButton('unshareProject', 'Unshare'); + this.shareButton.hide(); + this.unshareButton.hide(); + this.deleteButton = this.addButton('deleteProject', 'Delete'); + this.addButton('cancel', 'Cancel'); + + if (notification) { + this.setExtent(new Point(455, 335).add(notification.extent())); + } else { + this.setExtent(new Point(455, 335)); + } + this.fixLayout(); + +}; + +ProjectDialogMorph.prototype.popUp = function (wrrld) { + var world = wrrld || this.ide.world(); + if (world) { + ProjectDialogMorph.uber.popUp.call(this, world); + this.handle = new HandleMorph( + this, + 350, + 300, + this.corner, + this.corner + ); + } +}; + +// ProjectDialogMorph source buttons + +ProjectDialogMorph.prototype.addSourceButton = function ( + source, + label, + symbol +) { + var myself = this, + lbl1 = new StringMorph( + label, + 10, + null, + true, + null, + null, + new Point(1, 1), + new Color(255, 255, 255) + ), + lbl2 = new StringMorph( + label, + 10, + null, + true, + null, + null, + new Point(-1, -1), + this.titleBarColor.darker(50), + new Color(255, 255, 255) + ), + l1 = new Morph(), + l2 = new Morph(), + button; + + lbl1.add(new SymbolMorph( + symbol, + 24, + this.titleBarColor.darker(20), + new Point(1, 1), + this.titleBarColor.darker(50) + )); + lbl1.children[0].setCenter(lbl1.center()); + lbl1.children[0].setBottom(lbl1.top() - this.padding / 2); + + l1.image = lbl1.fullImage(); + l1.bounds = lbl1.fullBounds(); + + lbl2.add(new SymbolMorph( + symbol, + 24, + new Color(255, 255, 255), + new Point(-1, -1), + this.titleBarColor.darker(50) + )); + lbl2.children[0].setCenter(lbl2.center()); + lbl2.children[0].setBottom(lbl2.top() - this.padding / 2); + + l2.image = lbl2.fullImage(); + l2.bounds = lbl2.fullBounds(); + + button = new ToggleButtonMorph( + null, //colors, + myself, // the ProjectDialog is the target + function () { // action + myself.setSource(source); + }, + [l1, l2], + function () { // query + return myself.source === source; + } + ); + + button.corner = this.buttonCorner; + button.edge = this.buttonEdge; + button.outline = this.buttonOutline; + button.outlineColor = this.buttonOutlineColor; + button.outlineGradient = this.buttonOutlineGradient; + button.labelMinExtent = new Point(60, 0); + button.padding = this.buttonPadding; + button.contrast = this.buttonContrast; + button.pressColor = this.titleBarColor.darker(20); + + button.drawNew(); + button.fixLayout(); + button.refresh(); + this.srcBar.add(button); +}; + +// ProjectDialogMorph list field control + +ProjectDialogMorph.prototype.fixListFieldItemColors = function () { + // remember to always fixLayout() afterwards for the changes + // to take effect + var myself = this; + this.listField.contents.children[0].alpha = 0; + this.listField.contents.children[0].children.forEach(function (item) { + item.pressColor = myself.titleBarColor.darker(20); + item.color = new Color(0, 0, 0, 0); + item.noticesTransparentClick = true; + }); +}; + +// ProjectDialogMorph ops + +ProjectDialogMorph.prototype.setSource = function (source) { + var myself = this, + msg; + + this.source = source; //this.task === 'save' ? 'local' : source; + this.srcBar.children.forEach(function (button) { + button.refresh(); + }); + switch (this.source) { + case 'cloud': + msg = myself.ide.showMessage('Updating\nproject list...'); + this.projectList = []; + SnapCloud.getProjectList( + function (projectList) { + myself.installCloudProjectList(projectList); + msg.destroy(); + }, + function (err, lbl) { + msg.destroy(); + myself.ide.cloudError().call(null, err, lbl); + } + ); + return; + case 'local': + this.projectList = this.getLocalProjectList(); + break; + case 'examples': + this.projectList = []; + break; + } + + this.listField.destroy(); + this.listField = new ListMorph( + this.projectList, + this.projectList.length > 0 ? + function (element) { + return element.name; + } : null, + null, + function () {myself.ok(); } + ); + + this.fixListFieldItemColors(); + this.listField.fixLayout = nop; + this.listField.edge = InputFieldMorph.prototype.edge; + this.listField.fontSize = InputFieldMorph.prototype.fontSize; + this.listField.typeInPadding = InputFieldMorph.prototype.typeInPadding; + this.listField.contrast = InputFieldMorph.prototype.contrast; + this.listField.drawNew = InputFieldMorph.prototype.drawNew; + this.listField.drawRectBorder = InputFieldMorph.prototype.drawRectBorder; + + if (this.source === 'local') { + this.listField.action = function (item) { + var src, xml; + + if (item === undefined) {return; } + if (myself.nameField) { + myself.nameField.setContents(item.name || ''); + } + if (myself.task === 'open') { + + src = localStorage['-snap-project-' + item.name]; + xml = myself.ide.serializer.parse(src); + + myself.notesText.text = xml.childNamed('notes').contents + || ''; + myself.notesText.drawNew(); + myself.notesField.contents.adjustBounds(); + myself.preview.texture = xml.childNamed('thumbnail').contents + || null; + myself.preview.cachedTexture = null; + myself.preview.drawNew(); + } + myself.edit(); + }; + } else { // 'examples', 'cloud' is initialized elsewhere + this.listField.action = function (item) { + if (item === undefined) {return; } + if (myself.nameField) { + myself.nameField.setContents(item.name || ''); + } + if (myself.task === 'open') { + myself.notesText.text = item.notes || ''; + myself.notesText.drawNew(); + myself.notesField.contents.adjustBounds(); + myself.preview.texture = item.thumb || null; + myself.preview.cachedTexture = null; + myself.preview.drawNew(); + } + myself.edit(); + }; + } + this.body.add(this.listField); + + this.shareButton.hide(); + this.unshareButton.hide(); + if (this.source === 'local') { + this.deleteButton.show(); + } else { // examples + this.deleteButton.hide(); + } + this.buttons.fixLayout(); + this.fixLayout(); + if (this.task === 'open') { + this.clearDetails(); + } +}; + +ProjectDialogMorph.prototype.getLocalProjectList = function () { + var stored, name, dta, + projects = []; + for (stored in localStorage) { + if (Object.prototype.hasOwnProperty.call(localStorage, stored) + && stored.substr(0, 14) === '-snap-project-') { + name = stored.substr(14); + dta = { + name: name, + thumb: null, + notes: null + }; + projects.push(dta); + } + } + projects.sort(function (x, y) { + return x.name < y.name ? -1 : 1; + }); + return projects; +}; + +ProjectDialogMorph.prototype.installCloudProjectList = function (pl) { + var myself = this; + this.projectList = pl || []; + this.projectList.sort(function (x, y) { + return x.ProjectName < y.ProjectName ? -1 : 1; + }); + + this.listField.destroy(); + this.listField = new ListMorph( + this.projectList, + this.projectList.length > 0 ? + function (element) { + return element.ProjectName; + } : null, + [ // format: display shared project names bold + [ + 'bold', + function (proj) {return proj.Public === 'true'; } + ] + ], + function () {myself.ok(); } + ); + this.fixListFieldItemColors(); + this.listField.fixLayout = nop; + this.listField.edge = InputFieldMorph.prototype.edge; + this.listField.fontSize = InputFieldMorph.prototype.fontSize; + this.listField.typeInPadding = InputFieldMorph.prototype.typeInPadding; + this.listField.contrast = InputFieldMorph.prototype.contrast; + this.listField.drawNew = InputFieldMorph.prototype.drawNew; + this.listField.drawRectBorder = InputFieldMorph.prototype.drawRectBorder; + + this.listField.action = function (item) { + if (item === undefined) {return; } + if (myself.nameField) { + myself.nameField.setContents(item.ProjectName || ''); + } + if (myself.task === 'open') { + myself.notesText.text = item.Notes || ''; + myself.notesText.drawNew(); + myself.notesField.contents.adjustBounds(); + myself.preview.texture = item.Thumbnail || null; + myself.preview.cachedTexture = null; + myself.preview.drawNew(); + } + if (item.Public === 'true') { + myself.shareButton.hide(); + myself.unshareButton.show(); + } else { + myself.unshareButton.hide(); + myself.shareButton.show(); + } + myself.buttons.fixLayout(); + myself.fixLayout(); + myself.edit(); + }; + this.body.add(this.listField); + this.shareButton.show(); + this.unshareButton.hide(); + this.deleteButton.show(); + this.buttons.fixLayout(); + this.fixLayout(); + if (this.task === 'open') { + this.clearDetails(); + } +}; + +ProjectDialogMorph.prototype.clearDetails = function () { + this.notesText.text = ''; + this.notesText.drawNew(); + this.notesField.contents.adjustBounds(); + this.preview.texture = null; + this.preview.cachedTexture = null; + this.preview.drawNew(); +}; + +ProjectDialogMorph.prototype.openProject = function () { + var myself = this, + proj = this.listField.selected; + if (!proj) {return; } + if (this.source === 'cloud') { + this.openCloudProject(proj); + } else { // 'local, examples' + myself.ide.source = 'local'; + this.ide.openProject(proj.name); + this.destroy(); + } +}; + +ProjectDialogMorph.prototype.openCloudProject = function (project) { + var myself = this; + myself.ide.nextSteps([ + function () { + myself.ide.showMessage('Fetching project\nfrom the cloud...'); + }, + function () { + myself.rawOpenCloudProject(project); + } + ]); +}; + +ProjectDialogMorph.prototype.rawOpenCloudProject = function (proj) { + var myself = this; + SnapCloud.reconnect( + function () { + SnapCloud.callService( + 'getProject', + function (response) { + SnapCloud.disconnect(); + myself.ide.source = 'cloud'; + myself.ide.droppedText(response[0].SourceCode); + if (proj.Public === 'true') { + location.hash = '#present:Username=' + + encodeURIComponent(SnapCloud.username) + + '&ProjectName=' + + encodeURIComponent(proj.ProjectName); + } + }, + myself.ide.cloudError(), + [proj.ProjectName] + ); + }, + myself.ide.cloudError() + ); + this.destroy(); +}; + +ProjectDialogMorph.prototype.saveProject = function () { + var name = this.nameField.contents().text.text, + notes = this.notesText.text, + myself = this; + + this.ide.projectNotes = notes || this.ide.projectNotes; + if (name) { + if (this.source === 'cloud') { + if (detect( + this.projectList, + function (item) {return item.ProjectName === name; } + )) { + this.ide.confirm( + localize( + 'Are you sure you want to replace' + ) + '\n"' + name + '"?', + 'Replace Project', + function () { + myself.ide.setProjectName(name); + myself.saveCloudProject(); + } + ); + } else { + this.ide.setProjectName(name); + myself.saveCloudProject(); + } + } else { // 'local' + if (detect( + this.projectList, + function (item) {return item.name === name; } + )) { + this.ide.confirm( + localize( + 'Are you sure you want to replace' + ) + '\n"' + name + '"?', + 'Replace Project', + function () { + myself.ide.setProjectName(name); + myself.ide.source = 'local'; + myself.ide.saveProject(name); + myself.destroy(); + } + ); + } else { + this.ide.setProjectName(name); + myself.ide.source = 'local'; + this.ide.saveProject(name); + this.destroy(); + } + } + } +}; + +ProjectDialogMorph.prototype.saveCloudProject = function () { + var myself = this; + this.ide.showMessage('Saving project\nto the cloud...'); + SnapCloud.saveProject( + this.ide, + function () { + myself.ide.source = 'cloud'; + myself.ide.showMessage('saved.', 2); + }, + this.ide.cloudError() + ); + this.destroy(); +}; + +ProjectDialogMorph.prototype.deleteProject = function () { + var myself = this, + proj, + idx, + name; + + if (this.source === 'cloud') { + proj = this.listField.selected; + if (proj) { + this.ide.confirm( + localize( + 'Are you sure you want to delete' + ) + '\n"' + proj.ProjectName + '"?', + 'Delete Project', + function () { + SnapCloud.reconnect( + function () { + SnapCloud.callService( + 'deleteProject', + function () { + SnapCloud.disconnect(); + myself.ide.hasChangedMedia = true; + idx = myself.projectList.indexOf(proj); + myself.projectList.splice(idx, 1); + myself.installCloudProjectList( + myself.projectList + ); // refresh list + }, + myself.ide.cloudError(), + [proj.ProjectName] + ); + }, + myself.ide.cloudError() + ); + } + ); + } + } else { // 'local, examples' + if (this.listField.selected) { + name = this.listField.selected.name; + this.ide.confirm( + localize( + 'Are you sure you want to delete' + ) + '\n"' + name + '"?', + 'Delete Project', + function () { + delete localStorage['-snap-project-' + name]; + myself.setSource(myself.source); // refresh list + } + ); + } + } +}; + +ProjectDialogMorph.prototype.shareProject = function () { + var myself = this, + proj = this.listField.selected, + entry = this.listField.active; + + if (proj) { + this.ide.confirm( + localize( + 'Are you sure you want to publish' + ) + '\n"' + proj.ProjectName + '"?', + 'Share Project', + function () { + myself.ide.showMessage('sharing\nproject...'); + SnapCloud.reconnect( + function () { + SnapCloud.callService( + 'publishProject', + function () { + SnapCloud.disconnect(); + proj.Public = 'true'; + entry.label.isBold = true; + entry.label.drawNew(); + entry.label.changed(); + myself.ide.showMessage('shared.', 2); + }, + myself.ide.cloudError(), + [proj.ProjectName] + ); + }, + myself.ide.cloudError() + ); + } + ); + } +}; + +ProjectDialogMorph.prototype.unshareProject = function () { + var myself = this, + proj = this.listField.selected, + entry = this.listField.active; + + + if (proj) { + this.ide.confirm( + localize( + 'Are you sure you want to unpublish' + ) + '\n"' + proj.ProjectName + '"?', + 'Unshare Project', + function () { + myself.ide.showMessage('unsharing\nproject...'); + SnapCloud.reconnect( + function () { + SnapCloud.callService( + 'unpublishProject', + function () { + SnapCloud.disconnect(); + proj.Public = 'false'; + entry.label.isBold = false; + entry.label.drawNew(); + entry.label.changed(); + myself.ide.showMessage('unshared.', 2); + }, + myself.ide.cloudError(), + [proj.ProjectName] + ); + }, + myself.ide.cloudError() + ); + } + ); + } +}; + +ProjectDialogMorph.prototype.edit = function () { + if (this.nameField) { + this.nameField.edit(); + } +}; + +// ProjectDialogMorph layout + +ProjectDialogMorph.prototype.fixLayout = function () { + var th = fontHeight(this.titleFontSize) + this.titlePadding * 2, + thin = this.padding / 2, + oldFlag = Morph.prototype.trackChanges; + + Morph.prototype.trackChanges = false; + + if (this.buttons && (this.buttons.children.length > 0)) { + this.buttons.fixLayout(); + } + + if (this.body) { + this.body.setPosition(this.position().add(new Point( + this.padding, + th + this.padding + ))); + this.body.setExtent(new Point( + this.width() - this.padding * 2, + this.height() - this.padding * 3 - th - this.buttons.height() + )); + this.srcBar.setPosition(this.body.position()); + if (this.nameField) { + this.nameField.setWidth( + this.body.width() - this.srcBar.width() - this.padding * 6 + ); + this.nameField.setLeft(this.srcBar.right() + this.padding * 3); + this.nameField.setTop(this.srcBar.top()); + this.nameField.drawNew(); + } + + this.listField.setLeft(this.srcBar.right() + this.padding); + this.listField.setWidth( + this.body.width() + - this.srcBar.width() + - this.preview.width() + - this.padding + - thin + ); + this.listField.contents.children[0].adjustWidths(); + + if (this.nameField) { + this.listField.setTop(this.nameField.bottom() + this.padding); + this.listField.setHeight( + this.body.height() - this.nameField.height() - this.padding + ); + } else { + this.listField.setTop(this.body.top()); + this.listField.setHeight(this.body.height()); + } + + this.preview.setRight(this.body.right()); + if (this.nameField) { + this.preview.setTop(this.nameField.bottom() + this.padding); + } else { + this.preview.setTop(this.body.top()); + } + + this.notesField.setTop(this.preview.bottom() + thin); + this.notesField.setLeft(this.preview.left()); + this.notesField.setHeight( + this.body.bottom() - this.preview.bottom() - thin + ); + } + + if (this.label) { + this.label.setCenter(this.center()); + this.label.setTop(this.top() + (th - this.label.height()) / 2); + } + + if (this.buttons && (this.buttons.children.length > 0)) { + this.buttons.setCenter(this.center()); + this.buttons.setBottom(this.bottom() - this.padding); + } + + Morph.prototype.trackChanges = oldFlag; + this.changed(); +}; + +// SpriteIconMorph //////////////////////////////////////////////////// + +/* + I am a selectable element in the Sprite corral, keeping a self-updating + thumbnail of the sprite I'm respresenting, and a self-updating label + of the sprite's name (in case it is changed elsewhere) +*/ + +// SpriteIconMorph inherits from ToggleButtonMorph (Widgets) + +SpriteIconMorph.prototype = new ToggleButtonMorph(); +SpriteIconMorph.prototype.constructor = SpriteIconMorph; +SpriteIconMorph.uber = ToggleButtonMorph.prototype; + +// SpriteIconMorph settings + +SpriteIconMorph.prototype.thumbSize = new Point(40, 40); +SpriteIconMorph.prototype.labelShadowOffset = null; +SpriteIconMorph.prototype.labelShadowColor = null; +SpriteIconMorph.prototype.labelColor = new Color(255, 255, 255); +SpriteIconMorph.prototype.fontSize = 9; + +// SpriteIconMorph instance creation: + +function SpriteIconMorph(aSprite, aTemplate) { + this.init(aSprite, aTemplate); +} + +SpriteIconMorph.prototype.init = function (aSprite, aTemplate) { + var colors, action, query, myself = this; + + if (!aTemplate) { + colors = [ + IDE_Morph.prototype.groupColor, + IDE_Morph.prototype.frameColor, + IDE_Morph.prototype.frameColor + ]; + + } + + action = function () { + // make my sprite the current one + var ide = myself.parentThatIsA(IDE_Morph); + + if (ide) { + ide.selectSprite(myself.object); + } + }; + + query = function () { + // answer true if my sprite is the current one + var ide = myself.parentThatIsA(IDE_Morph); + + if (ide) { + return ide.currentSprite === myself.object; + } + return false; + }; + + // additional properties: + this.object = aSprite || new SpriteMorph(); // mandatory, actually + this.version = this.object.version; + this.thumbnail = null; + this.rotationButton = null; // synchronous rotation of nested sprites + + // initialize inherited properties: + SpriteIconMorph.uber.init.call( + this, + colors, // color overrides, : [normal, highlight, pressed] + null, // target - not needed here + action, // a toggle function + this.object.name, // label string + query, // predicate/selector + null, // environment + null, // hint + aTemplate // optional, for cached background images + ); + + // override defaults and build additional components + this.isDraggable = true; + this.createThumbnail(); + this.padding = 2; + this.corner = 8; + this.fixLayout(); + this.fps = 1; +}; + +SpriteIconMorph.prototype.createThumbnail = function () { + if (this.thumbnail) { + this.thumbnail.destroy(); + } + + this.thumbnail = new Morph(); + this.thumbnail.setExtent(this.thumbSize); + if (this.object instanceof SpriteMorph) { // support nested sprites + this.thumbnail.image = this.object.fullThumbnail(this.thumbSize); + this.createRotationButton(); + } else { + this.thumbnail.image = this.object.thumbnail(this.thumbSize); + } + this.add(this.thumbnail); +}; + +SpriteIconMorph.prototype.createLabel = function () { + var txt; + + if (this.label) { + this.label.destroy(); + } + txt = new StringMorph( + this.object.name, + this.fontSize, + this.fontStyle, + true, + false, + false, + this.labelShadowOffset, + this.labelShadowColor, + this.labelColor + ); + + this.label = new FrameMorph(); + this.label.acceptsDrops = false; + this.label.alpha = 0; + this.label.setExtent(txt.extent()); + txt.setPosition(this.label.position()); + this.label.add(txt); + this.add(this.label); +}; + +SpriteIconMorph.prototype.createRotationButton = function () { + var button, myself = this; + + if (this.rotationButton) { + this.rotationButton.destroy(); + this.roationButton = null; + } + if (!this.object.anchor) { + return; + } + + button = new ToggleButtonMorph( + null, // colors, + null, // target + function () { + myself.object.rotatesWithAnchor = + !myself.object.rotatesWithAnchor; + }, + [ + '\u2192', + '\u21BB' + ], + function () { // query + return myself.object.rotatesWithAnchor; + } + ); + + button.corner = 8; + button.labelMinExtent = new Point(11, 11); + button.padding = 0; + button.pressColor = button.color; + button.drawNew(); + // button.hint = 'rotate synchronously\nwith anchor'; + button.fixLayout(); + button.refresh(); + button.changed(); + this.rotationButton = button; + this.add(this.rotationButton); +}; + +// SpriteIconMorph stepping + +SpriteIconMorph.prototype.step = function () { + if (this.version !== this.object.version) { + this.createThumbnail(); + this.createLabel(); + this.fixLayout(); + this.version = this.object.version; + this.refresh(); + } +}; + +// SpriteIconMorph layout + +SpriteIconMorph.prototype.fixLayout = function () { + if (!this.thumbnail) {return null; } + + this.setWidth( + this.thumbnail.width() + + this.outline * 2 + + this.edge * 2 + + this.padding * 2 + ); + + this.setHeight( + this.thumbnail.height() + + this.outline * 2 + + this.edge * 2 + + this.padding * 3 + + this.label.height() + ); + + this.thumbnail.setCenter(this.center()); + this.thumbnail.setTop( + this.top() + this.outline + this.edge + this.padding + ); + + if (this.rotationButton) { + this.rotationButton.setTop(this.top()); + this.rotationButton.setRight(this.right()); + } + + this.label.setWidth( + Math.min( + this.label.children[0].width(), // the actual text + this.thumbnail.width() + ) + ); + this.label.setCenter(this.center()); + this.label.setTop( + this.thumbnail.bottom() + this.padding + ); +}; + +// SpriteIconMorph menu + +SpriteIconMorph.prototype.userMenu = function () { + var menu = new MenuMorph(this), + myself = this; + if (this.object instanceof StageMorph) { + menu.addItem( + 'pic...', + function () { + window.open(myself.object.fullImageClassic().toDataURL()); + }, + 'open a new window\nwith a picture of the stage' + ); + return menu; + } + if (!(this.object instanceof SpriteMorph)) {return null; } + menu.addItem("show", 'showSpriteOnStage'); + menu.addLine(); + menu.addItem("duplicate", 'duplicateSprite'); + menu.addItem("delete", 'removeSprite'); + menu.addLine(); + if (this.object.anchor) { + menu.addItem( + localize('detach from') + ' ' + this.object.anchor.name, + function () {myself.object.detachFromAnchor(); } + ); + } + if (this.object.parts.length) { + menu.addItem( + 'detach all parts', + function () {myself.object.detachAllParts(); } + ); + } + menu.addItem("export...", 'exportSprite'); + return menu; +}; + +SpriteIconMorph.prototype.duplicateSprite = function () { + var ide = this.parentThatIsA(IDE_Morph); + if (ide) { + ide.duplicateSprite(this.object); + } +}; + +SpriteIconMorph.prototype.removeSprite = function () { + var ide = this.parentThatIsA(IDE_Morph); + if (ide) { + ide.removeSprite(this.object); + } +}; + +SpriteIconMorph.prototype.exportSprite = function () { + this.object.exportSprite(); +}; + +SpriteIconMorph.prototype.showSpriteOnStage = function () { + this.object.showOnStage(); +}; + +// SpriteIconMorph drawing + +SpriteIconMorph.prototype.createBackgrounds = function () { +// only draw the edges if I am selected + var context, + ext = this.extent(); + + if (this.template) { // take the backgrounds images from the template + this.image = this.template.image; + this.normalImage = this.template.normalImage; + this.highlightImage = this.template.highlightImage; + this.pressImage = this.template.pressImage; + return null; + } + + this.normalImage = newCanvas(ext); + context = this.normalImage.getContext('2d'); + this.drawBackground(context, this.color); + + this.highlightImage = newCanvas(ext); + context = this.highlightImage.getContext('2d'); + this.drawBackground(context, this.highlightColor); + + this.pressImage = newCanvas(ext); + context = this.pressImage.getContext('2d'); + this.drawOutline(context); + this.drawBackground(context, this.pressColor); + this.drawEdges( + context, + this.pressColor, + this.pressColor.lighter(this.contrast), + this.pressColor.darker(this.contrast) + ); + + this.image = this.normalImage; +}; + +// SpriteIconMorph drag & drop + +SpriteIconMorph.prototype.prepareToBeGrabbed = function () { + var ide = this.parentThatIsA(IDE_Morph), + idx; + this.mouseClickLeft(); // select me + if (ide) { + idx = ide.sprites.asArray().indexOf(this.object); + ide.sprites.remove(idx + 1); + ide.createCorral(); + ide.fixLayout(); + } +}; + +SpriteIconMorph.prototype.wantsDropOf = function (morph) { + // allow scripts & media to be copied from one sprite to another + // by drag & drop + return morph instanceof BlockMorph + || (morph instanceof CostumeIconMorph) + || (morph instanceof SoundIconMorph); +}; + +SpriteIconMorph.prototype.reactToDropOf = function (morph, hand) { + if (morph instanceof BlockMorph) { + this.copyStack(morph); + } else if (morph instanceof CostumeIconMorph) { + this.copyCostume(morph.object); + } else if (morph instanceof SoundIconMorph) { + this.copySound(morph.object); + } + this.world().add(morph); + morph.slideBackTo(hand.grabOrigin); +}; + +SpriteIconMorph.prototype.copyStack = function (block) { + var dup = block.fullCopy(), + y = Math.max(this.object.scripts.children.map(function (stack) { + return stack.fullBounds().bottom(); + }).concat([this.object.scripts.top()])); + + dup.setPosition(new Point(this.object.scripts.left() + 20, y + 20)); + this.object.scripts.add(dup); + dup.allComments().forEach(function (comment) { + comment.align(dup); + }); + this.object.scripts.adjustBounds(); + + // delete all custom blocks pointing to local definitions + // under construction... + dup.allChildren().forEach(function (morph) { + if (morph.definition && !morph.definition.isGlobal) { + morph.deleteBlock(); + } + }); +}; + +SpriteIconMorph.prototype.copyCostume = function (costume) { + var dup = costume.copy(); + this.object.addCostume(dup); + this.object.wearCostume(dup); +}; + +SpriteIconMorph.prototype.copySound = function (sound) { + var dup = sound.copy(); + this.object.addSound(dup.audio, dup.name); +}; + +// CostumeIconMorph //////////////////////////////////////////////////// + +/* + I am a selectable element in the SpriteEditor's "Costumes" tab, keeping + a self-updating thumbnail of the costume I'm respresenting, and a + self-updating label of the costume's name (in case it is changed + elsewhere) +*/ + +// CostumeIconMorph inherits from ToggleButtonMorph (Widgets) +// ... and copies methods from SpriteIconMorph + +CostumeIconMorph.prototype = new ToggleButtonMorph(); +CostumeIconMorph.prototype.constructor = CostumeIconMorph; +CostumeIconMorph.uber = ToggleButtonMorph.prototype; + +// CostumeIconMorph settings + +CostumeIconMorph.prototype.thumbSize = new Point(80, 60); +CostumeIconMorph.prototype.labelShadowOffset = null; +CostumeIconMorph.prototype.labelShadowColor = null; +CostumeIconMorph.prototype.labelColor = new Color(255, 255, 255); +CostumeIconMorph.prototype.fontSize = 9; + +// CostumeIconMorph instance creation: + +function CostumeIconMorph(aCostume, aTemplate) { + this.init(aCostume, aTemplate); +} + +CostumeIconMorph.prototype.init = function (aCostume, aTemplate) { + var colors, action, query, myself = this; + + if (!aTemplate) { + colors = [ + IDE_Morph.prototype.groupColor, + IDE_Morph.prototype.frameColor, + IDE_Morph.prototype.frameColor + ]; + + } + + action = function () { + // make my costume the current one + var ide = myself.parentThatIsA(IDE_Morph), + wardrobe = myself.parentThatIsA(WardrobeMorph); + + if (ide) { + ide.currentSprite.wearCostume(myself.object); + } + if (wardrobe) { + wardrobe.updateSelection(); + } + }; + + query = function () { + // answer true if my costume is the current one + var ide = myself.parentThatIsA(IDE_Morph); + + if (ide) { + return ide.currentSprite.costume === myself.object; + } + return false; + }; + + // additional properties: + this.object = aCostume || new Costume(); // mandatory, actually + this.version = this.object.version; + this.thumbnail = null; + + // initialize inherited properties: + CostumeIconMorph.uber.init.call( + this, + colors, // color overrides, : [normal, highlight, pressed] + null, // target - not needed here + action, // a toggle function + this.object.name, // label string + query, // predicate/selector + null, // environment + null, // hint + aTemplate // optional, for cached background images + ); + + // override defaults and build additional components + this.isDraggable = true; + this.createThumbnail(); + this.padding = 2; + this.corner = 8; + this.fixLayout(); + this.fps = 1; +}; + +CostumeIconMorph.prototype.createThumbnail + = SpriteIconMorph.prototype.createThumbnail; + +CostumeIconMorph.prototype.createLabel + = SpriteIconMorph.prototype.createLabel; + +// CostumeIconMorph stepping + +CostumeIconMorph.prototype.step + = SpriteIconMorph.prototype.step; + +// CostumeIconMorph layout + +CostumeIconMorph.prototype.fixLayout + = SpriteIconMorph.prototype.fixLayout; + +// CostumeIconMorph menu + +CostumeIconMorph.prototype.userMenu = function () { + var menu = new MenuMorph(this); + if (!(this.object instanceof Costume)) {return null; } + menu.addItem("edit", "editCostume"); + if (this.world().currentKey === 16) { // shift clicked + menu.addItem( + 'edit rotation point only...', + 'editRotationPointOnly', + null, + new Color(100, 0, 0) + ); + } + menu.addItem("rename", "renameCostume"); + menu.addLine(); + menu.addItem("duplicate", "duplicateCostume"); + menu.addItem("delete", "removeCostume"); + menu.addLine(); + menu.addItem("export", "exportCostume"); + return menu; +}; + +CostumeIconMorph.prototype.editCostume = function () { + if (this.object instanceof SVG_Costume) { + this.object.editRotationPointOnly(this.world()); + } else { + this.object.edit( + this.world(), + this.parentThatIsA(IDE_Morph) + ); + } +}; + +CostumeIconMorph.prototype.editRotationPointOnly = function () { + var ide = this.parentThatIsA(IDE_Morph); + this.object.editRotationPointOnly(this.world()); + ide.hasChangedMedia = true; +}; + +CostumeIconMorph.prototype.renameCostume = function () { + var costume = this.object, + ide = this.parentThatIsA(IDE_Morph); + new DialogBoxMorph( + null, + function (answer) { + if (answer && (answer !== costume.name)) { + costume.name = answer; + costume.version = Date.now(); + ide.hasChangedMedia = true; + } + } + ).prompt( + 'rename costume', + costume.name, + this.world() + ); +}; + +CostumeIconMorph.prototype.duplicateCostume = function () { + var wardrobe = this.parentThatIsA(WardrobeMorph), + ide = this.parentThatIsA(IDE_Morph), + newcos = this.object.copy(), + split = newcos.name.split(" "); + if (split[split.length - 1] === "copy") { + newcos.name += " 2"; + } else if (isNaN(split[split.length - 1])) { + newcos.name = newcos.name + " copy"; + } else { + split[split.length - 1] = Number(split[split.length - 1]) + 1; + newcos.name = split.join(" "); + } + wardrobe.sprite.addCostume(newcos); + wardrobe.updateList(); + if (ide) { + ide.currentSprite.wearCostume(newcos); + } +}; + +CostumeIconMorph.prototype.removeCostume = function () { + var wardrobe = this.parentThatIsA(WardrobeMorph), + idx = this.parent.children.indexOf(this), + ide = this.parentThatIsA(IDE_Morph); + wardrobe.removeCostumeAt(idx - 2); + if (ide.currentSprite.costume === this.object) { + ide.currentSprite.wearCostume(null); + } +}; + +CostumeIconMorph.prototype.exportCostume = function () { + if (this.object instanceof SVG_Costume) { + window.open(this.object.contents.src); + } else { // rastered Costume + window.open(this.object.contents.toDataURL()); + } +}; + +// CostumeIconMorph drawing + +CostumeIconMorph.prototype.createBackgrounds + = SpriteIconMorph.prototype.createBackgrounds; + +// CostumeIconMorph drag & drop + +CostumeIconMorph.prototype.prepareToBeGrabbed = function () { + this.mouseClickLeft(); // select me + this.removeCostume(); +}; + +// TurtleIconMorph //////////////////////////////////////////////////// + +/* + I am a selectable element in the SpriteEditor's "Costumes" tab, keeping + a thumbnail of the sprite's or stage's default "Turtle" costume. +*/ + +// TurtleIconMorph inherits from ToggleButtonMorph (Widgets) +// ... and copies methods from SpriteIconMorph + +TurtleIconMorph.prototype = new ToggleButtonMorph(); +TurtleIconMorph.prototype.constructor = TurtleIconMorph; +TurtleIconMorph.uber = ToggleButtonMorph.prototype; + +// TurtleIconMorph settings + +TurtleIconMorph.prototype.thumbSize = new Point(80, 60); +TurtleIconMorph.prototype.labelShadowOffset = null; +TurtleIconMorph.prototype.labelShadowColor = null; +TurtleIconMorph.prototype.labelColor = new Color(255, 255, 255); +TurtleIconMorph.prototype.fontSize = 9; + +// TurtleIconMorph instance creation: + +function TurtleIconMorph(aSpriteOrStage, aTemplate) { + this.init(aSpriteOrStage, aTemplate); +} + +TurtleIconMorph.prototype.init = function (aSpriteOrStage, aTemplate) { + var colors, action, query, myself = this; + + if (!aTemplate) { + colors = [ + IDE_Morph.prototype.groupColor, + IDE_Morph.prototype.frameColor, + IDE_Morph.prototype.frameColor + ]; + + } + + action = function () { + // make my costume the current one + var ide = myself.parentThatIsA(IDE_Morph), + wardrobe = myself.parentThatIsA(WardrobeMorph); + + if (ide) { + ide.currentSprite.wearCostume(null); + } + if (wardrobe) { + wardrobe.updateSelection(); + } + }; + + query = function () { + // answer true if my costume is the current one + var ide = myself.parentThatIsA(IDE_Morph); + + if (ide) { + return ide.currentSprite.costume === null; + } + return false; + }; + + // additional properties: + this.object = aSpriteOrStage; // mandatory, actually + this.version = this.object.version; + this.thumbnail = null; + + // initialize inherited properties: + TurtleIconMorph.uber.init.call( + this, + colors, // color overrides, : [normal, highlight, pressed] + null, // target - not needed here + action, // a toggle function + 'default', // label string + query, // predicate/selector + null, // environment + null, // hint + aTemplate // optional, for cached background images + ); + + // override defaults and build additional components + this.isDraggable = false; + this.createThumbnail(); + this.padding = 2; + this.corner = 8; + this.fixLayout(); +}; + +TurtleIconMorph.prototype.createThumbnail = function () { + var isFlat = MorphicPreferences.isFlat; + + if (this.thumbnail) { + this.thumbnail.destroy(); + } + if (this.object instanceof SpriteMorph) { + this.thumbnail = new SymbolMorph( + 'turtle', + this.thumbSize.y, + this.labelColor, + isFlat ? null : new Point(-1, -1), + new Color(0, 0, 0) + ); + } else { + this.thumbnail = new SymbolMorph( + 'stage', + this.thumbSize.y, + this.labelColor, + isFlat ? null : new Point(-1, -1), + new Color(0, 0, 0) + ); + } + this.add(this.thumbnail); +}; + +TurtleIconMorph.prototype.createLabel = function () { + var txt; + + if (this.label) { + this.label.destroy(); + } + txt = new StringMorph( + localize( + this.object instanceof SpriteMorph ? 'Turtle' : 'Empty' + ), + this.fontSize, + this.fontStyle, + true, + false, + false, + this.labelShadowOffset, + this.labelShadowColor, + this.labelColor + ); + + this.label = new FrameMorph(); + this.label.acceptsDrops = false; + this.label.alpha = 0; + this.label.setExtent(txt.extent()); + txt.setPosition(this.label.position()); + this.label.add(txt); + this.add(this.label); +}; + +// TurtleIconMorph layout + +TurtleIconMorph.prototype.fixLayout + = SpriteIconMorph.prototype.fixLayout; + +// TurtleIconMorph drawing + +TurtleIconMorph.prototype.createBackgrounds + = SpriteIconMorph.prototype.createBackgrounds; + +// TurtleIconMorph user menu + +TurtleIconMorph.prototype.userMenu = function () { + var myself = this, + menu = new MenuMorph(this, 'pen'), + on = '\u25CF', + off = '\u25CB'; + if (this.object instanceof StageMorph) { + return null; + } + menu.addItem( + (this.object.penPoint === 'tip' ? on : off) + ' ' + localize('tip'), + function () { + myself.object.penPoint = 'tip'; + myself.object.changed(); + myself.object.drawNew(); + myself.object.changed(); + } + ); + menu.addItem( + (this.object.penPoint === 'middle' ? on : off) + ' ' + localize( + 'middle' + ), + function () { + myself.object.penPoint = 'middle'; + myself.object.changed(); + myself.object.drawNew(); + myself.object.changed(); + } + ); + return menu; +}; + +// WardrobeMorph /////////////////////////////////////////////////////// + +// I am a watcher on a sprite's costume list + +// WardrobeMorph inherits from ScrollFrameMorph + +WardrobeMorph.prototype = new ScrollFrameMorph(); +WardrobeMorph.prototype.constructor = WardrobeMorph; +WardrobeMorph.uber = ScrollFrameMorph.prototype; + +// WardrobeMorph settings + +// ... to follow ... + +// WardrobeMorph instance creation: + +function WardrobeMorph(aSprite, sliderColor) { + this.init(aSprite, sliderColor); +} + +WardrobeMorph.prototype.init = function (aSprite, sliderColor) { + // additional properties + this.sprite = aSprite || new SpriteMorph(); + this.costumesVersion = null; + this.spriteVersion = null; + + // initialize inherited properties + WardrobeMorph.uber.init.call(this, null, null, sliderColor); + + // configure inherited properties + this.fps = 2; + this.updateList(); +}; + +// Wardrobe updating + +WardrobeMorph.prototype.updateList = function () { + var myself = this, + x = this.left() + 5, + y = this.top() + 5, + padding = 4, + oldFlag = Morph.prototype.trackChanges, + oldPos = this.contents.position(), + icon, + template, + txt, + paintbutton; + + this.changed(); + oldFlag = Morph.prototype.trackChanges; + Morph.prototype.trackChanges = false; + + this.contents.destroy(); + this.contents = new FrameMorph(this); + this.contents.acceptsDrops = false; + this.contents.reactToDropOf = function (icon) { + myself.reactToDropOf(icon); + }; + this.addBack(this.contents); + + icon = new TurtleIconMorph(this.sprite); + icon.setPosition(new Point(x, y)); + myself.addContents(icon); + y = icon.bottom() + padding; + + paintbutton = new PushButtonMorph( + this, + "paintNew", + new SymbolMorph("brush", 15) + ); + paintbutton.padding = 0; + paintbutton.corner = 12; + paintbutton.color = IDE_Morph.prototype.groupColor; + paintbutton.highlightColor = IDE_Morph.prototype.frameColor.darker(50); + paintbutton.pressColor = paintbutton.highlightColor; + paintbutton.labelMinExtent = new Point(36, 18); + paintbutton.labelShadowOffset = new Point(-1, -1); + paintbutton.labelShadowColor = paintbutton.highlightColor; + paintbutton.labelColor = TurtleIconMorph.prototype.labelColor; + paintbutton.contrast = this.buttonContrast; + paintbutton.drawNew(); + paintbutton.hint = "Paint a new costume"; + paintbutton.setPosition(new Point(x, y)); + paintbutton.fixLayout(); + paintbutton.setCenter(icon.center()); + paintbutton.setLeft(icon.right() + padding * 4); + + + this.addContents(paintbutton); + + txt = new TextMorph(localize( + "costumes tab help" // look up long string in translator + )); + txt.fontSize = 9; + txt.setColor(SpriteMorph.prototype.paletteTextColor); + + txt.setPosition(new Point(x, y)); + this.addContents(txt); + y = txt.bottom() + padding; + + + this.sprite.costumes.asArray().forEach(function (costume) { + template = icon = new CostumeIconMorph(costume, template); + icon.setPosition(new Point(x, y)); + myself.addContents(icon); + y = icon.bottom() + padding; + }); + this.costumesVersion = this.sprite.costumes.lastChanged; + + this.contents.setPosition(oldPos); + this.adjustScrollBars(); + Morph.prototype.trackChanges = oldFlag; + this.changed(); + + this.updateSelection(); +}; + +WardrobeMorph.prototype.updateSelection = function () { + this.contents.children.forEach(function (morph) { + if (morph.refresh) {morph.refresh(); } + }); + this.spriteVersion = this.sprite.version; +}; + +// Wardrobe stepping + +WardrobeMorph.prototype.step = function () { + if (this.costumesVersion !== this.sprite.costumes.lastChanged) { + this.updateList(); + } + if (this.spriteVersion !== this.sprite.version) { + this.updateSelection(); + } +}; + +// Wardrobe ops + +WardrobeMorph.prototype.removeCostumeAt = function (idx) { + this.sprite.costumes.remove(idx); + this.updateList(); +}; + +WardrobeMorph.prototype.paintNew = function () { + var cos = new Costume(newCanvas(), "Untitled"), + ide = this.parentThatIsA(IDE_Morph), + myself = this; + cos.edit(this.world(), ide, true, null, function () { + myself.sprite.addCostume(cos); + myself.updateList(); + if (ide) { + ide.currentSprite.wearCostume(cos); + } + }); +}; + +// Wardrobe drag & drop + +WardrobeMorph.prototype.wantsDropOf = function (morph) { + return morph instanceof CostumeIconMorph; +}; + +WardrobeMorph.prototype.reactToDropOf = function (icon) { + var idx = 0, + costume = icon.object, + top = icon.top(); + + icon.destroy(); + this.contents.children.forEach(function (item) { + if (item instanceof CostumeIconMorph && item.top() < top - 4) { + idx += 1; + } + }); + this.sprite.costumes.add(costume, idx + 1); + this.updateList(); + icon.mouseClickLeft(); // select +}; + +// SoundIconMorph /////////////////////////////////////////////////////// + +/* + I am an element in the SpriteEditor's "Sounds" tab. +*/ + +// SoundIconMorph inherits from ToggleButtonMorph (Widgets) +// ... and copies methods from SpriteIconMorph + +SoundIconMorph.prototype = new ToggleButtonMorph(); +SoundIconMorph.prototype.constructor = SoundIconMorph; +SoundIconMorph.uber = ToggleButtonMorph.prototype; + +// SoundIconMorph settings + +SoundIconMorph.prototype.thumbSize = new Point(80, 60); +SoundIconMorph.prototype.labelShadowOffset = null; +SoundIconMorph.prototype.labelShadowColor = null; +SoundIconMorph.prototype.labelColor = new Color(255, 255, 255); +SoundIconMorph.prototype.fontSize = 9; + +// SoundIconMorph instance creation: + +function SoundIconMorph(aSound, aTemplate) { + this.init(aSound, aTemplate); +} + +SoundIconMorph.prototype.init = function (aSound, aTemplate) { + var colors, action, query; + + if (!aTemplate) { + colors = [ + IDE_Morph.prototype.groupColor, + IDE_Morph.prototype.frameColor, + IDE_Morph.prototype.frameColor + ]; + + } + + action = function () { + nop(); // When I am selected (which is never the case for sounds) + }; + + query = function () { + return false; + }; + + // additional properties: + this.object = aSound; // mandatory, actually + this.version = this.object.version; + this.thumbnail = null; + + // initialize inherited properties: + SoundIconMorph.uber.init.call( + this, + colors, // color overrides, : [normal, highlight, pressed] + null, // target - not needed here + action, // a toggle function + this.object.name, // label string + query, // predicate/selector + null, // environment + null, // hint + aTemplate // optional, for cached background images + ); + + // override defaults and build additional components + this.isDraggable = true; + this.createThumbnail(); + this.padding = 2; + this.corner = 8; + this.fixLayout(); + this.fps = 1; +}; + +SoundIconMorph.prototype.createThumbnail = function () { + var label; + if (this.thumbnail) { + this.thumbnail.destroy(); + } + this.thumbnail = new Morph(); + this.thumbnail.setExtent(this.thumbSize); + this.add(this.thumbnail); + label = new StringMorph( + this.createInfo(), + '16', + '', + true, + false, + false, + this.labelShadowOffset, + this.labelShadowColor, + new Color(200, 200, 200) + ); + this.thumbnail.add(label); + label.setCenter(new Point(40, 15)); + + this.button = new PushButtonMorph( + this, + 'toggleAudioPlaying', + (this.object.previewAudio ? 'Stop' : 'Play') + ); + this.button.drawNew(); + this.button.hint = 'Play sound'; + this.button.fixLayout(); + this.thumbnail.add(this.button); + this.button.setCenter(new Point(40, 40)); +}; + +SoundIconMorph.prototype.createInfo = function () { + var dur = Math.round(this.object.audio.duration || 0), + mod = dur % 60; + return Math.floor(dur / 60).toString() + + ":" + + (mod < 10 ? "0" : "") + + mod.toString(); +}; + +SoundIconMorph.prototype.toggleAudioPlaying = function () { + var myself = this; + if (!this.object.previewAudio) { + //Audio is not playing + this.button.labelString = 'Stop'; + this.button.hint = 'Stop sound'; + this.object.previewAudio = this.object.play(); + this.object.previewAudio.addEventListener('ended', function () { + myself.audioHasEnded(); + }, false); + } else { + //Audio is currently playing + this.button.labelString = 'Play'; + this.button.hint = 'Play sound'; + this.object.previewAudio.pause(); + this.object.previewAudio.terminated = true; + this.object.previewAudio = null; + } + this.button.createLabel(); +}; + +SoundIconMorph.prototype.audioHasEnded = function () { + this.button.trigger(); + this.button.mouseLeave(); +}; + +SoundIconMorph.prototype.createLabel + = SpriteIconMorph.prototype.createLabel; + +// SoundIconMorph stepping + +/* +SoundIconMorph.prototype.step + = SpriteIconMorph.prototype.step; +*/ + +// SoundIconMorph layout + +SoundIconMorph.prototype.fixLayout + = SpriteIconMorph.prototype.fixLayout; + +// SoundIconMorph menu + +SoundIconMorph.prototype.userMenu = function () { + var menu = new MenuMorph(this); + if (!(this.object instanceof Sound)) { return null; } + menu.addItem('rename', 'renameSound'); + menu.addItem('delete', 'removeSound'); + return menu; +}; + + +SoundIconMorph.prototype.renameSound = function () { + var sound = this.object, + ide = this.parentThatIsA(IDE_Morph), + myself = this; + (new DialogBoxMorph( + null, + function (answer) { + if (answer && (answer !== sound.name)) { + sound.name = answer; + sound.version = Date.now(); + myself.createLabel(); // can be omitted once I'm stepping + myself.fixLayout(); // can be omitted once I'm stepping + ide.hasChangedMedia = true; + } + } + )).prompt( + 'rename sound', + sound.name, + this.world() + ); +}; + +SoundIconMorph.prototype.removeSound = function () { + var jukebox = this.parentThatIsA(JukeboxMorph), + idx = this.parent.children.indexOf(this); + jukebox.removeSound(idx); +}; + +SoundIconMorph.prototype.createBackgrounds + = SpriteIconMorph.prototype.createBackgrounds; + +SoundIconMorph.prototype.createLabel + = SpriteIconMorph.prototype.createLabel; + +// SoundIconMorph drag & drop + +SoundIconMorph.prototype.prepareToBeGrabbed = function () { + this.removeSound(); +}; + +// JukeboxMorph ///////////////////////////////////////////////////// + +/* + I am JukeboxMorph, like WardrobeMorph, but for sounds +*/ + +// JukeboxMorph instance creation + +JukeboxMorph.prototype = new ScrollFrameMorph(); +JukeboxMorph.prototype.constructor = JukeboxMorph; +JukeboxMorph.uber = ScrollFrameMorph.prototype; + +function JukeboxMorph(aSprite, sliderColor) { + this.init(aSprite, sliderColor); +} + +JukeboxMorph.prototype.init = function (aSprite, sliderColor) { + // additional properties + this.sprite = aSprite || new SpriteMorph(); + this.costumesVersion = null; + this.spriteVersion = null; + + // initialize inherited properties + JukeboxMorph.uber.init.call(this, null, null, sliderColor); + + // configure inherited properties + this.acceptsDrops = false; + this.fps = 2; + this.updateList(); +}; + +// Jukebox updating + +JukeboxMorph.prototype.updateList = function () { + var myself = this, + x = this.left() + 5, + y = this.top() + 5, + padding = 4, + oldFlag = Morph.prototype.trackChanges, + icon, + template, + txt; + + this.changed(); + oldFlag = Morph.prototype.trackChanges; + Morph.prototype.trackChanges = false; + + this.contents.destroy(); + this.contents = new FrameMorph(this); + this.contents.acceptsDrops = false; + this.contents.reactToDropOf = function (icon) { + myself.reactToDropOf(icon); + }; + this.addBack(this.contents); + + txt = new TextMorph(localize( + 'import a sound from your computer\nby dragging it into here' + )); + txt.fontSize = 9; + txt.setColor(SpriteMorph.prototype.paletteTextColor); + txt.setPosition(new Point(x, y)); + this.addContents(txt); + y = txt.bottom() + padding; + + this.sprite.sounds.asArray().forEach(function (sound) { + template = icon = new SoundIconMorph(sound, template); + icon.setPosition(new Point(x, y)); + myself.addContents(icon); + y = icon.bottom() + padding; + }); + + Morph.prototype.trackChanges = oldFlag; + this.changed(); + + this.updateSelection(); +}; + +JukeboxMorph.prototype.updateSelection = function () { + this.contents.children.forEach(function (morph) { + if (morph.refresh) {morph.refresh(); } + }); + this.spriteVersion = this.sprite.version; +}; + +// Jukebox stepping + +/* +JukeboxMorph.prototype.step = function () { + if (this.spriteVersion !== this.sprite.version) { + this.updateSelection(); + } +}; +*/ + +// Jukebox ops + +JukeboxMorph.prototype.removeSound = function (idx) { + this.sprite.sounds.remove(idx); + this.updateList(); +}; + +// Jukebox drag & drop + +JukeboxMorph.prototype.wantsDropOf = function (morph) { + return morph instanceof SoundIconMorph; +}; + +JukeboxMorph.prototype.reactToDropOf = function (icon) { + var idx = 0, + costume = icon.object, + top = icon.top(); + + icon.destroy(); + this.contents.children.forEach(function (item) { + if (item.top() < top - 4) { + idx += 1; + } + }); + this.sprite.sounds.add(costume, idx); + this.updateList(); +}; diff --git a/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/history.txt b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/history.txt new file mode 100644 index 0000000..81eda4a --- /dev/null +++ b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/history.txt @@ -0,0 +1,1891 @@ +BYOB4 (Snap) history +--------------------- +110511 +------ +* Scrolling by dragging +* Scrolling by dragging velocity + +110516 +------ +* Autoscrolling + +110518 +------ +* Textures + +110524 +------ +* CommandSlotMorphs (%c) + +110527 +------ +* Templates +* Padding for ScrollFrames + +110530 +------ +* C-slots only attach to blocks' tops (no longer also to bottoms) + +110531 +------ +* ReporterBlockMorphs + +110628 +------ +* World menu in every Morph's developersMenu +* changed the standard to "sharp shadows" because of Firefox5 bug + +110630 +------ +* StringMorphs and TextMorph notify their parents of layout changes +* TypeInSlotMorphs (round - %n - and rectangular - %s -) + +110705 +------ +* block specs + +110706 +------ +* BooleanSlotMorphs (%b) +* Color mixing +* contrast setting for SyntaxElementMorphs +* exit confirmation + +110707 +------ +* BlockMorph color changing +* entry field tabbing (Firefox and Opera only) +* label multi-line wrapping for command blocks + +110708 +------ +* extrapolate blockSpec upon label part drop + +110711 +------ +* optional intra-block-label word wrap (flag) layout setting + +110712 +------ +* rectangular reporter layout +* label mutli-line wrapping for reporters +* user-definable label line breaks (%br) +* font size customizable for individual menus +* ArrowMorphs + +110714 +------ +* optional drop-down menu for type-in slots +* read-only menus for type-in slots (%inst, %var, %lst, %obj, %eff, + %dir, %cst, %snd, %key, %idx, %msg, %att, %fun, %typ) +* global pixel color sensing +* renamed TypeInSlotMorph to InputSlotMorph + +110718 +------ +* ColorSlotMorphs (%clr) +* collision detection groundwork + +110719 +------ +* high-level documentation and code comments +* optional blurred slot shades (off by default) + +110720 +------ +* HatBlocks + +110721 +------ +* scrollBarSize can now optionally be specified individually +* block highlighting +* specs for any-unevaluated and Boolean-unevaluated inputs + +110722 +------ +* stringField settable as numeric, supresses textual input +* editable numeric input slots supress textual type-in +* evaluation helper methods and properties +* collision detection + +110726 +------ +* MultiArgMorphs (%mult%x) + +110727 +------ +* Morphic: fullBounds() now ignores hidden submorphs +* MultiArgMorphs: Optional label and minimum inputs settings, '%inputs' +* Morphic: simplified BoxMorph rendering +* Same-colored (white), semi-transparent reporter drop feedbacks + +110804 +------ +* evaluator: ThreadManager, Process, StackFrame, VariableFrame + +110810 +------ +* nasciturus: objects, gui + +110811 +------ +* Morphic: broken rect fix for float-positioned Morphs +* Blocks: straight bottom edges for stop-blocks +* PenMorph: round line ends + +110816 +------ +* Morphic: SpeechBubbleMorphs and bubble help for menus/buttons + +110817 +------ +* Threads: evaluating reporters +* showValue bubbles + +110818 +------ +* optimizations for menu bubble help and Blocks layout + +110823 +------ +* Sprite-scoped variables + +110824 +------ +* numerical virtual keyboard (pop-up-sliders - taken out again) +* sliders now work with negative floor numbers +* mouse wheel scroll events (thanks, Nathan!) + +110826 +------ +* TemplateSlotMorphs (%t, %mult%t, %scriptVars) +* script variables +* lockable inputs + +110911 +------ +* Morphic: PenMorph.setHeading() fixed + +110912 +------ +* Threads: renamed StackFrame to Context +* Blocks: persistent input default values + +110913 +------ +* basic Lambda primitives +* basic Lambda visualization (showBubble) + +110914 +------ +* c-slots in primitives are now static by default +* basic THE BLOCK, CALL and REPORT + +110919 +------ +* formal parameters +* recursion +* closures + +110922 +------ +* implicit parameters + +110923 +------ +* error catching for block evaluation + +110926 +------ +* basic message broadcasting +* thread forking (LAUNCH block) + +110927 +------ +* WAIT block + +110928 +------ +* GLIDE block + +111006 +------ +* force yield after timeout + +111007 +------ +* swooshy hat block tops (instead of circle segments) + +111009 +------ +* call/cc + +111010 +------ +* hybrid scope + +111012 +------ +* autolambdafying CSlotMorphs (C-shaped) and CommandSlotMorphs (inline) +* Morphic: right mouse click emulation for Mac + +111017 +------ +* another take on continuations + +111019 +------ +* Morphic: scrolling speedup + +111020 +------ +* unevaluated FunctionSlotMorphs (%f) +* autolambdafying ReporterSlotMorphs (%r, %p) + +111021 +------ +* GUI: CellMorphs (for stage watchers) + +111025 +------ +* GUI: WatcherMorphs +* SHOW VARIABLE, HIDE VARIABLE blocks + +111026 +------ +* Blocks: empty choice for input drop down menus +* automatic positioning of new watchers +* watchers on temporary variables are deleted by HIDE VARIABLE block (not hidden) +* HIDE VARIABLE with empty input deletes all watchers on temporary vars + +111027 +------ +* more extensive Error catching +* slider for numerical text entries in "mobile mode" +* bigger blocks in "mobile mode" + +111031 +------ +* new: widgets.js +* PushButtons + +111102 +------ +* Morphic: StringMorph shadows + +111103 +------ +* widgets: ToggleMorphs (check boxes and radio buttons) +* non-variable watchers +* checkbox toggling for variable watchers + +111108 +------ +* Lists + +111109 +------ +* ListWatchers (basics) + +111111 +------ +* Morphic: visibleBounds() bug fix + +111114 +------ +* Morphic: fullImageClassic() for ListWatcherMorphs +* Threads: MultiArgMorph now use Lists instead of JS-Arrays +* List Blocks +* GUI: adding/removing variables doesn't make the palette jump to the top +* Blocks: list type slots + +111115 +------ +* Morphic: more tolerant grabbing +* Lists: synchronized Watcher updating (speed-up) + +111116 +------ +* Lists: conservative watcher updating (speed-up) +* GUI: logo pane and 'about' box + +111118 +------ +* Lists: watcher shows list range (speed-up, stability) + +111121 +------ +* Atomicity (WARP) +* REPEAT UNTIL +* WAIT UNTIL + +111123 +------ +* hybrid lists (arrayed and linked) +* CONS and CDR + +111124 +------ +* layout optimization for dropped and snapping blocks (thanks, John!) +* Equality testing for lists (thanks, Brian!) + +111128 +------ +* layout optimization merged into Morphic.js -> trackChanges + +111129 +------ +* Widgets: DialogBoxMorph basics + +111130 +------ +* Widgets: AlignmentMorphs +* keyboard events for DialogBoxMorphs + +111202 +------ +* Widgets: InputFieldMorphs +* Prompters based on DialogBoxes +* Renaming of input templates +* Morphic keyboard enhancements + +111205 +------ +* new primitives: MOUSE X, MOUSE Y, TIMER, RESET TIMER + +111207 +------ +* byob.js (CustomBlockDefinition, CustomCommandBlockMorph) + +111209 +------ +* BlockEditor basics for CustomCommandBlocks + +111212 +------ +* BlockDialogMorph (basics) +* CustomReporterBlockMorph + +111213 +------ +* call/cc for lambdas and custom blocks + +111214 +------ +* feature: deleting block instances and custom block definitions + +120106 +------ +* InputSlotEditor basics +* bigger tick for radio buttons +* PushButtons redone for WebKIT 2 compatibility + +120109 +------ +* Morphic: single quote input for WebKIT 2 compatibility +* BYOB: BlockInputFragmentMorphs + +120115 +------ +* BlockLabelPlaceHolderMorphs +* BlockInputDialogMorph (short form) + +120119 +------ +* MOD, TRUE and FALSE reporter blocks +* AND, OR, NOT reporter blocks +* BROADCAST AND WAIT command block + +120120 +------ +* Morphic: question mark input for WebKIT 2 compatibility (does it break on Windows?) +* Morphic: turtle tracks round endings for WebKIT 2 compatibility (cannot use closePath()) + +120123 +------ +* Threads: tail call elimination + +120125 +------ +* STORE: serializing, saving and loading projects, first pass, all by Nathan +* HatBlock bezier curve fixed width +* settings for AlignmentMorph regarding handling of hidden Morphs +* GUI enhancements +* input slot long form dialog variant outline +* pointless filters in most FORINS in response to Nathan's derogatory comments :-) + +120127 +------ +* input slot long form dialog - basic (single) input types + +120130 +------ +* input slot long form dialog - multiple inputs +* input slot long form dialog - default input values + +120131 +------ +* upvar GUI in input slot long form dialog (w/o upvar functionality) + +120201 +------ +* upvars in %var slot drop-down menu + +120202 +------ +* more primitives in Motion, Looks and Pen categories + +120203 +------ +* Morphic: horizontal mouse wheel scrolling (thanks for this fix, Nathan!) +* more primitives in the Pen category + +120206 +------ +* Morphic: color specifiable in String() constructor +* Widgets: ToggleButtonMorphs +* Objects: block categories +* GUI: tabbed palette mock-up (not yet within a real GUI) + +120207 +------ +* BYOB: categories (colors) for new custom blocks + +120208 +------ +* categories and block type editing for existing custom blocks + +120209 +------ +* Morphic: formatting capabilities for Menus and ListMorphs +* Morphic: optional 'own properties' highlighting in the Inspector's "show" menu + +120214 +------ +* multiple sprites & lots of new stuff in all modules +* Morphic: dragging optimization +* Nathan's fixes to Morphic (shadow fix, mouse wheel fix) + +120215 +------ +* scriptable and programmable stage, selectable in the corral +* stage watchers with "active", auto-updating object name labels +* IF ON EDGE BOUNCE primitive, still buggy +* GUI fixes, all frame morphs in the corral now reject object drops + +120216 +------ +* saving & loading, xml serialization, thanks, Nathan! + +120217 +------ +* Morphic: introducing combined mouse-keyboard events +* GUI: Project label + +120221 +------ +* user and development modes (shift-click on Snap! logo) +* Open Project dialog (thanks, Nathan) +* blocks caching for primitives and custom blocks +* custom block prototype edits visible in the palette while editing +* sprite duplication +* custom block definition duplication and re-binding +* the only sprite in the IDE is now deletable +* primitive blocks for GHOST effect + +120222 +------ +* Morphic: Tabbing among input fields fix +* Threads: REPORT primitive fix + +120224 +------ +* STOP BLOCK primitive +* error catching turns off in development mode (on in user mode) + +120226 +------ +* primitive control structures adjusted to new REPORT rule + +120229 +------ +* global variables +* hybrid lists CDR fix (thanks, Brian!) +* debugging primitives (alert, console.log) in development mode +* all libraries edited to conform to JsLint's latest petty rules ('else' after 'return') + +120301 +------ +* store.js: color slot and global vars patch (thanks, Nathan!) +* blocks.js: bug fix for drop-down menus (wouldn't allow selecting empty) + +120305 +------ +* upvars +* globals vars serialization fix +* MultiArgs: shift-clicking on an arrow repeats action 3 times + +120306 +------ +* Morphic: prevent text edits from reversing +* added "WITH INPUT LIST" variants for RUN/LAUNCH/CALL primitives - commented out +* changed '%inputs' slot type to non-static (makes "w/input list" redundant) +* Threads: fixed tail-call optimization induced bug in pushContext() +* WHEN I AM CLICKED hat block (control) +* WHEN KEY PRESSED hat block (control) +* MOUSE DOWN? predicate (sensing) +* KEY PRESSED? predicate (sensing) + +120307 +------ +* object collision detection (TOUCHING? predicate block for Sprites) +* poly-key state detection + +120308 +------ +* Morphic: SpeechBubbleMorph orientation left/right +* Threads: empty block definitions no longer raise an exception +* SAY primitive command block for Sprites + +120309 +------ +* SAY _ FOR _ SECS primitive command block for Sprites +* Morphic: thought bubble display variant of SpeechBubbleMorph +* THINK and THINK FOR SECS primitive command blocks for Sprites +* STAMP primitive command block for Sprites +* ROUND, JOIN, LETTER OF, LENGTH OF, UNICODE OF and UNICODE AS LETTER primitive reporters + +120313 +------ +* Widgets: ToggleElementMorph, TabMorph +* BlockEditor: Pictographic type buttons +* IDE: Tabs for scripts/costumes/sounds + +120314 +------ +* JOIN becomes variadic (Jens isn't enthusiastic about it) +* About text changed according to Mitch's suggestion +* BYOB: JaggedBlockMorph +* pictographic type buttons in the short form input dialog + +120315 +------ +* Morphic: colored shadows +* Widgets: ToggleMorph with embedded toggle elements +* pictographic slot type buttons in the long form input dialog +* palette speedup +* Error message when RUN/CALL/LAUNCHing a block w/o passing the expected no. of inputs +* Illegal drops prevented in user mode (enabled in dev mode) + +120316 +------ +* long form input dialog speedup (pictograms are now plain pictures instead of Toggles) +* Morphic: Morphs behind another one no longer receive mouseEnter/mouseLeave events +* Blocks: ScriptPanes behind other Morphs no longer show drop target feedbacks + +120319 +------ +* THREADS: unevaluated inputs +* Morphic: detect and respect minimum font size renderable +* Morphic: text selection display fix for FF + +120320 +------ +* Morphic: droppedImage() event + +120321 +------ +* Costume, CostumeEditorMorph, CostumeIconMorph + +120322 +------ +* GUI: WardrobeMorph +* Slider and ScrollFrame colors + +120323 +------ +* Morphic: handle multiple image file drops + +120327 +------ +* Costumes, first iteration + +120328 +------ +* Costumes: rotation center functionality + +120329 +------ +* Sprites: the rotation center now is the pen tip + +120331 +------ +* Stage: extra pen trail layer +* Morphic: texture handling (eliminating canvas patterns b/c of Chrome problems) +* Objects: motion precision fixes + +120401 +------ +* settings menu: touchscreen settings +* thread safety option +* store.js: Costumes & pen trails support. Thanks, Nathan! +* context menus for watchers (thx, Nathan!) + +120402 +------ +* pressing the stop sign makes all speech bubbles disappear +* null continuations now behave the same as STOP SCRIPT blocks + +120403 +------ +* minWidth property for SyntaxElements + +120406 +------ +* ASK/ANSWER for sprites + +120403 +------ +* ASK/ANSWER for the stage + +120416 +------ +* custom block prototype slot type and default value indicators +* Sounds, first pass (thanks, Ian!) + +120417 +------ +* Snap! Build Your Own Blocks. Alpha + +120420 +------ +* Rings (basics) + +120424 +------ +* Rings (first pass completed) + +120425 +------ +* unringify menu item for Blocks +* evaluator: variable setters can refer to variables by their reified getters + +120430 +______ +* zebra coloring (first pass) + +120502 +------ +* settings menu item for toggling zebra coloring +* new thumbnail() for StageMorph +* store.js: Fixes for local storage in local instance ("airplane saving") + +120503 +------ +* text- and object- type slots (and hints) +* zebra coloring fixes for input slots with pull-down menus +* costume flipping +* rotation styles + +120504 +------ +* rotation style support for sprite "turtle" costume +* rotation style buttons hidden for stage +* export background-less pictures of scripts +* sprite draggability control checkbox (in the IDE's sprite bar) + +120507 +------ +* reification: omit empty slots inside nested lambdas for implicit parameters +* display fixes for rings inside rings +* DISTANCE TO reporter block primitive in the sprite's sensing category + +120509 +------ +* exporting projects (holding the shift key URI-encodes the XML) + +120514 +------ +* Morphic: droppedText() event +* GUI: opening project files via drag & drop +* GUI: invoking the file dialog to open projects, import costumes and sounds +* Threads: nested upvar fix +* Threads: hybrid variable scope taken out (it's all lexical again for now) +* Blocks/BYOB: zebra-coloring related fixes + +120515 +------ +* GUI: disabled file dialog for now due to some issues +* Blocks/BYOB: Prototype block zebra coloring adjustment +* Store: minor fixes in the blocks dictionary + +120516 +------ +* monadic OF primitive block in the operators category + +120518 +------ +* Morphic: better keystroke detection +* new interpolating HTTP reporter in the sensor palette + +120521 +------ +* Pinch-Zoom for touchscreen devices +* Virtual keyboard for touchscreen devices + +120522 +------ +* Morphic/Blocks: SlideBackToFormerSituation + +120523 +------ +* Morphic: single-touch-and-hold pops up the context menu +* Morphic: pinch-zoom and virtual keyboard improvements + +120525 +------ +* late-binding custom blocks, changes in threads.js, byob.js and store.js (et al.) + +120611 +------ +* Morphic: auto-detect Chrome issue 90001 and set "useBlurredShadows" appropriately +* Blocks: solid block highlighting (as in Scratch 1.4) when "useBlurredShadows == false" +* GUI: Settings menu entry for blurred / solid shadows and highlights +* Threads: Type checking, primitive in the operators category + +120612 +------ +* global custom blocks (first pass, no serialization yet) + +120613 +------ +* new module: xml.js, a simple XML DOM/encoder/parser for Morphic.js + +120614 +------ +* store.js: Global vars fix (xml.js deprecated) + +120615 +------ +* store.js: Stage vars (watchers) fix +* store.js: Empty (bodiless) custom blocks fix +* store.js: Global custom blocks support + +120618 +------ +* GUI: Screenshot feature +* GUI, store.js: Error catching turned off in dev mode (for debugging store.js) +* store.js: saving / loading of sprites' scale, draggability and rotation style +* Morphic/GUI: Virtual keyboard support can be toggled (to hide caret in Opera etc.) +* GUI, store.js: Saving / loading of "thread safety" setting +* blocks caching limited to primitives +* introducing palette caching + +120619 +------ +* store.js fixes for empty, non-editable input slots (e.g. list and boolean slots) +* objects.js/byob.js fixes for editing recursive custom blocks + +120620 +------ +* Morphic/Blocks: More precise control over where reporters are dropped and snap +* Morphic documentation update + +120621 +------ +* POINT TOWARDS and GO TO primitive command blocks in the motion category + +120622 +------ +* changed license to AGPL (all modules and documentation) + +120625 +------ +* objects fix: changing pen properties sometimes offsets the sprite +* blocks: variable slots no longer accept reporter drops (Jens regrets but doesn't agree to auto-ringify dropped variable blobs at this stage of development) +* blocks: better (yet) control over where reporters can be dropped +* blocks fix: sometimes reporters cannot be dropped into slots in the block editor +* threads: comparing strings (the = block) is now case-insensitive +* threads/blocks: multi-args can now be eval'ed with variadic inputs +* lists: equality testing fix for mixed linked/arrayed lists (thanks, Brian!) +* LIST primitive with new, static input spec +* store: stage watcher styles (small, large, slider) are now persistent thru save/re-load +* objects: stage watcher slider min/max is now settable thru context menu +* store: stage watcher slider min/max are now persistent through save/re-load + +120626 +------ +* blocks/threads fix: "any unevaluated" slots now reify their typed-in input values +* byob/threads fix: custom block definition reification now ignores empty-slot bindings +* GUI: copying scripts among sprites via drag & drop on corral icons (first rough version) + +120627 +------ +* blocks/store/objects/threads fix: STOP BLOCK gets converted to REPORT on save/reload +* byob/GUI: new entry in the settings menu to always show input dialog in long form +* blocks/GUI: new entry in the settings menu to prefer empty slots for reporter drops +* blocks: in scripting areas rings and variable reporters can be nested inside each other +* in general rings will not vanish on ring/var drop if already inside other rings +* context menu help feature for blocks + +120628 +------ +* GUI: re-ordering costumes via drag & drop +* GUI: copying costumes among sprites via drag & drop on corral icons + +120629 +------ +* Morphic: StringMorphs now have the option to visualize blanks (as colored dots) +* Blocks: all input slots (in blocks) are now visualizing blanks +* GUI: re-ordering sounds via drag & drop +* GUI copying sounds among sprites via drag & drops on corral icons + +120702 +------ +* store fix: Newly loaded projects did not get keyboard events (now they do) +* threads fix: Evaluating STOP ALL did not stop sounds (now it does) + +120703 +------ +* GUI: open a project from URL via #open:URL +* GUI: run a project from data via #run:XML or from URL via #run:URL + +120704 +------ +* major refactoring of serialization (new xml.js, store.js) + +120705 +------ +* store: stage watchers monitoring lists remember their dimensions + +120709 +------ +* xml decoding fix +* app mode, first rough pass (no stage scaling yet) + +120710 +------ +* app mode related adjustments to blocks.js, gui.js and threads.js +* fix: line breaks in project notes are now preserved thru export/import (xml) + +120711 +------ +* app mode: arbitrary stage scaling (auto-resizes to fill the browser's client area) + +120712 +------ +* small stage mode (for bigger scripting area, e.g. in lectures or on mobiles) + +120713 +------ +* objects fix: zero values now show up in watchers (are no longer blank) +* objects fix: dragged sprites now keep their correct relative stage coordinates +* threads fix: dragged sprites are identifiable by running scripts + +120716 +------ +* Morphic scroll frames: customizable "growth" property, used in scripting panes +* store: Sprites' visibility state gets persisted thru save/load +* store: Watchers' visibility attribute format now same as sprites' (hidden="true") +* BYOB fix: Custom block prototype rendering fix when opening a Block Editor instance +* blocks fix: Predicate slots no longer turn into reporter slots upon save/load +* blocks fix: made dropping reporters into empty slots easier when preferring empty slots + +120717 +------ +* costumes/sounds: omit filename suffixes when importing +* costumes/sounds: rename via context menu +* costumes: export via context menu +* thumbnails: are now centered within their widgets + +120718 +------ +* fix: catch nil inputs in motion and looks primitives +* fix: answer variable value Boolean false not as zero +* GUI: window-reflow adjustments +* store fix: Booleans retain their type thru save/load (not converted to Strings) +* threads fix: using REPORT/STOP BLOCK inside a WARP block now stops warping + +120719 +------ +* graphic effects (currently only "ghost") for the stage +* new feature: Pen trails collision detection +* fix: Keystroke detection + +120720 +------ +* fix: textify zero and false values in JOIN primitive (don't skip) + +120723 +------ +* Color collision detection (first rough pass) + +120724 +------ +* Color collision detection & thumbnail adjustments and fixes, incl. helpscreens + +120725 +------ +* fix: SET PEN COLOR no longer offsets the sprite +* settings menu: optional input sliders (for Android) + +120726 +------ +* REPORT primitive moved to STOP blocks in palette +* graphical representation of Boolean values in watchers and bubbles +* fix: empty numerical input slots evaluate to zero (thanks, Stephen!) + +120728 +------ +* speech bubble scaling +* Boolean value representations in operator color (green) +* eliminated "ring" type + +120730 +------ +* adjust REPORT / STOP BLOCK semantics (special case implicit C-shaped slot lambdas) + +120731 +------ +* lists fix: preserve zero/false values when assigned in list blocks +* threads refactored (eliminated now redundant context.isInsideCustomBlock attribute) +* blocks/byob: mutable formal parameters for custom block definitions and rings +* threads: CHANGE VAR typecasting bug fixed + +120801 +------ +* JOIN can now have any number of input slots, and be CALLed with an input list + +120802 +------ +* threads: Invoking a lambda with empty input slots without arguments binds them to '' +* blocks/gui/byob: MultiArg layout fix +* "Clicking sound" option in the settings menu + +120803 +------ +* blocks fix: enable reporter drops on empty rings in "prefer empty slot drops" mode + +120806 +------ +* blocks: SymbolMorph replaces Unicode characters for "green flag" and "stop" signs +* widgets: allow SymbolMorphs as button labels, new layout rule: minLabelExtent for buttons +* gui: button layouts moved to minLabelExtent rule +* fix: prevent drops on multi-arg arrows +* SymbolMorphs for all items in the GUI's tool bar + +120807 +------ +* SymbolMorphs for object type slot and identifier, and for "new sprite" button +* Verdana font preference for block labels (wider) +* store fix: Watcher label for "answer" now survives save/load (Thanks, Tom!) + +120808 +------ +* Morphic, GUI, blocks, BYOB: More "gentle" font control (can be overridden by browser) +* BYOB: new "Apply" button in the block editor (updates definition keeping editor open) +* BYOB: editing custom block prototpyes preserves existing inputs in custom block instances + +120809 +------ +* Pause button: Pauses/resumes all currently active stage processes (scripts) +* blocks: minor performance tweaks + +120810 +------ +* blocks: bug fix for input accessing in variable drop-downs +* dev-mode reporter for STACK SIZE for tail-call-elimination monitoring + +120813 +------ +* dev-mode reporter for FRAMES for thread performance monitoring +* minor refactoring of store.js to conform with the latest JSLint + +120814 +------ +* fix: disappearing and undraggable sprite bug (thanks, Kirk!) +* widgets: ToggleMophs can now have two different labels/symbols to reflect their state +* gui/blocks: switching symbols for all toggles, re-introducing the green flag symbol + +120815 +------ +* octagonal stop sign symbol +* cache manifest +* SWITCH TO COSTUME (-1) goes back one costume in the list & wraps around +* Variable blobs can be renamed + +120816 +------ +* SNAP! Connection Strategy +* OS-native File Dialog for importing projects, pictures, sounds (also for Safari 6) + +120830 +------ +* custom block definition export (disabled for now) +* zebra-coloring fix (for Hummingbird-video bug) +* any number of script vars possible + +120910 +------ +* exporting global custom blocks (beginning) +* byob.js: BlockExportDialogMorph (beginning) + +120911 +------ +* exorting & importing global custom blocks + +120912 +------ +* serialization adjustments (app attribute in top-level XML node) +* same-named custom block conflict detection and resolution +* overloading of custom blocks with samed-named imported ones +* cascaded block library support (block sets depending on each other) + +120913 +------ +* morphic.js: Refactoring to conform with JSHint's line breaking checks +* new morphic.txt documentation version +* new version of "Contributing to BYOB4" guideline with section on coding style +* exporting & importing sprites + +120914 +------ +* store.js: fix for loading variables containing reporters and unevaluated inputs + +120918 +------ +* comments (non-sticky) +* ScriptsMorph duplicating fix +* block editor cleanUp fix (prototype hat always stays on top) +* block editor persistence of free-floating objects (scripts, comments) + +120919 +------ +* store.js: minor fixes +* gui.js: URL #open: feature now works with all importable resources (e.g. blocks) + +120920 +------ +* js: blocks, byob, morphic, objects, threads, widgets edited for latest JSLint + +120924 +------ +* threads.js fix for REPORT inside C-Slots (pop another frame under certain conditions) + +120925 +------ +* xml.js: escape tilde character to avoid file corruption thru serializer.store() + +121002 +------ +* basic localization mechanism, use settings menu to switch languages +* German translation (for testing), #lang:de launches Snap! localized + +121004 +------ +* Morphic: triggering "reactToEdit" when text editing is terminated + +121010 +------ +* generalized localization hooks merged into Morphic.js and Widgets.js +* Morphic: TextMorphs (multi-line strings) now support text shadows (used in widgets) + +121015 +------ +* Morphic: New "reactToKeystroke()" events are escalated when editing strings/texts +* Blocks, Threads, Objects, Store: InputSlots now have localizable menu options +* GUI, Locale, lang-de: localization re-organized (now considered complete for LTR) + +121016 +------ +* fixed clicking sound entry in the settings menu +* input slots are now deselected on losing focus +* fix: Cannot delete the only label part in a custom block prototype anymore +* button acknowledgement label now spells 'OK' instead of 'Ok' +* fix: Cannot create unnamed ('') variables anymore +* fix: ScriptVariables' names' spaces are now normalized & can't be set to empty ('') +* changed wording of "Import" tooltip +* Thanks, Nathan, for spotting and reporting these bugs! +* added localization for block definition deletion and about dialogs +* edits in the sprite name field no longer need to be acknowledged by pressing +* new file: Translation Guide (translating Snap.txt) + +121017 +------ +* Italian translation! Woohooo, thanks, Stefano! +* added "unringify" to translator dictionary, thanks, Stefano! +* fixed a require() bug in XML, thanks, Nathan! +* fixed #run: URL switch. #run: is now officially supported! + +121018 +------ +* minimal translation dict updates ('rename costume' and 'rename sound') + +121019 +------ +* the costumes tab now also displays the default "Turtle" icon symbols +* fixed a small scoping bug in Morphic's touched event (thanks, Davide!) +* new version of lang-it.js (thanks, Stefano!) + +121022 +------ +* Japanese(Kanji and Hiragana) translations! Woohooo, thanks, Kazuhiro Abe! +* IF ON EDGE BOUNCE fix. Thanks, Stefano! +* additional localization strings and snap.html fix, thanks, Kazuhiro Abe! +* global / local watcher label fix. Thanks, Nathan! +* Morphic: Text scrolling when editing. Thanks, Nathan and Stefano! +* Morphic: Took out WorldMorph.trailsCanvas handling, thanks, Davide! +* Morphic text rendering ascender space fix (+ adjustments mostly everywhere) + +121023 +------ +* added "Edit label fragment" to translator dictionary +* minor fix in language changing mechanism +* minor fix re. block rendering (hole erasing) b/c of new ascenders +* minor fix re. dialog box rendering b/c of new ascenders +* minor fix re. dialog box shadow rendering. Thanks, Brian, for spotting this! + +121024 +------ +* sprite sequence in corral can be ordered via drag & drop (& persists) + +121025 +------ +* Korean translation! Woohooo, thanks, Yunjae Jang! +* Portuguese translation! Wohoo, thanks, Manuel Menezes de Sequeira! +* Morphic optimizations in FrameMorph and InspectorMorph, thanks, Davide! +* removed defunct "Open Projekt" entry in lang-de.js, thanks, Manuel! + +121026 +------ +* fix: Process inputOption() backward compatibility for localizable drop-down options + +121029 +------ +* Czech translation! Woohooo, thanks, Michael Moc! +* translations now dynamically load and unload. Thanks, Nathan, for the hint! +* Morphic now supports + a on Macs, thanks, Davide! + +121030 +------ +* Morphic: allow edited text scrolling to be disabled + +121105 +------ +* GUI, Objects: Pressing triggers the green flag, the red stop sign + +121106 +------ +* Morphic: Menu re-vamp, now supporting multi-line items, icons, and icon-text pairs + +121107 +------ +* Morphic: new slider edit event, updated documentation (text editing) +* blocks, GUI: New "Execute on slider change" option for "live coding" + +121109 +------ +* Widgets: fixed minor rendering bugs for dialog boxes +* GUI, Blocks: changed control bar layout, added cloud button (under construction...) +* new module stub: cloud.js (likewise under construction) + +121112 +------ +* Simplified Chinese translation! Wohoo, thanks, 邓江华 ! + +121114 +------ +* first experimental Web Audio API version, sine-wave only. Thanks, Achal! +* new blocks: TEMPO, REST FOR n BEATS, PLAY NOTE, CHANGE TEMPO, SET TEMPO +* currently only fully supported by Safari + +121115 +------ +* WARP block moved up in Control palette (for better discoverability) + +121116 +------ +* Esperanto translation! Woohoooo, thanks Sebastian Cyprych! +* a few additional localizable strings +* store.js: "Obsolete!" Reporter fix, thanks, Nathan! +* Morphic.js: support for dropped binary files +* .ypr project loading, Whoa! Awesome, Nathan!! +* French translation stub! Thanks, Jean-Jacques Valliet! + +121119 +------ +* blocks context menu: duplicate "this block only" feature +* blocks context menu: relabel feature +* blocks context menu: ringify / unringify misplacement fix +* Morphic: MenuItem icon shadow dimension adjustments +* store: fixes STOP ALL block spec +* added some more translation strings +* updated Korean translation + +121120 +------ +* major refactoring of blocks dict and blocks generation code +* new "show all entry in the stage's context menu + +121121 +------ +* Morphic: fixed reactToEdit() event trigger -> fixes scrambled sprite names +* Threads: hide / show variable watcher fix for watchers on globals +* Threads: Process reentrancy fix for played notes in non-thread-safe mode +* Store: global watcher load fix +* Store: Sprite ordering fix for Safari +* Objects / GUI / Blocks: fix for "relabel" + +121122 +------ +* Blocks: right click delete reporter fix (restores slot), thanks, Ryan! +* Blocks: restore zero-value default fix +* Objects Fix: Variable blobs become undraggable on save / load. Thanks, Ryan! +* Morphic: enable all keys for text input (take out legacy browser support) +* new "Animations" option in the settings menu +* zooming the stage in & out now animates depending on the user's preference + +121123 +------ +* Blocks: C-Slot rendering fix (eliminate occasional transparent line) +* Store, GUI: Beginnings of the Cloud data format (in progress...) + +121127 +------ +* Morphic: SpeechBubbleMorph shadow artefact fix +* Morphic: Backtab support & entry field tabbing ("wrapping") fix +* Objects: List watchers inside speech bubbles are resizable again +* Store, GUI: Cloud data formats (separating media from program data) +* Store: Fix for saved "obsolete" blocks (projects can be re-loaded) +* new Operators primitive: IS IDENTICAL TO? +* new translation string for new primitive +* Simplified Chinese translation update +* BYOB, Objects: global custom block refresh fix + +121128 +------ +* Morphic: Interactive Tooltips ("isClickable" and resizing support for SpeechBubbleMorphs) +* Blocks: list watchers inside evaluation bubbles are now interactive +* Store: The user-edited name for the stage is now persistent +* Store: Cloud Data Format fix - mediaIDs are now independent of sprite sorting and layer +* French translation update + +121129 +------ +* Store: Cloud Data Format now references media by its name, obliterating the need to re-save media when reordering wardrobes or jukeboxes, but relying on unique names (within each sprite or the stage) +* Store: serializing / de-serializing of media in different receptacles +* Morphic: CTR-Z / CMD-Z for undo in text input fields +* Morphic: SHIFT-arrows selects text in input fields +* Morphic: new global method sizeOf(object) returns number of keys +* Morphic: redundant (quasi-inherited) code taken out of TextMorph +* GUI: "hasChangedMedia" property for IDE_Morph (Cloud Data Format support) +* GUI: When a sprite's current costume is deleted, it switches to the default one + +121203 +------ +* GUI, BYOB: tools module can be imported from the project menu +* Morphic: enhancement for editing non-left-aligned texts +* Morphic: minor text element fix for initial mouse down behavior +* Lists, Objects: text elements in list watcher cells are now editable +* Lists fix: comparing something with a non-existent list element no longer produces an infinite loop, thanks, Aleks, for reporting this! +* dynamically load ypr.js when first needed +* minor translation strings updates + +121204 +------ +* Morphic: text element mouse event propagation fix (list boxes) +* Lists: Empty list element follow-up fix +* Threads: Returning "undefined" to parent frame fix (caused type errors) + +121205 +------ +* Morphic: trigger "reactToEdit()" when tabbing among text fields +* GUI: display tool's name when importing the module + +121207 +------ +* Blocks: Drop target feedback for comments (in preparation for sticky ones) +* Objects: redraw turtle on pen color change, disable clicking on watchers + +121208 +------ +* Objects: SAY nothing bug fix. Thanks, Brian! + +121210 +------ +* Sticky comments (attachable to blocks in main scripting area) + +121211 +------ +* better alignment for sticky comments +* cloud api work + +121213 +------ +* "elastic" anchor lines for sticky comments +* cloud api work + +121217 +------ +* cloud api work +* Morphic: auto-text selection fix +* all modules: replaced tabs for spaces +* "Clean up" now arranges sticky comments correctly + +121219 +------ +* Threads, Cloud: switched most XMLHttpRequests to asynchronous (except URL switches) +* Morphic: Allow StringMorphs to hide their characters for password input +* Widgets: Login-Prompter +* cloud api work + +130107 +------ +* Slovenian translation!! Yay, thanks, Sasa!!! (Snap now supports a dozen languages!) +* list-colored drop "halo" for variadic inputs +* most modules: space / tab white space reformatting +* help screens!! Thanks, Brian!!! +* help screen API for custom blocks (currently only for the tools library) +* importing libraries is now "silent", i.e. it doesn't show a dialog letting you select which blocks to import anymore. + +130108 +------ +* Blocks: ArgLabelMorph. Dynamic labels for "kicked out" variadic inputs ("input list") +* Dynamic input label support in BLOCKS, STORE, THREADS, BYOB, GUI and LOCALE +* Blocks, BYOB: Zebra coloring fix for rings in grey blocks + +130110 +------ +* "input list:" (with colon) +* Blocks: Drawn symbols for TURN RIGHT / LEFT +* continuations tweaks +* revert of "returning 'undefined' to parent frame fix" (121204), breaks call/cc +* ScriptPane cleanUp tweak for attached comments + +130111 +------ +* Morphic: StringMorph leftClick event error catch + +130115 +------ +* Threads, Blocks: Continuations tweaks (enabling reporter - CATCH / THROW) + +130116 +------ +* Store, GUI: Cloud Data Format support +* Lists: CONS fix for zero CAR value + +130117 +------ +* "Reference Manual" entry in the Snap! Menu +* BYOB: Editing custom-block-prototypes only changes the prototype in the block editor (no longer every instance of the block), pressing OK or APPLY propagates changes to all block instances, pressing CANCEL does nothing (no longer reverts previously edited slots in instances back to their default state) + +130118 +------ +* new YPR version. Thanks, Nathan! +* Blocks: fixed "sometimes list watchers can be dragged out of value feedback bubbles" +* BYOB/Blocks: fixed restoring existing inputs and upvar names when editing custom blocks + +130121 +------ +* Threads: No more type coercion when setting a variable's value, instead only when incrementing it + +130122 +------ +* Symbols for local storage and for examples +* Cannot evaluate to null or undefined within an argument -> use empty string instead + +130123 +------ +* Import / Export text files from variable watchers (context menu) +* Max. size of displayed text in CellMorphs and value bubbles set to 500 characters + +130125 +------ +* Morphic: Better padding support for ScrollFrames +* Morphic: Better resizing & re-rendering support for Menus and ListMorphs +* Morphic: Inspection improvements for Menus and ListMorphs +* Morphic: Text scrolling improvements (scrollCursorIntoView()) +* Widgets: Rendering improvements for InputFields +* BYOB: changed all 'Ok' occurrences to 'OK' +* Threads, Lists: JOIN zero / false bug fix +* GUI: new ProjectDialogMorph + +130129 +------ +* Cloud: persistent log-in and auto-log-in +* GUI, Widgets: Cloud work... +* Morphic: "pic..." generic exporting feature + +130201 +------ +* Blocks: Context-menu-delete fix for CommandBlocks inside C-Slots: userDestroy() +* Morphic: Pen-redraw() optimization for warp() fix +* Cloud: Dual-component project optimization +* GUI: key now works with ProjectDialog + +130202 +------ +* Morphic, Objects, GUI, Store: "turtle costume pen" options (tip, middle) + +130204 +------ +* fast tracking, a.k.a. "Turbo Mode" + +130205 +------ +* Cloning, basic Scratch style, still *very* experimental + +130205 +------ +* Cloning collision detection refinements, still *very* experimental + +130211 +------ +* Fixed / Variable Frame Rate option in the settings menu (default is fixed, as in Scratch) + +130213 +------ +* GUI, Widgets: Cloud frontend complete +* OF reporter block in the sensor palette (Scratch functionality, not yet BYOB) +* CLONE block now takes sprite name or 'myself' as input, drop-down menu +* FAST TRACKING renamed to TURBO MODE +* unscheduled execution again made the default. +* "Prefer smooth animations" setting, runs strictly scheduled at around 30 fps max. +* scheduling mode saved in project data +* Settings menu clean-up +* Input slots in Hat blocks are now static (cannot receive reporters drops) + +130214 +------ +* clone drop-down menu fix (removed "close" entry) +* auto switching to small stage mode if the window gets narrow. Commented out b/c I don't like it +* changed costume name 'Turtle' to 'default' +* link to s.b.e/tos.html in signup dialog +* "Save project to disk" experimental feature (works currently only in Chrome) +* RUN variable OF sprite fix + +130215 +------ +* Store: Sprites are now first class stored objects (can be "values"), needed for OF block +* Blocks, GUI, Threads: "Turtle" and "Empty" costume names, gosh, Brian! +* Threads: Error messages fix + +130218 +------ +* SVG_Costumes (partial) +* Cloud work +* scaling during WARP glitch fix + +130221 +------ +* Cloud work: Connect / Reconnect mechanism and password hashing "salt" +* Exporting SVG_Costumes + +130222 +------ +* Objects: Fix for playNote distortion issue in Chrome + +130225 +------ +* Extended Signup dialog (COPPA-conforming, I hope) +* Morphic: mouse click event bubbling for input fields +* Widgets: Optional drop-downs for input fields + +130227 +------ +* Morphic: onNextStep and nextSteps() mechanism +* GUI, Cloud: Ersatz-progress-bar-messages, using nextSteps() + +130228 +------ +* "Updating..." message while updating the cloud project list +* Morphic: Clipboard "paste" text support (works currently only in Chrome) + +130311 +------ +* Czech translation update, thanks, Michael! +* Morphic: "pic..." fix for scroll panes, thanks, Davide! +* Morphic fix: Clicking on editable text once again moves the caret to the mouse cursor + +130312 +------ +* Threads: OR, AND are now special form primitives ("lazy") +* Threads: fix for minor pen optimization glitch (catching the stage) +* Lists, Objects: Resizing list watchers no longer makes them "tremble" + +130313 +------ +* Store: context receiver persistence fix (for reified scripts) +* Threads: Execute reified blocks in the callee's context (not in the caller's) + +130314 +------ +* GUI: When logged into the Cloud, "cloud" becomes default in the project dialog +* Store: local custom blocks can now store their definition receiver directly as value (avoiding turning them into "Obsolete!" blocks when re-opening the project), this is important for reified blocks assigned to variables elsewhere, and such for the part of OOP we can already do now. + +130318 +------ +* GUI, Blocks, BYOB, Widgets: Scaling Blocks and Scripts (shift-click on settings menu) +* Widets: numerical prompts +* GUI: #signup URL switch +* Blocks: adjusting highlights when modifying active scripts + +130319 +------ +* Blocks: SyntaxElementMorph fixLayout() optimization for active highlights +* Russian translation!! Yay, thanks, Svetlana Ptashnaya!! +* Store, GUI, Blocks: Scaling support for Comments and serialization/deserialization +* GUI: motd support: On startup Snap! looks for http://snap.berkeley.edu/motd.txt, if it exists it is shown in a dialog box +* GUI: fix for #run: URL switch +* GUI: cloudmsg support: cloud related notifications can be put into http://snap.berkeley.edu/cloudmsg.txt + +130320 +------ +* GUI: deactivated motd and cloudmsg mechanism for now (has some issues) +* Updated Portuguese translation, thanks, Manuel! +* Updated all translations for %keyHat and %msgHat specs +* YPR: fixed turnLeft / turnRight swap bug + +130321 +------ +* Cloud: allow every XMLHttpRequest to transport cookies (withCredentials = true) + +130322 +------ +* Widgets: optional sliders and "lively" graphics for numerical prompters +* Blocks, GUI: "Zoom blocks…" feature in the settings menu (no longer hidden) +* Objects: numeric prompters for watcher's sliderMin/Max +* translation updates +* Objects: 'pic…' screenshot feature for the stage +* GUI, Cloud: Fallback message support before showing an error + +130325 +------ +* Spanish translation! Yay, thanks, Victor Muratalla!! +* Objects: Boolean value block representations are now translated, thanks, Victor, for the report +* Simplified Chinese translation update, thanks 邓江华 !! + +130402 +------ +* Japanese translations update, thanks, Kazuhiro Abe! +* Content-type support for Cloud backend +* sharing / unsharing projects support and GUI +* the Block Editor now allows anchored comments +* duplicating a block / script / sprite now also duplicates anchored comments +* deleting a block / script now also deletes anchored comments + +130403 +------ +* YPR converter fix: No more text area in upper left corner of the Snap! IDE +* Blocks, BYOB, Store: PrototypeHatBlocks in the BlockEditor accept anchored comments + +130404 +------ +* loading shared projects in presentation mode, exporting URL for shared projects +* Selecting "Help" for a custom block now pops up the comment attached to its definition's prototype hat, if any +* BYOB fix for detaching comments from prototype hat blocks + +130405 +------ +* renaming variable blobs now features a drop-down with reachable variable names and a picture of the block to be renamed + +130408 +------ +* Cloud, GUI: Sharing / Unsharing projects finalization +* Lists: Adjust initial list watcher size to blocks' zoom scale +* Portuguese and Italian translations update, thanks, Manuel and Stefano! +* GUI fix: switch to edit mode and tab to scripts when loading a project, +* Objects: new feature (hidden in shift-clicked stage context menu): turn pen trails into new costume + +130409 +------ +* various formatting and encoding normalizations +* Morphic: Formatting options for Triggers and MenuItems (and ListItems): bold, italic +* Morphic: ListMorph (items) manipulation capabilites +* GUI: display shared project names bold typed in the project dialog +* GUI: Feedback msg when sharing / unsharing projects +* GUI: Shield (hide) IDE while opening a shared project for presentation +* GUI: Support for debugging shared projects + +130410 +------ +* Fixes for type casting and dragging dialogs by buttons, thanks, Nathan! +* Fix for loading shared projects in different formats (cloud data and plain project data) + +130411 +------ +* Morphic: virtual keyboard enhancements (see Morphic.js) +* GUI: disabled localStorage (as in I9 running locally) no longer prevents Snap! from loading + +130412 +------ +* Lists: fix for typecasting bug in CONTAINS +* BYOB: Tooltips for custom block templates (sitting in the palette): mousing over a custom block in the palette pops up its definition hat comment in a comment-colored speech bubble +* GUI: Sharing/Unsharing/Deleting now available in all version of the project dialog + +130415 +------ +* Blocks: place sticky comments on World layer on dragging their anchor block + +130416 +------ +* Cloud, GUI: additional dev settings + +130417 +------ +* Blocks: "scripts pic" option in the ScriptsMorph's userMenu lets you export a picture of all scripts (including comments) + +130418 +------ +* plenty of bug fixes from Nathan. Yay, you go!! + +130419 +------ +* German translation update for "scripts pic" feature + +130421 +------ +* using the percent character in variable names is now safe (fixes Github issue #65) +* Morphic: added Doubleclick support, example: inspectors +* GUI: Double clicking a project in the project dialog performs the dialog's action on it (open / save) + +130422 +------ +* GUI: Double clicking support for cloud side of project dialog + +130423 +------ +* Lists, Objects: Circularity no longer breaks watchers +* Widgets: Multiple Dialogs of the same kind are prevented except for a few (e.g. BlockEditor). Thanks for this fix, Nathan! (and for the many little UI things you've fixed as well) +* German translation update + +130424 +------ +* Widgets, BYOB, GUI: prevent multiple block editors on the same block definition, allow multiple dialogs on different objects, handle dialog instances in DialogBoxMorph.prototype + +130425 +------ +* Objects, Blocks, GUI, Store: Hide primitives feature +* Morphic: Introducing World.stamp as reference in multi-World setups +* Widgets: restore multi-dialog restrictions for multi-world setups +* Translation update for "hide primitives" feature + +130426 +------ +* Morphic: ensure unique World stamps +* Blocks: symbols for paint editor + +130427 +------ +* Blocks: paint bucket symbol +* highlight adjustments when merging scripts (#70) + +130429 +------ +* Blocks: symbols for solid rectangles and circles + +130430 +------ +* Objects: Costume shrink-wrapping +* Morphic: Allow triggers to be dragged if so specified (#83) +* GUI: select dragged costume +* Blocks: eraser symbol for paint editor +* Morphic: ScrollFrame scrollY() fix (fixes #24) + +130506 +------ +* Reset Password feature (frontend only) + +130510 +------ +* Reset Password via e-mailed link (frontend only) + +130514 +------ +* paint.js: Paint editor, first version, contributed by Kartik Chandra, Yay!! +* Threads, Objects, Blocks: Broadcast & message enhancements: When I receive , and getLastMessage reporter + watcher + +130515 +------ +* Objects: Costume shrinkWrap adjustments +* Morphic: Flat design preference introduced (default is off) +* Widgets: preparing for "flat GUI skins" + +130516 +------ +* "flat" GUI design preference (in the settings menu) + +130517 +------ +* GUI: user preferences (settings) are now made persistent in localStorage + +130604 +------ +* Morphic: Prevent undesired native dragstart events (introduced in Chrome 27) + +130605 +------ +* Objects: fix for hiding 'getLastAnswer' and 'getTimer' primitives + +130606 +------ +* BYOB: Newly created custom reporters now have an initial default REPORT block as definition body + +* Morphic: focus World canvas on mouse down (otherwise prevent default) + +130618 +------ +* Code mapping (generating textual code from blocks), first iteration + +130619 +------ +* Store: persisting code mappings in project and block library files + +130620 +------ +* GUI: add code mapping preference to persistent settings +* Blocks, BYOB, Lists, Objects: "flat" design enhancements for blocks and watchers +* Blocks: Multi-line input slots (TextSlotMorphs - %mlt) +* Objects: doMapCode() primitive now uses a multi-line input slot + +130621 +------ +* Morphic, Blocks: "flat" design fix: Handle manually "unshadowed" StringMorphs +* Objects, Blocks: %code input slot - multi-line, monospaced, type-in slot for code mappings + +130624 +------ +* Objects, Blocks: pretty printing for mapped code, now supporting Python mappings + +130625 +------ +* Widgets, Blocks: code mapping dialog input is now multi-line monospaced + +130626 +------ +* GUI: fixed #100 saving costumes to the cloud + +130627 +------ +* Objects: fixed speech bubble scaling when sprite is not onstage (reported in the forums) + +130628 +------ +* Morphic, GUI: improved importing costumes by dragging in pictures from other web pages + +130702 +------ +* Objects: took out "security margin" in Costume's shrinkWrap() method b/c Chrome no longer needs it -> fixed empty costume bug when drawing over the paint editor's bounds +* GUI: Import libraries feature (in the project menu) + +130704 +------ +* Codification (text code mapping and block header support) + +130705 +------ +* Blocks: fixed CommentMorph hiding/showing bug when switching to / from presentation mode + +130708 +------ +* Store: fixed serialization placement-bug for sprites + +130709 +------ +* Objects, Blocks, Threads: Collapsed codification primitives (code, header) into a single block +* Blocks: Added isEmptySlot() to BooleanArgMorph (thanks, Brian, for the bug report!) + +130710 +------ +* GUI: Reset hidden primitives and code mappings upon loading a new project + +130711 +------ +* Blocks: fixed occasional flickering in scripting areas (caused by deleted feedback morphs, a bug that surfaced in Chrome 28 on OSX and may be due to a possible Chrome GC issue) +* Blocks: preserve nested blocks in the scripting area when replacing a variadic input list with another input ("kick out" the nested blocks instead of "swallowing" them) +* Blocks, Threads: new floor() function in monadic math reporter's drop-down + +130712 +------ +* Blocks: Pipette symbol +* Paint: Pipette tool + +130713 +------ +* Paint: fixed pipette tool for floodfill + +130715 +------ +* Objects: increased palette's vertical growth by scrollBarSize +* Objects, Blocks, Threads: experimental text-function primitive (hidden, shown only in dev mode) + +130724 +------ +* Dutch translation, yay!! Thanks, Frank Sierens + +130730 +------ +* Blocks: Made it harder to drop reporters on the variadic input per se (as opposed to into one of its slots) in (default) "prefer empty slot drops" setting +* Blocks, Threads, Objects: PAUSE primitive command block +* GUI: fixed #104 (storing a cloud project under another name causes media loss) + +130731 +------ +* Blocks, Threads, Objects: experimental text SPLIT primitive in the operators category + +130801 +------ +* Blocks, Threads: "whitespace" & other options in SPLIT reporter's dropdown +* Blocks: Italicize editable input options (e.g. for the SPLT block) +* Blocks: Undrop Reporters feature (in script areas' context menus) + +130802 +------ +* Blocks: Undrop Reporters feature tweaks +* Blocks: Undrop Comments feature +* Blocks: Undrop Commands feature +* German translation update (for Undrop feature) + +130805 +------ +* Polish translation, yay!! Thanks, Witek Kranas! +* Morphic: mouseEnterDragging fix + +130807 +------ +* Objects, GUI: Sprite Nesting preliminaries +* Objects: Fixed stage costume scaling & misplacing bug. Thanks, Josh, for the report! +* Objects, GUI: Sprite Nesting GUI +* Objects: Nested Sprite Motion + +130808 +------ +* Objects: Nested Sprite Scaling +* Objects: Nested Sprite Rotation +* Objects: Nested Sprite synchronous / independent rotation +* Dutch translation update, thanks, Sjoerd Dirk Meijer! + +130809 +------ +* GUI: Nested Sprite Rotation style buttons on corral icons +* Store, Objects: Nested Sprite saving / loading + +130810 +------ +* Objects, GUI: Nestable Sprites fixes +* German translation update + +130812 +------ +* Objects, Threads: Nestable Sprites Collision Detection & fixes +* Dutch translation update + +130814 +------ +* Traditional Chinese translation, yay!! thanks, Chu-Ching-Huang! + +130817 +------ +* Norwegian translation, yay!! thanks, Olav Marschall! +* "Dynamic" library list, thanks, Brian diff --git a/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/lang-cs.js b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/lang-cs.js new file mode 100644 index 0000000..727fd68 --- /dev/null +++ b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/lang-cs.js @@ -0,0 +1,1136 @@ +/* + + lang-cs.js + + Czech translation for SNAP! + + written by Michal Moc + + Copyright (C) 2012 by Michal Moc + + This file is part of Snap!. + + Snap! is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + + + + Note to Translators: + -------------------- + At this stage of development, Snap! can be translated to any LTR language + maintaining the current order of inputs (formal parameters in blocks). + + Translating Snap! is easy: + + + 1. Download + + Download the sources and extract them into a local folder on your + computer: + + + + Use the German translation file (named 'lang-de.js') as template for your + own translations. Start with editing the original file, because that way + you will be able to immediately check the results in your browsers while + you're working on your translation (keep the local copy of snap.html open + in your web browser, and refresh it as you progress with your + translation). + + + 2. Edit + + Edit the translation file with a regular text editor, or with your + favorite JavaScript editor. + + In the first non-commented line (the one right below this + note) replace "de" with the two-letter ISO 639-1 code for your language, + e.g. + + fr - French => SnapTranslator.dict.fr = { + it - Italian => SnapTranslator.dict.it = { + pl - Polish => SnapTranslator.dict.pl = { + pt - Portuguese => SnapTranslator.dict.pt = { + es - Spanish => SnapTranslator.dict.es = { + el - Greek => => SnapTranslator.dict.el = { + + etc. (see ) + + + 3. Translate + + Then work through the dictionary, replacing the German strings against + your translations. The dictionary is a straight-forward JavaScript ad-hoc + object, for review purposes it should be formatted as follows: + + { + 'English string': + 'Translation string', + 'last key': + } 'last value' + + and you only edit the indented value strings. Note that each key-value + pair needs to be delimited by a comma, but that there shouldn't be a comma + after the last pair (again, just overwrite the template file and you'll be + fine). + + If something doesn't work, or if you're unsure about the formalities you + should check your file with + + + + This will inform you about any missed commas etc. + + + 4. Accented characters + + Depending on which text editor and which file encoding you use you can + directly enter special characters (e.g. Umlaut, accented characters) on + your keyboard. However, I've noticed that some browsers may not display + special characters correctly, even if other browsers do. So it's best to + check your results in several browsers. If you want to be on the safe + side, it's even better to escape these characters using Unicode. + + see: + + + 5. Block specs: + + At this time your translation of block specs will only work + correctly, if the order of formal parameters and their types + are unchanged. Placeholders for inputs (formal parameters) are + indicated by a preceding % prefix and followed by a type + abbreviation. + + For example: + + 'say %s for %n secs' + + can currently not be changed into + + 'say %n secs long %s' + + and still work as intended. + + Similarly + + 'point towards %dst' + + cannot be changed into + + 'point towards %cst' + + without breaking its functionality. + + + 6. Submit + + When you're done, rename the edited file by replacing the "de" part of the + filename with the two-letter ISO 639-1 code for your language, e.g. + + fr - French => lang-fr.js + it - Italian => lang-it.js + pl - Polish => lang-pl.js + pt - Portuguese => lang-pt.js + es - Spanish => lang-es.js + el - Greek => => lang-el.js + + and send it to me for inclusion in the official Snap! distribution. + Once your translation has been included, Your name will the shown in the + "Translators" tab in the "About Snap!" dialog box, and you will be able to + directly launch a translated version of Snap! in your browser by appending + + lang:xx + + to the URL, xx representing your translations two-letter code. + + + 7. Known issues + + In some browsers accents or ornaments located in typographic ascenders + above the cap height are currently (partially) cut-off. + + Enjoy! + -Jens +*/ + +/*global SnapTranslator*/ + +SnapTranslator.dict.cs = { + +/* + Special characters: (see ) + + Ä, ä \u00c4, \u00e4 + Ö, ö \u00d6, \u00f6 + Ü, ü \u00dc, \u00fc + ß \u00df +*/ + + // translations meta information + 'language_name': + 'Česky', // the name as it should appear in the language menu + 'language_translator': + 'Michal Moc', // your name for the Translators tab + 'translator_e-mail': + 'info@iguru.eu', // optional + 'last_changed': + '2013-03-011', // this, too, will appear in the Translators tab + + // GUI + // control bar: + 'untitled': + 'Bez názvu', + 'development mode': + 'Vývojový mód', + + // categories: + 'Motion': + 'Pohyb', + 'Looks': + 'Vzhled', + 'Sound': + 'Zvuk', + 'Pen': + 'Pero', + 'Control': + 'Ovládání', + 'Sensing': + 'Vnímání', + 'Operators': + 'Operátory', + 'Variables': + 'Proměnné', + 'Lists': + 'Seznamy', + 'Other': + 'Ostatní', + + // editor: + 'draggable': + 'přetahovatelný', + + // tabs: + 'Scripts': + 'Skripty', + 'Costumes': + 'Kostýmy', + 'Sounds': + 'Zvuky', + + // names: + 'Sprite': + 'Sprite', + 'Stage': + 'Scéna', + + // rotation styles: + 'don\'t rotate': + 'neotáčet', + 'can rotate': + 'lze otočit', + 'only face left/right': + 'jen vlevo/vpravo', + + // new sprite button: + 'add a new sprite': + 'přidat nový sprite', + + // tab help + 'costumes tab help': + 'Nahrajte obrázek odjinud z webu\n' + + 'nebo nahrajte soubor z Vašeho počítače přetažením sem.', + 'import a sound from your computer\nby dragging it into here': + 'Nahrajte zvuk z Vašeho počítače přetažením sem.', + + // primitive blocks: + + /* + Attention Translators: + ---------------------- + At this time your translation of block specs will only work + correctly, if the order of formal parameters and their types + are unchanged. Placeholders for inputs (formal parameters) are + indicated by a preceding % prefix and followed by a type + abbreviation. + + For example: + + 'say %s for %n secs' + + can currently not be changed into + + 'say %n secs long %s' + + and still work as intended. + + Similarly + + 'point towards %dst' + + cannot be changed into + + 'point towards %cst' + + without breaking its functionality. + */ + + // motion: + 'Stage selected:\nno motion primitives': + 'Vybraná scéna:' + + 'žádné pohybové bloky', + + 'move %n steps': + 'posuň se o %n kroků', + 'turn %clockwise %n degrees': + 'otoč se o %clockwise %n stupňů', + 'turn %counterclockwise %n degrees': + 'otoč se o %counterclockwise %n stupňů', + 'point in direction %dir': + 'zamiř směrem %dir', + 'point towards %dst': + 'zamiř k %dst', + 'go to x: %n y: %n': + 'jdi na pozici x: %n y: %n', + 'go to %dst': + 'jdi na %dst', + 'glide %n secs to x: %n y: %n': + 'plachti %n sekund na pozici x: %n y: %n', + 'change x by %n': + 'změň x o %n', + 'set x to %n': + 'nastav x na %n', + 'change y by %n': + 'změň y o %n', + 'set y to %n': + 'nastav y na %n', + 'if on edge, bounce': + 'pokud narazíš na okraj, odskoč', + 'x position': + 'pozice x', + 'y position': + 'pozice y', + 'direction': + 'směr', + + // looks: + 'switch to costume %cst': + 'oblékni kostým %cst', + 'next costume': + 'další kostým', + 'costume #': + 'kostým číslo', + 'say %s for %n secs': + 'povídej %s příštích %n sekund', + 'say %s': + 'povídej %s', + 'think %s for %n secs': + 'pomysli si %s dalších %n sekund', + 'think %s': + 'pomysli si %s', + 'Hello!': + 'Ahoj!', + 'Hmm...': + 'Hmm...', + 'change %eff effect by %n': + 'změň efekt %eff o %n', + 'set %eff effect to %n': + 'nastav efekt %eff na %n', + 'clear graphic effects': + 'odstraň grafické efekty', + 'change size by %n': + 'změň velikost o %n', + 'set size to %n %': + 'změň velikost na %n %', + 'size': + 'velikost', + 'show': + 'ukaž se', + 'hide': + 'schovej se', + 'go to front': + 'jdi do popředí', + 'go back %n layers': + 'jdi do pozadí o %n úrovní', + 'development mode \ndebugging primitives:': + 'vývojový mód \nladění primitiv', + 'console log %mult%s': + 'výstup do konsole: %mult%s', + 'alert %mult%s': + 'Upozornění: %mult%s', + + // sound: + 'play sound %snd': + 'hraj zvuk %snd', + 'play sound %snd until done': + 'hraj zvuk %snd a počkej', + 'stop all sounds': + 'vypni všechny zvuky', + 'rest for %n beats': + 'pauza %n dob(y)', + 'play note %n for %n beats': + 'zahraj tón %n po %n dob(y)', + 'change tempo by %n': + 'změň tempo o %n', + 'set tempo to %n bpm': + 'nastav tempo na %n bpm.', + 'tempo': + 'tempo', + + // pen: + 'clear': + 'smaž', + 'pen down': + 'pero dolů', + 'pen up': + 'pero nahoru', + 'set pen color to %clr': + 'nastavit barvu pera na %clr', + 'change pen color by %n': + 'změň barvu pera o %n', + 'set pen color to %n': + 'nastav barvu pera na %n', + 'change pen shade by %n': + 'změň odstín pera o %n', + 'set pen shade to %n': + 'nastav odstín pera na %n', + 'change pen size by %n': + 'změň tloušťku pera o %n', + 'set pen size to %n': + 'nastav tloušťku pera na %n', + 'stamp': + 'razítko', + + // control: + 'when %greenflag clicked': + 'Po klepnutí na %greenflag', + 'when %keyHat key pressed': + 'po stisku klávesy %keyHat', + 'when I am clicked': + 'po kliknutí na', + 'when I receive %msgHat': + 'po přijetí zprávy %msgHat', + 'broadcast %msg': + 'poslat všem %msg', + 'broadcast %msg and wait': + 'poslat všem %msg a čekat', + 'Message name': + 'jméno zprávy', + 'wait %n secs': + 'čekej %n sekund', + 'wait until %b': + 'čekej dokud nenastane %b', + 'forever %c': + 'stále opakuj %c', + 'repeat %n %c': + 'opakuj %n krát %c', + 'repeat until %b %c': + 'opakuj dokud %b %c', + 'if %b %c': + 'když %b %c', + 'if %b %c else %c': + 'když %b %c jinak %c', + 'report %s': + 'vrátit %s', + 'stop block': + 'zastav blok', + 'stop script': + 'zastav skript', + 'stop all %stop': + 'zastav vše %stop', + 'run %cmdRing %inputs': + 'spusť %cmdRing %inputs', + 'launch %cmdRing %inputs': + 'zahájit %cmdRing %inputs', + 'call %repRing %inputs': + 'zavolat %repRing %inputs', + 'run %cmdRing w/continuation': + 'spustit %cmdRing s podmínkou', + 'call %cmdRing w/continuation': + 'zavolat %cmdRing s podmínkou', + 'warp %c': + 'obal %c', + 'when I start as a clone': + 'začít po naklonování', + 'create a clone of %cln': + 'vytvořit klon %cln', + 'delete this clone': + 'odstranit klon', + + // sensing: + 'touching %col ?': + 'dotýká se %col ?', + 'touching %clr ?': + 'dotýká se barvy %clr ?', + 'color %clr is touching %clr ?': + 'barva %clr je na barvě %clr ?', + 'ask %s and wait': + 'zeptej se %s a čekej', + 'what\'s your name?': + 'Jak se jmenuješ?', + 'answer': + 'odpověď', + 'mouse x': + 'souřadnice myši x', + 'mouse y': + 'souřadnice myši y', + 'mouse down?': + 'stisknuto tlačítko myši?', + 'key %key pressed?': + 'stisknuta klávesa %key ?', + 'distance to %dst': + 'vzdálenost od %dst', + 'reset timer': + 'vynulovat stopky', + 'timer': + 'stopky', + '%att of %spr': + '%att z %spr', + 'http:// %s': + 'http:// %s', + 'turbo mode?': + 'turbo mód?', + 'set turbo mode to %b': + 'nastavit turbo mód na %b', + + 'filtered for %clr': + 'filtrovaný pro %clr', + 'stack size': + 'velikost zásobníku', + 'frames': + 'snímky', + + // operators: + '%n mod %n': + '%n modulo %n', + 'round %n': + 'zaokrouhli %n', + '%fun of %n': + '%fun z %n', + 'pick random %n to %n': + 'zvol náhodné číslo od %n do %n', + '%b and %b': + '%b a %b', + '%b or %b': + '%b nebo %b', + 'not %b': + 'není %b', + 'true': + 'pravda', + 'false': + 'nepravda', + 'join %words': + 'spoj %words', + 'hello': + 'ahoj', + 'world': + 'světe', + 'letter %n of %s': + 'písmeno %n z %s', + 'length of %s': + 'délka %s', + 'unicode of %s': + 'Unicode %s', + 'unicode %n as letter': + 'Unicode %n jako znak', + 'is %s a %typ ?': + 'je %s typu %typ ?', + 'is %s identical to %s ?': + 'je %s stejný jako %s ?', + + 'type of %s': + 'Typ %s', + + // variables: + 'Make a variable': + 'Vytvoř proměnnou', + 'Variable name': + 'Jméno proměnné', + 'Delete a variable': + 'Smaž proměnnou', + + 'set %var to %s': + 'nastav %var na %s', + 'change %var by %n': + 'změň %var o %n', + 'show variable %var': + 'ukaž proměnnou %var', + 'hide variable %var': + 'schovej proměnnou %var', + 'script variables %scriptVars': + 'Vytvoř seznam %scriptVars', + + // lists: + 'list %exp': + 'seznam %exp', + '%s in front of %l': + '%s v popředí z %l', + 'item %idx of %l': + 'položka %idx ze %l', + 'all but first of %l': + 'vše, ale první z %l', + 'length of %l': + 'velikost %l', + '%l contains %s': + '%l obsahuje %s', + 'thing': + 'věc', + 'add %s to %l': + 'přidat %s do %l', + 'delete %ida of %l': + 'smazat %ida z %l', + 'insert %s at %idx of %l': + 'vložit %s na %idx v %l', + 'replace item %idx of %l with %s': + 'nahraď prvek %idx v %l za %s', + + // other + 'Make a block': + 'Vytvoř blok', + + // menus + // snap menu + 'About...': + 'O programu...', + 'Snap! website': + 'Stránky Snap!', + 'Download source': + 'Stáhnout zdrojové kódy', + 'Switch back to user mode': + 'přepnout zpět do uživatelského módu', + 'disable deep-Morphic\ncontext menus\nand show user-friendly ones': + 'zobrazovat jednoduché menu', + 'Switch to dev mode': + 'přepnout do vývojářského módu', + 'enable Morphic\ncontext menus\nand inspectors,\nnot user-friendly!': + 'zobrazovat pokročilé menu', + 'Reference manual': + 'Referenční příručka', + + // project menu + 'Project notes...': + 'Poznámky k projektu...', + 'New': + 'Nový', + 'Open...': + 'Otevřít...', + 'Save': + 'Uložit', + 'Save As...': + 'Uložit jako...', + 'Import...': + 'Importovat...', + 'file menu import hint': + 'Načíst exportovaný projekt, ' + + 'knihovnu bloků, kostýmy nebo zvuky', + 'Export project as plain text...': + 'Exportovat projekt jako prostý text...', + 'Export project...': + 'Exportovat projekt...', + 'show project data as XML\nin a new browser window': + 'zobrazit data projektu jako xml XML\n v novém okně prohlížeče', + 'Export blocks...': + 'Exportovat bloky...', + 'show global custom block definitions as XML\nin a new browser window': + 'Zobrazit definici vlastních bloků jako\nXML v novém okně prohlížeče', + 'Import tools': + 'Importovat nástroje', + 'load the official library of\npowerful blocks': + 'nahraje oficialní knihovnu\npokročilých bloků', + + // settings menu + 'Language...': + 'Jazyk...', + 'Blurred shadows': + 'Měkké stíny', + 'uncheck to use solid drop\nshadows and highlights': + 'odškrtnutím se použijí\nostré stíny a světla', + 'check to use blurred drop\nshadows and highlights': + 'zaškrtni pro použití \nměkkých stínů a světel', + 'Zebra coloring': + 'Střídavé barvy', + 'check to enable alternating\ncolors for nested blocks': + 'Zaškrtnutí zapne střídavé\nbarvy pro vložené bloky', + 'uncheck to disable alternating\ncolors for nested block': + 'Odškrtnutí zruší použití střídavých barev pro vložené bloky', + 'Prefer empty slot drops': + 'Preferovat prázdný slot pro puštění', + 'settings menu prefer empty slots hint': + 'Zaškrtnutím bude preferováno prázdné místo na umístění', + 'uncheck to allow dropped\nreporters to kick out others': + 'odškrtnutím bude upřednostňováno nahrazení celé podmínky', + 'Long form input dialog': + 'Velké formuláře', + 'check to always show slot\ntypes in the input dialog': + 'Zaškrtnutím povolit zobrazování typů slotů ve vstupním dialogu', + 'uncheck to use the input\ndialog in short form': + 'odškrtnutí použije vstupní dialogy v krátké formě', + 'Virtual keyboard': + 'Virtuální klávesnice', + 'uncheck to disable\nvirtual keyboard support\nfor mobile devices': + 'odškrtnutí zakáže\npodporu virtuální klávesnice\n' + + 'na mobilních zařízeních', + 'check to enable\nvirtual keyboard support\nfor mobile devices': + 'zaškrtnutí povolí použití virtuální klávesnice\nna mobilních zařízeních', + 'Input sliders': + 'Posuvníky', + 'uncheck to disable\ninput sliders for\nentry fields': + 'odškrtnutí vypne použití posuvníků pro vstupní pole', + 'check to enable\ninput sliders for\nentry fields': + 'zaškrtnutní povolí použití posuvníků pro vstupní pole', + 'Clicking sound': + 'Zvuk kliknutí', + 'uncheck to turn\nblock clicking\nsound off': + 'odškrtnutí vypne zvuk při přicvaknutí bloku', + 'check to turn\nblock clicking\nsound on': + 'zaškrtnutí zapne zvuk přicvaknutí bloku', + 'Thread safe scripts': + 'Vláknově bezpečné skripty', + 'uncheck to allow\nscript reentrancy': + 'odškrtnutí povolí více vláken', + 'check to disallow\nscript reentrancy': + 'zaškrtnutí zakáže více vláken', + 'Turbo mode': + 'Turbo mód', + 'uncheck to run scripts\nat normal speed': + 'odškrtnutí spustí skript\nnormální rychlostí', + 'check to prioritize\nscript execution': + 'zaškrtnutí spustí skripty\nzvýšenou rychlostí', + 'Prefer smooth animations': + 'Zapnout plynulou animaci', + 'uncheck for greater speed\nat variable frame rates': + 'odškrtnout pro vyšší rychlost', + 'check for smooth, predictable\nanimations across computers': + 'zaškrtněte pro plynulé, předvídatelné\nanimace napříč počítači', + + // inputs + 'with inputs': + 's položkami', + 'input names:': + 'proměnné:', + 'Input Names:': + 'Proměnné:', + + // context menus: + 'help': + 'nápověda', + + // blocks: + 'help...': + 'nápověda...', + 'relabel...': + 'Zaměnit blok za...', + 'duplicate': + 'kopírovat', + 'make a copy\nand pick it up': + 'vytvořit kopii a držet ji', + 'only duplicate this block': + 'kopírovat pouze tento blok', + 'delete': + 'smazat', + 'script pic...': + 'obrázek skriptu...', + 'open a new window\nwith a picture of this script': + 'otevřít nové okno\ns obrázkem tohoto skriptu', + 'ringify': + 'obalit', + 'unringify': + 'zrušit zabalení', + + // custom blocks: + 'delete block definition...': + 'smazat definici bloku', + 'edit...': + 'upravit...', + + // sprites: + 'edit': + 'upravit', + 'export...': + 'export...', + + // stage: + 'show all': + 'Zobrazit vše', + + // scripting area + 'clean up': + 'Srovnat', + 'arrange scripts\nvertically': + 'zarovnat skripty vertikálně', + 'add comment': + 'přidat komentář', + 'make a block...': + 'vytvořit blok...', + + // costumes + 'rename': + 'přejmenovat', + 'export': + 'exportovat', + 'rename costume': + 'přejmenovat kostým', + + // sounds + 'Play sound': + 'spustit přehrávání', + 'Stop sound': + 'zastavit přehrávání', + 'Stop': + 'zastavit', + 'Play': + 'spustit', + 'rename sound': + 'přejmenovat zvuk', + + // dialogs + // buttons + 'OK': + 'OK', + 'Ok': + 'OK', + 'Cancel': + 'Zrušit', + 'Yes': + 'Ano', + 'No': + 'Ne', + + // help + 'Help': + 'Nápověda', + + // Project Manager + 'Untitled': + 'Nepojmenovaný', + 'Open Project': + 'Otevřít projekt', + '(empty)': + '(prázdný)', + 'Saved!': + 'Uloženo!', + 'Delete Project': + 'Smazat projekt', + 'Are you sure you want to delete': + 'Jste si jisti, že chcete projekt smazat?', + 'rename...': + 'přejmenovat...', + + // costume editor + 'Costume Editor': + 'Editor kostýmů', + 'click or drag crosshairs to move the rotation center': + 'klikni nebo přetáhni kříž pro přesunutí centra otáčení', + + // project notes + 'Project Notes': + 'Poznámky k projektu', + + // new project + 'New Project': + 'Nový projekt', + 'Replace the current project with a new one?': + 'Nahradit stávající projekt novým?', + + // save project + 'Save Project As...': + 'Uložit projekt jako...', + + // export blocks + 'Export blocks': + 'Export bloků', + 'this project doesn\'t have any\ncustom global blocks yet': + 'Tento projekt nyní nemá žádné globální bloky', + 'select': + 'vybrat', + 'all': + 'vše', + 'none': + 'nic', + + // variable dialog + 'for all sprites': + 'pro všechny sprite', + 'for this sprite only': + 'pouze pro tento sprite', + + // block dialog + 'Change block': + 'Změnit blok', + 'Command': + 'Příkaz', + 'Reporter': + 'Funkce', + 'Predicate': + 'Podmínka', + + // block editor + 'Block Editor': + 'Editor bloků', + 'Apply': + 'Použít', + + // block deletion dialog + 'Delete Custom Block': + 'smazat vlastní blok', + 'block deletion dialog text': + 'Smazáním tohoto bloku se odstraní všechna jeho použití.\n' + + 'Opravdu chcete tento blok smazat?', + + // input dialog + 'Create input name': + 'Vytvořit vstup', + 'Edit input name': + 'Upravit vstup', + 'Edit label fragment': + 'Upravit nápis', + 'Title text': + 'Nadpis', + 'Input name': + 'Vstup', + 'Delete': + 'Smazat', + 'Object': + 'Objekt', + 'Number': + 'Číslo', + 'Text': + 'Text', + 'List': + 'Seznam', + 'Any type': + 'Libovolný', + 'Boolean (T/F)': + 'Boolean (P/N)', + 'Command\n(inline)': + 'Příkaz\n(vnořený)', + 'Command\n(C-shape)': + 'Příkaz\n(C-tvar)', + 'Any\n(unevaluated)': + 'Cokoliv\n(nevyhodnoceno)', + 'Boolean\n(unevaluated)': + 'Boolean\n(nevyhodnoceno)', + 'Single input.': + 'Jednoduchý vstup.', + 'Default Value:': + 'Výchozí hodnota:', + 'Multiple inputs (value is list of inputs)': + 'Více vstupů (hodnoty v seznamu)', + 'Upvar - make internal variable visible to caller': + 'Vnitřní proměnná viditelná pro volání', + + // About Snap + 'About Snap': + 'O programu Snap', + 'Back...': + 'Zpět...', + 'License...': + 'Licence...', + 'Modules...': + 'Moduly...', + 'Credits...': + 'Přispěvatelé...', + 'Translators...': + 'Překladatelé', + 'License': + 'Licence', + 'current module versions:': + 'aktuální verze modulů:', + 'Contributors': + 'Přispěvatelé', + 'Translations': + 'Překlady', + + // variable watchers + 'normal': + 'normální', + 'large': + 'velký', + 'slider': + 'posuvník', + 'slider min...': + 'minimum...', + 'slider max...': + 'maximum...', + 'Slider minimum value': + 'minimální hodnota posuvníku', + 'Slider maximum value': + 'Maximální hodnota posuvníku', + + // list watchers + 'length: ': + 'délka: ', + + // coments + 'add comment here...': + 'přidat sem komentář...', + + // drow downs + // directions + '(90) right': + '(90) doprava', + '(-90) left': + '(-90) doleva', + '(0) up': + '(0) nahoru', + '(180) down': + '(180) dolů', + + // collision detection + 'mouse-pointer': + 'kurzor myši', + 'edge': + 'okraj', + 'pen trails': + 'stopa pera', + + // costumes + 'Turtle': + 'želva', + + // graphical effects + 'ghost': + 'duch', + + // keys + 'space': + 'mezerník', + 'up arrow': + 'šipka nahoru', + 'down arrow': + 'šipka dolů', + 'right arrow': + 'šipka doprava', + 'left arrow': + 'šipka doleva', + 'a': + 'a', + 'b': + 'b', + 'c': + 'c', + 'd': + 'd', + 'e': + 'e', + 'f': + 'f', + 'g': + 'g', + 'h': + 'h', + 'i': + 'i', + 'j': + 'j', + 'k': + 'k', + 'l': + 'l', + 'm': + 'm', + 'n': + 'n', + 'o': + 'o', + 'p': + 'p', + 'q': + 'q', + 'r': + 'r', + 's': + 's', + 't': + 't', + 'u': + 'u', + 'v': + 'v', + 'w': + 'w', + 'x': + 'x', + 'y': + 'y', + 'z': + 'z', + '0': + '0', + '1': + '1', + '2': + '2', + '3': + '3', + '4': + '4', + '5': + '5', + '6': + '6', + '7': + '7', + '8': + '8', + '9': + '9', + + // messages + 'new...': + 'Nový...', + + // math functions + 'abs': + 'absolutní hodnota', + 'sqrt': + 'odmocnina', + 'sin': + 'sin', + 'cos': + 'cos', + 'tan': + 'tan', + 'asin': + 'asin', + 'acos': + 'acos', + 'atan': + 'atan', + 'ln': + 'ln', + 'e^': + 'e^', + + // data types + 'number': + 'číslo', + 'text': + 'text', + 'Boolean': + 'boolean', + 'list': + 'seznam', + 'command': + 'blok příkazů', + 'reporter': + 'blok funkcí', + 'predicate': + 'podmínky', + + // list indices + 'last': + 'poslední', + 'any': + 'jakýkoliv' +}; diff --git a/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/lang-de.js b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/lang-de.js new file mode 100644 index 0000000..26e080c --- /dev/null +++ b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/lang-de.js @@ -0,0 +1,1231 @@ +/* + + lang-de.js + + German translation for SNAP! + + written by Jens Mönig + + Copyright (C) 2013 by Jens Mönig + + This file is part of Snap!. + + Snap! is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + + + + Note to Translators: + -------------------- + At this stage of development, Snap! can be translated to any LTR language + maintaining the current order of inputs (formal parameters in blocks). + + Translating Snap! is easy: + + + 1. Download + + Download the sources and extract them into a local folder on your + computer: + + + + Use the German translation file (named 'lang-de.js') as template for your + own translations. Start with editing the original file, because that way + you will be able to immediately check the results in your browsers while + you're working on your translation (keep the local copy of snap.html open + in your web browser, and refresh it as you progress with your + translation). + + + 2. Edit + + Edit the translation file with a regular text editor, or with your + favorite JavaScript editor. + + In the first non-commented line (the one right below this + note) replace "de" with the two-letter ISO 639-1 code for your language, + e.g. + + fr - French => SnapTranslator.dict.fr = { + it - Italian => SnapTranslator.dict.it = { + pl - Polish => SnapTranslator.dict.pl = { + pt - Portuguese => SnapTranslator.dict.pt = { + es - Spanish => SnapTranslator.dict.es = { + el - Greek => => SnapTranslator.dict.el = { + + etc. (see ) + + + 3. Translate + + Then work through the dictionary, replacing the German strings against + your translations. The dictionary is a straight-forward JavaScript ad-hoc + object, for review purposes it should be formatted as follows: + + { + 'English string': + 'Translation string', + 'last key': + } 'last value' + + and you only edit the indented value strings. Note that each key-value + pair needs to be delimited by a comma, but that there shouldn't be a comma + after the last pair (again, just overwrite the template file and you'll be + fine). + + If something doesn't work, or if you're unsure about the formalities you + should check your file with + + + + This will inform you about any missed commas etc. + + + 4. Accented characters + + Depending on which text editor and which file encoding you use you can + directly enter special characters (e.g. Umlaut, accented characters) on + your keyboard. However, I've noticed that some browsers may not display + special characters correctly, even if other browsers do. So it's best to + check your results in several browsers. If you want to be on the safe + side, it's even better to escape these characters using Unicode. + + see: + + + 5. Block specs: + + At this time your translation of block specs will only work + correctly, if the order of formal parameters and their types + are unchanged. Placeholders for inputs (formal parameters) are + indicated by a preceding % prefix and followed by a type + abbreviation. + + For example: + + 'say %s for %n secs' + + can currently not be changed into + + 'say %n secs long %s' + + and still work as intended. + + Similarly + + 'point towards %dst' + + cannot be changed into + + 'point towards %cst' + + without breaking its functionality. + + + 6. Submit + + When you're done, rename the edited file by replacing the "de" part of the + filename with the two-letter ISO 639-1 code for your language, e.g. + + fr - French => lang-fr.js + it - Italian => lang-it.js + pl - Polish => lang-pl.js + pt - Portuguese => lang-pt.js + es - Spanish => lang-es.js + el - Greek => => lang-el.js + + and send it to me for inclusion in the official Snap! distribution. + Once your translation has been included, Your name will the shown in the + "Translators" tab in the "About Snap!" dialog box, and you will be able to + directly launch a translated version of Snap! in your browser by appending + + lang:xx + + to the URL, xx representing your translations two-letter code. + + + 7. Known issues + + In some browsers accents or ornaments located in typographic ascenders + above the cap height are currently (partially) cut-off. + + Enjoy! + -Jens +*/ + +/*global SnapTranslator*/ + +SnapTranslator.dict.de = { + +/* + Special characters: (see ) + + Ä, ä \u00c4, \u00e4 + Ö, ö \u00d6, \u00f6 + Ü, ü \u00dc, \u00fc + ß \u00df +*/ + + // translations meta information + 'language_name': + 'Deutsch', // the name as it should appear in the language menu + 'language_translator': + 'Jens M\u00F6nig', // your name for the Translators tab + 'translator_e-mail': + 'jens@moenig.org', // optional + 'last_changed': + '2013-08-10', // this, too, will appear in the Translators tab + + // GUI + // control bar: + 'untitled': + 'Unbenannt', + 'development mode': + 'Hackermodus', + + // categories: + 'Motion': + 'Bewegung', + 'Looks': + 'Aussehen', + 'Sound': + 'Klang', + 'Pen': + 'Stift', + 'Control': + 'Steuerung', + 'Sensing': + 'F\u00fchlen', + 'Operators': + 'Operatoren', + 'Variables': + 'Variablen', + 'Lists': + 'Listen', + 'Other': + 'Andere', + + // editor: + 'draggable': + 'greifbar', + + // tabs: + 'Scripts': + 'Skripte', + 'Costumes': + 'Kost\u00fcme', + 'Sounds': + 'Kl\u00e4nge', + + // names: + 'Sprite': + 'Objekt', + 'Stage': + 'B\u00fchne', + + // rotation styles: + 'don\'t rotate': + 'nicht drehbar', + 'can rotate': + 'frei drehbar', + 'only face left/right': + 'kann sich nur nach\nlinks/rechts drehen', + + // new sprite button: + 'add a new sprite': + 'ein neues Objekt\nhinzuf\u00fcgen', + + // tab help + 'costumes tab help': + 'Bilder durch hereinziehen von einer anderen\n' + + 'Webseite or vom Computer importieren', + 'import a sound from your computer\nby dragging it into here': + 'Kl\u00e4nge durch hereinziehen importieren', + + // primitive blocks: + + /* + Attention Translators: + ---------------------- + At this time your translation of block specs will only work + correctly, if the order of formal parameters and their types + are unchanged. Placeholders for inputs (formal parameters) are + indicated by a preceding % prefix and followed by a type + abbreviation. + + For example: + + 'say %s for %n secs' + + can currently not be changed into + + 'say %n secs long %s' + + and still work as intended. + + Similarly + + 'point towards %dst' + + cannot be changed into + + 'point towards %cst' + + without breaking its functionality. + */ + + // motion: + 'Stage selected:\nno motion primitives': + 'B\u00fchne ausgew\u00e4hlt:\nkeine Standardbewegungsbl\u00f6cke\n' + + 'vorhanden', + + 'move %n steps': + 'gehe %n Schritte', + 'turn %clockwise %n degrees': + 'drehe %clockwise %n Grad', + 'turn %counterclockwise %n degrees': + 'drehe %counterclockwise %n Grad', + 'point in direction %dir': + 'zeige Richtung %dir', + 'point towards %dst': + 'zeige auf %dst', + 'go to x: %n y: %n': + 'gehe zu x: %n y: %n', + 'go to %dst': + 'gehe zu %dst', + 'glide %n secs to x: %n y: %n': + 'gleite %n Sek. zu x: %n y: %n', + 'change x by %n': + '\u00e4ndere x um %n', + 'set x to %n': + 'setze x auf %n', + 'change y by %n': + '\u00e4ndere y um %n', + 'set y to %n': + 'setze y auf %n', + 'if on edge, bounce': + 'pralle vom Rand ab', + 'x position': + 'x-Position', + 'y position': + 'y-Position', + 'direction': + 'Richtung', + + // looks: + 'switch to costume %cst': + 'ziehe Kost\u00fcm %cst an', + 'next costume': + 'n\u00e4chstes Kost\u00fcm', + 'costume #': + 'Kost\u00fcm Nr.', + 'say %s for %n secs': + 'sage %s f\u00fcr %n Sek.', + 'say %s': + 'sage %s', + 'think %s for %n secs': + 'denke %s f\u00fcr %n Sek.', + 'think %s': + 'denke %s', + 'Hello!': + 'Hallo!', + 'Hmm...': + 'Hmm...', + 'change %eff effect by %n': + '\u00e4ndere %eff -Effekt um %n', + 'set %eff effect to %n': + 'setze %eff -Effekt auf %n', + 'clear graphic effects': + 'schalte Grafikeffekte aus', + 'change size by %n': + '\u00e4ndere Gr\u00f6\u00dfe um %n', + 'set size to %n %': + 'setze Gr\u00f6\u00dfe auf %n %', + 'size': + 'Gr\u00f6\u00dfe', + 'show': + 'anzeigen', + 'hide': + 'verstecken', + 'go to front': + 'komme nach vorn', + 'go back %n layers': + 'gehe %n Ebenen zur\u00fcck', + + 'development mode \ndebugging primitives:': + 'Hackermodus \nDebugging-Bl\u00f6cke', + 'console log %mult%s': + 'schreibe in die Konsole: %mult%s', + 'alert %mult%s': + 'Pop-up: %mult%s', + + // sound: + 'play sound %snd': + 'spiele Klang %snd', + 'play sound %snd until done': + 'spiele Klang %snd ganz', + 'stop all sounds': + 'stoppe alle Kl\u00e4nge', + 'rest for %n beats': + 'spiele Pause f\u00fcr %n Schl\u00e4ge', + 'play note %n for %n beats': + 'spiele Note %n f\u00fcr %n Schl\u00e4ge', + 'change tempo by %n': + '\u00e4ndere Tempo um %n', + 'set tempo to %n bpm': + 'setze Tempo auf %n Schl\u00e4ge/Min.', + 'tempo': + 'Tempo', + + // pen: + 'clear': + 'wische', + 'pen down': + 'Stift runter', + 'pen up': + 'Stift hoch', + 'set pen color to %clr': + 'setze Stiftfarbe auf %clr', + 'change pen color by %n': + '\u00e4ndere Stiftfarbe um %n', + 'set pen color to %n': + 'setze Stiftfarbe auf %n', + 'change pen shade by %n': + '\u00e4ndere Farbst\u00e4rke um %n', + 'set pen shade to %n': + 'setze Farbst\u00e4rke auf %n', + 'change pen size by %n': + '\u00e4ndere Stiftdicke um %n', + 'set pen size to %n': + 'setze Stiftdicke auf %n', + 'stamp': + 'stemple', + + // control: + 'when %greenflag clicked': + 'Wenn %greenflag angeklickt', + 'when %keyHat key pressed': + 'Wenn Taste %keyHat gedr\u00fcckt', + 'when I am clicked': + 'Wenn ich angeklickt werde', + 'when I receive %msgHat': + 'Wenn ich %msgHat empfange', + 'broadcast %msg': + 'sende %msg an alle', + 'broadcast %msg and wait': + 'sende %msg an alle und warte', + 'Message name': + 'Nachricht', + 'message': + 'Nachricht', + 'any message': + 'eine beliebige Nachricht', + 'wait %n secs': + 'warte %n Sek.', + 'wait until %b': + 'warte bis %b', + 'forever %c': + 'fortlaufend %c', + 'repeat %n %c': + 'wiederhole %n mal %c', + 'repeat until %b %c': + 'wiederhole bis %b %c', + 'if %b %c': + 'falls %b %c', + 'if %b %c else %c': + 'falls %b %c sonst %c', + 'report %s': + 'berichte %s', + 'stop block': + 'stoppe diesen Block', + 'stop script': + 'stoppe dieses Skript', + 'stop all %stop': + 'stoppe alles %stop', + 'pause all %pause': + 'pausiere alles %pause', + 'run %cmdRing %inputs': + 'f\u00fchre %cmdRing aus %inputs', + 'launch %cmdRing %inputs': + 'starte %cmdRing %inputs', + 'call %repRing %inputs': + 'rufe %repRing auf %inputs', + 'run %cmdRing w/continuation': + 'f\u00fchre %cmdRing mit Continuation aus', + 'call %cmdRing w/continuation': + 'rufe %cmdRing mit Continuation auf', + 'warp %c': + 'Warp %c', + 'when I start as a clone': + 'Wenn ich geklont werde', + 'create a clone of %cln': + 'klone %cln', + 'myself': + 'mich', + 'delete this clone': + 'entferne diesen Klon', + + // sensing: + 'touching %col ?': + 'ber\u00fchre %col ?', + 'touching %clr ?': + 'ber\u00fchre %clr ?', + 'color %clr is touching %clr ?': + 'Farbe %clr ber\u00fchrt %clr ?', + 'ask %s and wait': + 'frage %s und warte', + 'what\'s your name?': + 'Wie hei\u00dft Du?', + 'answer': + 'Antwort', + 'mouse x': + 'Maus x-Position', + 'mouse y': + 'Maus y-Position', + 'mouse down?': + 'Maustaste gedr\u00fcckt?', + 'key %key pressed?': + 'Taste %key gedr\u00fcckt?', + 'distance to %dst': + 'Entfernung von %dst', + 'reset timer': + 'starte Stoppuhr neu', + 'timer': + 'Stoppuhr', + '%att of %spr': + '%att von %spr', + 'http:// %s': + 'http:// %s', + 'turbo mode?': + 'Turbomodus?', + 'set turbo mode to %b': + 'setze Turbomodus auf %b', + + 'filtered for %clr': + 'nach %clr gefiltert', + 'stack size': + 'Stapelgr\u00f6\u00dfe', + 'frames': + 'Rahmenz\u00e4hler', + + // operators: + '%n mod %n': + '%n modulo %n', + 'round %n': + '%n gerundet', + '%fun of %n': + '%fun von %n', + 'pick random %n to %n': + 'Zufallszahl von %n bis %n', + '%b and %b': + '%b und %b', + '%b or %b': + '%b oder %b', + 'not %b': + 'nicht %b', + 'true': + 'wahr', + 'false': + 'falsch', + 'join %words': + 'verbinde %words', + 'hello': + 'Hallo', + 'world': + 'Welt', + 'letter %n of %s': + 'Zeichen %n von %s', + 'length of %s': + 'L\u00e4nge von %s', + 'unicode of %s': + 'Unicode Wert von %s', + 'unicode %n as letter': + 'Unicode %n als Buchstabe', + 'is %s a %typ ?': + 'ist %s ein(e) %typ ?', + 'is %s identical to %s ?': + 'ist %s identisch mit %s ?', + + 'type of %s': + 'Typ von %s', + + // variables: + 'Make a variable': + 'Neue Variable', + 'Variable name': + 'Variablenname', + 'Script variable name': + 'Skriptvariablenname', + 'Delete a variable': + 'Variable l\u00f6schen', + + 'set %var to %s': + 'setze %var auf %s', + 'change %var by %n': + '\u00e4ndere %var um %n', + 'show variable %var': + 'zeige Variable %var', + 'hide variable %var': + 'verstecke Variable %var', + 'script variables %scriptVars': + 'Skriptvariablen %scriptVars', + + // lists: + 'list %exp': + 'Liste %exp', + '%s in front of %l': + '%s am Anfang von %l', + 'item %idx of %l': + 'Element %idx von %l', + 'all but first of %l': + 'alles au\u00dfer dem ersten von %l', + 'length of %l': + 'L\u00e4nge von %l', + '%l contains %s': + '%l enth\u00e4lt %s', + 'thing': + 'etwas', + 'add %s to %l': + 'f\u00fcge %s zu %l hinzu', + 'delete %ida of %l': + 'entferne %ida aus %l', + 'insert %s at %idx of %l': + 'f\u00fcge %s als %idx in %l ein', + 'replace item %idx of %l with %s': + 'ersetze Element %idx in %l durch %s', + + // other + 'Make a block': + 'Neuer Block', + + // menus + // snap menu + 'About...': + '\u00dcber Snap!...', + 'Reference manual': + 'Handbuch lesen', + 'Snap! website': + 'Snap! Webseite', + 'Download source': + 'Quellcode runterladen', + 'Switch back to user mode': + 'zur\u00fcck zum Benutzermodus', + 'disable deep-Morphic\ncontext menus\nand show user-friendly ones': + 'verl\u00e4sst Morphic', + 'Switch to dev mode': + 'zum Hackermodus wechseln', + 'enable Morphic\ncontext menus\nand inspectors,\nnot user-friendly!': + 'erm\u00f6glicht Morphic Funktionen', + + // project menu + 'Project notes...': + 'Projektanmerkungen...', + 'New': + 'Neu', + 'Open...': + '\u00d6ffnen...', + 'Save': + 'Sichern', + 'Save As...': + 'Sichern als...', + 'Import...': + 'Importieren...', + 'file menu import hint': + 'l\u00e4dt ein exportiertes Projekt,\neine Bibliothek mit ' + + 'Bl\u00f6cken\n' + + 'ein Kost\u00fcm oder einen Klang', + 'Export project as plain text...': + 'Projekt als normalen Text exportieren...', + 'Export project...': + 'Projekt exportieren...', + 'show project data as XML\nin a new browser window': + 'zeigt das Projekt als XML\nin einem neuen Browserfenster an', + 'Export blocks...': + 'Bl\u00f6cke exportieren...', + 'show global custom block definitions as XML\nin a new browser window': + 'zeigt globale Benutzerblockdefinitionen\nals XML im Browser an', + 'Import tools': + 'Tools laden', + 'load the official library of\npowerful blocks': + 'das offizielle Modul mit\nm\u00e4chtigen Bl\u00f6cken laden', + 'Libraries...': + 'Module...', + 'Import library': + 'Modul laden', + + // cloud menu + 'Login...': + 'Anmelden...', + 'Signup...': + 'Benutzerkonto einrichten...', + + // settings menu + 'Language...': + 'Sprache...', + 'Zoom blocks...': + 'Bl\u00f6cke vergr\u00f6\u00dfern...', + 'Blurred shadows': + 'Weiche Schatten', + 'uncheck to use solid drop\nshadows and highlights': + 'abschalten f\u00fcr harte Schatten\nund Beleuchtung', + 'check to use blurred drop\nshadows and highlights': + 'einschalten f\u00fcr harte Schatten\nund Beleuchtung', + 'Zebra coloring': + 'Zebrafarben', + 'check to enable alternating\ncolors for nested blocks': + 'einschalten \u00fcr abwechselnde Farbnuancen\nin Bl\u00f6cken', + 'uncheck to disable alternating\ncolors for nested block': + 'ausschalten verhindert abwechselnde\nFarbnuancen in Bl\u00f6cken', + 'Dynamic input labels': + 'Eingabenbeschriftung', + 'uncheck to disable dynamic\nlabels for variadic inputs': + 'ausschalten verhindert Beschriftung\nvon Mehrfacheingaben', + 'check to enable dynamic\nlabels for variadic inputs': + 'einschalten um Mehrfacheingabefelder\nautomatisch zu beschriften', + 'Prefer empty slot drops': + 'Leere Platzhalter bevorzugen', + 'settings menu prefer empty slots hint': + 'einschalten um leere Platzhalter\nbeim Platzieren von Bl\u00f6cken' + + 'zu bevorzugen', + 'uncheck to allow dropped\nreporters to kick out others': + 'ausschalten um das "Rauskicken"\nvon platzierten Bl\u00f6cken\n' + + 'zu erm\u00f6glichen', + 'Long form input dialog': + 'Ausf\u00fchrlicher Input-Dialog', + 'check to always show slot\ntypes in the input dialog': + 'einschalten, um immer die Datentypen\nim Input-Dialog zu sehen', + 'uncheck to use the input\ndialog in short form': + 'ausschalten f\u00fcr kurzen\nInput-Dialog', + 'Virtual keyboard': + 'Virtuelle Tastatur', + 'uncheck to disable\nvirtual keyboard support\nfor mobile devices': + 'ausschalten um die virtuelle\nTastatur auf mobilen Ger\u00e4ten\n' + + 'zu sperren', + 'check to enable\nvirtual keyboard support\nfor mobile devices': + 'einschalten um die virtuelle\nTastatur auf mobilen Ger\u00e4ten\n' + + 'zu erm\u00f6glichen', + 'Input sliders': + 'Eingabeschieber', + 'uncheck to disable\ninput sliders for\nentry fields': + 'ausschalten um Schieber\nin Eingabefeldern zu verhindern', + 'check to enable\ninput sliders for\nentry fields': + 'einschalten um Schieber\nin Eingabefeldern zu aktivieren', + 'Clicking sound': + 'Akustisches Klicken', + 'uncheck to turn\nblock clicking\nsound off': + 'ausschalten um akustisches\nKlicken zu deaktivieren', + 'check to turn\nblock clicking\nsound on': + 'einschalten um akustisches\nKlicken zu aktivieren', + 'Animations': + 'Animationen', + 'uncheck to disable\nIDE animations': + 'ausschalten um IDE-\nAnimationen zu verhindern', + 'Turbo mode': + 'Turbomodus', + 'check to prioritize\nscript execution': + 'einschalten, um Skripte\nzu priorisieren', + 'uncheck to run scripts\nat normal speed': + 'ausschalten, um Skripte\nnormal auszuf\u00fchren', + 'check to enable\nIDE animations': + 'einschalten um IDE-\nAnimationen zu erlauben', + 'Thread safe scripts': + 'Threadsicherheit', + 'uncheck to allow\nscript reentrance': + 'verhindert, dass unvollendete\nSkripte erneut gestartet werden', + 'check to disallow\nscript reentrance': + 'verhindert, dass unvollendete\nSkripte erneut gestartet werden', + 'Prefer smooth animations': + 'Fixe Framerate', + 'uncheck for greater speed\nat variable frame rates': + 'ausschalten, um Animationen \ndynamischer auszuf\u00fchren', + 'check for smooth, predictable\nanimations across computers': + 'einschalten, damit Animationen\n\u00fcberall gleich laufen', + + // inputs + 'with inputs': + 'mit Eingaben', + 'input names:': + 'Eingaben:', + 'Input Names:': + 'Eingaben:', + 'input list:': + 'Eingabeliste:', + + // context menus: + 'help': + 'Hilfe', + + // palette: + 'hide primitives': + 'Basisbl\u00f6cke ausblenden', + 'show primitives': + 'Basisbl\u00f6cke anzeigen', + + // blocks: + 'help...': + 'Hilfe...', + 'relabel...': + 'Umbenennen...', + 'duplicate': + 'Duplizieren', + 'make a copy\nand pick it up': + 'eine Kopie aufnehmen', + 'only duplicate this block': + 'nur diesen Block duplizieren', + 'delete': + 'L\u00f6schen', + 'script pic...': + 'Skriptbild...', + 'open a new window\nwith a picture of this script': + 'ein neues Browserfenster mit einem\nBild dieses Skripts \u00f6ffnen', + 'ringify': + 'Umringen', + 'unringify': + 'Entringen', + + // custom blocks: + 'delete block definition...': + 'Blockdefinition l\u00f6schen', + 'edit...': + 'Bearbeiten...', + + // sprites: + 'edit': + 'Bearbeiten', + 'detach from': + 'Abtrennen von', + 'detach all parts': + 'Alle Teile abtrennen', + 'export...': + 'Exportieren...', + + // stage: + 'show all': + 'Alles zeigen', + 'pic...': + 'Bild exportieren...', + 'open a new window\nwith a picture of the stage': + 'ein neues Browserfenster mit einem\nBild der B\u00fchne \u00f6ffnen', + + // scripting area + 'clean up': + 'Aufr\u00e4umen', + 'arrange scripts\nvertically': + 'Skripte der Reihe nach\nanordnen', + 'add comment': + 'Anmerkung hinzuf\u00fcgen', + 'undrop': + 'R\u00fcckg\u00e4ngig', + 'undo the last\nblock drop\nin this pane': + 'Setzen des letzten Blocks\nwiderrufen', + 'scripts pic...': + 'Bild aller Scripte...', + 'open a new window\nwith a picture of all scripts': + 'ein neues Browserfenster mit einem\nBild aller Skripte \u00f6ffnen', + 'make a block...': + 'Neuen Block bauen...', + + // costumes + 'rename': + 'Umbenennen', + 'export': + 'Exportieren', + 'rename costume': + 'Kost\u00fcm umbenennen', + + // sounds + 'Play sound': + 'Klang\nabspielen', + 'Stop sound': + 'Klang\nanhalten', + 'Stop': + 'Halt', + 'Play': + 'Los', + 'rename sound': + 'Klang umbenennen', + + // dialogs + // buttons + 'OK': + 'OK', + 'Ok': + 'OK', + 'Cancel': + 'Abbrechen', + 'Yes': + 'Ja', + 'No': + 'Nein', + + // help + 'Help': + 'Hilfe', + + // zoom blocks + 'Zoom blocks': + 'Bl\u00f6cke vergr\u00f6\u00dfern', + 'build': + 'baue', + 'your own': + 'eigene', + 'blocks': + 'Bl\u00f6cke', + 'normal (1x)': + 'normal (1x)', + 'demo (1.2x)': + 'Demo (1.2x)', + 'presentation (1.4x)': + 'Pr\u00e4sentation (1.4x)', + 'big (2x)': + 'gro\u00df (2x)', + 'huge (4x)': + 'riesig (4x)', + 'giant (8x)': + 'gigantisch (8x)', + 'monstrous (10x)': + 'ungeheuerlich (10x)', + + // Project Manager + 'Untitled': + 'Unbenannt', + 'Open Project': + 'Project \u00f6ffnen', + '(empty)': + '(leer)', + 'Saved!': + 'Gesichert!', + 'Delete Project': + 'Projekt l\u00f6schen', + 'Are you sure you want to delete': + 'Wirklich l\u00f6schen?', + 'rename...': + 'Umbenennen...', + + // costume editor + 'Costume Editor': + 'Kost\u00fcmeditor', + 'click or drag crosshairs to move the rotation center': + 'Fadenkreuz anklicken oder bewegen um den Drehpunkt zu setzen', + + // project notes + 'Project Notes': + 'Projektanmerkungen', + + // new project + 'New Project': + 'Neues Projekt', + 'Replace the current project with a new one?': + 'Das aktuelle Projekt durch ein neues ersetzen?', + + // save project + 'Save Project As...': + 'Projekt Sichern Als...', + + // export blocks + 'Export blocks': + 'Bl\u00f6cke exportieren', + 'Import blocks': + 'Bl\u00f6cke importieren', + 'this project doesn\'t have any\ncustom global blocks yet': + 'in diesem Projekt gibt es noch keine\nglobalen Bl\u00f6cke', + 'select': + 'ausw\u00e4hlen', + 'all': + 'alles', + 'none': + 'nichts', + + // variable dialog + 'for all sprites': + 'f\u00fcr alle', + 'for this sprite only': + 'nur f\u00fcr dieses Objekt', + + // block dialog + 'Change block': + 'Block ver\u00e4ndern', + 'Command': + 'Befehl', + 'Reporter': + 'Funktion', + 'Predicate': + 'Pr\u00e4dikat', + + // block editor + 'Block Editor': + 'Blockeditor', + 'Apply': + 'Anwenden', + + // block deletion dialog + 'Delete Custom Block': + 'Block L\u00f6schen', + 'block deletion dialog text': + 'Soll dieser Block mit allen seinen Exemplare\n' + + 'wirklich gel\u00f6scht werden?', + + // input dialog + 'Create input name': + 'Eingabe erstellen', + 'Edit input name': + 'Eingabe bearbeiten', + 'Edit label fragment': + 'Beschriftung bearbeiten', + 'Title text': + 'Beschriftung', + 'Input name': + 'Eingabe', + 'Delete': + 'L\u00f6schen', + 'Object': + 'Objekt', + 'Number': + 'Zahl', + 'Text': + 'Text', + 'List': + 'Liste', + 'Any type': + 'Beliebig', + 'Boolean (T/F)': + 'Boolsch (W/F)', + 'Command\n(inline)': + 'Befehl', + 'Command\n(C-shape)': + 'Befehl\n(C-Form)', + 'Any\n(unevaluated)': + 'Beliebig\n(zitiert)', + 'Boolean\n(unevaluated)': + 'Boolsch\n(zitiert)', + 'Single input.': + 'Einzeleingabe.', + 'Default Value:': + 'Standardwert:', + 'Multiple inputs (value is list of inputs)': + 'Mehrere Eingaben (als Liste)', + 'Upvar - make internal variable visible to caller': + 'Interne Variable au\u00dfen sichtbar machen', + + // About Snap + 'About Snap': + '\u00dcber Snap', + 'Back...': + 'Zur\u00fcck...', + 'License...': + 'Lizenz...', + 'Modules...': + 'Komponenten...', + 'Credits...': + 'Mitwirkende...', + 'Translators...': + '\u00dcbersetzer', + 'License': + 'Lizenz', + 'current module versions:': + 'Komponenten-Versionen', + 'Contributors': + 'Mitwirkende', + 'Translations': + '\u00dcbersetzungen', + + // variable watchers + 'normal': + 'normal', + 'large': + 'gro\u00df', + 'slider': + 'Regler', + 'slider min...': + 'Minimalwert...', + 'slider max...': + 'Maximalwert...', + 'import...': + 'Importieren...', + 'Slider minimum value': + 'Minimalwert des Reglers', + 'Slider maximum value': + 'Maximalwert des Reglers', + + // list watchers + 'length: ': + 'L\u00e4nge: ', + + // coments + 'add comment here...': + 'Anmerkung hier hinzuf\u00fcgen', + + // drow downs + // directions + '(90) right': + '(90) rechts', + '(-90) left': + '(-90) links', + '(0) up': + '(0) oben', + '(180) down': + '(180) unten', + + // collision detection + 'mouse-pointer': + 'Mauszeiger', + 'edge': + 'Kante', + 'pen trails': + 'Malspuren', + + // costumes + 'Turtle': + 'Richtungszeiger', + 'Empty': + 'Leer', + + // graphical effects + 'ghost': + 'Durchsichtigkeit', + + // keys + 'space': + 'Leertaste', + 'up arrow': + 'Pfeil nach oben', + 'down arrow': + 'Pfeil nach unten', + 'right arrow': + 'Pfeil nach rechts', + 'left arrow': + 'Pfeil nach links', + 'a': + 'a', + 'b': + 'b', + 'c': + 'c', + 'd': + 'd', + 'e': + 'e', + 'f': + 'f', + 'g': + 'g', + 'h': + 'h', + 'i': + 'i', + 'j': + 'j', + 'k': + 'k', + 'l': + 'l', + 'm': + 'm', + 'n': + 'n', + 'o': + 'o', + 'p': + 'p', + 'q': + 'q', + 'r': + 'r', + 's': + 's', + 't': + 't', + 'u': + 'u', + 'v': + 'v', + 'w': + 'w', + 'x': + 'x', + 'y': + 'y', + 'z': + 'z', + '0': + '0', + '1': + '1', + '2': + '2', + '3': + '3', + '4': + '4', + '5': + '5', + '6': + '6', + '7': + '7', + '8': + '8', + '9': + '9', + + // messages + 'new...': + 'Neu...', + + // math functions + 'abs': + 'Betrag', + 'floor': + 'Abgerundet', + 'sqrt': + 'Wurzel', + 'sin': + 'sin', + 'cos': + 'cos', + 'tan': + 'tan', + 'asin': + 'asin', + 'acos': + 'acos', + 'atan': + 'atan', + 'ln': + 'ln', + 'e^': + 'e^', + + // data types + 'number': + 'Zahl', + 'text': + 'Text', + 'Boolean': + 'Boole', + 'list': + 'Liste', + 'command': + 'Befehlsblock', + 'reporter': + 'Funktionsblock', + 'predicate': + 'Pr\u00e4dikat', + + // list indices + 'last': + 'letztes', + 'any': + 'beliebiges' +}; diff --git a/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/lang-eo.js b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/lang-eo.js new file mode 100644 index 0000000..1ed7c3e --- /dev/null +++ b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/lang-eo.js @@ -0,0 +1,1077 @@ +/* + + lang-eo.js + + German translation for SNAP! + + written by Jens Mönig + + Copyright (C) 2012 by Jens Mönig + + This file is part of Snap!. + + Snap! is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + + + + Note to Translators: + -------------------- + At this stage of development, Snap! can be translated to any LTR language + maintaining the current order of inputs (formal parameters in blocks). + + Translating Snap! is easy: + + + 1. Download + + Download the sources and extract them into a local folder on your + computer: + + + + Use the German translation file (named 'lang-de.js') as template for your + own translations. Start with editing the original file, because that way + you will be able to immediately check the results in your browsers while + you're working on your translation (keep the local copy of snap.html open + in your web browser, and refresh it as you progress with your + translation). + + + 2. Edit + + Edit the translation file with a regular text editor, or with your + favorite JavaScript editor. + + In the first non-commented line (the one right below this + note) replace "de" with the two-letter ISO 639-1 code for your language, + e.g. + + fr - French => SnapTranslator.dict.fr = { + it - Italian => SnapTranslator.dict.it = { + pl - Polish => SnapTranslator.dict.pl = { + pt - Portuguese => SnapTranslator.dict.pt = { + es - Spanish => SnapTranslator.dict.es = { + el - Greek => => SnapTranslator.dict.el = { + + etc. (see ) + + + 3. Translate + + Then work through the dictionary, replacing the German strings against + your translations. The dictionary is a straight-forward JavaScript ad-hoc + object, for review purposes it should be formatted as follows: + + { + 'English string': + 'English string' + 'last key': + 'last value' + } + + and you only edit the indented value strings. Note that each key-value + pair needs to be delimited by a comma, but that there shouldn't be a comma + after the last pair (again, just overwrite the template file and you'll be + fine). + + If something doesn't work, or if you're unsure about the formalities you + should check your file with + + + + This will inform you about any missed commas etc. + + + 4. Accented characters + + Depending on which text editor and which file encoding you use you can + directly enter special characters (e.g. Umlaut, accented characters) on + your keyboard. However, I've noticed that some browsers may not display + special characters correctly, even if other browsers do. So it's best to + check your results in several browsers. If you want to be on the safe + side, it's even better to escape these characters using Unicode. + + see: + + + 5. Block specs: + + At this time your translation of block specs will only work + correctly, if the order of formal parameters and their types + are unchanged. Placeholders for inputs (formal parameters) are + indicated by a preceding % prefix and followed by a type + abbreviation. + + For example: + + 'say %s for %n secs' + + can currently not be changed into + + 'say %n secs long %s' + + and still work as intended. + + Similarly + + 'point towards %dst' + + cannot be changed into + + 'point towards %cst' + + without breaking its functionality. + + + 6. Submit + + When you're done, rename the edited file by replacing the "de" part of the + filename with the two-letter ISO 639-1 code for your language, e.g. + + fr - French => lang-fr.js + it - Italian => lang-it.js + pl - Polish => lang-pl.js + pt - Portuguese => lang-pt.js + es - Spanish => lang-es.js + el - Greek => => lang-el.js + + and send it to me for inclusion in the official Snap! distribution. + Once your translation has been included, Your name will the shown in the + "Translators" tab in the "About Snap!" dialog box, and you will be able to + directly launch a translated version of Snap! in your browser by appending + + lang:xx + + to the URL, xx representing your translations two-letter code. + + + 7. Known issues + + In some browsers accents or ornaments located in typographic ascenders + above the cap height are currently (partially) cut-off. + + Enjoy! + -Jens +*/ + +/*global SnapTranslator*/ + +SnapTranslator.dict.eo = { + +/* + Special characters: (see ) + + Ĉ, ĉ \u0108, \u0109 + Ĝ, ĝ \u011c, \u011d + Ĥ, ĥ \u0124, \u0125 + Ĵ, ĵ \u0134, \u0135 + Ŝ, ŝ \u015c, \u015d + Ŭ, ŭ \u016c, \u016d +*/ + + // translations meta information + 'language_name': + 'Esperanto', + 'language_translator': + 'Sebastian CYPRYCH', + 'translator_e-mail': + 'scy(ĉe)epf.pl', + 'last_changed': + '2012-11-11', + + // GUI + // control bar: + 'untitled': + 'sentitola', + 'Untitled': + 'sentitola', + 'development mode': + 'programada reĝimo', + + // categories: + 'Motion': + 'Movo', + 'Looks': + 'Aspekto', + 'Sound': + 'Sono', + 'Pen': + 'Skribilo', + 'Control': + 'Regado', + 'Sensing': + 'Sentado', + 'Operators': + 'Operatoroj', + 'Variables': + 'Variabloj', + 'Lists': + 'Listoj', + 'Other': + 'Aliaj', + + // editor: + 'draggable': + 'trenebla', + + // tabs: + 'Scripts': + 'Skriptoj', + 'Costumes': + 'Kostumoj', + 'Sounds': + 'Sonoj', + + // names: + 'Sprite': + 'Objekto', + 'Stage': + 'Scenejo', + + // rotation styles: + 'don\'t rotate': + 'ne turnebla', + 'can rotate': + 'turnebla', + 'only face left/right': + 'nur maldekstren/dekstren', + + // new sprite button: + 'add a new sprite': + 'aldoni novan objekton', + + // tab help + 'costumes tab help': + 'trenu tien bildojn\nel aliaj retpaĝoj aŭ de via komputilo', + 'import a sound from your computer\nby dragging it into here': + 'importu sonon de via komputilo\ntrenante ĝin ĉi tien', + + // primitive blocks: + + /* + Attention Translators: + ---------------------- + At this time your translation of block specs will only work + correctly, if the order of formal parameters and their types + are unchanged. Placeholders for inputs (formal parameters) are + indicated by a preceding % prefix and followed by a type + abbreviation. + + For example: + + 'say %s for %n secs' + + can currently not be changed into + + 'say %n secs long %s' + + and still work as intended. + + Similarly + + 'point towards %dst' + + cannot be changed into + + 'point towards %cst' + + without breaking its functionality. + */ + + // motion: + 'Stage selected:\nno motion primitives': + 'Scenejo elektita:\nneniuj movaj bazelementoj', + 'move %n steps': + 'iri %n paŝojn', + 'turn %clockwise %n degrees': + 'turni %n gradojn %clockwise', + 'turn %counterclockwise %n degrees': + 'turni %n gradojn %counterclockwise', + 'point in direction %dir': + 'celi laŭ direkto %dir', + 'point towards %dst': + 'celi al %dst', + 'go to x: %n y: %n': + 'iri al x: %n y: %n', + 'go to %dst': + 'iri al %dst', + 'glide %n secs to x: %n y: %n': + 'gliti dum %n sek. al x: %n y: %n', + 'change x by %n': + 'ŝanĝi x je %n', + 'set x to %n': + 'ŝanĝi x al %n', + 'change y by %n': + 'ŝanĝi y je %n', + 'set y to %n': + 'ŝanĝi y al %n', + 'if on edge, bounce': + 'resalti de la rando', + 'x position': + 'x pozicio', + 'y position': + 'y pozicio', + 'direction': + 'direkto', + + // looks: + 'switch to costume %cst': + 'ŝanĝi al kostumo %cst', + 'next costume': + 'sekva kostumo', + 'costume #': + 'numero de kostumo', + 'say %s for %n secs': + 'diri %s dum %n sek.', + 'say %s': + 'diri %s', + 'think %s for %n secs': + 'pensi %s dum %n sek.', + 'think %s': + 'pensi %s', + 'Hello!': + 'Saluton!', + 'Hmm...': + 'Hmm...', + 'change %eff effect by %n': + 'ŝanĝi %eff efekton je %n', + 'set %eff effect to %n': + 'ŝanĝi efekton %eff al %n', + 'clear graphic effects': + 'forigi grafikajn efektojn', + 'change size by %n': + 'ŝanĝi grandecon je %n', + 'set size to %n %': + 'ŝanĝi grandecon al %n', + 'size': + 'grandeco', + 'show': + 'montri', + 'hide': + 'kaŝi', + 'go to front': + 'iri antaŭen', + 'go back %n layers': + 'iri %n tavolojn malantaŭen', + + 'development mode \ndebugging primitives:': + 'programada reĝimo \nsencimigadaj bazelementoj:', + 'console log %mult%s': + 'konzola protokolo: %mult%oj', + 'alert %mult%s': + 'averto %mult%oj', + + // sound: + 'play sound %snd': + 'aŭdigi sonon %snd', + 'play sound %snd until done': + 'aŭdigi sonon %snd ĝis finite', + 'stop all sounds': + 'finigi ĉiujn sonojn', + + // pen: + 'clear': + 'forigi desegnaĵon', + 'pen down': + 'malsuprenigi skribilon', + 'pen up': + 'suprenigi skribilon', + 'set pen color to %clr': + 'ŝanĝi skribilokoloron al %clr', + 'change pen color by %n': + 'ŝanĝi skribilokoloron je %n', + 'set pen color to %n': + 'ŝanĝi skribilokoloron al %n', + 'change pen shade by %n': + 'ŝanĝi kolorombron je %n', + 'set pen shade to %n': + 'ŝanĝi kolorombron al %n', + 'change pen size by %n': + 'ŝanĝi skribilodikecon je %n', + 'set pen size to %n': + 'ŝanĝi skribilodikecon al %n', + 'stamp': + 'stampi', + + // control: + 'when %greenflag clicked': + 'Kiam %greenflag estas alklakita', + 'when %keyHat key pressed': + 'Kiam klavo %keyHat estas premita', + 'when I am clicked': + 'Kiam mi estas alklakita', + 'when I receive %msgHat': + 'Kiam mi ricevas %msgHat', + 'broadcast %msg': + 'elsendi %msg al ĉiuj', + 'broadcast %msg and wait': + 'elsendi %msg al ĉiuj kaj atendi', + 'Message name': + 'Mesaĝonomo', + 'wait %n secs': + 'atendi %n sek.', + 'wait until %b': + 'atendi ĝis %b', + 'forever %c': + 'ripeti eterne %c', + 'repeat %n %c': + 'ripeti %n -foje %c', + 'repeat until %b %c': + 'ripeti ĝis %b %c', + 'if %b %c': + 'se %b %c', + 'if %b %c else %c': + 'se %b %c alie %c', + 'report %s': + 'raporti %s', + 'stop block': + 'halti blokon', + 'stop script': + 'halti skripton', + 'stop all %stop': + 'halti ĉiujn %stop', + 'run %cmdRing %inputs': + 'ruli %cmdRing %inputs', + 'launch %cmdRing %inputs': + 'lanĉi %cmdRing %inputs', + 'call %repRing %inputs': + 'voki %repRing %inputs', + 'run %cmdRing w/continuation': + 'ruli %cmdRing %inputs kun daŭrigo', + 'call %cmdRing w/continuation': + 'voki %cmdRing %inputs kun daŭrigo', + 'warp %c': + 'nedisigeble %c', + + // sensing: + 'touching %col ?': + 'tuŝas %col ?', + 'touching %clr ?': + 'tuŝas %clr ?', + 'color %clr is touching %clr ?': + 'koloro %clr tuŝas %clr ?', + 'ask %s and wait': + 'demandi %s kaj atendi', + 'what\'s your name?': + 'Kiu estas via nomo?', + 'answer': + 'respondo', + 'mouse x': + 'musa x-pozicio', + 'mouse y': + 'musa y-pozicio', + 'mouse down?': + 'musklavo estas premita?', + 'key %key pressed?': + 'klavo %key estas premita?', + 'distance to %dst': + 'distanco de %dst', + 'reset timer': + 'nuligi klikhorloĝon', + 'timer': + 'klikhorloĝo', + 'http:// %s': + 'http:// %s', + 'filtered for %clr': + 'filtrita por %clr', + 'stack size': + 'stakokapacito', + 'frames': + 'kadroj', + + // operators: + '%n mod %n': + '%n mod %n', + 'round %n': + 'rondigi %n', + '%fun of %n': + '%fun de %n', + 'pick random %n to %n': + 'elekti stokaston inter %n kaj %n', + '%b and %b': + '%b kaj %b', + '%b or %b': + '%b aŭ %b', + 'not %b': + 'ne %b', + 'true': + 'vero', + 'false': + 'malvero', + 'join %words': + 'kunigi %words', + 'hello': + 'saluton', + 'world': + 'mondo', + 'letter %n of %s': + 'litero %n el %s', + 'length of %s': + 'longeco de %s', + 'unicode of %s': + 'unikodo de %s', + 'unicode %n as letter': + 'unikodo %n kiel litero', + 'is %s a %typ ?': + 'ĉu %s estas %typ ?', + 'type of %s': + 'tipo de %s', + + // variables: + 'Make a variable': + 'Krei variablon', + 'Variable name': + 'Variablonomo', + 'Delete a variable': + 'Forigi variablon', + 'set %var to %s': + 'ŝanĝi %var al %s', + 'change %var by %n': + 'ŝanĝi %var je %n', + 'show variable %var': + 'montri variablon %var', + 'hide variable %var': + 'kaŝi variablon %var', + 'script variables %scriptVars': + 'variabloj de skripto %scriptVars', + + // lists: + 'list %exp': + 'listo %exp', + '%s in front of %l': + '%s estas antaŭ %l', + 'item %idx of %l': + 'elemento %idx el %l', + 'all but first of %l': + 'ĉiuj krom la unua el %l', + 'length of %l': + 'longeco de %l', + '%l contains %s': + '%l enhavas %s', + 'thing': + 'io', + 'add %s to %l': + 'aldoni %s al %l', + 'delete %ida of %l': + 'forigi %ida el %l', + 'insert %s at %idx of %l': + 'enigi %s je %idx en %l', + 'replace item %idx of %l with %s': + 'astataŭi elementon %idx de %l per %s', + + // other + 'Make a block': + 'Krei blokon', + + // menus + // snap menu + 'About...': + 'Pri...', + 'Snap! website': + 'Snap! paĝaro', + 'Download source': + 'Elŝuti fontokodon', + 'Switch back to user mode': + 'Ŝanĝi reen al uzantoreĝimo', + 'disable deep-Morphic\ncontext menus\nand show user-friendly ones': + 'malŝalti deep-Morphic\nkuntekstajn menuojn\nkaj montri la afablajn', + 'Switch to dev mode': + 'Ŝanĝi al programada reĝimo', + 'enable Morphic\ncontext menus\nand inspectors,\nnot user-friendly!': + 'ŝalti Morphic\nkuntekstajn menuojn\nkaj kontrolilojn, \nne la afablajn!', + + // project menu + 'Project notes...': + 'Projektonotoj...', + 'New': + 'Nova', + 'Open...': + 'Malfermi...', + 'Save': + 'Konservi', + 'Save As...': + 'Konservi kiel...', + 'Import...': + 'Importi...', + 'file menu import hint': + 'elŝutu dosieron kun blokoj sonoj aŭ kostumoj', + 'Export project as plain text...': + 'Eksporti projekton en plata teksta formo...', + 'Export project...': + 'Eksporti projekton...', + 'show project data as XML\nin a new browser window': + 'prezenti projekton kiel XML\nen nova fenestro de retumilo', + 'Export blocks...': + 'Eksporti blokojn...', + 'show global custom block definitions as XML\nin a new browser window': + 'prezenti mallokajn difinojn de propraj blokoj kiel XML\nen nova fenestro de retumilo', + 'Delete Project': + 'Forigi projekton', + 'Are you sure you want to delete': + 'Ĉu vi certas ke vi volas forigi', + + // settings menu + 'Language...': + 'Lingvo...', + 'Blurred shadows': + 'Malklaraj ombroj', + 'uncheck to use solid drop\nshadows and highlights': + 'malŝaltu por uzi klarajn\nombrojn kaj emfazojn', + 'check to use blurred drop\nshadows and highlights': + 'ŝaltu por uzi malklarajn\nombrojn kaj emfazojn', + 'Zebra coloring': + 'Zebra kolorigado', + 'check to enable alternating\ncolors for nested blocks': + 'ŝaltu por ebligi diferencigadon\n de koloroj de stake kolektitaj blokoj', + 'uncheck to disable alternating\ncolors for nested block': + 'malŝaltu por malebligi diferencigadon\n de koloroj de stake kolektitaj blokoj', + 'Prefer empty slot drops': + 'Preferas malplenajn malplenajn ingojn', + 'settings menu prefer empty slots hint': + 'ŝaltu por malebligi demetatajn\nraportilojn forŝovi la aliajn', + 'uncheck to allow dropped\nreporters to kick out others': + 'malŝaltu por ebligi demetatajn\nraportilojn forŝovi la aliajn', + 'Long form input dialog': + 'Longa formo de eniga formularo', + 'check to always show slot\ntypes in the input dialog': + 'ŝaltu por uzi la enigan\nformularon en longa formo', + 'uncheck to use the input\ndialog in short form': + 'malŝaltu por uzi la enigan\nformularon en mallonga formo', + 'Virtual keyboard': + 'Virtuala klavaro', + 'uncheck to disable\nvirtual keyboard support\nfor mobile devices': + 'malŝaltu por malebligi\nsubtenon de virtuala klavaro\npor porteblaj aparatoj', + 'check to enable\nvirtual keyboard support\nfor mobile devices': + 'ŝaltu por ebligi\nsubtenon de virtuala klavaro\npor porteblaj aparatoj', + 'Input sliders': + 'Enigaj ŝoviloj', + 'uncheck to disable\ninput sliders for\nentry fields': + 'malŝaltu por malebligi\nenigajn ŝovilojn por\nenigaj kampoj', + 'check to enable\ninput sliders for\nentry fields': + 'ŝaltu por ebligi\nenigajn ŝovilojn por\nenigaj kampoj', + 'Clicking sound': + 'Klakanta sono', + 'uncheck to turn\nblock clicking\nsound off': + 'malŝaltu por malebligi\nklakantan sonon', + 'check to turn\nblock clicking\nsound on': + 'ŝaltu por ebligi\nklakantan sonon', + 'Thread safe scripts': + 'Fadensekuraj skriptoj', + 'uncheck to allow\nscript reentrancy': + 'malŝaltu por ebligi\nreeniron en fadenon', + 'check to disallow\nscript reentrancy': + 'ŝaltu por malebligi\nreeniron en fadenon', + + // inputs + 'with inputs': + 'kun enigoj', + 'input names:': + 'enigonomoj:', + 'Input Names:': + 'Enigonomoj:', + + // context menus: + 'help': + 'helpo', + + // blocks: + 'help...': + 'help...', + 'duplicate': + 'duobligi', + 'make a copy\nand pick it up': + 'krei kopion\nkaj elekt ĝin', + 'delete': + 'forigi', + 'script pic...': + 'bildo de skripto...', + 'open a new window\nwith a picture of this script': + 'malfermi novan fenestron\nkun bildo de ĉi tiu skripto', + 'ringify': + 'procedurigi', + 'unringify': + 'malprocedurigi', + + // custom blocks: + 'delete block definition...': + 'forigi blokodifinon...', + 'edit...': + 'redakti...', + + // sprites: + 'edit': + 'redakti', + 'export...': + 'eksporti...', + + // scripting area + 'clean up': + 'purigi', + 'arrange scripts\nvertically': + 'ordigi skriptojn\nvertikale', + 'add comment': + 'aldoni komenton', + 'make a block...': + 'krei blokon...', + + // costumes + 'rename': + 'alinomi', + 'export': + 'eksporti', + 'rename costume': + 'Alinomi kostumon', + + // sounds + 'Play sound': + 'Aŭdigi sonon', + 'Stop sound': + 'Halti sonon', + 'Stop': + 'Haltigi', + 'Play': + 'Aŭdigi', + + // dialogs + // buttons + 'OK': + 'Bone', + 'Ok': + 'Bone', + 'Cancel': + 'Rezigni', + 'Yes': + 'Jes', + 'No': + 'Ne', + 'Open': + 'Malfermi', + + // help + 'Help': + 'Helpo', + + // costume editor + 'Costume Editor': + 'Kostumoredaktilo', + 'click or drag crosshairs to move the rotation center': + 'klaku aŭ trenu la krucon por movi la turnocentron', + + // project notes + 'Project Notes': + 'Projektonotoj', + + // new project + 'New Project': + 'Nova projekto', + 'Replace the current project with a new one?': + 'Anstataŭi la aktualan projekton per la nova?', + + // open project + 'Open Project': + 'Malfermi projekton', + '(empty)': + '(nenio)', + + // save project + 'Save Project As...': + 'Konservi projekton kiel...', + 'Saved!': + 'Konservita!', + + // export blocks + 'Export blocks': + 'Eksporti blokojn', + 'this project doesn\'t have any\ncustom global blocks yet': + 'ĉi tiu projekto\nhavas ankoraŭ neniun\npropran blokon', + 'select': + 'elekti', + 'all': + 'ĉiujn', + 'none': + 'neniun', + + // variable dialog + 'for all sprites': + 'por ĉiuj objektoj', + 'for this sprite only': + 'nur por ĉi tiu objekto', + + // block dialog + 'Change block': + 'Ŝanĝi blokon', + 'Command': + 'Komando', + 'Reporter': + 'Raportilo', + 'Predicate': + 'Predikato', + + // block editor + 'Block Editor': + 'Blokoredaktilo', + 'Apply': + 'Apliki', + + // block deletion dialog + 'Delete Custom Block': + 'Forigi propran blokon', + 'block deletion dialog text': + 'forigo de la bloko estas ne malfarebla, ĉu vi vere volas ĝin forigi?', + + // input dialog + 'Create input name': + 'Krei enigonomon', + 'Edit input name': + 'Redakti enigonomon', + 'Title text': + 'Teksto de titolo', + 'Input name': + 'Nomo de enigo', + 'Delete': + 'Forigi', + 'Object': + 'Objekto', + 'Number': + 'Nombro', + 'Text': + 'Teksto', + 'List': + 'Listo', + 'Any type': + 'Iu tipo', + 'Boolean (T/F)': + 'Buleo', + 'Command\n(inline)': + 'Komando\n(enlinia)', + 'Command\n(C-shape)': + 'Komando\n(C-forma)', + 'Any\n(unevaluated)': + 'Iu\n(nekalkulita)', + 'Boolean\n(unevaluated)': + 'Buleo\n(nekalkulita)', + 'Single input.': + 'Unuopa enigo.', + 'Default Value:': + 'Defaŭlta valoro:', + 'Multiple inputs (value is list of inputs)': + 'Pluraj enigoj (valoro estas listo de enigoj)', + 'Upvar - make internal variable visible to caller': + 'Fari internan variablon videblan por vokanto', + + // About Snap + 'About Snap': + 'Pri Snap', + 'Back...': + 'Reen...', + 'License...': + 'Licenco...', + 'Modules...': + 'Moduloj...', + 'Credits...': + 'Honoroj...', + 'Translators...': + 'Tradukantoj...', + 'License': + 'Licenco', + 'current module versions:': + 'versioj de aktualaj moduloj:', + 'Contributors': + 'Kontribuantoj', + 'Translations': + 'Tradukoj', + + // variable watchers + 'normal': + 'normala', + 'large': + 'larĝe', + 'slider': + 'ŝovilo', + 'slider min...': + 'ŝovilo min...', + 'slider max...': + 'ŝovilo maks...', + 'Slider minimum value': + 'Minimuma valoro de ŝovilo', + 'Slider maximum value': + 'Maksimuma valoro de ŝovilo', + + // list watchers + 'length: ': + 'longeco: ', + + // coments + 'add comment here...': + 'aldonu komenton ĉi tie...', + + // drow downs + // directions + '(90) right': + '(90) dekstren', + '(-90) left': + '(-90) maldekstren', + '(0) up': + '(0) supren', + '(180) right': + '(180) suben', + + // collision detection + 'mouse-pointer': + 'musmontrilo', + 'edge': + 'rando', + 'pen trails': + 'spuro de skribilo', + + // costumes + 'Turtle': + 'Testudo', + + // graphical effects + 'ghost': + 'diafaneco', + + // keys + 'space': + 'spacetklavo', + 'up arrow': + 'sago supren', + 'down arrow': + 'sago malsupren', + 'right arrow': + 'sago dekstren', + 'left arrow': + 'sago maldekstren', + 'a': + 'a', + 'b': + 'b', + 'c': + 'c', + 'd': + 'd', + 'e': + 'e', + 'f': + 'f', + 'g': + 'g', + 'h': + 'h', + 'i': + 'i', + 'j': + 'j', + 'k': + 'k', + 'l': + 'l', + 'm': + 'm', + 'n': + 'n', + 'o': + 'o', + 'p': + 'p', + 'q': + 'kvo', + 'r': + 'r', + 's': + 's', + 't': + 't', + 'u': + 'u', + 'v': + 'v', + 'w': + 'germana v', + 'x': + 'ikso', + 'y': + 'y', + 'z': + 'z', + '0': + '0', + '1': + '1', + '2': + '2', + '3': + '3', + '4': + '4', + '5': + '5', + '6': + '6', + '7': + '7', + '8': + '8', + '9': + '9', + + // messages + 'new...': + 'nova...', + + // math functions + 'abs': + 'abs', + 'sqrt': + 'radiko', + 'sin': + 'sin', + 'cos': + 'cos', + 'tan': + 'tan', + 'asin': + 'asin', + 'acos': + 'acos', + 'atan': + 'atan', + 'ln': + 'ln', + 'e^': + 'e^', + + // data types + 'number': + 'nombro', + 'text': + 'teksto', + 'Boolean': + 'Buleo', + 'list': + 'listo', + 'command': + 'komando', + 'reporter': + 'raportilo', + 'predicate': + 'predikato', + + // list indices + 'last': + 'lasta', + 'any': + 'iu' +}; diff --git a/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/lang-es.js b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/lang-es.js new file mode 100644 index 0000000..120d519 --- /dev/null +++ b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/lang-es.js @@ -0,0 +1,1124 @@ +/* + + lang-es.js + + Spanish translation for SNAP! + + written by Jens Mönig + + Copyright (C) 2013 by Jens Mönig + + This file is part of Snap!. + + Snap! is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + + + + Note to Translators: + -------------------- + At this stage of development, Snap! can be translated to any LTR language + maintaining the current order of inputs (formal parameters in blocks). + + Translating Snap! is easy: + + + 1. Download + + Download the sources and extract them into a local folder on your + computer: + + + + Use the German translation file (named 'lang-de.js') as template for your + own translations. Start with editing the original file, because that way + you will be able to immediately check the results in your browsers while + you're working on your translation (keep the local copy of snap.html open + in your web browser, and refresh it as you progress with your + translation). + + + 2. Edit + + Edit the translation file with a regular text editor, or with your + favorite JavaScript editor. + + In the first non-commented line (the one right below this + note) replace "de" with the two-letter ISO 639-1 code for your language, + e.g. + + fr - French => SnapTranslator.dict.fr = { + it - Italian => SnapTranslator.dict.it = { + pl - Polish => SnapTranslator.dict.pl = { + pt - Portuguese => SnapTranslator.dict.pt = { + es - Spanish => SnapTranslator.dict.es = { + el - Greek => => SnapTranslator.dict.el = { + + etc. (see ) + + + 3. Translate + + Then work through the dictionary, replacing the German strings against + your translations. The dictionary is a straight-forward JavaScript ad-hoc + object, for review purposes it should be formatted as follows: + + { + 'English string': + 'Translation string', + 'last key': + } 'last value' + + and you only edit the indented value strings. Note that each key-value + pair needs to be delimited by a comma, but that there shouldn't be a comma + after the last pair (again, just overwrite the template file and you'll be + fine). + + If something doesn't work, or if you're unsure about the formalities you + should check your file with + + + + This will inform you about any missed commas etc. + + + 4. Accented characters + + Depending on which text editor and which file encoding you use you can + directly enter special characters (e.g. Umlaut, accented characters) on + your keyboard. However, I've noticed that some browsers may not display + special characters correctly, even if other browsers do. So it's best to + check your results in several browsers. If you want to be on the safe + side, it's even better to escape these characters using Unicode. + + see: + + + 5. Block specs: + + At this time your translation of block specs will only work + correctly, if the order of formal parameters and their types + are unchanged. Placeholders for inputs (formal parameters) are + indicated by a preceding % prefix and followed by a type + abbreviation. + + For example: + + 'say %s for %n secs' + + can currently not be changed into + + 'say %n secs long %s' + + and still work as intended. + + Similarly + + 'point towards %dst' + + cannot be changed into + + 'point towards %cst' + + without breaking its functionality. + + + 6. Submit + + When you're done, rename the edited file by replacing the "de" part of the + filename with the two-letter ISO 639-1 code for your language, e.g. + + fr - French => lang-fr.js + it - Italian => lang-it.js + pl - Polish => lang-pl.js + pt - Portuguese => lang-pt.js + es - Spanish => lang-es.js + el - Greek => => lang-el.js + + and send it to me for inclusion in the official Snap! distribution. + Once your translation has been included, Your name will the shown in the + "Translators" tab in the "About Snap!" dialog box, and you will be able to + directly launch a translated version of Snap! in your browser by appending + + lang:xx + + to the URL, xx representing your translations two-letter code. + + + 7. Known issues + + In some browsers accents or ornaments located in typographic ascenders + above the cap height are currently (partially) cut-off. + + Enjoy! + -Jens +*/ + +/*global SnapTranslator*/ + +SnapTranslator.dict.es = { + +/* + Special characters: (see ) + + Ä, ä \u00c4, \u00e4 + Ö, ö \u00d6, \u00f6 + Ü, ü \u00dc, \u00fc + ß \u00df +*/ + + // translations meta information + 'language_name': + 'Espa\u00F1ol', // the name as it should appear in the language menu + 'language_translator': + 'V\u00EDctor Manuel Muratalla Morales', // your name for the Translators tab + 'translator_e-mail': + 'victor.muratalla@yahoo.com', // optional + 'last_changed': + '2013-03-25', // this, too, will appear in the Translators tab + + // GUI + // control bar: + 'untitled': + 'Sin t\u00EDtulo', + 'development mode': + 'modo de desarrollo', + + // categories: + 'Motion': + 'Movimiento', + 'Looks': + 'Apariencia', + 'Sound': + 'Sonido', + 'Pen': + 'L\u00E1piz', + 'Control': + 'Control', + 'Sensing': + 'Sensores', + 'Operators': + 'Operadores', + 'Variables': + 'Variables', + 'Lists': + 'Listas', + 'Other': + 'Otro', + + // editor: + 'draggable': + 'arrastrable', + + // tabs: + 'Scripts': + 'Objetos', + 'Costumes': + 'Disfraces', + 'Sounds': + 'Sonidos', + + // names: + 'Sprite': + 'Objeto', + 'Stage': + 'Escenario', + + // rotation styles: + 'don\'t rotate': + 'no girar', + 'can rotate': + 'puede girar', + 'only face left/right': + 's\u00F3lo mirar izquierda/derecha', + + // new sprite button: + 'add a new sprite': + 'agregar un nuevo objeto', + + // tab help + 'costumes tab help': + 'importar una foto de otro sitio Webo desde\n' + + 'su ordenador arrastr\u00E1ndolo hasta aqu\u00ED', + 'import a sound from your computer\nby dragging it into here': + 'importar un sonido desde su ordenador\narrastr\u00E1ndolo hasta aqu\u00ED', + + // primitive blocks: + + /* + Attention Translators: + ---------------------- + At this time your translation of block specs will only work + correctly, if the order of formal parameters and their types + are unchanged. Placeholders for inputs (formal parameters) are + indicated by a preceding % prefix and followed by a type + abbreviation. + + For example: + + 'say %s for %n secs' + + can currently not be changed into + + 'say %n secs long %s' + + and still work as intended. + + Similarly + + 'point towards %dst' + + cannot be changed into + + 'point towards %cst' + + without breaking its functionality. + */ + + // motion: + 'Stage selected:\nno motion primitives': + 'Escenario seleccionado:\nno primitivos de movimiento\n' + + 'disponibles', + + 'move %n steps': + 'mover %n pasos', + 'turn %clockwise %n degrees': + 'girar %clockwise %n grados', + 'turn %counterclockwise %n degrees': + 'girar %counterclockwise %n grados', + 'point in direction %dir': + 'apuntar en direcci\u00F3n %dir', + 'point towards %dst': + 'apuntar hacia %dst', + 'go to x: %n y: %n': + 'ir a x: %n y: %n', + 'go to %dst': + 'ir a %dst', + 'glide %n secs to x: %n y: %n': + 'deslizar %n segs a x: %n y: %n', + 'change x by %n': + 'cambiar x por %n', + 'set x to %n': + 'fijar x a %n', + 'change y by %n': + 'cambiar y por %n', + 'set y to %n': + 'fijar y a %n', + 'if on edge, bounce': + 'rebotar si est\u0061 tocando un borde', + 'x position': + 'posici\u00F3n en x', + 'y position': + 'posici\u00F3n en y', + 'direction': + 'direcci\u00F3n', + + // looks: + 'switch to costume %cst': + 'cambiar el disfraz a %cst', + 'next costume': + 'siguiente disfraz', + 'costume #': + '# de disfraz', + 'say %s for %n secs': + 'decir %s por %n segs', + 'say %s': + 'decir %s', + 'think %s for %n secs': + 'pensar %s por %n segs', + 'think %s': + 'pensar %s', + 'Hello!': + '\u00A1Hola!', + 'Hmm...': + 'mmm...', + 'change %eff effect by %n': + 'cambiar %eff efecto por %n', + 'set %eff effect to %n': + 'fijar %eff efecto a %n', + 'clear graphic effects': + 'quitar efectos gr\u00E1ficos', + 'change size by %n': + 'cambiar tama\u00F1o por %n', + 'set size to %n %': + 'fijar tama\u00F1o a %n %', + 'size': + 'tama\u00F1o', + 'show': + 'mostrar', + 'hide': + 'esconder', + 'go to front': + 'enviar al frente', + 'go back %n layers': + 'enviar hacia atr\u00E1s %n capas', + + 'development mode \ndebugging primitives:': + 'modo de desarrollo \nprimitivos de depuraci\u00F3n', + 'console log %mult%s': + 'log de consola: %mult%s', + 'alert %mult%s': + 'alerta: %mult%s', + + // sound: + 'play sound %snd': + 'tocar sonido %snd', + 'play sound %snd until done': + 'tocar sonido %snd y esperar', + 'stop all sounds': + 'detener todos los sonidos', + 'rest for %n beats': + 'silencio por %n pulsos', + 'play note %n for %n beats': + 'tocar nota %n por %n pulsos', + 'change tempo by %n': + 'cambiar tempo por %n', + 'set tempo to %n bpm': + 'fijar tempo a %n', + 'tempo': + 'tempo', + + // pen: + 'clear': + 'borrar', + 'pen down': + 'bajar l\u00E1piz', + 'pen up': + 'subir l\u00E1piz', + 'set pen color to %clr': + 'fijar color de l\u00E1piz a %clr', + 'change pen color by %n': + 'cambiar color de l\u00E1piz por %n', + 'set pen color to %n': + 'fijar color de l\u00E1piz a %n', + 'change pen shade by %n': + 'cambiar intensidad de l\u00E1piz por %n', + 'set pen shade to %n': + 'fijar intensidad de l\u00E1piz a %n', + 'change pen size by %n': + 'cambiar tama\u00F1o de l\u00E1piz por %n', + 'set pen size to %n': + 'fijar tama\u00F1o de l\u00E1piz a %n', + 'stamp': + 'sellar', + + // control: + 'when %greenflag clicked': + 'Al presionar %greenflag', + 'when %keyHat key pressed': + 'Al presionar tecla %keyHat', + 'when I am clicked': + 'Al presionar Objeto', + 'when I receive %msgHat': + 'Al recibir %msgHat', + 'broadcast %msg': + 'enviar mensaje %msg', + 'broadcast %msg and wait': + 'enviar mensaje %msg y esperar', + 'Message name': + 'Nombre de mensaje', + 'wait %n secs': + 'esperar %n segs', + 'wait until %b': + 'esperar hasta que %b', + 'forever %c': + 'por siempre %c', + 'repeat %n %c': + 'repetir %n %c', + 'repeat until %b %c': + 'repetir hasta que %b %c', + 'if %b %c': + 'si %b %c', + 'if %b %c else %c': + 'si %b %c si no %c', + 'report %s': + 'reportar %s', + 'stop block': + 'detener bloque', + 'stop script': + 'detener programa', + 'stop all %stop': + 'detener todo %stop', + 'run %cmdRing %inputs': + 'correr %cmdRing %inputs', + 'launch %cmdRing %inputs': + 'iniciar %cmdRing %inputs', + 'call %repRing %inputs': + 'llamar %repRing %inputs', + 'run %cmdRing w/continuation': + 'correr %cmdRing con continuaci\u00F3n', + 'call %cmdRing w/continuation': + 'llamar %cmdRing con continuaci\u00F3n', + 'warp %c': + 'ejecutar en modo turbo %c', + + // sensing: + 'touching %col ?': + '\u00BFtocando %col ?', + 'touching %clr ?': + '\u00BFtocando el color %clr ?', + 'color %clr is touching %clr ?': + '\u00BFcolor %clr tocando %clr ?', + 'ask %s and wait': + 'preguntar %s y esperar', + 'what\'s your name?': + '\u00BFCu\u00E1l es tu nombre?', + 'answer': + 'respuesta', + 'mouse x': + 'x del rat\u00F3n', + 'mouse y': + 'y del rat\u00F3n', + 'mouse down?': + '\u00BFrat\u00F3n abajo?', + 'key %key pressed?': + '\u00BFtecla %key presionada?', + 'distance to %dst': + 'distancia a %dst', + 'reset timer': + 'reiniciar cron\u00F3metro', + 'timer': + 'cron\u00F3metro', + 'http:// %s': + 'http:// %s', + + 'filtered for %clr': + 'filtrado para %clr', + 'stack size': + 'tama\u00F1o de pila', + 'frames': + 'marcos', + + // operators: + '%n mod %n': + '%n mod %n', + 'round %n': + 'redondear %n', + '%fun of %n': + '%fun de %n', + 'pick random %n to %n': + 'n\u00FAmero al azar entre %n y %n', + '%b and %b': + '%b y %b', + '%b or %b': + '%b o %b', + 'not %b': + 'no %b', + 'true': + 'cierto', + 'false': + 'falso', + 'join %words': + 'unir %words', + 'hello': + 'hola', + 'world': + 'mundo', + 'letter %n of %s': + 'letra %n de %s', + 'length of %s': + 'longitud de %s', + 'unicode of %s': + 'UniC\u00F3digo de %s', + 'unicode %n as letter': + 'UniC\u00F3digo %n como letra', + 'is %s a %typ ?': + '\u00BFes %s un %typ ?', + 'is %s identical to %s ?': + '\u00BFes %s id\u00E9ntico a %s ?', + + 'type of %s': + 'tipo de %s', + + // variables: + 'Make a variable': + 'Crear un variable', + 'Variable name': + 'Nombre de variable', + 'Delete a variable': + 'Borrar un variable', + + 'set %var to %s': + 'fijar %var a %s', + 'change %var by %n': + 'cambiar %var por %n', + 'show variable %var': + 'mostrar variable %var', + 'hide variable %var': + 'esconder variable %var', + 'script variables %scriptVars': + 'variables de programa %scriptVars', + + // lists: + 'list %exp': + 'lista %exp', + '%s in front of %l': + '%s en frente de %l', + 'item %idx of %l': + 'elemento %idx de %l', + 'all but first of %l': + 'todo menos la primera de %l', + 'length of %l': + 'longitud de %l', + '%l contains %s': + '%l contiene %s', + 'thing': + 'cosa', + 'add %s to %l': + 'agregar %s a %l hinzu', + 'delete %ida of %l': + 'borrar %ida de %l', + 'insert %s at %idx of %l': + 'insertar %s en %idx de %l', + 'replace item %idx of %l with %s': + 'remplazar elemento %idx de %l con %s', + + // other + 'Make a block': + 'Crear un bloque', + + // menus + // snap menu + 'About...': + 'Acerca de...', + 'Snap! website': + 'Sitio web de Snap!', + 'Download source': + 'Bajar recurso', + 'Switch back to user mode': + 'Regresar a modo de usuario', + 'disable deep-Morphic\ncontext menus\nand show user-friendly ones': + 'inhabilitar M\u00F3rfica-profunda\nmen\u0075s contextuales\ny mostrar unos f\u0061ciles de utilizar', + 'Switch to dev mode': + 'Cambiar a modo de elaborador', + 'enable Morphic\ncontext menus\nand inspectors,\nnot user-friendly!': + 'habilitar men\u0075s \nM\u00F3rficos contextuales\n e inspectores,\n\u00A1no f\u0061ciles de utilizar! ', + + // project menu + 'Project notes...': + 'Notas del proyecto...', + 'New': + 'Nuevo', + 'Open...': + 'Abrir...', + 'Save': + 'Guardar', + 'Save As...': + 'Guardar como...', + 'Import...': + 'Importar...', + 'file menu import hint': + 'men\u00FA de archivo de importaci\u00F3n indirecta', + 'Export project as plain text...': + 'Exportar proyecto como texto...', + 'Export project...': + 'Exportar proyecto...', + 'show project data as XML\nin a new browser window': + 'mostrar informaci\u00F3n de proyecto como XML\nen una nueva ventana', + 'Export blocks...': + 'Exportar bloques...', + 'show global custom block definitions as XML\nin a new browser window': + 'mostrar definiciones de bloques globales personalizados como XML\nen una nueva ventana', + 'Import tools': + 'Herramientas de importaci\u00F3n', + 'load the official library of\npowerful blocks': + 'cargar la biblioteca oficial de\nbloques poderosos', + + // settings menu + 'Language...': + 'Idioma...', + 'Blurred shadows': + 'Sombras borrosas', + 'uncheck to use solid drop\nshadows and highlights': + 'desmarque para usar sombras s\u00F3lidas \ne iluminaciones', + 'check to use blurred drop\nshadows and highlights': + 'marcar para usar sombras borrosas\ne iluminaciones', + 'Zebra coloring': + 'Coloraci\u00F3n de cebra', + 'check to enable alternating\ncolors for nested blocks': + 'marcar para habilitar alternaci\u00F3n\nde colores para bloques anidados', + 'uncheck to disable alternating\ncolors for nested block': + 'desmarcar para inhabilitar alternaci\u00F3n\nde colores para bloques anidados', + 'Dynamic input labels': + 'Etiquetas de entradas din\u00E1micas', + 'uncheck to disable dynamic\nlabels for variadic inputs': + 'desmarcar para inhabilitar etiquetas\ndin\u00E1micas para entradas varidic', + 'check to enable dynamic\nlabels for variadic inputs': + 'marcar para habilitar etiquetas\ndin\u00E1micas para entradas varidic', + 'Prefer empty slot drops': + 'Preferir ranuras de gotas vac\u00EDas', + 'settings menu prefer empty slots hint': + 'men\u00FA de ajustes prefiere pistas de ranuras vac\u00EDas', + 'uncheck to allow dropped\nreporters to kick out others': + 'desmarcar para permitir reporteros\nca\u00EDdos para echar a otros', + 'Long form input dialog': + 'di\u00E1logo de entradas de forma larga', + 'check to always show slot\ntypes in the input dialog': + 'marcar para siempre mostrar tipos\nde espacios en el di\u00E1logo de insumo', + 'uncheck to use the input\ndialog in short form': + 'desmarcar para usar el di\u00E1logo\nde insumo en forma corta', + 'Virtual keyboard': + 'Teclado virtual', + 'uncheck to disable\nvirtual keyboard support\nfor mobile devices': + 'desmarcar para inhabilitar\nsoporte al teclado virtual\npara dispositivos m\u00F3viles', + 'check to enable\nvirtual keyboard support\nfor mobile devices': + 'marcar para habilitar\nsoporte para el teclado virtual\npara dispositivos m\u00F3viles', + 'Input sliders': + 'Deslizadores de insumo', + 'uncheck to disable\ninput sliders for\nentry fields': + 'desmarcar para inhabilitar\ndeslizadores de insumo para\ncampos de entrada', + 'check to enable\ninput sliders for\nentry fields': + 'marcar para habilitar\ndeslizadores de entrada para\ncampos de entrada', + 'Clicking sound': + 'Sonido de clic', + 'uncheck to turn\nblock clicking\nsound off': + 'desmarcar para encender\nbloquear clic\napagar sonido', + 'check to turn\nblock clicking\nsound on': + 'marcar para encender\nbloque de clic\nencender sonido', + 'Animations': + 'Animaciones', + 'uncheck to disable\nIDE animations': + 'desmarcar para inhabilitar\nanimaciones IDE', + 'check to enable\nIDE animations': + 'marcar para habilitar\nanimaciones IDE', + 'Thread safe scripts': + 'Programas seguros para serie', + 'uncheck to allow\nscript reentrancy': + 'desmarcar para permitir\nreingreso de programa', + 'check to disallow\nscript reentrancy': + 'marcar para no permitir\nreingreso de programa', + + // inputs + 'with inputs': + 'con entradas', + 'input names:': + 'nombres de entradas:', + 'Input Names:': + 'Nombres de entradas:', + 'input list:': + 'lista de entradas:', + + // context menus: + 'help': + 'ayuda', + + // blocks: + 'help...': + 'ayuda...', + 'relabel...': + 'renombrar...', + 'duplicate': + 'duplicar', + 'make a copy\nand pick it up': + 'crear una copia y recogerla', + 'only duplicate this block': + 's\u00F3lo duplicar este bloque', + 'delete': + 'borrar', + 'script pic...': + 'foto de programa...', + 'open a new window\nwith a picture of this script': + 'abrir una nueva ventana\ncon una foto de este programa', + 'ringify': + 'zumbar', + 'unringify': + 'deszumbar', + + // custom blocks: + 'delete block definition...': + 'borrar definici\u00F3n de bloque', + 'edit...': + 'editar...', + + // sprites: + 'edit': + 'editar', + 'export...': + 'exportar...', + + // stage: + 'show all': + 'mostrar todos', + + // scripting area + 'clean up': + 'limpiar', + 'arrange scripts\nvertically': + 'alinear programas\nverticalmente', + 'add comment': + 'agregar comentario', + 'make a block...': + 'crear un bloque...', + + // costumes + 'rename': + 'renombrar', + 'export': + 'exportar', + 'rename costume': + 'renombrar disfraz', + + // sounds + 'Play sound': + 'Tocar sonido', + 'Stop sound': + 'Detener sonido', + 'Stop': + 'Detener', + 'Play': + 'Tocar', + 'rename sound': + 'renombrar sonido', + + // dialogs + // buttons + 'OK': + 'OK', + 'Ok': + 'Ok', + 'Cancel': + 'Cancelar', + 'Yes': + 'Si', + 'No': + 'No', + + // help + 'Help': + 'Ayuda', + + // Project Manager + 'Untitled': + 'Sin T\u00EDtulo', + 'Open Project': + 'Abrir Proyecto', + '(empty)': + '(vacio)', + 'Saved!': + '\u00A1Guardado!', + 'Delete Project': + 'Borrar Proyecto', + 'Are you sure you want to delete': + '\u00BFEst\u00E1s seguro que deseas borrar?', + 'rename...': + 'renombrar...', + + // costume editor + 'Costume Editor': + 'Editor de disfraz', + 'click or drag crosshairs to move the rotation center': + 'da clic o arrastra punto de mira para mover el centro de rotaci\u00F3n', + + // project notes + 'Project Notes': + 'Notas de proyecto', + + // new project + 'New Project': + 'Nuevo Proyecto', + 'Replace the current project with a new one?': + '\u00BFReemplazar este proyecto con uno nuevo?', + + // save project + 'Save Project As...': + 'Guardar Proyecto Como...', + + // export blocks + 'Export blocks': + 'Exportar bloques', + 'Import blocks': + 'Importar bloques', + 'this project doesn\'t have any\ncustom global blocks yet': + 'este proyecto no tiene ning\u00FAn bloque personalizado todab\u00EDa', + 'select': + 'seleccionar', + 'all': + 'todos', + 'none': + 'ninguno', + + // variable dialog + 'for all sprites': + 'para todos los objetos', + 'for this sprite only': + 'para este objeto solamente', + + // block dialog + 'Change block': + 'Cambiar bloque', + 'Command': + 'Comando', + 'Reporter': + 'Reportero', + 'Predicate': + 'Predicado', + + // block editor + 'Block Editor': + 'Bloquear editor', + 'Apply': + 'Aplicar', + + // block deletion dialog + 'Delete Custom Block': + 'Borrar Bloque Personalizado', + 'block deletion dialog text': + 'supreci\u00F3n de bloque de texto de di\u00E1logo', + + // input dialog + 'Create input name': + 'Crear nombre de insumo', + 'Edit input name': + 'Editar nombre de insumo', + 'Edit label fragment': + 'Editar fragmento de etiqueta', + 'Title text': + 'Texto de t\u00EDtulo', + 'Input name': + 'Nombre de insumo', + 'Delete': + 'Borrar', + 'Object': + 'Objeto', + 'Number': + 'N\u00FAmero', + 'Text': + 'Texto', + 'List': + 'Lista', + 'Any type': + 'Cualquier tipo', + 'Boolean (T/F)': + 'Booleano (C/F)', + 'Command\n(inline)': + 'Comando\n(en l\u00EDnea)', + 'Command\n(C-shape)': + 'Comando\n(forma C)', + 'Any\n(unevaluated)': + 'Cualquier\n(sin evaluar)', + 'Boolean\n(unevaluated)': + 'Booleano\n(sin evaluar)', + 'Single input.': + 'Entrada sola.', + 'Default Value:': + 'Valor Predeterminado:', + 'Multiple inputs (value is list of inputs)': + 'M\u00FAltiples entradas (valor es lista de insumos)', + 'Upvar - make internal variable visible to caller': + 'Crear variable interno visible al llamador', + + // About Snap + 'About Snap': + 'Acerca de Snap', + 'Back...': + 'Atr\u00E1s...', + 'License...': + 'Licencia...', + 'Modules...': + 'M\u00F3dulos...', + 'Credits...': + 'Creditos...', + 'Translators...': + 'Traductores', + 'License': + 'Licencia', + 'current module versions:': + 'versiones del m\u00F3dulo actual', + 'Contributors': + 'Contribuidores', + 'Translations': + 'Traducciones', + + // variable watchers + 'normal': + 'normal', + 'large': + 'grande', + 'slider': + 'deslizador', + 'slider min...': + 'm\u00EDnimo de deslizador...', + 'slider max...': + 'm\u00E1ximo de deslizador...', + 'Slider minimum value': + 'm\u00EDnimo valor de deslizador', + 'Slider maximum value': + 'm\u00E1ximo valor de deslizador', + + // list watchers + 'length: ': + 'longitud: ', + + // coments + 'add comment here...': + 'agregar comentario aqu\u00ED', + + // drow downs + // directions + '(90) right': + '(90) derecha', + '(-90) left': + '(-90) izquierda', + '(0) up': + '(0) arriba', + '(180) down': + '(180) abajo', + + // collision detection + 'mouse-pointer': + 'puntero del rat\u00F3n', + 'edge': + 'borde', + 'pen trails': + 'rastro del l\u00E1piz', + + // costumes + 'Turtle': + 'Tortuga', + + // graphical effects + 'ghost': + 'fantasma', + + // keys + 'space': + 'espacio', + 'up arrow': + 'flecha de arriba', + 'down arrow': + 'flecha de abajo', + 'right arrow': + 'flecha derecha', + 'left arrow': + 'flecha izquierda', + 'a': + 'a', + 'b': + 'b', + 'c': + 'c', + 'd': + 'd', + 'e': + 'e', + 'f': + 'f', + 'g': + 'g', + 'h': + 'h', + 'i': + 'i', + 'j': + 'j', + 'k': + 'k', + 'l': + 'l', + 'm': + 'm', + 'n': + 'n', + 'o': + 'o', + 'p': + 'p', + 'q': + 'q', + 'r': + 'r', + 's': + 's', + 't': + 't', + 'u': + 'u', + 'v': + 'v', + 'w': + 'w', + 'x': + 'x', + 'y': + 'y', + 'z': + 'z', + '0': + '0', + '1': + '1', + '2': + '2', + '3': + '3', + '4': + '4', + '5': + '5', + '6': + '6', + '7': + '7', + '8': + '8', + '9': + '9', + + // messages + 'new...': + 'nuevo...', + + // math functions + 'abs': + 'abs', + 'sqrt': + 'ra\u00EDz cuadrada', + 'sin': + 'sin', + 'cos': + 'cos', + 'tan': + 'tan', + 'asin': + 'asin', + 'acos': + 'acos', + 'atan': + 'atan', + 'ln': + 'ln', + 'e^': + 'e^', + + // data types + 'number': + 'n\u00FAmero', + 'text': + 'texto', + 'Boolean': + 'Booleano', + 'list': + 'lista', + 'command': + 'comando', + 'reporter': + 'reportero', + 'predicate': + 'predicado', + + // list indices + 'last': + '\u00FAltimo', + 'any': + 'cualquier' +}; diff --git a/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/lang-fr.js b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/lang-fr.js new file mode 100644 index 0000000..b545185 --- /dev/null +++ b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/lang-fr.js @@ -0,0 +1,1102 @@ +/* + + lang-de.js + + German translation for SNAP! + + written by Jens Mönig + + Copyright (C) 2012 by Jens Mönig + + This file is part of Snap!. + + Snap! is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + + + + Note to Translators: + -------------------- + At this stage of development, Snap! can be translated to any LTR language + maintaining the current order of inputs (formal parameters in blocks). + + Translating Snap! is easy: + + + 1. Download + + Download the sources and extract them into a local folder on your + computer: + + + + Use the German translation file (named 'lang-de.js') as template for your + own translations. Start with editing the original file, because that way + you will be able to immediately check the results in your browsers while + you're working on your translation (keep the local copy of snap.html open + in your web browser, and refresh it as you progress with your + translation). + + + 2. Edit + + Edit the translation file with a regular text editor, or with your + favorite JavaScript editor. + + In the first non-commented line (the one right below this + note) replace "de" with the two-letter ISO 639-1 code for your language, + e.g. + + fr - French => SnapTranslator.dict.fr = { + it - Italian => SnapTranslator.dict.it = { + pl - Polish => SnapTranslator.dict.pl = { + pt - Portuguese => SnapTranslator.dict.pt = { + es - Spanish => SnapTranslator.dict.es = { + el - Greek => => SnapTranslator.dict.el = { + + etc. (see ) + + + 3. Translate + + Then work through the dictionary, replacing the German strings against + your translations. The dictionary is a straight-forward JavaScript ad-hoc + object, for review purposes it should be formatted as follows: + + { + 'English string': + 'Translation string', + 'last key': + } 'last value' + + and you only edit the indented value strings. Note that each key-value + pair needs to be delimited by a comma, but that there shouldn't be a comma + after the last pair (again, just overwrite the template file and you'll be + fine). + + If something doesn't work, or if you're unsure about the formalities you + should check your file with + + + + This will inform you about any missed commas etc. + + + 4. Accented characters + + Depending on which text editor and which file encoding you use you can + directly enter special characters (e.g. Umlaut, accented characters) on + your keyboard. However, I've noticed that some browsers may not display + special characters correctly, even if other browsers do. So it's best to + check your results in several browsers. If you want to be on the safe + side, it's even better to escape these characters using Unicode. + + see: + + + 5. Block specs: + + At this time your translation of block specs will only work + correctly, if the order of formal parameters and their types + are unchanged. Placeholders for inputs (formal parameters) are + indicated by a preceding % prefix and followed by a type + abbreviation. + + For example: + + 'say %s for %n secs' + + can currently not be changed into + + 'say %n secs long %s' + + and still work as intended. + + Similarly + + 'point towards %dst' + + cannot be changed into + + 'point towards %cst' + + without breaking its functionality. + + + 6. Submit + + When you're done, rename the edited file by replacing the "de" part of the + filename with the two-letter ISO 639-1 code for your language, e.g. + + fr - French => lang-fr.js + it - Italian => lang-it.js + pl - Polish => lang-pl.js + pt - Portuguese => lang-pt.js + es - Spanish => lang-es.js + el - Greek => => lang-el.js + + and send it to me for inclusion in the official Snap! distribution. + Once your translation has been included, Your name will the shown in the + "Translators" tab in the "About Snap!" dialog box, and you will be able to + directly launch a translated version of Snap! in your browser by appending + + lang:xx + + to the URL, xx representing your translations two-letter code. + + + 7. Known issues + + In some browsers accents or ornaments located in typographic ascenders + above the cap height are currently (partially) cut-off. + + Enjoy! + -Jens +*/ + +/*global SnapTranslator*/ + +SnapTranslator.dict.fr = { + +/* + Special characters: (see ) + + Ä, ä \u00c4, \u00e4 + Ö, ö \u00d6, \u00f6 + Ü, ü \u00dc, \u00fc + ß \u00df +*/ + + // translations meta information + 'language_name': + 'Fran\u00E7ais', // the name as it should appear in the language menu + 'language_translator': + 'Jean-Jacques Valliet - Mark Rafter', // your name for the Translators tab + 'translator_e-mail': + 'i.scool@mac.com', // optional + 'last_changed': + '2012-11-27', // this, too, will appear in the Translators tab + + // GUI + // control bar: + 'untitled': + 'Sans Titre', + 'development mode': + 'mode de d\u00E9veloppeur', + + // categories: + 'Motion': + 'Mouvement', + 'Looks': + 'Apparence', + 'Sound': + 'Sons', + 'Pen': + 'Stylo', + 'Control': + 'Contr\u00F4les', + 'Sensing': + 'Capteurs', + 'Operators': + 'Op\u00E9rateurs', + 'Variables': + 'Variables', + 'Lists': + 'Listes', + 'Other': + 'Autres', + + // editor: + 'draggable': + 'd\u00E9pla\u00E7able avec la souris', + + // tabs: + 'Scripts': + 'Scripts', + 'Costumes': + 'Costumes', + 'Sounds': + 'Sons', + + // names: + 'Sprite': + 'Lutin', + 'Stage': + 'Sc\u00E8ne', + + // rotation styles: + 'don\'t rotate': + 'le lutin ne pivote pas \nautour de son centre de rotation', + 'can rotate': + 'le lutin pivote \nautour de son centre de rotation', + 'only face left/right': + 'le lutin reste en position horizontale \nsoit vers la gauche soit vers la droite ', + + // new sprite button: + 'add a new sprite': + 'ajouter un nouveau lutin', + + // tab help + 'costumes tab help': + 'Importer une image depuis votre ordinateur ou une page web \npar un presser-glisser-d\u00E9poser dans l\u0027aire des costumes', + 'import a sound from your computer\nby dragging it into here': + 'Importer un son depuis votre ordinateur \npar un presser-glisser-d\u00E9poser dans l\u0027aire des sons', + + // primitive blocks: + + /* + Attention Translators: + ---------------------- + At this time your translation of block specs will only work + correctly, if the order of formal parameters and their types + are unchanged. Placeholders for inputs (formal parameters) are + indicated by a preceding % prefix and followed by a type + abbreviation. + + For example: + + 'say %s for %n secs' + + can currently not be changed into + + 'say %n secs long %s' + + and still work as intended. + + Similarly + + 'point towards %dst' + + cannot be changed into + + 'point towards %cst' + + without breaking its functionality. + */ + + // motion: + 'Stage selected:\nno motion primitives': + 'Stage selected:\nno motion primitives' + + 'disponible', + + 'move %n steps': + 'avancer de %n pas', + 'turn %clockwise %n degrees': + 'tourner de %n degr\u00E9s %clockwise' , + 'turn %counterclockwise %n degrees': + 'tourner de %n degr\u00E9s %counterclockwise', + 'point in direction %dir': + 'se diriger en faisant un angle de %dir', + 'point towards %dst': + 'se diriger vers %dst', + 'go to x: %n y: %n': + 'aller \u00E0 x: %n y: %n', + 'go to %dst': + 'aller \u00E0 %dst', + 'glide %n secs to x: %n y: %n': + 'glisser en %n sec. \u00E0 x: %n y: %n', + 'change x by %n': + 'ajouter %n \u00E0 x', + 'set x to %n': + 'donner la valeur %n \u00E0 x', + 'change y by %n': + 'ajouter %n \u00E0 y', + 'set y to %n': + 'donner la valeur %n \u00E0 y', + 'if on edge, bounce': + 'rebondir si le bord est atteint', + 'x position': + 'position x', + 'y position': + 'position y', + 'direction': + 'direction', + + // looks: + 'switch to costume %cst': + 'basculer sur le costume %cst', + 'next costume': + 'costume suivant', + 'costume #': + 'costume n\u00B0', + 'say %s for %n secs': + 'dire %s pendant %n sec.', + 'say %s': + 'dire %s', + 'think %s for %n secs': + 'penser %s pendant %n sec.', + 'think %s': + 'penser %s', + 'Hello!': + 'Salut!', + 'Hmm...': + 'Mmmh...', + 'change %eff effect by %n': + 'ajouter \u00E0 l\u0027effet %eff %n', + 'set %eff effect to %n': + 'mettre l\u0027effet %eff \u00E0 %n', + 'clear graphic effects': + 'annuler les effets graphiques', + 'change size by %n': + 'ajouter %n \u00E0 la taille', + 'set size to %n %': + 'choisir %n % de la taille initiale', + 'size': + 'taille', + 'show': + 'montrer', + 'hide': + 'cacher', + 'go to front': + 'envoyer au premier plan', + 'go back %n layers': + 'd\u00E9placer de %n plan arri\u00E8re', + + 'development mode \ndebugging primitives:': + 'mode d\u00E9veloppement \ndebugging primitives:', + 'console log %mult%s': + 'console log %mult%s', + 'alert %mult%s': + 'Pop-up: %mult%s', + + // sound: + 'play sound %snd': + 'jouer le son %snd', + 'play sound %snd until done': + 'jouer le son %snd jusqu\u0027au bout', + 'stop all sounds': + 'arr\u00EAter tous les sons', + 'rest for %n beats': + 'faire une pause pour %n temps', + 'play note %n for %n beats': + 'jouer la note %n pour %n temps', + 'change tempo by %n': + 'ajouter %n au tempo', + 'set tempo to %n bpm': + 'choisir le tempo \u00E0 %n bpm', + 'tempo': + 'tempo', + + // pen: + 'clear': + 'effacer tout', + 'pen down': + 'stylo en position d\u0027\u00EAcriture', + 'pen up': + 'relever le stylo', + 'set pen color to %clr': + 'mettre la couleur %clr pour le stylo', + 'change pen color by %n': + 'ajouter %n \u00E0 la couleur du stylo', + 'set pen color to %n': + 'choisir la couleur %n pour le stylo', + 'change pen shade by %n': + 'ajouter %n \u00E0 l\u0027intensit\u00E9 du stylo ', + 'set pen shade to %n': + 'choisir l\u0027intensit\u00E9 %n pour le stylo', + 'change pen size by %n': + 'ajouter %n \u00E0 la taille du stylo ', + 'set pen size to %n': + 'choisir la taille %n pour le stylo', + 'stamp': + 'estampiller', + + // control: + 'when %greenflag clicked': + 'Quand %greenflag est press\u00E9', + 'when %keyHat key pressed': + 'Quand %keyHat est press\u00E9', + 'when I am clicked': + 'Quand je suis press\u00E9 ', + 'when I receive %msgHat': + 'Quand je re\u00E7ois %msgHat', + 'broadcast %msg': + 'envoyer \u00E0 tous %msg', + 'broadcast %msg and wait': + 'envoyer \u00E0 tous %msg et attendre', + 'Message name': + 'Nom du message', + 'wait %n secs': + 'attends %n sec.', + 'wait until %b': + 'attendre jusqu\u0027\u00E0 %b', + 'forever %c': + 'r\u00E9p\u00E9ter ind\u00E9finiment %c', + 'repeat %n %c': + 'r\u00E9p\u00E9ter %n fois %c', + 'repeat until %b %c': + 'r\u00E9p\u00E9ter ind\u00E9finiment si %b %c', + 'if %b %c': + 'si %b %c', + 'if %b %c else %c': + 'si %b %c sinon %c', + 'report %s': + 'rapporte %s', + 'stop block': + 'arr\u00EAter le bloc', + 'stop script': + 'arr\u00EAter le script', + 'stop all %stop': + 'arr\u00EAter tout %stop', + 'run %cmdRing %inputs': + 'ex\u00E9cute %cmdRing %inputs', + 'launch %cmdRing %inputs': + 'lance %cmdRing %inputs', + 'call %repRing %inputs': + 'appelle %repRing %inputs', + 'run %cmdRing w/continuation': + 'run %cmdRing w/continuation', + 'call %cmdRing w/continuation': + 'call %cmdRing w/continuation', + 'warp %c': + 'Warp %c', + + // sensing: + 'touching %col ?': + ' %col touch\u00E9?', + 'touching %clr ?': + ' couleur %clr touch\u00E9e?', + 'color %clr is touching %clr ?': + 'couleur %clr touche %clr ?', + 'ask %s and wait': + 'demander %s et attendre', + 'what\'s your name?': + 'Quel est ton nom?', + 'answer': + 'r\u00E9ponse', + 'mouse x': + 'souris x', + 'mouse y': + 'souris y', + 'mouse down?': + 'souris press\u00E9e?', + 'key %key pressed?': + 'touche %key press\u00E9e?', + 'distance to %dst': + 'distance de %dst', + 'reset timer': + 'r\u00E9initialiser le chronom\u00E8tre', + 'timer': + 'chronom\u00E8tre', + 'http:// %s': + 'http:// %s', + + 'filtered for %clr': + 'filtr\u00E9 pour %clr ', + 'stack size': + 'taille de la pile', + 'frames': + 'cadres', + + // operators: + '%n mod %n': + '%n mod %n', + 'round %n': + 'arrondi de %n', + '%fun of %n': + '%fun appliqu\u00E9 \u00E0 %n', + 'pick random %n to %n': + 'nombre al\u00E9atoire entre %n et %n', + '%b and %b': + '%b et %b', + '%b or %b': + '%b ou %b', + 'not %b': + 'non %b', + 'true': + 'vrai', + 'false': + 'faux', + 'join %words': + 'regroupe %words', + 'hello': + 'Bonjour', + 'world': + 'Monde', + 'letter %n of %s': + 'lettre %n de %s', + 'length of %s': + 'longueur de %s', + 'unicode of %s': + 'valeur unicode de %s', + 'unicode %n as letter': + 'unicode %n comme lettre', + 'is %s a %typ ?': + 'est %s un(e) %typ ?', + + 'type of %s': + 'type de %s', + + // variables: + 'Make a variable': + 'Nouvelle variable', + 'Variable name': + 'Nom de la variable', + 'Delete a variable': + 'Supprimer une variable', + + 'set %var to %s': + '%var prend la valeur %s', + 'change %var by %n': + 'ajouter \u00E0 %var %n ', + 'show variable %var': + 'afficher la variable %var', + 'hide variable %var': + 'cacher la variable %var', + 'script variables %scriptVars': + 'script variables %scriptVars', + + // lists: + 'list %exp': + 'liste %exp', + '%s in front of %l': + '%s au d\u00E9but de %l', + 'item %idx of %l': + '\u00E9l\u00E9ment %idx de %l', + 'all but first of %l': + 'tous sauf le premier de %l', + 'length of %l': + 'longueur de %l', + '%l contains %s': + '%l contient %s', + 'thing': + 'qqchose', + 'add %s to %l': + 'ajouter %s \u00E0 %l', + 'delete %ida of %l': + 'supprimer l\u0027\u00E9l\u00E9ment %ida de %l', + 'insert %s at %idx of %l': + 'ins\u00E9rer %s en position %idx de %l', + 'replace item %idx of %l with %s': + 'remplacer l\u0027\u00E9l\u00E9ment %idx de %l par %s', + + // other + 'Make a block': + 'Nouveau bloc', + + // menus + // snap menu + 'About...': + 'A propos de Snap!...', + 'Snap! website': + 'Snap! le site web', + 'Download source': + 'T\u00E9l\u00E9charger le code source', + 'Switch back to user mode': + 'Revenir en mode utilisateur', + 'disable deep-Morphic\ncontext menus\nand show user-friendly ones': + 'd\u00E9sactiver la fonction morphic', + 'Switch to dev mode': + 'Passer en mode d\u00E9velopper', + 'enable Morphic\ncontext menus\nand inspectors,\nnot user-friendly!': + 'activer la fonction morphic', + + // project menu + 'Project notes...': + 'Notes du projet...', + 'New': + 'Nouveau', + 'Open...': + 'Ouvrir...', + 'Save': + 'Sauvegarder', + 'Save As...': + 'Sauvegarder sous...', + 'Import...': + 'Importer...', + 'file menu import hint': + 'importer un projet export\u00E9,\nune biblioth\u00E8que de ' + + 'blocs\n' + + 'un costume ou un son', + 'Export project as plain text...': + 'Exporter le projet comme texte...', + 'Export project...': + 'Exporter le projet...', + 'show project data as XML\nin a new browser window': + 'ouvrir le projet au format XML\ndans une nouvelle fen\u00EAtre de votre navigateur', + 'Export blocks...': + 'Exporter les blocs ', + 'show global custom block definitions as XML\nin a new browser window': + 'montrer les d\u00E9finitions de bloc global personnalis\u00E9 au format XML \ndans une nouvelle fen\u00EAtre de navigateur', + + // settings menu + 'Language...': + 'Langue...', + 'Blurred shadows': + 'Ombres floues', + 'uncheck to use solid drop\nshadows and highlights': + 'D\u00E9cocher pour utiliser des rehauts et des ombres \n port\u00E9es floues', + 'check to use blurred drop\nshadows and highlights': + 'Cocher pour utiliser des rehauts et des ombres \n port\u00E9es pleines', + 'Zebra coloring': + 'Colorations altern\u00E9es', + 'check to enable alternating\ncolors for nested blocks': + 'Cocher pour activer des couleurs altern\u00E9es \n pour les blocs embo\u00EEt\u00E9s', + 'uncheck to disable alternating\ncolors for nested block': + 'D\u00E9cocher pour d\u00E9sactiver des couleurs altern\u00E9es \n pour les blocs embo\u00EEt\u00E9s', + 'Prefer empty slot drops': + 'Pr\u00E9f\u00E9rer des entr\u00E9es vides', + 'settings menu prefer empty slots hint': + 'Cocher pour pr\u00E9f\u00E9rer des entr\u00E9es vides \n' + + 'lors du glisser-d\u00E9poser d\u0027un reporter', + 'uncheck to allow dropped\nreporters to kick out others': + 'D\u00E9cocher pour ne pas pr\u00E9f\u00E9rer des entr\u00E9es vides \n' + + 'lors du glisser-d\u00E9poser d\u0027un reporter', + 'Long form input dialog': + 'Bo\u00EEte d\u0027entr\u00E9e en mode d\u00E9taill\u00E9', + 'check to always show slot\ntypes in the input dialog': + 'Cocher pour toujours ouvrir la bo\u00EEte de dialogue \nd\u0027entr\u00E9e en mode d\u00E9taill\u00E9 : avec tous les types de blocs', + 'uncheck to use the input\ndialog in short form': + 'D\u00E9cocher pour utiliser la bo\u00EEte de dialogue \nd\u0027entr\u00E9e en mode simple ', + 'Virtual keyboard': + 'Clavier virtuel', + 'uncheck to disable\nvirtual keyboard support\nfor mobile devices': + 'D\u00E9cocher pour d\u00E9sactiver le clavier virtuel pour \nles tablettes et smartphones : mobile devices ', + 'check to enable\nvirtual keyboard support\nfor mobile devices': + 'Cocher pour activer le clavier virtuel pour \nles tablettes et smartphones : mobile devices ', + 'Input sliders': + 'Entr\u00E9e curseurs', + 'uncheck to disable\ninput sliders for\nentry fields': + 'D\u00E9cocher pour d\u00E9sactiver le curseur coulissant \ndans le champ de saisie', + 'check to enable\ninput sliders for\nentry fields': + 'Cocher pour activer un curseur coulissant \ndans le champ de saisie ', + 'Clicking sound': + 'Cliquetis', + 'uncheck to turn\nblock clicking\nsound off': + 'D\u00E9cocher pour d\u00E9sactiver le cliquetis \n' + +'lors de l\u0027embo\u00EEtement des blocs' , + 'check to turn\nblock clicking\nsound on': + 'Cocher pour activer le cliquetis \n' + +'lors de l\u0027embo\u00EEtement des blocs', + 'Thread safe scripts': + 'Scripts thread-safe', + 'uncheck to allow\nscript reentrancy': + 'D\u00E9cocher pour permettre\n des scripts r\u00E9entrants', + 'check to disallow\nscript reentrancy': + 'Cocher pour interdire\n des scripts r\u00E9entrants', + // inputs + 'with inputs': + 'avec entr\u00E9es', + 'input names:': + 'renseigner un nom:', + 'Input Names:': + 'renseigner un nom:', + + // context menus: + 'help': + 'Aide', + + // blocks: + 'help...': + 'Aide...', + 'duplicate': + 'dupliquer', + 'make a copy\nand pick it up': + 'faire une copie\n et le d\u00E9placer', + 'delete': + 'supprimer', + 'script pic...': + 'image du script...', + 'open a new window\nwith a picture of this script': + 'ouvrir une nouvelle fen\u00EAtre avec une \nimage .png de ce script', + 'ringify': + 'entourer', + 'unringify': + 'd\u00E9tourer', + + // custom blocks: + 'delete block definition...': + 'supprimer les d\u00E9finitions de bloc', + 'edit...': + '\u00E9diter...', + + // sprites: + 'edit': + '\u00E9diter', + 'export...': + 'exporter...', + + // scripting area + 'clean up': + 'effacer', + 'arrange scripts\nvertically': + 'arrange scripts\nvertically', + 'add comment': + 'ajouter un commentaire', + 'make a block...': + 'cr\u00E9er un nouveau bloc...', + + // costumes + 'rename': + 'renommer', + 'export': + 'exporter', + 'rename costume': + 'renommer un costume', + + // sounds + 'Play sound': + 'jouer un son', + 'Stop sound': + 'arr\u00EAter un son', + 'Stop': + 'arr\u00EAter', + 'Play': + 'jouer', + 'rename sound': + 'renommer un son', + + // dialogs + // buttons + 'OK': + 'OK', + 'Ok': + 'Ok', + 'Cancel': + 'Annuler', + 'Yes': + 'Oui', + 'No': + 'Non', + + // help + 'Help': + 'Aide', + + // Project Manager + 'Untitled': + 'Sans titre', + 'Open Project': + 'Ouvrir un projet', + '(empty)': + '(vide)', + 'Saved!': + 'Enregistr\u00EA !', + 'Delete Project': + 'Supprimer un projet', + 'Are you sure you want to delete': + 'Est ce que vous voulez le supprimer?', + 'rename...': + 'Renommer', + + // costume editor + 'Costume Editor': + '\u00EAditeur de costumes', + 'click or drag crosshairs to move the rotation center': + 'cliquez ou faites d\u00EAfiler la ligne de mire pour d\u00EAfinir le centre de rotation du costume', + + // project notes + 'Project Notes': + 'Notes du projet', + + // new project + 'New Project': + 'Nouveau projet', + 'Replace the current project with a new one?': + 'Remplacer le projet actuel par un nouveau?', + + // open project + 'Open Projekt': + 'Ouvrir un projet', + + // save project + 'Save Project As...': + 'Sauvegarder un projet sous...', + + // export blocks + 'Export blocks': + 'exporter des blocs', + 'this project doesn\'t have any\ncustom global blocks yet': + 'ce projet ne contient pas \nde bloc global personnalis\u00E9', + 'select': + 's\u00E9lectionner', + 'all': + 'tout', + 'none': + 'aucun', + + // variable dialog + 'for all sprites': + 'pour tous les lutins', + 'for this sprite only': + 'pour ce lutin uniquement', + + // block dialog + 'Change block': + 'Changer le bloc', + 'Command': + 'Commande', + 'Reporter': + 'Reporter', + 'Predicate': + 'Pr\u00E9dicat', + + // block editor + 'Block Editor': + '\u00C9diteur de bloc', + 'Apply': + 'Appliquer', + + // block deletion dialog + 'Delete Custom Block': + 'Effacer le bloc personnalis\u00E9', + 'block deletion dialog text': + 'Etes-vous s\u00FBr de supprimer ce bloc personnalis\u00E9 \net ' + + 'toutes ces instances?', + + // input dialog + 'Create input name': + 'Cr\u00E9er le nom de l\u0027entr\u00E9e', + 'Edit input name': + '\u00C9diter le nom de l\u0027entr\u00E9e', + 'Edit label fragment': + '\u00C9diter le fragment du label', + 'Title text': + 'Texte du titre', + 'Input name': + 'Nom de l\u0027entr\u00E9e', + 'Delete': + 'Supprimer', + 'Object': + 'Objet', + 'Number': + 'Nombre', + 'Text': + 'Texte', + 'List': + 'Liste', + 'Any type': + 'Tout type', + 'Boolean (T/F)': + 'Bool\u00E9en (V/F)', + 'Command\n(inline)': + 'Commande\n(en ligne)', + 'Command\n(C-shape)': + 'Commande\n(en forme de C)', + 'Any\n(unevaluated)': + 'Tout type\n(non \u00E9valu\u00E9e)', + 'Boolean\n(unevaluated)': + 'Bool\u00E9en\n(non \u00E9valu\u00E9e)', + 'Single input.': + 'Entr\u00E9e unique.', + 'Default Value:': + 'Valeur par d\u00E9faut:', + 'Multiple inputs (value is list of inputs)': + 'Entr\u00E9es multiples (la valeur est une liste des entr\u00E9es)', + 'Upvar - make internal variable visible to caller': + 'Upvar - Rendre la variable interne visible pour l\u0027appelant', + // About Snap + 'About Snap': + 'A propos de Snap', + 'Back...': + 'Retour...', + 'License...': + 'Licence...', + 'Modules...': + 'Modules...', + 'Credits...': + 'Contributeurs...', + 'Translators...': + 'Traducteurs', + 'License': + 'License', + 'current module versions:': + 'Versions du module courant', + 'Contributors': + 'Contributeurs', + 'Translations': + 'Traductions', + + // variable watchers + 'normal': + 'normal', + 'large': + 'grand', + 'slider': + 'curseur', + 'slider min...': + 'min...', + 'slider max...': + 'max...', + 'Slider minimum value': + 'Valeur minimale du curseur', + 'Slider maximum value': + 'Valeur maximale du curseur', + + // list watchers + 'length: ': + 'Longueur: ', + + // coments + 'add comment here...': + 'ajoute un commentaire ici', + + // drow downs + // directions + '(90) right': + '(90) \u00E0 droite', + '(-90) left': + '(-90) \u00E0 gauche', + '(0) up': + '(0) vers le haut', + '(180) right': + '(180) vers le bas', + + // collision detection + 'mouse-pointer': + 'pointeur souris', + 'edge': + 'bord', + 'pen trails': + 'traces de stylo', + + // costumes + 'Turtle': + 'Pointeur', + + // graphical effects + 'ghost': + 'transparence', + + // keys + 'space': + 'espace', + 'up arrow': + 'fl\u00E8che vers le haut', + 'down arrow': + 'fl\u00E8che vers le bas', + 'right arrow': + 'fl\u00E8che vers la droite', + 'left arrow': + 'fl\u00E8che vers la gauche', + 'a': + 'a', + 'b': + 'b', + 'c': + 'c', + 'd': + 'd', + 'e': + 'e', + 'f': + 'f', + 'g': + 'g', + 'h': + 'h', + 'i': + 'i', + 'j': + 'j', + 'k': + 'k', + 'l': + 'l', + 'm': + 'm', + 'n': + 'n', + 'o': + 'o', + 'p': + 'p', + 'q': + 'q', + 'r': + 'r', + 's': + 's', + 't': + 't', + 'u': + 'u', + 'v': + 'v', + 'w': + 'w', + 'x': + 'x', + 'y': + 'y', + 'z': + 'z', + '0': + '0', + '1': + '1', + '2': + '2', + '3': + '3', + '4': + '4', + '5': + '5', + '6': + '6', + '7': + '7', + '8': + '8', + '9': + '9', + + // messages + 'new...': + 'nouveau...', + + // math functions + 'abs': + 'v. absolue', + 'sqrt': + 'sqrt', + 'sin': + 'sin', + 'cos': + 'cos', + 'tan': + 'tan', + 'asin': + 'asin', + 'acos': + 'acos', + 'atan': + 'atan', + 'ln': + 'ln', + 'e^': + 'e^', + + // data types + 'number': + 'nombre', + 'text': + 'texte', + 'Boolean': + 'bool\u00E9en', + 'list': + 'liste', + 'command': + 'bloc de commande', + 'reporter': + 'bloc reporter', + 'predicate': + 'pr\u00E9dicat', + + // list indices + 'last': + 'dernier', + 'any': + 'n\u0027importe quel' +}; diff --git a/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/lang-it.js b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/lang-it.js new file mode 100644 index 0000000..44111aa --- /dev/null +++ b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/lang-it.js @@ -0,0 +1,1194 @@ +/* + + lang-it.js + + Italian translation for SNAP! + + written by Jens Mönig + + Copyright (C) 2012 by Jens Mönig + + This file is part of Snap!. + + Snap! is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + + + + Note to Translators: + -------------------- + At this stage of development, Snap! can be translated to any LTR language + maintaining the current order of inputs (formal parameters in blocks). + + Translating Snap! is easy: + + + 1. Download + + Download the sources and extract them into a local folder on your + computer: + + + + Use the German translation file (named 'lang-de.js') as template for your + own translations. Start with editing the original file, because that way + you will be able to immediately check the results in your browsers while + you're working on your translation (keep the local copy of snap.html open + in your web browser, and refresh it as you progress with your + translation). + + + 2. Edit + + Edit the translation file with a regular text editor, or with your + favorite JavaScript editor. + + In the first non-commented line (the one right below this + note) replace "de" with the two-letter ISO 639-1 code for your language, + e.g. + + fr - French => SnapTranslator.dict.fr = { + it - Italian => SnapTranslator.dict.it = { + pl - Polish => SnapTranslator.dict.pl = { + pt - Portuguese => SnapTranslator.dict.pt = { + es - Spanish => SnapTranslator.dict.es = { + el - Greek => => SnapTranslator.dict.el = { + + etc. (see ) + + + 3. Translate + + Then work through the dictionary, replacing the German strings against + your translations. The dictionary is a straight-forward JavaScript ad-hoc + object, for review purposes it should be formatted as follows: + + { + 'English string': + 'Translation string', + 'last key': + } 'last value' + + and you only edit the indented value strings. Note that each key-value + pair needs to be delimited by a comma, but that there shouldn't be a comma + after the last pair (again, just overwrite the template file and you'll be + fine). + + If something doesn't work, or if you're unsure about the formalities you + should check your file with + + + + This will inform you about any missed commas etc. + + + 4. Accented characters + + Depending on which text editor and which file encoding you use you can + directly enter special characters (e.g. Umlaut, accented characters) on + your keyboard. However, I've noticed that some browsers may not display + special characters correctly, even if other browsers do. So it's best to + check your results in several browsers. If you want to be on the safe + side, it's even better to escape these characters using Unicode. + + see: + + + 5. Block specs: + + At this time your translation of block specs will only work + correctly, if the order of formal parameters and their types + are unchanged. Placeholders for inputs (formal parameters) are + indicated by a preceding % prefix and followed by a type + abbreviation. + + For example: + + 'say %s for %n secs' + + can currently not be changed into + + 'say %n secs long %s' + + and still work as intended. + + Similarly + + 'point towards %dst' + + cannot be changed into + + 'point towards %cst' + + without breaking its functionality. + + + 6. Submit + + When you're done, rename the edited file by replacing the "de" part of the + filename with the two-letter ISO 639-1 code for your language, e.g. + + fr - French => lang-fr.js + it - Italian => lang-it.js + pl - Polish => lang-pl.js + pt - Portuguese => lang-pt.js + es - Spanish => lang-es.js + el - Greek => => lang-el.js + + and send it to me for inclusion in the official Snap! distribution. + Once your translation has been included, Your name will the shown in the + "Translators" tab in the "About Snap!" dialog box, and you will be able to + directly launch a translated version of Snap! in your browser by appending + + lang:xx + + to the URL, xx representing your translations two-letter code. + + + 7. Known issues + + In some browsers accents or ornaments located in typographic ascenders + above the cap height are currently (partially) cut-off. + + Enjoy! + -Jens +*/ + +/*global SnapTranslator*/ + +SnapTranslator.dict.it = { + +/* + Special characters: (see ) + + Ä, ä \u00c4, \u00e4 + Ö, ö \u00d6, \u00f6 + Ü, ü \u00dc, \u00fc + ß \u00df +*/ + + // translations meta information + 'language_name': + 'Italiano', // the name as it should appear in the language menu + 'language_translator': + 'Stefano Federici', // your name for the Translators tab + 'translator_e-mail': + 's_federici@yahoo.com', // optional + 'last_changed': + '2012-10-16', // this, too, will appear in the Translators tab + + // GUI + // control bar: + 'untitled': + 'SenzaTitolo', + 'development mode': + 'modalit\u00E0 sviluppo', + + // categories: + 'Motion': + 'Movimento', + 'Looks': + 'Aspetto', + 'Sound': + 'Suono', + 'Pen': + 'Penna', + 'Control': + 'Controllo', + 'Sensing': + 'Sensori', + 'Operators': + 'Operatori', + 'Variables': + 'Variabili', + 'Lists': + 'Liste', + 'Other': + 'Altro', + + // editor: + 'draggable': + 'trascinabile', + + // tabs: + 'Scripts': + 'Script', + 'Costumes': + 'Costumi', + 'Sounds': + 'Suoni', + + // names: + 'Sprite': + 'Sprite', + 'Stage': + 'Stage', + + // rotation styles: + 'don\'t rotate': + 'non ruotare', + 'can rotate': + 'pu\u00F2 ruotare', + 'only face left/right': + 'voltati solo a destra/sinistra', + + // new sprite button: + 'add a new sprite': + 'aggiungi un nuovo sprite', + + // tab help + 'costumes tab help': + 'Importa un\'immagine da una pagina web\n' + + 'o da un file sul tuo computer trascinandolo qui', + 'import a sound from your computer\nby dragging it into here': + 'Importa un suono dal tuo computer trascinandolo qui', + + // primitive blocks: + + /* + Attention Translators: + ---------------------- + At this time your translation of block specs will only work + correctly, if the order of formal parameters and their types + are unchanged. Placeholders for inputs (formal parameters) are + indicated by a preceding % prefix and followed by a type + abbreviation. + + For example: + + 'say %s for %n secs' + + can currently not be changed into + + 'say %n secs long %s' + + and still work as intended. + + Similarly + + 'point towards %dst' + + cannot be changed into + + 'point towards %cst' + + without breaking its functionality. + */ + + // motion: + 'Stage selected:\nno motion primitives': + 'Stage selezionato:\nNessun blocco per il movimento\n', + + 'move %n steps': + 'fai %n passi', + 'turn %clockwise %n degrees': + 'ruota di %clockwise %n gradi', + 'turn %counterclockwise %n degrees': + 'ruota di %counterclockwise %n gradi', + 'point in direction %dir': + 'punta in direzione %dir', + 'point towards %dst': + 'punta verso %dst', + 'go to x: %n y: %n': + 'vai a x: %n y: %n', + 'go to %dst': + 'raggiungi %dst', + 'glide %n secs to x: %n y: %n': + 'scivola in %n secondi a x: %n y: %n', + 'change x by %n': + 'cambia x di %n', + 'set x to %n': + 'vai dove x \u00E8 %n', + 'change y by %n': + 'cambia y di %n', + 'set y to %n': + 'vai dove y \u00E8 %n', + 'if on edge, bounce': + 'rimbalza quando tocchi il bordo', + 'x position': + 'posizione x', + 'y position': + 'posizione y', + 'direction': + 'direzione', + + // looks: + 'switch to costume %cst': + 'passa al costume %cst', + 'next costume': + 'passa al costume seguente', + 'costume #': + 'numero costume', + 'say %s for %n secs': + 'dire %s per %n secondi', + 'say %s': + 'dire %s', + 'think %s for %n secs': + 'pensa %s per %n secondi', + 'think %s': + 'pensa %s', + 'Hello!': + 'Ciao!', + 'Hmm...': + 'Hmm...', + 'change %eff effect by %n': + 'cambia effetto %eff di %n', + 'set %eff effect to %n': + 'porta effetto %eff a %n', + 'clear graphic effects': + 'rimuovi effetti grafici', + 'change size by %n': + 'cambia dimensione di %n', + 'set size to %n %': + 'porta dimensione a %n %', + 'size': + 'dimensione', + 'show': + 'mostra', + 'hide': + 'nascondi', + 'go to front': + 'vai in primo piano', + 'go back %n layers': + 'vai indietro di %n livelli', + + 'development mode \ndebugging primitives:': + 'modalit\u00E0 sviluppo\nComandi di debug', + 'console log %mult%s': + 'console log: %mult%s', + 'alert %mult%s': + 'avviso: %mult%s', + + // sound: + 'play sound %snd': + 'produci suono %snd', + 'play sound %snd until done': + 'produci suono %snd e attendi la fine', + 'stop all sounds': + 'arresta tutti i suoni', + 'rest for %n beats': + 'fai una pausa di %n battute', + 'play note %n for %n beats': + 'suona nota %n per %n battute', + 'change tempo by %n': + 'cambia tempo di %n', + 'set tempo to %n bpm': + 'porta tempo a %n bpm', + 'tempo': + 'tempo', + + // pen: + 'clear': + 'pulisci', + 'pen down': + 'penna gi\u00F9', + 'pen up': + 'penna su', + 'set pen color to %clr': + 'usa penna di colore %clr', + 'change pen color by %n': + 'cambia colore penna di %n', + 'set pen color to %n': + 'usa penna di colore %n', + 'change pen shade by %n': + 'cambia luminosit\u00E0 penna di %n', + 'set pen shade to %n': + 'porta luminosit\u00E0 penna a %n', + 'change pen size by %n': + 'cambia dimensione penna di %n', + 'set pen size to %n': + 'porta dimensione penna a %n', + 'stamp': + 'timbra', + + // control: + 'when %greenflag clicked': + 'quando si clicca su %greenflag', + 'when %keyHat key pressed': + 'quando si preme il tasto %keyHat', + 'when I am clicked': + 'quando vengo cliccato', + 'when I receive %msgHat': + 'quando ricevo %msgHat', + 'broadcast %msg': + 'invia a tutti %msg', + 'broadcast %msg and wait': + 'invia a tutti %msg e attendi', + 'Message name': + 'Nome messaggio', + 'wait %n secs': + 'attendi %n secondi', + 'wait until %b': + 'attendi fino a quando %b', + 'forever %c': + 'per sempre %c', + 'repeat %n %c': + 'ripeti %n volte %c', + 'repeat until %b %c': + 'ripeti fino a quando %b %c', + 'if %b %c': + 'se %b %c', + 'if %b %c else %c': + 'se %b %c altrimenti %c', + 'report %s': + 'risultato %s', + 'stop block': + 'ferma il blocco', + 'stop script': + 'ferma lo script', + 'stop all %stop': + 'ferma tutto %stop', + 'run %cmdRing %inputs': + 'esegui %cmdRing %inputs', + 'launch %cmdRing %inputs': + 'lancia %cmdRing %inputs', + 'call %repRing %inputs': + 'chiama %repRing %inputs', + 'run %cmdRing w/continuation': + 'esegui %cmdRing con continuazione', + 'call %cmdRing w/continuation': + 'chiama %cmdRing con continuazione', + 'warp %c': + 'esegui in modalit\u00E0 turbo %c', + 'when I start as a clone': + 'quando vengo clonato', + 'create a clone of %cln': + 'crea un clone di %cln', + 'myself': + 'me stesso', + 'delete this clone': + 'elimina questo clone', + + // sensing: + 'touching %col ?': + 'sta toccando %col', + 'touching %clr ?': + 'sta toccando il colore %clr', + 'color %clr is touching %clr ?': + 'il colore %clr sta toccando il colore %clr', + 'ask %s and wait': + 'chiedi %s e attendi', + 'what\'s your name?': + 'come ti chiami?', + 'answer': + 'risposta', + 'mouse x': + 'x del mouse', + 'mouse y': + 'y del mouse', + 'mouse down?': + 'tasto del mouse premuto', + 'key %key pressed?': + 'tasto %key premuto', + 'distance to %dst': + 'distanza da %dst', + 'reset timer': + 'azzera cronometro', + 'timer': + 'cronometro', + '%att of %spr': + '%att di %spr', + 'http:// %s': + 'leggi pagina web http:// %s', + 'turbo mode?': + 'modalit\u00E0 turbo attiva', + 'set turbo mode to %b': + 'porta modalit\u00E0 turbo a %b', + + 'filtered for %clr': + 'selezionati per colore %clr', + 'stack size': + 'dimensione stack', + 'frames': + 'frame', + + // operators: + '%n mod %n': + 'resto della divisione di %n diviso %n', + 'round %n': + 'arrotonda %n', + '%fun of %n': + '%fun di %n', + 'pick random %n to %n': + 'numero a caso tra %n e %n', + '%b and %b': + '%b e %b', + '%b or %b': + '%b o %b', + 'not %b': + 'non %b', + 'true': + 'vero', + 'false': + 'falso', + 'join %words': + 'unione di %words', + 'hello': + 'ciao', + 'world': + 'mondo', + 'letter %n of %s': + 'lettera in posizione %n di %s', + 'length of %s': + 'lunghezza di %s', + 'unicode of %s': + 'codice unicode di %s', + 'unicode %n as letter': + 'lettera del codice unicode %n', + 'is %s a %typ ?': + '%s \u00E8 di tipo %typ', + 'is %s identical to %s ?': + '%s \u00E8 identico a %s ?', + + 'type of %s': + 'tipo di %s', + + // variables: + 'Make a variable': + 'Nuova variabile', + 'Variable name': + 'Nome della variabile?', + 'Delete a variable': + 'Cancella variabile', + + 'set %var to %s': + 'porta %var a %s', + 'change %var by %n': + 'cambia %var di %n', + 'show variable %var': + 'mostra variabile %var', + 'hide variable %var': + 'nascondi variabile %var', + 'script variables %scriptVars': + 'variabili dello script: %scriptVars', + + // lists: + 'list %exp': + 'lista %exp', + '%s in front of %l': + '%s davanti a %l', + 'item %idx of %l': + 'elemento %idx di %l', + 'all but first of %l': + 'tutto meno il primo elemento di %l', + 'length of %l': + 'lunghezza di %l', + '%l contains %s': + '%l contiene %s', + 'thing': + 'cosa', + 'add %s to %l': + 'aggiungi %s a %l', + 'delete %ida of %l': + 'cancella %ida da %l', + 'insert %s at %idx of %l': + 'inserisci %s alla posizione %idx di %l', + 'replace item %idx of %l with %s': + 'sostituisci elemento %idx di %l con %s', + + // other + 'Make a block': + 'Crea un blocco', + + // menus + // snap menu + 'About...': + 'Informazioni su Snap!...', + 'Snap! website': + 'Sito web di Snap!', + 'Download source': + 'Scarica il codice sorgente', + 'Switch back to user mode': + 'Torna alla modalit\u00E0 utente', + 'disable deep-Morphic\ncontext menus\nand show user-friendly ones': + 'disabilita i menu contestuali\ndi Morphic e mostra quelli user-friendly', + 'Switch to dev mode': + 'Passa alla modalit\u00E0 sviluppo', + 'enable Morphic\ncontext menus\nand inspectors,\nnot user-friendly!': + 'Abilita i menu contestuali\ndi Morphic e l\'inspector,\n non user-friendly', + + // project menu + 'Project notes...': + 'Note di Progetto...', + 'New': + 'Nuovo', + 'Open...': + 'Apri...', + 'Save': + 'Salva', + 'Save As...': + 'Salva con nome...', + 'Import...': + 'Importa...', + 'file menu import hint': + 'carica un file di progetto,\nuna libreria di blocchi,' + + '\nun costume o un suono esportati' + + '\n\nNon supportato da tutti i browser', + 'Export project as plain text...': + 'Esporta il progetto come un file di testo...', + 'Export project...': + 'Esporta il progetto...', + 'show project data as XML\nin a new browser window': + 'mostra i dati del progetto in formato XML\nin una nuova finestra del browser', + 'Export blocks...': + 'Esporta blocchi...', + 'show global custom block definitions as XML\nin a new browser window': + 'mostra in formato XML le definizione dei nuovi blocchi\nin una nuova finestra del browser', + 'Import tools': + 'Importa tools', + 'load the official library of\npowerful blocks': + 'carica la libreria ufficiale di\nblocchi Snap', + + // cloud menu + 'Login...': + 'Accedi...', + 'Signup...': + 'Registrati...', + + // settings menu + 'Language...': + 'Lingua...', + 'Zoom blocks...': + 'Zoom dei blocchi...', + 'Blurred shadows': + 'Ombreggiature attenuate', + 'uncheck to use solid drop\nshadows and highlights': + 'disabilitare per visualizzare ombreggiature\ned evidenziature solide', + 'check to use blurred drop\nshadows and highlights': + 'abilitare per visualizzare ombreggiature\ned evidenziature attenuate', + 'Zebra coloring': + 'Colorazione alternata', + 'check to enable alternating\ncolors for nested blocks': + 'abilitare per visualizzare a colori\nalternati i blocchi annidati', + 'uncheck to disable alternating\ncolors for nested block': + 'disabilitare per non visualizzare a colori\nalternati i blocchi annidati', + + 'Dynamic input labels': + 'Etichette degli input dinamiche', + 'uncheck to disable dynamic\nlabels for variadic inputs': + 'disabilitare per non avere etichette\ndinamiche per input variabili', + 'check to enable dynamic\nlabels for variadic inputs': + 'abilitare per avere etichette\ndinamiche per input variabili', + + 'Prefer empty slot drops': + 'Favorisci l\'aggancio a slot vuoti', + 'settings menu prefer empty slots hint': + 'abilitare per favorire l\'inserimento in slot vuoti\nquando si trascinano e rilasciano dei reporter', + 'uncheck to allow dropped\nreporters to kick out others': + 'disabilitare per permettere agli slot di espellere\ni reporter inclusi al loro interno', + 'Long form input dialog': + 'Usa finestra degli input estesa', + 'check to always show slot\ntypes in the input dialog': + 'abilitare per mostrare sempre i tipi degli slot\nnella finestra di creazione degli input', + 'uncheck to use the input\ndialog in short form': + 'disabilitare per non mostrare automaticamente i tipi degli slot\nnella finestra di creazione degli input', + 'Virtual keyboard': + 'Tastiera virtuale', + 'uncheck to disable\nvirtual keyboard support\nfor mobile devices': + 'disabilitare per non usare il supporto\ndella tastiera virtuale con i dispositivi mobili', + 'check to enable\nvirtual keyboard support\nfor mobile devices': + 'abilitare per usare il supporto della\ntastiera virtuale con i dispositivi mobili', + 'Input sliders': + 'Usa slider per gli input', + 'uncheck to disable\ninput sliders for\nentry fields': + 'disabilitare per non visualizzare gli slider\n per inserire valori numerici', + 'check to enable\ninput sliders for\nentry fields': + 'abilitare per visualizzare gli slider\n per inserire valori numerici', + 'Clicking sound': + 'Click di aggancio dei blocchi', + 'uncheck to turn\nblock clicking\nsound off': + 'disabilitare per non riprodurre il suono di aggancio dei blocchi', + 'check to turn\nblock clicking\nsound on': + 'abilitare per riprodurre il suono di aggancio dei blocchi', + 'Animations': + 'Animazioni', + 'uncheck to disable\nIDE animations': + 'disabilitare per non consentire\nanimazioni dell\u0027IDE', + 'Turbo mode': + 'Modalit\u00E0 Turbo', + 'check to prioritize\nscript execution': + 'abilitare per dare priorit\u00E0\nall\u0027esecuzione degli script', + 'uncheck to run scripts\nat normal speed': + 'disabilitare per eseguire gli script\na velocità normale', + 'check to enable\nIDE animations': + 'abilitare per nconsentire\nanimazioni dell\u0027IDE', + 'Thread safe scripts': + 'Script thread safe', + 'uncheck to allow\nscript reentrancy': + 'disabilitare per permettere agli script di rientrare', + 'check to disallow\nscript reentrancy': + 'abilitare per impedire agli script di rientrare', + 'Prefer smooth animations': + 'Animazioni a framerate fisso', + 'uncheck for greater speed\nat variable frame rates': + 'disabilitare per massima velocità\na framerate variabile', + 'check for smooth, predictable\nanimations across computers': + 'abilitare per avere animazioni\nfluide su tutti i computer', + + // inputs + 'with inputs': + 'con argomenti', + 'input names:': + 'con variabili:', + 'Input Names:': + 'Con Variabili:', + + // context menus: + 'help': + 'aiuto', + + // blocks: + 'help...': + 'aiuto...', + 'relabel...': + 'rinomina...', + 'duplicate': + 'duplica', + 'make a copy\nand pick it up': + 'crea una copia', + 'only duplicate this block': + 'duplica solo questo blocco', + 'delete': + 'cancella', + 'script pic...': + 'immagine script...', + 'open a new window\nwith a picture of this script': + 'apri una nuova finestra\ncon un\'immagine di questo script', + 'ringify': + 'inserisci in un anello', + 'unringify': + 'estrai dall\'anello', + + // custom blocks: + 'delete block definition...': + 'cancella la definizione del blocco...', + 'edit...': + 'modifica...', + + // sprites: + 'edit': + 'modifica', + 'export...': + 'esporta...', + + // stage: + 'show all': + 'mostra tutti gli sprite', + 'pic...': + 'salva immagine dello Stage...', + 'open a new window\nwith a picture of the stage': + 'apre una nuova finestra con un\u0027immagine dello Stage', + + // scripting area + 'clean up': + 'riordina', + 'arrange scripts\nvertically': + 'riordina gli script\nuno sotto l\'altro', + 'add comment': + 'aggiungi un commento', + 'make a block...': + 'crea un blocco...', + + // costumes + 'rename': + 'rinomina', + 'export': + 'esporta', + 'rename costume': + 'rinomina costume', + + // sounds + 'Play sound': + 'Riproduci\nquesto suono', + 'Stop sound': + 'Ferma\nil suono', + 'Stop': + 'Stop', + 'Play': + 'Play', + 'rename sound': + 'rinomina suono', + + // dialogs + // buttons + 'OK': + 'OK', + 'Cancel': + 'Annulla', + 'Yes': + 'Si', + 'No': + 'No', + + // help + 'Help': + 'Aiuto', + + // zoom blocks + 'Zoom blocks': + 'Zoom dei blocchi', + 'build': + 'costruisci', + 'your own': + 'i tuoi', + 'blocks': + 'blocchi', + 'normal (1x)': + 'normale (1x)', + 'demo (1.2x)': + 'Demo (1.2x)', + 'presentation (1.4x)': + 'presentazione(1.4x)', + 'big (2x)': + 'grandi (2x)', + 'huge (4x)': + 'molto grandi (4x)', + 'giant (8x)': + 'giganti (8x)', + 'monstrous (10x)': + 'grandissimi (10x)', + + // Project Manager + 'Untitled': + 'Senza Titolo', + 'Open Project': + 'Apri Progetto', + '(empty)': + '(vuoto)', + 'Saved!': + 'Salvato!', + 'Delete Project': + 'Elimina Progetto', + 'Are you sure you want to delete': + 'Sei sicuro di voler eliminare', + 'rename...': + 'rinomina...', + + // costume editor + 'Costume Editor': + 'Editor di Immagini', + 'click or drag crosshairs to move the rotation center': + 'clicca e trascina la croce per spostare il centro di rotazione', + + // project notes + 'Project Notes': + 'Note di Progetto', + + // new project + 'New Project': + 'Nuovo Progetto', + 'Replace the current project with a new one?': + 'Vuoi sostituire il progetto attuale con uno nuovo?', + + // open project + 'Open Projekt': + 'Apri Progetto', + + // save project + 'Save Project As...': + 'Salva Progetto Come...', + + // export blocks + 'Export blocks': + 'Esporta blocchi', + 'Import blocks': + 'Importa blocchi', + 'this project doesn\'t have any\ncustom global blocks yet': + 'in questo progetto non sono stati ancora definiti dei nuovi blocchi', + 'select': + 'seleziona', + 'all': + 'tutti', + 'none': + 'nessuno', + + // variable dialog + 'for all sprites': + 'per tutti gli sprite', + 'for this sprite only': + 'solo per questo sprite', + + // block dialog + 'Change block': + 'Cambia categoria e tipo del blocco', + 'Command': + 'Comando', + 'Reporter': + 'Monitor', + 'Predicate': + 'Condizione', + + // block editor + 'Block Editor': + 'Editor di Blocchi', + 'Apply': + 'Applica', + + // block deletion dialog + 'Delete Custom Block': + 'Cancella Blocco', + 'block deletion dialog text': + 'Sei sicuro di voler cancellare questo blocco\n' + + 'e tutte le sue occorrenze?', + + // input dialog + 'Create input name': + 'Crea parametro', + 'Edit input name': + 'Modifica parametro', + 'Edit label fragment': + 'Modifica porzione di etichetta', + 'Title text': + 'Parole della definizione', + 'Input name': + 'Parametro', + 'Delete': + 'Cancella', + 'Object': + 'Oggetto', + 'Number': + 'Numero', + 'Text': + 'Testo', + 'List': + 'Lista', + 'Any type': + 'Qualunque tipo', + 'Boolean (T/F)': + 'Booleano (V/F)', + 'Command\n(inline)': + 'Comando\n(in linea)', + 'Command\n(C-shape)': + 'Comando \n(a forma di C)', + 'Any\n(unevaluated)': + 'Qualunque\n(non valutato)', + 'Boolean\n(unevaluated)': + 'Booleano\n(non valutato)', + 'Single input.': + 'Un solo valore.', + 'Default Value:': + 'Valore predefinito:', + 'Multiple inputs (value is list of inputs)': + 'Molti valori (il valore \u00E8 una lista di argomenti)', + 'Upvar - make internal variable visible to caller': + 'Rendi il parametro visibile all\'esterno', + + // About Snap + 'About Snap': + 'Informazioni su Snap', + 'Back...': + 'Indietro...', + 'License...': + 'Licenza...', + 'Modules...': + 'Moduli...', + 'Credits...': + 'Crediti...', + 'Translators...': + 'Traduttori', + 'License': + 'Licenza', + 'current module versions:': + 'versione corrente dei moduli:', + 'Contributors': + 'Hanno contribuito:', + 'Translations': + 'Traduttori', + + // variable watchers + 'normal': + 'normale', + 'large': + 'grande', + 'slider': + 'cursore', + 'slider min...': + 'Scegli il min del cursore...', + 'slider max...': + 'Scegli il max del cursore...', + 'import...': + 'importa...', + 'Slider minimum value': + 'Valore minimo del cursore', + 'Slider maximum value': + 'Valore massimo del cursore', + + // list watchers + 'length: ': + 'lunghezza: ', + + // coments + 'add comment here...': + 'aggiunto un commento in questo punto...', + + // drow downs + // directions + '(90) right': + '(90) destra', + '(-90) left': + '(-90) sinistra', + '(0) up': + '(0) su', + '(180) down': + '(180) gi\u00F9', + + // collision detection + 'mouse-pointer': + 'puntatore del mouse', + 'edge': + 'bordo', + 'pen trails': + 'tratti della penna', + + // costumes + 'Turtle': + 'Tartaruga', + 'Empty': + 'Vuoto', + + // graphical effects + 'ghost': + 'fantasma', + + // keys + 'space': + 'spazio', + 'up arrow': + 'freccia su', + 'down arrow': + 'freccia gi\u00F9', + 'right arrow': + 'freccia destra', + 'left arrow': + 'freccia sinistra', + 'a': + 'a', + 'b': + 'b', + 'c': + 'c', + 'd': + 'd', + 'e': + 'e', + 'f': + 'f', + 'g': + 'g', + 'h': + 'h', + 'i': + 'i', + 'j': + 'j', + 'k': + 'k', + 'l': + 'l', + 'm': + 'm', + 'n': + 'n', + 'o': + 'o', + 'p': + 'p', + 'q': + 'q', + 'r': + 'r', + 's': + 's', + 't': + 't', + 'u': + 'u', + 'v': + 'v', + 'w': + 'w', + 'x': + 'x', + 'y': + 'y', + 'z': + 'z', + '0': + '0', + '1': + '1', + '2': + '2', + '3': + '3', + '4': + '4', + '5': + '5', + '6': + '6', + '7': + '7', + '8': + '8', + '9': + '9', + + // messages + 'new...': + 'nuovo...', + + // math functions + 'abs': + 'abs', + 'sqrt': + 'sqrt', + 'sin': + 'sin', + 'cos': + 'cos', + 'tan': + 'tan', + 'asin': + 'asin', + 'acos': + 'acos', + 'atan': + 'atan', + 'ln': + 'ln', + 'e^': + 'e^', + + // data types + 'number': + 'numero', + 'text': + 'testo', + 'Boolean': + 'booleano', + 'list': + 'lista', + 'command': + 'comando', + 'reporter': + 'monitor', + 'predicate': + 'condizione', + + // list indices + 'last': + 'ultimo', + 'any': + 'qualunque' +}; diff --git a/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/lang-ja.js b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/lang-ja.js new file mode 100644 index 0000000..ad80e91 --- /dev/null +++ b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/lang-ja.js @@ -0,0 +1,1250 @@ +/* + + lang-ja.js + + Japanese translation for SNAP! + + written by Jens Mönig + + Copyright (C) 2012 by Jens Mönig + + This file is part of Snap!. + + Snap! is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + + + + Note to Translators: + -------------------- + At this stage of development, Snap! can be translated to any LTR language + maintaining the current order of inputs (formal parameters in blocks). + + Translating Snap! is easy: + + + 1. Download + + Download the sources and extract them into a local folder on your + computer: + + + + Use the German translation file (named 'lang-de.js') as template for your + own translations. Start with editing the original file, because that way + you will be able to immediately check the results in your browsers while + you're working on your translation (keep the local copy of snap.html open + in your web browser, and refresh it as you progress with your + translation). + + + 2. Edit + + Edit the translation file with a regular text editor, or with your + favorite JavaScript editor. + + In the first non-commented line (the one right below this + note) replace "de" with the two-letter ISO 639-1 code for your language, + e.g. + + fr - French => SnapTranslator.dict.fr = { + it - Italian => SnapTranslator.dict.it = { + pl - Polish => SnapTranslator.dict.pl = { + pt - Portuguese => SnapTranslator.dict.pt = { + es - Spanish => SnapTranslator.dict.es = { + el - Greek => => SnapTranslator.dict.el = { + + etc. (see ) + + + 3. Translate + + Then work through the dictionary, replacing the German strings against + your translations. The dictionary is a straight-forward JavaScript ad-hoc + object, for review purposes it should be formatted as follows: + + { + 'English string': + 'Translation string', + 'last key': + } 'last value' + + and you only edit the indented value strings. Note that each key-value + pair needs to be delimited by a comma, but that there shouldn't be a comma + after the last pair (again, just overwrite the template file and you'll be + fine). + + If something doesn't work, or if you're unsure about the formalities you + should check your file with + + + + This will inform you about any missed commas etc. + + + 4. Accented characters + + Depending on which text editor and which file encoding you use you can + directly enter special characters (e.g. Umlaut, accented characters) on + your keyboard. However, I've noticed that some browsers may not display + special characters correctly, even if other browsers do. So it's best to + check your results in several browsers. If you want to be on the safe + side, it's even better to escape these characters using Unicode. + + see: + + + 5. Block specs: + + At this time your translation of block specs will only work + correctly, if the order of formal parameters and their types + are unchanged. Placeholders for inputs (formal parameters) are + indicated by a preceding % prefix and followed by a type + abbreviation. + + For example: + + 'say %s for %n secs' + + can currently not be changed into + + 'say %n secs long %s' + + and still work as intended. + + Similarly + + 'point towards %dst' + + cannot be changed into + + 'point towards %cst' + + without breaking its functionality. + + + 6. Submit + + When you're done, rename the edited file by replacing the "de" part of the + filename with the two-letter ISO 639-1 code for your language, e.g. + + fr - French => lang-fr.js + it - Italian => lang-it.js + pl - Polish => lang-pl.js + pt - Portuguese => lang-pt.js + es - Spanish => lang-es.js + el - Greek => => lang-el.js + + and send it to me for inclusion in the official Snap! distribution. + Once your translation has been included, Your name will the shown in the + "Translators" tab in the "About Snap!" dialog box, and you will be able to + directly launch a translated version of Snap! in your browser by appending + + lang:xx + + to the URL, xx representing your translations two-letter code. + + + 7. Known issues + + In some browsers accents or ornaments located in typographic ascenders + above the cap height are currently (partially) cut-off. + + Enjoy! + -Jens +*/ + +/*global SnapTranslator*/ + +SnapTranslator.dict.ja = { + +/* + Special characters: (see ) + + Ä, ä \u00c4, \u00e4 + Ö, ö \u00d6, \u00f6 + Ü, ü \u00dc, \u00fc + ß \u00df +*/ + + // translations meta information + 'language_name': + '日本語', // the name as it should appear in the language menu + 'language_translator': + 'Kazuhiro Abe', // your name for the Translators tab + 'translator_e-mail': + 'abee@squeakland.jp', // optional + 'last_changed': + '2013-04-02', // this, too, will appear in the Translators tab + + // GUI + // control bar: + 'untitled': + '名称未設定', + 'development mode': + '開発者モード', + + // categories: + 'Motion': + '動き', + 'Looks': + '見た目', + 'Sound': + '音', + 'Pen': + 'ペン', + 'Control': + '制御', + 'Sensing': + '調べる', + 'Operators': + '演算', + 'Variables': + '変数', + 'Lists': + 'リスト', + 'Other': + 'その他', + + // editor: + 'draggable': + 'ドラッグ可能', + + // tabs: + 'Scripts': + 'スクリプト', + 'Costumes': + 'コスチューム', + 'Sounds': + '音', + + // names: + 'Sprite': + 'スプライト', + 'Stage': + 'ステージ', + + // rotation styles: + 'don\'t rotate': + '回転しない', + 'can rotate': + '回転する', + 'only face left/right': + '左右に反転するだけ', + + // new sprite button: + 'add a new sprite': + '新しいスプライトを追加する', + + // tab help + 'costumes tab help': + '他のWebページやコンピューター上の画像を\n' + + 'ここにドロップして読み込みます', + 'import a sound from your computer\nby dragging it into here': + 'コンピューター上のサウンドを\nここにドラッグして読み込みます', + + // primitive blocks: + + /* + Attention Translators: + ---------------------- + At this time your translation of block specs will only work + correctly, if the order of formal parameters and their types + are unchanged. Placeholders for inputs (formal parameters) are + indicated by a preceding % prefix and followed by a type + abbreviation. + + For example: + + 'say %s for %n secs' + + can currently not be changed into + + 'say %n secs long %s' + + and still work as intended. + + Similarly + + 'point towards %dst' + + cannot be changed into + + 'point towards %cst' + + without breaking its functionality. + */ + + // motion: + 'Stage selected:\nno motion primitives': + '選択されたステージ:\n動きのプリミティブがありません', + + 'move %n steps': + '%n 歩動かす', + 'turn %clockwise %n degrees': + '%clockwise %n 度回す', + 'turn %counterclockwise %n degrees': + '%counterclockwise %n 度回す', + 'point in direction %dir': + '%dir 度に向ける', + 'point towards %dst': + '%dst へ向ける', + 'go to x: %n y: %n': + 'x座標を %n 、y座標を %n にする', + 'go to %dst': + '%dst へ行く', + 'glide %n secs to x: %n y: %n': + '%n 秒でx座標を %n に、y座標を %n に変える', + 'change x by %n': + 'x座標を %n ずつ変える', + 'set x to %n': + 'x座標を %n にする', + 'change y by %n': + 'y座標を %n ずつ変える', + 'set y to %n': + '座標を %n にする', + 'if on edge, bounce': + 'もし端に着いたら、跳ね返る', + 'x position': + 'x座標', + 'y position': + 'y座標', + 'direction': + '向き', + + // looks: + 'switch to costume %cst': + 'コスチュームを %cst にする', + 'next costume': + '次のコスチュームにする', + 'costume #': + 'コスチュームの番号', + 'say %s for %n secs': + '%s と %n 秒言う', + 'say %s': + '%s という', + 'think %s for %n secs': + '%s と %n 秒考える', + 'think %s': + '%s と考える', + 'Hello!': + 'こんにちは!', + 'Hmm...': + 'うーん...', + 'change %eff effect by %n': + '%eff の効果を %n ずつ変える', + 'set %eff effect to %n': + '%eff の効果を %n にする', + 'clear graphic effects': + '画像効果をなくす', + 'change size by %n': + '大きさを %n ずつ変える', + 'set size to %n %': + '大きさを %n にする', + 'size': + '大きさ', + 'show': + '表示する', + 'hide': + '隠す', + 'go to front': + '前に出す', + 'go back %n layers': + '%n 層下げる', + + 'development mode \ndebugging primitives:': + '開発者モード\nデバッグ用プリミティブ:', + 'console log %mult%s': + 'コンソールログ %mult%s', + 'alert %mult%s': + '警告: %mult%s', + + // sound: + 'play sound %snd': + '%snd の音を鳴らす', + 'play sound %snd until done': + '終わるまで %snd の音を鳴らす', + 'stop all sounds': + 'すべての音を止める', + 'rest for %n beats': + '%n 拍休む', + 'play note %n for %n beats': + '%n の音符を %n 拍鳴らす', + 'change tempo by %n': + 'テンポを %n ずつ変える', + 'set tempo to %n bpm': + 'テンポを %n BPMにする', + 'tempo': + 'テンポ', + + // pen: + 'clear': + '消す', + 'pen down': + 'ペンを下ろす', + 'pen up': + 'ペンを上げる', + 'set pen color to %clr': + 'ペンの色を %clr にする', + 'change pen color by %n': + 'ペンの色を %n ずつ変える', + 'set pen color to %n': + 'ペンの色を %n にする', + 'change pen shade by %n': + 'ペンの濃さを %n ずつ変える', + 'set pen shade to %n': + 'ペンの濃さを %n にする', + 'change pen size by %n': + 'ペンの太さを %n ずつ変える', + 'set pen size to %n': + 'ペンの太さを %n にする', + 'stamp': + 'スタンプ', + + // control: + 'when %greenflag clicked': + '%greenflag が押されたとき', + 'when %keyHat key pressed': + '%keyHat が押されたとき', + 'when I am clicked': + '自分がクリックされたとき', + 'when I receive %msgHat': + '%msgHat を受け取ったとき', + 'broadcast %msg': + '%msg を送る', + 'broadcast %msg and wait': + '%msg を送って待つ', + 'Message name': + 'メッセージ名', + 'wait %n secs': + '%n 秒待つ', + 'wait until %b': + '%b まで待つ', + 'forever %c': + 'ずっと %c', + 'repeat %n %c': + '%n 回繰り返す %c', + 'repeat until %b %c': + '%b まで繰り返す %c', + 'if %b %c': + 'もし %b なら %c', + 'if %b %c else %c': + 'もし %b なら %c でなければ %c', + 'report %s': + '%s を返す', + 'stop block': + 'ブロックを止める', + 'stop script': + 'スクリプトを止める', + 'stop all %stop': + 'すべてを止める %stop', + 'run %cmdRing %inputs': + '%cmdRing を %inputs で実行する', + 'launch %cmdRing %inputs': + '%cmdRing を %inputs で起動する', + 'call %repRing %inputs': + '%repRing を %inputs で呼ぶ', + 'run %cmdRing w/continuation': + '継続付きで %cmdRing を実行する', + 'call %cmdRing w/continuation': + '継続付きで %cmdRing を呼ぶ', + 'warp %c': + 'ワープする %c', + 'when I start as a clone': + 'クローンされたとき', + 'create a clone of %cln': + '%cln のクローンを作る', + 'myself': + '自分自身', + 'delete this clone': + 'このクローンを削除する', + + // sensing: + 'touching %col ?': + '%col に触れた', + 'touching %clr ?': + '%clr 色に触れた', + 'color %clr is touching %clr ?': + '%clr 色が %clr 色に触れた', + 'ask %s and wait': + '%s と聞いて待つ', + 'what\'s your name?': + 'あなたの名前は何ですか?', + 'answer': + '答え', + 'mouse x': + 'マウスのx座標', + 'mouse y': + 'マウスのy座標', + 'mouse down?': + 'マウスが押された', + 'key %key pressed?': + '%key が押された', + 'distance to %dst': + '%dst までの距離', + 'reset timer': + 'タイマーをリセット', + 'timer': + 'タイマー', + 'http:// %s': + 'http:// %s', + 'turbo mode?': + 'ターボモード?', + 'set turbo mode to %b': + 'ターボーモードを %b にする', + + 'filtered for %clr': + '%clr 色を抽出', + 'stack size': + 'スタックの大きさ', + 'frames': + 'フレーム', + + // operators: + '%n mod %n': + '%n を %n で割った余り', + 'round %n': + '%n を丸める', + '%fun of %n': + '%fun %n', + 'pick random %n to %n': + '%n から %n までの乱数', + '%b and %b': + '%b かつ %b', + '%b or %b': + '%b または %b', + 'not %b': + '%b ではない', + 'true': + 'はい', + 'false': + 'いいえ', + 'join %words': + '%words をつなぐ', + 'hello': + 'ハロー', + 'world': + 'ワールド', + 'letter %n of %s': + '%n 文字目の文字 %s', + 'length of %s': + '%s の長さ', + 'unicode of %s': + '%s のUnicode', + 'unicode %n as letter': + 'Unicodeで %n の文字', + 'is %s a %typ ?': + '%s は %typ 型', + 'is %s identical to %s ?': + '%s は %s と同一', + + 'type of %s': + '%s の型', + + // variables: + 'Make a variable': + '新しい変数を作る', + 'Variable name': + '変数名', + 'Delete a variable': + '変数を削除', + + 'set %var to %s': + '%var を %s にする', + 'change %var by %n': + '%var を %n ずつ変える', + 'show variable %var': + '%var 表示する', + 'hide variable %var': + '%var を隠す', + 'script variables %scriptVars': + 'スクリプト変数 %scriptVars', + + // lists: + 'list %exp': + 'リスト %exp', + '%s in front of %l': + '%s を %l の先頭に置く', + 'item %idx of %l': + '%idx 番目 %l', + 'all but first of %l': + '%l の先頭以外', + 'length of %l': + '%l の長さ', + '%l contains %s': + '%l に %s が含まれているか', + 'thing': + 'なにか', + 'add %s to %l': + '%s を %l に追加する', + 'delete %ida of %l': + '%ida を %l から削除する', + 'insert %s at %idx of %l': + '%s を %idx 番目に挿入する %l', + 'replace item %idx of %l with %s': + '%idx 番目 %l を %s で置き換える', + + // other + 'Make a block': + 'ブロックを作る', + + // menus + // snap menu + 'About...': + 'Snap!について...', + 'Snap! website': + 'Snap!のWebサイト', + 'Download source': + 'ソースをダウンロード', + 'Switch back to user mode': + 'ユーザーモードに切り替え', + 'disable deep-Morphic\ncontext menus\nand show user-friendly ones': + '高度なモーフィックコンテクストメニューを無効にして\nユーザーフレンドリーなメニューを表示する', + 'Switch to dev mode': + '開発者モードに切り替える', + 'enable Morphic\ncontext menus\nand inspectors,\nnot user-friendly!': + 'ユーザーフレンドリーではない\nモーフィックコンテクストメニューと\nインスペクターを有効にする', + + // project menu + 'Project notes...': + 'プロジェクトのメモ...', + 'New': + '新規', + 'Open...': + '開く...', + 'Save': + '保存', + 'Save As...': + '名前をつけて保存...', + 'Import...': + '読み込み...', + 'file menu import hint': + 'チェックするとレポーターをドラッグ&ドロップするとき\n' + + '空のレポーターにフォーカスします\n\n' + + 'いくつかのブラウザーではサポートされません', + 'Export project as plain text...': + 'テキストファイルとしてプロジェクトを書き出す...', + 'Export project...': + 'プロジェクトを書き出す...', + 'show project data as XML\nin a new browser window': + 'プロジェクトのデータをXMLとして\nブラウザの新しいウインドウに表示する', + 'Export blocks...': + 'ブロックを書き出す...', + 'show global custom block definitions as XML\nin a new browser window': + 'グローバルカスタムブロックの定義をXMLとして\nブラウザの新しいウインドウに表示する', + 'Import tools': + 'ツールを読み込む', + 'load the official library of\npowerful blocks': + '強力なブロックの公式\nライブラリを読み込む', + + // cloud menu + 'Login...': + 'ログイン...', + 'Signup...': + 'サインアップ...', + + // settings menu + 'Language...': + '言語...', + 'Zoom blocks...': + 'ブロックをズーム...', + 'Blurred shadows': + '半透明の影', + 'uncheck to use solid drop\nshadows and highlights': + 'チェックを外すと単色の影と\nハイライトになります', + 'check to use blurred drop\nshadows and highlights': + 'チェックすると半透明の影と\nハイライトになります', + 'Zebra coloring': + '縞々で表示', + 'check to enable alternating\ncolors for nested blocks': + 'チェックすると入れ子になった\nブロックを縞々で表示します', + 'uncheck to disable alternating\ncolors for nested block': + 'チェックを外すと入れ子になった\nブロックを普通に表示します', + 'Dynamic input labels': + '動的な入力ラベル', + 'uncheck to disable dynamic\nlabels for variadic inputs': + 'チェックを外すと可変個引数の\n動的ラベルを不可にします', + 'check to enable dynamic\nlabels for variadic inputs': + 'チェックすると可変個引数の\n動的ラベルを可能にします', + 'Prefer empty slot drops': + '空のスロットのドロップを許す', + 'settings menu prefer empty slots hint': + '設定メニューが空のスロットのヒントを許します', + 'uncheck to allow dropped\nreporters to kick out others': + 'チェックを外すとドロップしたレポーターが\n他を押し出せるようになります', + 'Long form input dialog': + '引数ダイアログを長い形式にする', + 'check to always show slot\ntypes in the input dialog': + 'チェックすると引数ダイアログに\n常にスロットの型を表示します', + 'uncheck to use the input\ndialog in short form': + 'チェックを外すと引数ダイアログを短く表示します', + 'Virtual keyboard': + '仮想キーボード', + 'uncheck to disable\nvirtual keyboard support\nfor mobile devices': + 'チェックを外すとモバイル機器用の\n仮想キーボードを無効にします', + 'check to enable\nvirtual keyboard support\nfor mobile devices': + 'チェックするとモバイル機器用の\n仮想キーボードを有効にします', + 'Input sliders': + '入力スライダー', + 'uncheck to disable\ninput sliders for\nentry fields': + 'チェックを外すと入力フィールドのスライダーを無効にします', + 'check to enable\ninput sliders for\nentry fields': + 'チェックすると入力フィールドのスライダーを有効にします', + 'Clicking sound': + 'クリック音', + 'uncheck to turn\nblock clicking\nsound off': + 'チェックを外すとブロックの\nクリック音を切ります', + 'check to turn\nblock clicking\nsound on': + 'チェックを外すとブロックの\nクリック音を入れます', + 'Animations': + 'アニメーション', + 'uncheck to disable\nIDE animations': + 'チェックを外すとIDEの\nアニメーションを切ります', + 'check to prioritize\nscript execution': + 'チェックするとスクリプトの\n処理を優先します', + 'uncheck to run scripts\nat normal speed': + 'チェックを外すとスクリプトを\n通常の速度で実行します', + 'check to enable\nIDE animations': + 'チェックするとIDEの\nアニメーションを入れます', + 'Turbo mode': + 'ターボモード', + 'Thread safe scripts': + 'スクリプトをスレッドセーフにする', + 'uncheck to allow\nscript reentrancy': + 'チェックを外すとスクリプトを\n再入可能にします', + 'check to disallow\nscript reentrancy': + 'チェックするとスクリプトを\n再入不能にします', + 'Prefer smooth animations': + 'なめらかなアニメーションにする', + 'uncheck for greater speed\nat variable frame rates': + 'チェックを外すとフレームレート\n当たりの速度を上げます', + 'check for smooth, predictable\nanimations across computers': + 'チェックするとコンピューター間で\nなめらかで予測可能なアニメーションにします', + + // inputs + 'with inputs': + '引数', + 'input names:': + '引数名:', + 'Input Names:': + '引数名:', + 'input list:': + '引数リスト:', + + // context menus: + 'help': + 'ヘルプ', + + // blocks: + 'help...': + 'ヘルプ...', + 'duplicate': + '複製', + 'make a copy\nand pick it up': + 'コピーを作って\nそれを掴みます', + 'only duplicate this block': + 'このブロックをコピーするだけ', + 'delete': + '削除', + 'script pic...': + 'スクリプトの画像...', + 'open a new window\nwith a picture of this script': + 'このスクリプトの画像を表示する新しいウィンドウを開きます', + 'ringify': + 'リング化', + 'unringify': + '非リング化', + + // custom blocks: + 'delete block definition...': + 'ブロックの定義を削除', + 'edit...': + '編集...', + + // sprites: + 'edit': + '編集', + 'export...': + '書き出し...', + + // stage: + 'show all': + 'すべてを表示', + 'pic...': + '画像...', + 'open a new window\nwith a picture of the stage': + 'このステージの画像で\n新しいウィンドウを開く', + + // scripting area + 'clean up': + 'きれいにする', + 'arrange scripts\nvertically': + 'スクリプトを\n縦に整列します', + 'add comment': + 'コメントを追加', + 'make a block...': + 'ブロックを作る...', + + // costumes + 'rename': + '名前を変更', + 'export': + '書き出し', + 'rename costume': + 'コスチュームの名前を変更', + + // sounds + 'Play sound': + '音を鳴らす', + 'Stop sound': + '音を止める', + 'Stop': + '停止', + 'Play': + '再生', + 'rename sound': + '音の名前を変更', + + // dialogs + // buttons + 'OK': + 'OK', + 'Ok': + 'OK', + 'Cancel': + 'キャンセル', + 'Yes': + 'はい', + 'No': + 'いいえ', + + // help + 'Help': + 'ヘルプ', + + // zoom blocks + 'Zoom blocks': + 'ブロックをズーム', + 'build': + '作ろう', + 'your own': + 'あなた自身の', + 'blocks': + 'ブロックを', + 'normal (1x)': + 'ノーマル (1x)', + 'demo (1.2x)': + 'デモ (1.2x)', + 'presentation (1.4x)': + 'プレゼンテーション (1.4x)', + 'big (2x)': + '大 (2x)', + 'huge (4x)': + '特大 (4x)', + 'giant (8x)': + '巨大 (8x)', + 'monstrous (10x)': + '超巨大 (10x)', + + // Project Manager + 'Untitled': + '名称未設定', + 'Open Project': + 'プロジェクトを開く', + 'Open': + '開く', + '(empty)': + '(空)', + 'Saved!': + '保存しました!', + 'Delete Project': + 'プロジェクトを削除', + 'Are you sure you want to delete': + '本当に削除しますか', + 'rename...': + '名前を変更...', + + // costume editor + 'Costume Editor': + 'コスチュームエディター', + 'click or drag crosshairs to move the rotation center': + 'クリックかドラッグで回転中心を移動する', + + // project notes + 'Project Notes': + 'プロジェクトのメモ', + + // new project + 'New Project': + '新しいプロジェクト', + 'Replace the current project with a new one?': + '現在のプロジェクトを新しいもので置き換えますか?', + + // open project + 'Open Projekt': + 'プロジェクトを開く', + + // save project + 'Save Project As...': + '名前を付けてプロジェクトを保存...', + + // export blocks + 'Export blocks': + 'ブロックを書き出し', + 'Import blocks': + 'ブロックを読み込み', + 'this project doesn\'t have any\ncustom global blocks yet': + 'このプロジェクトはカスタムグローバルブロックを持っていません', + 'select': + '選択', + 'all': + 'すべて', + 'none': + 'なし', + + // variable dialog + 'for all sprites': + 'すべてのスプライト用', + 'for this sprite only': + 'このスプライト用', + + // block dialog + 'Change block': + 'ブロックを変更', + 'Command': + 'コマンド', + 'Reporter': + 'モニター', + 'Predicate': + '述語', + + // block editor + 'Block Editor': + 'ブロックエディター', + 'Apply': + '適用', + + // block deletion dialog + 'Delete Custom Block': + 'カスタムブロックを削除', + 'block deletion dialog text': + 'このカスタムブロックとすべてのインスタンスを\n削除してもよいですか?', + + // input dialog + 'Create input name': + '引数名を作成', + 'Edit input name': + '引数名を編集', + 'Edit label fragment': + 'ラベルの断片を編集', + 'Title text': + 'タイトルテキスト', + 'Input name': + '引数名', + 'Delete': + '削除', + 'Object': + 'オブジェクト', + 'Number': + '数', + 'Text': + 'テキスト', + 'List': + 'リスト', + 'Any type': + '全タイプ', + 'Boolean (T/F)': + '真偽値 (はい/いいえ)', + 'Command\n(inline)': + 'コマンド\n(インライン)', + 'Command\n(C-shape)': + 'コマンド \n(C形)', + 'Any\n(unevaluated)': + '任意\n(未評価)', + 'Boolean\n(unevaluated)': + '真偽値\n(未評価)', + 'Single input.': + '単一引数.', + 'Default Value:': + 'デフォルト値:', + 'Multiple inputs (value is list of inputs)': + '複数の引数 (値は引数のリスト)', + 'Upvar - make internal variable visible to caller': + 'Upvar - 呼び出し元から見える内部的な変数', + + // About Snap + 'About Snap': + 'Snapについて', + 'Back...': + '戻る...', + 'License...': + 'ライセンス...', + 'Modules...': + 'モジュール...', + 'Credits...': + 'クレジット...', + 'Translators...': + '翻訳者', + 'License': + 'ライセンス', + 'current module versions:': + '現在のモジュールのバージョン:', + 'Contributors': + '貢献者:', + 'Translations': + '翻訳', + + // variable watchers + 'normal': + '通常', + 'large': + '大', + 'slider': + 'スライダー', + 'slider min...': + 'スライダーの最小値...', + 'slider max...': + 'スライダーの最大値...', + 'import...': + '読み込み...', + 'Slider minimum value': + 'スライダーの最小値', + 'Slider maximum value': + 'スライダーの最大値', + + // list watchers + 'length: ': + '長さ: ', + + // coments + 'add comment here...': + 'ここにコメントを追加...', + + // drow downs + // directions + '(90) right': + '(90) 右', + '(-90) left': + '(-90) 左', + '(0) up': + '(0) 上', + '(180) down': + '(180) 下', + + // collision detection + 'mouse-pointer': + 'マウスのポインター', + 'edge': + '端', + 'pen trails': + 'ペンの軌跡', + + // costumes + 'Turtle': + 'タートル', + 'Empty': + '空', + + // graphical effects + 'ghost': + '幽霊', + + // keys + 'space': + 'スペース', + 'up arrow': + '上向き矢印', + 'down arrow': + '下向き矢印', + 'right arrow': + '右向き矢印', + 'left arrow': + '左向き矢印', + 'a': + 'a', + 'b': + 'b', + 'c': + 'c', + 'd': + 'd', + 'e': + 'e', + 'f': + 'f', + 'g': + 'g', + 'h': + 'h', + 'i': + 'i', + 'j': + 'j', + 'k': + 'k', + 'l': + 'l', + 'm': + 'm', + 'n': + 'n', + 'o': + 'o', + 'p': + 'p', + 'q': + 'q', + 'r': + 'r', + 's': + 's', + 't': + 't', + 'u': + 'u', + 'v': + 'v', + 'w': + 'w', + 'x': + 'x', + 'y': + 'y', + 'z': + 'z', + '0': + '0', + '1': + '1', + '2': + '2', + '3': + '3', + '4': + '4', + '5': + '5', + '6': + '6', + '7': + '7', + '8': + '8', + '9': + '9', + + // messages + 'new...': + '新規...', + + // math functions + 'abs': + '絶対値', + 'sqrt': + '平方根', + 'sin': + 'sin', + 'cos': + 'cos', + 'tan': + 'tan', + 'asin': + 'asin', + 'acos': + 'acos', + 'atan': + 'atan', + 'ln': + 'ln', + 'e^': + 'e^', + + // data types + 'number': + '数', + 'text': + 'テキスト', + 'Boolean': + '真偽値', + 'list': + 'リスト', + 'command': + 'コマンド', + 'reporter': + 'レポーター', + 'predicate': + '述語', + + // list indices + 'last': + '最後', + 'any': + '任意', + + // missing entries + 'Reference manual': + 'リファレンスマニュアル', + 'Sign in': + 'サインイン', + 'User name:': + 'ユーザー名:', + 'Password:': + 'パスワード:', + 'stay signed in on this computer\nuntil logging out': + 'ログアウトするまでこのコンピューターに\nサインインしたままにする', + 'Sign up': + 'サインアップ', + 'User name:': + 'ユーザー名:', + 'Password:': + 'パスワード:', + 'Birth date:': + '誕生月:', + 'Birth date:': + '年:', + 'January': + '1月', + 'February': + '2月', + 'March': + '3月', + 'April': + '4月', + 'May': + '5月', + 'June': + '6月', + 'July': + '7月', + 'August': + '8月', + 'September': + '9月', + 'October': + '10月', + 'November': + '11月', + 'December': + '12月', + '1993 or before': + '1993年以前', + 'E-mail address:': + '電子メールアドレス:', + 'Terms of Service...': + 'サービス利用規約...', + 'Privacy...': + '個人情報...', + 'I have read and agree\nto the Terms of Service': + 'サービス利用規約を読み\nそれに同意します', + +}; diff --git a/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/lang-ja_HIRA.js b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/lang-ja_HIRA.js new file mode 100644 index 0000000..6cbae53 --- /dev/null +++ b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/lang-ja_HIRA.js @@ -0,0 +1,1250 @@ +/* + + lang-ja_HIRA.js + + Japanese Hiragana translation for SNAP! + + written by Jens Mönig + + Copyright (C) 2012 by Jens Mönig + + This file is part of Snap!. + + Snap! is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + + + + Note to Translators: + -------------------- + At this stage of development, Snap! can be translated to any LTR language + maintaining the current order of inputs (formal parameters in blocks). + + Translating Snap! is easy: + + + 1. Download + + Download the sources and extract them into a local folder on your + computer: + + + + Use the German translation file (named 'lang-de.js') as template for your + own translations. Start with editing the original file, because that way + you will be able to immediately check the results in your browsers while + you're working on your translation (keep the local copy of snap.html open + in your web browser, and refresh it as you progress with your + translation). + + + 2. Edit + + Edit the translation file with a regular text editor, or with your + favorite JavaScript editor. + + In the first non-commented line (the one right below this + note) replace "de" with the two-letter ISO 639-1 code for your language, + e.g. + + fr - French => SnapTranslator.dict.fr = { + it - Italian => SnapTranslator.dict.it = { + pl - Polish => SnapTranslator.dict.pl = { + pt - Portuguese => SnapTranslator.dict.pt = { + es - Spanish => SnapTranslator.dict.es = { + el - Greek => => SnapTranslator.dict.el = { + + etc. (see ) + + + 3. Translate + + Then work through the dictionary, replacing the German strings against + your translations. The dictionary is a straight-forward JavaScript ad-hoc + object, for review purposes it should be formatted as follows: + + { + 'English string': + 'Translation string', + 'last key': + } 'last value' + + and you only edit the indented value strings. Note that each key-value + pair needs to be delimited by a comma, but that there shouldn't be a comma + after the last pair (again, just overwrite the template file and you'll be + fine). + + If something doesn't work, or if you're unsure about the formalities you + should check your file with + + + + This will inform you about any missed commas etc. + + + 4. Accented characters + + Depending on which text editor and which file encoding you use you can + directly enter special characters (e.g. Umlaut, accented characters) on + your keyboard. However, I've noticed that some browsers may not display + special characters correctly, even if other browsers do. So it's best to + check your results in several browsers. If you want to be on the safe + side, it's even better to escape these characters using Unicode. + + see: + + + 5. Block specs: + + At this time your translation of block specs will only work + correctly, if the order of formal parameters and their types + are unchanged. Placeholders for inputs (formal parameters) are + indicated by a preceding % prefix and followed by a type + abbreviation. + + For example: + + 'say %s for %n secs' + + can currently not be changed into + + 'say %n secs long %s' + + and still work as intended. + + Similarly + + 'point towards %dst' + + cannot be changed into + + 'point towards %cst' + + without breaking its functionality. + + + 6. Submit + + When you're done, rename the edited file by replacing the "de" part of the + filename with the two-letter ISO 639-1 code for your language, e.g. + + fr - French => lang-fr.js + it - Italian => lang-it.js + pl - Polish => lang-pl.js + pt - Portuguese => lang-pt.js + es - Spanish => lang-es.js + el - Greek => => lang-el.js + + and send it to me for inclusion in the official Snap! distribution. + Once your translation has been included, Your name will the shown in the + "Translators" tab in the "About Snap!" dialog box, and you will be able to + directly launch a translated version of Snap! in your browser by appending + + lang:xx + + to the URL, xx representing your translations two-letter code. + + + 7. Known issues + + In some browsers accents or ornaments located in typographic ascenders + above the cap height are currently (partially) cut-off. + + Enjoy! + -Jens +*/ + +/*global SnapTranslator*/ + +SnapTranslator.dict.ja_HIRA = { + +/* + Special characters: (see ) + + Ä, ä \u00c4, \u00e4 + Ö, ö \u00d6, \u00f6 + Ü, ü \u00dc, \u00fc + ß \u00df +*/ + + // translations meta information + 'language_name': + 'にほんご', // the name as it should appear in the language menu + 'language_translator': + 'Kazuhiro Abe', // your name for the Translators tab + 'translator_e-mail': + 'abee@squeakland.jp', // optional + 'last_changed': + '2013-04-02', // this, too, will appear in the Translators tab + + // GUI + // control bar: + 'untitled': + 'めいしょうみせってい', + 'development mode': + 'かいはつしゃモード', + + // categories: + 'Motion': + 'うごき', + 'Looks': + 'みため', + 'Sound': + 'おと', + 'Pen': + 'ペン', + 'Control': + 'せいぎょ', + 'Sensing': + 'しらべる', + 'Operators': + 'えんざん', + 'Variables': + 'へんすう', + 'Lists': + 'リスト', + 'Other': + 'そのた', + + // editor: + 'draggable': + 'ドラッグかのう', + + // tabs: + 'Scripts': + 'スクリプト', + 'Costumes': + 'コスチューム', + 'Sounds': + 'おと', + + // names: + 'Sprite': + 'スプライト', + 'Stage': + 'ステージ', + + // rotation styles: + 'don\'t rotate': + 'かいてんしない', + 'can rotate': + 'かいてんする', + 'only face left/right': + 'さゆうにはんてんするだけ', + + // new sprite button: + 'add a new sprite': + 'あたらしいスプライトをついかする', + + // tab help + 'costumes tab help': + 'ほかのWebページやコンピューターじょうのがぞうを\n' + + 'ここにドロップしてよみこみます', + 'import a sound from your computer\nby dragging it into here': + 'コンピューターじょうのサウンドを\nここにドラッグしてよみこみます', + + // primitive blocks: + + /* + Attention Translators: + ---------------------- + At this time your translation of block specs will only work + correctly, if the order of formal parameters and their types + are unchanged. Placeholders for inputs (formal parameters) are + indicated by a preceding % prefix and followed by a type + abbreviation. + + For example: + + 'say %s for %n secs' + + can currently not be changed into + + 'say %n secs long %s' + + and still work as intended. + + Similarly + + 'point towards %dst' + + cannot be changed into + + 'point towards %cst' + + without breaking its functionality. + */ + + // motion: + 'Stage selected:\nno motion primitives': + 'せんたくされたステージ:\nうごきのプリミティブがありません', + + 'move %n steps': + '%n ほうごかす', + 'turn %clockwise %n degrees': + '%clockwise %n どまわす', + 'turn %counterclockwise %n degrees': + '%counterclockwise %n どまわす', + 'point in direction %dir': + '%dir どにむける', + 'point towards %dst': + '%dst へむける', + 'go to x: %n y: %n': + 'xざひょうを %n 、yざひょうを %n にする', + 'go to %dst': + '%dst へいく', + 'glide %n secs to x: %n y: %n': + '%n びょうでxざひょうを %n に、yざひょうを %n にかえる', + 'change x by %n': + 'xざひょうを %n ずつかえる', + 'set x to %n': + 'xざひょうを %n にする', + 'change y by %n': + 'yざひょうを %n ずつかえる', + 'set y to %n': + 'ざひょうを %n にする', + 'if on edge, bounce': + 'もしはしについたら、はねかえる', + 'x position': + 'xざひょう', + 'y position': + 'yざひょう', + 'direction': + 'むき', + + // looks: + 'switch to costume %cst': + 'コスチュームを %cst にする', + 'next costume': + 'つぎのコスチュームにする', + 'costume #': + 'コスチュームのばんごう', + 'say %s for %n secs': + '%s と %n びょういう', + 'say %s': + '%s という', + 'think %s for %n secs': + '%s と %n びょうかんがえる', + 'think %s': + '%s とかんがえる', + 'Hello!': + 'こんにちは!', + 'Hmm...': + 'うーん...', + 'change %eff effect by %n': + '%eff のこうかを %n ずつかえる', + 'set %eff effect to %n': + '%eff のこうかを %n にする', + 'clear graphic effects': + 'がぞうこうかをなくす', + 'change size by %n': + 'おおきさを %n ずつかえる', + 'set size to %n %': + 'おおきさを %n にする', + 'size': + 'おおきさ', + 'show': + 'ひょうじする', + 'hide': + 'かくす', + 'go to front': + 'まえにだす', + 'go back %n layers': + '%n そうさげる', + + 'development mode \ndebugging primitives:': + 'かいはつしゃモード\nデバッグようプリミティブ:', + 'console log %mult%s': + 'コンソールログ %mult%s', + 'alert %mult%s': + 'けいこく: %mult%s', + + // sound: + 'play sound %snd': + '%snd のおとをならす', + 'play sound %snd until done': + 'おわるまで %snd のおとをならす', + 'stop all sounds': + 'すべてのおとをとめる', + 'rest for %n beats': + '%n はくやすむ', + 'play note %n for %n beats': + '%n のおんぷを %n はくならす', + 'change tempo by %n': + 'テンポを %n ずつかえる', + 'set tempo to %n bpm': + 'テンポを %n BPMにする', + 'tempo': + 'テンポ', + + // pen: + 'clear': + 'けす', + 'pen down': + 'ペンをおろす', + 'pen up': + 'ペンをあげる', + 'set pen color to %clr': + 'ペンのいろを %clr にする', + 'change pen color by %n': + 'ペンのいろを %n ずつかえる', + 'set pen color to %n': + 'ペンのいろを %n にする', + 'change pen shade by %n': + 'ペンのこさを %n ずつかえる', + 'set pen shade to %n': + 'ペンのこさを %n にする', + 'change pen size by %n': + 'ペンのふとさを %n ずつかえる', + 'set pen size to %n': + 'ペンのふとさを %n にする', + 'stamp': + 'スタンプ', + + // control: + 'when %greenflag clicked': + '%greenflag がおされたとき', + 'when %keyHat key pressed': + '%keyHat がおされたとき', + 'when I am clicked': + 'じぶんがクリックされたとき', + 'when I receive %msgHat': + '%msgHat をうけとったとき', + 'broadcast %msg': + '%msg をおくる', + 'broadcast %msg and wait': + '%msg をおくってまつ', + 'Message name': + 'メッセージめい', + 'wait %n secs': + '%n びょうまつ', + 'wait until %b': + '%b までまつ', + 'forever %c': + 'ずっと %c', + 'repeat %n %c': + '%n かいくりかえす %c', + 'repeat until %b %c': + '%b までくりかえす %c', + 'if %b %c': + 'もし %b なら %c', + 'if %b %c else %c': + 'もし %b なら %c でなければ %c', + 'report %s': + '%s をかえす', + 'stop block': + 'ブロックをとめる', + 'stop script': + 'スクリプトをとめる', + 'stop all %stop': + 'すべてをとめる %stop', + 'run %cmdRing %inputs': + '%cmdRing を %inputs でじっこうする', + 'launch %cmdRing %inputs': + '%cmdRing を %inputs できどうする', + 'call %repRing %inputs': + '%repRing を %inputs でよぶ', + 'run %cmdRing w/continuation': + 'けいぞくつきで %cmdRing をじっこうする', + 'call %cmdRing w/continuation': + 'けいぞくつきで %cmdRing をよぶ', + 'warp %c': + 'ワープする %c', + 'when I start as a clone': + 'クローンされたとき', + 'create a clone of %cln': + '%cln のクローンをつくる', + 'myself': + 'じぶんじしん', + 'delete this clone': + 'このクローンをさくじょする', + + // sensing: + 'touching %col ?': + '%col にふれた', + 'touching %clr ?': + '%clr いろにふれた', + 'color %clr is touching %clr ?': + '%clr いろが %clr いろにふれた', + 'ask %s and wait': + '%s ときいてまつ', + 'what\'s your name?': + 'あなたのなまえはなんですか?', + 'answer': + 'こたえ', + 'mouse x': + 'マウスのxざひょう', + 'mouse y': + 'マウスのyざひょう', + 'mouse down?': + 'マウスがおされた', + 'key %key pressed?': + '%key がおされた', + 'distance to %dst': + '%dst までのきょり', + 'reset timer': + 'タイマーをリセット', + 'timer': + 'タイマー', + 'http:// %s': + 'http:// %s', + 'turbo mode?': + 'ターボモード?', + 'set turbo mode to %b': + 'ターボーモードを %b にする', + + 'filtered for %clr': + '%clr いろをちゅうしゅつ', + 'stack size': + 'スタックのおおきさ', + 'frames': + 'フレーム', + + // operators: + '%n mod %n': + '%n を %n でわったあまり', + 'round %n': + '%n をまるめる', + '%fun of %n': + '%fun %n', + 'pick random %n to %n': + '%n から %n までのらんすう', + '%b and %b': + '%b かつ %b', + '%b or %b': + '%b または %b', + 'not %b': + '%b ではない', + 'true': + 'はい', + 'false': + 'いいえ', + 'join %words': + '%words をつなぐ', + 'hello': + 'ハロー', + 'world': + 'ワールド', + 'letter %n of %s': + '%n もじめのもじ %s', + 'length of %s': + '%s のながさ', + 'unicode of %s': + '%s のUnicode', + 'unicode %n as letter': + 'Unicodeで %n のもじ', + 'is %s a %typ ?': + '%s は %typ がた', + 'is %s identical to %s ?': + '%s は %s とどういつ', + + 'type of %s': + '%s のかた', + + // variables: + 'Make a variable': + 'あたらしいへんすうをつくる', + 'Variable name': + 'へんすうめい', + 'Delete a variable': + 'へんすうをさくじょ', + + 'set %var to %s': + '%var を %s にする', + 'change %var by %n': + '%var を %n ずつかえる', + 'show variable %var': + '%var ひょうじする', + 'hide variable %var': + '%var をかくす', + 'script variables %scriptVars': + 'スクリプトへんすう %scriptVars', + + // lists: + 'list %exp': + 'リスト %exp', + '%s in front of %l': + '%s を %l のせんとうにおく', + 'item %idx of %l': + '%idx ばんめ %l', + 'all but first of %l': + '%l のせんとういがい', + 'length of %l': + '%l のながさ', + '%l contains %s': + '%l に %s がふくまれているか', + 'thing': + 'なにか', + 'add %s to %l': + '%s を %l についかする', + 'delete %ida of %l': + '%ida を %l からさくじょする', + 'insert %s at %idx of %l': + '%s を %idx ばんめにそうにゅうする %l', + 'replace item %idx of %l with %s': + '%idx ばんめ %l を %s でおきかえる', + + // other + 'Make a block': + 'ブロックをつくる', + + // menus + // snap menu + 'About...': + 'Snap!について...', + 'Snap! website': + 'Snap!のWebサイト', + 'Download source': + 'ソースをダウンロード', + 'Switch back to user mode': + 'ユーザーモードにきりかえ', + 'disable deep-Morphic\ncontext menus\nand show user-friendly ones': + 'こうどなモーフィックコンテクストメニューをむこうにして\nユーザーフレンドリーなメニューをひょうじする', + 'Switch to dev mode': + 'かいはつしゃモードにきりかえる', + 'enable Morphic\ncontext menus\nand inspectors,\nnot user-friendly!': + 'ユーザーフレンドリーではない\nモーフィックコンテクストメニューと\nインスペクターをゆうこうにする', + + // project menu + 'Project notes...': + 'プロジェクトのメモ...', + 'New': + 'しんき', + 'Open...': + 'ひらく...', + 'Save': + 'ほぞん', + 'Save As...': + 'なまえをつけてほぞん...', + 'Import...': + 'よみこみ...', + 'file menu import hint': + 'チェックするとレポーターをドラッグ&ドロップするとき\n' + + 'そらのレポーターにフォーカスします\n\n' + + 'いくつかのブラウザーではサポートされません', + 'Export project as plain text...': + 'テキストファイルとしてプロジェクトをかきだす...', + 'Export project...': + 'プロジェクトをかきだす...', + 'show project data as XML\nin a new browser window': + 'プロジェクトのデータをXMLとして\nブラウザのあたらしいウインドウにひょうじする', + 'Export blocks...': + 'ブロックをかきだす...', + 'show global custom block definitions as XML\nin a new browser window': + 'グローバルカスタムブロックのていぎをXMLとして\nブラウザのあたらしいウインドウにひょうじする', + 'Import tools': + 'ツールをよみこむ', + 'load the official library of\npowerful blocks': + 'きょうりょくなブロックのこうしき\nライブラリをよみこむ', + + // cloud menu + 'Login...': + 'ログイン...', + 'Signup...': + 'サインアップ...', + + // settings menu + 'Language...': + 'げんご...', + 'Zoom blocks...': + 'ブロックをズーム...', + 'Blurred shadows': + 'はんとうめいのかげ', + 'uncheck to use solid drop\nshadows and highlights': + 'チェックをはずすとたんしょくのかげと\nハイライトになります', + 'check to use blurred drop\nshadows and highlights': + 'チェックするとはんとうめいのかげと\nハイライトになります', + 'Zebra coloring': + 'じま々でひょうじ', + 'check to enable alternating\ncolors for nested blocks': + 'チェックするといれこになった\nブロックをじま々でひょうじします', + 'uncheck to disable alternating\ncolors for nested block': + 'チェックをはずすといれこになった\nブロックをふつうにひょうじします', + 'Dynamic input labels': + 'どうてきなにゅうりょくラベル', + 'uncheck to disable dynamic\nlabels for variadic inputs': + 'チェックをはずすとかへんこひきすうの\nどうてきラベルをふかにします', + 'check to enable dynamic\nlabels for variadic inputs': + 'チェックするとかへんこひきすうの\nどうてきラベルをかのうにします', + 'Prefer empty slot drops': + 'そらのスロットのドロップをゆるす', + 'settings menu prefer empty slots hint': + 'せっていメニューがそらのスロットのヒントをゆるします', + 'uncheck to allow dropped\nreporters to kick out others': + 'チェックをはずすとドロップしたレポーターが\nほかをおしだせるようになります', + 'Long form input dialog': + 'ひきすうダイアログをながいけいしきにする', + 'check to always show slot\ntypes in the input dialog': + 'チェックするとひきすうダイアログに\nつねにスロットのかたをひょうじします', + 'uncheck to use the input\ndialog in short form': + 'チェックをはずすとひきすうダイアログをみじかくひょうじします', + 'Virtual keyboard': + 'かそうキーボード', + 'uncheck to disable\nvirtual keyboard support\nfor mobile devices': + 'チェックをはずすとモバイルききようの\nかそうキーボードをむこうにします', + 'check to enable\nvirtual keyboard support\nfor mobile devices': + 'チェックするとモバイルききようの\nかそうキーボードをゆうこうにします', + 'Input sliders': + 'にゅうりょくスライダー', + 'uncheck to disable\ninput sliders for\nentry fields': + 'チェックをはずすとにゅうりょくフィールドのスライダーをむこうにします', + 'check to enable\ninput sliders for\nentry fields': + 'チェックするとにゅうりょくフィールドのスライダーをゆうこうにします', + 'Clicking sound': + 'クリックおん', + 'uncheck to turn\nblock clicking\nsound off': + 'チェックをはずすとブロックの\nクリックおんをきります', + 'check to turn\nblock clicking\nsound on': + 'チェックをはずすとブロックの\nクリックおんをいれます', + 'Animations': + 'アニメーション', + 'uncheck to disable\nIDE animations': + 'チェックをはずすとIDEの\nアニメーションをきります', + 'check to prioritize\nscript execution': + 'チェックするとスクリプトの\nしょりをゆうせんします', + 'uncheck to run scripts\nat normal speed': + 'チェックをはずすとスクリプトを\nつうじょうのそくどでじっこうします', + 'check to enable\nIDE animations': + 'チェックするとIDEの\nアニメーションをいれます', + 'Turbo mode': + 'ターボモード', + 'Thread safe scripts': + 'スクリプトをスレッドセーフにする', + 'uncheck to allow\nscript reentrancy': + 'チェックをはずすとスクリプトを\nさいにゅうかのうにします', + 'check to disallow\nscript reentrancy': + 'チェックするとスクリプトを\nさいにゅうふのうにします', + 'Prefer smooth animations': + 'なめらかなアニメーションにする', + 'uncheck for greater speed\nat variable frame rates': + 'チェックをはずすとフレームレート\nあたりのそくどをあげます', + 'check for smooth, predictable\nanimations across computers': + 'チェックするとコンピューターかんで\nなめらかでよそくかのうなアニメーションにします', + + // inputs + 'with inputs': + 'ひきすう', + 'input names:': + 'ひきすうめい:', + 'Input Names:': + 'ひきすうめい:', + 'input list:': + 'ひきすうリスト:', + + // context menus: + 'help': + 'ヘルプ', + + // blocks: + 'help...': + 'ヘルプ...', + 'duplicate': + 'ふくせい', + 'make a copy\nand pick it up': + 'コピーをつくって\nそれをつかみます', + 'only duplicate this block': + 'このブロックをコピーするだけ', + 'delete': + 'さくじょ', + 'script pic...': + 'スクリプトのがぞう...', + 'open a new window\nwith a picture of this script': + 'このスクリプトのがぞうをひょうじするあたらしいウィンドウをひらきます', + 'ringify': + 'リングか', + 'unringify': + 'ひリングか', + + // custom blocks: + 'delete block definition...': + 'ブロックのていぎをさくじょ', + 'edit...': + 'へんしゅう...', + + // sprites: + 'edit': + 'へんしゅう', + 'export...': + 'かきだし...', + + // stage: + 'show all': + 'すべてをひょうじ', + 'pic...': + 'がぞう...', + 'open a new window\nwith a picture of the stage': + 'このステージのがぞうで\nあたらしいウィンドウをひらく', + + // scripting area + 'clean up': + 'きれいにする', + 'arrange scripts\nvertically': + 'スクリプトを\nたてにせいれつします', + 'add comment': + 'コメントをついか', + 'make a block...': + 'ブロックをつくる...', + + // costumes + 'rename': + 'なまえをへんこう', + 'export': + 'かきだし', + 'rename costume': + 'コスチュームのなまえをへんこう', + + // sounds + 'Play sound': + 'おとをならす', + 'Stop sound': + 'おとをとめる', + 'Stop': + 'ていし', + 'Play': + 'さいせい', + 'rename sound': + 'おとのなまえをへんこう', + + // dialogs + // buttons + 'OK': + 'OK', + 'Ok': + 'OK', + 'Cancel': + 'キャンセル', + 'Yes': + 'はい', + 'No': + 'いいえ', + + // help + 'Help': + 'ヘルプ', + + // zoom blocks + 'Zoom blocks': + 'ブロックをズーム', + 'build': + 'つくろう', + 'your own': + 'あなたじしんの', + 'blocks': + 'ブロックを', + 'normal (1x)': + 'ノーマル (1x)', + 'demo (1.2x)': + 'デモ (1.2x)', + 'presentation (1.4x)': + 'プレゼンテーション (1.4x)', + 'big (2x)': + 'だい (2x)', + 'huge (4x)': + 'とくだい (4x)', + 'giant (8x)': + 'きょだい (8x)', + 'monstrous (10x)': + 'ちょうきょだい (10x)', + + // Project Manager + 'Untitled': + 'めいしょうみせってい', + 'Open Project': + 'プロジェクトをひらく', + 'Open': + 'ひらく', + '(empty)': + '(そら)', + 'Saved!': + 'ほぞんしました!', + 'Delete Project': + 'プロジェクトをさくじょ', + 'Are you sure you want to delete': + 'ほんとうにさくじょしますか', + 'rename...': + 'なまえをへんこう...', + + // costume editor + 'Costume Editor': + 'コスチュームエディター', + 'click or drag crosshairs to move the rotation center': + 'クリックかドラッグでかいてんちゅうしんをいどうする', + + // project notes + 'Project Notes': + 'プロジェクトのメモ', + + // new project + 'New Project': + 'あたらしいプロジェクト', + 'Replace the current project with a new one?': + 'げんざいのプロジェクトをあたらしいものでおきかえますか?', + + // open project + 'Open Projekt': + 'プロジェクトをひらく', + + // save project + 'Save Project As...': + 'なまえをつけてプロジェクトをほぞん...', + + // export blocks + 'Export blocks': + 'ブロックをかきだし', + 'Import blocks': + 'ブロックをよみこみ', + 'this project doesn\'t have any\ncustom global blocks yet': + 'このプロジェクトはカスタムグローバルブロックをもっていません', + 'select': + 'せんたく', + 'all': + 'すべて', + 'none': + 'なし', + + // variable dialog + 'for all sprites': + 'すべてのスプライトよう', + 'for this sprite only': + 'このスプライトよう', + + // block dialog + 'Change block': + 'ブロックをへんこう', + 'Command': + 'コマンド', + 'Reporter': + 'モニター', + 'Predicate': + 'じゅつご', + + // block editor + 'Block Editor': + 'ブロックエディター', + 'Apply': + 'てきよう', + + // block deletion dialog + 'Delete Custom Block': + 'カスタムブロックをさくじょ', + 'block deletion dialog text': + 'このカスタムブロックとすべてのインスタンスを\nさくじょしてもよいですか?', + + // input dialog + 'Create input name': + 'ひきすうめいをさくせい', + 'Edit input name': + 'ひきすうめいをへんしゅう', + 'Edit label fragment': + 'ラベルのだんぺんをへんしゅう', + 'Title text': + 'タイトルテキスト', + 'Input name': + 'ひきすうめい', + 'Delete': + 'さくじょ', + 'Object': + 'オブジェクト', + 'Number': + 'かず', + 'Text': + 'テキスト', + 'List': + 'リスト', + 'Any type': + 'ぜんタイプ', + 'Boolean (T/F)': + 'しんぎち (はい/いいえ)', + 'Command\n(inline)': + 'コマンド\n(インライン)', + 'Command\n(C-shape)': + 'コマンド \n(Cけい)', + 'Any\n(unevaluated)': + 'にんい\n(みひょうか)', + 'Boolean\n(unevaluated)': + 'しんぎち\n(みひょうか)', + 'Single input.': + 'たんいつひきすう.', + 'Default Value:': + 'デフォルトち:', + 'Multiple inputs (value is list of inputs)': + 'ふくすうのひきすう (あたいはひきすうのリスト)', + 'Upvar - make internal variable visible to caller': + 'Upvar - よびだしもとからみえるないぶてきなへんすう', + + // About Snap + 'About Snap': + 'Snapについて', + 'Back...': + 'もどる...', + 'License...': + 'ライセンス...', + 'Modules...': + 'モジュール...', + 'Credits...': + 'クレジット...', + 'Translators...': + 'ほんやくしゃ', + 'License': + 'ライセンス', + 'current module versions:': + 'げんざいのモジュールのバージョン:', + 'Contributors': + 'こうけんしゃ:', + 'Translations': + 'ほんやく', + + // variable watchers + 'normal': + 'つうじょう', + 'large': + 'だい', + 'slider': + 'スライダー', + 'slider min...': + 'スライダーのさいしょうち...', + 'slider max...': + 'スライダーのさいだいち...', + 'import...': + 'よみこみ...', + 'Slider minimum value': + 'スライダーのさいしょうち', + 'Slider maximum value': + 'スライダーのさいだいち', + + // list watchers + 'length: ': + 'ながさ: ', + + // coments + 'add comment here...': + 'ここにコメントをついか...', + + // drow downs + // directions + '(90) right': + '(90) みぎ', + '(-90) left': + '(-90) ひだり', + '(0) up': + '(0) じょう', + '(180) down': + '(180) か', + + // collision detection + 'mouse-pointer': + 'マウスのポインター', + 'edge': + 'はし', + 'pen trails': + 'ペンのきせき', + + // costumes + 'Turtle': + 'タートル', + 'Empty': + 'そら', + + // graphical effects + 'ghost': + 'ゆうれい', + + // keys + 'space': + 'スペース', + 'up arrow': + 'うわむきやじるし', + 'down arrow': + 'したむきやじるし', + 'right arrow': + 'みぎむきやじるし', + 'left arrow': + 'ひだりむきやじるし', + 'a': + 'a', + 'b': + 'b', + 'c': + 'c', + 'd': + 'd', + 'e': + 'e', + 'f': + 'f', + 'g': + 'g', + 'h': + 'h', + 'i': + 'i', + 'j': + 'j', + 'k': + 'k', + 'l': + 'l', + 'm': + 'm', + 'n': + 'n', + 'o': + 'o', + 'p': + 'p', + 'q': + 'q', + 'r': + 'r', + 's': + 's', + 't': + 't', + 'u': + 'u', + 'v': + 'v', + 'w': + 'w', + 'x': + 'x', + 'y': + 'y', + 'z': + 'z', + '0': + '0', + '1': + '1', + '2': + '2', + '3': + '3', + '4': + '4', + '5': + '5', + '6': + '6', + '7': + '7', + '8': + '8', + '9': + '9', + + // messages + 'new...': + 'しんき...', + + // math functions + 'abs': + 'ぜったいち', + 'sqrt': + 'へいほうこん', + 'sin': + 'sin', + 'cos': + 'cos', + 'tan': + 'tan', + 'asin': + 'asin', + 'acos': + 'acos', + 'atan': + 'atan', + 'ln': + 'ln', + 'e^': + 'e^', + + // data types + 'number': + 'かず', + 'text': + 'テキスト', + 'Boolean': + 'しんぎち', + 'list': + 'リスト', + 'command': + 'コマンド', + 'reporter': + 'レポーター', + 'predicate': + 'じゅつご', + + // list indices + 'last': + 'さいご', + 'any': + 'にんい', + + // missing entries + 'Reference manual': + 'リファレンスマニュアル', + 'Sign in': + 'サインイン', + 'User name:': + 'ユーザーめい:', + 'Password:': + 'パスワード:', + 'stay signed in on this computer\nuntil logging out': + 'ログアウトするまでこのコンピューターに\nサインインしたままにする', + 'Sign up': + 'サインアップ', + 'User name:': + 'ユーザーめい:', + 'Password:': + 'パスワード:', + 'Birth date:': + 'たんじょうづき:', + 'Birth date:': + 'とし:', + 'January': + '1がつ', + 'February': + '2がつ', + 'March': + '3がつ', + 'April': + '4がつ', + 'May': + '5がつ', + 'June': + '6がつ', + 'July': + '7がつ', + 'August': + '8がつ', + 'September': + '9がつ', + 'October': + '10がつ', + 'November': + '11がつ', + 'December': + '12がつ', + '1993 or before': + '1993ねんいぜん', + 'E-mail address:': + 'でんしメールアドレス:', + 'Terms of Service...': + 'サービスりようきやく...', + 'Privacy...': + 'こじんじょうほう...', + 'I have read and agree\nto the Terms of Service': + 'サービスりようきやくをよみ\nそれにどういします', + +}; diff --git a/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/lang-ko.js b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/lang-ko.js new file mode 100644 index 0000000..a1e50eb --- /dev/null +++ b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/lang-ko.js @@ -0,0 +1,1098 @@ +/* + + lang-ko.js + + Korean translation for SNAP! + + written by Jens Mönig + + Copyright (C) 2012 by Jens Mönig + + This file is part of Snap!. + + Snap! is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + + + + Note to Translators: + -------------------- + At this stage of development, Snap! can be translated to any LTR language + maintaining the current order of inputs (formal parameters in blocks). + + Translating Snap! is easy: + + + 1. Download + + Download the sources and extract them into a local folder on your + computer: + + + + Use the German translation file (named 'lang-de.js') as template for your + own translations. Start with editing the original file, because that way + you will be able to immediately check the results in your browsers while + you're working on your translation (keep the local copy of snap.html open + in your web browser, and refresh it as you progress with your + translation). + + + 2. Edit + + Edit the translation file with a regular text editor, or with your + favorite JavaScript editor. + + In the first non-commented line (the one right below this + note) replace "de" with the two-letter ISO 639-1 code for your language, + e.g. + + fr - French => SnapTranslator.dict.fr = { + it - Italian => SnapTranslator.dict.it = { + pl - Polish => SnapTranslator.dict.pl = { + pt - Portuguese => SnapTranslator.dict.pt = { + es - Spanish => SnapTranslator.dict.es = { + el - Greek => => SnapTranslator.dict.el = { + + etc. (see ) + + + 3. Translate + + Then work through the dictionary, replacing the German strings against + your translations. The dictionary is a straight-forward JavaScript ad-hoc + object, for review purposes it should be formatted as follows: + + { + 'English string': + 'Translation string', + 'last key': + } 'last value' + + and you only edit the indented value strings. Note that each key-value + pair needs to be delimited by a comma, but that there shouldn't be a comma + after the last pair (again, just overwrite the template file and you'll be + fine). + + If something doesn't work, or if you're unsure about the formalities you + should check your file with + + + + This will inform you about any missed commas etc. + + + 4. Accented characters + + Depending on which text editor and which file encoding you use you can + directly enter special characters (e.g. Umlaut, accented characters) on + your keyboard. However, I've noticed that some browsers may not display + special characters correctly, even if other browsers do. So it's best to + check your results in several browsers. If you want to be on the safe + side, it's even better to escape these characters using Unicode. + + see: + + + 5. Block specs: + + At this time your translation of block specs will only work + correctly, if the order of formal parameters and their types + are unchanged. Placeholders for inputs (formal parameters) are + indicated by a preceding % prefix and followed by a type + abbreviation. + + For example: + + 'say %s for %n secs' + + can currently not be changed into + + 'say %n secs long %s' + + and still work as intended. + + Similarly + + 'point towards %dst' + + cannot be changed into + + 'point towards %cst' + + without breaking its functionality. + + + 6. Submit + + When you're done, rename the edited file by replacing the "de" part of the + filename with the two-letter ISO 639-1 code for your language, e.g. + + fr - French => lang-fr.js + it - Italian => lang-it.js + pl - Polish => lang-pl.js + pt - Portuguese => lang-pt.js + es - Spanish => lang-es.js + el - Greek => => lang-el.js + + and send it to me for inclusion in the official Snap! distribution. + Once your translation has been included, Your name will the shown in the + "Translators" tab in the "About Snap!" dialog box, and you will be able to + directly launch a translated version of Snap! in your browser by appending + + lang:xx + + to the URL, xx representing your translations two-letter code. + + + 7. Known issues + + In some browsers accents or ornaments located in typographic ascenders + above the cap height are currently (partially) cut-off. + + Enjoy! + -Jens +*/ + +/*global SnapTranslator*/ + +SnapTranslator.dict.ko = { + +/* + Special characters: (see ) + + Ä, ä \u00c4, \u00e4 + Ö, ö \u00d6, \u00f6 + Ü, ü \u00dc, \u00fc + ß \u00df +*/ + + // translations meta information + 'language_name': + 'Korean', // the name as it should appear in the language menu + 'language_translator': + 'Yunjae Jang', // your name for the Translators tab + 'translator_e-mail': + 'yunjae.jang@inc.korea.ac.kr', // optional + 'last_changed': + '2012-11-18', // this, too, will appear in the Translators tab + + // GUI + // control bar: + 'untitled': + '이름없음', + 'development mode': + '개발자 모드', + + // categories: + 'Motion': + '동작', + 'Looks': + '형태', + 'Sound': + '소리', + 'Pen': + '펜', + 'Control': + '제어', + 'Sensing': + '관찰', + 'Operators': + '연산', + 'Variables': + '변수', + 'Lists': + '리스트', + 'Other': + '기타', + + // editor: + 'draggable': + '드래그 가능?', + + // tabs: + 'Scripts': + '스크립트', + 'Costumes': + '모양', + 'Sounds': + '소리', + + // names: + 'Sprite': + '스프라이트', + 'Stage': + '무대', + + // rotation styles: + 'don\'t rotate': + '회전할 수 없습니다', + 'can rotate': + '회전가능', + 'only face left/right': + '왼쪽에서 오른쪽으로만', + + // new sprite button: + 'add a new sprite': + '새로운 스프라이트 추가', + + // tab help + 'costumes tab help': + '다른 웹페이지나 컴퓨터에 있는 이미지 파일을\n' + + '여기로 드래그해서 가져옵니다.', + 'import a sound from your computer\nby dragging it into here': + '컴퓨터에 있는 소리 파일을\n 여기로 드래그해서 가져옵니다.', + + // primitive blocks: + + /* + Attention Translators: + ---------------------- + At this time your translation of block specs will only work + correctly, if the order of formal parameters and their types + are unchanged. Placeholders for inputs (formal parameters) are + indicated by a preceding % prefix and followed by a type + abbreviation. + + For example: + + 'say %s for %n secs' + + can currently not be changed into + + 'say %n secs long %s' + + and still work as intended. + + Similarly + + 'point towards %dst' + + cannot be changed into + + 'point towards %cst' + + without breaking its functionality. + */ + + // motion: + 'Stage selected:\nno motion primitives': + '무대 선택: 사용 가능한 동작 블록이 없습니다.', + + 'move %n steps': + '%n 만큼 움직이기', + 'turn %clockwise %n degrees': + '%clockwise %n 도 돌기', + 'turn %counterclockwise %n degrees': + '%counterclockwise %n 도 돌기', + 'point in direction %dir': + '%dir 도 방향 보기', + 'point towards %dst': + '%dst 쪽 보기', + 'go to x: %n y: %n': + 'x: %n 、y: %n 쪽으로 가기', + 'go to %dst': + '%dst 위치로 가기', + 'glide %n secs to x: %n y: %n': + '%n 초 동안 x: %n 、y: %n 쪽으로 움직이기', + 'change x by %n': + 'x좌표 %n 만큼 바꾸기', + 'set x to %n': + 'x좌표 %n 로 정하기', + 'change y by %n': + 'y좌표 %n 만큼 바꾸기', + 'set y to %n': + 'y좌표 %n 로 정하기', + 'if on edge, bounce': + '벽에 닿으면 튕기기', + 'x position': + 'x좌표', + 'y position': + 'y좌표', + 'direction': + '방향', + + // looks: + 'switch to costume %cst': + '모양 %cst 로 바꾸기', + 'next costume': + '다음 모양', + 'costume #': + '모양 #', + 'say %s for %n secs': + '%s %n 초 동안 말하기', + 'say %s': + '%s 말하기', + 'think %s for %n secs': + '%s %n 초간 생각하기', + 'think %s': + '%s 생각하기', + 'Hello!': + '안녕!', + 'Hmm...': + '흠…', + 'change %eff effect by %n': + '%eff 효과 %n 만큼 바꾸기', + 'set %eff effect to %n': + '%eff 효과 %n 만큼 주기', + 'clear graphic effects': + '그래픽 효과 지우기', + 'change size by %n': + '크기 %n 만큼 바꾸기', + 'set size to %n %': + '크기 %n % 로 정하기', + 'size': + '크기', + 'show': + '보이기', + 'hide': + '숨기기', + 'go to front': + '맨 앞으로 나오기', + 'go back %n layers': + '%n 번째로 물러나기', + + 'development mode \ndebugging primitives:': + '개발자 모드\n디버깅 프리미티브:', + 'console log %mult%s': + '콘솔 로그 %mult%s', + 'alert %mult%s': + '경고: %mult%s', + + // sound: + 'play sound %snd': + '%snd 소리내기', + 'play sound %snd until done': + '끝날때까지 %snd 소리내기', + 'stop all sounds': + '모든 소리 끄기', + 'rest for %n beats': + '%n 비트 동안 쉬기', + 'play note %n for %n beats': + '%n 음을 %n 비트로 연주하기', + 'change tempo by %n': + '템포를 %n 만큼 바꾸기', + 'set tempo to %n bpm': + '템포를 %n bpm으로 맞추기', + 'tempo': + '템포', + + + // pen: + 'clear': + '지우기', + 'pen down': + '펜 내리기', + 'pen up': + '펜 올리기', + 'set pen color to %clr': + '펜의 색 %clr 으로 정하기', + 'change pen color by %n': + '펜의 색 %n 만큼 바꾸기', + 'set pen color to %n': + '펜의 색 %n 으로 정하기', + 'change pen shade by %n': + '펜의 그림자 %n 만큼 바꾸기', + 'set pen shade to %n': + '펜의 그림자 %n 으로 정하기', + 'change pen size by %n': + '펜의 크기 %n 만큼 바꾸기', + 'set pen size to %n': + '펜의 크기 %n 으로 정하기', + 'stamp': + '스탬프', + + // control: + 'when %greenflag clicked': + '%greenflag 클릭되었을 때', + 'when %keyHat key pressed': + '%keyHat 키 눌렀을 때', + 'when I am clicked': + '자신이 클릭되었을 때', + 'when I receive %msgHat': + '%msgHat 받을 때', + 'broadcast %msg': + '%msg 방송하기', + 'broadcast %msg and wait': + '%msg 방송하고 기다리기', + 'Message name': + '메세지 이름', + 'wait %n secs': + '%n 초 기다리기', + 'wait until %b': + '%b 까지 기다리기', + 'forever %c': + '무한반복 %c', + 'repeat %n %c': + '반복 %n 회 %c', + 'repeat until %b %c': + '반복 %b 계속 확인 %c', + 'if %b %c': + '만약 %b 라면 %c', + 'if %b %c else %c': + '만약 %b 라면 %c 아니면 %c', + 'report %s': + '%s 출력하기', + 'stop block': + '블록 멈추기', + 'stop script': + '스크립트 멈추기', + 'stop all %stop': + '모두 멈추기 %stop', + 'run %cmdRing %inputs': + '%cmdRing 을(를) %inputs 으로 실행하기', + 'launch %cmdRing %inputs': + '%cmdRing 을(를) %inputs 으로 동시실행하기', + 'call %repRing %inputs': + '%repRing 을(를) %inputs 으로 호출하기', + 'run %cmdRing w/continuation': + '반복해서 %cmdRing 을(를) 실행하기', + 'call %cmdRing w/continuation': + '반복해서 %cmdRing 을(를) 호출하기', + 'warp %c': + '워프 %c', + + // sensing: + 'touching %col ?': + '%col 에 닿기?', + 'touching %clr ?': + '%clr 색에 닿기?', + 'color %clr is touching %clr ?': + '%clr 색이 %clr 색에 닿기?', + 'ask %s and wait': + '%s 을(를) 묻고 기다리기', + 'what\'s your name?': + '당신의 이름은?', + 'answer': + '대답', + 'mouse x': + '마우스 x좌표', + 'mouse y': + '마우스 y좌표', + 'mouse down?': + '마우스 클릭하기?', + 'key %key pressed?': + '%key 키 클릭하기?', + 'distance to %dst': + '%dst 까지 거리', + 'reset timer': + '타이머 초기화', + 'timer': + '타이머', + 'http:// %s': + 'http:// %s', + + 'filtered for %clr': + '%clr 색 추출하기', + 'stack size': + '스택 크기', + 'frames': + '프레임', + + // operators: + '%n mod %n': + '%n 나누기 %n 의 나머지', + 'round %n': + '%n 반올림', + '%fun of %n': + '%n 의 %fun', + 'pick random %n to %n': + '%n 부터 %n 사이의 난수', + '%b and %b': + '%b 그리고 %b', + '%b or %b': + '%b 또는 %b', + 'not %b': + '%b 이(가) 아니다', + 'true': + '참', + 'false': + '거짓', + 'join %words': + '%words 결합하기', + 'hello': + '안녕', + 'world': + '세계', + 'letter %n of %s': + '%n 의 %s 번째 글자', + 'length of %s': + '%s 의 길이', + 'unicode of %s': + '%s 의 유니코드', + 'unicode %n as letter': + '유니코드 %n 에 대한 문자', + 'is %s a %typ ?': + '%s 이(가) %typ 인가요?', + 'type of %s': + '%s 타입', + + // variables: + 'Make a variable': + '변수 만들기', + 'Variable name': + '변수 이름', + 'Delete a variable': + '변수 삭제', + + 'set %var to %s': + '%var 을(를) %s 로 저장', + 'change %var by %n': + '%var 에 %n 씩 누적하기', + 'show variable %var': + '변수 %var 보이기', + 'hide variable %var': + '변수 %var 숨기기', + 'script variables %scriptVars': + '스크립트 변수 %scriptVars', + + // lists: + 'list %exp': + '리스트 %exp', + '%s in front of %l': + '%s 을(를) %l 처음에 추가하기 ', + 'item %idx of %l': + '%idx 항목 %l', + 'all but first of %l': + '%l 첫번째 아이템 제외한 모든 아이템', + 'length of %l': + '%l 의 크기', + '%l contains %s': + '%l 에 %s 포함?', + 'thing': + '아이템', + 'add %s to %l': + '%s 을(를) %l 마지막에 추가하기 ', + 'delete %ida of %l': + '%ida 을(를) %l 에서 삭제하기 ', + 'insert %s at %idx of %l': + '%s 을(를) %idx 위치에 추가하기 %l', + 'replace item %idx of %l with %s': + '%idx 항목 %l 에 %s 로 교체하기', + + // other + 'Make a block': + '블록 만들기', + + // menus + // snap menu + 'About...': + 'Snap! 에 대해서...', + 'Snap! website': + 'Snap! 웹사이트', + 'Download source': + '소스 다운로드', + 'Switch back to user mode': + '사용자 모드로 전환', + 'disable deep-Morphic\ncontext menus\nand show user-friendly ones': + '고도의 모픽 컨텍스트 메뉴를 숨겨 사용자 친화적으로 보여줍니다.', + 'Switch to dev mode': + '개발자 모드로 전환', + 'enable Morphic\ncontext menus\nand inspectors,\nnot user-friendly!': + '모픽 컨텍스트 메뉴와 인스펙터를 사용할 수 있으나, 사용자 친화적이지 않습니다!', + + // project menu + 'Project notes...': + '프로젝트 메모...', + 'New': + '새로 만들기', + 'Open...': + '열기...', + 'Save': + '저장', + 'Save As...': + '다른 이름으로 저장...', + 'Import...': + '가져오기...', + 'file menu import hint': + '내보낸 프로젝트 파일, 블록 라이브러리\n' + + '스프라이트 모양 또는 소리를 가져옵니다.\n\n' + + '일부 웹브라우저에서는 지원되지 않습니다.', + 'Export project as plain text...': + '프로젝트를 텍스트 파일로 내보내기...', + 'Export project...': + '프로젝트 내보내기...', + 'show project data as XML\nin a new browser window': + '프로젝트 데이터를\n새로운 윈도우에 XML 형태로 보여주기', + 'Export blocks...': + '블록 내보내기...', + 'show global custom block definitions as XML\nin a new browser window': + '새롭게 정의한 전역 블록 데이터를\n새로운 윈도우에 XML 형태로 보여주기', + + // settings menu + 'Language...': + '언어선택...', + 'Blurred shadows': + '반투명 그림자', + 'uncheck to use solid drop\nshadows and highlights': + '체크해제하면, 그림자와 하이라이트가\n불투명 상태로 됩니다.', + 'check to use blurred drop\nshadows and highlights': + '체크하면, 그림자와 하이라이트가\n반투명 상태로 됩니다.', + 'Zebra coloring': + '중첩 블록 구분하기', + 'check to enable alternating\ncolors for nested blocks': + '체크하면, 중첩된 블록을\n다른 색으로 구분할 수 있습니다.', + 'uncheck to disable alternating\ncolors for nested block': + '체크해제하면, 중첩된 블록을\n다른 색으로 구분할 수 없습니다.', + 'Prefer empty slot drops': + '빈 슬롯에 입력 가능', + 'settings menu prefer empty slots hint': + '설정 메뉴에 빈 슬롯의\n힌트를 사용할 수 있습니다.', + 'uncheck to allow dropped\nreporters to kick out others': + '체크해제하면, 기존 리포터 블록에\n새로운 리포터 블록으로 대체할 수 있습니다.', + 'Long form input dialog': + '긴 형태의 입력 대화창', + 'check to always show slot\ntypes in the input dialog': + '체크하면, 입력 대화창에\n항상 슬롯의 형태를 보여줍니다.', + 'uncheck to use the input\ndialog in short form': + '체크해제하면, 입력 대화창을\n짧은 형태로 사용합니다.', + 'Virtual keyboard': + '가상 키보드', + 'uncheck to disable\nvirtual keyboard support\nfor mobile devices': + '체크해제하면, 모바일 기기에서\n가상 키보드를 사용할 수 없습니다.', + 'check to enable\nvirtual keyboard support\nfor mobile devices': + '체크하면, 모바일 기기에서\n가상 키보드를 사용할 수 있습니다.', + 'Input sliders': + '입력창에서 슬라이더 사용', + 'uncheck to disable\ninput sliders for\nentry fields': + '체크해제하면, 입력창에서\n슬라이더를 사용할 수 없습니다.', + 'check to enable\ninput sliders for\nentry fields': + '체크하면, 입력창에서\n슬라이더를 사용할 수 있습니다.', + 'Clicking sound': + '블록 클릭시 소리', + 'uncheck to turn\nblock clicking\nsound off': + '체크해제하면, 블록 클릭시\n소리가 꺼집니다.', + 'check to turn\nblock clicking\nsound on': + '체크하면, 블록 클릭시\n소리가 켜집니다.', + 'Thread safe scripts': + '스레드 안전 스크립트', + 'uncheck to allow\nscript reentrancy': + '체크해제하면, 스크립트\n재진입성을 허락합니다.', + 'check to disallow\nscript reentrancy': + '체크하면, 스크립트\n재진입성을 허락하지 않습니다.', + + // inputs + 'with inputs': + '매개변수', + 'input names:': + '매개변수이름:', + 'Input Names:': + '매개변수이름:', + + // context menus: + 'help': + '도움말', + + // blocks: + 'help...': + '블록 도움말...', + 'duplicate': + '복사', + 'make a copy\nand pick it up': + '복사해서\n그 블록을 들고 있습니다.', + 'delete': + '삭제', + 'script pic...': + '스크립트 그림...', + 'open a new window\nwith a picture of this script': + '이 스크립트 그림을\n새로운 윈도우에서 엽니다.', + 'ringify': + '형태변환', + + // custom blocks: + 'delete block definition...': + '블록 삭제', + 'edit...': + '편집…', + + // sprites: + 'edit': + '편집', + 'export...': + '내보내기...', + + // scripting area + 'clean up': + '스크립트 정리하기', + 'arrange scripts\nvertically': + '스크립트를\n수직으로 정렬한다.', + 'add comment': + '주석 추가하기', + 'make a block...': + '블록 만들기...', + + // costumes + 'rename': + '이름수정', + 'export': + '내보내기', + + // sounds + 'Play sound': + '소리 재생', + 'Stop sound': + '소리 정지', + 'Stop': + '정지', + 'Play': + '재생', + + // dialogs + // buttons + 'OK': + 'OK', + 'Cancel': + '취소', + 'Yes': + '예', + 'No': + '아니오', + + // help + 'Help': + '도움말', + + // costume editor + 'Costume Editor': + '모양 편집기', + 'click or drag crosshairs to move the rotation center': + '클릭 또는 드래그 해서 회전 중심 설정하기', + + // project notes + 'Project Notes': + '프로젝트 메모', + + // new project + 'New Project': + '새로운 프로젝트', + 'Replace the current project with a new one?': + '현재 프로젝트를 새로운 프로젝트로 교체하시겠습니까?', + + // open project + 'Open Projekt': + '프로젝트 열기', + + // save project + 'Save Project As...': + '프로젝트 저장...', + + // export blocks + 'Export blocks': + '블록 내보내기', + 'this project doesn\'t have any\ncustom global blocks yet': + '이 프로젝트에는 아직 새로 만든 전역 블록이 없습니다.', + 'select': + '선택', + 'all': + '모두', + 'none': + '모두 취소', + + // variable dialog + 'for all sprites': + '모든 스프라이트에 대해', + 'for this sprite only': + '이 스프라이트에 대해', + + // block dialog + 'Change block': + '블록 변경', + 'Command': + '커맨드', + 'Reporter': + '리포터', + 'Predicate': + '프레디키트', + + // block editor + 'Block Editor': + '블록 편집기', + 'Apply': + '적용', + + // block deletion dialog + 'Delete Custom Block': + '블록 삭제', + 'block deletion dialog text': + '이 블록과 모든 인스턴스를\n 삭제해도 괜찮습니까?', + + // input dialog + 'Create input name': + '입력 이름 생성', + 'Edit input name': + '입력 이름 편집', + 'Title text': + '제목 텍스트', + 'Input name': + '입력 이름', + 'Delete': + '삭제', + 'Object': + '객체', + 'Number': + '숫자', + 'Text': + '텍스트', + 'List': + '리스트', + 'Any type': + '아무타입', + 'Boolean (T/F)': + '불리언 (참/거짓)', + 'Command\n(inline)': + '커맨드\n(인라인)', + 'Command\n(C-shape)': + '커맨드 \n(C-모양)', + 'Any\n(unevaluated)': + '아무값\n(평가되지 않음)', + 'Boolean\n(unevaluated)': + '불리언\n(평가되지 않음)', + 'Single input.': + '단일 입력', + 'Default Value:': + '기본 값:', + 'Multiple inputs (value is list of inputs)': + '다중 입력 (값은 리스트의 입력값입니다)', + 'Upvar - make internal variable visible to caller': + 'Upvar - 호출자에게 내부 변수 보이게 만들기', + + // About Snap + 'About Snap': + 'Snap에 대해서', + 'Back...': + '뒤로…', + 'License...': + '라이센스...', + 'Modules...': + '모듈...', + 'Credits...': + '크레디트...', + 'Translators...': + '번역자', + 'License': + '라이센스', + 'current module versions:': + '현재 모듈 버전:', + 'Contributors': + '기여자:', + 'Translations': + '번역', + + // variable watchers + 'normal': + '보통 읽기', + 'large': + '크게 보기', + 'slider': + '슬라이더', + 'slider min...': + '슬라이더 최소값 설정...', + 'slider max...': + '슬라이더 최대값 설정...', + 'Slider minimum value': + '슬라이더 최소값 설정', + 'Slider maximum value': + '슬라이더 최대값 설정', + + // list watchers + 'length: ': + '길이: ', + + // coments + 'add comment here...': + '여기에 주석 추가…', + + // drow downs + // directions + '(90) right': + '(90) 오른쪽', + '(-90) left': + '(-90) 왼쪽', + '(0) up': + '(0) 위', + '(180) right': + '(180) 아래', + + // collision detection + 'mouse-pointer': + '마우스의 포인터', + 'edge': + '벽', + 'pen trails': + '펜의 궤적', + + // costumes + 'Turtle': + '터틀', + + // graphical effects + 'ghost': + '유령', + + // keys + 'space': + '스페이스', + 'up arrow': + '위쪽 화살표', + 'down arrow': + '아래쪽 화살표', + 'right arrow': + '오른쪽 화살표', + 'left arrow': + '왼쪽 화살표', + 'a': + 'a', + 'b': + 'b', + 'c': + 'c', + 'd': + 'd', + 'e': + 'e', + 'f': + 'f', + 'g': + 'g', + 'h': + 'h', + 'i': + 'i', + 'j': + 'j', + 'k': + 'k', + 'l': + 'l', + 'm': + 'm', + 'n': + 'n', + 'o': + 'o', + 'p': + 'p', + 'q': + 'q', + 'r': + 'r', + 's': + 's', + 't': + 't', + 'u': + 'u', + 'v': + 'v', + 'w': + 'w', + 'x': + 'x', + 'y': + 'y', + 'z': + 'z', + '0': + '0', + '1': + '1', + '2': + '2', + '3': + '3', + '4': + '4', + '5': + '5', + '6': + '6', + '7': + '7', + '8': + '8', + '9': + '9', + + // messages + 'new...': + '새로 만들기...', + + // math functions + 'abs': + '절대값', + 'sqrt': + '제곱근', + 'sin': + 'sin', + 'cos': + 'cos', + 'tan': + 'tan', + 'asin': + 'asin', + 'acos': + 'acos', + 'atan': + 'atan', + 'ln': + 'ln', + 'e^': + 'e^', + + // data types + 'number': + '숫자', + 'text': + '텍스트', + 'Boolean': + '불리언', + 'list': + '리스트', + 'command': + '커맨드', + 'reporter': + '리포터', + 'predicate': + '프레디키트', + + // list indices + 'last': + '마지막', + 'any': + '임의', + + // missing entries + 'Untitled': + '이름없음', + 'Open Project': + '프로젝트 열기', + 'Open': + '열기', + '(empty)': + '(공란)', + 'Saved!': + '저장했습니다!', + 'Delete Project': + '프로젝트 삭제', + 'Are you sure you want to delete': + '정말로 삭제합니까?', + 'unringify': + '형태변환취소', + 'rename...': + '이름수정...', + '(180) down': + '(180) 아래', + 'Ok': + 'OK' + +}; diff --git a/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/lang-nl.js b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/lang-nl.js new file mode 100644 index 0000000..630e49f --- /dev/null +++ b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/lang-nl.js @@ -0,0 +1,1216 @@ +/* + + lang-nl.js + + This file is part of Snap!. + + Snap! is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + + + + Note to Translators: + -------------------- + At this stage of development, Snap! can be translated to any LTR language + maintaining the current order of inputs (formal parameters in blocks). + + Translating Snap! is easy: + + + 1. Download + + Download the sources and extract them into a local folder on your + computer: + + + + Use the German translation file (named 'lang-de.js') as template for your + own translations. Start with editing the original file, because that way + you will be able to immediately check the results in your browsers while + you're working on your translation (keep the local copy of snap.html open + in your web browser, and refresh it as you progress with your + translation). + + + 2. Edit + + Edit the translation file with a regular text editor, or with your + favorite JavaScript editor. + + In the first non-commented line (the one right below this + note) replace "de" with the two-letter ISO 639-1 code for your language, + e.g. + + fr - French => SnapTranslator.dict.fr = { + it - Italian => SnapTranslator.dict.it = { + pl - Polish => SnapTranslator.dict.pl = { + pt - Portuguese => SnapTranslator.dict.pt = { + es - Spanish => SnapTranslator.dict.es = { + el - Greek => => SnapTranslator.dict.el = { + + etc. (see ) + + + 3. Translate + + Then work through the dictionary, replacing the German strings against + your translations. The dictionary is a straight-forward JavaScript ad-hoc + object, for review purposes it should be formatted as follows: + + { + 'English string': + 'Translation string', + 'last key': + } 'last value' + + and you only edit the indented value strings. Note that each key-value + pair needs to be delimited by a comma, but that there shouldn't be a comma + after the last pair (again, just overwrite the template file and you'll be + fine). + + If something doesn't work, or if you're unsure about the formalities you + should check your file with + + + + This will inform you about any missed commas etc. + + + 4. Accented characters + + Depending on which text editor and which file encoding you use you can + directly enter special characters (e.g. Umlaut, accented characters) on + your keyboard. However, I've noticed that some browsers may not display + special characters correctly, even if other browsers do. So it's best to + check your results in several browsers. If you want to be on the safe + side, it's even better to escape these characters using Unicode. + + see: + + + 5. Block specs: + + At this time your translation of block specs will only work + correctly, if the order of formal parameters and their types + are unchanged. Placeholders for inputs (formal parameters) are + indicated by a preceding % prefix and followed by a type + abbreviation. + + For example: + + 'say %s for %n secs' + + can currently not be changed into + + 'say %n secs long %s' + + and still work as intended. + + Similarly + + 'point towards %dst' + + cannot be changed into + + 'point towards %cst' + + without breaking its functionality. + + + 6. Submit + + When you're done, rename the edited file by replacing the "de" part of the + filename with the two-letter ISO 639-1 code for your language, e.g. + + fr - French => lang-fr.js + it - Italian => lang-it.js + pl - Polish => lang-pl.js + pt - Portuguese => lang-pt.js + es - Spanish => lang-es.js + el - Greek => => lang-el.js + + and send it to me for inclusion in the official Snap! distribution. + Once your translation has been included, Your name will the shown in the + "Translators" tab in the "About Snap!" dialog box, and you will be able to + directly launch a translated version of Snap! in your browser by appending + + lang:xx + + to the URL, xx representing your translations two-letter code. + + + 7. Known issues + + In some browsers accents or ornaments located in typographic ascenders + above the cap height are currently (partially) cut-off. + + Enjoy! + -Jens +*/ + +/*global SnapTranslator*/ + +SnapTranslator.dict.nl = { + +/* + Special characters: (see ) + + Ä, ä \u00c4, \u00e4 + Ö, ö \u00d6, \u00f6 + Ü, ü \u00dc, \u00fc + ß \u00df +*/ + + // translations meta information + 'language_name': + 'Nederlands', // the name as it should appear in the language menu + 'language_translator': + 'Sjoerd Dirk Meijer, Frank Sierens', // your name for the Translators tab + 'translator_e-mail': + 'sjoerddirk@fromScratchEd.nl, frank.sierens@telenet.be', // optional + 'last_changed': + '2013-08-12', // this, too, will appear in the Translators tab + + // GUI + // control bar: + 'untitled': + 'zonder titel', + 'development mode': + 'ontwikkelmodus', + + // categories: + 'Motion': + 'Bewegen', + 'Looks': + 'Uiterlijk', + 'Sound': + 'Geluid', + 'Pen': + 'Pen', + 'Control': + 'Besturen', + 'Sensing': + 'Waarnemen', + 'Operators': + 'Functies', + 'Variables': + 'Variabelen', + 'Lists': + 'Lijsten', + 'Other': + 'Overig', + + // editor: + 'draggable': + 'versleepbaar', + + // tabs: + 'Scripts': + 'Scripts', + 'Costumes': + 'Uiterlijken', + 'Sounds': + 'Geluiden', + + // names: + 'Sprite': + 'Sprite', + 'Stage': + 'Speelveld', + + // rotation styles: + 'don\'t rotate': + 'niet draaibaar', + 'can rotate': + 'draaibaar', + 'only face left/right': + 'alleen links/rechts draaibaar', + + // new sprite button: + 'add a new sprite': + 'een nieuwe sprite toevoegen', + + // tab help + 'costumes tab help': + 'help uiterlijkentab', + 'import a sound from your computer\nby dragging it into here': + 'importeer een geluid vanaf je computer\ndoor deze hierin te slepen', + + // primitive blocks: + + /* + Attention Translators: + ---------------------- + At this time your translation of block specs will only work + correctly, if the order of formal parameters and their types + are unchanged. Placeholders for inputs (formal parameters) are + indicated by a preceding % prefix and followed by a type + abbreviation. + + For example: + + 'say %s for %n secs' + + can currently not be changed into + + 'say %n secs long %s' + + and still work as intended. + + Similarly + + 'point towards %dst' + + cannot be changed into + + 'point towards %cst' + + without breaking its functionality. + */ + + // motion: + 'Stage selected:\nno motion primitives': + 'Toneel geselecteerd: geen standaardbeweging mogelijk', + + 'move %n steps': + 'neem %n stappen', + 'turn %clockwise %n degrees': + 'draai %clockwise %n graden', + 'turn %counterclockwise %n degrees': + 'draai %counterclockwise %n graden', + 'point in direction %dir': + 'wijs naar richting %dir', + 'point towards %dst': + 'richt naar %dst', + 'go to x: %n y: %n': + 'ga naar x: %n y: %n', + 'go to %dst': + 'ga naar %dst', + 'glide %n secs to x: %n y: %n': + 'glijd in %n sec. naar x: %n y: %n', + 'change x by %n': + 'verander x met %n', + 'set x to %n': + 'maak x %n', + 'change y by %n': + 'verander y met %n', + 'set y to %n': + 'maak y %n', + 'if on edge, bounce': + 'aan de rand, keer om', + 'x position': + 'x-positie', + 'y position': + 'y-positie', + 'direction': + 'richting', + + // looks: + 'switch to costume %cst': + 'wissel naar uiterlijk %cst', + 'next costume': + 'volgende uiterlijk', + 'costume #': + 'uiterlijk #', + 'say %s for %n secs': + 'zeg %s gedurende %n sec.', + 'say %s': + 'zeg %s', + 'think %s for %n secs': + 'denk %s gedurende %n sec.', + 'think %s': + 'denk %s', + 'Hello!': + 'Hallo!', + 'Hmm...': + 'Hmm...', + 'change %eff effect by %n': + 'verander %eff -effect met %n', + 'set %eff effect to %n': + 'maak %eff -effect %n', + 'clear graphic effects': + 'zet grafische effecten uit', + 'change size by %n': + 'verander grootte met %n', + 'set size to %n %': + 'maak grootte %n %', + 'size': + 'grootte', + 'show': + 'verschijn', + 'hide': + 'verdwijn', + 'go to front': + 'ga naar voorgrond', + 'go back %n layers': + 'ga %n lagen terug', + + 'development mode \ndebugging primitives:': + 'ontwikkelmodus \ndebugging basisblokken', + 'console log %mult%s': + 'console log %mult%s', + 'alert %mult%s': + 'waarschuwing %mult%s', + + // sound: + 'play sound %snd': + 'start geluid %snd', + 'play sound %snd until done': + 'start geluid %snd en wacht', + 'stop all sounds': + 'stop alle geluiden', + 'rest for %n beats': + 'pauzeer %n tellen', + 'play note %n for %n beats': + 'speel noot %n %n tellen', + 'change tempo by %n': + 'verander tempo met %n', + 'set tempo to %n bpm': + 'maak tempo %n bpm', + 'tempo': + 'tempo', + + // pen: + 'clear': + 'wissen', + 'pen down': + 'pen neer', + 'pen up': + 'pen omhoog', + 'set pen color to %clr': + 'maak penkleur %clr', + 'change pen color by %n': + 'verander penkleur met %n', + 'set pen color to %n': + 'maak penkleur %n', + 'change pen shade by %n': + 'verander penschaduw met %n', + 'set pen shade to %n': + 'maak penschaduw %n', + 'change pen size by %n': + 'verander pengrootte met %n', + 'set pen size to %n': + 'maak pengrootte %n', + 'stamp': + 'stempel', + + // control: + 'when %greenflag clicked': + 'wanneer %greenflag wordt aangeklikt', + 'when %keyHat key pressed': + 'wanneer %keyHat wordt ingedrukt', + 'when I am clicked': + 'wanneer er op mij wordt geklikt', + 'when I receive %msgHat': + 'wanneer ik %msgHat ontvang', + 'broadcast %msg': + 'zend signaal %msg', + 'broadcast %msg and wait': + 'zend signaal %msg en wacht', + 'Message name': + 'signaalnaam', + 'message': + 'signaal', + 'any message': + 'elk signaal', + 'wait %n secs': + 'wacht %n sec.', + 'wait until %b': + 'wacht tot %b', + 'forever %c': + 'herhaal %c', + 'repeat %n %c': + 'herhaal %n keer %c', + 'repeat until %b %c': + 'herhaal tot %b %c', + 'if %b %c': + 'als %b %c', + 'if %b %c else %c': + 'als %b %c anders %c', + 'report %s': + 'rapporteer %s', + 'stop block': + 'stop blok', + 'stop script': + 'stop script', + 'stop all %stop': + 'stop alle %stop', + 'pause all %pause': + 'pauzeer alles %pause', + 'run %cmdRing %inputs': + 'voer %cmdRing uit %inputs', + 'launch %cmdRing %inputs': + 'start %cmdRing %inputs', + 'call %repRing %inputs': + 'roep %repRing aan %inputs', + 'run %cmdRing w/continuation': + 'voer %cmdRing uit en ga door', + 'call %cmdRing w/continuation': + 'roep %cmdRing aan en ga door', + 'warp %c': + 'warp %c', + 'when I start as a clone': + 'wanneer ik als kloon start', + 'create a clone of %cln': + 'maak kloon van %cln', + 'myself': + 'mijzelf', + 'delete this clone': + 'verwijder deze kloon', + + // sensing: + 'touching %col ?': + 'raak ik %col ?', + 'touching %clr ?': + 'raak ik kleur %clr ?', + 'color %clr is touching %clr ?': + 'kleur %clr raakt %clr ?', + 'ask %s and wait': + 'vraag %s en wacht', + 'what\'s your name?': + 'Hoe heet je?', + 'answer': + 'antwoord', + 'mouse x': + 'muis x', + 'mouse y': + 'muis y', + 'mouse down?': + 'muis ingedrukt?', + 'key %key pressed?': + 'toets %key ingedrukt?', + 'distance to %dst': + 'afstand tot %dst', + 'reset timer': + 'zet tijd op nul', + 'timer': + 'tijd', + '%att of %spr': + '%att van %spr', + 'http:// %s': + 'http:// %s', + 'turbo mode?': + 'turbomodus?', + 'set turbo mode to %b': + 'zet turbomodus op %b', + + 'filtered for %clr': + 'gefilterd op %clr', + 'stack size': + 'stapelgrootte', + 'frames': + 'beelden', + + // operators: + '%n mod %n': + '%n modulo %n', + 'round %n': + 'afgerond %n', + '%fun of %n': + '%fun van %n', + 'pick random %n to %n': + 'willekeurig getal tussen %n en %n', + '%b and %b': + '%b en %b', + '%b or %b': + '%b of %b', + 'not %b': + 'niet %b', + 'true': + 'waar', + 'false': + 'onwaar', + 'join %words': + 'voeg %words samen', + 'hello': + 'hallo', + 'world': + 'wereld', + 'letter %n of %s': + 'letter %n van %s', + 'length of %s': + 'lengte van %s', + 'unicode of %s': + 'unicode waarde van %s', + 'unicode %n as letter': + 'unicode %n als letter', + 'is %s a %typ ?': + 'is %s een %typ ?', + 'is %s identical to %s ?': + 'is %s gelijk aan %s ?', + 'type of %s': + 'type van %s', + + // variables: + 'Make a variable': + 'Maak een variabele', + 'Variable name': + 'Variabelenaam', + 'Script variable name': + 'Scriptvariabelenaam', + 'Delete a variable': + 'Variabele wissen', + + 'set %var to %s': + 'maak %var %s', + 'change %var by %n': + 'verander %var met %n', + 'show variable %var': + 'toon variabele %var', + 'hide variable %var': + 'verberg variabele %var', + 'script variables %scriptVars': + 'scriptvariabelen %scriptVars', + + // lists: + 'list %exp': + 'lijst %exp', + '%s in front of %l': + '%s voor %l', + 'item %idx of %l': + 'item %idx van %l', + 'all but first of %l': + 'alles, behalve de eerste van %l', + 'length of %l': + 'lengte van %l', + '%l contains %s': + '%l bevat %s', + 'thing': + 'ding', + 'add %s to %l': + 'voeg %s in op %l', + 'delete %ida of %l': + 'verwijder %ida van %l', + 'insert %s at %idx of %l': + 'voeg %s op %idx aan %l toe', + 'replace item %idx of %l with %s': + 'vervang item %idx van %l door %s', + + // other + 'Make a block': + 'Maak een blok', + + // menus + // snap menu + 'About...': + 'Over...', + 'Reference manual': + 'Handleiding', + 'Snap! website': + 'Snap!-website', + 'Download source': + 'Broncode downloaden', + 'Switch back to user mode': + 'Terug naar gebruikersmodus', + 'disable deep-Morphic\ncontext menus\nand show user-friendly ones': + 'verlaat Morphic', + 'Switch to dev mode': + 'Naar ontwikkelmodus wisselen', + 'enable Morphic\ncontext menus\nand inspectors,\nnot user-friendly!': + 'gebruik Morphic\nniet gebruikersvriendelijk!', + + // project menu + 'Project notes...': + 'Notities...', + 'New': + 'Nieuw', + 'Open...': + 'Openen...', + 'Save': + 'opslaan', + 'Save As...': + 'Opslaan als...', + 'Import...': + 'Importeren...', + 'file menu import hint': + 'importeer een project,\neen bibliotheek met blokken,\neen uiterlijk of een geluid', + 'Export project as plain text...': + 'Project exporteren als tekst...', + 'Export project...': + 'Project exporteren...', + 'show project data as XML\nin a new browser window': + 'Toon projectdata als XML\nin een nieuw browservenster', + 'Export blocks...': + 'Blokken exporteren...', + 'show global custom block definitions as XML\nin a new browser window': + 'toon globale aangepaste blokdefinities\nals XML in browser', + 'Import tools': + 'Importeer tools', + 'load the official library of\npowerful blocks': + 'laad de officiele bibliotheek\nmet krachtige blokken', + 'Libraries...': + 'Bibliotheken...', + 'Import library': + 'Importeer bibliotheek', + + // cloud menu + 'Login...': + 'Inloggen...', + 'Signup...': + 'Registeren...', + + // settings menu + 'Language...': + 'Taal...', + 'Zoom blocks...': + 'Blokken inzoomen...', + 'Blurred shadows': + 'Onscherpe schaduwen', + 'uncheck to use solid drop\nshadows and highlights': + 'uitvinken om scherpe schaduwen\nen uitlichtingen te krijgen', + 'check to use blurred drop\nshadows and highlights': + 'aanvinken om onscherpe schaduwen\nen uitlichtingen te krijgen', + 'Zebra coloring': + 'Zebrakleuren', + 'check to enable alternating\ncolors for nested blocks': + 'afwisselende kleuren voor\ngeneste blokken aanzetten', + 'uncheck to disable alternating\ncolors for nested block': + 'afwisselende kleuren voor\ngeneste blokken uitzetten', + 'Dynamic input labels': + 'Dynamische inputlabels', + 'uncheck to disable dynamic\nlabels for variadic inputs': + 'dynamische labels voor\nmeervaksinvoer uitzetten', + 'check to enable dynamic\nlabels for variadic inputs': + 'dynamische labels voor\nmeervaksinvoer aanzetten', + 'Prefer empty slot drops': + 'Voorkeur voor lege plaatshouders', + 'settings menu prefer empty slots hint': + 'lege plaatshouders in instellingenmenu', + 'uncheck to allow dropped\nreporters to kick out others': + 'uitschakelen om lege functies\n anderen uit te sluiten', + 'Long form input dialog': + 'Lang formulier-invoerscherm', + 'check to always show slot\ntypes in the input dialog': + 'aanvinken om data type in\ninvoerscherm te zien', + 'uncheck to use the input\ndialog in short form': + 'uitvinken voor verkort invoerscherm', + 'Virtual keyboard': + 'Virtueel toetsenbord', + 'uncheck to disable\nvirtual keyboard support\nfor mobile devices': + 'uitvinken om het virtueel\ntoetsenbord uit te schakelen\nvoor mobiele toestellen', + 'check to enable\nvirtual keyboard support\nfor mobile devices': + 'aanvinken om het virtueel\ntoetsenbord in te schakelen\nvoor mobiele toestellen', + 'Input sliders': + 'Invoer schuifbalk', + 'uncheck to disable\ninput sliders for\nentry fields': + 'uitvinken om\nschuifbalken voor invoer\nuit te schakelen', + 'check to enable\ninput sliders for\nentry fields': + 'aanvinken om\nschuifbalken voor invoer\nin te schakelen', + 'Clicking sound': + 'Klikgeluid', + 'uncheck to turn\nblock clicking\nsound off': + 'uitvinken om\nklikgeluiden uit te\nschakelen', + 'check to turn\nblock clicking\nsound on': + 'aanvinken om\nklikgeluid in te\nschakelen', + 'Animations': + 'Animaties', + 'uncheck to disable\nIDE animations': + 'IDE-animaties\nuitschakelen', + 'Turbo mode': + 'Turbomodus', + 'check to prioritize\nscript execution': + 'aanvinken om scriptuitvoering\nprioriteit te geven', + 'uncheck to run scripts\nat normal speed': + 'uitvinken voor scripuitvoering\nop normale snelheid', + 'check to enable\nIDE animations': + 'aanvinken om IDE-animaties\ntoe te laten', + 'Thread safe scripts': + 'Thread-veilige scripts', + 'uncheck to allow\nscript reentrance': + 'uitvinken om niet-\nafgewerkte scripts opnieuw\nte starten', + 'check to disallow\nscript reentrance': + 'aanvinken om niet-\nafgewerkte scripts niet opnieuw\n te starten', + 'Prefer smooth animations': + 'Voorkeur voor vloeiende animatie', + 'uncheck for greater speed\nat variable frame rates': + 'uitvinken voor hogere snelheid\nbij variabele framerates', + 'check for smooth, predictable\nanimations across computers': + 'aanvinken voor vloeiende,\nvoorspelbare animaties tussen computers', + + // inputs + 'with inputs': + 'met invoer', + 'input names:': + 'invoernamen:', + 'Input Names:': + 'Invoernamen:', + 'input list:': + 'invoerlijst:', + + // context menus: + 'help': + 'help', + + // palette: + 'hide primitives': + 'basisblokken verbergen', + 'show primitives': + 'basisblokken tonen', + + // blocks: + 'help...': + 'help...', + 'relabel...': + 'label hernoemen...', + 'duplicate': + 'kopieer', + 'make a copy\nand pick it up': + 'maak een kopie\nen gebruikt het', + 'only duplicate this block': + 'alleen dit blok kopi\u00EBren', + 'delete': + 'verwijder', + 'script pic...': + 'scriptafbeelding...', + 'open a new window\nwith a picture of this script': + 'open een nieuw venster\nmet de afbeelding van dit script', + 'ringify': + 'omringen', + 'unringify': + 'niet omringen', + + // custom blocks: + 'delete block definition...': + 'verwijder blokdefinitie', + 'edit...': + 'bewerken...', + + // sprites: + 'edit': + 'bewerken', + 'detach from': + 'losmaken van', + 'detach all parts': + 'alle onderdelen losmaken', + 'export...': + 'exporteren...', + + // stage: + 'show all': + 'toon alles', + 'pic...': + 'afbeelding...', + 'open a new window\nwith a picture of the stage': + 'open een nieuw\nbrowservenster met een\nafbeelding van het\nspeelveld', + + // scripting area + 'clean up': + 'opruimen', + 'arrange scripts\nvertically': + 'scripts verticaal\nordenen', + 'add comment': + 'opmerking toevoegen', + 'undrop': + 'ongedaan maken', + 'undo the last\nblock drop\nin this pane': + 'de laatste blokbeweging\nongedaan maken', + 'scripts pic...': + 'scripts-afbeelding...', + 'open a new window\nwith a picture of all scripts': + 'open een nieuw venster\nmet een afbeelding\nvan alle scripts', + 'make a block...': + 'maak een blok...', + + // costumes + 'rename': + 'hernoemen', + 'export': + 'exporteren', + 'rename costume': + 'uiterlijk hernoemen', + + // sounds + 'Play sound': + 'Geluid afspelen', + 'Stop sound': + 'Geluid stoppen', + 'Stop': + 'Stop', + 'Play': + 'Speel', + 'rename sound': + 'geluid hernoemen', + + // dialogs + // buttons + 'OK': + 'OK', + 'Ok': + 'Ok', + 'Cancel': + 'Annuleren', + 'Yes': + 'Ja', + 'No': + 'Nee', + + // help + 'Help': + 'Help', + + // zoom blocks + 'Zoom blocks': + 'Blokken inzoomen', + 'build': + 'bouw', + 'your own': + 'je eigen', + 'blocks': + 'blokken', + 'normal (1x)': + 'normaal (1x)', + 'demo (1.2x)': + 'demo (1.2x)', + 'presentation (1.4x)': + 'presentatie (1.4x)', + 'big (2x)': + 'groot (2x)', + 'huge (4x)': + 'enorm (4x)', + 'giant (8x)': + 'gigantisch (8x)', + 'monstrous (10x)': + 'monsterlijk (10x)', + + // Project Manager + 'Untitled': + 'Zonder titel', + 'Open Project': + 'Project openen', + '(empty)': + '(leeg)', + 'Saved!': + 'Opgeslagen!', + 'Delete Project': + 'Projekt verwijderen', + 'Are you sure you want to delete': + 'Weet je zeker dat je wilt verwijderen?', + 'rename...': + 'hernoemen...', + + // costume editor + 'Costume Editor': + 'Uiterlijk bewerken', + 'click or drag crosshairs to move the rotation center': + 'Klik of sleep de kruisdraden om het rotatiecentrum te verplaatsen', + + // project notes + 'Project Notes': + 'Projectnotities', + + // new project + 'New Project': + 'Nieuw project', + 'Replace the current project with a new one?': + 'Vervang het huidige project door een nieuwe?', + + // save project + 'Save Project As...': + 'Project opslaan als...', + + // export blocks + 'Export blocks': + 'Exporteer blokkken', + 'Import blocks': + 'Importeer blokken', + 'this project doesn\'t have any\ncustom global blocks yet': + 'dit project\nbevat nog geen \nglobale blokken', + 'select': + 'selecteer', + 'all': + 'alle', + 'none': + 'niets', + + // variable dialog + 'for all sprites': + 'voor alle sprite', + 'for this sprite only': + 'alleen voor deze sprite', + + // block dialog + 'Change block': + 'Blok veranderen', + 'Command': + 'Commando', + 'Reporter': + 'Functie', + 'Predicate': + 'Predicaat', + + // block editor + 'Block Editor': + 'Blok bewerken', + 'Apply': + 'Toepassen', + + // block deletion dialog + 'Delete Custom Block': + 'Verwijder aangepast blok', + 'block deletion dialog text': + 'Moet dit blok met al zijn\ninstanties verwijderd worden?', + + // input dialog + 'Create input name': + 'Maak invoernaam', + 'Edit input name': + 'Invoernaam bewerken', + 'Edit label fragment': + 'Labelfragment bewerken', + 'Title text': + 'Titel', + 'Input name': + 'Invoernaam', + 'Delete': + 'Verwijder', + 'Object': + 'Object', + 'Number': + 'Getal', + 'Text': + 'Tekst', + 'List': + 'Lijst', + 'Any type': + 'Elk type', + 'Boolean (T/F)': + 'Booleaans (waar/niet waar)', + 'Command\n(inline)': + 'Opdracht\n(inline)', + 'Command\n(C-shape)': + 'Opdracht\n(C-vorm)', + 'Any\n(unevaluated)': + 'Alle\n(onge\u00EBvalueerd)', + 'Boolean\n(unevaluated)': + 'Booleaans\n(onge\u00EBvalueerd)', + 'Single input.': + 'Enkelvoudige invoer.', + 'Default Value:': + 'Standaardwaarde:', + 'Multiple inputs (value is list of inputs)': + 'Meervoudige invoer (als lijst)', + 'Upvar - make internal variable visible to caller': + 'Upvar - maak interne variabele zichtbaar voor aanroeper', + + // About Snap + 'About Snap': + 'Over Snap', + 'Back...': + 'Vorige...', + 'License...': + 'Licentie...', + 'Modules...': + 'Module...', + 'Credits...': + 'Credits...', + 'Translators...': + 'Vertalers...', + 'License': + 'Licentie', + 'current module versions:': + 'huidige moduleversies', + 'Contributors': + 'Bijdragers', + 'Translations': + 'Vertalingen', + + // variable watchers + 'normal': + 'normaal', + 'large': + 'groot', + 'slider': + 'schuifbalk', + 'slider min...': + 'schuif min...', + 'slider max...': + 'schuif max...', + 'import...': + 'importeren...', + 'Slider minimum value': + 'Minimumwaarde van schuifbalk', + 'Slider maximum value': + 'Maximumwaarde van schuifbalk', + + // list watchers + 'length: ': + 'lengte: ', + + // coments + 'add comment here...': + 'hier commentaar invoegen', + + // drow downs + // directions + '(90) right': + '(90) rechts', + '(-90) left': + '(-90) links', + '(0) up': + '(0) omhoog', + '(180) down': + '(180) omlaag', + + // collision detection + 'mouse-pointer': + 'muisaanwijzer', + 'edge': + 'rand', + 'pen trails': + 'penspoor', + + // costumes + 'Turtle': + 'Schildpad', + 'Empty': + 'Leeg', + + // graphical effects + 'ghost': + 'geest', + + // keys + 'space': + 'spatiebalk', + 'up arrow': + 'pijltje omhoog', + 'down arrow': + 'pijltje omlaag', + 'right arrow': + 'pijltje naar rechts', + 'left arrow': + 'pijltje naar links', + 'a': + 'a', + 'b': + 'b', + 'c': + 'c', + 'd': + 'd', + 'e': + 'e', + 'f': + 'f', + 'g': + 'g', + 'h': + 'h', + 'i': + 'i', + 'j': + 'j', + 'k': + 'k', + 'l': + 'l', + 'm': + 'm', + 'n': + 'n', + 'o': + 'o', + 'p': + 'p', + 'q': + 'q', + 'r': + 'r', + 's': + 's', + 't': + 't', + 'u': + 'u', + 'v': + 'v', + 'w': + 'w', + 'x': + 'x', + 'y': + 'y', + 'z': + 'z', + '0': + '0', + '1': + '1', + '2': + '2', + '3': + '3', + '4': + '4', + '5': + '5', + '6': + '6', + '7': + '7', + '8': + '8', + '9': + '9', + + // messages + 'new...': + 'nieuw...', + + // math functions + 'abs': + 'abs', + 'floor': + 'afgerond', + 'sqrt': + 'wortel', + 'sin': + 'sin', + 'cos': + 'cos', + 'tan': + 'tan', + 'asin': + 'asin', + 'acos': + 'acos', + 'atan': + 'atan', + 'ln': + 'ln', + 'e^': + 'e^', + + // data types + 'number': + 'getal', + 'text': + 'tekst', + 'Boolean': + 'booleaans', + 'list': + 'lijst', + 'command': + 'commando', + 'reporter': + 'functie', + 'predicate': + 'predicaat', + + // list indices + 'last': + 'laatste', + 'any': + 'alle' +}; + diff --git a/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/lang-no.js b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/lang-no.js new file mode 100644 index 0000000..01b6dc8 --- /dev/null +++ b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/lang-no.js @@ -0,0 +1,12 @@ +/* lang-no.js Norwegian translation for SNAP! written by Olav A Marschall Copyright (C) 2013 by Jens Mnig This file is part of Snap!. Snap! is free software: you can redistribute it and/or modify it under the terms of the GNU Affero 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 Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . Note to Translators: -------------------- At this stage of development, Snap! can be translated to any LTR language maintaining the current order of inputs (formal parameters in blocks). Translating Snap! is easy: 1. Download Download the sources and extract them into a local folder on your computer: Use the German translation file (named 'lang-de.js') as template for your own translations. Start with editing the original file, because that way you will be able to immediately check the results in your browsers while you're working on your translation (keep the local copy of snap.html open in your web browser, and refresh it as you progress with your translation). 2. Edit Edit the translation file with a regular text editor, or with your favorite JavaScript editor. In the first non-commented line (the one right below this note) replace "de" with the two-letter ISO 639-1 code for your language, e.g. fr - French => SnapTranslator.dict.fr = { it - Italian => SnapTranslator.dict.it = { pl - Polish => SnapTranslator.dict.pl = { pt - Portuguese => SnapTranslator.dict.pt = { es - Spanish => SnapTranslator.dict.es = { el - Greek => => SnapTranslator.dict.el = { etc. (see ) 3. Translate Then work through the dictionary, replacing the German strings against your translations. The dictionary is a straight-forward JavaScript ad-hoc object, for review purposes it should be formatted as follows: { 'English string': 'Translation string', 'last key': } 'last value' and you only edit the indented value strings. Note that each key-value pair needs to be delimited by a comma, but that there shouldn't be a comma after the last pair (again, just overwrite the template file and you'll be fine). If something doesn't work, or if you're unsure about the formalities you should check your file with This will inform you about any missed commas etc. 4. Accented characters Depending on which text editor and which file encoding you use you can directly enter special characters (e.g. Umlaut, accented characters) on your keyboard. However, I've noticed that some browsers may not display special characters correctly, even if other browsers do. So it's best to check your results in several browsers. If you want to be on the safe side, it's even better to escape these characters using Unicode. see: 5. Block specs: At this time your translation of block specs will only work correctly, if the order of formal parameters and their types are unchanged. Placeholders for inputs (formal parameters) are indicated by a preceding % prefix and followed by a type abbreviation. For example: 'say %s for %n secs' can currently not be changed into 'say %n secs long %s' and still work as intended. Similarly 'point towards %dst' cannot be changed into 'point towards %cst' without breaking its functionality. 6. Submit When you're done, rename the edited file by replacing the "de" part of the filename with the two-letter ISO 639-1 code for your language, e.g. fr - French => lang-fr.js it - Italian => lang-it.js pl - Polish => lang-pl.js pt - Portuguese => lang-pt.js es - Spanish => lang-es.js el - Greek => => lang-el.js and send it to me for inclusion in the official Snap! distribution. Once your translation has been included, Your name will the shown in the "Translators" tab in the "About Snap!" dialog box, and you will be able to directly launch a translated version of Snap! in your browser by appending lang:xx to the URL, xx representing your translations two-letter code. 7. Known issues In some browsers accents or ornaments located in typographic ascenders above the cap height are currently (partially) cut-off. Enjoy! -Jens */ /*global SnapTranslator*/ SnapTranslator.dict.no = { /* Special characters: (see ) , \u00c4, \u00e4 , \u00d6, \u00f6 , \u00dc, \u00fc \u00df */ // translations meta information 'language_name': 'Norsk', // the name as it should appear in the language menu 'language_translator': 'Olav A Marschall', // your name for the Translators tab 'translator_e-mail': 'mattebananer@gmail.com', // optional 'last_changed': '2013-08-17', // this, too, will appear in the Translators tab // GUI // control bar: 'untitled': 'uten navn', 'development mode': 'utviklermodus', // categories: 'Motion': 'Bevegelse', 'Looks': 'Utseende', 'Sound': 'Lyd', 'Pen': 'Penn', 'Control': 'Styring', 'Sensing': 'Sansning', 'Operators': 'Operatorer', 'Variables': 'Variabler', 'Lists': 'Lister', 'Other': 'Andre', // editor: 'draggable': 'flyttbar', // tabs: 'Scripts': 'Skripter', 'Costumes': 'Drakter', 'Sounds': 'Lyder', // names: 'Sprite': 'Objekt', 'Stage': 'Scene', // rotation styles: 'don\'t rotate': 'roteres ikke', 'can rotate': 'roteres', 'only face left/right': 'kun mot venstre/hyre', // new sprite button: 'add a new Sprite': 'legg til nytt objekt', // tab help 'costumes tab help': 'Grafikk importeres ved dra den fra annen' + 'webside e. datamaskin', 'import a sound from your computer\nby dragging it into here': 'importer lyder fra din datamaskin', // primitive blocks: /* Attention Translators: ---------------------- At this time your translation of block specs will only work correctly, if the order of formal parameters and their types are unchanged. Placeholders for inputs (formal parameters) are indicated by a preceding % prefix and followed by a type abbreviation. For example: 'say %s for %n secs' can currently not be changed into 'say %n secs long %s' and still work as intended. Similarly 'point towards %dst' cannot be changed into 'point towards %cst' without breaking its functionality. */ // motion: 'Stage selected:\nno motion primitives': 'Scene valgte:\ningen standard bevegelse' + 'finnes', 'move %n steps': 'g %n steg', 'turn %clockwise %n degrees': 'vend %clockwise %n grader', 'turn %counterclockwise %n degrees': 'vend %counterclockwise %n grader', 'point in direction %dir': 'pek i retning %dir', 'point towards %dst': 'pek mot %dst', 'go to x: %n y: %n': 'g til x: %n y: %n', 'go to %dst': 'g til %dst', 'glide %n secs to x: %n y: %n': 'gli %n sek til x: %n y: %n', 'change x by %n': 'endre x med %n', 'set x to %n': 'sett x til %n', 'change y by %n': 'endre y med %n', 'set y to %n': 'sett y til %n', 'if on edge, bounce': 'sprett tilbake ved kanten', 'x position': 'x-posisjon', 'y position': 'y-posisjon', 'direction': 'retning', // looks: 'switch to costume %cst': 'switch til kostyme %cst', 'next costume': 'neste kostyme', 'costume #': 'kostyme nr.', 'say %s for %n secs': 'si %s i %n sek', 'say %s': 'si %s', 'think %s for %n secs': 'tenk %s i %n sek', 'think %s': 'tenk %s', 'Hello!': 'Heisann!', 'Hmm...': 'Vel...', 'change %eff effect by %n': 'endre %eff -effekt med %n', 'set %eff effect to %n': 'sett %eff -effekt til %n', 'clear graphic effects': 'nullstill grafiske effekter', 'change size by %n': 'endre strrelse med %n', 'set size to %n %': 'sett strrelse til %n %', 'size': 'strrelse', 'show': 'vis', 'hide': 'skjul', 'go to front': 'g fremst', 'go back %n layers': 'g %n lag tilbake', 'development mode \ndebugging primitives:': 'Hackermodus \nDebugging av blokker', 'console log %mult%s': 'skrive i konsoll: %mult%s', 'alert %mult%s': 'Pop-up: %mult%s', // sound: 'play sound %snd': 'spill lyd %snd', 'play sound %snd until done': 'spill lyd %snd ferdig', 'stop all sounds': 'stopp all lyd', 'rest for %n beats': 'pause i %n slag', 'play note %n for %n beats': 'spill note %n i %n slag', 'change tempo by %n': 'endre tempo med %n', 'set tempo to %n bpm': 'sett tempo til %n bpm', 'tempo': 'tempo', // pen: 'clear': 'slett', 'pen down': 'penn ned', 'pen up': 'penn opp', 'set pen color to %clr': 'sett pennfargen til %clr', 'change pen color by %n': 'endre pennfargen med %n', 'set pen color to %n': 'sett penfargen til %n', 'change pen shade by %n': 'endre pennintensitet med %n', 'set pen shade to %n': 'sett pennintensitet til %n', 'change pen size by %n': 'endre pennbredde med %n', 'set pen size to %n': 'sett pennbredde til %n', 'stamp': 'stemple', // control: 'when %greenflag clicked': 'Nr %greenflag klikkes', 'when %keyHat key pressed': 'Nr tast %keyHat trykkes', 'when I am clicked': 'Nr jeg klikkes', 'when I receive %msgHat': 'Nr jeg %msgHat mottar', 'broadcast %msg': 'send melding %msg', 'broadcast %msg and wait': 'send melding %msg og vent', 'Message name': 'Meldingens navn', 'wait %n secs': 'vent i %n sek', 'wait until %b': 'vent til %b', 'forever %c': 'for alltid %c', 'repeat %n %c': 'gjenta %n ganger %c', 'repeat until %b %c': 'gjenta til %b %c', 'if %b %c': 'hvis %b %c', 'if %b %c else %c': 'hvis %b %c ellers %c', 'report %s': 'rapporter %s', 'stop block': 'stopp denne blokk', 'stop script': 'stopp dette skript', 'stop all %stop': 'stopp alt %stop', 'run %cmdRing %inputs': 'kjr %cmdRing fra %inputs', 'launch %cmdRing %inputs': 'start %cmdRing %inputs', 'call %repRing %inputs': 'kall %repRing fra %inputs', 'run %cmdRing w/continuation': 'kjr %cmdRing med kontinuering', 'call %cmdRing w/continuation': 'kall %cmdRing med kontinuering', 'warp %c': 'Warp %c', // sensing: 'touching %col ?': 'berrer %col ?', 'touching %clr ?': 'berrer %clr ?', 'color %clr is touching %clr ?': 'farge %clr berrer %clr ?', 'ask %s and wait': 'spr %s og vent', 'what\'s your name?': 'hva heter du?', 'answer': 'svar', 'mouse x': 'mus x-posisjon', 'mouse y': 'mus y-posisjon', 'mouse down?': 'mustast trykket?', 'key %key pressed?': 'tast %key trykket?', 'distance to %dst': 'avstand til %dst', 'reset timer': 'start stoppeklokke', 'timer': 'stoppeklokke', 'http:// %s': 'http:// %s', 'filtered for %clr': 'filter %clr', 'stack size': 'stack-strrelse', 'frames': 'rammer', // operators: '%n mod %n': '%n mod %n', 'round %n': 'rund av %n', '%fun av %n': '%fun von %n', 'pick random %n to %n': 'tilfeldig fra %n til %n', '%b and %b': '%b OG %b', '%b or %b': '%b ELLER %b', 'not %b': 'IKKE %b', 'true': 'SANN', 'false': 'USANN', 'join %words': 'skjte %words', 'hello': 'hei', 'world': 'verden', 'letter %n of %s': 'bokstav %n av %s', 'length of %s': 'lengde av %s', 'unicode of %s': 'unicode av %s', 'unicode %n as letter': 'unicode %n som bokstav', 'is %s a %typ ?': 'er %s et/en %typ ?', 'is %s identical to %s ?': 'er %s identisk med %s ?', 'type of %s': 'type %s', // variables: 'Make a variable': 'Ny variabel', 'Variable name': 'Variabelnavn', 'Delete a variable': 'Slett variabel', 'set %var to %s': 'sett %var til %s', 'change %var by %n': 'endre %var med %n', 'show variable %var': 'vis variabel %var', 'hide variable %var': 'skjul variabel %var', 'script variables %scriptVars': 'skriptvariable %scriptVars', // lists: 'list %exp': 'liste %exp', '%s in front of %l': '%s framfor %l', 'item %idx of %l': 'element %idx av %l', 'all but first of %l': 'alt utenom frste av %l', 'length of %l': 'lengde av %l', '%l contains %s': '%l inneholder %s', 'thing': 'noe', 'add %s to %l': 'legg %s til %l', 'delete %ida of %l': 'fjern %ida fra %l', 'insert %s at %idx of %l': 'sett inn %s ved %idx i %l ein', 'replace item %idx of %l with %s': 'erstatt element %idx i %l med %s', // other 'Make a block': 'Ny blokk', // menus // snap menu 'About...': 'Om Snap!...', 'Snap! website': 'Snap! websiden', 'Download source': 'Last ned kildekoden', 'Switch back to user mode': 'Tilbake til brukermodus', 'disable deep-Morphic\ncontext menus\nand show user-friendly ones': 'ut av Morphic\nkontekst menyer\nog vis kun brukervennlige', 'Switch to dev mode': 'inn i utviklermodus', 'enable Morphic\ncontext menus\nand inspectors,\nnot user-friendly!': 'inn i Morphic funksjoner\nog inspektorer,\nikke brukervennlig', // project menu 'Project Notes...': 'Prosjektnotater...', 'New': 'Nytt', 'Open...': 'pne...', 'Save': 'Lagre', 'Save As...': 'Lagre som...', 'Import...': 'Importer...', 'file menu import hint': 'laster inn eksportertes prosjekt,\net bibliotek med ' + 'blokker\n' + 'et kostym eller en lyd', 'Export project as plain text ...': 'Eksporter prosjekt som ren tekst...', 'Export project...': 'Eksporter prosjekt...', 'show project data as XML\nin a new browser window': 'vis prosjektdata som XML\ni et nytt nettleser vindu', 'Export blocks ...': 'Eksporter blokker...', 'show global custom block definitions as XML\nin a new browser window': 'viser globale blokkdefinisjoner fra bruker\nsom XML i et nytt nettleser vindu', 'Import tools...': 'Importer verkty...', 'load the official library of\npowerful blocks': 'last ned snap-bibliotek med komplekse blokker', // settings menu 'Language...': 'Sprk...', 'Blurred shadows': 'Mjuke skygger (blurred)', 'uncheck to use solid drop\nshadows and highlights': 'fjern kryss for hard skygge\nog lyssetting', 'check to use blurred drop\nshadows and highlights': 'kryss av for hard skygge\nog lyssetting', 'Zebra coloring': 'Zebra farget', 'check to enable alternating\ncolors for nested blocks': 'kryss av for vekslende fargenyanser\ni nestede blokker', 'uncheck to disable alternating\ncolors for nested block': 'fjern kryss for hindre vekslende\nfargenyanser i nestede blokker', + 'Dynamic input labels': + 'Dynamisk inndata navn', + 'uncheck to disable dynamic\nlabels for variadic inputs': + 'fjern kryss for hindre dynamisk benevning\nav inndata med flere variabelfelt', + 'check to enable dynamic\nlabels for variadic inputs': + 'kryss av for\ndynamisk benevning av inndata med flere variabelfelt', + 'Prefer empty slot drops': 'Preferanse for tomme variabelfelt', 'settings menu prefer empty slots hint': 'Valg meny\ntomme variabelfelt' + 'preferanse', 'uncheck to allow dropped\nreporters to kick out others': 'kryss vekk for at flyttede reportere vil ta plassen til andre\n', 'Long form input dialog': 'Lange inndata dialoger', 'check to always show slot\ntypes in the input dialog': 'kryss av for vise variabelfelttype\ni inndata dialoger', 'uncheck to use the input\ndialog in short form': 'kryss vekk for bruke korte inndata\ndialoger', 'Virtual keyboard': 'Virtuelt tastatur', 'uncheck to disable\nvirtual keyboard support\nfor mobile devices': 'kryss vekk for sl av virtuelt\ntastatur p mobile enheter', 'check to enable\nvirtual keyboard support\nfor mobile devices': 'kryss av for sl p virtuelt\ntastatur p mobile enheter', 'Input sliders': 'Inndata skyveknapp', 'uncheck to disable\ninput sliders for\nentry fields': 'kryss vekk for sl av\nskyveknapper i inndatafelt', 'check to enable\ninput sliders for\nentry fields': 'kryss av for sl p skyveknapper\ni inndatafelt', 'Clicking sound': 'Klikkelyd', 'uncheck to turn\nblock clicking\nsound off': 'kryss vekk for sl av klikkelyd', 'check to turn\nblock clicking\nsound on': 'kryss av for sl p klikkelyd', 'Animations': 'Animasjoner', 'uncheck to disable\nIDE animations': 'kryss vekk for sl av IDE-animasjoner', 'check to enable\nIDE animations': 'kryss av for sl p IDE-animasjoner', 'Thread safe scripts': 'Trdsikker skripting', 'uncheck to allow\nscript reentrancy': 'kryss vekk for sl p gjenbruk av pbegynte skripter', 'check to disallow\nscript reentrancy': 'kryss av for sl av gjenbruk av pbegynte skripter', // inputs 'with inputs': 'med inndata', 'input names:': 'inndata navn:', 'Input Names:': 'Inndata navn:', + 'input list:': + 'Inndata liste:', // context menus: 'help': 'hjelp', // blocks: 'hjelp...': 'Hjelp...', 'relabel...': 'gi nytt navn...', 'duplicate': 'dupliser', 'make a copy\nand pick it up': 'lag kopi\n og plukk opp', 'only duplicate this block': 'dupliser kun denne blokk', 'delete': 'slette', 'script pic...': 'skript bilde...', 'open a new window\nwith a picture of this script': 'pne nytt vindu med bildet av dette skriptet', 'ringify': 'ring rundt', 'unringify': 'fjerne ringen rundt', // custom blocks: 'delete block definition...': 'slett blokk definisjoner', 'edit...': 'rediger...', // sprites: 'edit': 'redigere', 'export...': 'eksporter...', // stage: 'show all': 'vis alt', // scripting area 'clean up': 'rydd', 'arrange scripts\nvertically': 'sett opp skriptene\nvertikalt', 'add comment': 'legg til kommentar', 'make a block...': 'lag ny blokk...', // costumes 'rename': 'nytt navn', 'export': 'eksportere', 'rename costume': 'nytt navn for kostyme', // sounds 'Play sound': 'Spill lyd', 'Stop sound': 'Stop lyd', 'Stop': 'Stop', 'Play': 'Start', 'rename sound': 'nytt navn lyd', // dialogs // buttons 'OK': 'OK', 'Ok': 'OK', 'Cancel': 'Avbryt', 'Yes': 'Ja', 'No': 'Nei', // help 'Help': 'Hjelp', // Project Manager 'Untitled': 'Uten navn', 'Open Project': 'pne prosjekt', '(empty)': '(tomt)', 'Saved!': 'Lagret!', 'Delete Project': 'Slett prosjekt', 'Are you sure you want to delete': 'Vil du virkelig slette?', 'rename...': 'nytt navn...', // costume editor 'Costume Editor': 'Kostyme editor', 'click or drag crosshairs to move the rotation center': 'Klikk p kors for flytte rotasjonssenteret', // project notes 'Project Notes': 'Prosjekt notater', // new project 'New Project': 'Nytt prosjekt', 'Replace the current project with a new one?': 'Erstatt nvrende prosjekt med nytt prosjekt?', // save project 'Save Project As...': 'Lagre prosjekt som...', // export blocks 'Export blocks': 'Eksporter blokker', 'Import blocks': 'Importer blokker', 'this project doesn\'t have any\ncustom global blocks yet': 'dette prosjektet har slangt ingen\nglobale blokker fra bruker', 'select': 'Velg', 'all': 'alt', 'none': 'ingenting', // variable dialog 'for all sprites': 'for alle objekter', 'for this sprite only': 'for dette objektet (ekslusivt)', // block dialog 'Change block': 'Endre blokker', 'Command': 'Styring', 'Reporter': 'Funksjon', 'Predicate': 'Predikat', // block editor 'Block Editor': 'Blokk editor', 'Apply': 'Gjr gjeldende', // block deletion dialog 'Delete Custom Block': 'Slett custom blokk', 'block deletion dialog text': 'Skal denne blokken med alle dens instanser\n' + 'bli slettet?', // input dialog 'Create input name': 'Lag inndata navn', 'Edit input name': 'Rediger inndata navn', 'Edit label fragment': 'Rediger label fragment', 'Title text': 'Tittel', 'Input name': 'Inndata navn', 'Delete': 'Slett', 'Object': 'Objekt', 'Number': 'Tall', 'Text': 'Tekst', 'List': 'Liste', 'Any type': 'Type valgfritt', 'Boolean (T/F)': 'Boolsk (S/U)', 'Command\n(inline)': 'Kommando\n(inline)', 'Command\n(C-shape)': 'Kommando\n(C-Form)', 'Any\n(unevaluated)': 'Hvilken som helst\n(uevaluert)', 'Boolean\n(unevaluated)': 'Boolsk\n(uevaluert)', 'Single input.': 'Singel inndata.', 'Default Value:': 'Standardverdi:', 'Multiple inputs (value is list of inputs)': 'Fler-inndata (verdi er liste over inndata)', 'Upvar - make internal variable visible to caller': 'Upvar - gjr interne variable synlig for den som kaller', // About Snap 'About Snap': 'Om Snap', 'Back...': 'Tilbake...', 'License...': 'Lisens...', 'Modules...': 'Moduler...', 'Credits...': 'Takk til...', 'Translators...': 'Oversettere', 'License': 'Lisens', 'current module versions:': 'Komponent-versjoner', 'Contributors': 'Bidragsytere', 'Translations': 'Oversettelser', // variable watchers 'normal': 'normal', 'large': 'stor', 'slider': 'skyveknapp', 'slider min...': 'skyveknapp min...', 'slider max...': 'skyveknapp max...', + 'import...': + 'importer...', 'Slider minimum value': 'Skyveknapp - minimumsverdi', 'Slider maximum value': 'Skyveknapp - maksimumsverdi', // list watchers 'length: ': 'lengde: ', // coments 'add comment here...': 'legg til kommentar her...', // drow downs // directions '(90) hyre': '(90) hyre', '(-90) venstre': '(-90) venstre', '(0) opp': '(0) oppe', '(180) ned': '(180) nede', // collision detection 'mouse-pointer': 'Muspeker', 'edge': 'Kante', 'pen trails': 'pennens spor', // costumes 'Turtle': 'Retning', // graphical effects 'ghost': 'Gjennomsiktig', // keys 'space': 'Mellomrom', 'up arrow': 'pil opp', 'down arrow': 'pil ned', 'right arrow': 'pil hyre', 'left arrow': 'pil', 'a': 'a', 'b': 'b', 'c': 'c', 'd': 'd', 'e': 'e', 'f': 'f', 'g': 'g', 'h': 'h', 'i': 'i', 'j': 'j', 'k': 'k', 'l': 'l', 'm': 'm', 'n': 'n', 'o': 'o', 'p': 'p', 'q': 'q', 'r': 'r', 's': 's', 't': 't', 'u': 'u', 'v': 'v', 'w': 'w', 'x': 'x', 'y': 'y', 'z': 'z', '0': '0', '1': '1', '2': '2', '3': '3', '4': '4', '5': '5', '6': '6', '7': '7', '8': '8', '9': '9', // messages 'new...': 'ny...', // math functions 'abs': 'abs', 'sqrt': 'kvardrat', 'sin': 'sin', 'cos': 'cos', 'tan': 'tan', 'asin': 'arc-1', 'acos': 'cos-1', 'atan': 'tan-1', 'ln': 'ln', 'e^': 'e^', // data types 'number': 'tall', 'text': 'tekst', 'Boolean': 'boolsk', 'list': 'liste', 'command': 'kommando', 'reporter': 'funksjonsblokk', 'predicate': 'predikat', // list indices 'last': 'siste', 'any': 'hvilken som helst' }; diff --git a/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/lang-pl.js b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/lang-pl.js new file mode 100644 index 0000000..669a22b --- /dev/null +++ b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/lang-pl.js @@ -0,0 +1,1225 @@ +/* + + lang-pl.js + + Polskie tłumaczenie SNAP! + + Podziękowania dla Jensa Möniga + + za przygotowanie mechanizmu tłumaczenia + + This file is part of Snap!. + + Snap! is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + + + + Note to Translators: + -------------------- + At this stage of development, Snap! can be translated to any LTR language + maintaining the current order of inputs (formal parameters in blocks). + + Translating Snap! is easy: + + + 1. Download + + Download the sources and extract them into a local folder on your + computer: + + + + Use the German translation file (named 'lang-de.js') as template for your + own translations. Start with editing the original file, because that way + you will be able to immediately check the results in your browsers while + you're working on your translation (keep the local copy of snap.html open + in your web browser, and refresh it as you progress with your + translation). + + + 2. Edit + + Edit the translation file with a regular text editor, or with your + favorite JavaScript editor. + + In the first non-commented line (the one right below this + note) replace "de" with the two-letter ISO 639-1 code for your language, + e.g. + + fr - French => SnapTranslator.dict.fr = { + it - Italian => SnapTranslator.dict.it = { + pl - Polish => SnapTranslator.dict.pl = { + pt - Portuguese => SnapTranslator.dict.pt = { + es - Spanish => SnapTranslator.dict.es = { + el - Greek => => SnapTranslator.dict.el = { + + etc. (see ) + + + 3. Translate + + Then work through the dictionary, replacing the German strings against + your translations. The dictionary is a straight-forward JavaScript ad-hoc + object, for review purposes it should be formatted as follows: + + { + 'English string': + 'Translation string', + 'last key': + } 'last value' + + and you only edit the indented value strings. Note that each key-value + pair needs to be delimited by a comma, but that there shouldn't be a comma + after the last pair (again, just overwrite the template file and you'll be + fine). + + If something doesn't work, or if you're unsure about the formalities you + should check your file with + + + + This will inform you about any missed commas etc. + + + 4. Accented characters + + Depending on which text editor and which file encoding you use you can + directly enter special characters (e.g. Umlaut, accented characters) on + your keyboard. However, I've noticed that some browsers may not display + special characters correctly, even if other browsers do. So it's best to + check your results in several browsers. If you want to be on the safe + side, it's even better to escape these characters using Unicode. + + see: + + + 5. Block specs: + + At this time your translation of block specs will only work + correctly, if the order of formal parameters and their types + are unchanged. Placeholders for inputs (formal parameters) are + indicated by a preceding % prefix and followed by a type + abbreviation. + + For example: + + 'say %s for %n secs' + + can currently not be changed into + + 'say %n secs long %s' + + and still work as intended. + + Similarly + + 'point towards %dst' + + cannot be changed into + + 'point towards %cst' + + without breaking its functionality. + + + 6. Submit + + When you're done, rename the edited file by replacing the "de" part of the + filename with the two-letter ISO 639-1 code for your language, e.g. + + fr - French => lang-fr.js + it - Italian => lang-it.js + pl - Polish => lang-pl.js + pt - Portuguese => lang-pt.js + es - Spanish => lang-es.js + el - Greek => => lang-el.js + + and send it to me for inclusion in the official Snap! distribution. + Once your translation has been included, Your name will the shown in the + "Translators" tab in the "About Snap!" dialog box, and you will be able to + directly launch a translated version of Snap! in your browser by appending + + lang:xx + + to the URL, xx representing your translations two-letter code. + + + 7. Known issues + + In some browsers accents or ornaments located in typographic ascenders + above the cap height are currently (partially) cut-off. + + Enjoy! + -Jens +*/ + +/*global SnapTranslator*/ + +SnapTranslator.dict.pl = { + +/* + Special characters: (see ) + Ą, ą \u0104, \u0105 + Ć, ć \u0106, \u0107 + Ę, ę \u0118, \u0119 + Ł, ł \u0141, \u0142 + Ń, ń \u0143, \u0144 + Ś, ś \u015A, \u015B + Ó, ó \u00D3, \u00F3 + Ź, ź \u0179, \u017A + Ż, ż \u017B, \u017C + +*/ + + // translations meta information + 'language_name': + 'Polski', // the name as it should appear in the language menu + 'language_translator': + 'Witek Kranas', // your name for the Translators tab + 'translator_e-mail': + 'witek@oeiizk.waw.pl', // optional + 'last_changed': + '2013-08-05', // this, too, will appear in the Translators tab + + // GUI + // control bar: + 'untitled': + 'bez nazwy', + 'development mode': + 'tryb budowania', + + // categories: + 'Motion': + 'Ruch', + 'Looks': + 'Wygl\u0105d', + 'Sound': + 'D\u017Awi\u0119k', + 'Pen': + 'Pisak', + 'Control': + 'Kontrola', + 'Sensing': + 'Czujniki', + 'Operators': + 'Wyra\u017Cenia', + 'Variables': + 'Dane', + 'Lists': + 'Listy', + 'Other': + 'Inne', + + // editor: + 'draggable': + 'przeci\u0105ganie', + + // tabs: + 'Scripts': + 'Skrypty', + 'Costumes': + 'Kostiumy', + 'Sounds': + 'D\u017Awi\u0119ki', + + // names: + 'Sprite': + 'Duszek', + 'Stage': + 'Scena', + + // rotation styles: + 'don\'t rotate': + 'nie obracaj', + 'can rotate': + 'dowolny obrót', + 'only face left/right': + 'tylko lewo/prawo', + + // new sprite button: + 'add a new sprite': + 'dodaj nowego duszka', + + // tab help + 'costumes tab help': + 'Importuj obrazy z innej strony\n' + + 'lub z komputera przeci\u0105gaj\u0105c tu', + 'import a sound from your computer\nby dragging it into here': + 'Importuj d\u017Awi\u0119k z komputera\nprzeci\u0105gaj\u0105c tu', + + // primitive blocks: + + /* + Attention Translators: + ---------------------- + At this time your translation of block specs will only work + correctly, if the order of formal parameters and their types + are unchanged. Placeholders for inputs (formal parameters) are + indicated by a preceding % prefix and followed by a type + abbreviation. + + For example: + + 'say %s for %n secs' + + can currently not be changed into + + 'say %n secs long %s' + + and still work as intended. + + Similarly + + 'point towards %dst' + + cannot be changed into + + 'point towards %cst' + + without breaking its functionality. + */ + + // motion: + 'Stage selected:\nno motion primitives': + 'Wybrana scena\nnie ma blok\u00F3w ruchu', + + 'move %n steps': + 'przesu\u0144 o %n krok\u00F3w', + 'turn %clockwise %n degrees': + 'obr\u00F3\u0107 %clockwise o %n stopni', + 'turn %counterclockwise %n degrees': + 'obr\u00F3\u0107 %counterclockwise o %n stopni', + 'point in direction %dir': + 'ustaw kierunek na %dir', + 'point towards %dst': + 'ustaw w stron\u0119 %dst', + 'go to x: %n y: %n': + 'id\u017A do x: %n y: %n', + 'go to %dst': + 'id\u017A do %dst', + 'glide %n secs to x: %n y: %n': + 'le\u0107 przez %n s do x: %n y: %n', + 'change x by %n': + 'zmie\u0144 x o %n', + 'set x to %n': + 'ustaw x na %n', + 'change y by %n': + 'zmie\u0144 y o %n', + 'set y to %n': + 'ustaw y na %n', + 'if on edge, bounce': + 'je\u017Celi na brzegu, odbij si\u0119', + 'x position': + 'pozycja X', + 'y position': + 'pozycja Y', + 'direction': + 'kierunek', + + // wyglad: + 'switch to costume %cst': + 'zmie\u0144 kostium na %cst', + 'next costume': + 'nast\u0118pny kostium', + 'costume #': + 'kostium nr ', + 'say %s for %n secs': + 'powiedz %s przez %n s', + 'say %s': + 'powiedz %s', + 'think %s for %n secs': + 'pomy\u015Bl %s przez %n s', + 'think %s': + 'pomy\u015Bl %s', + 'Hello!': + 'Hallo!', + 'Hmm...': + 'Hmm...', + 'change %eff effect by %n': + 'zmie\u0144 efekt %eff o %n', + 'set %eff effect to %n': + 'ustaw efekt %eff na %n', + 'clear graphic effects': + 'wyczy\u015B\u0107 efekty graficzne', + 'change size by %n': + 'zmie\u0144 rozmiar o %n', + 'set size to %n %': + 'ustaw rozmiar na %n %', + 'size': + 'rozmiar', + 'show': + 'poka\u017C', + 'hide': + 'ukryj', + 'go to front': + 'na wierzch', + 'go back %n layers': + 'wr\u00F3\u0107 o %n poziom\u00F3w', + + 'development mode \ndebugging primitives:': + 'tryb budowania \ndebugowanie procedur pierwotnych', + 'console log %mult%s': + 'log konsoli: %mult%s', + 'alert %mult%s': + 'alert: %mult%s', + + // dzwiek: + 'play sound %snd': + 'zagraj d\u017Awi\u0119k %snd', + 'play sound %snd until done': + 'zagraj d\u017Awi\u0119k %snd i czekaj', + 'stop all sounds': + 'zatrzymaj wszystkie d\u017Awi\u0119ki', + 'rest for %n beats': + 'pauzuj przez %n takt\u00F3w', + 'play note %n for %n beats': + 'zagraj nut\u0119 %n przez %n takt\u00F3w', + 'change tempo by %n': + 'zmie\u0144 tempo o %n', + 'set tempo to %n bpm': + 'ustaw tempo na %n takt\u00F3w na min.', + 'tempo': + 'tempo', + + // pisak: + 'clear': + 'wyczy\u015B\u0107', + 'pen down': + 'przy\u0142\u00F3\u017C pisak', + 'pen up': + 'podnie\u015B pisak', + 'set pen color to %clr': + 'ustaw kolor piaka na %clr', + 'change pen color by %n': + 'zmie\u0144 kolor pisaka o %n', + 'set pen color to %n': + 'ustaw kolor pisaka na %n', + 'change pen shade by %n': + 'zmie\u0144 odcie\u0144 pisaka o %n', + 'set pen shade to %n': + 'ustaw odcie\u0144 pisaka na %n', + 'change pen size by %n': + 'zmie\u0144 rozmiar pisaka o %n', + 'set pen size to %n': + 'ustaw rozmiar pisaka na %n', + 'stamp': + 'stempluj', + + // control: + 'when %greenflag clicked': + 'kiedy klikni\u0119to %greenflag', + 'when %keyHat key pressed': + 'kiedy klawisz %keyHat naci\u015Bni\u0119ty', + 'when I am clicked': + 'kiedy duszek klikni\u0119ty', + 'when I receive %msgHat': + 'kiedy otrzymam %msgHat', + 'broadcast %msg': + 'nadaj %msg do wszystkich', + 'broadcast %msg and wait': + 'nadaj %msg do wszystkich i czekaj', + 'Message name': + 'nazwa wiadomo\u015Bci', + 'message': + 'wiadomo\u015B\u0107', + 'any message': + 'dowolna wiadomo\u015B\u0107', + 'wait %n secs': + 'czekaj %n s', + 'wait until %b': + 'czekaj a\u017C %b', + 'forever %c': + 'zawsze %c', + 'repeat %n %c': + 'powt\u00F3rz %n razy %c', + 'repeat until %b %c': + 'powtarzaj a\u017C %b %c', + 'if %b %c': + 'je\u017Celi %b to %c', + 'if %b %c else %c': + 'je\u017Celi %b to %c w przeciwnym razie %c', + 'report %s': + 'zwr\u00F3\u0107 %s', + 'stop block': + 'zatrzymaj ten blok', + 'stop script': + 'zatrzymaj ten skrypt', + 'stop all %stop': + 'zatrzymaj wszystko %stop', + 'pause all %pause': + 'pauzuj wszystko %pause', + 'run %cmdRing %inputs': + 'uruchom %cmdRing z %inputs', + 'launch %cmdRing %inputs': + 'zacznij %cmdRing %inputs', + 'call %repRing %inputs': + 'wywo\u0142aj %repRing z %inputs', + 'run %cmdRing w/continuation': + 'uruchom %cmdRing z kontynuacj\u0105', + 'call %cmdRing w/continuation': + 'wywo\u0142aj %cmdRing z kontynuacj\u0105', + 'warp %c': + 'warp %c', + 'when I start as a clone': + 'kiedy zaczynam jako klon', + 'create a clone of %cln': + 'sklonuj %cln', + 'myself': + 'ja', + 'delete this clone': + 'usu\u0144 tego klona', + + // sensing: + 'touching %col ?': + 'dotyka %col ?', + 'touching %clr ?': + 'dotyka koloru %clr ?', + 'color %clr is touching %clr ?': + 'czy kolor %clr dotyka %clr ?', + 'ask %s and wait': + 'zapytaj %s i czekaj', + 'what\'s your name?': + 'Jak masz na imi\u0119?', + 'answer': + 'odpowied\u017A', + 'mouse x': + 'x myszy', + 'mouse y': + 'y myszy', + 'mouse down?': + 'przycisk myszy naci\u015Bni\u0119ty', + 'key %key pressed?': + 'klawisz %key naci\u015Bni\u0119ty?', + 'distance to %dst': + 'odleg\u0142o\u015B\u0107 do %dst', + 'reset timer': + 'kasuj zegar', + 'timer': + 'czasomierz', + '%att of %spr': + '%att z %spr', + 'http:// %s': + 'http:// %s', + 'turbo mode?': + 'tryb turbo?', + 'set turbo mode to %b': + 'ustaw tryb turbo na %b', + + 'filtered for %clr': + 'przefiltrowane dla %clr', + 'stack size': + 'rozmiar stosu', + 'frames': + 'klatki', + + // operators: + '%n mod %n': + '%n modulo %n', + 'round %n': + 'zaokr\u0105glij %n', + '%fun of %n': + '%fun z %n', + 'pick random %n to %n': + 'losuj od %n do %n', + '%b and %b': + '%b i %b', + '%b or %b': + '%b lub %b', + 'not %b': + 'nie %b', + 'true': + 'prawda', + 'false': + 'fa\u0142sz', + 'join %words': + 'po\u0142\u0105cz %words', + 'hello': + 'Hallo', + 'world': + 's\u0142owo', + 'letter %n of %s': + 'litera %n z %s', + 'length of %s': + 'd\u0142ugo\u015B\u0107 %s', + 'unicode of %s': + 'Unicode z %s', + 'unicode %n as letter': + 'Unicode %n jako litera', + 'is %s a %typ ?': + 'jest %s typu %typ ?', + 'is %s identical to %s ?': + 'jest %s identyczne z %s ?', + + 'type of %s': + 'typ %s', + + // variables: + 'Make a variable': + 'Stw\u00F3rz zmienn\u0105', + 'Variable name': + 'nazwa zmiennej', + 'Script variable name': + 'nazwa zmiennej skryptu', + 'Delete a variable': + 'usu\u0144 zmienn\u0105', + + 'set %var to %s': + 'ustaw %var na %s', + 'change %var by %n': + 'zmie\u0144 %var o %n', + 'show variable %var': + 'poka\u017C zmienn\u0105 %var', + 'hide variable %var': + 'ukryj zmienn\u0105 %var', + 'script variables %scriptVars': + 'zmienne skryptu %scriptVars', + + // lists: + 'list %exp': + 'lista %exp', + '%s in front of %l': + 'wstaw %s przed %l', + 'item %idx of %l': + 'element %idx z %l', + 'all but first of %l': + 'bez pierwszego z %l', + 'length of %l': + 'd\u0142ugo\u015B\u0107 %l', + '%l contains %s': + '%l zawiera %s', + 'thing': + 'co\u015B', + 'add %s to %l': + 'dodaj %s do %l', + 'delete %ida of %l': + 'usu\u0144 %ida z %l', + 'insert %s at %idx of %l': + 'wstaw %s na pozycji %idx do %l', + 'replace item %idx of %l with %s': + 'zamie\u0144 element %idx z %l na %s', + + // other + 'Make a block': + 'nowy blok', + + // menus + // snap menu + 'About...': + 'O Snap!...', + 'Reference manual': + 'Podr\u0119cznik', + 'Snap! website': + 'Strona Snap!', + 'Download source': + 'Pobierz \u017Ar\u00F3d\u0142o', + 'Switch back to user mode': + 'Prze\u0142\u0105cz do trybu u\u017Cytkownika', + 'disable deep-Morphic\ncontext menus\nand show user-friendly ones': + 'disable Morphic', + 'Switch to dev mode': + 'do trybu budowania', + 'enable Morphic\ncontext menus\nand inspectors,\nnot user-friendly!': + 'enable Morphic', + + // project menu + 'Project notes...': + 'O projekcie...', + 'New': + 'Nowy', + 'Open...': + 'Otw\u00F3rz...', + 'Save': + 'Zapisz', + 'Save As...': + 'Zapisz jako...', + 'Import...': + 'Importuj...', + 'file menu import hint': + '\u0142aduje wyeksportowany projekt\n' + + 'biblitek\u0119 ' + + 'kostium lub d\u017Awi\u0119k', + 'Export project as plain text...': + 'Eksportuj projekt jako tekst...', + 'Export project...': + 'Eksportuj projekt...', + 'show project data as XML\nin a new browser window': + 'poka\u017C projekt jako XML\nin w nowej karcie', + 'Export blocks...': + 'Eksportuj bloki...', + 'show global custom block definitions as XML\nin a new browser window': + 'poka\u017C definicje blok\u00F3w jako XML/min w nowej karcie', + 'Import tools': + 'Importuj narz\u0119dzia', + 'load the official library of\npowerful blocks': + 'za\u0142aduj oficjaln\u0105 bibliotek\u0119 blok\u00F3w', + 'Libraries...': + 'Biblioteka...', + 'Import library': + 'Importuj bibliotek\u0119', + + // cloud menu + 'Login...': + 'Logowanie...', + 'Signup...': + 'Rejestracja...', + + // settings menu + 'Language...': + 'J\u0119zyk...', + 'Zoom blocks...': + 'Powi\u0119ksz bloki...', + 'Blurred shadows': + 'Rozmyte cienie', + 'uncheck to use solid drop\nshadows and highlights': + 'Odznacz, aby uzyska\u0107\nmocne cienie i granice', + 'check to use blurred drop\nshadows and highlights': + 'Zaznacz, aby uzyska\u0107\rozmyte cienie i granice', + 'Zebra coloring': + 'Kolorowanie zebr\u0105', + 'check to enable alternating\ncolors for nested blocks': + 'zaznacz, aby pozowli\u0107 na zmian\u0119\nbarw zagnie\u017Cd\u017Conych blok\u00F3w', + 'uncheck to disable alternating\ncolors for nested block': + 'odznacz, aby nie pozowli\u0107 na zmian\u0119\nbarw zagnie\u017Cd\u017Conych blok\u00F3w', + 'Dynamic input labels': + 'Dynamiczne opisy parametr\u00F3w', + 'uncheck to disable dynamic\nlabels for variadic inputs': + 'odznacz to disable dynamic\nlabels for variadic inputs', + 'check to enable dynamic\nlabels for variadic inputs': + 'zaznacz to enable dynamic\nlabels for variadic inputs', + 'Prefer empty slot drops': + 'Preferuj empty slot drops', + 'settings menu prefer empty slots hint': + 'menu ustawie\u0144 prefer empty slots hint', + 'uncheck to allow dropped\nreporters to kick out others': + 'odznacz to allow dropped\nreporters to kick out others', + 'Long form input dialog': + 'D\u0142uga forma dialogu wej\u015Bcia', + 'check to always show slot\ntypes in the input dialog': + 'zaznacz to always show slot\ntypes in the input dialog', + 'uncheck to use the input\ndialog in short form': + 'odznacz to use the input\ndialog in short form', + 'Virtual keyboard': + 'Witualna klawiatura', + 'uncheck to disable\nvirtual keyboard support\nfor mobile devices': + 'odznacz, aby nie u\u017Cywa\u0107 klawiatury\nwirtualnej dla urzdze\u0144 mobilnych', + 'check to enable\nvirtual keyboard support\nfor mobile devices': + 'zaznacz, aby u\u017Cywa\u0107 klawiatury\nwirtualnej dla urzdze\u0144 mobilnych', + 'uncheck to disable\ninput sliders for\nentry fields': + 'odznacz, aby nie pozwoli\u0107 na suwaki w polach wej\u015Bciowych', + 'check to enable\ninput sliders for\nentry fields': + 'zaznacz, aby pozwoli\u0107 na suwaki w polach wej\u015Bciowych', + 'Clicking sound': + 'D\u017Awi\u0119k klikni\u0119cia', + 'uncheck to turn\nblock clicking\nsound off': + 'odznacz, aby wy\u0142\u0105czy\u0107 \nd\u017Awi\u0119k klikni\u0119cia', + 'check to turn\nblock clicking\nsound on': + 'zaznacz, aby w\u0142\u0105czy\u0107 \nd\u017Awi\u0119k klikni\u0119cia', + 'Animations': + 'Animacje', + 'uncheck to disable\nIDE animations': + 'odznacz, aby nie pozwoli\u0107\nna animacje IDE', + 'Turbo mode': + 'Tryb turbo', + 'check to prioritize\nscript execution': + 'zaznacz, aby nada\u0107 priorytet\nwykonaniu skryptu', + 'uncheck to run scripts\nat normal speed': + 'odznacz, aby wykona\u0107 skrypt\nz normaln\u0105 szybko\u015Bci\u0105', + 'check to enable\nIDE animations': + 'zaznacz, aby pozwoli\u0107\nna animacje IDE', + 'Thread safe scripts': + 'Omijaj bezpieczne skrypty', + 'uncheck to allow\nscript reentrance': + 'odznacz, aby pozwoli\u0107\nna restartowanie skryptu', + 'check to disallow\nscript reentrance': + 'zaznacz, aby nie pozwoli\u0107\nna restartowanie skryptu', + 'Prefer smooth animations': + 'Preferuj g\u0142adkie animacje', + 'uncheck for greater speed\nat variable frame rates': + 'odznacz, aby pozwoli\u0107na\nwi\u0119ksz pr\u0119dko\u015B\u0107 ramek animacji', + 'check for smooth, predictable\nanimations across computers': + 'zaznacz, aby zapewni\u0107na\njednakowe, g\u0142adkie animacje', + + // inputs + 'with inputs': + 'z parametrami', + 'input names:': + 'nazwy parametr\u00F3w:', + 'Input Names:': + 'Nazwy Parametr\u00F3w:', + 'input list:': + 'parametr - lista:', + + // context menus: + 'help': + 'pomoc', + + // palette: + 'hide primitives': + 'ukryj pierwotne', + 'show primitives': + 'poka\u017C pierwotne', + + // blocks: + 'help...': + 'pomoc...', + 'relabel...': + 'przemianuj...', + 'duplicate': + 'powiel', + 'make a copy\nand pick it up': + 'wykonaj i we\u017A kopi\u0119', + 'only duplicate this block': + 'powiel tylko ten blok', + 'delete': + 'usu\u0144', + 'script pic...': + 'obrazek skryptu...', + 'open a new window\nwith a picture of this script': + 'otw\u00F3rz nowe okno\nz obrazkiem tego skryptu', + 'ringify': + 'obwiednia', + 'unringify': + 'bez obwiedni', + + // custom blocks: + 'delete block definition...': + 'usu\u0144 definicj\u0119 bloku', + 'edit...': + 'edytuj...', + + // sprites: + 'edit': + 'edytuj', + 'export...': + 'eksportuj...', + + // stage: + 'show all': + 'poka\u017C wszystko', + 'pic...': + 'obrazek...', + 'open a new window\nwith a picture of the stage': + 'otw\u00F3rz w nowym oknie\nz obrazkiem sceny', + + // scripting area + 'clean up': + 'wyczy\u015B\u0107', + 'arrange scripts\nvertically': + 'ustaw skrypty pionowo', + 'add comment': + 'dodaj komentarz', + 'undrop': + 'odklej', + 'undo the last\nblock drop\nin this pane': + 'cofnij ostatnie upuszczenie\nbloku na tej planszy', + 'scripts pic...': + 'obrazek skryptu...', + 'open a new window\nwith a picture of all scripts': + 'otw\u00F3rz nowe okno\nz obrazkami wszystkich skrypt\u00F3w', + 'make a block...': + 'buduj nowy blok...', + + // costumes + 'rename': + 'zmie\u0144 nazw\u0119', + 'export': + 'eksportuj', + 'rename costume': + 'zmie\u0144 nazw\u0119 kostiumu', + + // sounds + 'Play sound': + 'Zagraj d\u017Cwi\u0119k', + 'Stop sound': + 'Zatrzymaj d\u017Cwi\u0119k', + 'Stop': + 'Stop', + 'Play': + 'Graj', + 'rename sound': + 'zmie\u0144 nazw\u0119 d\u017Cwi\u0119ku', + + // dialogs + // buttons + 'OK': + 'OK', + 'Ok': + 'Ok', + 'Cancel': + 'Poniechaj', + 'Yes': + 'Tak', + 'No': + 'Nie', + + // help + 'Help': + 'Pomoc', + + // zoom blocks + 'Zoom blocks': + 'Zoom blok\u00F3w', + 'build': + 'buduj', + 'your own': + 'swoje', + 'blocks': + 'bloki', + 'normal (1x)': + 'normalne (1x)', + 'demo (1.2x)': + 'demo (1.2x)', + 'presentation (1.4x)': + 'prezentacja (1.4x)', + 'big (2x)': + 'du\u017Ce (2x)', + 'huge (4x)': + 'ogromne (4x)', + 'giant (8x)': + 'gigantyczne (8x)', + 'monstrous (10x)': + 'monstrualne (10x)', + + // Project Manager + 'Untitled': + 'Bez nazwy', + 'Open Project': + 'Otw\u00F3rz projekt', + '(empty)': + '(puste)', + 'Saved!': + 'Zapisane!', + 'Delete Project': + 'Usu\u0144 projekt', + 'Are you sure you want to delete': + 'Czy napewno chcesz usun\u0105\u0107?', + 'rename...': + 'przemianuj', + + // costume editor + 'Costume Editor': + 'Edytor kostium\u00F3w', + 'click or drag crosshairs to move the rotation center': + 'Kliknij lub przeci\u0105gnij krzy\u017Cyk, aby ustawi\u0107 centrum obrotu', + + // project notes + 'Project Notes': + 'Opis projektu', + + // new project + 'New Project': + 'Nowy projekt', + 'Replace the current project with a new one?': + 'Zast\u0105pi\u0107 aktualny projekt przez nowy?', + + // save project + 'Save Project As...': + 'Zapisz projekt jako...', + + // export blocks + 'Export blocks': + 'Eksportuj bloki', + 'Import blocks': + 'Importuj bloki', + 'this project doesn\'t have any\ncustom global blocks yet': + 'ten projekt nie ma jeszcze\nw\u0142asnych globalnych blok\u00F3w', + 'select': + 'wybierz', + 'all': + 'wszystko', + 'none': + 'nic', + + // variable dialog + 'for all sprites': + 'dla wszystkich duszk\u00F3w', + 'for this sprite only': + 'tylko dla tego duszka', + + // block dialog + 'Change block': + 'Zmie\u0144 blok', + 'Command': + 'Komenda', + 'Reporter': + 'Funkcja', + 'Predicate': + 'Predykat', + + // block editor + 'Block Editor': + 'Edytor blok\u00F3w', + 'Apply': + 'Zastosuj', + + // block deletion dialog + 'Delete Custom Block': + 'Usu\u0144 w\u0142asny blok', + 'block deletion dialog text': + 'czy ten blok ze wszystkimi wyst\u0105pieniami\n' + + 'rzeczywi\u015Bcie usun\u0105\u0107?', + + // input dialog + 'Create input name': + 'Utw\u00F3rz nazw\u0119 parametru', + 'Edit input name': + 'Edytuj nazw\u0119 parametru', + 'Edit label fragment': + 'Edytuj opis parametru', + 'Title text': + 'Tekst tutu\u0142owy', + 'Input name': + 'Nazwa', + 'Delete': + 'Usu\u0144', + 'Object': + 'Obiekt', + 'Number': + 'Liczba', + 'Text': + 'Tekst', + 'List': + 'Lista', + 'Any type': + 'Dowolnego rodzaju', + 'Boolean (T/F)': + 'Logiczny (P/F)', + 'Command\n(inline)': + 'Komenda', + 'Command\n(C-shape)': + 'Komenda\n(C-Form)', + 'Any\n(unevaluated)': + 'Dowolny\n(nieokre\u015Blony)', + 'Boolean\n(unevaluated)': + 'Logiczny\n(nieokre\u015Blony)', + 'Single input.': + 'Jeden parametr.', + 'Default Value:': + 'Warto\u015B\u0107 standardowa:', + 'Multiple inputs (value is list of inputs)': + 'Wiele parametr\u00F3w (jako lista)', + 'Upvar - make internal variable visible to caller': + 'Wewn\u0119trzna zmienna widoczna dla wywo\u0142ania', + + // About Snap + 'About Snap': + 'O Snap', + 'Back...': + 'Wstecz...', + 'License...': + 'Licencja...', + 'Modules...': + 'Modu\u0142y...', + 'Credits...': + 'Podzi\u0119kowania...', + 'Translators...': + 'T\u0142umacze', + 'License': + 'Licencja', + 'current module versions:': + 'aktualna wersja modu\u0142\u00F3w', + 'Contributors': + 'Wsp\u00F3\u0142pracownicy', + 'Translations': + 'T\u0142umaczenia', + + // variable watchers + 'normal': + 'normalny', + 'large': + 'wielki', + 'slider': + 'suwak', + 'slider min...': + 'minimum suwaka...', + 'slider max...': + 'maksimum suwaka...', + 'import...': + 'importuj...', + 'Slider minimum value': + 'Minimalna warto\u015B\u0107 suwaka', + 'Slider maximum value': + 'Maksymalna warto\u015B\u0107 suwaka', + + // list watchers + 'length: ': + 'du\u0142go\u015B\u0107: ', + + // coments + 'add comment here...': + 'dodaj komentarz tutaj...', + + // drow downs + // directions + '(90) right': + '(90) prawo', + '(-90) left': + '(-90) lewo', + '(0) up': + '(0) g\u00F3ra', + '(180) down': + '(180) d\u00F3\u0142', + + // collision detection + 'mouse-pointer': + 'Wska\u017Anik myszy', + 'edge': + 'Kraw\u0119dzie', + 'pen trails': + '\u015Alady pisaka', + + // costumes + 'Turtle': + 'Duszek', + 'Empty': + 'Pusty', + + // graphical effects + 'ghost': + 'duch', + + // keys + 'space': + 'spacja', + 'up arrow': + 'strz\u0142aka w g\u00F3r\u0119', + 'down arrow': + 'strz\u0142aka w d\u00F3\u0142', + 'right arrow': + 'strz\u0142aka w prawo', + 'left arrow': + 'strz\u0142aka w lewo', + 'a': + 'a', + 'b': + 'b', + 'c': + 'c', + 'd': + 'd', + 'e': + 'e', + 'f': + 'f', + 'g': + 'g', + 'h': + 'h', + 'i': + 'i', + 'j': + 'j', + 'k': + 'k', + 'l': + 'l', + 'm': + 'm', + 'n': + 'n', + 'o': + 'o', + 'p': + 'p', + 'q': + 'q', + 'r': + 'r', + 's': + 's', + 't': + 't', + 'u': + 'u', + 'v': + 'v', + 'w': + 'w', + 'x': + 'x', + 'y': + 'y', + 'z': + 'z', + '0': + '0', + '1': + '1', + '2': + '2', + '3': + '3', + '4': + '4', + '5': + '5', + '6': + '6', + '7': + '7', + '8': + '8', + '9': + '9', + + // messages + 'new...': + 'nowy...', + + // math functions + 'abs': + 'modu\u0142', + 'floor': + 'pod\u0142oga', + 'sqrt': + 'pierwiastek kwadratowy', + 'sin': + 'sin', + 'cos': + 'cos', + 'tan': + 'tg', + 'asin': + 'arcsin', + 'acos': + 'arccos', + 'atan': + 'arctg', + 'ln': + 'ln', + 'e^': + 'e^', + + // data types + 'number': + 'liczba', + 'text': + 'tekst', + 'Boolean': + 'logiczna', + 'list': + 'lista', + 'command': + 'komenda', + 'reporter': + 'funkcja', + 'predicate': + 'predykat', + + // list indices + 'last': + 'ostatni', + 'any': + 'dowolny' +}; diff --git a/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/lang-pt.js b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/lang-pt.js new file mode 100644 index 0000000..679a0bf --- /dev/null +++ b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/lang-pt.js @@ -0,0 +1,1223 @@ +/* + + lang-pt.js + + Portuguese (literary) translation for SNAP! + + translated by Manuel Menezes de Sequeira + + Copyright (C) 2012 by Manuel Menezes de Sequeira + + This file is part of Snap!. + + Snap! is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + + + + Note to Translators: + -------------------- + At this stage of development, Snap! can be translated to any LTR language + maintaining the current order of inputs (formal parameters in blocks). + + Translating Snap! is easy: + + + 1. Download + + Download the sources and extract them into a local folder on your + computer: + + + + Use the German translation file (named 'lang-de.js') as template for your + own translations. Start with editing the original file, because that way + you will be able to immediately check the results in your browsers while + you're working on your translation (keep the local copy of snap.html open + in your web browser, and refresh it as you progress with your + translation). + + + 2. Edit + + Edit the translation file with a regular text editor, or with your + favorite JavaScript editor. + + In the first non-commented line (the one right below this + note) replace "de" with the two-letter ISO 639-1 code for your language, + e.g. + + fr - French => SnapTranslator.dict.fr = { + it - Italian => SnapTranslator.dict.it = { + pl - Polish => SnapTranslator.dict.pl = { + pt - Portuguese => SnapTranslator.dict.pt = { + es - Spanish => SnapTranslator.dict.es = { + el - Greek => => SnapTranslator.dict.el = { + + etc. (see ) + + + 3. Translate + + Then work through the dictionary, replacing the German strings against + your translations. The dictionary is a straight-forward JavaScript ad-hoc + object, for review purposes it should be formatted as follows: + + { + 'English string': + 'Translation string', + 'last key': + } 'last value' + + and you only edit the indented value strings. Note that each key-value + pair needs to be delimited by a comma, but that there shouldn't be a comma + after the last pair (again, just overwrite the template file and you'll be + fine). + + If something doesn't work, or if you're unsure about the formalities you + should check your file with + + + + This will inform you about any missed commas etc. + + + 4. Accented characters + + Depending on which text editor and which file encoding you use you can + directly enter special characters (e.g. Umlaut, accented characters) on + your keyboard. However, I've noticed that some browsers may not display + special characters correctly, even if other browsers do. So it's best to + check your results in several browsers. If you want to be on the safe + side, it's even better to escape these characters using Unicode. + + see: + + + 5. Block specs: + + At this time your translation of block specs will only work + correctly, if the order of formal parameters and their types + are unchanged. Placeholders for inputs (formal parameters) are + indicated by a preceding % prefix and followed by a type + abbreviation. + + For example: + + 'say %s for %n secs' + + can currently not be changed into + + 'say %n secs long %s' + + and still work as intended. + + Similarly + + 'point towards %dst' + + cannot be changed into + + 'point towards %cst' + + without breaking its functionality. + + + 6. Submit + + When you're done, rename the edited file by replacing the "de" part of the + filename with the two-letter ISO 639-1 code for your language, e.g. + + fr - French => lang-fr.js + it - Italian => lang-it.js + pl - Polish => lang-pl.js + pt - Portuguese => lang-pt.js + es - Spanish => lang-es.js + el - Greek => => lang-el.js + + and send it to me for inclusion in the official Snap! distribution. + Once your translation has been included, Your name will the shown in the + "Translators" tab in the "About Snap!" dialog box, and you will be able to + directly launch a translated version of Snap! in your browser by appending + + lang:xx + + to the URL, xx representing your translations two-letter code. + + + 7. Known issues + + In some browsers accents or ornaments located in typographic ascenders + above the cap height are currently (partially) cut-off. + + Enjoy! + -Jens +*/ + +/*global SnapTranslator*/ + +SnapTranslator.dict.pt = { + +/* + Special characters: (see ) + + Ä, ä \u00c4, \u00e4 + Ö, ö \u00d6, \u00f6 + Ü, ü \u00dc, \u00fc + ß \u00df +*/ + + // meta informação sobre a tradução + 'language_name': + 'Português (literário)', + 'language_translator': + 'Manuel Menezes de Sequeira', + 'translator_e-mail': + 'mmsequeira@gmail.com', + 'last_changed': + '2013-03-22', + + // GUI + // control bar: + 'untitled': + 'Sem título', + 'development mode': + 'modo de desenvolvimento', + + // categorias: + 'Motion': + 'Movimento', + 'Looks': + 'Aparência', + 'Sound': + 'Som', + 'Pen': + 'Caneta', + 'Control': + 'Controlo', + 'Sensing': + 'Sensores', + 'Operators': + 'Operadores', + 'Variables': + 'Variáveis', + 'Lists': + 'Listas', + 'Other': + 'Outros', + + // editor: + 'draggable': + 'arrastável', + + // separadores: + 'Scripts': + 'Guiões', + 'Costumes': + 'Trajes', + 'Sounds': + 'Sons', + + // nomes: + 'Sprite': + 'o actor', + 'Stage': + 'o palco', + + // estilos de rotação: + 'don\'t rotate': + 'não roda', + 'can rotate': + 'roda', + 'only face left/right': + 'olha apenas para a esquerda ou para a direita', + + // botão de criação de novo actor: + 'add a new sprite': + 'adicionar um novo actor', + + // ajuda nos tabuladores + 'costumes tab help': + 'Importa uma imagem de uma página Web ou de um\n' + + 'arquivo no teu computador arrastando-a para aqui', + 'import a sound from your computer\nby dragging it into here': + 'Importa um som do teu computador\narrastando-o para aqui', + + // blocos primitivos: + + /* + Attention Translators: + ---------------------- + At this time your translation of block specs will only work + correctly, if the order of formal parameters and their types + are unchanged. Placeholders for inputs (formal parameters) are + indicated by a preceding % prefix and followed by a type + abbreviation. + + For example: + + 'say %s for %n secs' + + can currently not be changed into + + 'say %n secs long %s' + + and still work as intended. + + Similarly + + 'point towards %dst' + + cannot be changed into + + 'point towards %cst' + + without breaking its functionality. + */ + + // movimento: + 'Stage selected:\nno motion primitives': + 'Palco seleccionado:\nsem primitivas de movimento', + + + 'move %n steps': + 'anda %n passos', + 'turn %clockwise %n degrees': + 'gira %clockwise %n °', + 'turn %counterclockwise %n degrees': + 'gira %counterclockwise %n °', + 'point in direction %dir': + 'altera a tua direcção para %dir °', + 'point towards %dst': + 'aponta em direcção a %dst', + 'go to x: %n y: %n': + 'vai para as coordenadas (x: %n , y: %n )', + 'go to %dst': + 'vai para a posição de %dst', + 'glide %n secs to x: %n y: %n': + 'desliza em %n s para as coordenadas (x: %n , y: %n )', + 'change x by %n': + 'adiciona %n à tua coordenada x', + 'set x to %n': + 'altera a tua coordenada x para %n', + 'change y by %n': + 'adiciona %n à tua coordenada y', + 'set y to %n': + 'altera a tua coordenada y para %n', + 'if on edge, bounce': + 'se estiveres a bater na borda, ressalta', + 'x position': + 'a coordenada x da posição', + 'y position': + 'a coordenada y da posição', + 'direction': + 'a direcção', + + // aparência: + 'switch to costume %cst': + 'muda o traje para %cst', + 'next costume': + 'passa para o próximo traje', + 'costume #': + 'o número do traje', + 'costume name': + 'o nome do traje', + 'say %s for %n secs': + 'diz %s durante %n s', + 'say %s': + 'diz %s', + 'think %s for %n secs': + 'pensa %s durante %n s', + 'think %s': + 'pensa %s', + 'Hello!': + 'Olá!', + 'Hmm...': + 'Hmm…', + 'change %eff effect by %n': + 'adiciona ao efeito %eff o valor %n', + 'set %eff effect to %n': + 'altera o teu efeito %eff para %n', + 'clear graphic effects': + 'limpa os efeitos gráficos', + 'change size by %n': + 'adiciona %n % ao teu tamanho', + 'set size to %n %': + 'altera o teu tamanho para %n %', + 'size': + 'o tamanho', + 'show': + 'mostra-te', + 'hide': + 'esconde-te', + 'go to front': + 'vem para a frente', + 'go back %n layers': + 'recua %n camadas', + + 'development mode \ndebugging primitives:': + 'primitivas de depuração \ndo modo de desenvolvimento:', + 'console log %mult%s': + 'regista %mult%s na consola', + 'alert %mult%s': + 'mostra janela de alerta com %mult%s', + + // sons: + 'play sound %snd': + 'toca o som %snd', + 'play sound %snd until done': + 'toca o som %snd até terminar', + 'stop all sounds': + 'pára todos os sons', + 'rest for %n beats': + 'faz uma pausa de %n tempos', + 'play note %n for %n beats': + 'toca a nota %n durante %n tempos', + 'change tempo by %n': + 'adiciona %n bpm ao teu andamento', + 'set tempo to %n bpm': + 'altera o teu andamento para %n bpm', + 'tempo': + 'o andamento', + + // caneta: + 'clear': + 'apaga tudo do palco', + 'pen down': + 'baixa a tua caneta', + 'pen up': + 'levanta a tua caneta', + 'set pen color to %clr': + 'altera a cor da tua caneta para %clr', + 'change pen color by %n': + 'adiciona %n à cor da tua caneta', + 'set pen color to %n': + 'altera a cor da tua caneta para %n', + 'change pen shade by %n': + 'adiciona %n ao tom da tua caneta', + 'set pen shade to %n': + 'altera o tom da tua caneta para %n', + 'change pen size by %n': + 'adiciona %n à espessura da tua caneta', + 'set pen size to %n': + 'altera a espessura da tua caneta para %n', + 'stamp': + 'carimba-te', + + // controlo: + 'when %greenflag clicked': + 'Quando alguém clicar em %greenflag', + 'when %keyHat key pressed': + 'Quando alguém pressionar a tecla %keyHat', + 'when I am clicked': + 'Quando alguém clicar em ti', + 'when I receive %msgHat': + 'Quando receberes a mensagem %msgHat', + 'broadcast %msg': + 'difunde a mensagem %msg', + 'broadcast %msg and wait': + 'difunde a mensagem %msg e espera', + 'Message name': + 'Qual o nome da mensagem?', + 'wait %n secs': + 'espera %n s', + 'wait until %b': + 'espera até que %b', + 'forever %c': + 'repete %c para sempre', + 'repeat %n %c': + 'repete %n vezes %c', + 'repeat until %b %c': + 'até que %b , repete %c', + 'if %b %c': + 'se %b , então %c', + 'if %b %c else %c': + 'se %b , então %c senão, %c', + 'report %s': + 'reporta %s', + 'stop block': + 'pára este guião de bloco', + 'stop script': + 'pára este guião de actor', + 'stop all %stop': + 'pára tudo %stop', + 'run %cmdRing %inputs': + 'executa %cmdRing %inputs', + 'launch %cmdRing %inputs': + 'lança execução de %cmdRing %inputs', + 'call %repRing %inputs': + 'o resultado da invocação de %repRing %inputs', + 'run %cmdRing w/continuation': + 'executa %cmdRing com continuação', + 'call %cmdRing w/continuation': + 'o resultado da invocação de %cmdRing com continuação', + 'warp %c': + 'executa atomicamente %c', + 'when I start as a clone': + 'Quando fores criado como clone', + 'create a clone of %cln': + 'clona o actor %cln', + 'myself': + 'tu próprio', + 'delete this clone': + 'remove-te', + + // sensores: + 'touching %col ?': + 'estás a tocar em %col', + 'touching %clr ?': + 'estás a tocar na cor %clr', + 'color %clr is touching %clr ?': + 'a cor %clr está a tocar na cor %clr', + 'ask %s and wait': + 'pergunta %s e espera pela resposta', + 'what\'s your name?': + 'Como te chamas?', + 'answer': + 'a resposta dada', + 'mouse x': + 'a coordenada x do rato', + 'mouse y': + 'a coordenada y do rato', + 'mouse down?': + 'o botão do rato está pressionado', + 'key %key pressed?': + 'a tecla %key está a ser pressionada', + 'distance to %dst': + 'a distância até %dst', + 'reset timer': + 'reinicia o cronómetro', + 'timer': + 'o valor do cronómetro', + '%att of %spr': + '%att de %spr', + 'http:// %s': + 'http:// %s', + 'turbo mode?': + 'o modo turbo está activo', + 'set turbo mode to %b': + 'alterar o modo turbo para %b', + + 'filtered for %clr': + 'filtrado para %clr', + 'stack size': + 'altura da pilha', + 'frames': + 'molduras', + + // operadores: + '%n mod %n': + 'o resto de %n a dividir por %n', + 'round %n': + 'o arredondamento de %n', + '%fun of %n': + '%fun de %n', + 'pick random %n to %n': + 'um valor ao acaso entre %n e %n', + '%b and %b': + '%b e %b', + '%b or %b': + '%b ou %b', + 'not %b': + 'é falso que %b', + 'true': + 'verdadeiro', + 'false': + 'falso', + 'join %words': + 'a junção de %words', + 'hello': + 'Olá', + 'world': + 'mundo!', + 'letter %n of %s': + 'o caractere %n de %s', + 'length of %s': + 'o comprimento de %s', + 'unicode of %s': + 'o código Unicode do caractere %s', + 'unicode %n as letter': + 'o caractere cujo código Unicode é %n', + 'is %s a %typ ?': + '%s é um/uma %typ', + 'is %s identical to %s ?': + '%s é idêntico a %s', + + 'type of %s': + 'o tipo de %s', + + // variáveis: + 'Make a variable': + 'Criar uma variável', + 'Variable name': + 'Qual o nome da variável?', + 'Delete a variable': + 'Remover uma variável', + + 'set %var to %s': + 'altera %var para %s', + 'change %var by %n': + 'adiciona a %var o valor %n', + 'show variable %var': + 'mostra a variável %var', + 'hide variable %var': + 'esconde a variável %var', + 'script variables %scriptVars': + 'cria as variáveis de guião %scriptVars', + + // listas: + 'list %exp': + 'uma nova lista com %exp', + '%s in front of %l': + 'a prefixação de %s a %l', + 'item %idx of %l': + '%idx de %l', + 'all but first of %l': + 'uma nova lista com todos menos o primeiro item de %l', + 'length of %l': + 'o comprimento de %l', + '%l contains %s': + '%l contém %s', + 'thing': + 'um valor', + 'add %s to %l': + 'acrescenta %s a %l', + 'delete %ida of %l': + 'remove %ida de %l', + 'insert %s at %idx of %l': + 'insere %s antes de %idx de %l', + 'replace item %idx of %l with %s': + 'substitui %idx de %l por %s', + + // outros + 'Make a block': + 'Criar um bloco', + + // menus + // snap menu + 'About...': + 'Acerca do Snap!…', + 'Reference manual': + 'Ler o Manual de referência', + 'Snap! website': + 'Ir para o sítio Web do Snap!', + 'Download source': + 'Descarregar o código fonte', + 'Switch back to user mode': + 'Regressar ao modo de utilizador', + 'disable deep-Morphic\ncontext menus\nand show user-friendly ones': + 'Desactivar menus de contexto\nprofundos do Morphic e\nmostrar menus amigáveis.', + 'Switch to dev mode': + 'Passar ao modo de desenvolvimento', + 'enable Morphic\ncontext menus\nand inspectors,\nnot user-friendly!': + 'Activar menus de contexto\ne inspectores não\namigáveis do Morphic!', + + // menu de projecto + 'Project notes...': + 'Notas deste projecto…', + 'New': + 'Criar um novo projecto', + 'Open...': + 'Abrir um projecto…', + 'Save': + 'Guardar este projecto', + 'Save As...': + 'Guardar este projecto como…', + 'Import...': + 'Importar para este projecto…', + 'file menu import hint': + 'Importar para este projecto\num projecto exportado,\n' + + 'uma biblioteca de blocos,\n' + + 'um traje ou um som.', + 'Export project as plain text...': + 'Exportar este projecto como texto simples…', + 'Export project...': + 'Exportar este projecto…', + 'show project data as XML\nin a new browser window': + 'Mostrar os dados do projecto no\nformato XML numa nova janela do navegador.', + 'Export blocks...': + 'Exportar blocos deste projecto…', + 'show global custom block definitions as XML\nin a new browser window': + 'Mostrar as definições de blocos\npersonalizados globais no formato\nXML numa nova janela do navegador.', + 'Import tools': + 'Importar as ferramentas oficiais para este projecto', + 'load the official library of\npowerful blocks': + 'Importar para este projecto\na biblioteca oficial de blocos.', + + // menu da nuvem + 'Login...': + 'Entrar na sua conta…', + 'Signup...': + 'Registar uma nova conta…', + + // menu de preferências + 'Language...': + 'Língua…', + 'Zoom blocks...': + 'Ampliação dos blocos…', + 'Blurred shadows': + 'Sombras desfocadas', + 'uncheck to use solid drop\nshadows and highlights': + 'Desassinalar para usar sombras\ne realces nítidos.', + 'check to use blurred drop\nshadows and highlights': + 'Assinalar para usar sombras\ne realces desfocados.', + 'Zebra coloring': + 'Coloração em zebra', + 'check to enable alternating\ncolors for nested blocks': + 'Assinalar para alternar\nas cores de blocos aninhados.', + 'uncheck to disable alternating\ncolors for nested block': + 'Desassinalar para deixar de alternar\nas cores de blocos aninhados.', + 'Dynamic input labels': + 'Etiquetas de entrada dinâmicas', + 'uncheck to disable dynamic\nlabels for variadic inputs': + 'Desassinalar para desactivar etiquetas\ndinâmicas nas entradas variádicas.', + 'check to enable dynamic\nlabels for variadic inputs': + 'Assinalar para activar etiquetas\ndinâmicas nas entradas variádicas.', + 'Prefer empty slot drops': + 'Preferir largadas em ranhuras vazias', + 'settings menu prefer empty slots hint': + 'Assinalar para focar em ranhuras vazias\nquando arrastando e ' + + 'largando repórteres.', + 'uncheck to allow dropped\nreporters to kick out others': + 'Desassinalar para permitir que\nrepórteres largados ' + + 'desalojem outros.', + 'Long form input dialog': + 'Forma longa da caixa de diálogo dos parâmetros', + 'check to always show slot\ntypes in the input dialog': + 'Assinalar para mostrar sempre\no tipo das ranhuras na caixa\nde diálogo dos parâmetros.', + 'uncheck to use the input\ndialog in short form': + 'Desassinalar para usar a forma curta\nda caixa de diálogo dos parâmetros.', + 'Virtual keyboard': + 'Teclado virtual', + 'uncheck to disable\nvirtual keyboard support\nfor mobile devices': + 'Desassinalar para desactivar o\nsuporte do teclado virtual\npara dispositivos ' + + 'móveis.', + 'check to enable\nvirtual keyboard support\nfor mobile devices': + 'Assinalar para activar o\nsuporte do teclado virtual\npara dispositivos ' + + 'móveis.', + 'Input sliders': + 'Deslizadores nas ranhuras', + 'uncheck to disable\ninput sliders for\nentry fields': + 'Desassinalar para desactivar\ndeslizadores nas ranhuras dos blocos.', + 'check to enable\ninput sliders for\nentry fields': + 'Assinalar para activar deslizadores\nnas ranhuras dos blocos.', + 'Clicking sound': + 'Som de cliques', + 'uncheck to turn\nblock clicking\nsound off': + 'Desassinalar para desactivar o som\nproduzido ao clicar nos blocos.', + 'check to turn\nblock clicking\nsound on': + 'Assinar para activar o som\nproduzido ao clicar nos blocos.', + 'Animations': + 'Animações', + 'uncheck to disable\nIDE animations': + 'Desassinalar para desactivar\nas animações do AID', + 'Turbo mode': + 'Modo turbo', + 'check to prioritize\nscript execution': + 'Assinalar para dar prioridade\nà execução de guiões.', + 'uncheck to run scripts\nat normal speed': + 'Desssinalar para executar os guiões\nà velocidade normal.', + 'check to enable\nIDE animations': + 'Assinalar para activar\nas animações do AID', + 'Thread safe scripts': + 'Guiões seguros face a threads', + 'uncheck to allow\nscript reentrance': + 'Desassinar para permitir\nreentrância nos guiões.', + 'check to disallow\nscript reentrance': + 'Assinar para não permitir\nreentrância nos guiões.', + 'Prefer smooth animations': + 'Preferir animações suaves', + 'uncheck for greater speed\nat variable frame rates': + 'Desassinalar para aumentar a velocidade\npermitindo ritmos variáveis das tramas.', + 'check for smooth, predictable\nanimations across computers': + 'Assinalar para obter animações mais suaves\ne previsíveis de computador para computador.', + + // entradas + 'with inputs': + 'com argumentos', + 'input names:': + 'com parâmetros', + 'Input Names:': + 'Parâmetros:', + 'input list:': + 'lista de argumentos:', + + // menus de contexto: + 'help': + 'ajuda', + + // blocos: + 'help...': + 'ajuda…', + 'relabel...': + 'mudar para outro bloco…', + 'duplicate': + 'duplicar', + 'make a copy\nand pick it up': + 'Fazer uma cópia do\nbloco e agarrá-la.', + 'only duplicate this block': + 'Duplicar apenas este bloco.', + 'delete': + 'remover', + 'script pic...': + 'imagem do guião…', + 'open a new window\nwith a picture of this script': + 'Abrir uma nova janela com\numa imagem deste guião.', + 'ringify': + 'adicionar anel', + 'unringify': + 'remover anel', + + // blocos personalizados: + 'delete block definition...': + 'remover definição do bloco…', + 'edit...': + 'editar…', + + // actores: + 'edit': + 'editar', + 'export...': + 'exportar…', + + // palco: + 'show all': + 'mostrar todos os actores', + 'pic...': + 'fotografar...', + 'open a new window\nwith a picture of the stage': + 'Abre uma nova janela com\numa fotografia do palco.', + + // área de guiões: + 'clean up': + 'arrumar', + 'arrange scripts\nvertically': + 'Organizar os guiões\nverticalmente.', + 'add comment': + 'adicionar comentário', + 'make a block...': + 'criar um bloco…', + + // trajes: + 'rename': + 'alterar o nome', + 'export': + 'exportar', + 'rename costume': + 'Qual o novo nome do traje?', + + // sounds + 'Play sound': + 'Tocar som.', + 'Stop sound': + 'Parar som.', + 'Stop': + 'Parar', + 'Play': + 'Tocar', + 'rename sound': + 'Qual o novo nome do som?', + + // caixas de diálogo + // botões + 'OK': + 'OK', + 'Ok': + 'OK', + 'Cancel': + 'Cancelar', + 'Yes': + 'Sim', + 'No': + 'Não', + 'Open': + 'Abrir', + 'Share': + 'Partilhar', + + // ajuda + 'Help': + 'Ajuda', + + // ampliação de blocos + 'Zoom blocks': + 'Ampliação de blocos', + 'build': + 'cria', + 'your own': + 'os teus próprios', + 'blocks': + 'blocos', + 'normal (1x)': + 'normal (1x)', + 'demo (1.2x)': + 'demonstração (1.2x)', + 'presentation (1.4x)': + 'apresentação (1.4x)', + 'big (2x)': + 'grande (2x)', + 'huge (4x)': + 'enorme (4x)', + 'giant (8x)': + 'gigante (8x)', + 'monstrous (10x)': + 'monstruosa (10x)', + + // Gestor de Projectos + 'Untitled': + 'Sem título', + 'Open Project': + 'Abrir Projecto', + '(empty)': + '(nada)', + 'Saved!': + 'Guardado!', + 'Delete Project': + 'Remover Projecto', + 'Are you sure you want to delete': + 'Quer mesmo remover?', + 'rename...': + 'alterar o nome…', + 'Cloud': + 'Nuvem', + 'Browser': + 'Navegador', + 'Examples': + 'Exemplos', + 'You are not logged in': + 'Ainda não se autenticou', + 'Updating project list...': + 'Actualizando a lista de projectos…', + 'Opening project...': + 'Abrindo o projecto…', + 'Fetching project from the cloud...': + 'Obtendo o projecto da nuvem…', + 'Saving project to the cloud...': + 'Guardando o projecto na nuvem…', + 'saved.': + 'guardado.', + + // editor de trajes + 'Costume Editor': + 'Editor de Trajes', + 'click or drag crosshairs to move the rotation center': + 'Clique ou arraste a mira para alterar o centro de rotação.', + + // notas de projecto + 'Project Notes': + 'Notas do Projecto', + + // novo projecto + 'New Project': + 'Novo Projecto', + 'Replace the current project with a new one?': + 'Substituir este projecto por um novo projecto?', + + // guardar projecto + 'Save Project As...': + 'Guardar Projecto Como…', + + // exportar blocos + 'Export blocks': + 'Exportar blocos', + 'Import blocks': + 'Importar blocos', + 'this project doesn\'t have any\ncustom global blocks yet': + 'Este projecto ainda não tem\nnenhum bloco personalizado global.', + 'select': + 'seleccionar', + 'all': + 'todos', + 'none': + 'nenhum', + + // caixa de diálogo de variáveis + 'for all sprites': + 'para todos os objectos', + 'for this sprite only': + 'apenas para este objecto', + + // caixa de diálogo de blocos + 'Change block': + 'Alterar tipo de bloco', + 'Command': + 'Comando', + 'Reporter': + 'Repórter', + 'Predicate': + 'Predicado', + + // editor de blocos + 'Block Editor': + 'Editor de Blocos', + 'Apply': + 'Aplicar', + + // caixa de diálogo de remoção de bloco + 'Delete Custom Block': + 'Remover Bloco Personalizado', + 'block deletion dialog text': + 'Quer mesmo remover este bloco e ' + + 'todas as suas utilizações?', + + // caixa de diálogo de parâmetros + 'Create input name': + 'Criar parâmetro', + 'Edit input name': + 'Editar parâmetro', + 'Edit label fragment': + 'Editar etiqueta', + 'Title text': + 'Etiqueta', + 'Input name': + 'Parâmetro', + 'Delete': + 'Remover', + 'Object': + 'Objecto', + 'Number': + 'Número', + 'Text': + 'Texto', + 'List': + 'Lista', + 'Any type': + 'Qualquer tipo', + 'Boolean (T/F)': + 'Booleano (V/F)', + 'Command\n(inline)': + 'Comando\n(em linha)', + 'Command\n(C-shape)': + 'Comando\n(em forquilha)', + 'Any\n(unevaluated)': + 'Repórter\n(forma especial)', + 'Boolean\n(unevaluated)': + 'Predicado\n(forma especial)', + 'Single input.': + 'Parâmetro único.', + 'Default Value:': + 'Valor em caso de omissão do argumento:', + 'Multiple inputs (value is list of inputs)': + 'Múltiplos argumentos (o valor do parâmetro é a lista dos argumentos).', + 'Upvar - make internal variable visible to caller': + 'Tornar o parâmetro visível ao invocador.', + + // Acerca do Snap + 'About Snap': + 'Sobre o Snap!', + 'Back...': + 'Para trás…', + 'License...': + 'Licença…', + 'Modules...': + 'Módulos…', + 'Credits...': + 'Créditos…', + 'Translators...': + 'Tradutores…', + 'License': + 'Licença', + 'current module versions:': + 'versões actuais dos módulos', + 'Contributors': + 'Contribuidores', + 'Translations': + 'Traduções', + + // observadores de variáveis + 'normal': + 'normal', + 'large': + 'grande', + 'slider': + 'deslizador', + 'slider min...': + 'mínimmo do deslizador…', + 'slider max...': + 'máximo do deslizador…', + 'import...': + 'importar…', + 'Slider minimum value': + 'Valor mínimo do deslizador', + 'Slider maximum value': + 'Valor máximo do deslizador', + + // observadores de listas + 'length: ': + 'comprimento: ', + + // comentários + 'add comment here...': + 'colocar aqui um comentário…', + + // drop downs + // direcções + '(90) right': + '90° (direita)', + '(-90) left': + '-90° (esquerda)', + '(0) up': + '0° (cima)', + '(180) down': + '180° (baixo)', + + // detecção de colisões + 'mouse-pointer': + 'ponteiro do rato', + 'edge': + 'borda', + 'pen trails': + 'traços da caneta', + + // trajes + 'Turtle': + 'Seta', + 'Empty': + 'Vazio', + + // efeitos gráficos + 'ghost': + 'fantasma', + + // teclas + 'space': + 'espaço', + 'up arrow': + 'seta para cima', + 'down arrow': + 'seta para baixo', + 'right arrow': + 'seta para a direita', + 'left arrow': + 'seta para a esquerda', + 'a': + 'a', + 'b': + 'b', + 'c': + 'c', + 'd': + 'd', + 'e': + 'e', + 'f': + 'f', + 'g': + 'g', + 'h': + 'h', + 'i': + 'i', + 'j': + 'j', + 'k': + 'k', + 'l': + 'l', + 'm': + 'm', + 'n': + 'n', + 'o': + 'o', + 'p': + 'p', + 'q': + 'q', + 'r': + 'r', + 's': + 's', + 't': + 't', + 'u': + 'u', + 'v': + 'v', + 'w': + 'w', + 'x': + 'x', + 'y': + 'y', + 'z': + 'z', + '0': + '0', + '1': + '1', + '2': + '2', + '3': + '3', + '4': + '4', + '5': + '5', + '6': + '6', + '7': + '7', + '8': + '8', + '9': + '9', + + // messagens + 'new...': + 'Nova…', + + // funções matemáticas + 'abs': + 'valor absoluto', + 'sqrt': + 'raiz quadrada', + 'sin': + 'seno', + 'cos': + 'cosseno', + 'tan': + 'tangente', + 'asin': + 'arco-seno', + 'acos': + 'arco-cosseno', + 'atan': + 'arco-tangente', + 'ln': + 'logaritmo natural', + 'e^': + 'exponencial', + + // tipos de dados + 'number': + 'número', + 'text': + 'texto', + 'Boolean': + 'booleano', + 'list': + 'lista', + 'command': + 'comando', + 'reporter': + 'repórter', + 'predicate': + 'predicado', + + // índices de listas + 'last': + 'o fim', + 'any': + 'qualquer dos itens' +}; diff --git a/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/lang-ru.js b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/lang-ru.js new file mode 100644 index 0000000..107d46c --- /dev/null +++ b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/lang-ru.js @@ -0,0 +1,1120 @@ +/* + + lang-ru.js + + Russian translation for SNAP! + + This file is part of Snap!. + + Snap! is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + + + + Note to Translators: + -------------------- + At this stage of development, Snap! can be translated to any LTR language + maintaining the current order of inputs (formal parameters in blocks). + + Translating Snap! is easy: + + + 1. Download + + Download the sources and extract them into a local folder on your + computer: + + + + Use the German translation file (named 'lang-de.js') as template for your + own translations. Start with editing the original file, because that way + you will be able to immediately check the results in your browsers while + you're working on your translation (keep the local copy of snap.html open + in your web browser, and refresh it as you progress with your + translation). + + + 2. Edit + + Edit the translation file with a regular text editor, or with your + favorite JavaScript editor. + + In the first non-commented line (the one right below this + note) replace "de" with the two-letter ISO 639-1 code for your language, + e.g. + + fr - French => SnapTranslator.dict.fr = { + it - Italian => SnapTranslator.dict.it = { + pl - Polish => SnapTranslator.dict.pl = { + pt - Portuguese => SnapTranslator.dict.pt = { + es - Spanish => SnapTranslator.dict.es = { + el - Greek => => SnapTranslator.dict.el = { + + etc. (see ) + + + 3. Translate + + Then work through the dictionary, replacing the German strings against + your translations. The dictionary is a straight-forward JavaScript ad-hoc + object, for review purposes it should be formatted as follows: + + { + 'English string': + 'Translation string', + 'last key': + } 'last value' + + and you only edit the indented value strings. Note that each key-value + pair needs to be delimited by a comma, but that there shouldn't be a comma + after the last pair (again, just overwrite the template file and you'll be + fine). + + If something doesn't work, or if you're unsure about the formalities you + should check your file with + + + + This will inform you about any missed commas etc. + + + 4. Accented characters + + Depending on which text editor and which file encoding you use you can + directly enter special characters (e.g. Umlaut, accented characters) on + your keyboard. However, I've noticed that some browsers may not display + special characters correctly, even if other browsers do. So it's best to + check your results in several browsers. If you want to be on the safe + side, it's even better to escape these characters using Unicode. + + see: + + + 5. Block specs: + + At this time your translation of block specs will only work + correctly, if the order of formal parameters and their types + are unchanged. Placeholders for inputs (formal parameters) are + indicated by a preceding % prefix and followed by a type + abbreviation. + + For example: + + 'say %s for %n secs' + + can currently not be changed into + + 'say %n secs long %s' + + and still work as intended. + + Similarly + + 'point towards %dst' + + cannot be changed into + + 'point towards %cst' + + without breaking its functionality. + + + 6. Submit + + When you're done, rename the edited file by replacing the "de" part of the + filename with the two-letter ISO 639-1 code for your language, e.g. + + fr - French => lang-fr.js + it - Italian => lang-it.js + pl - Polish => lang-pl.js + pt - Portuguese => lang-pt.js + es - Spanish => lang-es.js + el - Greek => => lang-el.js + + and send it to me for inclusion in the official Snap! distribution. + Once your translation has been included, Your name will the shown in the + "Translators" tab in the "About Snap!" dialog box, and you will be able to + directly launch a translated version of Snap! in your browser by appending + + lang:xx + + to the URL, xx representing your translations two-letter code. + + + 7. Known issues + + In some browsers accents or ornaments located in typographic ascenders + above the cap height are currently (partially) cut-off. + + Enjoy! + -Jens +*/ + +/*global SnapTranslator*/ + +SnapTranslator.dict.ru = { + +/* + Special characters: (see ) + + €, Š \u00c4, \u00e4 +…, š \u00d6, \u00f6 + †, Ÿ \u00dc, \u00fc + § \u00df +*/ + + // translations meta information + 'language_name': + 'Русский', // the name as it should appear in the language menu + 'language_translator': + 'Svetlana Ptashnaya', // your name for the Translators tab + 'translator_e-mail': + 'svetlanap@berkeley.edu', // optional + 'last_changed': + '2013-03-19', // this, too, will appear in the Translators tab + + // GUI + // control bar: + 'untitled': + 'неозаглавленный', + 'development mode': + 'разрабатываемая версия', + + // categories: + 'Motion': + 'Движение', + 'Looks': + 'Изображение', + 'Sound': + 'Звук', + 'Pen': + 'Перо', + 'Control': + 'Управление', + 'Sensing': + 'Состояние', + 'Operators': + 'Операторы', + 'Variables': + 'Переменные', + 'Lists': + 'Списки', + 'Other': + 'Прочее', + + // editor: + 'draggable': + 'движимый', + + // tabs: + 'Scripts': + 'Скрипты', + 'Costumes': + 'Маски', + 'Sounds': + 'Звучания', + + // names: + 'Sprite': + 'Образ', + 'Stage': + 'Сцена', + + // rotation styles: + 'don\'t rotate': + 'не вращаемый', + 'can rotate': + 'вращаемый', + 'only face left/right': + 'вращаемый только слева направо', + + // new sprite button: + 'add a new sprite': + 'Добавить новый Образ', + + // tab help + 'costumes tab help': + 'импорт изображение с другого веб-сайта\nили со своего компьютера скопировав его сюда', + 'import a sound from your computer\nby dragging it into here': + 'импорт звук со своего компьютера\nскопировав его сюда', + + // primitive blocks: + + /* + Attention Translators: + ---------------------- + At this time your translation of block specs will only work + correctly, if the order of formal parameters and their types + are unchanged. Placeholders for inputs (formal parameters) are + indicated by a preceding % prefix and followed by a type + abbreviation. + + For example: + + 'say %s for %n secs' + + can currently not be changed into + + 'say %n secs long %s' + + and still work as intended. + + Similarly + + 'point towards %dst' + + cannot be changed into + + 'point towards %cst' + + without breaking its functionality. + */ + + // motion: + 'Stage selected:\nno motion primitives': + 'Сцена-клавиша нажата:\nДвижение-примитивы отключены', + + 'move %n steps': + 'передвинуть на %n шагов', + 'turn %clockwise %n degrees': + 'повернуть %clockwise на %n градусов', + 'turn %counterclockwise %n degrees': + 'повернуть %counterclockwise на %n градусов', + 'point in direction %dir': + 'указывать в направл. %dir', + 'point towards %dst': + 'указывать на %dst', + 'go to x: %n y: %n': + 'перейти в точку x %n y %n', + 'go to %dst': + 'перейти в точку %dst', + 'glide %n secs to x: %n y: %n': + 'скользить %n сек к x %n y %n', + 'change x by %n': + 'изменить х на %n', + 'set x to %n': + 'установить х %n', + 'change y by %n': + 'изменить y на %n', + 'set y to %n': + 'установить y %n', + 'if on edge, bounce': + 'на грани развернуться', + 'x position': + 'x позиция', + 'y position': + 'y позиция', + 'direction': + 'направление', + + // looks: + 'switch to costume %cst': + 'измен. маску на %cst', + 'next costume': + 'следующая маска', + 'costume #': + 'маска #', + 'say %s for %n secs': + 'произн. %s в теч. %n сек.', + 'say %s': + 'произнести %s', + 'think %s for %n secs': + 'думать %s в теч. %n сек.', + 'think %s': + 'думать %s', + 'Hello!': + 'Привет!', + 'Hmm...': + 'Хмм...', + 'change %eff effect by %n': + 'измен. %eff эфф. на %n', + 'set %eff effect to %n': + 'устан. %eff эфф. %n', + 'clear graphic effects': + 'аннулировать графич. эфф-ты', + 'change size by %n': + 'изменить размер на %n', + 'set size to %n %': + 'установить размер %n %', + 'size': + 'размер', + 'show': + 'показывать', + 'hide': + 'прятать', + 'go to front': + 'переместить вперед', + 'go back %n layers': + 'перемест. на %n уровня назад', + + 'development mode \ndebugging primitives:': + 'Разрабатываемая версия \nотладка примитивов:', + 'console log %mult%s': + 'консоль-регистрация %mult%', + 'alert %mult%s': + 'предупреждение %mult%', + + // sound: + 'play sound %snd': + 'воспроизводить звук %snd', + 'play sound %snd until done': + 'воспроизв. звук %snd до конца', + 'stop all sounds': + 'остановить все звуки', + 'rest for %n beats': + 'пауза %n тактов', + 'play note %n for %n beats': + 'сыграть ноту %n %n тактов', + 'change tempo by %n': + 'изменить темп на %n', + 'set tempo to %n bpm': + 'устан. темп %n ударов в мин.', + 'tempo': + 'темп', + + // pen: + 'clear': + 'убрать все', + 'pen down': + 'опустить перо', + 'pen up': + 'поднять перо', + 'set pen color to %clr': + 'установить цвет пера %clr', + 'change pen color by %n': + 'изменить цвет пера на %n', + 'set pen color to %n': + 'установить цвет пера %n', + 'change pen shade by %n': + 'изменить яркость пера на %n', + 'set pen shade to %n': + 'установить яркость пера %n', + 'change pen size by %n': + 'изменить размер пера на %n', + 'set pen size to %n': + 'установить размер пера %n', + 'stamp': + 'оттиск', + + // control: + 'when %greenflag clicked': + 'когда щелкнуть на %greenflag', + 'when %keyHat key pressed': + 'когда нажать %keyHat клавишу', + 'when I am clicked': + 'когда щелкнуть на меня', + 'when I receive %msgHat': + 'когда я получу %msgHat', + 'broadcast %msg': + 'переслать %msg всем', + 'broadcast %msg and wait': + 'переслать %msg всем и ждать', + 'Message name': + 'Название сообщения', + 'wait %n secs': + 'ждать %n сек.', + 'wait until %b': + 'ждать пока %b', + 'forever %c': + 'непрерывно %c', + 'repeat %n %c': + 'повторять %n %c', + 'repeat until %b %c': + 'повторять пока %b %c', + 'if %b %c': + 'если %b %c', + 'if %b %c else %c': + 'если %b %c иначе %c', + 'report %s': + 'результат %s', + 'stop block': + 'стоп блок', + 'stop script': + 'стоп скрипт', + 'stop all %stop': + 'стоп все %stop', + 'run %cmdRing %inputs': + 'выполнять %cmdRing %inputs', + 'launch %cmdRing %inputs': + 'запустить %cmdRing %inputs', + 'call %repRing %inputs': + 'вызвать %repRing %inputs', + 'run %cmdRing w/continuation': + 'выполнять %cmdRing с продолжением', + 'call %cmdRing w/continuation': + 'вызвать %cmdRing с продолжением', + 'warp %c': + 'warp %c', + + // sensing: + 'touching %col ?': + 'касаеться %col ?', + 'touching %clr ?': + 'касаеться %clr ?', + 'color %clr is touching %clr ?': + 'цвет %clr касаеться %clr ?', + 'ask %s and wait': + 'спросить %s и ждать', + 'what\'s your name?': + 'как вас зовут?', + 'answer': + 'ответ', + 'mouse x': + 'мышка x-позиция', + 'mouse y': + 'мышка y-позиция', + 'mouse down?': + 'клавиша мышки нажата?', + 'key %key pressed?': + 'клавиша %key нажата?', + 'distance to %dst': + 'расстояние до %dst', + 'reset timer': + 'переустановить таймер', + 'timer': + 'таймер', + 'http:// %s': + 'http:// %s', + + 'filtered for %clr': + 'отфильтровано для %clr', + 'stack size': + 'размер стека', + 'frames': + 'рамки', + + // operators: + '%n mod %n': + '%n по модулю %n', + 'round %n': + 'округлить %n', + '%fun of %n': + '%fun %n', + 'pick random %n to %n': + 'случайное число от %n до %n', + '%b and %b': + '%b и %b', + '%b or %b': + '%b или %b', + 'not %b': + 'не %b', + 'true': + 'истина', + 'false': + 'ложь', + 'join %words': + 'объединить %words', + 'hello': + 'привет', + 'world': + 'мир', + 'letter %n of %s': + '%n буква слова %s', + 'length of %s': + 'длина %s', + 'unicode of %s': + 'Unicode буквы %s', + 'unicode %n as letter': + 'буква с Unicode %n', + 'is %s a %typ ?': + '%s это %typ ?', + 'is %s identical to %s ?': + '%s тождественно %s ?', + + 'type of %s': + 'тип %s', + + // variables: + 'Make a variable': + 'Объявить переменную', + 'Variable name': + 'Имя переменной', + 'Delete a variable': + 'Удалить переменную', + + 'set %var to %s': + 'придать %var значение %s', + 'change %var by %n': + 'изменить %var на %n', + 'show variable %var': + 'показать переменную %var', + 'hide variable %var': + 'спрятать переменную %var', + 'script variables %scriptVars': + 'переменные скрипта %scriptVars', + + // lists: + 'list %exp': + 'список %exp', + '%s in front of %l': + '%s впереди %l', + 'item %idx of %l': + 'элемент %idx из %l', + 'all but first of %l': + 'все кроме первого из %l', + 'length of %l': + 'длина %l', + '%l contains %s': + '%l содержит %s', + 'thing': + 'что-либо', + 'add %s to %l': + 'добавить %s к %l', + 'delete %ida of %l': + 'удалить %ida из %l', + 'insert %s at %idx of %l': + 'встав. %s в полож. %idx в %l', + 'replace item %idx of %l with %s': + 'заменить элем. %idx в %l на %s', + + // other + 'Make a block': + 'Новый блок', + + // menus + // snap menu + 'About...': + 'Snap! Реквизиты', + 'Snap! website': + 'Snap! веб-сайт', + 'Download source': + 'Загрузить материалы источника', + 'Switch back to user mode': + 'Вернуться в режим пользователя', + 'disable deep-Morphic\ncontext menus\nand show user-friendly ones': + 'отключить deep-Morphic\nконтекст меню', + 'Switch to dev mode': + 'перейти в режим разрабатываемой версии', + 'enable Morphic\ncontext menus\nand inspectors,\nnot user-friendly!': + 'включить Morphic\nконтекст меню', + + // project menu + 'Project notes...': + 'Проектные Записки...', + 'New': + 'Новый проект', + 'Open...': + 'Открыть...', + 'Save': + 'Сохранить', + 'Save As...': + 'Сохранить как...', + 'Import...': + 'Импорт...', + 'file menu import hint': + 'загрузить экспортированный проект\nили библиотеку блоков, маску или звук', + 'Export project as plain text...': + 'Экспорт проект как текстовый файл...', + 'Export project...': + 'Экспорт проект...', + 'show project data as XML\nin a new browser window': + 'представить проектные данные как XML\nв новом окне браузера', + 'Export blocks...': + 'Экспорт блоки...', + 'show global custom block definitions as XML\nin a new browser window': + 'представить определения глобальных пользовательских блоков как XML\nв новом окне браузера', + 'Import tools': + 'Импорт сервисные ср-ва', + 'load the official library of\npowerful blocks': + 'загрузить служебную библиотеку блоков', + + // settings menu + 'Language...': + 'Язык...', + 'Blurred shadows': + 'Контрастность тени', + 'uncheck to use solid drop\nshadows and highlights': + 'убрать метку - использовать сплошные\nтени и подсветки', + 'check to use blurred drop\nshadows and highlights': + 'поставить метку - использовать размытые\nтени и подсветки', + 'Zebra coloring': + 'Использование альтернативных цветов', + 'check to enable alternating\ncolors for nested blocks': + 'поставить метку - разрешить использование\nперемежающихся цветов для вложенных блоков', + 'uncheck to disable alternating\ncolors for nested block': + 'убрать метку - отключить использование\nперемежающихся цветов для вложенных блоков', + 'Dynamic input labels': + 'Использование динамических обозначений', + 'uncheck to disable dynamic\nlabels for variadic inputs': + 'убрать метку - отключить использование динамических обозначений\nпри вводе с переменным числом аргументов', + 'check to enable dynamic\nlabels for variadic inputs': + 'поставить метку - разрешить использование динамических обозначений\nпри вводе с переменным числом аргументов', + 'Prefer empty slot drops': + 'Использование незанятых ячеек ввода', + 'settings menu prefer empty slots hint': + 'поставить метку - помещать генераторы значений\nтолько в незанятые ячейки ввода', + 'uncheck to allow dropped\nreporters to kick out others': + 'убрать метку - разрешить помещать генераторы значений\nв занятые ячейки ввода', + 'Long form input dialog': + 'Расширенная форма диалога ввода', + 'check to always show slot\ntypes in the input dialog': + 'поставить метку - указывать типы ячеек ввода\nв диалоге ввода', + 'uncheck to use the input\ndialog in short form': + 'убрать метку - использовать краткую форму\nдиалога ввода', + 'Virtual keyboard': + 'Виртуальная клавиатура', + 'uncheck to disable\nvirtual keyboard support\nfor mobile devices': + 'убрать метку - отключить использование виртуальной клавиатуры\nдля мобильных устройств', + 'check to enable\nvirtual keyboard support\nfor mobile devices': + 'поставить метку - разрешить использование виртуальной клавиатуры\nдля мобильных устройств', + 'Input sliders': + 'Использование бегунков ввода', + 'uncheck to disable\ninput sliders for\nentry fields': + 'убрать метку - отключить использование бегунков\nпри заполнении полей ввода', + 'check to enable\ninput sliders for\nentry fields': + 'поставить метку - разрешить использование бегунков\nпри заполнении полей ввода', + 'Clicking sound': + 'Щелк-звук', + 'uncheck to turn\nblock clicking\nsound off': + 'убрать метку - выключить звук\nпри щелчке на блок', + 'check to turn\nblock clicking\nsound on': + 'поставить метку - включить звук\nпри щелчке на блок', + 'Animations': + 'Aнимация', + 'uncheck to disable\nIDE animations': + 'убрать метку - отключить\nIDE aнимацию', + 'check to enable\nIDE animations': + 'поставить метку - разрешить\nIDE aнимацию', + 'Thread safe scripts': + 'Защищенность скрипта в многопоточном режиме', + 'uncheck to allow\nscript reentrancy': + 'убрать метку - разрешить\nповторное вхождение в скрипт', + 'check to disallow\nscript reentrancy': + 'поставить метку - отключить\nповторное вхождение в скрипт', + + // inputs + 'with inputs': + 'с вводимыми данными', + 'input names:': + 'имена вводимых данных:', + 'Input Names:': + 'Имена Вводимых Данных:', + 'input list:': + 'вводимый список:', + + // context menus: + 'help': + 'Справка', + + // blocks: + 'help...': + 'справка...', + 'relabel...': + 'переобозначить...', + 'duplicate': + 'продублировать', + 'make a copy\nand pick it up': + 'скопировать\nи запомнить', + 'only duplicate this block': + 'продублировать только данный блок', + 'delete': + 'удалить', + 'script pic...': + 'изображение скрипта...', + 'open a new window\nwith a picture of this script': + 'представить изображение скрипта\nна новой странице', + 'ringify': + 'обвести', + 'unringify': + 'убрать обводку', + + // custom blocks: + 'delete block definition...': + 'удалить определение блока', + 'edit...': + 'редактировать...', + + // sprites: + 'edit': + 'редактировать', + 'export...': + 'экспорт...', + + // stage: + 'show all': + 'показать все', + + // scripting area + 'clean up': + 'упорядочить', + 'arrange scripts\nvertically': + 'размещать скрипты\nвертикально', + 'add comment': + 'добавить комментарий', + 'make a block...': + 'новый блок...', + + // costumes + 'rename': + 'переименовать', + 'export': + 'экспорт', + 'rename costume': + 'переименовать маску', + + // sounds + 'Play sound': + 'Воспроизводить звук', + 'Stop sound': + 'Остановить звук', + 'Stop': + 'Стоп', + 'Play': + 'Воспроизводить', + 'rename sound': + 'переименовать звук', + + // dialogs + // buttons + 'OK': + 'OK', + 'Ok': + 'Ok', + 'Cancel': + 'Отменить', + 'Yes': + 'Да', + 'No': + 'Нет', + + // help + 'Help': + 'Справка', + + // Project Manager + 'Untitled': + 'Неозаглавленный', + 'Open Project': + 'Открыть Проект', + '(empty)': + '(пусто)', + 'Saved!': + 'Сохранен!', + 'Delete Project': + 'Удалить Проект', + 'Are you sure you want to delete': + 'Вы уверены вы хотите удалить?', + 'rename...': + 'переименовать...', + + // costume editor + 'Costume Editor': + 'Редактор Масок', + 'click or drag crosshairs to move the rotation center': + 'щелкните на перекрестье переместить центр поворота', + + // project notes + 'Project Notes': + 'Проектные Записки', + + // new project + 'New Project': + 'Новый Проект', + 'Replace the current project with a new one?': + 'Заменить данный проект на новый?', + + // save project + 'Save Project As...': + 'Сохранить Проект как...', + + // export blocks + 'Export blocks': + 'Экспорт блоки', + 'Import blocks': + 'Импорт блоки', + 'this project doesn\'t have any\ncustom global blocks yet': + 'У этого проекта пока нет глобальных\nпользовательских блоков', + 'select': + 'выделить', + 'all': + 'все', + 'none': + 'ничего', + + // variable dialog + 'for all sprites': + 'применительно ко всем Образам', + 'for this sprite only': + 'применительно только к данному Образу', + + // block dialog + 'Change block': + 'Заменить блок', + 'Command': + 'Команда', + 'Reporter': + 'Генератор значений', + 'Predicate': + 'Предикат', + + // block editor + 'Block Editor': + 'Редактор Блоков', + 'Apply': + 'Применить', + + // block deletion dialog + 'Delete Custom Block': + 'Удалить Пользовательский Блок', + 'block deletion dialog text': + 'Вы уверены вы хотите удалить этот блок?', + + // input dialog + 'Create input name': + 'Определить имя вводимых данных', + 'Edit input name': + 'Редактировать имя вводимых данных', + 'Edit label fragment': + 'Редактировать фрагмент обозначения', + 'Title text': + 'Заголовок текста', + 'Input name': + 'Имя вводимых данных', + 'Delete': + 'Удалить', + 'Object': + 'Объект', + 'Number': + 'Число', + 'Text': + 'Tекст', + 'List': + 'Список', + 'Any type': + 'Любой тип', + 'Boolean (T/F)': + 'Булев (И/Л)', + 'Command\n(inline)': + 'Команда\n(встроенная)', + 'Command\n(C-shape)': + 'Команда\n(С-форма)', + 'Any\n(unevaluated)': + 'Любой\n(неопределенный)', + 'Boolean\n(unevaluated)': + 'Булев\n(неопределенный)', + 'Single input.': + 'Единичный ввод.', + 'Default Value:': + 'Значение по умолчанию:', + 'Multiple inputs (value is list of inputs)': + 'Многократный ввод (список)', + 'Upvar - make internal variable visible to caller': + 'Сделать внутреннюю переменную видимой извне', + + // About Snap + 'About Snap': + 'Snap! Реквизиты', + 'Back...': + 'Bозврат...', + 'License...': + 'Лицензия...', + 'Modules...': + 'Модули...', + 'Credits...': + 'Сотрудники...', + 'Translators...': + 'Переводчики', + 'License': + 'Лицензия', + 'current module versions:': + 'Komponenten-Versionen', + 'Contributors': + 'Участники', + 'Translations': + 'Переводы', + + // variable watchers + 'normal': + 'стандартный', + 'large': + 'масштабированный', + 'slider': + 'бегунок', + 'slider min...': + 'бегунок min...', + 'slider max...': + 'бегунок max...', + 'import...': + 'импорт...', + 'Slider minimum value': + 'Бегунок - min значение', + 'Slider maximum value': + 'Бегунок - max значение', + + // list watchers + 'length: ': + 'длина: ', + + // coments + 'add comment here...': + 'добавить комментарий сюда...', + + // drow downs + // directions + '(90) right': + '(90) направо', + '(-90) left': + '(-90) налево', + '(0) up': + '(0) вверх', + '(180) down': + '(180) вниз', + + // collision detection + 'mouse-pointer': + 'курсор мышки', + 'edge': + 'грань', + 'pen trails': + 'линии пера', + + // costumes + 'Turtle': + 'Горлица', + + // graphical effects + 'ghost': + 'прозрачн.', + + // keys + 'space': + 'пробел', + 'up arrow': + 'стрелка вверх', + 'down arrow': + 'стрелка вниз', + 'right arrow': + 'стрелка вправо', + 'left arrow': + 'стрелка влево', + 'a': + 'a', + 'b': + 'b', + 'c': + 'c', + 'd': + 'd', + 'e': + 'e', + 'f': + 'f', + 'g': + 'g', + 'h': + 'h', + 'i': + 'i', + 'j': + 'j', + 'k': + 'k', + 'l': + 'l', + 'm': + 'm', + 'n': + 'n', + 'o': + 'o', + 'p': + 'p', + 'q': + 'q', + 'r': + 'r', + 's': + 's', + 't': + 't', + 'u': + 'u', + 'v': + 'v', + 'w': + 'w', + 'x': + 'x', + 'y': + 'y', + 'z': + 'z', + '0': + '0', + '1': + '1', + '2': + '2', + '3': + '3', + '4': + '4', + '5': + '5', + '6': + '6', + '7': + '7', + '8': + '8', + '9': + '9', + + // messages + 'new...': + 'новый...', + + // math functions + 'abs': + 'абсолютное значение', + 'sqrt': + 'квадратный корень', + 'sin': + 'sin', + 'cos': + 'cos', + 'tan': + 'tan', + 'asin': + 'asin', + 'acos': + 'acos', + 'atan': + 'atan', + 'ln': + 'ln', + 'e^': + 'e^', + + // data types + 'number': + 'число', + 'text': + 'текст', + 'Boolean': + 'булев', + 'list': + 'список', + 'command': + 'команда', + 'reporter': + 'генератор значений', + 'predicate': + 'предикат', + + // list indices + 'last': + 'последний', + 'any': + 'любой' +}; diff --git a/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/lang-si.js b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/lang-si.js new file mode 100644 index 0000000..8985286 --- /dev/null +++ b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/lang-si.js @@ -0,0 +1,1069 @@ +/* + + lang-si.js + + Slovenian translation for SNAP! + + translated by Sasa Divjak + + Copyright (C) 2012 by Jens Mönig + + This file is part of Snap!. + + Snap! is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + + + + Note to Translators: + -------------------- + At this stage of development, Snap! can be translated to any LTR language + maintaining the current order of inputs (formal parameters in blocks). + + Translating Snap! is easy: + + + 1. Download + + Download the sources and extract them into a local folder on your + computer: + + + + Use the German translation file (named 'lang-de.js') as template for your + own translations. Start with editing the original file, because that way + you will be able to immediately check the results in your browsers while + you're working on your translation (keep the local copy of snap.html open + in your web browser, and refresh it as you progress with your + translation). + + + 2. Edit + + Edit the translation file with a regular text editor, or with your + favorite JavaScript editor. + + In the first non-commented line (the one right below this + note) replace "de" with the two-letter ISO 639-1 code for your language, + e.g. + + fr - French => SnapTranslator.dict.fr = { + it - Italian => SnapTranslator.dict.it = { + pl - Polish => SnapTranslator.dict.pl = { + pt - Portuguese => SnapTranslator.dict.pt = { + es - Spanish => SnapTranslator.dict.es = { + el - Greek => => SnapTranslator.dict.el = { + si - Slovenian => => SnapTranslator.dict.si = { + + etc. (see ) + + + 3. Translate + + Then work through the dictionary, replacing the German strings against + your translations. The dictionary is a straight-forward JavaScript ad-hoc + object, for review purposes it should be formatted as follows: + + { + 'English string': + 'Translation string', + 'last key': + } 'last value' + + and you only edit the indented value strings. Note that each key-value + pair needs to be delimited by a comma, but that there shouldn't be a comma + after the last pair (again, just overwrite the template file and you'll be + fine). + + If something doesn't work, or if you're unsure about the formalities you + should check your file with + + + + This will inform you about any missed commas etc. + + + 4. Accented characters + + Depending on which text editor and which file encoding you use you can + directly enter special characters (e.g. Umlaut, accented characters) on + your keyboard. However, I've noticed that some browsers may not display + special characters correctly, even if other browsers do. So it's best to + check your results in several browsers. If you want to be on the safe + side, it's even better to escape these characters using Unicode. + + see: + + + 5. Block specs: + + At this time your translation of block specs will only work + correctly, if the order of formal parameters and their types + are unchanged. Placeholders for inputs (formal parameters) are + indicated by a preceding % prefix and followed by a type + abbreviation. + + For example: + + 'say %s for %n secs' + + can currently not be changed into + + 'say %n secs long %s' + + and still work as intended. + + Similarly + + 'point towards %dst' + + cannot be changed into + + 'point towards %cst' + + without breaking its functionality. + + + 6. Submit + + When you're done, rename the edited file by replacing the "de" part of the + filename with the two-letter ISO 639-1 code for your language, e.g. + + fr - French => lang-fr.js + it - Italian => lang-it.js + pl - Polish => lang-pl.js + pt - Portuguese => lang-pt.js + es - Spanish => lang-es.js + el - Greek => => lang-el.js + si - Slovenian => => lang-si.js + + and send it to me for inclusion in the official Snap! distribution. + Once your translation has been included, Your name will the shown in the + "Translators" tab in the "About Snap!" dialog box, and you will be able to + directly launch a translated version of Snap! in your browser by appending + + lang:xx + + to the URL, xx representing your translations two-letter code. + + + 7. Known issues + + In some browsers accents or ornaments located in typographic ascenders + above the cap height are currently (partially) cut-off. + + Enjoy! + -Jens +*/ + +/*global SnapTranslator*/ + +SnapTranslator.dict.si = { + +/* + Special characters: (see ) + + Ss , \u0160, \u0161 + Cc \u010C, \u010D + Zz, \u017D, \u017E + +*/ + + // translations meta information + 'language_name': + 'Sloven\u0161\u010Dina', // the name as it should appear in the language menu + 'language_translator': + 'Sasa Divjak', // your name for the Translators tab + 'translator_e-mail': + 'sasa.divjak@fri.uni-lj.si', // optional + 'last_changed': + '2013-01-07', // this, too, will appear in the Translators tab + + // GUI + // control bar: + 'untitled': + 'Neimenovan', + 'development mode': + 'Razvojni na\u010Din', + + // categories: + 'Motion': + 'Premikanje', + 'Looks': + 'Izgled', + 'Sound': + 'Zvok', + 'Pen': + 'Svin\u010Dnik', + 'Control': + 'Krmiljenje', + 'Sensing': + 'Zaznavanje', + 'Operators': + 'Operatorji', + 'Variables': + 'Spremenljivke', + 'Lists': + 'Seznami', + 'Other': + 'Drugo', + + // editor: + 'draggable': + 'vle\u010Dljiv', + + // tabs: + 'Scripts': + 'Skripte', + 'Costumes': + 'Obleke', + 'Sounds': + 'Zvoki', + + // names: + 'Sprite': + 'Objekt', + 'Stage': + 'Oder', + + // rotation styles: + 'don\'t rotate': + 'ne vrti', + 'can rotate': + 'prosto vrtenje', + 'only face left/right': + 'lahko obrnemo le levo/desno', + + // new sprite button: + 'add a new sprite': + 'dodaj nov objekt', + + // tab help + 'costumes tab help': + 'Slike uvozi\u0161 s povle\u010Denjem iz ene druge\n' + + 'spletne strani ali ra\u010Dunalnika', + 'import a sound from your computer\nby dragging it into here': + 'Zvok uvozi\u0161 tako, da ga povle\u010De\u0161 sem', + + // primitive blocks: + + /* + Attention Translators: + ---------------------- + At this time your translation of block specs will only work + correctly, if the order of formal parameters and their types + are unchanged. Placeholders for inputs (formal parameters) are + indicated by a preceding % prefix and followed by a type + abbreviation. + + For example: + + 'say %s for %n secs' + + can currently not be changed into + + 'say %n secs long %s' + + and still work as intended. + + Similarly + + 'point towards %dst' + + cannot be changed into + + 'point towards %cst' + + without breaking its functionality. + */ + + // motion: + 'Stage selected:\nno motion primitives': + 'Oder je izbran:\nni na voljo gibljivih gradnikov', + + 'move %n steps': + 'premakni se %n korakov', + 'turn %clockwise %n degrees': + 'obrni se %clockwise %n stopinj', + 'turn %counterclockwise %n degrees': + 'obrni se %counterclockwise %n stopinj', + 'point in direction %dir': + 'obrni se v smeri %dir', + 'point towards %dst': + 'obrni se proti %dst', + 'go to x: %n y: %n': + 'pojdi na x: %n y: %n', + 'go to %dst': + 'pojdi k %dst', + 'glide %n secs to x: %n y: %n': + 'drsi %n sekund do x: %n y: %n', + 'change x by %n': + 'spremeni x za %n', + 'set x to %n': + 'nastavi x na %n', + 'change y by %n': + 'spremeni y za %n', + 'set y to %n': + 'nastavi y na %n', + 'if on edge, bounce': + 'odbij se, \u010De si na robu', + 'x position': + 'polo\u017Eaj x', + 'y position': + 'polo\u017Eaj y', + 'direction': + 'smer', + + // looks: + 'switch to costume %cst': + 'Preklopi na obleko %cst', + 'next costume': + 'naslednja obleka', + 'costume #': + '\u0160t.obleke', + 'say %s for %n secs': + 'reci %s za %n sekund.', + 'say %s': + 'reci %s', + 'think %s for %n secs': + 'misli %s za %n sekund', + 'think %s': + 'misli %s', + 'Hello!': + 'Halo!', + 'Hmm...': + 'Hmm...', + 'change %eff effect by %n': + 'spremeni u\u010Dinek %eff za %n', + 'set %eff effect to %n': + 'nastavi u\u010Dinek %eff na %n', + 'clear graphic effects': + 'zbri\u0161i grafi\u010Dne u\u010Dinke', + 'change size by %n': + 'spremeni velikost za %n', + 'set size to %n %': + 'nastavi velikost na %n %', + 'size': + 'velikost', + 'show': + 'prika\u017Ei', + 'hide': + 'skrij', + 'go to front': + 'prestavi v ospredje', + 'go back %n layers': + 'prestavi %n ravnin nazaj', + + 'development mode \ndebugging primitives:': + 'razvojni na\u010Din \nrazhro\u0161\u010Devanje gradnikov', + 'console log %mult%s': + 'izpi\u0161i na konzolo: %mult%s', + 'alert %mult%s': + 'pozor: %mult%s', + + // sound: + 'play sound %snd': + 'predvajaj zvok %snd', + 'play sound %snd until done': + 'predvajaj zvok %snd do konca', + 'stop all sounds': + 'ustavi vse zvoke', + + // pen: + 'clear': + 'zbri\u0161i', + 'pen down': + 'svin\u010Dnik spu\u0161\u010Den', + 'pen up': + 'svin\u010Dnik dvignjen', + 'set pen color to %clr': + 'nastavi barvo svin\u010Dnika na %clr', + 'change pen color by %n': + 'spremeni barvo svin\u010Dnika za %n', + 'set pen color to %n': + 'nastavi barvo svin\u010Dnika na %n', + 'change pen shade by %n': + 'spremeni senco svin\u010Dnika za %n', + 'set pen shade to %n': + 'nastavi senco svin\u010Dnika na %n', + 'change pen size by %n': + 'spremeni debelino svin\u010Dnika za %n', + 'set pen size to %n': + 'nastavi debelino svin\u010Dnika na %n', + 'stamp': + '\u0161tampiljka', + + // control: + 'when %greenflag clicked': + 'ko kliknemo na %greenflag', + 'when %keyHat key pressed': + 'ko pritisnemo na tipko %keyHat ', + 'when I am clicked': + 'ko je klik name', + 'when I receive %msgHat': + 'ko sprejmem %msgHat', + 'broadcast %msg': + 'po\u0161lji %msg vsem', + 'broadcast %msg and wait': + 'po\u0161lji vsem %msg in po\u010Dakaj', + 'Message name': + 'Obvestilo', + 'wait %n secs': + '\u010Dakaj %n sekund.', + 'wait until %b': + '\u010Dakaj, dokler %b', + 'forever %c': + 'za vedno %c', + 'repeat %n %c': + 'ponovi %n krat %c', + 'repeat until %b %c': + 'ponavljaj, dokler %b %c', + 'if %b %c': + '\u010De %b %c', + 'if %b %c else %c': + '\u010De %b %c sicer %c', + 'report %s': + 'sporo\u010Di %s', + 'stop block': + 'ustavi ta blok', + 'stop script': + 'ustavi ta program', + 'stop all %stop': + 'ustavi vse %stop', + 'run %cmdRing %inputs': + 'izvajaj %cmdRing %inputs', + 'launch %cmdRing %inputs': + 'po\u017Eeni %cmdRing %inputs', + 'call %repRing %inputs': + 'pokli\u010Di %repRing %inputs', + 'run %cmdRing w/continuation': + 'izvajaj %cmdRing z nadaljevanjem', + 'call %cmdRing w/continuation': + 'pokli\u010Di %cmdRing z nadaljevanjem', + 'warp %c': + 'Warp %c', + + // sensing: + 'touching %col ?': + 'se dotika %col ?', + 'touching %clr ?': + 'se dotika %clr ?', + 'color %clr is touching %clr ?': + 'barva %clr se dotika %clr ?', + 'ask %s and wait': + 'vpra\u0161aj %s in \u010Dakaj', + 'what\'s your name?': + 'Kako ti je ime?', + 'answer': + 'odgovor', + 'mouse x': + 'x polo\u017Eaj mi\u0161ke', + 'mouse y': + 'y polo\u017Eaj mi\u0161ke', + 'mouse down?': + 'gumb mi\u0161ke pritisnjen?', + 'key %key pressed?': + 'tipka %key pritisnjena?', + 'distance to %dst': + 'razdalja do %dst', + 'reset timer': + 'reset \u0161toparice', + 'timer': + '\u0161toparica', + 'http:// %s': + 'http:// %s', + + 'filtered for %clr': + 'filtriran za %clr', + 'stack size': + 'velikost sklada', + 'frames': + 'sli\u010Dice', + + // operators: + '%n mod %n': + '%n modulo %n', + 'round %n': + 'zaokro\u017Eeno %n', + '%fun of %n': + '%fun von %n', + 'pick random %n to %n': + 'naklju\u010Dno \u0161tevilo od %n do %n', + '%b and %b': + '%b in %b', + '%b or %b': + '%b ali %b', + 'not %b': + 'ne %b', + 'true': + 'res', + 'false': + 'ni res', + 'join %words': + 'pove\u017Ei %words', + 'hello': + 'Halo', + 'world': + 'Svet', + 'letter %n of %s': + '\u010Drka %n od %s', + 'length of %s': + 'dol\u017Eina %s', + 'unicode of %s': + 'Unicode vrednost od %s', + 'unicode %n as letter': + 'Unicode %n kot \u010Drka', + 'is %s a %typ ?': + 'je %s tipa %typ ?', + + 'type of %s': + 'Tip od %s', + + // variables: + 'Make a variable': + 'Nova spremenljivka', + 'Variable name': + 'Ime spremenljivke', + 'Delete a variable': + 'Zbri\u0161i spremenljivko', + + 'set %var to %s': + 'nastavi %var na %s', + 'change %var by %n': + 'spremeni spremenljivko %var za %n', + 'show variable %var': + 'prika\u017Ei spremenljivko %var', + 'hide variable %var': + 'skrij spremenljivko %var', + 'script variables %scriptVars': + 'spremenljivke programa %scriptVars', + + // lists: + 'list %exp': + 'Seznam %exp', + '%s in front of %l': + '%s na za\u010Detku %l', + 'item %idx of %l': + 'Element %idx od %l', + 'all but first of %l': + 'vsi razen prvega od %l', + 'length of %l': + 'dol\u017Eina %l', + '%l contains %s': + '%l vsebuje %s', + 'thing': + 'stvar', + 'add %s to %l': + 'dodaj %s k %l', + 'delete %ida of %l': + 'zbri\u0161i %ida iz %l', + 'insert %s at %idx of %l': + 'vstavi %s na mesto %idx v %l', + 'replace item %idx of %l with %s': + 'zamenjaj element %idx v %l z %s', + + // other + 'Make a block': + 'Nov blok', + + // menus + // snap menu + 'About...': + 'Nekaj o Snap!...', + 'Snap! website': + 'Spletna stran Snap!', + 'Download source': + 'Nalo\u017Ei izvorno kodo', + 'Switch back to user mode': + 'Preklop nazaj na uporabni\u0161ki na\u010Din', + 'disable deep-Morphic\ncontext menus\nand show user-friendly ones': + 'izklop Morfic menujev in prikaz uporabni\u0161ko prijaznih', + 'Switch to dev mode': + 'preklop na razvojni na\u010Din', + 'enable Morphic\ncontext menus\nand inspectors,\nnot user-friendly!': + 'omogo\u010Di Morphic menuje in in\u0161pektorje, \ni uporabniku prijazno', + + // project menu + 'Project notes...': + 'Opis projekta...', + 'New': + 'Nov', + 'Open...': + 'Odpri...', + 'Save': + 'Shrani', + 'Save As...': + 'Shrani kot...', + 'Import...': + 'Uvozi...', + 'file menu import hint': + 'Nalaganje izvo\u017Eenega projekta,\nknji\u017Enice z ' + + 'bloki\n' + + 'obleko ali zvokom', + 'Export project as plain text...': + 'Izvozi projekt kot navadno besedilo...', + 'Export project...': + 'Izvozi projekt...', + 'show project data as XML\nin a new browser window': + 'Prikaz projekta kot XML\nv novem oknu brkljalnika', + 'Export blocks...': + 'Izvozi bloke', + 'show global custom block definitions as XML\nin a new browser window': + 'Prikaz definicij globalnih lastnih blokov kot XML\nv novem oknu brkljalnika', + + // settings menu + 'Language...': + 'Jezik...', + 'Blurred shadows': + 'Mehke sence', + 'uncheck to use solid drop\nshadows and highlights': + 'izklopi za uporabo trdih senc in osvetlitev', + 'check to use blurred drop\nshadows and highlights': + 'vklopi za mehke sence in osvetlitve', + 'Zebra coloring': + 'barvanje kot zebra', + 'check to enable alternating\ncolors for nested blocks': + 'vklopi izmenjujo\u010De barve vgnezdenih blokov', + 'uncheck to disable alternating\ncolors for nested block': + 'izklopi izmenjujo\u010De barve gnezdenih blokov', + 'Prefer empty slot drops': + 'Imejmo raje prazne reže', + 'settings menu prefer empty slots hint': + 'vklop raje namiga za prazne reže' + + 'zu bevorzugen', + 'uncheck to allow dropped\nreporters to kick out others': + 'razkljukaj za to, da reporterji odrinejo druge', + 'Long form input dialog': + 'Vhodni dialog dolge oblike', + 'check to always show slot\ntypes in the input dialog': + 'odkljukaj za prikaz tipov v vhodnih dialogih', + 'razkljukaj za uporabo kratke oblike vhodnih dialogov': + 'razkljukaj za uporabo dialoga kratke oblike', + 'Virtual keyboard': + 'Virtualna tipkovnicaa', + 'uncheck to disable\nvirtual keyboard support\nfor mobile devices': + 'razkljukaj za izklop podpore virtualne tipkovnice za mobilne naprave', + 'check to enable\nvirtual keyboard support\nfor mobile devices': + 'odkljukaj za vklop podpore z virtualni tipkovnico za mobilne naprave', + 'Input sliders': + 'Vhodni drsniki', + 'uncheck to disable\ninput sliders for\nentry fields': + 'razkljukaj za izklop vhodnih drsnikov', + 'check to enable\ninput sliders for\nentry fields': + 'odkljukaj za aktiviranje vhodnih drsnikov', + 'Clicking sound': + 'Akusti\u010Dno klikanje', + 'uncheck to turn\nblock clicking\nsound off': + 'razkljukaj za deaktiviranje akusti\u010Dnega klikanja', + 'check to turn\nblock clicking\nsound on': + 'odkljukaj za vklop akusti\u010Dnega klikanja', + 'Thread safe scripts': + 'Varnost niti', + 'uncheck to allow\nscript reentrancy': + 'razkljukaj za dopu\u0161\u010Danje ve\u010Dkraten vstop skript (reentrancy)', + 'check to disallow\nscript reentrancy': + 'odkljukaj za onemogo\u010Danje ve\u010Dkratnega vstopa skript', + + // inputs + 'with inputs': + 'z vhodi', + 'input names:': + 'imena vhodov:', + 'Input Names:': + 'imena vhodov:', + + // context menus: + 'help': + 'Pomo\u010D', + + // blocks: + 'help...': + 'Pomo\u010D...', + 'duplicate': + 'podvoji', + 'make a copy\nand pick it up': + 'kopiraj', + 'delete': + 'bri\u0161i', + 'script pic...': + 'slika skript...', + 'open a new window\nwith a picture of this script': + 'odpri novo okno s sliko tega skripta', + 'ringify': + 'Obkro\u017Ei', + 'unringify': + 'odstrani obro\u010D', + + // custom blocks: + 'delete block definition...': + 'bri\u0161i definicijo bloka', + 'edit...': + 'uredi...', + + // sprites: + 'edit': + 'uredi', + 'export...': + 'izvozi...', + + // scripting area + 'clean up': + 'po\u010Disti', + 'arrange scripts\nvertically': + 'uredi skripte vertikalno', + 'add comment': + 'dodaj komentar', + 'make a block...': + 'Gradnja novega bloka...', + + // costumes + 'rename': + 'preimenuj', + 'export': + 'izvozi', + + // sounds + 'Play sound': + 'Predvajaj zvok', + 'Stop sound': + 'Ustavi zvok', + 'Stop': + 'Ustavi', + 'Play': + 'Predvajaj', + + // dialogs + // buttons + 'OK': + 'V redu', + 'Cancel': + 'Prekli\u010Di', + 'Yes': + 'Da', + 'No': + 'Ne', + + // help + 'Help': + 'Pomo\u010D', + + // costume editor + 'Costume Editor': + 'Urejevalnik oblek', + 'click or drag crosshairs to move the rotation center': + 'Klikni ali povle\u010Di kri\u017Eec za premik centra vrtenja', + + // project notes + 'Project Notes': + 'Opis projekta', + + // new project + 'New Project': + 'Nov projekt', + 'Replace the current project with a new one?': + 'Zamenjam trenutni projekt z novim?', + + // open project + 'Open Project': + 'Odpri projekt', + + // save project + 'Save Project As...': + 'Shrani projekt kot...', + + // export blocks + 'Export blocks': + 'Izvoz blokov', + 'this project doesn\'t have any\ncustom global blocks yet': + 'ta projekt \u0161e nima lastnih globalnih blokov', + 'select': + 'izberi', + 'all': + 'vse', + 'none': + 'ni\u010D', + + // variable dialog + 'for all sprites': + 'za vse objekte', + 'for this sprite only': + 'le za ta objekt', + + // block dialog + 'Change block': + 'Spremeni blok', + 'Command': + 'Ukaz', + 'Reporter': + 'Funkcija', + 'Predicate': + 'Predikat', + + // block editor + 'Block Editor': + 'Urejevalnik blokov', + 'Apply': + 'Uporabi', + + // block deletion dialog + 'Delete Custom Block': + 'Zbri\u0161i latni blok', + 'block deletion dialog text': + 'Ali naj res zbri\u0161em ta blok\n' + + 'z vsemi njegovimi primeri?', + + // input dialog + 'Create input name': + 'Tvori ime vhoda', + 'Edit input name': + 'Uredi ime vhoda', + 'Title text': + 'Naslovno besedilo', + 'Input name': + 'ime vhoda', + 'Delete': + 'Bri\u0161i', + 'Object': + 'Objekt', + 'Number': + '\u0160tevilo', + 'Text': + 'Tekst', + 'List': + 'Seznam', + 'Any type': + 'Poljuben tip', + 'Boolean (T/F)': + 'Boolova spr. (W/F)', + 'Command\n(inline)': + 'Ukaz', + 'Command\n(C-shape)': + 'Ukaz\n(C-oblika)', + 'Any\n(unevaluated)': + 'Poljuben\n(neovrednoten)', + 'Boolean\n(unevaluated)': + 'Boolova spr.\n(neovrednotena)', + 'Single input.': + 'En vnos.', + 'Default Value:': + 'Privzeta vrednost:', + 'Multiple inputs (value is list of inputs)': + 'Ve\u010D vnosov (kot seznam)', + 'Upvar - make internal variable visible to caller': + 'interne spremenljivke naj bodo navzven vidne', + + // About Snap + 'About Snap': + 'nekaj o Snap', + 'Back...': + 'Nazaj...', + 'License...': + 'Licenca...', + 'Modules...': + 'Komponente...', + 'Credits...': + 'Sodelujo\u010Di...', + 'Translators...': + 'Prevajalci', + 'License': + 'Licenca', + 'current module versions:': + 'Verzije komponent', + 'Contributors': + 'Prispevali', + 'Translations': + 'prevodi', + + // variable watchers + 'normal': + 'normalen', + 'large': + 'velik', + 'slider': + 'drsnik', + 'slider min...': + 'min vrednost...', + 'slider max...': + 'maks vrednost...', + 'Slider minimum value': + 'Minimalna vrednost drsnika', + 'Slider maximum value': + 'Maksimalna vrednost drsnika', + + // list watchers + 'length: ': + 'Dol\u017Eina: ', + + // coments + 'add comment here...': + 'tu vnese\u0161 komentar', + + // drow downs + // directions + '(90) right': + '(90) desno', + '(-90) left': + '(-90) levo', + '(0) up': + '(0) gor', + '(180) right': + '(180) dol', + + // collision detection + 'mouse-pointer': + 'kazalec mi\u0161ke', + 'edge': + 'rob', + 'pen trails': + 'sledi svin\u010Dnika', + + // costumes + 'Turtle': + 'Kazalec smeri', + + // graphical effects + 'ghost': + 'prosojnost', + + // keys + 'space': + 'presledek', + 'up arrow': + 'pu\u0161\u010Dica gor', + 'down arrow': + 'pu\u0161\u010Dica dol', + 'right arrow': + 'pu\u0161\u010Dica desno', + 'left arrow': + 'pu\u0161\u010Dica levo', + 'a': + 'a', + 'b': + 'b', + 'c': + 'c', + 'd': + 'd', + 'e': + 'e', + 'f': + 'f', + 'g': + 'g', + 'h': + 'h', + 'i': + 'i', + 'j': + 'j', + 'k': + 'k', + 'l': + 'l', + 'm': + 'm', + 'n': + 'n', + 'o': + 'o', + 'p': + 'p', + 'q': + 'q', + 'r': + 'r', + 's': + 's', + 't': + 't', + 'u': + 'u', + 'v': + 'v', + 'w': + 'w', + 'x': + 'x', + 'y': + 'y', + 'z': + 'z', + '0': + '0', + '1': + '1', + '2': + '2', + '3': + '3', + '4': + '4', + '5': + '5', + '6': + '6', + '7': + '7', + '8': + '8', + '9': + '9', + + // messages + 'new...': + 'nov...', + + // math functions + 'abs': + 'abs', + 'sqrt': + 'koren', + 'sin': + 'sin', + 'cos': + 'cos', + 'tan': + 'tan', + 'asin': + 'asin', + 'acos': + 'acos', + 'atan': + 'atan', + 'ln': + 'ln', + 'e^': + 'e^', + + // data types + 'number': + '\u0161tevilo', + 'text': + 'Tekst', + 'Boolean': + 'logi\u010Dna spr.', + 'list': + 'seznam', + 'command': + 'ukaz', + 'reporter': + 'funkcijski blok', + 'predicate': + 'Predikat', + + // list indices + 'last': + 'zadnji', + 'any': + 'poljuben' +}; diff --git a/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/lang-tw.js b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/lang-tw.js new file mode 100644 index 0000000..cd6a262 --- /dev/null +++ b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/lang-tw.js @@ -0,0 +1,1214 @@ +/* + + lang-tw.js + + Traditional Chinese translation for SNAP! + SNAP 繁體中文翻譯版 + + written by chu-chung Huang + + Copyright (C) 2012 by Jens Mönig + + This file is part of Snap!. + + Snap! is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + + + + Note to Translators: + -------------------- + At this stage of development, Snap! can be translated to any LTR language + maintaining the current order of inputs (formal parameters in blocks). + + Translating Snap! is easy: + + + 1. Download + + Download the sources and extract them into a local folder on your + computer: + + + + Use the German translation file (named 'lang-de.js') as template for your + own translations. Start with editing the original file, because that way + you will be able to immediately check the results in your browsers while + you're working on your translation (keep the local copy of snap.html open + in your web browser, and refresh it as you progress with your + translation). + + + 2. Edit + + Edit the translation file with a regular text editor, or with your + favorite JavaScript editor. + + In the first non-commented line (the one right below this + note) replace "de" with the two-letter ISO 639-1 code for your language, + e.g. + + fr - French => SnapTranslator.dict.fr = { + it - Italian => SnapTranslator.dict.it = { + pl - Polish => SnapTranslator.dict.pl = { + pt - Portuguese => SnapTranslator.dict.pt = { + es - Spanish => SnapTranslator.dict.es = { + el - Greek => => SnapTranslator.dict.el = { + + etc. (see ) + + + 3. Translate + + Then work through the dictionary, replacing the German strings against + your translations. The dictionary is a straight-forward JavaScript ad-hoc + object, for review purposes it should be formatted as follows: + + { + 'English string': + 'Translation string', + 'last key': + } 'last value' + + and you only edit the indented value strings. Note that each key-value + pair needs to be delimited by a comma, but that there shouldn't be a comma + after the last pair (again, just overwrite the template file and you'll be + fine). + + If something doesn't work, or if you're unsure about the formalities you + should check your file with + + + + This will inform you about any missed commas etc. + + + 4. Accented characters + + Depending on which text editor and which file encoding you use you can + directly enter special characters (e.g. Umlaut, accented characters) on + your keyboard. However, I've noticed that some browsers may not display + special characters correctly, even if other browsers do. So it's best to + check your results in several browsers. If you want to be on the safe + side, it's even better to escape these characters using Unicode. + + see: + + + 5. Block specs: + + At this time your translation of block specs will only work + correctly, if the order of formal parameters and their types + are unchanged. Placeholders for inputs (formal parameters) are + indicated by a preceding % prefix and followed by a type + abbreviation. + + For example: + + 'say %s for %n secs' + + can currently not be changed into + + 'say %n secs long %s' + + and still work as intended. + + Similarly + + 'point towards %dst' + + cannot be changed into + + 'point towards %cst' + + without breaking its functionality. + + + 6. Submit + + When you're done, rename the edited file by replacing the "de" part of the + filename with the two-letter ISO 639-1 code for your language, e.g. + + fr - French => lang-fr.js + it - Italian => lang-it.js + pl - Polish => lang-pl.js + pt - Portuguese => lang-pt.js + es - Spanish => lang-es.js + el - Greek => => lang-el.js + + and send it to me for inclusion in the official Snap! distribution. + Once your translation has been included, Your name will the shown in the + "Translators" tab in the "About Snap!" dialog box, and you will be able to + directly launch a translated version of Snap! in your browser by appending + + lang:xx + + to the URL, xx representing your translations two-letter code. + + + 7. Known issues + + In some browsers accents or ornaments located in typographic ascenders + above the cap height are currently (partially) cut-off. + + Enjoy! + -Jens +*/ + +/*global SnapTranslator*/ + +SnapTranslator.dict.tw = { + +/* + Special characters: (see ) + + Ä, ä \u00c4, \u00e4 + Ö, ö \u00d6, \u00f6 + Ü, ü \u00dc, \u00fc + ß \u00df +*/ + + // translations meta information + 'language_name': + '繁體中文', // the name as it should appear in the language menu + 'language_translator': + 'cch', // your name for the Translators tab + 'translator_e-mail': + 'cchuang2009@gmail.com', // optional + 'last_changed': + '2013-8-14', // this, too, will appear in the Translators tab + + // GUI + // control bar: + 'untitled': + '無標題', + 'development mode': + '開發模式', + + // categories: + 'Motion': + '動作', + 'Looks': + '外觀', + 'Sound': + '聲音', + 'Pen': + '畫筆', + 'Control': + '控制', + 'Sensing': + '偵測', + 'Operators': + '運算', + 'Variables': + '變數', + 'Lists': + '鏈表', + 'Other': + '其他', + + // editor: + 'draggable': + '可拖動', + + // tabs: + 'Scripts': + '腳本', + 'Costumes': + '造型', + 'Sounds': + '聲音', + + // names: + 'Sprite': + '角色', + 'Stage': + '舞臺', + + // rotation styles: + 'don\'t rotate': + '不能旋轉', + 'can rotate': + '可以旋轉', + 'only face left/right': + '只能左右翻轉', + + // new sprite button: + 'add a new sprite': + '新增角色', + + // tab help + 'costumes tab help': + '造型選卡幫助\n要使用另外網站上的圖片或電腦中的圖像' + + '只需拖到圖像到這裏即可', + 'import a sound from your computer\nby dragging it into here': + '從電腦中導入音效檔案\n只需拖動音效檔案到這裏即可', + + // primitive blocks: + + /* + Attention Translators: + ---------------------- + At this time your translation of block specs will only work + correctly, if the order of formal parameters and their types + are unchanged. Placeholders for inputs (formal parameters) are + indicated by a preceding % prefix and followed by a type + abbreviation. + + For example: + + 'say %s for %n secs' + + can currently not be changed into + + 'say %n secs long %s' + + and still work as intended. + + Similarly + + 'point towards %dst' + + cannot be changed into + + 'point towards %cst' + + without breaking its functionality. + */ + + // motion: + 'Stage selected:\nno motion primitives': + '舞臺選擇:\n沒有動作程式語言', + + 'move %n steps': + '移動 %n 歩', + 'turn %clockwise %n degrees': + '順時鐘旋轉 %clockwise %n 度', + 'turn %counterclockwise %n degrees': + '逆時鐘旋轉 %counterclockwise %n 度', + 'point in direction %dir': + '面向 %dir 度', + 'point towards %dst': + '面向 %dst ', + 'go to x: %n y: %n': + '移到 x: %n y: %n ', + 'go to %dst': + '移到 %dst ', + 'glide %n secs to x: %n y: %n': + ' %n 秒內,移到 x: %n y: %n ', + 'change x by %n': + '增加 x 座標 %n ', + 'set x to %n': + '設定 x 座標為 %n ', + 'change y by %n': + '增加 y 座標 %n ', + 'set y to %n': + '設定 y 座標為 %n ', + 'if on edge, bounce': + '碰到邊緣就反彈', + 'x position': + 'x 座標', + 'y position': + 'y 座標', + 'direction': + '方向', + + // looks: + 'switch to costume %cst': + '切換到造型 %cst ', + 'next costume': + '下一個造型', + 'costume #': + '造型編號', + 'say %s for %n secs': + '說 %s %n 秒', + 'say %s': + '說 %s ', + 'think %s for %n secs': + '思考 %s %n 秒', + 'think %s': + '思考 %s ', + 'Hello!': + '你好!', + 'Hmm...': + '嗯...', + 'change %eff effect by %n': + '將 %eff 特效增加 %n ', + 'set %eff effect to %n': + '將 %eff 特效設定為 %n ', + 'clear graphic effects': + '清除所有圖形特效', + 'change size by %n': + '增加角色的大小 %n ', + 'set size to %n %': + '設定角色的大小為 %n ', + 'size': + '大小', + 'show': + '顯示', + 'hide': + '隱藏', + 'go to front': + '移至最上層', + 'go back %n layers': + '下移 %n 層', + + 'development mode \ndebugging primitives:': + '開發模式\n調式程式語言:', + 'console log %mult%s': + '控制臺日誌 %mult%s', + 'alert %mult%s': + '警告: %mult%s', + + // sound: + 'play sound %snd': + '播放聲音 %snd ', + 'play sound %snd until done': + '播放聲音 %snd 直到播放完畢', + 'stop all sounds': + '停止所有聲音', + 'rest for %n beats': + '停止 %n 秒', + 'play note %n for %n beats': + '彈奏 %n %n 拍', + 'change tempo by %n': + '加快節奏 %n', + 'set tempo to %n bpm': + '設定節奏為 %n', + 'tempo': + '節奏', + + // pen: + 'clear': + '清除所有畫筆', + 'pen down': + '落筆', + 'pen up': + '抬筆', + 'set pen color to %clr': + '設定畫筆顏色為 %clr ', + 'change pen color by %n': + '增加畫筆顏色值 %n ', + 'set pen color to %n': + '設定畫筆顏色值為 %n ', + 'change pen shade by %n': + '增加畫筆色度 %n ', + 'set pen shade to %n': + '設定畫筆色度為 %n ', + 'change pen size by %n': + '增加畫筆粗細 %n ', + 'set pen size to %n': + '設定畫筆的粗細為 %n ', + 'stamp': + '圖章', + + // control: + 'when %greenflag clicked': + '當 %greenflag 被點擊', + 'when %keyHat key pressed': + '當按下 %keyHat', + 'when I am clicked': + '當角色被點擊', + 'when I receive %msgHat': + '當接收到 %msgHat', + 'broadcast %msg': + '廣播 %msg ', + 'broadcast %msg and wait': + '廣播 %msg 並等待', + 'Message name': + '資訊名稱', + 'wait %n secs': + '等待 %n 秒', + 'wait until %b': + '直到 %b 前都等待闐', + 'forever %c': + '重複執行 %c', + 'repeat %n %c': + '重複執行 %n %c', + 'repeat until %b %c': + '重複執行直到 %b %c', + 'if %b %c': + '如果 %b %c', + 'if %b %c else %c': + '如果 %b %c 否則 %c', + 'report %s': + '報告 %s ', + 'stop block': + '停止程式塊', + 'stop script': + '停止腳本', + 'stop all %stop': + '全部停止 %stop', + 'run %cmdRing %inputs': + ' 行 %cmdRing %inputs ', + 'launch %cmdRing %inputs': + '啟動 %cmdRing %inputs ', + 'call %repRing %inputs': + '調用 %repRing %inputs ', + 'run %cmdRing/continuation': + '持續執行 %cmdRing ', + 'call %cmdRing w/continuation': + '持續調用 %cmdRing ', + 'warp %c': + '直接運行 %c', + 'when I start as a clone': + '以複製身份開始', + 'create a clone of %cln': + '複製 %cln', + 'myself': + '自身', + 'delete this clone': + '刪除這個複製', + + // sensing: + 'touching %col ?': + '碰到 %col ', + 'touching %clr ?': + '碰到顏色 %clr ', + 'color %clr is touching %clr ?': + '顏色 %clr 碰到了顏色 %clr ?', + 'ask %s and wait': + '詢問 %s 並等待', + 'what\'s your name?': + '你的名字?', + 'answer': + '回答', + 'mouse x': + '滑鼠的 x 座標', + 'mouse y': + '滑鼠的 y 座標', + 'mouse down?': + '按下滑鼠?', + 'key %key pressed?': + '按鍵 %key 是否按下?', + 'distance to %dst': + '到 %dst 的距離', + 'reset timer': + '計時器歸零', + 'timer': + '計時器', + 'http:// %s': + 'http:// %s', + 'turbo mode?': + 'Turbo模式', + 'set turbo mode to %b': + '設置 Turbo 模式 %b', + + 'filtered for %clr': + '選擇顏色 %clr ', + 'stack size': + '堆疊大小', + 'frames': + '框架', + + // operators: + '%n mod %n': + '%n 除以 %n 的餘數', + 'round %n': + '將 %n 四捨五入', + '%fun of %n': + '%fun %n', + 'pick random %n to %n': + '隨機在 %n 到 %n 間選一個數', + '%b and %b': + '%b 且 %b', + '%b or %b': + '%b 或 %b', + 'not %b': + '%b 不成立', + 'true': + '成立', + 'false': + '不成立', + 'join %words': + '將 %words 加入到', + 'hello': + '歡迎', + 'world': + '光臨', + 'letter %n of %s': + '第 %n 位元在文字 %s 中', + 'length of %s': + '%s 的長度', + 'unicode of %s': + '字元 %s 的Unicode編碼值', + 'unicode %n as letter': + 'Unicode編碼值為 %n 的字元', + 'is %s a %typ ?': + '%s 是 %typ 類型?', + 'is %s identical to %s ?': + '%s 與 %s 相同嗎?', + + 'type of %s': + '%s 類型', + + // variables: + 'Make a variable': + '新建變數', + 'Variable name': + '變數名', + 'Delete a variable': + '刪除變數', + + 'set %var to %s': + '設定變數 %var 的值為 %s ', + 'change %var by %n': + '增加變數 %var 的值 %n ', + 'show variable %var': + '顯示變數 %var ', + 'hide variable %var': + '隱藏變數 %var ', + 'script variables %scriptVars': + '腳本變數 %scriptVars', + + // lists: + 'list %exp': + '表列 %exp', + '%s in front of %l': + '設定 %s 為 %l 第一項', + 'item %idx of %l': + '第 %idx 項在 %l 中', + 'all but first of %l': + ' %l 中除第一項之外內容', + 'length of %l': + ' %l 的大小', + '%l contains %s': + ' %l 包含 %s ', + 'thing': + '事項', + 'add %s to %l': + '將 %s 加入 %l ', + 'delete %ida of %l': + '刪除 %ida 第 %l 項', + 'insert %s at %idx of %l': + '插入 %s 到第 %idx 項在 %l 中', + 'replace item %idx of %l with %s': + '替換第 %idx 項在 %l 中為 %s ', + + // other + 'Make a block': + '新建程式塊', + + // menus + // snap menu + 'About...': + '關於Snap!...', + 'Reference manual': + '參考手冊', + 'Snap! website': + '官方網站', + 'Download source': + '下載源碼', + 'Switch back to user mode': + '切換到使用者模式', + 'disable deep-Morphic\ncontext menus\nand show user-friendly ones': + '停用 變形語式\n快顯功能表\n\n與非\n友好使用者介面', + 'Switch to dev mode': + '切換到開發人員模式', + 'enable Morphic\ncontext menus\nand inspectors,\nnot user-friendly!': + '啟用 正常語式\n快顯功能表\n與非檢查\n友好使用者介面', + + + // project menu + 'Project notes...': + '專案說明...', + 'New': + '新建', + 'Open...': + '打開...', + 'Save': + '存', + 'Save As...': + '另存為...', + 'Import...': + '導入...', + 'file menu import hint': + '當你拖動到系統,注意查看檢查報告\n' + + '要注意檢查報告為空\n\n' + + '有一些流覽器不支持這一功能', + 'Export project as plain text...': + '純文本格式導出專案...', + 'Export project...': + '導出項目...', + 'show project data as XML\nin a new browser window': + '新瀏覽視窗以XML格式顯示專案', + 'Export blocks...': + '輸出程式塊...', + 'show global custom block definitions as XML\nin a new browser window': + '新瀏覽視窗以XML格式顯示全局自定義程式塊', + 'Import tools': + '導入工具包', + 'load the official library of\npowerful blocks': + '載入官方庫和強大的程式塊', + + // cloud menu + 'Login...': + '登錄...', + 'Signup...': + '註冊...', + // settings menu + 'Language...': + '語言選擇...', + 'Zoom blocks...': + '放大程式塊...', + 'Blurred shadows': + '半透明陰影', + 'uncheck to use solid drop\nshadows and highlights': + '取消選中 降低陰影和高亮的清晰度', + 'check to use blurred drop\nshadows and highlights': + '檢測 降低陰影和高亮的模糊度', + 'Zebra coloring': + '斑馬著色', + 'check to enable alternating\ncolors for nested blocks': + '檢測 使嵌套塊的顏色交換', + 'uncheck to disable alternating\ncolors for nested block': + '取消選中 使嵌套塊的顏色交換', + 'Dynamic input labels': + '動態輸入標籤', + 'uncheck to disable dynamic\nlabels for variadic inputs': + '取消選中要禁用動態可變參數輸入標籤', + '檢查啟用動態可變參數輸入標籤': + 'marcar para habilitar etiquetas\ndin\u00E1micas para entradas varidic', + 'Prefer empty slot drops': + '喜歡減少空槽', + 'settings menu prefer empty slots hint': + '喜歡空槽設置菜單', + 'uncheck to allow dropped\nreporters to kick out others': + '取消選中 允許下降報告並取消其他報告', + 'Long form input dialog': + '長形式輸入對話方塊', + 'check to always show slot\ntypes in the input dialog': + '檢查顯示插槽在輸入對話方塊中的類型', + 'uncheck to use the input\ndialog in short form': + '取消選擇 輸入窗並顯示簡潔對話方塊', + 'Virtual keyboard': + '虛擬鍵盤', + 'uncheck to disable\nvirtual keyboard support\nfor mobile devices': + '取消選中 禁用虛擬鍵盤、可移動設備', + 'check to enable\nvirtual keyboard support\nfor mobile devices': + '檢查 使用虛擬鍵、可移動設備', + 'Input sliders': + '輸入滑塊', + 'uncheck to disable\ninput sliders for\nentry fields': + '取消選中 禁用輸入滑塊、輸入欄位', + 'check to enable\ninput sliders for\nentry fields': + '檢查 使用輸入滑塊、輸入欄位', + 'Clicking sound': + '點擊聲音', + 'uncheck to turn\nblock clicking\nsound off': + '取消選中 關閉點擊程式塊的聲音', + 'check to turn\nblock clicking\nsound on': + '檢查 關閉點擊程式塊的聲音', + 'Animations': + '動畫', + 'uncheck to disable\nIDE animations': + '取消選中禁用IDE動畫', + 'Turbo mode': + 'Turbo模式', + 'check to prioritize\nscript execution': + '檢查的優先執行腳本順序', + 'uncheck to run scripts\nat normal speed': + '取消選中正常速度運行腳本', + 'check to enable\nIDE animations': + '檢查啟用IDE動畫', + 'Thread safe scripts': + '線程安全的腳本', + 'uncheck to allow\nscript reentrance': + '取消選中 允許腳本重新載入', + 'check to disallow\nscript reentrance': + '檢查 不允許腳本重新載入', + 'Prefer smooth animations': + '不流暢的動畫', + 'uncheck for greater speed\nat variable frame rates': + '取消選中在可變幀頻更快的速度', + 'check for smooth, predictable\nanimations across computers': + '檢查是否平滑,可預見的多台電腦動畫', + + // inputs + 'with inputs': + '參數', + 'input names:': + '參數名:', + 'Input Names:': + '參數名:', + 'input list:': + '輸入列表:', + + // context menus: + 'help': + '説明', + + // blocks: + 'help...': + '説明...', + 'relabel...': + '重新標記...', + 'duplicate': + '複製', + 'make a copy\nand pick it up': + '創建一個副本並抓起', + 'only duplicate this block': + '只複製此塊', + 'delete': + '刪除', + 'script pic...': + '將腳本存為圖像...', + 'open a new window\nwith a picture of this script': + '新流覽視窗中打開腳本的圖片', + 'ringify': + '環', + 'unringify': + '刪除環', + + // custom blocks: + 'delete block definition...': + '刪除自定義程式塊', + 'edit...': + '編輯...', + + // sprites: + 'edit': + '編輯', + 'export...': + '導出...', + + 'show all': + '顯示所有', + 'pic...': + '導出圖像...', + 'open a new window\nwith a picture of the stage': + '打開一張圖片舞臺的新視窗,', + // scripting area + 'clean up': + '清除', + 'arrange scripts\nvertically': + '整理腳本,垂直排列', + 'add comment': + '添加注釋', + 'make a block...': + '創建程式塊...', + + // costumes + 'rename': + '重命名', + 'export': + '導出', + 'rename costume': + '重命名造型', + + // sounds + 'Play sound': + '播放聲音', + 'Stop sound': + '停止聲音', + 'Stop': + '停止', + 'Play': + '播放', + 'rename sound': + '重命名聲音', + + // dialogs + // buttons + 'OK': + '確定', + 'Cancel': + '取消', + 'Yes': + '是', + 'No': + '否', + + // help + 'Help': + '説明', + // zoom blocks + 'Zoom blocks': + '放大程式塊', + 'build': + '建立', + 'your own': + '你自己', + 'blocks': + '程式塊', + 'normal (1x)': + '標準 (1x)', + 'demo (1.2x)': + '演示 (1.2x)', + 'presentation (1.4x)': + '演示文稿 (1.4x)', + 'big (2x)': + '大(2x)', + 'huge (4x)': + '超大型 (4x)', + 'giant (8x)': + '巨人型 (8x)', + 'monstrous (10x)': + '無敵型 (10x)', + + 'Untitled': + '無標題', + 'Open Project': + '打開項目', + 'Open': + '打開', + '(empty)': + '(空)', + 'Saved!': + '已保存!', + 'Delete Project': + '刪除項目', + 'Are you sure you want to delete': + '你確定要刪除嗎?', + 'rename...': + '重命名...', + // costume editor + 'Costume Editor': + '造型編輯器', + 'click or drag crosshairs to move the rotation center': + '點擊或拖動十字准線,設置旋轉中心', + + // project notes + 'Project Notes': + '項目注釋', + + // new project + 'New Project': + '新建項目', + 'Replace the current project with a new one?': + '你要取消當前編輯的專案,重新建立專案嗎?', + + // open project + 'Open Projekt': + '打開項目', + + // save project + 'Save Project As...': + '項目另存為...', + + // export blocks + 'Export blocks': + '導出程式塊', + 'Import blocks': + '導入程式塊', + 'this project doesn\'t have any\ncustom global blocks yet': + '這個項目沒有包含全局性的自定義程式塊', + 'select': + '選擇', + 'all': + '全部', + 'none': + '無', + + // variable dialog + 'for all sprites': + '對所有的角色', + 'for this sprite only': + '只對這個角色', + + // block dialog + 'Change block': + '修改程式塊', + 'Command': + '命令', + 'Reporter': + '記錄', + 'Predicate': + '謂語', + + // block editor + 'Block Editor': + '程式塊編輯器', + 'Apply': + '應用', + + // block deletion dialog + 'Delete Custom Block': + '刪除自定義程式塊', + 'block deletion dialog text': + '你確定要刪除自定義程式塊及所有實例嗎?', + + // input dialog + 'Create input name': + '創建參數名', + 'Edit input name': + '編輯參數名', + 'Edit label fragment': + '編輯標籤片段', + 'Title text': + '標題文本', + 'Input name': + '參數名', + 'Delete': + '刪除', + 'Object': + '對象', + 'Number': + '數字', + 'Text': + '文本', + 'List': + '鏈表', + 'Any type': + '所有類型', + 'Boolean (T/F)': + '布林值(是/否)', + 'Command\n(inline)': + '命令(內置)', + 'Command\n(C-shape)': + '命令(C型)', + 'Any\n(unevaluated)': + '任意(未評價)', + 'Boolean\n(unevaluated)': + '布林(評價)', + 'Single input.': + '單一參數.', + 'Default Value:': + '預設值:', + 'Multiple inputs (value is list of inputs)': + '多行輸入(值為參數列表)', + 'Upvar - make internal variable visible to caller': + '上傳變數 - 使內部變數對調用者可見', + + // About Snap + 'About Snap': + '關於 Snap', + 'Back...': + '返回...', + 'License...': + '許可...', + 'Modules...': + '模組...', + 'Credits...': + '光榮榜...', + 'Translators...': + '翻譯者', + 'License': + '版權', + 'current module versions:': + '目前模組版本:', + 'Contributors': + '貢獻者:', + 'Translations': + '翻譯者', + + // variable watchers + 'normal': + '標準', + 'large': + '大型', + 'slider': + '滑塊', + 'slider min...': + '滑塊的最小值...', + 'slider max...': + '滑塊的最大值...', + 'import...': + '導入...', + 'Slider minimum value': + '滑塊的最小值', + 'Slider maximum value': + '滑塊的最大值', + + // list watchers + 'length: ': + '長度: ', + + // coments + 'add comment here...': + '在這裏添加注釋...', + + // drow downs + // directions + '(90) right': + '(90) 右', + '(-90) left': + '(-90) 左', + '(0) up': + '(0) 上', + '(180) right': + '(180) 右', + + // collision detection + 'mouse-pointer': + '滑鼠指標', + 'edge': + '邊緣', + 'pen trails': + '畫筆軌跡', + + // costumes + 'Turtle': + '海龜', + + // graphical effects + 'ghost': + '鬼影', + + // keys + 'space': + '空白鍵', + 'up arrow': + '上移鍵', + 'down arrow': + '下移鍵', + 'right arrow': + '右移鍵', + 'left arrow': + '左移鍵', + 'a': + 'a', + 'b': + 'b', + 'c': + 'c', + 'd': + 'd', + 'e': + 'e', + 'f': + 'f', + 'g': + 'g', + 'h': + 'h', + 'i': + 'i', + 'j': + 'j', + 'k': + 'k', + 'l': + 'l', + 'm': + 'm', + 'n': + 'n', + 'o': + 'o', + 'p': + 'p', + 'q': + 'q', + 'r': + 'r', + 's': + 's', + 't': + 't', + 'u': + 'u', + 'v': + 'v', + 'w': + 'w', + 'x': + 'x', + 'y': + 'y', + 'z': + 'z', + '0': + '0', + '1': + '1', + '2': + '2', + '3': + '3', + '4': + '4', + '5': + '5', + '6': + '6', + '7': + '7', + '8': + '8', + '9': + '9', + + // messages + 'new...': + '新增...', + + // math functions + 'abs': + 'abs', + 'sqrt': + 'sqrt', + 'sin': + 'sin', + 'cos': + 'cos', + 'tan': + 'tan', + 'asin': + 'asin', + 'acos': + 'acos', + 'atan': + 'atan', + 'ln': + 'ln', + 'e^': + 'e^', + + // data types + 'number': + '數字', + 'text': + '文字', + 'Boolean': + '布林值', + 'list': + '表列', + 'command': + '命令', + 'reporter': + '記錄', + 'predicate': + '謂語', + + // list indices + 'last': + '最後', + 'any': + '任意', + + // missing entries + 'Untitled': + '無標題', + 'Open Project': + '打開專案', + 'Open': + '打開', + '(empty)': + '(空)', + 'Saved!': + '已保存!', + 'Delete Project': + '刪除項目', + 'Are you sure you want to delete': + '確定要刪除嗎?', + 'unringify': + '刪除環', + 'rename...': + '重命名為...', + '(180) down': + '(180) 下', + 'Ok': + '確定' + +}; diff --git a/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/lang-zh.js b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/lang-zh.js new file mode 100644 index 0000000..b80470d --- /dev/null +++ b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/lang-zh.js @@ -0,0 +1,1214 @@ +/* + + lang-zh.js + + Simplified Chinese translation for SNAP! + SNAP 简体中文翻译版 + + written by Jens Mönig + + Copyright (C) 2012 by Jens Mönig + + This file is part of Snap!. + + Snap! is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + + + + Note to Translators: + -------------------- + At this stage of development, Snap! can be translated to any LTR language + maintaining the current order of inputs (formal parameters in blocks). + + Translating Snap! is easy: + + + 1. Download + + Download the sources and extract them into a local folder on your + computer: + + + + Use the German translation file (named 'lang-de.js') as template for your + own translations. Start with editing the original file, because that way + you will be able to immediately check the results in your browsers while + you're working on your translation (keep the local copy of snap.html open + in your web browser, and refresh it as you progress with your + translation). + + + 2. Edit + + Edit the translation file with a regular text editor, or with your + favorite JavaScript editor. + + In the first non-commented line (the one right below this + note) replace "de" with the two-letter ISO 639-1 code for your language, + e.g. + + fr - French => SnapTranslator.dict.fr = { + it - Italian => SnapTranslator.dict.it = { + pl - Polish => SnapTranslator.dict.pl = { + pt - Portuguese => SnapTranslator.dict.pt = { + es - Spanish => SnapTranslator.dict.es = { + el - Greek => => SnapTranslator.dict.el = { + + etc. (see ) + + + 3. Translate + + Then work through the dictionary, replacing the German strings against + your translations. The dictionary is a straight-forward JavaScript ad-hoc + object, for review purposes it should be formatted as follows: + + { + 'English string': + 'Translation string', + 'last key': + } 'last value' + + and you only edit the indented value strings. Note that each key-value + pair needs to be delimited by a comma, but that there shouldn't be a comma + after the last pair (again, just overwrite the template file and you'll be + fine). + + If something doesn't work, or if you're unsure about the formalities you + should check your file with + + + + This will inform you about any missed commas etc. + + + 4. Accented characters + + Depending on which text editor and which file encoding you use you can + directly enter special characters (e.g. Umlaut, accented characters) on + your keyboard. However, I've noticed that some browsers may not display + special characters correctly, even if other browsers do. So it's best to + check your results in several browsers. If you want to be on the safe + side, it's even better to escape these characters using Unicode. + + see: + + + 5. Block specs: + + At this time your translation of block specs will only work + correctly, if the order of formal parameters and their types + are unchanged. Placeholders for inputs (formal parameters) are + indicated by a preceding % prefix and followed by a type + abbreviation. + + For example: + + 'say %s for %n secs' + + can currently not be changed into + + 'say %n secs long %s' + + and still work as intended. + + Similarly + + 'point towards %dst' + + cannot be changed into + + 'point towards %cst' + + without breaking its functionality. + + + 6. Submit + + When you're done, rename the edited file by replacing the "de" part of the + filename with the two-letter ISO 639-1 code for your language, e.g. + + fr - French => lang-fr.js + it - Italian => lang-it.js + pl - Polish => lang-pl.js + pt - Portuguese => lang-pt.js + es - Spanish => lang-es.js + el - Greek => => lang-el.js + + and send it to me for inclusion in the official Snap! distribution. + Once your translation has been included, Your name will the shown in the + "Translators" tab in the "About Snap!" dialog box, and you will be able to + directly launch a translated version of Snap! in your browser by appending + + lang:xx + + to the URL, xx representing your translations two-letter code. + + + 7. Known issues + + In some browsers accents or ornaments located in typographic ascenders + above the cap height are currently (partially) cut-off. + + Enjoy! + -Jens +*/ + +/*global SnapTranslator*/ + +SnapTranslator.dict.zh = { + +/* + Special characters: (see ) + + Ä, ä \u00c4, \u00e4 + Ö, ö \u00d6, \u00f6 + Ü, ü \u00dc, \u00fc + ß \u00df +*/ + + // translations meta information + 'language_name': + '简体中文', // the name as it should appear in the language menu + 'language_translator': + '邓江华', // your name for the Translators tab + 'translator_e-mail': + 'djh@rhjxx.cn', // optional + 'last_changed': + '2013-3-25', // this, too, will appear in the Translators tab + + // GUI + // control bar: + 'untitled': + '无标题', + 'development mode': + '开发模式', + + // categories: + 'Motion': + '动作', + 'Looks': + '外观', + 'Sound': + '声音', + 'Pen': + '画笔', + 'Control': + '控制', + 'Sensing': + '侦测', + 'Operators': + '运算', + 'Variables': + '变量', + 'Lists': + '链表', + 'Other': + '其他', + + // editor: + 'draggable': + '可拖动', + + // tabs: + 'Scripts': + '脚本', + 'Costumes': + '造型', + 'Sounds': + '声音', + + // names: + 'Sprite': + '角色', + 'Stage': + '舞台', + + // rotation styles: + 'don\'t rotate': + '不能旋转', + 'can rotate': + '可以旋转', + 'only face left/right': + '只能水平翻转', + + // new sprite button: + 'add a new sprite': + '新建角色', + + // tab help + 'costumes tab help': + '造型选卡帮助\n要使用另外网站上的图片或计算机中的图像' + + '只需拖到图像到这里即可', + 'import a sound from your computer\nby dragging it into here': + '从计算机中导入声音文件\n只需拖动声音文件到这里即可', + + // primitive blocks: + + /* + Attention Translators: + ---------------------- + At this time your translation of block specs will only work + correctly, if the order of formal parameters and their types + are unchanged. Placeholders for inputs (formal parameters) are + indicated by a preceding % prefix and followed by a type + abbreviation. + + For example: + + 'say %s for %n secs' + + can currently not be changed into + + 'say %n secs long %s' + + and still work as intended. + + Similarly + + 'point towards %dst' + + cannot be changed into + + 'point towards %cst' + + without breaking its functionality. + */ + + // motion: + 'Stage selected:\nno motion primitives': + '舞台选择:\n没有动作程序语言', + + 'move %n steps': + '移动 %n 歩', + 'turn %clockwise %n degrees': + '旋转 %clockwise %n 度', + 'turn %counterclockwise %n degrees': + '旋转 %counterclockwise %n 度', + 'point in direction %dir': + '面向 %dir 度', + 'point towards %dst': + '面向 %dst ', + 'go to x: %n y: %n': + '移到 x: %n y: %n ', + 'go to %dst': + '移到 %dst ', + 'glide %n secs to x: %n y: %n': + '在 %n 秒内,平滑移动到 x: %n y: %n ', + 'change x by %n': + '将x坐标增加 %n ', + 'set x to %n': + '将x坐标设定为 %n ', + 'change y by %n': + '将y坐标增加 %n ', + 'set y to %n': + '将y坐标设定为 %n ', + 'if on edge, bounce': + '碰到边缘就反弹', + 'x position': + 'x坐标', + 'y position': + 'y坐标', + 'direction': + '方向', + + // looks: + 'switch to costume %cst': + '切换到造型 %cst ', + 'next costume': + '下一个造型', + 'costume #': + '造型编号', + 'say %s for %n secs': + '说 %s %n 秒', + 'say %s': + '说 %s ', + 'think %s for %n secs': + '思考 %s %n 秒', + 'think %s': + '思考 %s ', + 'Hello!': + '你好!', + 'Hmm...': + '嗯...', + 'change %eff effect by %n': + '将 %eff 特效增加 %n ', + 'set %eff effect to %n': + '将 %eff 特效设定为 %n ', + 'clear graphic effects': + '清除所有图形特效', + 'change size by %n': + '将角色的大小增加 %n ', + 'set size to %n %': + '将角色的大小设定为 %n ', + 'size': + '大小', + 'show': + '显示', + 'hide': + '隐藏', + 'go to front': + '移至最上层', + 'go back %n layers': + '下移 %n 层', + + 'development mode \ndebugging primitives:': + '开发模式\n调式程序语言:', + 'console log %mult%s': + '控制台日志 %mult%s', + 'alert %mult%s': + '警告: %mult%s', + + // sound: + 'play sound %snd': + '播放声音 %snd ', + 'play sound %snd until done': + '播放声音 %snd 直到播放完毕', + 'stop all sounds': + '停止所有声音', + 'rest for %n beats': + '停止 %n 秒', + 'play note %n for %n beats': + '弹奏 %n %n 拍', + 'change tempo by %n': + '将节奏加快 %n', + 'set tempo to %n bpm': + '将节奏设定为 %n', + 'tempo': + '节奏', + + // pen: + 'clear': + '清除所有画笔', + 'pen down': + '落笔', + 'pen up': + '抬笔', + 'set pen color to %clr': + '将画笔的颜色设定为 %clr ', + 'change pen color by %n': + '将画笔的颜色值增加 %n ', + 'set pen color to %n': + '将画笔的颜色值设定为 %n ', + 'change pen shade by %n': + '将画笔的色度增加 %n ', + 'set pen shade to %n': + '将画笔的色度设定为 %n ', + 'change pen size by %n': + '将画笔的大小增加 %n ', + 'set pen size to %n': + '将画笔的大小设定为 %n ', + 'stamp': + '图章', + + // control: + 'when %greenflag clicked': + '当 %greenflag 被点击', + 'when %keyHat key pressed': + '当按下 %keyHat', + 'when I am clicked': + '当角色被点击', + 'when I receive %msgHat': + '当接收到 %msgHat', + 'broadcast %msg': + '广播 %msg ', + 'broadcast %msg and wait': + '广播 %msg 并等待', + 'Message name': + '信息名称', + 'wait %n secs': + '等待 %n 秒', + 'wait until %b': + '直到 %b 前都等待阗', + 'forever %c': + '重复执行 %c', + 'repeat %n %c': + '重复执行 %n %c', + 'repeat until %b %c': + '重复执行直到 %b %c', + 'if %b %c': + '如果 %b %c', + 'if %b %c else %c': + '如果 %b %c 否则 %c', + 'report %s': + '报告 %s ', + 'stop block': + '停止程序块', + 'stop script': + '停止脚本', + 'stop all %stop': + '全部停止 %stop', + 'run %cmdRing %inputs': + '运行 %cmdRing %inputs ', + 'launch %cmdRing %inputs': + '启动 %cmdRing %inputs ', + 'call %repRing %inputs': + '调用 %repRing %inputs ', + 'run %cmdRing w/continuation': + '持续运行 %cmdRing ', + 'call %cmdRing w/continuation': + '持续调用 %cmdRing ', + 'warp %c': + '直接运行 %c', + 'when I start as a clone': + '当我开始克隆', + 'create a clone of %cln': + '新建一个克隆 %cln', + 'myself': + '自己', + 'delete this clone': + '删除这个克隆', + + // sensing: + 'touching %col ?': + '碰到 %col ', + 'touching %clr ?': + '碰到颜色 %clr ', + 'color %clr is touching %clr ?': + '颜色 %clr 碰到了颜色 %clr ?', + 'ask %s and wait': + '询问 %s 并等待', + 'what\'s your name?': + '你的名字?', + 'answer': + '回答', + 'mouse x': + '鼠标的x坐标', + 'mouse y': + '鼠标的y坐标', + 'mouse down?': + '按下鼠标?', + 'key %key pressed?': + '按键 %key 是否按下?', + 'distance to %dst': + '到 %dst 的距离', + 'reset timer': + '计时器归零', + 'timer': + '计时器', + 'http:// %s': + 'http:// %s', + 'turbo mode?': + 'Turbo模式', + 'set turbo mode to %b': + '设置Turbo模式 %b', + + 'filtered for %clr': + '选择颜色 %clr ', + 'stack size': + '堆栈大小', + 'frames': + '框架', + + // operators: + '%n mod %n': + '%n 除以 %n 的余数', + 'round %n': + '将 %n 四舍五入', + '%fun of %n': + '%fun %n', + 'pick random %n to %n': + '在 %n 到 %n 间随机选一个数', + '%b and %b': + '%b 且 %b', + '%b or %b': + '%b 或 %b', + 'not %b': + '%b 不成立', + 'true': + '成立', + 'false': + '不成立', + 'join %words': + '将 %words 加入到', + 'hello': + '你好', + 'world': + '行者老邓', + 'letter %n of %s': + '第 %n 位在文字 %s 中', + 'length of %s': + '%s 的长度', + 'unicode of %s': + '字符 %s 的Unicode编码值', + 'unicode %n as letter': + 'Unicode编码值为 %n 的字符', + 'is %s a %typ ?': + '%s 是 %typ 类型?', + 'is %s identical to %s ?': + '%s 与 %s 相同吗?', + + 'type of %s': + '%s 类型', + + // variables: + 'Make a variable': + '新建一个变量', + 'Variable name': + '变量名', + 'Delete a variable': + '删除变量', + + 'set %var to %s': + '将变量 %var 的值设定为 %s ', + 'change %var by %n': + '将变量 %var 的值增加 %n ', + 'show variable %var': + '显示变量 %var ', + 'hide variable %var': + '隐藏变量 %var ', + 'script variables %scriptVars': + '脚本变量 %scriptVars', + + // lists: + 'list %exp': + '链表 %exp', + '%s in front of %l': + '设定 %s 为链表 %l 第一项', + 'item %idx of %l': + '第 %idx 项在链表 %l 中', + 'all but first of %l': + '链表 %l 除第一记录以外内容', + 'length of %l': + '链表 %l 的长度', + '%l contains %s': + '链表 %l 包含 %s ', + 'thing': + '东西', + 'add %s to %l': + '将 %s 加入链表 %l ', + 'delete %ida of %l': + '删除链表 %ida 第 %l 项', + 'insert %s at %idx of %l': + '将 %s 插入到第 %idx 项在 %l 中', + 'replace item %idx of %l with %s': + '将第 %idx 项在链表 %l 中替换为 %s ', + + // other + 'Make a block': + '新建程序块', + + // menus + // snap menu + 'About...': + '关于Snap!...', + 'Reference manual': + '参考手册', + 'Snap! website': + '官方网站', + 'Download source': + '下载源码', + 'Switch back to user mode': + '切换到用户模式', + 'disable deep-Morphic\ncontext menus\nand show user-friendly ones': + '禁用 变形语式\n快捷菜单\n\n与非\n友好用户界面', + 'Switch to dev mode': + '切换到开发人员模式', + 'enable Morphic\ncontext menus\nand inspectors,\nnot user-friendly!': + '启用 正常语式\n快捷菜单\n与非检查\n友好用户界面', + + + // project menu + 'Project notes...': + '项目说明...', + 'New': + '新建', + 'Open...': + '打开...', + 'Save': + '保存', + 'Save As...': + '另存为...', + 'Import...': + '导入...', + 'file menu import hint': + '当你拖动到系统,注意查看检查报告\n' + + '要注意检查报告为空\n\n' + + '有一些浏览器不支持这一功能', + 'Export project as plain text...': + '纯文本格式导出项目...', + 'Export project...': + '导出项目...', + 'show project data as XML\nin a new browser window': + '新浏览窗以XML格式显示项目', + 'Export blocks...': + '导出程序块...', + 'show global custom block definitions as XML\nin a new browser window': + '新浏览窗以XML格式显示全局自定义程序块', + 'Import tools': + '导入工具包', + 'load the official library of\npowerful blocks': + '载入官方库和强大的程序块', + + // cloud menu + 'Login...': + '登录...', + 'Signup...': + '注册...', + // settings menu + 'Language...': + '语言选择...', + 'Zoom blocks...': + '放大程序块...', + 'Blurred shadows': + '半透明阴影', + 'uncheck to use solid drop\nshadows and highlights': + '取消选中 降低阴影和高亮的清晰度', + 'check to use blurred drop\nshadows and highlights': + '检测 降低阴影和高亮的模糊度', + 'Zebra coloring': + '斑马着色', + 'check to enable alternating\ncolors for nested blocks': + '检测 使嵌套块的颜色交换', + 'uncheck to disable alternating\ncolors for nested block': + '取消选中 使嵌套块的颜色交换', + 'Dynamic input labels': + '动态输入标签', + 'uncheck to disable dynamic\nlabels for variadic inputs': + '取消选中要禁用动态可变参数输入标签', + '检查启用动态可变参数输入标签': + 'marcar para habilitar etiquetas\ndin\u00E1micas para entradas varidic', + 'Prefer empty slot drops': + '喜欢减少空槽', + 'settings menu prefer empty slots hint': + '喜欢空槽设置菜单', + 'uncheck to allow dropped\nreporters to kick out others': + '取消选中 允许下降报告并取消其它报告', + 'Long form input dialog': + '长形式输入对话框', + 'check to always show slot\ntypes in the input dialog': + '检查显示插槽在输入对话框中的类型', + 'uncheck to use the input\ndialog in short form': + '取消选择 输入窗并显示简洁对话框', + 'Virtual keyboard': + '虚拟键盘', + 'uncheck to disable\nvirtual keyboard support\nfor mobile devices': + '取消选中 禁用虚拟键盘、可移动设备', + 'check to enable\nvirtual keyboard support\nfor mobile devices': + '检查 使用虚拟键、可移动设备', + 'Input sliders': + '输入滑块', + 'uncheck to disable\ninput sliders for\nentry fields': + '取消选中 禁用输入滑块、输入字段', + 'check to enable\ninput sliders for\nentry fields': + '检查 使用输入滑块、输入字段', + 'Clicking sound': + '点击声音', + 'uncheck to turn\nblock clicking\nsound off': + '取消选中 关闭点击程序块的声音', + 'check to turn\nblock clicking\nsound on': + '检查 关闭点击程序块的声音', + 'Animations': + '动画', + 'uncheck to disable\nIDE animations': + '取消选中禁用IDE动画', + 'Turbo mode': + 'Turbo模式', + 'check to prioritize\nscript execution': + '检查的优先执行脚本顺序', + 'uncheck to run scripts\nat normal speed': + '取消选中正常速度运行脚本', + 'check to enable\nIDE animations': + '检查启用IDE动画', + 'Thread safe scripts': + '线程安全的脚本', + 'uncheck to allow\nscript reentrance': + '取消选中 允许脚本重新载入', + 'check to disallow\nscript reentrance': + '检查 不允许脚本重新载入', + 'Prefer smooth animations': + '不流畅的动画', + 'uncheck for greater speed\nat variable frame rates': + '取消选中在可变帧频更快的速度', + 'check for smooth, predictable\nanimations across computers': + '检查是否平滑,可预见的多台电脑动画', + + // inputs + 'with inputs': + '参数', + 'input names:': + '参数名:', + 'Input Names:': + '参数名:', + 'input list:': + '输入列表:', + + // context menus: + 'help': + '帮助', + + // blocks: + 'help...': + '帮助...', + 'relabel...': + '重新标记...', + 'duplicate': + '复制', + 'make a copy\nand pick it up': + '创建一个副本并抓起', + 'only duplicate this block': + '只复制此块', + 'delete': + '删除', + 'script pic...': + '将脚本存为图像...', + 'open a new window\nwith a picture of this script': + '新浏览窗口中打开脚本的图片', + 'ringify': + '环', + 'unringify': + '删除环', + + // custom blocks: + 'delete block definition...': + '删除自定义程序块', + 'edit...': + '编辑...', + + // sprites: + 'edit': + '编辑', + 'export...': + '导出...', + + 'show all': + '显示所有', + 'pic...': + '导出图像...', + 'open a new window\nwith a picture of the stage': + '打开一张图片舞台的新窗口,', + // scripting area + 'clean up': + '清除', + 'arrange scripts\nvertically': + '整理脚本,垂直排列', + 'add comment': + '添加注释', + 'make a block...': + '创建程序块...', + + // costumes + 'rename': + '重命名', + 'export': + '导出', + 'rename costume': + '重命名造型', + + // sounds + 'Play sound': + '播放声音', + 'Stop sound': + '停止声音', + 'Stop': + '停止', + 'Play': + '播放', + 'rename sound': + '重命名声音', + + // dialogs + // buttons + 'OK': + '确定', + 'Cancel': + '取消', + 'Yes': + '是', + 'No': + '否', + + // help + 'Help': + '帮助', + // zoom blocks + 'Zoom blocks': + '放大程序块', + 'build': + '建立', + 'your own': + '你自己', + 'blocks': + '程序块', + 'normal (1x)': + '标准 (1x)', + 'demo (1.2x)': + '演示 (1.2x)', + 'presentation (1.4x)': + '演示文稿 (1.4x)', + 'big (2x)': + '大(2x)', + 'huge (4x)': + '超大型 (4x)', + 'giant (8x)': + '巨人型 (8x)', + 'monstrous (10x)': + '无敌型 (10x)', + + 'Untitled': + '无标题', + 'Open Project': + '打开项目', + 'Open': + '打开', + '(empty)': + '(空)', + 'Saved!': + '已保存!', + 'Delete Project': + '删除项目', + 'Are you sure you want to delete': + '你确定要删除吗?', + 'rename...': + '重命名...', + // costume editor + 'Costume Editor': + '造型编辑器', + 'click or drag crosshairs to move the rotation center': + '点击或拖动十字准线,设置旋转中心', + + // project notes + 'Project Notes': + '项目注释', + + // new project + 'New Project': + '新建项目', + 'Replace the current project with a new one?': + '你要取消当前编辑的项目,重新建立项目吗?', + + // open project + 'Open Projekt': + '打开项目', + + // save project + 'Save Project As...': + '项目另存为...', + + // export blocks + 'Export blocks': + '导出程序块', + 'Import blocks': + '导入程序块', + 'this project doesn\'t have any\ncustom global blocks yet': + '这个项目没有包含全局性的自定义程序块', + 'select': + '选择', + 'all': + '全部', + 'none': + '无', + + // variable dialog + 'for all sprites': + '对所有的角色', + 'for this sprite only': + '只对这个角色', + + // block dialog + 'Change block': + '修改程序块', + 'Command': + '命令', + 'Reporter': + '记录', + 'Predicate': + '谓语', + + // block editor + 'Block Editor': + '程序块编辑器', + 'Apply': + '应用', + + // block deletion dialog + 'Delete Custom Block': + '删除自定义程序块', + 'block deletion dialog text': + '你确定要删除自定义程序块及所有实例吗?', + + // input dialog + 'Create input name': + '创建参数名', + 'Edit input name': + '编辑参数名', + 'Edit label fragment': + '编辑标签片段', + 'Title text': + '标题文本', + 'Input name': + '参数名', + 'Delete': + '删除', + 'Object': + '对象', + 'Number': + '数字', + 'Text': + '文本', + 'List': + '链表', + 'Any type': + '所有类型', + 'Boolean (T/F)': + '布尔值(是/否)', + 'Command\n(inline)': + '命令(内置)', + 'Command\n(C-shape)': + '命令(C型)', + 'Any\n(unevaluated)': + '任意(未评价)', + 'Boolean\n(unevaluated)': + '布尔(评价)', + 'Single input.': + '单一参数.', + 'Default Value:': + '默认值:', + 'Multiple inputs (value is list of inputs)': + '多行输入(值为参数列表)', + 'Upvar - make internal variable visible to caller': + '上传变量 - 使内部变量对调用者可见', + + // About Snap + 'About Snap': + '关于 Snap', + 'Back...': + '返回...', + 'License...': + '许可...', + 'Modules...': + '模块...', + 'Credits...': + '光荣榜...', + 'Translators...': + '翻译者', + 'License': + '许可', + 'current module versions:': + '目前版本的模块:', + 'Contributors': + '贡献者:', + 'Translations': + '翻译者', + + // variable watchers + 'normal': + '标准', + 'large': + '大型', + 'slider': + '滑块', + 'slider min...': + '滑块的最小值...', + 'slider max...': + '滑块的最大值...', + 'import...': + '导入...', + 'Slider minimum value': + '滑块的最小值', + 'Slider maximum value': + '滑块的最大值', + + // list watchers + 'length: ': + '长度: ', + + // coments + 'add comment here...': + '在这里添加注释...', + + // drow downs + // directions + '(90) right': + '(90) 右', + '(-90) left': + '(-90) 左', + '(0) up': + '(0) 上', + '(180) right': + '(180) 右', + + // collision detection + 'mouse-pointer': + '鼠标指针', + 'edge': + '边缘', + 'pen trails': + '画笔轨迹', + + // costumes + 'Turtle': + '海龟', + + // graphical effects + 'ghost': + '鬼影', + + // keys + 'space': + '空格键', + 'up arrow': + '上移键', + 'down arrow': + '下移键', + 'right arrow': + '右移键', + 'left arrow': + '左移键', + 'a': + 'a', + 'b': + 'b', + 'c': + 'c', + 'd': + 'd', + 'e': + 'e', + 'f': + 'f', + 'g': + 'g', + 'h': + 'h', + 'i': + 'i', + 'j': + 'j', + 'k': + 'k', + 'l': + 'l', + 'm': + 'm', + 'n': + 'n', + 'o': + 'o', + 'p': + 'p', + 'q': + 'q', + 'r': + 'r', + 's': + 's', + 't': + 't', + 'u': + 'u', + 'v': + 'v', + 'w': + 'w', + 'x': + 'x', + 'y': + 'y', + 'z': + 'z', + '0': + '0', + '1': + '1', + '2': + '2', + '3': + '3', + '4': + '4', + '5': + '5', + '6': + '6', + '7': + '7', + '8': + '8', + '9': + '9', + + // messages + 'new...': + '新建...', + + // math functions + 'abs': + '绝对值', + 'sqrt': + '平方根', + 'sin': + 'sin', + 'cos': + 'cos', + 'tan': + 'tan', + 'asin': + 'asin', + 'acos': + 'acos', + 'atan': + 'atan', + 'ln': + 'ln', + 'e^': + 'e^', + + // data types + 'number': + '数字', + 'text': + '文本', + 'Boolean': + '布尔值', + 'list': + '链表', + 'command': + '命令', + 'reporter': + '记录', + 'predicate': + '谓语', + + // list indices + 'last': + '最后', + 'any': + '任意', + + // missing entries + 'Untitled': + '无标题', + 'Open Project': + '打开项目', + 'Open': + '打开', + '(empty)': + '(空)', + 'Saved!': + '已保存!', + 'Delete Project': + '删除项目', + 'Are you sure you want to delete': + '你确定要删除吗?', + 'unringify': + '删除环', + 'rename...': + '重命名为...', + '(180) down': + '(180) 下', + 'Ok': + '确定' + +}; diff --git a/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/lists.js b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/lists.js new file mode 100644 index 0000000..e70c385 --- /dev/null +++ b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/lists.js @@ -0,0 +1,668 @@ +/* + + lists.js + + list data structure and GUI for SNAP! + + written by Jens Mönig and Brian Harvey + jens@moenig.org, bh@cs.berkeley.edu + + Copyright (C) 2013 by Jens Mönig and Brian Harvey + + This file is part of Snap!. + + Snap! is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + + + prerequisites: + -------------- + needs morphic.js, widgets.js and gui.js + + + I. hierarchy + ------------- + the following tree lists all constructors hierarchically, + indentation indicating inheritance. Refer to this list to get a + contextual overview: + + List + + BoxMorph* + ListWatcherMorph + + * from Morphic.js + + + II. toc + ------- + the following list shows the order in which all constructors are + defined. Use this list to locate code in this document: + + List + ListWatcherMorph + +*/ + +// Global settings ///////////////////////////////////////////////////// + +/*global modules, contains, BoxMorph, WorldMorph, HandleMorph, +PushButtonMorph, SyntaxElementMorph, Color, Point, WatcherMorph, +StringMorph, SpriteMorph, ScrollFrameMorph, CellMorph, ArrowMorph, +MenuMorph, snapEquals, Morph, isNil, localize, MorphicPreferences*/ + +modules.lists = '2013-June-20'; + +var List; +var ListWatcherMorph; + +// List //////////////////////////////////////////////////////////////// + +/* + I am a dynamic array data structure for SNAP! + My index starts with 1 + + I am a "smart" hybrid list, because I can be used as both a linked + list and as a dynamic array + + public interface: + + setters (linked): + ----------------- + cons - answer a new list with the given item in front + cdr - answer all but the first element + + setters (arrayed): + ------------------ + add(element, index) - insert the element before the given slot, + put(element, index) - overwrite the element at the given slot + remove(index) - remove the given slot, shortening the list + clear() - remove all elements + + getters (all hybrid): + --------------------- + length() - number of slots + at(index) - element present in specified slot + contains(element) - + + conversion: + ----------- + asArray() - answer me as JavaScript array + asText() - answer my elements (recursively) concatenated +*/ + +// List instance creation: + +function List(array) { + this.contents = array || []; + this.first = null; + this.rest = null; + this.isLinked = false; + this.lastChanged = Date.now(); +} + +List.prototype.toString = function () { + return 'a List [' + this.asArray + ']'; +}; + +// List updating: + +List.prototype.changed = function () { + this.lastChanged = Date.now(); +}; + +// Linked List ops: + +List.prototype.cons = function (car, cdr) { + var answer = new List(); + answer.first = isNil(car) ? null : car; + answer.rest = cdr || null; + answer.isLinked = true; + return answer; +}; + +List.prototype.cdr = function () { + function helper(i) { + if (i > this.contents.length) { + return new List(); + } + return this.cons(this.at(i), helper.call(this, i + 1)); + } + if (this.isLinked) { + return this.rest || new List(); + } + if (this.contents.length < 2) { + return new List(); + } + return helper.call(this, 2); +}; + +// List array setters: + +List.prototype.add = function (element, index) { +/* + insert the element before the given slot index, + if no index is specifed, append the element +*/ + var idx = index || this.length() + 1, + obj = element === 0 ? 0 + : element === false ? false + : element || null; + this.becomeArray(); + this.contents.splice(idx - 1, 0, obj); + this.changed(); +}; + +List.prototype.put = function (element, index) { + // exchange the element at the given slot for another + var data = element === 0 ? 0 + : element === false ? false + : element || null; + + this.becomeArray(); + this.contents[index - 1] = data; + this.changed(); +}; + +List.prototype.remove = function (index) { + // remove the given slot, shortening the list + this.becomeArray(); + this.contents.splice(index - 1, 1); + this.changed(); +}; + +List.prototype.clear = function () { + this.contents = []; + this.first = null; + this.rest = null; + this.isLinked = false; + this.changed(); +}; + +// List getters (all hybrid): + +List.prototype.length = function () { + if (this.isLinked) { + return (this.first === undefined ? 0 : 1) + + (this.rest ? this.rest.length() : 0); + } + return this.contents.length; +}; + +List.prototype.at = function (index) { + var value; + if (this.isLinked) { + return index === 1 ? this.first : this.rest.at(index - 1); + } + value = this.contents[index - 1]; + return isNil(value) ? '' : value; +}; + +List.prototype.contains = function (element) { + var num = parseFloat(element); + if (this.isLinked) { + if (this.first === element) { + return true; + } + if (!isNaN(num)) { + if (parseFloat(this.first) === num) { + return true; + } + } + if (this.rest instanceof List) { + return this.rest.contains(element); + } + return false; + } + // in case I'm arrayed + if (contains(this.contents, element)) { + return true; + } + if (!isNaN(num)) { + return (contains(this.contents, num)) + || contains(this.contents, num.toString()); + } + return false; +}; + +// List conversion: + +List.prototype.asArray = function () { + // for use in the evaluator + this.becomeArray(); + return this.contents; +}; + +List.prototype.asText = function () { + var result = '', + length = this.length(), + element, + i; + for (i = 1; i <= length; i += 1) { + element = this.at(i); + if (element instanceof List) { + result = result.concat(element.asText()); + } else { + element = isNil(element) ? '' : element.toString(); + result = result.concat(element); + } + } + return result; +}; + +List.prototype.becomeArray = function () { + if (this.isLinked) { + var next = this; + this.contents = []; + while (next instanceof List && (next.length() > 0)) { + this.contents.push(next.at(1)); + next = next.cdr(); + } + this.isLinked = false; + } +}; + +List.prototype.becomeLinked = function () { + var i, stop, tail = this; + if (!this.isLinked) { + stop = this.length(); + for (i = 0; i < stop; i += 1) { + tail.first = this.contents[i]; + tail.rest = new List(); + tail.isLinked = true; + tail = tail.rest; + } + this.contents = []; + this.isLinked = true; + } +}; + +// List testing + +List.prototype.equalTo = function (other) { + var i; + if (!(other instanceof List)) { + return false; + } + if ((!this.isLinked) && (!other.isLinked)) { + if (this.length() === 0 && (other.length() === 0)) { + return true; + } + if (this.length() !== other.length()) { + return false; + } + for (i = 0; i < this.length(); i += 1) { + if (!snapEquals(this.contents[i], other.contents[i])) { + return false; + } + } + return true; + } + if ((this.isLinked) && (other.isLinked)) { + if (snapEquals(this.at(1), other.at(1))) { + return this.cdr().equalTo(other.cdr()); + } + return false; + } + if (this.length() !== other.length()) { + return false; + } + for (i = 0; i < this.length(); i += 1) { + if (!snapEquals(this.at(i), other.at(i))) { + return false; + } + } + return true; +}; + +// ListWatcherMorph //////////////////////////////////////////////////// + +/* + I am a little window which observes a list and continuously + updates itself accordingly +*/ + +// ListWatcherMorph inherits from BoxMorph: + +ListWatcherMorph.prototype = new BoxMorph(); +ListWatcherMorph.prototype.constructor = ListWatcherMorph; +ListWatcherMorph.uber = BoxMorph.prototype; + +// ListWatcherMorph default settings + +ListWatcherMorph.prototype.cellColor = + SpriteMorph.prototype.blockColor.lists; + +// ListWatcherMorph instance creation: + +function ListWatcherMorph(list, parentCell) { + this.init(list, parentCell); +} + +ListWatcherMorph.prototype.init = function (list, parentCell) { + var myself = this; + + this.list = list || new List(); + this.start = 1; + this.range = 100; + this.lastUpdated = Date.now(); + this.lastCell = null; + this.parentCell = parentCell || null; // for circularity detection + + // elements declarations + this.label = new StringMorph( + localize('length: ') + this.list.length(), + SyntaxElementMorph.prototype.fontSize, + null, + false, + false, + false, + MorphicPreferences.isFlat ? new Point() : new Point(1, 1), + new Color(255, 255, 255) + ); + this.label.mouseClickLeft = function () {myself.startIndexMenu(); }; + + + this.frame = new ScrollFrameMorph(null, 10); + this.frame.alpha = 0; + this.frame.acceptsDrops = false; + this.frame.contents.acceptsDrops = false; + + this.handle = new HandleMorph( + this, + 80, + 70, + 3, + 3 + ); + this.handle.setExtent(new Point(13, 13)); + + this.arrow = new ArrowMorph( + 'down', + SyntaxElementMorph.prototype.fontSize + ); + this.arrow.mouseClickLeft = function () {myself.startIndexMenu(); }; + this.arrow.setRight(this.handle.right()); + this.arrow.setBottom(this.handle.top()); + this.handle.add(this.arrow); + + this.plusButton = new PushButtonMorph( + this.list, + 'add', + '+' + ); + this.plusButton.padding = 0; + this.plusButton.edge = 0; + this.plusButton.outlineColor = this.color; + this.plusButton.drawNew(); + this.plusButton.fixLayout(); + + ListWatcherMorph.uber.init.call( + this, + SyntaxElementMorph.prototype.rounding, + 1.000001, // shadow bug in Chrome, + new Color(120, 120, 120) + ); + + this.color = new Color(220, 220, 220); + this.isDraggable = true; + this.setExtent(new Point(80, 70).multiplyBy( + SyntaxElementMorph.prototype.scale + )); + this.add(this.label); + this.add(this.frame); + this.add(this.plusButton); + this.add(this.handle); + this.handle.drawNew(); + this.update(); + this.fixLayout(); +}; + +// ListWatcherMorph updating: + +ListWatcherMorph.prototype.update = function (anyway) { + var i, idx, ceil, morphs, cell, cnts, label, button, max, + starttime, maxtime = 1000; + + this.frame.contents.children.forEach(function (m) { + + if (m instanceof CellMorph + && m.contentsMorph instanceof ListWatcherMorph) { + m.contentsMorph.update(); + } + }); + + if (this.lastUpdated === this.list.lastChanged && !anyway) { + return null; + } + + this.updateLength(true); + + // adjust start index to current list length + this.start = Math.max( + Math.min( + this.start, + Math.floor((this.list.length() - 1) / this.range) + * this.range + 1 + ), + 1 + ); + + // refresh existing cells + // highest index shown: + max = Math.min( + this.start + this.range - 1, + this.list.length() + ); + + // number of morphs available for refreshing + ceil = Math.min( + (max - this.start + 1) * 3, + this.frame.contents.children.length + ); + + for (i = 0; i < ceil; i += 3) { + idx = this.start + (i / 3); + + cell = this.frame.contents.children[i]; + label = this.frame.contents.children[i + 1]; + button = this.frame.contents.children[i + 2]; + cnts = this.list.at(idx); + + if (cell.contents !== cnts) { + cell.contents = cnts; + cell.drawNew(); + if (this.lastCell) { + cell.setLeft(this.lastCell.left()); + } + } + this.lastCell = cell; + + if (label.text !== idx.toString()) { + label.text = idx.toString(); + label.drawNew(); + } + + button.action = idx; + } + + // remove excess cells + // number of morphs to be shown + morphs = (max - this.start + 1) * 3; + + while (this.frame.contents.children.length > morphs) { + this.frame.contents.children[morphs].destroy(); + } + + // add additional cells + ceil = morphs; //max * 3; + i = this.frame.contents.children.length; + + starttime = Date.now(); + if (ceil > i + 1) { + for (i; i < ceil; i += 3) { + if (Date.now() - starttime > maxtime) { + this.fixLayout(); + this.frame.contents.adjustBounds(); + this.frame.contents.setLeft(this.frame.left()); + return null; + } + idx = this.start + (i / 3); + label = new StringMorph( + idx.toString(), + SyntaxElementMorph.prototype.fontSize, + null, + false, + false, + false, + MorphicPreferences.isFlat ? new Point() : new Point(1, 1), + new Color(255, 255, 255) + ); + cell = new CellMorph( + this.list.at(idx), + this.cellColor, + idx, + this.parentCell + ); + button = new PushButtonMorph( + this.list.remove, + idx, + '-', + this.list + ); + button.padding = 1; + button.edge = 0; + button.corner = 1; + button.outlineColor = this.color.darker(); + button.drawNew(); + button.fixLayout(); + + this.frame.contents.add(cell); + if (this.lastCell) { + cell.setPosition(this.lastCell.bottomLeft()); + } else { + cell.setTop(this.frame.contents.top()); + } + this.lastCell = cell; + label.setCenter(cell.center()); + label.setRight(cell.left() - 2); + this.frame.contents.add(label); + this.frame.contents.add(button); + } + } + this.lastCell = null; + + this.fixLayout(); + this.frame.contents.adjustBounds(); + this.frame.contents.setLeft(this.frame.left()); + this.updateLength(); + this.lastUpdated = this.list.lastChanged; +}; + +ListWatcherMorph.prototype.updateLength = function (notDone) { + this.label.text = localize('length: ') + this.list.length(); + if (notDone) { + this.label.color = new Color(0, 0, 100); + } else { + this.label.color = new Color(0, 0, 0); + } + this.label.drawNew(); + this.label.setCenter(this.center()); + this.label.setBottom(this.bottom() - 3); +}; + +ListWatcherMorph.prototype.startIndexMenu = function () { + var i, + range, + myself = this, + items = Math.ceil(this.list.length() / this.range), + menu = new MenuMorph( + function (idx) {myself.setStartIndex(idx); }, + null, + myself + ); + menu.addItem('1...', 1); + for (i = 1; i < items; i += 1) { + range = i * 100 + 1; + menu.addItem(range + '...', range); + } + menu.popUpAtHand(this.world()); +}; + +ListWatcherMorph.prototype.setStartIndex = function (index) { + this.start = index; + this.list.changed(); +}; + +ListWatcherMorph.prototype.fixLayout = function () { + Morph.prototype.trackChanges = false; + if (this.frame) { + this.arrangeCells(); + this.frame.silentSetPosition(this.position().add(3)); + this.frame.bounds.corner = this.bounds.corner.subtract(new Point( + 3, + 17 + )); + this.frame.drawNew(); + this.frame.contents.adjustBounds(); + } + + this.label.setCenter(this.center()); + this.label.setBottom(this.bottom() - 3); + this.plusButton.setLeft(this.left() + 3); + this.plusButton.setBottom(this.bottom() - 3); + + Morph.prototype.trackChanges = true; + this.changed(); + + if (this.parent && this.parent.fixLayout) { + this.parent.fixLayout(); + } +}; + +ListWatcherMorph.prototype.arrangeCells = function () { + var i, cell, label, button, lastCell, + end = this.frame.contents.children.length; + for (i = 0; i < end; i += 3) { + cell = this.frame.contents.children[i]; + label = this.frame.contents.children[i + 1]; + button = this.frame.contents.children[i + 2]; + if (lastCell) { + cell.setTop(lastCell.bottom()); + } + if (label) { + label.setTop(cell.center().y - label.height() / 2); + label.setRight(cell.left() - 2); + } + if (button) { + button.setCenter(cell.center()); + button.setLeft(cell.right() + 2); + } + lastCell = cell; + } + this.frame.contents.adjustBounds(); +}; + +// ListWatcherMorph hiding/showing: + +ListWatcherMorph.prototype.show = function () { + ListWatcherMorph.uber.show.call(this); + this.frame.contents.adjustBounds(); +}; + +// ListWatcherMorph drawing: + +ListWatcherMorph.prototype.drawNew = function () { + WatcherMorph.prototype.drawNew.call(this); + this.fixLayout(); +}; diff --git a/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/locale.js b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/locale.js new file mode 100644 index 0000000..4f9afe4 --- /dev/null +++ b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/locale.js @@ -0,0 +1,338 @@ +/* + + locale.js + + spoken language translation for SNAP! + + written by Jens Mönig + + Copyright (C) 2013 by Jens Mönig + + This file is part of Snap!. + + Snap! is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + + + Attention Translators! + ---------------------- + Please refer to the documentation in the file + + lang-de.js + + or to the separate file + + translating Snap.txt + + (same contents) if you would like to contribute. + +*/ + +// Global settings ///////////////////////////////////////////////////// + +/*global modules, contains*/ + +modules.locale = '2013-August-17'; + +// Global stuff + +var Localizer; +var SnapTranslator = new Localizer(); + +function localize(string) { + return SnapTranslator.translate(string); +} + +// Localizer ///////////////////////////////////////////////////////////// + +function Localizer(language, dict) { + this.language = language || 'en'; + this.dict = dict || {}; +} + +Localizer.prototype.translate = function (string) { + return Object.prototype.hasOwnProperty.call( + this.dict[this.language], + string + ) ? this.dict[this.language][string] : string; +}; + +Localizer.prototype.languages = function () { + var property, arr = []; + for (property in this.dict) { + if (Object.prototype.hasOwnProperty.call(this.dict, property)) { + arr.push(property); + } + } + return arr.sort(); +}; + +Localizer.prototype.languageName = function (lang) { + return this.dict[lang].language_name || lang; +}; + +Localizer.prototype.credits = function () { + var txt = '', + myself = this; + this.languages().forEach(function (lang) { + txt = txt + '\n' + + myself.languageName(lang) + + ' (' + lang + ') - ' + + myself.dict[lang].language_translator + + ' - ' + myself.dict[lang].last_changed; + }); + return txt; +}; + +Localizer.prototype.unload = function () { + var dict, + keep = ['language_name', 'language_translator', 'last_changed'], + myself = this; + this.languages().forEach(function (lang) { + var key; + if (lang !== 'en') { + dict = myself.dict[lang]; + for (key in dict) { + if (Object.prototype.hasOwnProperty.call(dict, key) + && !contains(keep, key)) { + delete dict[key]; + } + } + } + }); +}; + +// SnapTranslator initialization + +SnapTranslator.dict.en = { + // meta information + 'language_name': + 'English', + 'language_translator': + 'Jens M\u00F6nig', + 'translator_e-mail': + 'jens@moenig.org', + 'last_changed': + '2012-10-16', + + // long strings look-up only + 'file menu import hint': + 'load an exported project file\nor block library, a costume\n' + + 'or a sound', + 'settings menu prefer empty slots hint': + 'check to focus on empty slots\nwhen dragging & ' + + 'dropping reporters', + 'costumes tab help': + 'import a picture from another web page or from\n' + + 'a file on your computer by dropping it here\n', + 'block deletion dialog text': + 'Are you sure you want to delete this\n' + + 'custom block and all its instances?' +}; + +SnapTranslator.dict.de = { + // meta information + 'language_name': + 'Deutsch', + 'language_translator': + 'Jens M\u00F6nig', + 'translator_e-mail': + 'jens@moenig.org', + 'last_changed': + '2013-08-10' +}; + +SnapTranslator.dict.it = { + // meta information + 'language_name': + 'Italiano', + 'language_translator': + 'Stefano Federici', + 'translator_e-mail': + 's_federici@yahoo.com', + 'last_changed': + '2013-04-08' +}; + +SnapTranslator.dict.ja = { + // meta information + 'language_name': + '日本語', + 'language_translator': + 'Kazuhiro Abe', + 'translator_e-mail': + 'abee@squeakland.jp', + 'last_changed': + '2012-04-02' +}; + +SnapTranslator.dict.ja_HIRA = { + // meta information + 'language_name': + 'にほんご', + 'language_translator': + 'Kazuhiro Abe', + 'translator_e-mail': + 'abee@squeakland.jp', + 'last_changed': + '2012-04-02' +}; + +SnapTranslator.dict.ko = { + // meta information + 'language_name': + '한국어', + 'language_translator': + 'Yunjae Jang', + 'translator_e-mail': + 'yunjae.jang@inc.korea.ac.kr', + 'last_changed': + '2012-11-18' +}; + +SnapTranslator.dict.pt = { + // meta information + 'language_name': + 'Português', + 'language_translator': + 'Manuel Menezes de Sequeira', + 'translator_e-mail': + 'mmsequeira@gmail.com', + 'last_changed': + '2013-04-08' +}; + +SnapTranslator.dict.cs = { + // meta information + 'language_name': + 'Česky', + 'language_translator': + 'Michal Moc', + 'translator_e-mail': + 'info@iguru.eu', + 'last_changed': + '2013-03-11' +}; + +SnapTranslator.dict.zh = { + // translations meta information + 'language_name': + '简体中文', + 'language_translator': + '邓江华', + 'translator_e-mail': + 'djh@rhjxx.cn', + 'last_changed': + '2013-03-25' +}; + +SnapTranslator.dict.eo = { + // translations meta information + 'language_name': + 'Esperanto', + 'language_translator': + 'Sebastian Cyprych', + 'translator_e-mail': + 'scy(ĉe)epf.pl', + 'last_changed': + '2012-11-11' +}; + +SnapTranslator.dict.fr = { + // translations meta information + 'language_name': + 'Fran\u00E7ais', + 'language_translator': + 'Jean-Jacques Valliet - Mark Rafter', + 'translator_e-mail': + 'i.scool@mac.com', + 'last_changed': + '2012-11-27' +}; + +SnapTranslator.dict.si = { + 'language_name': + 'Sloven\u0161\u010Dina', + 'language_translator': + 'Sasa Divjak', + 'translator_e-mail': + 'sasa.divjak@fri.uni-lj.si', + 'last_changed': + '2013-01-07' +}; + +SnapTranslator.dict.ru = { + 'language_name': + 'Русский', + 'language_translator': + 'Svetlana Ptashnaya', + 'translator_e-mail': + 'svetlanap@berkeley.edu', + 'last_changed': + '2013-03-19' +}; + +SnapTranslator.dict.es = { + 'language_name': + 'Espa\u00F1ol', + 'language_translator': + 'V\u00EDctor Manuel Muratalla Morales', + 'translator_e-mail': + 'victor.muratalla@yahoo.com', + 'last_changed': + '2013-03-25' +}; + +SnapTranslator.dict.nl = { + 'language_name': + 'Nederlands', + 'language_translator': + 'Frank Sierens, Sjoerd Dirk Meijer', + 'translator_e-mail': + 'frank.sierens@telenet.be, sjoerddirk@fromScratchEd.nl', + 'last_changed': + '2013-08-12' +}; + +SnapTranslator.dict.pl = { + 'language_name': + 'Polski', + 'language_translator': + 'Witek Kranas', + 'translator_e-mail': + 'witek@oeiizk.waw.pl', + 'last_changed': + '2013-08-05' +}; + +SnapTranslator.dict.tw = { + 'language_name': + '繁體中文', + 'language_translator': + 'cch', + 'translator_e-mail': + 'cchuang2009@gmail.com', + 'last_changed': + '2013-8-14' +}; + +SnapTranslator.dict.no = { + 'language_name': + 'Norsk', + 'language_translator': + 'Olav A Marschall', + 'translator_e-mail': + 'mattebananer@gmail.com', + 'last_changed': + '2013-08-17' +}; diff --git a/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/manifest.mf b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/manifest.mf new file mode 100644 index 0000000..353d47f --- /dev/null +++ b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/manifest.mf @@ -0,0 +1,14 @@ +CACHE MANIFEST +snap.html +blocks.js +byob.js +gui.js +lists.js +morphic.js +objects.js +threads.js +widgets.js +store.js +xml.js +scriptsPaneTexture.gif +snap_logo_sm.gif diff --git a/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/minimal.html b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/minimal.html new file mode 100644 index 0000000..571cd2c --- /dev/null +++ b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/minimal.html @@ -0,0 +1,25 @@ + + + + Morphic! + + + + + +

Your browser doesn't support canvas.

+
+ + \ No newline at end of file diff --git a/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/morphic.html b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/morphic.html new file mode 100644 index 0000000..8c91cf1 --- /dev/null +++ b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/morphic.html @@ -0,0 +1,49 @@ + + + + Morphic! + + + + + + +

Your browser doesn't support canvas.

+
+ + \ No newline at end of file diff --git a/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/morphic.js b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/morphic.js new file mode 100644 index 0000000..aae17fd --- /dev/null +++ b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/morphic.js @@ -0,0 +1,10750 @@ +/* + + morphic.js + + a lively Web-GUI + inspired by Squeak + + written by Jens Mönig + jens@moenig.org + + Copyright (C) 2013 by Jens Mönig + + This file is part of Snap!. + + Snap! is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + + + documentation contents + ---------------------- + I. inheritance hierarchy + II. object definition toc + III. yet to implement + IV. open issues + V. browser compatibility + VI. the big picture + VII. programming guide + (1) setting up a web page + (a) single world + (b) multiple worlds + (c) an application + (2) manipulating morphs + (3) events + (a) mouse events + (b) context menu + (c) dragging + (d) dropping + (e) keyboard events + (f) resize event + (g) combined mouse-keyboard events + (h) text editing events + (4) stepping + (5) creating new kinds of morphs + (6) development and user modes + (7) turtle graphics + (8) damage list housekeeping + (9) minifying morphic.js + VIII. acknowledgements + IX. contributors + + + I. hierarchy + ------------- + the following tree lists all constructors hierarchically, + indentation indicating inheritance. Refer to this list to get a + contextual overview: + + Color + Node + Morph + BlinkerMorph + CursorMorph + BouncerMorph* + BoxMorph + InspectorMorph + MenuMorph + MouseSensorMorph* + SpeechBubbleMorph + CircleBoxMorph + SliderButtonMorph + SliderMorph + ColorPaletteMorph + GrayPaletteMorph + ColorPickerMorph + FrameMorph + ScrollFrameMorph + ListMorph + StringFieldMorph + WorldMorph + HandleMorph + HandMorph + PenMorph + ShadowMorph + StringMorph + TextMorph + TriggerMorph + MenuItemMorph + Point + Rectangle + + + II. toc + ------- + the following list shows the order in which all constructors are + defined. Use this list to locate code in this document: + + + Global settings + Global functions + + Color + Point + Rectangle + Node + Morph + ShadowMorph + HandleMorph + PenMorph + ColorPaletteMorph + GrayPaletteMorph + ColorPickerMorph + BlinkerMorph + CursorMorph + BoxMorph + SpeechBubbleMorph + CircleBoxMorph + SliderButtonMorph + SliderMorph + MouseSensorMorph* + InspectorMorph + MenuMorph + StringMorph + TextMorph + TriggerMorph + MenuItemMorph + FrameMorph + ScrollFrameMorph + ListMorph + StringFieldMorph + BouncerMorph* + HandMorph + WorldMorph + + * included only for demo purposes + + + III. yet to implement + --------------------- + - keyboard support for scroll frames and lists + - virtual keyboard support for Android and IE + + + IV. open issues + ---------------- + - blurry shadows don't work well in Chrome + + + V. browser compatibility + ------------------------ + I have taken great care and considerable effort to make morphic.js + runnable and appearing exactly the same on all current browsers + available to me: + + - Firefox for Windows + - Firefox for Mac + - Chrome for Windows (blurry shadows have some issues) + - Chrome for Mac + - Safari for Windows + - safari for Mac + - Safari for iOS (mobile) + - IE for Windows + - Opera for Windows + - Opera for Mac + + + VI. the big picture + ------------------- + Morphic.js is completely based on Canvas and JavaScript, it is just + Morphic, nothing else. Morphic.js is very basic and covers only the + bare essentials: + + * a stepping mechanism (a time-sharing multiplexer for lively + user interaction ontop of a single OS/browser thread) + * progressive display updates (only dirty rectangles are + redrawn in each display cycle) + * a tree structure + * a single World per Canvas element (although you can have + multiple worlds in multiple Canvas elements on the same web + page) + * a single Hand per World (but you can support multi-touch + events) + * a single text entry focus per World + + In its current state morphic.js doesn't support Transforms (you + cannot rotate Morphs), but with PenMorph there already is a simple + LOGO-like turtle that you can use to draw onto any Morph it is + attached to. I'm planning to add special Morphs that support these + operations later on, but not for every Morph in the system. + Therefore these additions ("sprites" etc.) are likely to be part of + other libraries ("microworld.js") in separate files. + + the purpose of morphic.js is to provide a malleable framework that + will let me experiment with lively GUIs for my hobby horse, which + is drag-and-drop, blocks based programming languages. Those things + (BYOB4 - http://byob.berkeley.edu) will be written using morphic.js + as a library. + + + VII. programming guide + ---------------------- + Morphic.js provides a library for lively GUIs inside single HTML + Canvas elements. Each such canvas element functions as a "world" in + which other visible shapes ("morphs") can be positioned and + manipulated, often directly and interactively by the user. Morphs + are tree nodes and may contain any number of submorphs ("children"). + + All things visible in a morphic World are morphs themselves, i.e. + all text rendering, blinking cursors, entry fields, menus, buttons, + sliders, windows and dialog boxes etc. are created with morphic.js + rather than using HTML DOM elements, and as a consequence can be + changed and adjusted by the programmer regardless of proprietary + browser behavior. + + Each World has an - invisible - "Hand" resembling the mouse cursor + (or the user's finger on touch screens) which handles mouse events, + and may also have a keyboardReceiver to handle key events. + + The basic idea of Morphic is to continuously run display cycles and + to incrementally update the screen by only redrawing those World + regions which have been "dirtied" since the last redraw. Before + each shape is processed for redisplay it gets the chance to perform + a "step" procedure, thus allowing for an illusion of concurrency. + + + (1) setting up a web page + ------------------------- + Setting up a web page for Morphic always involves three steps: + adding one or more Canvas elements, defining one or more worlds, + initializing and starting the main loop. + + + (a) single world + ----------------- + Most commonly you will want your World to fill the browsers's whole + client area. This default situation is easiest and most straight + forward. + + example html file: + + + + + Morphic! + + + + + +

Your browser doesn't support canvas.

+
+ + + + if you use ScrollFrames or otherwise plan to support mouse wheel + scrolling events, you might also add the following inline-CSS + attribute to the Canvas element: + + style="position: absolute;" + + which will prevent the World to be scrolled around instead of the + elements inside of it in some browsers. + + + (b) multiple worlds + ------------------- + If you wish to create a web page with more than one world, make + sure to prevent each world from auto-filling the whole page and + include it in the main loop. It's also a good idea to give each + world its own tabindex: + + example html file: + + + + + Morphic! + + + + +

first world:

+ +

Your browser doesn't support canvas.

+
+

second world:

+ +

Your browser doesn't support canvas.

+
+ + + + + (c) an application + ------------------- + Of course, most of the time you don't want to just plain use the + standard Morhic World "as is" out of the box, but write your own + application (something like Scratch!) in it. For such an + application you'll create your own morph prototypes, perhaps + assemble your own "window frame" and bring it all to life in a + customized World state. the following example creates a simple + snake-like mouse drawing game. + + example html file: + + + + + touch me! + + + + + +

Your browser doesn't support canvas.

+
+ + + + To get an idea how you can craft your own custom morph prototypes + I've included two examples which should give you an idea how to add + properties, override inherited methods and use the stepping + mechanism for "livelyness": + + BouncerMorph + MouseSensorMorph + + For the sake of sharing a single file I've included those examples + in morphic.js itself. Usually you'll define your additions in a + separate file and keep morphic.js untouched. + + + (2) manipulating morphs + ----------------------- + There are many methods to programmatically manipulate morphs. Among + the most important and common ones among all morphs are the + following nine: + + * hide() + * show() + + * setPosition(aPoint) + * setExtent(aPoint) + * setColor(aColor) + + * add(submorph) - attaches submorph ontop + * addBack(submorph) - attaches submorph underneath + + * fullCopy() - duplication + * destroy() - deletion + + + (3) events + ---------- + All user (and system) interaction is triggered by events, which are + passed on from the root element - the World - to its submorphs. The + World contains a list of system (browser) events it reacts to in its + + initEventListeners() + + method. Currently there are + + - mouse + - drop + - keyboard + - (window) resize + + events. + + These system events are dispatched within the morphic World by the + World's Hand and its keyboardReceiver (usually the active text + cursor). + + + (a) mouse events: + ----------------- + The Hand dispatches the following mouse events to relevant morphs: + + mouseDownLeft + mouseDownRight + mouseClickLeft + mouseClickRight + mouseDoubleClick + mouseEnter + mouseLeave + mouseEnterDragging + mouseLeaveDragging + mouseMove + mouseScroll + + If you wish your morph to react to any such event, simply add a + method of the same name as the event, e.g: + + MyMorph.prototype.mouseMove = function(pos) {}; + + The only optional parameter of such a method is a Point object + indicating the current position of the Hand inside the World's + coordinate system. + + Events may be "bubbled" up a morph's owner chain by calling + + this.escalateEvent(functionName, arg) + + in the event handler method's code. + + Likewise, removing the event handler method will render your morph + passive to the event in question. + + + (b) context menu: + ----------------- + By default right-clicking (or single-finger tap-and-hold) on a morph + also invokes its context menu (in addition to firing the + mouseClickRight event). A morph's context menu can be customized by + assigning a Menu instance to its + + customContextMenu + + property, or altogether suppressed by overriding its inherited + + contextMenu() + + method. + + + (c) dragging: + ------------- + Dragging a morph is initiated when the left mouse button is pressed, + held and the mouse is moved. + + You can control whether a morph is draggable by setting its + + isDraggable + + property either to false or true. If a morph isn't draggable itself + it will pass the pick-up request up its owner chain. This lets you + create draggable composite morphs like Windows, DialogBoxes, + Sliders etc. + + Sometimes it is desireable to make "template" shapes which cannot be + moved themselves, but from which instead duplicates can be peeled + off. This is especially useful for building blocks in construction + kits, e.g. the MIT-Scratch palette. Morphic.js lets you control this + functionality by setting the + + isTemplate + + property flag to true for any morph whose "isDraggable" property is + turned off. When dragging such a Morph the hand will instead grab + a duplicate of the template whose "isDraggable" flag is true and + whose "isTemplate" flag is false, in other words: a non-template. + + Dragging is indicated by adding a drop shadow to the morph in hand. + If a morph follows the hand without displaying a drop shadow it is + merely being moved about without changing its parent (owner morph), + e.g. when "dragging" a morph handle to resize its owner, or when + "dragging" a slider button. + + Right before a morph is picked up its + + prepareToBeGrabbed(handMorph) + + method is invoked, if it is present. Immediately after the pick-up + the former parent's + + reactToGrabOf(grabbedMorph) + + method is called, again only if it exists. + + Similar to events, these methods are optional and don't exist by + default. For a simple example of how they can be used to adjust + scroll bars in a scroll frame please have a look at their + implementation in FrameMorph. + + + (d) dropping: + ------------- + Dropping is triggered when the left mouse button is either pressed + or released while the Hand is dragging a morph. + + Dropping a morph causes it to become embedded in a new owner morph. + You can control this embedding behavior by setting the prospective + drop target's + + acceptsDrops + + property to either true or false, or by overriding its inherited + + wantsDropOf(aMorph) + + method. + + Right after a morph has been dropped its + + justDropped(handMorph) + + method is called, and its new parent's + + reactToDropOf(droppedMorph, handMorph) + + method is invoked, again only if each method exists. + + Similar to events, these methods are optional and by default are + not present in morphs by default (watch out for inheritance, + though!). For a simple example of how they can be used to adjust + scroll bars in a scroll frame please have a look at their + implementation in FrameMorph. + + Drops of image elements from outside the world canvas are dispatched as + + droppedImage(aCanvas, name) + droppedSVG(anImage, name) + + events to interested Morphs at the mouse pointer. If you want you Morph + to e.g. import outside images you can add the droppedImage() and / or the + droppedSVG() methods to it. The parameter passed to the event handles is + a new offscreen canvas element representing a copy of the original image + element which can be directly used, e.g. by assigning it to another + Morph's image property. In the case of a dropped SVG it is an image + element (not a canvas), which has to be rasterized onto a canvas before + it can be used. The benefit of handling SVGs as image elements is that + rasterization can be deferred until the destination scale is known, taking + advantage of SVG's ability for smooth scaling. If instead SVGs are to be + rasterized right away, you can set the + + MorphicPreferences.rasterizeSVGs + + preference to . In this case dropped SVGs also trigger the + droppedImage() event with a canvas containing a rasterized version of the + SVG. + + The same applies to drops of audio or text files from outside the world + canvas. + + Those are dispatched as + + droppedAudio(anAudio, name) + droppedText(aString, name) + + events to interested Morphs at the mouse pointer. + + if none of the above content types can be determined, the file contents + is dispatched as an ArrayBuffer to interested Morphs: + + droppedBinary(anArrayBuffer, name) + + + (e) keyboard events + ------------------- + The World dispatches the following key events to its active + keyboardReceiver: + + keypress + keydown + keyup + + Currently the only morph which acts as keyboard receiver is + CursorMorph, the basic text editing widget. If you wish to add + keyboard support to your morph you need to add event handling + methods for + + processKeyPress(event) + processKeyDown(event) + processKeyUp(event) + + and activate them by assigning your morph to the World's + + keyboardReceiver + + property. + + Note that processKeyUp() is optional and doesn't have to be present + if your morph doesn't require it. + + + (f) resize event + ---------------- + The Window resize event is handled by the World and allows the + World's extent to be adjusted so that it always completely fills + the browser's visible page. You can turn off this default behavior + by setting the World's + + useFillPage + + property to false. + + Alternatively you can also initialize the World with the + useFillPage switch turned off from the beginning by passing the + false value as second parameter to the World's constructor: + + world = new World(aCanvas, false); + + Use this when creating a web page with multiple Worlds. + + if "useFillPage" is turned on the World dispatches an + + reactToWorldResize(newBounds) + + events to all of its children (toplevel only), allowing each to + adjust to the new World bounds by implementing a corresponding + method, the passed argument being the World's new dimensions after + completing the resize. By default, the "reactToWorldResize" Method + does not exist. + + Example: + + Add the following method to your Morph to let it automatically + fill the whole World, but leave a 10 pixel border uncovered: + + MyMorph.prototype.reactToWorldResize = function (rect) { + this.changed(); + this.bounds = rect.insetBy(10); + this.drawNew(); + this.changed(); + }; + + + (g) combined mouse-keyboard events + ---------------------------------- + Occasionally you'll want an object to react differently to a mouse + click or to some other mouse event while the user holds down a key + on the keyboard. Such "shift-click", "ctl-click", or "alt-click" + events can be implemented by querying the World's + + currentKey + + property inside the function that reacts to the mouse event. This + property stores the keyCode of the key that's currently pressed. + Once the key is released by the user it reverts to null. + + + (h) text editing events + ----------------------- + Much of Morphic's "liveliness" comes out of allowing text elements + (instances of either single-lined StringMorph or multi-lined TextMorph) + to be directly manipulated and edited by users. This requires other + objects which may have an interest in the text element's state to react + appropriately. Therefore text elements and their manipulators emit + a stream of events, mostly by "bubbling" them up the text element's + owner chain. Text elements' parents are notified about the following + events: + + Whenever the user presses a key on the keyboard while a text element + is being edited, a + + reactToKeystroke(event) + + is escalated up its parent chain, the "event" parameter being the + original one received by the World. + + Once the user has completed the edit, the following events are + dispatched: + + accept() - was pressed on a single line of text + cancel() - was pressed on any text element + + Note that "accept" only gets triggered by single-line texte elements, + as the key is used to insert line breaks in multi-line + elements. Therefore, whenever a text edit is terminated by the user + (accepted, cancelled or otherwise), + + reactToEdit(StringOrTextMorph) + + is triggered. + + If the MorphicPreference's + + useSliderForInput + + setting is turned on, a slider is popped up underneath the currently + edited text element letting the user insert numbers out of the given + slider range. Whenever this happens, i.e. whenever the slider is moved + or while the slider button is pressed, a stream of + + reactToSliderEdit(StringOrTextMorph) + + events is dispatched, allowing for "Bret-Victor" style "live coding" + applications. + + In addition to user-initiated events text elements also emit + change notifications to their direct parents whenever their drawNew() + method is invoked. That way complex Morphs containing text elements + get a chance to react if something about the embedded text has been + modified programmatically. These events are: + + layoutChanged() - sent from instances of TextMorph + fixLayout() - sent from instances of StringMorph + + they are different so that Morphs which contain both multi-line and + single-line text elements can hold them apart. + + + (4) stepping + ------------ + Stepping is what makes Morphic "magical". Two properties control + a morph's stepping behavior: the fps attribute and the step() + method. + + By default the + + step() + + method does nothing. As you can see in the examples of BouncerMorph + and MouseSensorMorph you can easily override this inherited method + to suit your needs. + + By default the step() method is called once per display cycle. + Depending on the number of actively stepping morphs and the + complexity of your step() methods this can cause quite a strain on + your CPU, and also result in your application behaving differently + on slower computers than on fast ones. + + setting + + myMorph.fps + + to a number lower than the interval for the main loop lets you free + system resources (albeit at the cost of a less responsive or slower + behavior for this particular morph). + + + (5) creating new kinds of morphs + -------------------------------- + The real fun begins when you start to create new kinds of morphs + with customized shapes. Imagine, e.g. jigsaw puzzle pieces or + musical notes. For this you have to override the default + + drawNew() + + method. + + This method creates a new offscreen Canvas and stores it in + the morph's + + image + + property. + + Use the following template for a start: + + MyMorph.prototype.drawNew = function() { + var context; + this.image = newCanvas(this.extent()); + context = this.image.getContext('2d'); + // use context to paint stuff here + }; + + If your new morph stores or references other morphs outside of the + submorph tree in other properties, be sure to also override the + default + + copyRecordingReferences() + + method accordingly if you want it to support duplication. + + + (6) development and user modes + ------------------------------ + When working with Squeak on Scratch or BYOB among the features I + like the best and use the most is inspecting what's going on in + the World while it is up and running. That's what development mode + is for (you could also call it debug mode). In essence development + mode controls which context menu shows up. In user mode right + clicking (or double finger tapping) a morph invokes its + + customContextMenu + + property, whereas in development mode only the general + + developersMenu() + + method is called and the resulting menu invoked. The developers' + menu features Gui-Builder-wise functionality to directly inspect, + take apart, reassamble and otherwise manipulate morphs and their + contents. + + Instead of using the "customContextMenu" property you can also + assign a more dynamic contextMenu by overriding the general + + userMenu() + + method with a customized menu constructor. The difference between + the customContextMenu property and the userMenu() method is that + the former is also present in development mode and overrides the + developersMenu() result. For an example of how to use the + customContextMenu property have a look at TextMorph's evaluation + menu, which is used for the Inspector's evaluation pane. + + When in development mode you can inspect every Morph's properties + with the inspector, including all of its methods. The inspector + also lets you add, remove and rename properties, and even edit + their values at runtime. Like in a Smalltalk environment the inspect + features an evaluation pane into which you can type in arbitrary + JavaScript code and evaluate it in the context of the inspectee. + + Use switching between user and development modes while you are + developing an application and disable switching to development once + you're done and deploying, because generally you don't want to + confuse end-users with inspectors and meta-level stuff. + + + (7) turtle graphics + ------------------- + + The basic Morphic kernel features a simple LOGO turtle constructor + called + + PenMorph + + which you can use to draw onto its parent Morph. By default every + Morph in the system (including the World) is able to act as turtle + canvas and can display pen trails. Pen trails will be lost whenever + the trails morph (the pen's parent) performs a "drawNew()" + operation. If you want to create your own pen trails canvas, you + may wish to modify its + + penTrails() + + property, so that it keeps a separate offscreen canvas for pen + trails (and doesn't loose these on redraw). + + the following properties of PenMorph are relevant for turtle + graphics: + + color - a Color + size - line width of pen trails + heading - degrees + isDown - drawing state + + the following commands can be used to actually draw something: + + up() - lift the pen up, further movements leave no trails + down() - set down, further movements leave trails + clear() - remove all trails from the current parent + forward(n) - move n steps in the current direction (heading) + turn(n) - turn right n degrees + + Turtle graphics can best be explored interactively by creating a + new PenMorph object and by manipulating it with the inspector + widget. + + NOTE: PenMorph has a special optimization for recursive operations + called + + warp(function) + + You can significantly speed up recursive ops and increase the depth + of recursion that's displayable by wrapping WARP around your + recursive function call: + + example: + + myPen.warp(function () { + myPen.tree(12, 120, 20); + }) + + will be much faster than just invoking the tree function, because it + prevents the parent's parent from keeping track of every single line + segment and instead redraws the outcome in a single pass. + + + (8) damage list housekeeping + ---------------------------- + Morphic's progressive display update comes at the cost of having to + cycle through a list of "broken rectangles" every display cycle. If + this list gets very long working this damage list can lead to a + seemingly dramatic slow-down of the Morphic system. Typically this + occurs when updating the layout of complex Morphs with very many + submorphs, e.g. when resizing an inspector window. + + An effective strategy to cope with this is to use the inherited + + trackChanges + + property of the Morph prototype for damage list housekeeping. + + The trackChanges property of the Morph prototype is a Boolean switch + that determines whether the World's damage list ('broken' rectangles) + tracks changes. By default the switch is always on. If set to false + changes are not stored. This can be very useful for housekeeping of + the damage list in situations where a large number of (sub-) morphs + are changed more or less at once. Instead of keeping track of every + single submorph's changes tremendous performance improvements can be + achieved by setting the trackChanges flag to false before propagating + the layout changes, setting it to true again and then storing the full + bounds of the surrounding morph. An an example refer to the + + moveBy() + + method of HandMorph, and to the + + fixLayout() + + method of InspectorMorph, or the + + startLayout() + endLayout() + + methods of SyntaxElementMorph in the Snap application. + + + (9) minifying morphic.js + ------------------------ + Coming from Smalltalk and being a Squeaker at heart I am a huge fan + of browsing the code itself to make sense of it. Therefore I have + included this documentation and (too little) inline comments so all + you need to get going is this very file. + + Nowadays with live streaming HD video even on mobile phones 250 KB + shouldn't be a big strain on bandwith, still minifying and even + compressing morphic.js down do about 100 KB may sometimes improve + performance in production use. + + Being an attorney-at-law myself you programmer folk keep harassing + me with rabulistic nitpickings about free software licenses. I'm + releasing morphic.js under an AGPL license. Therefore please make + sure to adhere to that license in any minified or compressed version. + + + VIII. acknowledgements + ---------------------- + The original Morphic was designed and written by Randy Smith and + John Maloney for the SELF programming language, and later ported to + Squeak (Smalltalk) by John Maloney and Dan Ingalls, who has also + ported it to JavaScript (the Lively Kernel), once again setting + a "Gold Standard" for self sustaining systems which morphic.js + cannot and does not aspire to meet. + + This Morphic implementation for JavaScript is not a direct port of + Squeak's Morphic, but still many individual functions have been + ported almost literally from Squeak, sometimes even including their + comments, e.g. the morph duplication mechanism fullCopy(). Squeak + has been a treasure trove, and if morphic.js looks, feels and + smells a lot like Squeak, I'll take it as a compliment. + + Evelyn Eastmond has inspired and encouraged me with her wonderful + implementation of DesignBlocksJS. Thanks for sharing code, ideas + and enthusiasm for programming. + + John Maloney has been my mentor and my source of inspiration for + these Morphic experiments. Thanks for the critique, the suggestions + and explanations for all things Morphic and for being my all time + programming hero. + + I have originally written morphic.js in Florian Balmer's Notepad2 + editor for Windows and later switched to Apple's Dashcode. I've also + come to depend on both Douglas Crockford's JSLint, Mozilla's Firebug + and Google's Chrome to get it right. + + + IX. contributors + ---------------------- + Joe Otto found and fixed many early bugs and taught me some tricks. + Nathan Dinsmore contributed mouse wheel scrolling, cached + background texture handling and countless bug fixes. + Ian Reynolds contributed backspace key handling for Chrome. + Davide Della Casa contributed performance optimizations for Firefox. + + - Jens Mönig +*/ + +// Global settings ///////////////////////////////////////////////////// + +/*global window, HTMLCanvasElement, getMinimumFontHeight, FileReader, Audio, +FileList, getBlurredShadowSupport*/ + +var morphicVersion = '2013-August-06'; +var modules = {}; // keep track of additional loaded modules +var useBlurredShadows = getBlurredShadowSupport(); // check for Chrome-bug + +var standardSettings = { + minimumFontHeight: getMinimumFontHeight(), // browser settings + globalFontFamily: '', + menuFontName: 'sans-serif', + menuFontSize: 12, + bubbleHelpFontSize: 10, + prompterFontName: 'sans-serif', + prompterFontSize: 12, + prompterSliderSize: 10, + handleSize: 15, + scrollBarSize: 12, + mouseScrollAmount: 40, + useSliderForInput: false, + useVirtualKeyboard: true, + isTouchDevice: false, // turned on by touch events, don't set + rasterizeSVGs: false, + isFlat: false +}; + +var touchScreenSettings = { + minimumFontHeight: standardSettings.minimumFontHeight, + globalFontFamily: '', + menuFontName: 'sans-serif', + menuFontSize: 24, + bubbleHelpFontSize: 18, + prompterFontName: 'sans-serif', + prompterFontSize: 24, + prompterSliderSize: 20, + handleSize: 26, + scrollBarSize: 24, + mouseScrollAmount: 40, + useSliderForInput: true, + useVirtualKeyboard: true, + isTouchDevice: false, + rasterizeSVGs: false, + isFlat: false +}; + +var MorphicPreferences = standardSettings; + +// Global Functions //////////////////////////////////////////////////// + +function nop() { + // do explicitly nothing + return null; +} + +function localize(string) { + // override this function with custom localizations + return string; +} + +function isNil(thing) { + return thing === undefined || thing === null; +} + +function contains(list, element) { + // answer true if element is a member of list + return list.some(function (any) { + return any === element; + }); +} + +function detect(list, predicate) { + // answer the first element of list for which predicate evaluates + // true, otherwise answer null + var i, size = list.length; + for (i = 0; i < size; i += 1) { + if (predicate.call(null, list[i])) { + return list[i]; + } + } + return null; +} + +function sizeOf(object) { + // answer the number of own properties + var size = 0, key; + for (key in object) { + if (Object.prototype.hasOwnProperty.call(object, key)) { + size += 1; + } + } + return size; +} + +function isString(target) { + return typeof target === 'string' || target instanceof String; +} + +function isObject(target) { + return target !== null && + (typeof target === 'object' || target instanceof Object); +} + +function radians(degrees) { + return degrees * Math.PI / 180; +} + +function degrees(radians) { + return radians * 180 / Math.PI; +} + +function fontHeight(height) { + var minHeight = Math.max(height, MorphicPreferences.minimumFontHeight); + return minHeight * 1.2; // assuming 1/5 font size for ascenders +} + +function newCanvas(extentPoint) { + // answer a new empty instance of Canvas, don't display anywhere + var canvas, ext; + ext = extentPoint || {x: 0, y: 0}; + canvas = document.createElement('canvas'); + canvas.width = ext.x; + canvas.height = ext.y; + return canvas; +} + +function getMinimumFontHeight() { + // answer the height of the smallest font renderable in pixels + var str = 'I', + size = 50, + canvas = document.createElement('canvas'), + ctx, + maxX, + data, + x, + y; + canvas.width = size; + canvas.height = size; + ctx = canvas.getContext('2d'); + ctx.font = '1px serif'; + maxX = ctx.measureText(str).width; + ctx.fillStyle = 'black'; + ctx.textBaseline = 'bottom'; + ctx.fillText(str, 0, size); + for (y = 0; y < size; y += 1) { + for (x = 0; x < maxX; x += 1) { + data = ctx.getImageData(x, y, 1, 1); + if (data.data[3] !== 0) { + return size - y + 1; + } + } + } + return 0; +} + +function getBlurredShadowSupport() { + // check for Chrome issue 90001 + // http://code.google.com/p/chromium/issues/detail?id=90001 + var source, target, ctx; + source = document.createElement('canvas'); + source.width = 10; + source.height = 10; + ctx = source.getContext('2d'); + ctx.fillStyle = 'rgb(255, 0, 0)'; + ctx.beginPath(); + ctx.arc(5, 5, 5, 0, Math.PI * 2, true); + ctx.closePath(); + ctx.fill(); + target = document.createElement('canvas'); + target.width = 10; + target.height = 10; + ctx = target.getContext('2d'); + ctx.shadowBlur = 10; + ctx.shadowColor = 'rgba(0, 0, 255, 1)'; + ctx.drawImage(source, 0, 0); + return ctx.getImageData(0, 0, 1, 1).data[3] ? true : false; +} + +function getDocumentPositionOf(aDOMelement) { + // answer the absolute coordinates of a DOM element in the document + var pos, offsetParent; + if (aDOMelement === null) { + return {x: 0, y: 0}; + } + pos = {x: aDOMelement.offsetLeft, y: aDOMelement.offsetTop}; + offsetParent = aDOMelement.offsetParent; + while (offsetParent !== null) { + pos.x += offsetParent.offsetLeft; + pos.y += offsetParent.offsetTop; + if (offsetParent !== document.body && + offsetParent !== document.documentElement) { + pos.x -= offsetParent.scrollLeft; + pos.y -= offsetParent.scrollTop; + } + offsetParent = offsetParent.offsetParent; + } + return pos; +} + +function clone(target) { + // answer a new instance of target's type + if (typeof target === 'object') { + var Clone = function () {nop(); }; + Clone.prototype = target; + return new Clone(); + } + return target; +} + +function copy(target) { + // answer a shallow copy of target + var value, c, property; + + if (typeof target !== 'object') { + return target; + } + value = target.valueOf(); + if (target !== value) { + return new target.constructor(value); + } + if (target instanceof target.constructor && + target.constructor !== Object) { + c = clone(target.constructor.prototype); + for (property in target) { + if (Object.prototype.hasOwnProperty.call(target, property)) { + c[property] = target[property]; + } + } + } else { + c = {}; + for (property in target) { + if (!c[property]) { + c[property] = target[property]; + } + } + } + return c; +} + +// Colors ////////////////////////////////////////////////////////////// + +// Color instance creation: + +function Color(r, g, b, a) { + // all values are optional, just (r, g, b) is fine + this.r = r || 0; + this.g = g || 0; + this.b = b || 0; + this.a = a || ((a === 0) ? 0 : 1); +} + +// Color string representation: e.g. 'rgba(255,165,0,1)' + +Color.prototype.toString = function () { + return 'rgba(' + + Math.round(this.r) + ',' + + Math.round(this.g) + ',' + + Math.round(this.b) + ',' + + this.a + ')'; +}; + +// Color copying: + +Color.prototype.copy = function () { + return new Color( + this.r, + this.g, + this.b, + this.a + ); +}; + +// Color comparison: + +Color.prototype.eq = function (aColor) { + // == + return aColor && + this.r === aColor.r && + this.g === aColor.g && + this.b === aColor.b; +}; + +// Color conversion (hsv): + +Color.prototype.hsv = function () { + // ignore alpha + var max, min, h, s, v, d, + rr = this.r / 255, + gg = this.g / 255, + bb = this.b / 255; + max = Math.max(rr, gg, bb); + min = Math.min(rr, gg, bb); + h = max; + s = max; + v = max; + d = max - min; + s = max === 0 ? 0 : d / max; + if (max === min) { + h = 0; + } else { + switch (max) { + case rr: + h = (gg - bb) / d + (gg < bb ? 6 : 0); + break; + case gg: + h = (bb - rr) / d + 2; + break; + case bb: + h = (rr - gg) / d + 4; + break; + } + h /= 6; + } + return [h, s, v]; +}; + +Color.prototype.set_hsv = function (h, s, v) { + // ignore alpha, h, s and v are to be within [0, 1] + var i, f, p, q, t; + i = Math.floor(h * 6); + f = h * 6 - i; + p = v * (1 - s); + q = v * (1 - f * s); + t = v * (1 - (1 - f) * s); + switch (i % 6) { + case 0: + this.r = v; + this.g = t; + this.b = p; + break; + case 1: + this.r = q; + this.g = v; + this.b = p; + break; + case 2: + this.r = p; + this.g = v; + this.b = t; + break; + case 3: + this.r = p; + this.g = q; + this.b = v; + break; + case 4: + this.r = t; + this.g = p; + this.b = v; + break; + case 5: + this.r = v; + this.g = p; + this.b = q; + break; + } + + this.r *= 255; + this.g *= 255; + this.b *= 255; + +}; + +// Color mixing: + +Color.prototype.mixed = function (proportion, otherColor) { + // answer a copy of this color mixed with another color, ignore alpha + var frac1 = Math.min(Math.max(proportion, 0), 1), + frac2 = 1 - frac1; + return new Color( + this.r * frac1 + otherColor.r * frac2, + this.g * frac1 + otherColor.g * frac2, + this.b * frac1 + otherColor.b * frac2 + ); +}; + +Color.prototype.darker = function (percent) { + // return an rgb-interpolated darker copy of me, ignore alpha + var fract = 0.8333; + if (percent) { + fract = (100 - percent) / 100; + } + return this.mixed(fract, new Color(0, 0, 0)); +}; + +Color.prototype.lighter = function (percent) { + // return an rgb-interpolated lighter copy of me, ignore alpha + var fract = 0.8333; + if (percent) { + fract = (100 - percent) / 100; + } + return this.mixed(fract, new Color(255, 255, 255)); +}; + +Color.prototype.dansDarker = function () { + // return an hsv-interpolated darker copy of me, ignore alpha + var hsv = this.hsv(), + result = new Color(), + vv = Math.max(hsv[2] - 0.16, 0); + result.set_hsv(hsv[0], hsv[1], vv); + return result; +}; + +// Points ////////////////////////////////////////////////////////////// + +// Point instance creation: + +function Point(x, y) { + this.x = x || 0; + this.y = y || 0; +} + +// Point string representation: e.g. '12@68' + +Point.prototype.toString = function () { + return Math.round(this.x.toString()) + + '@' + Math.round(this.y.toString()); +}; + +// Point copying: + +Point.prototype.copy = function () { + return new Point(this.x, this.y); +}; + +// Point comparison: + +Point.prototype.eq = function (aPoint) { + // == + return this.x === aPoint.x && this.y === aPoint.y; +}; + +Point.prototype.lt = function (aPoint) { + // < + return this.x < aPoint.x && this.y < aPoint.y; +}; + +Point.prototype.gt = function (aPoint) { + // > + return this.x > aPoint.x && this.y > aPoint.y; +}; + +Point.prototype.ge = function (aPoint) { + // >= + return this.x >= aPoint.x && this.y >= aPoint.y; +}; + +Point.prototype.le = function (aPoint) { + // <= + return this.x <= aPoint.x && this.y <= aPoint.y; +}; + +Point.prototype.max = function (aPoint) { + return new Point(Math.max(this.x, aPoint.x), + Math.max(this.y, aPoint.y)); +}; + +Point.prototype.min = function (aPoint) { + return new Point(Math.min(this.x, aPoint.x), + Math.min(this.y, aPoint.y)); +}; + +// Point conversion: + +Point.prototype.round = function () { + return new Point(Math.round(this.x), Math.round(this.y)); +}; + +Point.prototype.abs = function () { + return new Point(Math.abs(this.x), Math.abs(this.y)); +}; + +Point.prototype.neg = function () { + return new Point(-this.x, -this.y); +}; + +Point.prototype.mirror = function () { + return new Point(this.y, this.x); +}; + +Point.prototype.floor = function () { + return new Point( + Math.max(Math.floor(this.x), 0), + Math.max(Math.floor(this.y), 0) + ); +}; + +Point.prototype.ceil = function () { + return new Point(Math.ceil(this.x), Math.ceil(this.y)); +}; + +// Point arithmetic: + +Point.prototype.add = function (other) { + if (other instanceof Point) { + return new Point(this.x + other.x, this.y + other.y); + } + return new Point(this.x + other, this.y + other); +}; + +Point.prototype.subtract = function (other) { + if (other instanceof Point) { + return new Point(this.x - other.x, this.y - other.y); + } + return new Point(this.x - other, this.y - other); +}; + +Point.prototype.multiplyBy = function (other) { + if (other instanceof Point) { + return new Point(this.x * other.x, this.y * other.y); + } + return new Point(this.x * other, this.y * other); +}; + +Point.prototype.divideBy = function (other) { + if (other instanceof Point) { + return new Point(this.x / other.x, this.y / other.y); + } + return new Point(this.x / other, this.y / other); +}; + +Point.prototype.floorDivideBy = function (other) { + if (other instanceof Point) { + return new Point(Math.floor(this.x / other.x), + Math.floor(this.y / other.y)); + } + return new Point(Math.floor(this.x / other), + Math.floor(this.y / other)); +}; + +// Point polar coordinates: + +Point.prototype.r = function () { + var t = (this.multiplyBy(this)); + return Math.sqrt(t.x + t.y); +}; + +Point.prototype.degrees = function () { +/* + answer the angle I make with origin in degrees. + Right is 0, down is 90 +*/ + var tan, theta; + + if (this.x === 0) { + if (this.y >= 0) { + return 90; + } + return 270; + } + tan = this.y / this.x; + theta = Math.atan(tan); + if (this.x >= 0) { + if (this.y >= 0) { + return degrees(theta); + } + return 360 + (degrees(theta)); + } + return 180 + degrees(theta); +}; + +Point.prototype.theta = function () { +/* + answer the angle I make with origin in radians. + Right is 0, down is 90 +*/ + var tan, theta; + + if (this.x === 0) { + if (this.y >= 0) { + return radians(90); + } + return radians(270); + } + tan = this.y / this.x; + theta = Math.atan(tan); + if (this.x >= 0) { + if (this.y >= 0) { + return theta; + } + return radians(360) + theta; + } + return radians(180) + theta; +}; + +// Point functions: + +Point.prototype.crossProduct = function (aPoint) { + return this.multiplyBy(aPoint.mirror()); +}; + +Point.prototype.distanceTo = function (aPoint) { + return (aPoint.subtract(this)).r(); +}; + +Point.prototype.rotate = function (direction, center) { + // direction must be 'right', 'left' or 'pi' + var offset = this.subtract(center); + if (direction === 'right') { + return new Point(-offset.y, offset.y).add(center); + } + if (direction === 'left') { + return new Point(offset.y, -offset.y).add(center); + } + // direction === 'pi' + return center.subtract(offset); +}; + +Point.prototype.flip = function (direction, center) { + // direction must be 'vertical' or 'horizontal' + if (direction === 'vertical') { + return new Point(this.x, center.y * 2 - this.y); + } + // direction === 'horizontal' + return new Point(center.x * 2 - this.x, this.y); +}; + +Point.prototype.distanceAngle = function (dist, angle) { + var deg = angle, x, y; + if (deg > 270) { + deg = deg - 360; + } else if (deg < -270) { + deg = deg + 360; + } + if (-90 <= deg && deg <= 90) { + x = Math.sin(radians(deg)) * dist; + y = Math.sqrt((dist * dist) - (x * x)); + return new Point(x + this.x, this.y - y); + } + x = Math.sin(radians(180 - deg)) * dist; + y = Math.sqrt((dist * dist) - (x * x)); + return new Point(x + this.x, this.y + y); +}; + +// Point transforming: + +Point.prototype.scaleBy = function (scalePoint) { + return this.multiplyBy(scalePoint); +}; + +Point.prototype.translateBy = function (deltaPoint) { + return this.add(deltaPoint); +}; + +Point.prototype.rotateBy = function (angle, centerPoint) { + var center = centerPoint || new Point(0, 0), + p = this.subtract(center), + r = p.r(), + theta = angle - p.theta(); + return new Point( + center.x + (r * Math.cos(theta)), + center.y - (r * Math.sin(theta)) + ); +}; + +// Point conversion: + +Point.prototype.asArray = function () { + return [this.x, this.y]; +}; + +// Rectangles ////////////////////////////////////////////////////////// + +// Rectangle instance creation: + +function Rectangle(left, top, right, bottom) { + this.init(new Point((left || 0), (top || 0)), + new Point((right || 0), (bottom || 0))); +} + +Rectangle.prototype.init = function (originPoint, cornerPoint) { + this.origin = originPoint; + this.corner = cornerPoint; +}; + +// Rectangle string representation: e.g. '[0@0 | 160@80]' + +Rectangle.prototype.toString = function () { + return '[' + this.origin.toString() + ' | ' + + this.extent().toString() + ']'; +}; + +// Rectangle copying: + +Rectangle.prototype.copy = function () { + return new Rectangle( + this.left(), + this.top(), + this.right(), + this.bottom() + ); +}; + +// creating Rectangle instances from Points: + +Point.prototype.corner = function (cornerPoint) { + // answer a new Rectangle + return new Rectangle( + this.x, + this.y, + cornerPoint.x, + cornerPoint.y + ); +}; + +Point.prototype.rectangle = function (aPoint) { + // answer a new Rectangle + var org, crn; + org = this.min(aPoint); + crn = this.max(aPoint); + return new Rectangle(org.x, org.y, crn.x, crn.y); +}; + +Point.prototype.extent = function (aPoint) { + //answer a new Rectangle + var crn = this.add(aPoint); + return new Rectangle(this.x, this.y, crn.x, crn.y); +}; + +// Rectangle accessing - setting: + +Rectangle.prototype.setTo = function (left, top, right, bottom) { + // note: all inputs are optional and can be omitted + + this.origin = new Point( + left || ((left === 0) ? 0 : this.left()), + top || ((top === 0) ? 0 : this.top()) + ); + + this.corner = new Point( + right || ((right === 0) ? 0 : this.right()), + bottom || ((bottom === 0) ? 0 : this.bottom()) + ); +}; + +// Rectangle accessing - getting: + +Rectangle.prototype.area = function () { + //requires width() and height() to be defined + var w = this.width(); + if (w < 0) { + return 0; + } + return Math.max(w * this.height(), 0); +}; + +Rectangle.prototype.bottom = function () { + return this.corner.y; +}; + +Rectangle.prototype.bottomCenter = function () { + return new Point(this.center().x, this.bottom()); +}; + +Rectangle.prototype.bottomLeft = function () { + return new Point(this.origin.x, this.corner.y); +}; + +Rectangle.prototype.bottomRight = function () { + return this.corner.copy(); +}; + +Rectangle.prototype.boundingBox = function () { + return this; +}; + +Rectangle.prototype.center = function () { + return this.origin.add( + this.corner.subtract(this.origin).floorDivideBy(2) + ); +}; + +Rectangle.prototype.corners = function () { + return [this.origin, + this.bottomLeft(), + this.corner, + this.topRight()]; +}; + +Rectangle.prototype.extent = function () { + return this.corner.subtract(this.origin); +}; + +Rectangle.prototype.height = function () { + return this.corner.y - this.origin.y; +}; + +Rectangle.prototype.left = function () { + return this.origin.x; +}; + +Rectangle.prototype.leftCenter = function () { + return new Point(this.left(), this.center().y); +}; + +Rectangle.prototype.right = function () { + return this.corner.x; +}; + +Rectangle.prototype.rightCenter = function () { + return new Point(this.right(), this.center().y); +}; + +Rectangle.prototype.top = function () { + return this.origin.y; +}; + +Rectangle.prototype.topCenter = function () { + return new Point(this.center().x, this.top()); +}; + +Rectangle.prototype.topLeft = function () { + return this.origin; +}; + +Rectangle.prototype.topRight = function () { + return new Point(this.corner.x, this.origin.y); +}; + +Rectangle.prototype.width = function () { + return this.corner.x - this.origin.x; +}; + +Rectangle.prototype.position = function () { + return this.origin; +}; + +// Rectangle comparison: + +Rectangle.prototype.eq = function (aRect) { + return this.origin.eq(aRect.origin) && + this.corner.eq(aRect.corner); +}; + +Rectangle.prototype.abs = function () { + var newOrigin, newCorner; + + newOrigin = this.origin.abs(); + newCorner = this.corner.max(newOrigin); + return newOrigin.corner(newCorner); +}; + +// Rectangle functions: + +Rectangle.prototype.insetBy = function (delta) { + // delta can be either a Point or a Number + var result = new Rectangle(); + result.origin = this.origin.add(delta); + result.corner = this.corner.subtract(delta); + return result; +}; + +Rectangle.prototype.expandBy = function (delta) { + // delta can be either a Point or a Number + var result = new Rectangle(); + result.origin = this.origin.subtract(delta); + result.corner = this.corner.add(delta); + return result; +}; + +Rectangle.prototype.growBy = function (delta) { + // delta can be either a Point or a Number + var result = new Rectangle(); + result.origin = this.origin.copy(); + result.corner = this.corner.add(delta); + return result; +}; + +Rectangle.prototype.intersect = function (aRect) { + var result = new Rectangle(); + result.origin = this.origin.max(aRect.origin); + result.corner = this.corner.min(aRect.corner); + return result; +}; + +Rectangle.prototype.merge = function (aRect) { + var result = new Rectangle(); + result.origin = this.origin.min(aRect.origin); + result.corner = this.corner.max(aRect.corner); + return result; +}; + +Rectangle.prototype.round = function () { + return this.origin.round().corner(this.corner.round()); +}; + +Rectangle.prototype.spread = function () { + // round me by applying floor() to my origin and ceil() to my corner + return this.origin.floor().corner(this.corner.ceil()); +}; + +Rectangle.prototype.amountToTranslateWithin = function (aRect) { +/* + Answer a Point, delta, such that self + delta is forced within + aRectangle. when all of me cannot be made to fit, prefer to keep + my topLeft inside. Taken from Squeak. +*/ + var dx, dy; + + if (this.right() > aRect.right()) { + dx = aRect.right() - this.right(); + } + if (this.bottom() > aRect.bottom()) { + dy = aRect.bottom() - this.bottom(); + } + if ((this.left() + dx) < aRect.left()) { + dx = aRect.left() - this.right(); + } + if ((this.top() + dy) < aRect.top()) { + dy = aRect.top() - this.top(); + } + return new Point(dx, dy); +}; + +// Rectangle testing: + +Rectangle.prototype.containsPoint = function (aPoint) { + return this.origin.le(aPoint) && aPoint.lt(this.corner); +}; + +Rectangle.prototype.containsRectangle = function (aRect) { + return aRect.origin.gt(this.origin) && + aRect.corner.lt(this.corner); +}; + +Rectangle.prototype.intersects = function (aRect) { + var ro = aRect.origin, rc = aRect.corner; + return (rc.x >= this.origin.x) && + (rc.y >= this.origin.y) && + (ro.x <= this.corner.x) && + (ro.y <= this.corner.y); +}; + +// Rectangle transforming: + +Rectangle.prototype.scaleBy = function (scale) { + // scale can be either a Point or a scalar + var o = this.origin.multiplyBy(scale), + c = this.corner.multiplyBy(scale); + return new Rectangle(o.x, o.y, c.x, c.y); +}; + +Rectangle.prototype.translateBy = function (factor) { + // factor can be either a Point or a scalar + var o = this.origin.add(factor), + c = this.corner.add(factor); + return new Rectangle(o.x, o.y, c.x, c.y); +}; + +// Rectangle converting: + +Rectangle.prototype.asArray = function () { + return [this.left(), this.top(), this.right(), this.bottom()]; +}; + +Rectangle.prototype.asArray_xywh = function () { + return [this.left(), this.top(), this.width(), this.height()]; +}; + +// Nodes /////////////////////////////////////////////////////////////// + +// Node instance creation: + +function Node(parent, childrenArray) { + this.init(parent || null, childrenArray || []); +} + +Node.prototype.init = function (parent, childrenArray) { + this.parent = parent || null; + this.children = childrenArray || []; +}; + +// Node string representation: e.g. 'a Node[3]' + +Node.prototype.toString = function () { + return 'a Node' + '[' + this.children.length.toString() + ']'; +}; + +// Node accessing: + +Node.prototype.addChild = function (aNode) { + this.children.push(aNode); + aNode.parent = this; +}; + +Node.prototype.addChildFirst = function (aNode) { + this.children.splice(0, null, aNode); + aNode.parent = this; +}; + +Node.prototype.removeChild = function (aNode) { + var idx = this.children.indexOf(aNode); + if (idx !== -1) { + this.children.splice(idx, 1); + } +}; + +// Node functions: + +Node.prototype.root = function () { + if (this.parent === null) { + return this; + } + return this.parent.root(); +}; + +Node.prototype.depth = function () { + if (this.parent === null) { + return 0; + } + return this.parent.depth() + 1; +}; + +Node.prototype.allChildren = function () { + // includes myself + var result = [this]; + this.children.forEach(function (child) { + result = result.concat(child.allChildren()); + }); + return result; +}; + +Node.prototype.forAllChildren = function (aFunction) { + if (this.children.length > 0) { + this.children.forEach(function (child) { + child.forAllChildren(aFunction); + }); + } + aFunction.call(null, this); +}; + +Node.prototype.allLeafs = function () { + var result = []; + this.allChildren().forEach(function (element) { + if (element.children.length === 0) { + result.push(element); + } + }); + return result; +}; + +Node.prototype.allParents = function () { + // includes myself + var result = [this]; + if (this.parent !== null) { + result = result.concat(this.parent.allParents()); + } + return result; +}; + +Node.prototype.siblings = function () { + var myself = this; + if (this.parent === null) { + return []; + } + return this.parent.children.filter(function (child) { + return child !== myself; + }); +}; + +Node.prototype.parentThatIsA = function (constructor) { + // including myself + if (this instanceof constructor) { + return this; + } + if (!this.parent) { + return null; + } + return this.parent.parentThatIsA(constructor); +}; + +Node.prototype.parentThatIsAnyOf = function (constructors) { + // including myself + var yup = false, + myself = this; + constructors.forEach(function (each) { + if (myself.constructor === each) { + yup = true; + return; + } + }); + if (yup) { + return this; + } + if (!this.parent) { + return null; + } + return this.parent.parentThatIsAnyOf(constructors); +}; + +// Morphs ////////////////////////////////////////////////////////////// + +// Morph: referenced constructors + +var Morph; +var WorldMorph; +var HandMorph; +var ShadowMorph; +var FrameMorph; +var MenuMorph; +var HandleMorph; +var StringFieldMorph; +var ColorPickerMorph; +var SliderMorph; +var ScrollFrameMorph; +var InspectorMorph; +var StringMorph; +var TextMorph; + +// Morph inherits from Node: + +Morph.prototype = new Node(); +Morph.prototype.constructor = Morph; +Morph.uber = Node.prototype; + +// Morph settings: + +/* + damage list housekeeping + + the trackChanges property of the Morph prototype is a Boolean switch + that determines whether the World's damage list ('broken' rectangles) + tracks changes. By default the switch is always on. If set to false + changes are not stored. This can be very useful for housekeeping of + the damage list in situations where a large number of (sub-) morphs + are changed more or less at once. Instead of keeping track of every + single submorph's changes tremendous performance improvements can be + achieved by setting the trackChanges flag to false before propagating + the layout changes, setting it to true again and then storing the full + bounds of the surrounding morph. An an example refer to the + + fixLayout() + + method of InspectorMorph, or the + + startLayout() + endLayout() + + methods of SyntaxElementMorph in the Snap application. +*/ + +Morph.prototype.trackChanges = true; +Morph.prototype.shadowBlur = 4; + +// Morph instance creation: + +function Morph() { + this.init(); +} + +// Morph initialization: + +Morph.prototype.init = function () { + Morph.uber.init.call(this); + this.isMorph = true; + this.bounds = new Rectangle(0, 0, 50, 40); + this.color = new Color(80, 80, 80); + this.texture = null; // optional url of a fill-image + this.cachedTexture = null; // internal cache of actual bg image + this.alpha = 1; + this.isVisible = true; + this.isDraggable = false; + this.isTemplate = false; + this.acceptsDrops = false; + this.noticesTransparentClick = false; + this.drawNew(); + this.fps = 0; + this.customContextMenu = null; + this.lastTime = Date.now(); + this.onNextStep = null; // optional function to be run once +}; + +// Morph string representation: e.g. 'a Morph 2 [20@45 | 130@250]' + +Morph.prototype.toString = function () { + return 'a ' + + (this.constructor.name || + this.constructor.toString().split(' ')[1].split('(')[0]) + + ' ' + + this.children.length.toString() + ' ' + + this.bounds; +}; + +// Morph deleting: + +Morph.prototype.destroy = function () { + if (this.parent !== null) { + this.fullChanged(); + this.parent.removeChild(this); + } +}; + +// Morph stepping: + +Morph.prototype.stepFrame = function () { + if (!this.step) { + return null; + } + var current, elapsed, leftover, nxt; + current = Date.now(); + elapsed = current - this.lastTime; + if (this.fps > 0) { + leftover = (1000 / this.fps) - elapsed; + } else { + leftover = 0; + } + if (leftover < 1) { + this.lastTime = current; + if (this.onNextStep) { + nxt = this.onNextStep; + this.onNextStep = null; + nxt.call(this); + } + this.step(); + this.children.forEach(function (child) { + child.stepFrame(); + }); + } +}; + +Morph.prototype.nextSteps = function (arrayOfFunctions) { + var lst = arrayOfFunctions || [], + nxt = lst.shift(), + myself = this; + if (nxt) { + this.onNextStep = function () { + nxt.call(myself); + myself.nextSteps(lst); + }; + } +}; + +Morph.prototype.step = function () { + nop(); +}; + +// Morph accessing - geometry getting: + +Morph.prototype.left = function () { + return this.bounds.left(); +}; + +Morph.prototype.right = function () { + return this.bounds.right(); +}; + +Morph.prototype.top = function () { + return this.bounds.top(); +}; + +Morph.prototype.bottom = function () { + return this.bounds.bottom(); +}; + +Morph.prototype.center = function () { + return this.bounds.center(); +}; + +Morph.prototype.bottomCenter = function () { + return this.bounds.bottomCenter(); +}; + +Morph.prototype.bottomLeft = function () { + return this.bounds.bottomLeft(); +}; + +Morph.prototype.bottomRight = function () { + return this.bounds.bottomRight(); +}; + +Morph.prototype.boundingBox = function () { + return this.bounds; +}; + +Morph.prototype.corners = function () { + return this.bounds.corners(); +}; + +Morph.prototype.leftCenter = function () { + return this.bounds.leftCenter(); +}; + +Morph.prototype.rightCenter = function () { + return this.bounds.rightCenter(); +}; + +Morph.prototype.topCenter = function () { + return this.bounds.topCenter(); +}; + +Morph.prototype.topLeft = function () { + return this.bounds.topLeft(); +}; + +Morph.prototype.topRight = function () { + return this.bounds.topRight(); +}; +Morph.prototype.position = function () { + return this.bounds.origin; +}; + +Morph.prototype.extent = function () { + return this.bounds.extent(); +}; + +Morph.prototype.width = function () { + return this.bounds.width(); +}; + +Morph.prototype.height = function () { + return this.bounds.height(); +}; + +Morph.prototype.fullBounds = function () { + var result; + result = this.bounds; + this.children.forEach(function (child) { + if (child.isVisible) { + result = result.merge(child.fullBounds()); + } + }); + return result; +}; + +Morph.prototype.fullBoundsNoShadow = function () { + // answer my full bounds but ignore any shadow + var result; + result = this.bounds; + this.children.forEach(function (child) { + if (!(child instanceof ShadowMorph) && (child.isVisible)) { + result = result.merge(child.fullBounds()); + } + }); + return result; +}; + +Morph.prototype.visibleBounds = function () { + // answer which part of me is not clipped by a Frame + var visible = this.bounds, + frames = this.allParents().filter(function (p) { + return p instanceof FrameMorph; + }); + frames.forEach(function (f) { + visible = visible.intersect(f.bounds); + }); + return visible; +}; + +// Morph accessing - simple changes: + +Morph.prototype.moveBy = function (delta) { + this.changed(); + this.bounds = this.bounds.translateBy(delta); + this.children.forEach(function (child) { + child.moveBy(delta); + }); + this.changed(); +}; + +Morph.prototype.silentMoveBy = function (delta) { + this.bounds = this.bounds.translateBy(delta); + this.children.forEach(function (child) { + child.silentMoveBy(delta); + }); +}; + +Morph.prototype.setPosition = function (aPoint) { + var delta = aPoint.subtract(this.topLeft()); + if ((delta.x !== 0) || (delta.y !== 0)) { + this.moveBy(delta); + } +}; + +Morph.prototype.silentSetPosition = function (aPoint) { + var delta = aPoint.subtract(this.topLeft()); + if ((delta.x !== 0) || (delta.y !== 0)) { + this.silentMoveBy(delta); + } +}; + +Morph.prototype.setLeft = function (x) { + this.setPosition( + new Point( + x, + this.top() + ) + ); +}; + +Morph.prototype.setRight = function (x) { + this.setPosition( + new Point( + x - this.width(), + this.top() + ) + ); +}; + +Morph.prototype.setTop = function (y) { + this.setPosition( + new Point( + this.left(), + y + ) + ); +}; + +Morph.prototype.setBottom = function (y) { + this.setPosition( + new Point( + this.left(), + y - this.height() + ) + ); +}; + +Morph.prototype.setCenter = function (aPoint) { + this.setPosition( + aPoint.subtract( + this.extent().floorDivideBy(2) + ) + ); +}; + +Morph.prototype.setFullCenter = function (aPoint) { + this.setPosition( + aPoint.subtract( + this.fullBounds().extent().floorDivideBy(2) + ) + ); +}; + +Morph.prototype.keepWithin = function (aMorph) { + // make sure I am completely within another Morph's bounds + var leftOff, rightOff, topOff, bottomOff; + leftOff = this.fullBounds().left() - aMorph.left(); + if (leftOff < 0) { + this.moveBy(new Point(-leftOff, 0)); + } + rightOff = this.fullBounds().right() - aMorph.right(); + if (rightOff > 0) { + this.moveBy(new Point(-rightOff, 0)); + } + topOff = this.fullBounds().top() - aMorph.top(); + if (topOff < 0) { + this.moveBy(new Point(0, -topOff)); + } + bottomOff = this.fullBounds().bottom() - aMorph.bottom(); + if (bottomOff > 0) { + this.moveBy(new Point(0, -bottomOff)); + } +}; + +// Morph accessing - dimensional changes requiring a complete redraw + +Morph.prototype.setExtent = function (aPoint) { + if (!aPoint.eq(this.extent())) { + this.changed(); + this.silentSetExtent(aPoint); + this.changed(); + this.drawNew(); + } +}; + +Morph.prototype.silentSetExtent = function (aPoint) { + var ext, newWidth, newHeight; + ext = aPoint.round(); + newWidth = Math.max(ext.x, 0); + newHeight = Math.max(ext.y, 0); + this.bounds.corner = new Point( + this.bounds.origin.x + newWidth, + this.bounds.origin.y + newHeight + ); +}; + +Morph.prototype.setWidth = function (width) { + this.setExtent(new Point(width || 0, this.height())); +}; + +Morph.prototype.silentSetWidth = function (width) { + // do not drawNew() just yet + var w = Math.max(Math.round(width || 0), 0); + this.bounds.corner = new Point( + this.bounds.origin.x + w, + this.bounds.corner.y + ); +}; + +Morph.prototype.setHeight = function (height) { + this.setExtent(new Point(this.width(), height || 0)); +}; + +Morph.prototype.silentSetHeight = function (height) { + // do not drawNew() just yet + var h = Math.max(Math.round(height || 0), 0); + this.bounds.corner = new Point( + this.bounds.corner.x, + this.bounds.origin.y + h + ); +}; + +Morph.prototype.setColor = function (aColor) { + if (aColor) { + if (!this.color.eq(aColor)) { + this.color = aColor; + this.changed(); + this.drawNew(); + } + } +}; + +// Morph displaying: + +Morph.prototype.drawNew = function () { + // initialize my surface property + this.image = newCanvas(this.extent()); + var context = this.image.getContext('2d'); + context.fillStyle = this.color.toString(); + context.fillRect(0, 0, this.width(), this.height()); + if (this.cachedTexture) { + this.drawCachedTexture(); + } else if (this.texture) { + this.drawTexture(this.texture); + } +}; + +Morph.prototype.drawTexture = function (url) { + var myself = this; + this.cachedTexture = new Image(); + this.cachedTexture.onload = function () { + myself.drawCachedTexture(); + }; + this.cachedTexture.src = this.texture = url; // make absolute +}; + +Morph.prototype.drawCachedTexture = function () { + var bg = this.cachedTexture, + cols = Math.floor(this.image.width / bg.width), + lines = Math.floor(this.image.height / bg.height), + x, + y, + context = this.image.getContext('2d'); + + for (y = 0; y <= lines; y += 1) { + for (x = 0; x <= cols; x += 1) { + context.drawImage(bg, x * bg.width, y * bg.height); + } + } + this.changed(); +}; + +/* +Morph.prototype.drawCachedTexture = function () { + var context = this.image.getContext('2d'), + pattern = context.createPattern(this.cachedTexture, 'repeat'); + context.fillStyle = pattern; + context.fillRect(0, 0, this.image.width, this.image.height); + this.changed(); +}; +*/ + +Morph.prototype.drawOn = function (aCanvas, aRect) { + var rectangle, area, delta, src, context, w, h, sl, st; + if (!this.isVisible) { + return null; + } + rectangle = aRect || this.bounds(); + area = rectangle.intersect(this.bounds).round(); + if (area.extent().gt(new Point(0, 0))) { + delta = this.position().neg(); + src = area.copy().translateBy(delta).round(); + context = aCanvas.getContext('2d'); + context.globalAlpha = this.alpha; + + sl = src.left(); + st = src.top(); + w = Math.min(src.width(), this.image.width - sl); + h = Math.min(src.height(), this.image.height - st); + + if (w < 1 || h < 1) { + return null; + } + + context.drawImage( + this.image, + src.left(), + src.top(), + w, + h, + area.left(), + area.top(), + w, + h + ); + + /* "for debugging purposes:" + + try { + context.drawImage( + this.image, + src.left(), + src.top(), + w, + h, + area.left(), + area.top(), + w, + h + ); + } catch (err) { + alert('internal error\n\n' + err + + '\n ---' + + '\n canvas: ' + aCanvas + + '\n canvas.width: ' + aCanvas.width + + '\n canvas.height: ' + aCanvas.height + + '\n ---' + + '\n image: ' + this.image + + '\n image.width: ' + this.image.width + + '\n image.height: ' + this.image.height + + '\n ---' + + '\n w: ' + w + + '\n h: ' + h + + '\n sl: ' + sl + + '\n st: ' + st + + '\n area.left: ' + area.left() + + '\n area.top ' + area.top() + ); + } + */ + + } +}; + +Morph.prototype.fullDrawOn = function (aCanvas, aRect) { + var rectangle; + if (!this.isVisible) { + return null; + } + rectangle = aRect || this.fullBounds(); + this.drawOn(aCanvas, rectangle); + this.children.forEach(function (child) { + child.fullDrawOn(aCanvas, rectangle); + }); +}; + +Morph.prototype.hide = function () { + this.isVisible = false; + this.changed(); + this.children.forEach(function (child) { + child.hide(); + }); +}; + +Morph.prototype.show = function () { + this.isVisible = true; + this.changed(); + this.children.forEach(function (child) { + child.show(); + }); +}; + +Morph.prototype.toggleVisibility = function () { + this.isVisible = (!this.isVisible); + this.changed(); + this.children.forEach(function (child) { + child.toggleVisibility(); + }); +}; + +// Morph full image: + +Morph.prototype.fullImageClassic = function () { + // why doesn't this work for all Morphs? + var fb = this.fullBounds(), + img = newCanvas(fb.extent()), + ctx = img.getContext('2d'); + ctx.translate(-this.bounds.origin.x, -this.bounds.origin.y); + this.fullDrawOn(img, fb); + img.globalAlpha = this.alpha; + return img; +}; + +Morph.prototype.fullImage = function () { + var img, ctx, fb; + img = newCanvas(this.fullBounds().extent()); + ctx = img.getContext('2d'); + fb = this.fullBounds(); + this.allChildren().forEach(function (morph) { + if (morph.isVisible) { + ctx.globalAlpha = morph.alpha; + ctx.drawImage( + morph.image, + morph.bounds.origin.x - fb.origin.x, + morph.bounds.origin.y - fb.origin.y + ); + } + }); + return img; +}; + +// Morph shadow: + +Morph.prototype.shadowImage = function (off, color) { + // fallback for Windows Chrome-Shadow bug + var fb, img, outline, sha, ctx, + offset = off || new Point(7, 7), + clr = color || new Color(0, 0, 0); + fb = this.fullBounds().extent(); + img = this.fullImage(); + outline = newCanvas(fb); + ctx = outline.getContext('2d'); + ctx.drawImage(img, 0, 0); + ctx.globalCompositeOperation = 'destination-out'; + ctx.drawImage( + img, + -offset.x, + -offset.y + ); + sha = newCanvas(fb); + ctx = sha.getContext('2d'); + ctx.drawImage(outline, 0, 0); + ctx.globalCompositeOperation = 'source-atop'; + ctx.fillStyle = clr.toString(); + ctx.fillRect(0, 0, fb.x, fb.y); + return sha; +}; + +Morph.prototype.shadowImageBlurred = function (off, color) { + var fb, img, sha, ctx, + offset = off || new Point(7, 7), + blur = this.shadowBlur, + clr = color || new Color(0, 0, 0); + fb = this.fullBounds().extent().add(blur * 2); + img = this.fullImage(); + sha = newCanvas(fb); + ctx = sha.getContext('2d'); + ctx.shadowOffsetX = offset.x; + ctx.shadowOffsetY = offset.y; + ctx.shadowBlur = blur; + ctx.shadowColor = clr.toString(); + ctx.drawImage( + img, + blur - offset.x, + blur - offset.y + ); + ctx.shadowOffsetX = 0; + ctx.shadowOffsetY = 0; + ctx.shadowBlur = 0; + ctx.globalCompositeOperation = 'destination-out'; + ctx.drawImage( + img, + blur - offset.x, + blur - offset.y + ); + return sha; +}; + +Morph.prototype.shadow = function (off, a, color) { + var shadow = new ShadowMorph(), + offset = off || new Point(7, 7), + alpha = a || ((a === 0) ? 0 : 0.2), + fb = this.fullBounds(); + shadow.setExtent(fb.extent().add(this.shadowBlur * 2)); + if (useBlurredShadows && !MorphicPreferences.isFlat) { + shadow.image = this.shadowImageBlurred(offset, color); + shadow.alpha = alpha; + shadow.setPosition(fb.origin.add(offset).subtract(this.shadowBlur)); + } else { + shadow.image = this.shadowImage(offset, color); + shadow.alpha = alpha; + shadow.setPosition(fb.origin.add(offset)); + } + return shadow; +}; + +Morph.prototype.addShadow = function (off, a, color) { + var shadow, + offset = off || new Point(7, 7), + alpha = a || ((a === 0) ? 0 : 0.2); + shadow = this.shadow(offset, alpha, color); + this.addBack(shadow); + this.fullChanged(); + return shadow; +}; + +Morph.prototype.getShadow = function () { + var shadows; + shadows = this.children.slice(0).reverse().filter( + function (child) { + return child instanceof ShadowMorph; + } + ); + if (shadows.length !== 0) { + return shadows[0]; + } + return null; +}; + +Morph.prototype.removeShadow = function () { + var shadow = this.getShadow(); + if (shadow !== null) { + this.fullChanged(); + this.removeChild(shadow); + } +}; + +// Morph pen trails: + +Morph.prototype.penTrails = function () { + // answer my pen trails canvas. default is to answer my image + return this.image; +}; + +// Morph updating: + +Morph.prototype.changed = function () { + if (this.trackChanges) { + var w = this.root(); + if (w instanceof WorldMorph) { + w.broken.push(this.visibleBounds().spread()); + } + } + if (this.parent) { + this.parent.childChanged(this); + } +}; + +Morph.prototype.fullChanged = function () { + if (this.trackChanges) { + var w = this.root(); + if (w instanceof WorldMorph) { + w.broken.push(this.fullBounds().spread()); + } + } +}; + +Morph.prototype.childChanged = function () { + // react to a change in one of my children, + // default is to just pass this message on upwards + // override this method for Morphs that need to adjust accordingly + if (this.parent) { + this.parent.childChanged(this); + } +}; + +// Morph accessing - structure: + +Morph.prototype.world = function () { + var root = this.root(); + if (root instanceof WorldMorph) { + return root; + } + if (root instanceof HandMorph) { + return root.world; + } + return null; +}; + +Morph.prototype.add = function (aMorph) { + var owner = aMorph.parent; + if (owner !== null) { + owner.removeChild(aMorph); + } + this.addChild(aMorph); +}; + +Morph.prototype.addBack = function (aMorph) { + var owner = aMorph.parent; + if (owner !== null) { + owner.removeChild(aMorph); + } + this.addChildFirst(aMorph); +}; + +Morph.prototype.topMorphSuchThat = function (predicate) { + var next; + if (predicate.call(null, this)) { + next = detect( + this.children.slice(0).reverse(), + predicate + ); + if (next) { + return next.topMorphSuchThat(predicate); + } + return this; + } + return null; +}; + +Morph.prototype.morphAt = function (aPoint) { + var morphs = this.allChildren().slice(0).reverse(), + result = null; + morphs.forEach(function (m) { + if (m.fullBounds().containsPoint(aPoint) && + (result === null)) { + result = m; + } + }); + return result; +}; + +/* + alternative - more elegant and possibly more + performant - solution for morphAt. + Has some issues, commented out for now + +Morph.prototype.morphAt = function (aPoint) { + return this.topMorphSuchThat(function (m) { + return m.fullBounds().containsPoint(aPoint); + }); +}; +*/ + +Morph.prototype.overlappedMorphs = function () { + //exclude the World + var world = this.world(), + fb = this.fullBounds(), + myself = this, + allParents = this.allParents(), + allChildren = this.allChildren(), + morphs; + + morphs = world.allChildren(); + return morphs.filter(function (m) { + return m.isVisible && + m !== myself && + m !== world && + !contains(allParents, m) && + !contains(allChildren, m) && + m.fullBounds().intersects(fb); + }); +}; + +// Morph pixel access: + +Morph.prototype.getPixelColor = function (aPoint) { + var point, context, data; + point = aPoint.subtract(this.bounds.origin); + context = this.image.getContext('2d'); + data = context.getImageData(point.x, point.y, 1, 1); + return new Color( + data.data[0], + data.data[1], + data.data[2], + data.data[3] + ); +}; + +Morph.prototype.isTransparentAt = function (aPoint) { + var point, context, data; + if (this.bounds.containsPoint(aPoint)) { + if (this.texture) { + return false; + } + point = aPoint.subtract(this.bounds.origin); + context = this.image.getContext('2d'); + data = context.getImageData( + Math.floor(point.x), + Math.floor(point.y), + 1, + 1 + ); + return data.data[3] === 0; + } + return false; +}; + +// Morph duplicating: + +Morph.prototype.copy = function () { + var c = copy(this); + c.parent = null; + c.children = []; + c.bounds = this.bounds.copy(); + return c; +}; + +Morph.prototype.fullCopy = function () { + /* + Produce a copy of me with my entire tree of submorphs. Morphs + mentioned more than once are all directed to a single new copy. + Other properties are also *shallow* copied, so you must override + to deep copy Arrays and (complex) Objects + */ + var dict = {}, c; + c = this.copyRecordingReferences(dict); + c.forAllChildren(function (m) { + m.updateReferences(dict); + }); + return c; +}; + +Morph.prototype.copyRecordingReferences = function (dict) { + /* + Recursively copy this entire composite morph, recording the + correspondence between old and new morphs in the given dictionary. + This dictionary will be used to update intra-composite references + in the copy. See updateReferences(). + Note: This default implementation copies ONLY morphs in the + submorph hierarchy. If a morph stores morphs in other properties + that it wants to copy, then it should override this method to do so. + The same goes for morphs that contain other complex data that + should be copied when the morph is duplicated. + */ + var c = this.copy(); + dict[this] = c; + this.children.forEach(function (m) { + c.add(m.copyRecordingReferences(dict)); + }); + return c; +}; + +Morph.prototype.updateReferences = function (dict) { + /* + Update intra-morph references within a composite morph that has + been copied. For example, if a button refers to morph X in the + orginal composite then the copy of that button in the new composite + should refer to the copy of X in new composite, not the original X. + */ + var property; + for (property in this) { + if (property.isMorph && dict[property]) { + this[property] = dict[property]; + } + } +}; + +// Morph dragging and dropping: + +Morph.prototype.rootForGrab = function () { + if (this instanceof ShadowMorph) { + return this.parent.rootForGrab(); + } + if (this.parent instanceof ScrollFrameMorph) { + return this.parent; + } + if (this.parent === null || + this.parent instanceof WorldMorph || + this.parent instanceof FrameMorph || + this.isDraggable === true) { + return this; + } + return this.parent.rootForGrab(); +}; + +Morph.prototype.wantsDropOf = function (aMorph) { + // default is to answer the general flag - change for my heirs + if ((aMorph instanceof HandleMorph) || + (aMorph instanceof MenuMorph) || + (aMorph instanceof InspectorMorph)) { + return false; + } + return this.acceptsDrops; +}; + +Morph.prototype.pickUp = function (wrrld) { + var world = wrrld || this.world(); + this.setPosition( + world.hand.position().subtract( + this.extent().floorDivideBy(2) + ) + ); + world.hand.grab(this); +}; + +Morph.prototype.isPickedUp = function () { + return this.parentThatIsA(HandMorph) !== null; +}; + +Morph.prototype.situation = function () { + // answer a dictionary specifying where I am right now, so + // I can slide back to it if I'm dropped somewhere else + if (this.parent) { + return { + origin: this.parent, + position: this.position().subtract(this.parent.position()) + }; + } + return null; +}; + +Morph.prototype.slideBackTo = function (situation, inSteps) { + var steps = inSteps || 5, + pos = situation.origin.position().add(situation.position), + xStep = -(this.left() - pos.x) / steps, + yStep = -(this.top() - pos.y) / steps, + stepCount = 0, + oldStep = this.step, + oldFps = this.fps, + myself = this; + + this.fps = 0; + this.step = function () { + myself.fullChanged(); + myself.silentMoveBy(new Point(xStep, yStep)); + myself.fullChanged(); + stepCount += 1; + if (stepCount === steps) { + situation.origin.add(myself); + if (situation.origin.reactToDropOf) { + situation.origin.reactToDropOf(myself); + } + myself.step = oldStep; + myself.fps = oldFps; + } + }; +}; + +// Morph utilities: + +Morph.prototype.nop = function () { + nop(); +}; + +Morph.prototype.resize = function () { + this.world().activeHandle = new HandleMorph(this); +}; + +Morph.prototype.move = function () { + this.world().activeHandle = new HandleMorph( + this, + null, + null, + null, + null, + 'move' + ); +}; + +Morph.prototype.hint = function (msg) { + var m, text; + text = msg; + if (msg) { + if (msg.toString) { + text = msg.toString(); + } + } else { + text = 'NULL'; + } + m = new MenuMorph(this, text); + m.isDraggable = true; + m.popUpCenteredAtHand(this.world()); +}; + +Morph.prototype.inform = function (msg) { + var m, text; + text = msg; + if (msg) { + if (msg.toString) { + text = msg.toString(); + } + } else { + text = 'NULL'; + } + m = new MenuMorph(this, text); + m.addItem("Ok"); + m.isDraggable = true; + m.popUpCenteredAtHand(this.world()); +}; + +Morph.prototype.prompt = function ( + msg, + callback, + environment, + defaultContents, + width, + floorNum, + ceilingNum, + isRounded +) { + var menu, entryField, slider, isNumeric; + if (ceilingNum) { + isNumeric = true; + } + menu = new MenuMorph( + callback || null, + msg || '', + environment || null + ); + entryField = new StringFieldMorph( + defaultContents || '', + width || 100, + MorphicPreferences.prompterFontSize, + MorphicPreferences.prompterFontName, + false, + false, + isNumeric + ); + menu.items.push(entryField); + if (ceilingNum || MorphicPreferences.useSliderForInput) { + slider = new SliderMorph( + floorNum || 0, + ceilingNum, + parseFloat(defaultContents), + Math.floor((ceilingNum - floorNum) / 4), + 'horizontal' + ); + slider.alpha = 1; + slider.color = new Color(225, 225, 225); + slider.button.color = menu.borderColor; + slider.button.highlightColor = slider.button.color.copy(); + slider.button.highlightColor.b += 100; + slider.button.pressColor = slider.button.color.copy(); + slider.button.pressColor.b += 150; + slider.setHeight(MorphicPreferences.prompterSliderSize); + if (isRounded) { + slider.action = function (num) { + entryField.changed(); + entryField.text.text = Math.round(num).toString(); + entryField.text.drawNew(); + entryField.text.changed(); + entryField.text.edit(); + }; + } else { + slider.action = function (num) { + entryField.changed(); + entryField.text.text = num.toString(); + entryField.text.drawNew(); + entryField.text.changed(); + }; + } + menu.items.push(slider); + } + + menu.addLine(2); + menu.addItem('Ok', function () { + return entryField.string(); + }); + menu.addItem('Cancel', function () { + return null; + }); + menu.isDraggable = true; + menu.popUpAtHand(this.world()); + entryField.text.edit(); +}; + +Morph.prototype.pickColor = function ( + msg, + callback, + environment, + defaultContents +) { + var menu, colorPicker; + menu = new MenuMorph( + callback || null, + msg || '', + environment || null + ); + colorPicker = new ColorPickerMorph(defaultContents); + menu.items.push(colorPicker); + menu.addLine(2); + menu.addItem('Ok', function () { + return colorPicker.getChoice(); + }); + menu.addItem('Cancel', function () { + return null; + }); + menu.isDraggable = true; + menu.popUpAtHand(this.world()); +}; + +Morph.prototype.inspect = function (anotherObject) { + var world = this.world instanceof Function ? + this.world() : this.root() || this.world, + inspector, + inspectee = this; + + if (anotherObject) { + inspectee = anotherObject; + } + inspector = new InspectorMorph(inspectee); + inspector.setPosition(world.hand.position()); + inspector.keepWithin(world); + world.add(inspector); + inspector.changed(); +}; + +// Morph menus: + +Morph.prototype.contextMenu = function () { + var world; + + if (this.customContextMenu) { + return this.customContextMenu; + } + world = this.world instanceof Function ? this.world() : this.world; + if (world && world.isDevMode) { + if (this.parent === world) { + return this.developersMenu(); + } + return this.hierarchyMenu(); + } + return this.userMenu() || + (this.parent && this.parent.userMenu()); +}; + +Morph.prototype.hierarchyMenu = function () { + var parents = this.allParents(), + world = this.world instanceof Function ? this.world() : this.world, + menu = new MenuMorph(this, null); + + parents.forEach(function (each) { + if (each.developersMenu && (each !== world)) { + menu.addItem(each.toString().slice(0, 50), function () { + each.developersMenu().popUpAtHand(world); + }); + } + }); + return menu; +}; + +Morph.prototype.developersMenu = function () { + // 'name' is not an official property of a function, hence: + var world = this.world instanceof Function ? this.world() : this.world, + userMenu = this.userMenu() || + (this.parent && this.parent.userMenu()), + menu = new MenuMorph(this, this.constructor.name || + this.constructor.toString().split(' ')[1].split('(')[0]); + if (userMenu) { + menu.addItem( + 'user features...', + function () { + userMenu.popUpAtHand(world); + } + ); + menu.addLine(); + } + menu.addItem( + "color...", + function () { + this.pickColor( + menu.title + '\ncolor:', + this.setColor, + this, + this.color + ); + }, + 'choose another color \nfor this morph' + ); + menu.addItem( + "transparency...", + function () { + this.prompt( + menu.title + '\nalpha\nvalue:', + this.setAlphaScaled, + this, + (this.alpha * 100).toString(), + null, + 1, + 100, + true + ); + }, + 'set this morph\'s\nalpha value' + ); + menu.addItem( + "resize...", + 'resize', + 'show a handle\nwhich can be dragged\nto change this morph\'s' + + ' extent' + ); + menu.addLine(); + menu.addItem( + "duplicate", + function () { + this.fullCopy().pickUp(this.world()); + }, + 'make a copy\nand pick it up' + ); + menu.addItem( + "pick up", + 'pickUp', + 'disattach and put \ninto the hand' + ); + menu.addItem( + "attach...", + 'attach', + 'stick this morph\nto another one' + ); + menu.addItem( + "move...", + 'move', + 'show a handle\nwhich can be dragged\nto move this morph' + ); + menu.addItem( + "inspect...", + 'inspect', + 'open a window\non all properties' + ); + menu.addItem( + "pic...", + function () { + window.open(this.fullImageClassic().toDataURL()); + }, + 'open a new window\nwith a picture of this morph' + ); + menu.addLine(); + if (this.isDraggable) { + menu.addItem( + "lock", + 'toggleIsDraggable', + 'make this morph\nunmovable' + ); + } else { + menu.addItem( + "unlock", + 'toggleIsDraggable', + 'make this morph\nmovable' + ); + } + menu.addItem("hide", 'hide'); + menu.addItem("delete", 'destroy'); + if (!(this instanceof WorldMorph)) { + menu.addLine(); + menu.addItem( + "World...", + function () { + world.contextMenu().popUpAtHand(world); + }, + 'show the\nWorld\'s menu' + ); + } + return menu; +}; + +Morph.prototype.userMenu = function () { + return null; +}; + +// Morph menu actions + +Morph.prototype.setAlphaScaled = function (alpha) { + // for context menu demo purposes + var newAlpha, unscaled; + if (typeof alpha === 'number') { + unscaled = alpha / 100; + this.alpha = Math.min(Math.max(unscaled, 0.1), 1); + } else { + newAlpha = parseFloat(alpha); + if (!isNaN(newAlpha)) { + unscaled = newAlpha / 100; + this.alpha = Math.min(Math.max(unscaled, 0.1), 1); + } + } + this.changed(); +}; + +Morph.prototype.attach = function () { + var choices = this.overlappedMorphs(), + menu = new MenuMorph(this, 'choose new parent:'), + myself = this; + + choices.forEach(function (each) { + menu.addItem(each.toString().slice(0, 50), function () { + each.add(myself); + myself.isDraggable = false; + }); + }); + if (choices.length > 0) { + menu.popUpAtHand(this.world()); + } +}; + +Morph.prototype.toggleIsDraggable = function () { + // for context menu demo purposes + this.isDraggable = !this.isDraggable; +}; + +Morph.prototype.colorSetters = function () { + // for context menu demo purposes + return ['color']; +}; + +Morph.prototype.numericalSetters = function () { + // for context menu demo purposes + return [ + 'setLeft', + 'setTop', + 'setWidth', + 'setHeight', + 'setAlphaScaled' + ]; +}; + +// Morph entry field tabbing: + +Morph.prototype.allEntryFields = function () { + return this.allChildren().filter(function (each) { + return each.isEditable && + (each instanceof StringMorph || + each instanceof TextMorph); + }); +}; + +Morph.prototype.nextEntryField = function (current) { + var fields = this.allEntryFields(), + idx = fields.indexOf(current); + if (idx !== -1) { + if (fields.length > idx + 1) { + return fields[idx + 1]; + } + } + return fields[0]; +}; + +Morph.prototype.previousEntryField = function (current) { + var fields = this.allEntryFields(), + idx = fields.indexOf(current); + if (idx !== -1) { + if (idx > 0) { + return fields[idx - 1]; + } + return fields[fields.length - 1]; + } + return fields[0]; +}; + +Morph.prototype.tab = function (editField) { +/* + the key was pressed in one of my edit fields. + invoke my "nextTab()" function if it exists, else + propagate it up my owner chain. +*/ + if (this.nextTab) { + this.nextTab(editField); + } else if (this.parent) { + this.parent.tab(editField); + } +}; + +Morph.prototype.backTab = function (editField) { +/* + the key was pressed in one of my edit fields. + invoke my "previousTab()" function if it exists, else + propagate it up my owner chain. +*/ + if (this.previousTab) { + this.previousTab(editField); + } else if (this.parent) { + this.parent.backTab(editField); + } +}; + +/* + the following are examples of what the navigation methods should + look like. Insert these at the World level for fallback, and at lower + levels in the Morphic tree (e.g. dialog boxes) for a more fine-grained + control over the tabbing cycle. + +Morph.prototype.nextTab = function (editField) { + var next = this.nextEntryField(editField); + editField.clearSelection(); + next.selectAll(); + next.edit(); +}; + +Morph.prototype.previousTab = function (editField) { + var prev = this.previousEntryField(editField); + editField.clearSelection(); + prev.selectAll(); + prev.edit(); +}; + +*/ + +// Morph events: + +Morph.prototype.escalateEvent = function (functionName, arg) { + var handler = this.parent; + while (!handler[functionName] && handler.parent !== null) { + handler = handler.parent; + } + if (handler[functionName]) { + handler[functionName](arg); + } +}; + +// Morph eval: + +Morph.prototype.evaluateString = function (code) { + var result; + + try { + result = eval(code); + this.drawNew(); + this.changed(); + } catch (err) { + this.inform(err); + } + return result; +}; + +// Morph collision detection: + +Morph.prototype.isTouching = function (otherMorph) { + var oImg = this.overlappingImage(otherMorph), + data = oImg.getContext('2d') + .getImageData(1, 1, oImg.width, oImg.height) + .data; + return detect( + data, + function (each) { + return each !== 0; + } + ) !== null; +}; + +Morph.prototype.overlappingImage = function (otherMorph) { + var fb = this.fullBounds(), + otherFb = otherMorph.fullBounds(), + oRect = fb.intersect(otherFb), + oImg = newCanvas(oRect.extent()), + ctx = oImg.getContext('2d'); + if (oRect.width() < 1 || oRect.height() < 1) { + return newCanvas(new Point(1, 1)); + } + ctx.drawImage( + this.fullImage(), + oRect.origin.x - fb.origin.x, + oRect.origin.y - fb.origin.y + ); + ctx.globalCompositeOperation = 'source-in'; + ctx.drawImage( + otherMorph.fullImage(), + otherFb.origin.x - oRect.origin.x, + otherFb.origin.y - oRect.origin.y + ); + return oImg; +}; + +// ShadowMorph ///////////////////////////////////////////////////////// + +// ShadowMorph inherits from Morph: + +ShadowMorph.prototype = new Morph(); +ShadowMorph.prototype.constructor = ShadowMorph; +ShadowMorph.uber = Morph.prototype; + +// ShadowMorph instance creation: + +function ShadowMorph() { + this.init(); +} + +// HandleMorph //////////////////////////////////////////////////////// + +// I am a resize / move handle that can be attached to any Morph + +// HandleMorph inherits from Morph: + +HandleMorph.prototype = new Morph(); +HandleMorph.prototype.constructor = HandleMorph; +HandleMorph.uber = Morph.prototype; + +// HandleMorph instance creation: + +function HandleMorph(target, minX, minY, insetX, insetY, type) { + // if insetY is missing, it will be the same as insetX + this.init(target, minX, minY, insetX, insetY, type); +} + +HandleMorph.prototype.init = function ( + target, + minX, + minY, + insetX, + insetY, + type +) { + var size = MorphicPreferences.handleSize; + this.target = target || null; + this.minExtent = new Point(minX || 0, minY || 0); + this.inset = new Point(insetX || 0, insetY || insetX || 0); + this.type = type || 'resize'; // can also be 'move' + HandleMorph.uber.init.call(this); + this.color = new Color(255, 255, 255); + this.isDraggable = false; + this.noticesTransparentClick = true; + this.setExtent(new Point(size, size)); +}; + +// HandleMorph drawing: + +HandleMorph.prototype.drawNew = function () { + this.normalImage = newCanvas(this.extent()); + this.highlightImage = newCanvas(this.extent()); + this.drawOnCanvas( + this.normalImage, + this.color, + new Color(100, 100, 100) + ); + this.drawOnCanvas( + this.highlightImage, + new Color(100, 100, 255), + new Color(255, 255, 255) + ); + this.image = this.normalImage; + if (this.target) { + this.setPosition( + this.target.bottomRight().subtract( + this.extent().add(this.inset) + ) + ); + this.target.add(this); + this.target.changed(); + } +}; + +HandleMorph.prototype.drawOnCanvas = function ( + aCanvas, + color, + shadowColor +) { + var context = aCanvas.getContext('2d'), + p1, + p11, + p2, + p22, + i; + + context.lineWidth = 1; + context.lineCap = 'round'; + + context.strokeStyle = color.toString(); + + if (this.type === 'move') { + + p1 = this.bottomLeft().subtract(this.position()); + p11 = p1.copy(); + p2 = this.topRight().subtract(this.position()); + p22 = p2.copy(); + + for (i = 0; i <= this.height(); i = i + 6) { + p11.y = p1.y - i; + p22.y = p2.y - i; + + context.beginPath(); + context.moveTo(p11.x, p11.y); + context.lineTo(p22.x, p22.y); + context.closePath(); + context.stroke(); + } + } + + p1 = this.bottomLeft().subtract(this.position()); + p11 = p1.copy(); + p2 = this.topRight().subtract(this.position()); + p22 = p2.copy(); + + for (i = 0; i <= this.width(); i = i + 6) { + p11.x = p1.x + i; + p22.x = p2.x + i; + + context.beginPath(); + context.moveTo(p11.x, p11.y); + context.lineTo(p22.x, p22.y); + context.closePath(); + context.stroke(); + } + + context.strokeStyle = shadowColor.toString(); + + if (this.type === 'move') { + + p1 = this.bottomLeft().subtract(this.position()); + p11 = p1.copy(); + p2 = this.topRight().subtract(this.position()); + p22 = p2.copy(); + + for (i = -2; i <= this.height(); i = i + 6) { + p11.y = p1.y - i; + p22.y = p2.y - i; + + context.beginPath(); + context.moveTo(p11.x, p11.y); + context.lineTo(p22.x, p22.y); + context.closePath(); + context.stroke(); + } + } + + p1 = this.bottomLeft().subtract(this.position()); + p11 = p1.copy(); + p2 = this.topRight().subtract(this.position()); + p22 = p2.copy(); + + for (i = 2; i <= this.width(); i = i + 6) { + p11.x = p1.x + i; + p22.x = p2.x + i; + + context.beginPath(); + context.moveTo(p11.x, p11.y); + context.lineTo(p22.x, p22.y); + context.closePath(); + context.stroke(); + } +}; + +// HandleMorph stepping: + +HandleMorph.prototype.step = null; + +HandleMorph.prototype.mouseDownLeft = function (pos) { + var world = this.root(), + offset = pos.subtract(this.bounds.origin), + myself = this; + + if (!this.target) { + return null; + } + this.step = function () { + var newPos, newExt; + if (world.hand.mouseButton) { + newPos = world.hand.bounds.origin.copy().subtract(offset); + if (this.type === 'resize') { + newExt = newPos.add( + myself.extent().add(myself.inset) + ).subtract(myself.target.bounds.origin); + newExt = newExt.max(myself.minExtent); + myself.target.setExtent(newExt); + + myself.setPosition( + myself.target.bottomRight().subtract( + myself.extent().add(myself.inset) + ) + ); + } else { // type === 'move' + myself.target.setPosition( + newPos.subtract(this.target.extent()) + .add(this.extent()) + ); + } + } else { + this.step = null; + } + }; + if (!this.target.step) { + this.target.step = function () { + nop(); + }; + } +}; + +// HandleMorph dragging and dropping: + +HandleMorph.prototype.rootForGrab = function () { + return this; +}; + +// HandleMorph events: + +HandleMorph.prototype.mouseEnter = function () { + this.image = this.highlightImage; + this.changed(); +}; + +HandleMorph.prototype.mouseLeave = function () { + this.image = this.normalImage; + this.changed(); +}; + +// HandleMorph duplicating: + +HandleMorph.prototype.copyRecordingReferences = function (dict) { + // inherited, see comment in Morph + var c = HandleMorph.uber.copyRecordingReferences.call( + this, + dict + ); + if (c.target && dict[this.target]) { + c.target = (dict[this.target]); + } + return c; +}; + +// HandleMorph menu: + +HandleMorph.prototype.attach = function () { + var choices = this.overlappedMorphs(), + menu = new MenuMorph(this, 'choose target:'), + myself = this; + + choices.forEach(function (each) { + menu.addItem(each.toString().slice(0, 50), function () { + myself.isDraggable = false; + myself.target = each; + myself.drawNew(); + myself.noticesTransparentClick = true; + }); + }); + if (choices.length > 0) { + menu.popUpAtHand(this.world()); + } +}; + +// PenMorph //////////////////////////////////////////////////////////// + +// I am a simple LOGO-wise turtle. + +// PenMorph: referenced constructors + +var PenMorph; + +// PenMorph inherits from Morph: + +PenMorph.prototype = new Morph(); +PenMorph.prototype.constructor = PenMorph; +PenMorph.uber = Morph.prototype; + +// PenMorph instance creation: + +function PenMorph() { + this.init(); +} + +PenMorph.prototype.init = function () { + var size = MorphicPreferences.handleSize * 4; + + // additional properties: + this.isWarped = false; // internal optimization + this.heading = 0; + this.isDown = true; + this.size = 1; + this.wantsRedraw = false; + this.penPoint = 'tip'; // or 'center" + + HandleMorph.uber.init.call(this); + this.setExtent(new Point(size, size)); +}; + +// PenMorph updating - optimized for warping, i.e atomic recursion + +PenMorph.prototype.changed = function () { + if (this.isWarped === false) { + var w = this.root(); + if (w instanceof WorldMorph) { + w.broken.push(this.visibleBounds().spread()); + } + if (this.parent) { + this.parent.childChanged(this); + } + } +}; + +// PenMorph display: + +PenMorph.prototype.drawNew = function (facing) { +/* + my orientation can be overridden with the "facing" parameter to + implement Scratch-style rotation styles + +*/ + var context, start, dest, left, right, len, + direction = facing || this.heading; + + if (this.isWarped) { + this.wantsRedraw = true; + return; + } + this.image = newCanvas(this.extent()); + context = this.image.getContext('2d'); + len = this.width() / 2; + start = this.center().subtract(this.bounds.origin); + + if (this.penPoint === 'tip') { + dest = start.distanceAngle(len * 0.75, direction - 180); + left = start.distanceAngle(len, direction + 195); + right = start.distanceAngle(len, direction - 195); + } else { // 'middle' + dest = start.distanceAngle(len * 0.75, direction); + left = start.distanceAngle(len * 0.33, direction + 230); + right = start.distanceAngle(len * 0.33, direction - 230); + } + + context.fillStyle = this.color.toString(); + context.beginPath(); + + context.moveTo(start.x, start.y); + context.lineTo(left.x, left.y); + context.lineTo(dest.x, dest.y); + context.lineTo(right.x, right.y); + + context.closePath(); + context.strokeStyle = 'white'; + context.lineWidth = 3; + context.stroke(); + context.strokeStyle = 'black'; + context.lineWidth = 1; + context.stroke(); + context.fill(); + +}; + +// PenMorph access: + +PenMorph.prototype.setHeading = function (degrees) { + this.heading = parseFloat(degrees) % 360; + this.drawNew(); + this.changed(); +}; + +// PenMorph drawing: + +PenMorph.prototype.drawLine = function (start, dest) { + var context = this.parent.penTrails().getContext('2d'), + from = start.subtract(this.parent.bounds.origin), + to = dest.subtract(this.parent.bounds.origin); + if (this.isDown) { + context.lineWidth = this.size; + context.strokeStyle = this.color.toString(); + context.lineCap = 'round'; + context.lineJoin = 'round'; + context.beginPath(); + context.moveTo(from.x, from.y); + context.lineTo(to.x, to.y); + context.stroke(); + if (this.isWarped === false) { + this.world().broken.push( + start.rectangle(dest).expandBy( + Math.max(this.size / 2, 1) + ).intersect(this.parent.visibleBounds()).spread() + ); + } + } +}; + +// PenMorph turtle ops: + +PenMorph.prototype.turn = function (degrees) { + this.setHeading(this.heading + parseFloat(degrees)); +}; + +PenMorph.prototype.forward = function (steps) { + var start = this.center(), + dest, + dist = parseFloat(steps); + if (dist >= 0) { + dest = this.position().distanceAngle(dist, this.heading); + } else { + dest = this.position().distanceAngle( + Math.abs(dist), + (this.heading - 180) + ); + } + this.setPosition(dest); + this.drawLine(start, this.center()); +}; + +PenMorph.prototype.down = function () { + this.isDown = true; +}; + +PenMorph.prototype.up = function () { + this.isDown = false; +}; + +PenMorph.prototype.clear = function () { + this.parent.drawNew(); + this.parent.changed(); +}; + +// PenMorph optimization for atomic recursion: + +PenMorph.prototype.startWarp = function () { + this.wantsRedraw = false; + this.isWarped = true; +}; + +PenMorph.prototype.endWarp = function () { + this.isWarped = false; + if (this.wantsRedraw) { + this.drawNew(); + this.wantsRedraw = false; + } + this.parent.changed(); +}; + +PenMorph.prototype.warp = function (fun) { + this.startWarp(); + fun.call(this); + this.endWarp(); +}; + +PenMorph.prototype.warpOp = function (selector, argsArray) { + this.startWarp(); + this[selector].apply(this, argsArray); + this.endWarp(); +}; + +// PenMorph demo ops: +// try these with WARP eg.: this.warp(function () {tree(12, 120, 20)}) + +PenMorph.prototype.warpSierpinski = function (length, min) { + this.warpOp('sierpinski', [length, min]); +}; + +PenMorph.prototype.sierpinski = function (length, min) { + var i; + if (length > min) { + for (i = 0; i < 3; i += 1) { + this.sierpinski(length * 0.5, min); + this.turn(120); + this.forward(length); + } + } +}; + +PenMorph.prototype.warpTree = function (level, length, angle) { + this.warpOp('tree', [level, length, angle]); +}; + +PenMorph.prototype.tree = function (level, length, angle) { + if (level > 0) { + this.size = level; + this.forward(length); + this.turn(angle); + this.tree(level - 1, length * 0.75, angle); + this.turn(angle * -2); + this.tree(level - 1, length * 0.75, angle); + this.turn(angle); + this.forward(-length); + } +}; + +// ColorPaletteMorph /////////////////////////////////////////////////// + +var ColorPaletteMorph; + +// ColorPaletteMorph inherits from Morph: + +ColorPaletteMorph.prototype = new Morph(); +ColorPaletteMorph.prototype.constructor = ColorPaletteMorph; +ColorPaletteMorph.uber = Morph.prototype; + +// ColorPaletteMorph instance creation: + +function ColorPaletteMorph(target, sizePoint) { + this.init( + target || null, + sizePoint || new Point(80, 50) + ); +} + +ColorPaletteMorph.prototype.init = function (target, size) { + ColorPaletteMorph.uber.init.call(this); + this.target = target; + this.targetSetter = 'color'; + this.silentSetExtent(size); + this.choice = null; + this.drawNew(); +}; + +ColorPaletteMorph.prototype.drawNew = function () { + var context, ext, x, y, h, l; + + ext = this.extent(); + this.image = newCanvas(this.extent()); + context = this.image.getContext('2d'); + this.choice = new Color(); + for (x = 0; x <= ext.x; x += 1) { + h = 360 * x / ext.x; + for (y = 0; y <= ext.y; y += 1) { + l = 100 - (y / ext.y * 100); + context.fillStyle = 'hsl(' + h + ',100%,' + l + '%)'; + context.fillRect(x, y, 1, 1); + } + } +}; + +ColorPaletteMorph.prototype.mouseMove = function (pos) { + this.choice = this.getPixelColor(pos); + this.updateTarget(); +}; + +ColorPaletteMorph.prototype.mouseDownLeft = function (pos) { + this.choice = this.getPixelColor(pos); + this.updateTarget(); +}; + +ColorPaletteMorph.prototype.updateTarget = function () { + if (this.target instanceof Morph && this.choice !== null) { + if (this.target[this.targetSetter] instanceof Function) { + this.target[this.targetSetter](this.choice); + } else { + this.target[this.targetSetter] = this.choice; + this.target.drawNew(); + this.target.changed(); + } + } +}; + +// ColorPaletteMorph duplicating: + +ColorPaletteMorph.prototype.copyRecordingReferences = function (dict) { + // inherited, see comment in Morph + var c = ColorPaletteMorph.uber.copyRecordingReferences.call( + this, + dict + ); + if (c.target && dict[this.target]) { + c.target = (dict[this.target]); + } + return c; +}; + +// ColorPaletteMorph menu: + +ColorPaletteMorph.prototype.developersMenu = function () { + var menu = ColorPaletteMorph.uber.developersMenu.call(this); + menu.addLine(); + menu.addItem( + 'set target', + "setTarget", + 'choose another morph\nwhose color property\n will be' + + ' controlled by this one' + ); + return menu; +}; + +ColorPaletteMorph.prototype.setTarget = function () { + var choices = this.overlappedMorphs(), + menu = new MenuMorph(this, 'choose target:'), + myself = this; + + choices.push(this.world()); + choices.forEach(function (each) { + menu.addItem(each.toString().slice(0, 50), function () { + myself.target = each; + myself.setTargetSetter(); + }); + }); + if (choices.length === 1) { + this.target = choices[0]; + this.setTargetSetter(); + } else if (choices.length > 0) { + menu.popUpAtHand(this.world()); + } +}; + +ColorPaletteMorph.prototype.setTargetSetter = function () { + var choices = this.target.colorSetters(), + menu = new MenuMorph(this, 'choose target property:'), + myself = this; + + choices.forEach(function (each) { + menu.addItem(each, function () { + myself.targetSetter = each; + }); + }); + if (choices.length === 1) { + this.targetSetter = choices[0]; + } else if (choices.length > 0) { + menu.popUpAtHand(this.world()); + } +}; + +// GrayPaletteMorph /////////////////////////////////////////////////// + +var GrayPaletteMorph; + +// GrayPaletteMorph inherits from ColorPaletteMorph: + +GrayPaletteMorph.prototype = new ColorPaletteMorph(); +GrayPaletteMorph.prototype.constructor = GrayPaletteMorph; +GrayPaletteMorph.uber = ColorPaletteMorph.prototype; + +// GrayPaletteMorph instance creation: + +function GrayPaletteMorph(target, sizePoint) { + this.init( + target || null, + sizePoint || new Point(80, 10) + ); +} + +GrayPaletteMorph.prototype.drawNew = function () { + var context, ext, gradient; + + ext = this.extent(); + this.image = newCanvas(this.extent()); + context = this.image.getContext('2d'); + this.choice = new Color(); + gradient = context.createLinearGradient(0, 0, ext.x, ext.y); + gradient.addColorStop(0, 'black'); + gradient.addColorStop(1, 'white'); + context.fillStyle = gradient; + context.fillRect(0, 0, ext.x, ext.y); +}; + +// ColorPickerMorph /////////////////////////////////////////////////// + +// ColorPickerMorph inherits from Morph: + +ColorPickerMorph.prototype = new Morph(); +ColorPickerMorph.prototype.constructor = ColorPickerMorph; +ColorPickerMorph.uber = Morph.prototype; + +// ColorPickerMorph instance creation: + +function ColorPickerMorph(defaultColor) { + this.init(defaultColor || new Color(255, 255, 255)); +} + +ColorPickerMorph.prototype.init = function (defaultColor) { + this.choice = defaultColor; + ColorPickerMorph.uber.init.call(this); + this.color = new Color(255, 255, 255); + this.silentSetExtent(new Point(80, 80)); + this.drawNew(); +}; + +ColorPickerMorph.prototype.drawNew = function () { + ColorPickerMorph.uber.drawNew.call(this); + this.buildSubmorphs(); +}; + +ColorPickerMorph.prototype.buildSubmorphs = function () { + var cpal, gpal, x, y; + + this.children.forEach(function (child) { + child.destroy(); + }); + this.children = []; + this.feedback = new Morph(); + this.feedback.color = this.choice; + this.feedback.setExtent(new Point(20, 20)); + cpal = new ColorPaletteMorph( + this.feedback, + new Point(this.width(), 50) + ); + gpal = new GrayPaletteMorph( + this.feedback, + new Point(this.width(), 5) + ); + cpal.setPosition(this.bounds.origin); + this.add(cpal); + gpal.setPosition(cpal.bottomLeft()); + this.add(gpal); + x = (gpal.left() + + Math.floor((gpal.width() - this.feedback.width()) / 2)); + y = gpal.bottom() + Math.floor((this.bottom() - + gpal.bottom() - this.feedback.height()) / 2); + this.feedback.setPosition(new Point(x, y)); + this.add(this.feedback); +}; + +ColorPickerMorph.prototype.getChoice = function () { + return this.feedback.color; +}; + +ColorPickerMorph.prototype.rootForGrab = function () { + return this; +}; + +// BlinkerMorph //////////////////////////////////////////////////////// + +// can be used for text cursors + +var BlinkerMorph; + +// BlinkerMorph inherits from Morph: + +BlinkerMorph.prototype = new Morph(); +BlinkerMorph.prototype.constructor = BlinkerMorph; +BlinkerMorph.uber = Morph.prototype; + +// BlinkerMorph instance creation: + +function BlinkerMorph(rate) { + this.init(rate); +} + +BlinkerMorph.prototype.init = function (rate) { + BlinkerMorph.uber.init.call(this); + this.color = new Color(0, 0, 0); + this.fps = rate || 2; + this.drawNew(); +}; + +// BlinkerMorph stepping: + +BlinkerMorph.prototype.step = function () { + this.toggleVisibility(); +}; + +// CursorMorph ///////////////////////////////////////////////////////// + +// I am a String/Text editing widget + +// CursorMorph: referenced constructors + +var CursorMorph; + +// CursorMorph inherits from BlinkerMorph: + +CursorMorph.prototype = new BlinkerMorph(); +CursorMorph.prototype.constructor = CursorMorph; +CursorMorph.uber = BlinkerMorph.prototype; + +// CursorMorph preferences settings: + +CursorMorph.prototype.viewPadding = 1; + +// CursorMorph instance creation: + +function CursorMorph(aStringOrTextMorph) { + this.init(aStringOrTextMorph); +} + +CursorMorph.prototype.init = function (aStringOrTextMorph) { + var ls; + + // additional properties: + this.keyDownEventUsed = false; + this.target = aStringOrTextMorph; + this.originalContents = this.target.text; + this.originalAlignment = this.target.alignment; + this.slot = this.target.text.length; + CursorMorph.uber.init.call(this); + ls = fontHeight(this.target.fontSize); + this.setExtent(new Point(Math.max(Math.floor(ls / 20), 1), ls)); + this.drawNew(); + this.image.getContext('2d').font = this.target.font(); + if (this.target instanceof TextMorph && + (this.target.alignment !== 'left')) { + this.target.setAlignmentToLeft(); + } + this.gotoSlot(this.slot); +}; + +// CursorMorph event processing: + +CursorMorph.prototype.processKeyPress = function (event) { + // this.inspectKeyEvent(event); + if (this.keyDownEventUsed) { + this.keyDownEventUsed = false; + return null; + } + if ((event.keyCode === 40) || event.charCode === 40) { + this.insert('('); + return null; + } + if ((event.keyCode === 37) || event.charCode === 37) { + this.insert('%'); + return null; + } + if (event.keyCode) { // Opera doesn't support charCode + if (event.ctrlKey) { + this.ctrl(event.keyCode); + } else if (event.metaKey) { + this.cmd(event.keyCode); + } else { + this.insert( + String.fromCharCode(event.keyCode), + event.shiftKey + ); + } + } else if (event.charCode) { // all other browsers + if (event.ctrlKey) { + this.ctrl(event.charCode); + } else if (event.metaKey) { + this.cmd(event.keyCode); + } else { + this.insert( + String.fromCharCode(event.charCode), + event.shiftKey + ); + } + } + // notify target's parent of key event + this.target.escalateEvent('reactToKeystroke', event); +}; + +CursorMorph.prototype.processKeyDown = function (event) { + // this.inspectKeyEvent(event); + var shift = event.shiftKey; + this.keyDownEventUsed = false; + if (event.ctrlKey) { + this.ctrl(event.keyCode); + // notify target's parent of key event + this.target.escalateEvent('reactToKeystroke', event); + return; + } + if (event.metaKey) { + this.cmd(event.keyCode); + // notify target's parent of key event + this.target.escalateEvent('reactToKeystroke', event); + return; + } + + switch (event.keyCode) { + case 37: + this.goLeft(shift); + this.keyDownEventUsed = true; + break; + case 39: + this.goRight(shift); + this.keyDownEventUsed = true; + break; + case 38: + this.goUp(shift); + this.keyDownEventUsed = true; + break; + case 40: + this.goDown(shift); + this.keyDownEventUsed = true; + break; + case 36: + this.goHome(shift); + this.keyDownEventUsed = true; + break; + case 35: + this.goEnd(shift); + this.keyDownEventUsed = true; + break; + case 46: + this.deleteRight(); + this.keyDownEventUsed = true; + break; + case 8: + this.deleteLeft(); + this.keyDownEventUsed = true; + break; + case 13: + if (this.target instanceof StringMorph) { + this.accept(); + } else { + this.insert('\n'); + } + this.keyDownEventUsed = true; + break; + case 27: + this.cancel(); + this.keyDownEventUsed = true; + break; + default: + // this.inspectKeyEvent(event); + } + // notify target's parent of key event + this.target.escalateEvent('reactToKeystroke', event); +}; + +// CursorMorph navigation: + +/* +// original non-scrolling code, commented out in case we need to fall back: + +CursorMorph.prototype.gotoSlot = function (newSlot) { + this.setPosition(this.target.slotPosition(newSlot)); + this.slot = Math.max(newSlot, 0); +}; +*/ + +CursorMorph.prototype.gotoSlot = function (slot) { + var length = this.target.text.length, + pos = this.target.slotPosition(slot), + right, + left; + this.slot = slot < 0 ? 0 : slot > length ? length : slot; + if (this.parent && this.target.isScrollable) { + right = this.parent.right() - this.viewPadding; + left = this.parent.left() + this.viewPadding; + if (pos.x > right) { + this.target.setLeft(this.target.left() + right - pos.x); + pos.x = right; + } + if (pos.x < left) { + left = Math.min(this.parent.left(), left); + this.target.setLeft(this.target.left() + left - pos.x); + pos.x = left; + } + if (this.target.right() < right && + right - this.target.width() < left) { + pos.x += right - this.target.right(); + this.target.setRight(right); + } + } + this.show(); + this.setPosition(pos); + if (this.parent + && this.parent.parent instanceof ScrollFrameMorph + && this.target.isScrollable) { + this.parent.parent.scrollCursorIntoView(this); + } +}; + +CursorMorph.prototype.goLeft = function (shift) { + this.updateSelection(shift); + this.gotoSlot(this.slot - 1); + this.updateSelection(shift); +}; + +CursorMorph.prototype.goRight = function (shift, howMany) { + this.updateSelection(shift); + this.gotoSlot(this.slot + (howMany || 1)); + this.updateSelection(shift); +}; + +CursorMorph.prototype.goUp = function (shift) { + this.updateSelection(shift); + this.gotoSlot(this.target.upFrom(this.slot)); + this.updateSelection(shift); +}; + +CursorMorph.prototype.goDown = function (shift) { + this.updateSelection(shift); + this.gotoSlot(this.target.downFrom(this.slot)); + this.updateSelection(shift); +}; + +CursorMorph.prototype.goHome = function (shift) { + this.updateSelection(shift); + this.gotoSlot(this.target.startOfLine(this.slot)); + this.updateSelection(shift); +}; + +CursorMorph.prototype.goEnd = function (shift) { + this.updateSelection(shift); + this.gotoSlot(this.target.endOfLine(this.slot)); + this.updateSelection(shift); +}; + +CursorMorph.prototype.gotoPos = function (aPoint) { + this.gotoSlot(this.target.slotAt(aPoint)); + this.show(); +}; + +// CursorMorph selecting: + +CursorMorph.prototype.updateSelection = function (shift) { + if (shift) { + if (!this.target.endMark && !this.target.startMark) { + this.target.startMark = this.slot; + this.target.endMark = this.slot; + } else if (this.target.endMark !== this.slot) { + this.target.endMark = this.slot; + this.target.drawNew(); + this.target.changed(); + } + } else { + this.target.clearSelection(); + } +}; + +// CursorMorph editing: + +CursorMorph.prototype.accept = function () { + var world = this.root(); + if (world) { + world.stopEditing(); + } + this.escalateEvent('accept', null); +}; + +CursorMorph.prototype.cancel = function () { + var world = this.root(); + this.undo(); + if (world) { + world.stopEditing(); + } + this.escalateEvent('cancel', null); +}; + +CursorMorph.prototype.undo = function () { + this.target.text = this.originalContents; + this.target.changed(); + this.target.drawNew(); + this.target.changed(); + this.gotoSlot(0); +}; + +CursorMorph.prototype.insert = function (aChar, shiftKey) { + var text; + if (aChar === '\u0009') { + this.target.escalateEvent('reactToEdit', this.target); + if (shiftKey) { + return this.target.backTab(this.target); + } + return this.target.tab(this.target); + } + if (!this.target.isNumeric || + !isNaN(parseFloat(aChar)) || + contains(['-', '.'], aChar)) { + if (this.target.selection() !== '') { + this.gotoSlot(this.target.selectionStartSlot()); + this.target.deleteSelection(); + } + text = this.target.text; + text = text.slice(0, this.slot) + + aChar + + text.slice(this.slot); + this.target.text = text; + this.target.drawNew(); + this.target.changed(); + this.goRight(false, aChar.length); + } +}; + +CursorMorph.prototype.ctrl = function (aChar) { + if ((aChar === 97) || (aChar === 65)) { + this.target.selectAll(); + } else if (aChar === 90) { + this.undo(); + } else if (aChar === 123) { + this.insert('{'); + } else if (aChar === 125) { + this.insert('}'); + } else if (aChar === 91) { + this.insert('['); + } else if (aChar === 93) { + this.insert(']'); + } else if (aChar === 64) { + this.insert('@'); + } + +}; + +CursorMorph.prototype.cmd = function (aChar) { + if (aChar === 65) { + this.target.selectAll(); + } else if (aChar === 90) { + this.undo(); + } +}; + +CursorMorph.prototype.deleteRight = function () { + var text; + if (this.target.selection() !== '') { + this.gotoSlot(this.target.selectionStartSlot()); + this.target.deleteSelection(); + } else { + text = this.target.text; + this.target.changed(); + text = text.slice(0, this.slot) + text.slice(this.slot + 1); + this.target.text = text; + this.target.drawNew(); + } +}; + +CursorMorph.prototype.deleteLeft = function () { + var text; + if (this.target.selection()) { + this.gotoSlot(this.target.selectionStartSlot()); + return this.target.deleteSelection(); + } + text = this.target.text; + this.target.changed(); + this.target.text = text.substring(0, this.slot - 1) + + text.substr(this.slot); + this.target.drawNew(); + this.goLeft(); +}; + +// CursorMorph destroying: + +CursorMorph.prototype.destroy = function () { + if (this.target.alignment !== this.originalAlignment) { + this.target.alignment = this.originalAlignment; + this.target.drawNew(); + this.target.changed(); + } + CursorMorph.uber.destroy.call(this); +}; + +// CursorMorph utilities: + +CursorMorph.prototype.inspectKeyEvent = function (event) { + // private + this.inform( + 'Key pressed: ' + + String.fromCharCode(event.charCode) + + '\n------------------------' + + '\ncharCode: ' + + event.charCode.toString() + + '\nkeyCode: ' + + event.keyCode.toString() + + '\nshiftKey: ' + + event.shiftKey.toString() + + '\naltKey: ' + + event.altKey.toString() + + '\nctrlKey: ' + + event.ctrlKey.toString() + + '\ncmdKey: ' + + event.metaKey.toString() + ); +}; + +// BoxMorph //////////////////////////////////////////////////////////// + +// I can have an optionally rounded border + +var BoxMorph; + +// BoxMorph inherits from Morph: + +BoxMorph.prototype = new Morph(); +BoxMorph.prototype.constructor = BoxMorph; +BoxMorph.uber = Morph.prototype; + +// BoxMorph instance creation: + +function BoxMorph(edge, border, borderColor) { + this.init(edge, border, borderColor); +} + +BoxMorph.prototype.init = function (edge, border, borderColor) { + this.edge = edge || 4; + this.border = border || ((border === 0) ? 0 : 2); + this.borderColor = borderColor || new Color(); + BoxMorph.uber.init.call(this); +}; + +// BoxMorph drawing: + +BoxMorph.prototype.drawNew = function () { + var context; + + this.image = newCanvas(this.extent()); + context = this.image.getContext('2d'); + if ((this.edge === 0) && (this.border === 0)) { + BoxMorph.uber.drawNew.call(this); + return null; + } + context.fillStyle = this.color.toString(); + context.beginPath(); + this.outlinePath( + context, + Math.max(this.edge - this.border, 0), + this.border + ); + context.closePath(); + context.fill(); + if (this.border > 0) { + context.lineWidth = this.border; + context.strokeStyle = this.borderColor.toString(); + context.beginPath(); + this.outlinePath(context, this.edge, this.border / 2); + context.closePath(); + context.stroke(); + } +}; + +BoxMorph.prototype.outlinePath = function (context, radius, inset) { + var offset = radius + inset, + w = this.width(), + h = this.height(); + + // top left: + context.arc( + offset, + offset, + radius, + radians(-180), + radians(-90), + false + ); + // top right: + context.arc( + w - offset, + offset, + radius, + radians(-90), + radians(-0), + false + ); + // bottom right: + context.arc( + w - offset, + h - offset, + radius, + radians(0), + radians(90), + false + ); + // bottom left: + context.arc( + offset, + h - offset, + radius, + radians(90), + radians(180), + false + ); +}; + + +// BoxMorph menus: + +BoxMorph.prototype.developersMenu = function () { + var menu = BoxMorph.uber.developersMenu.call(this); + menu.addLine(); + menu.addItem( + "border width...", + function () { + this.prompt( + menu.title + '\nborder\nwidth:', + this.setBorderWidth, + this, + this.border.toString(), + null, + 0, + 100, + true + ); + }, + 'set the border\'s\nline size' + ); + menu.addItem( + "border color...", + function () { + this.pickColor( + menu.title + '\nborder color:', + this.setBorderColor, + this, + this.borderColor + ); + }, + 'set the border\'s\nline color' + ); + menu.addItem( + "corner size...", + function () { + this.prompt( + menu.title + '\ncorner\nsize:', + this.setCornerSize, + this, + this.edge.toString(), + null, + 0, + 100, + true + ); + }, + 'set the corner\'s\nradius' + ); + return menu; +}; + +BoxMorph.prototype.setBorderWidth = function (size) { + // for context menu demo purposes + var newSize; + if (typeof size === 'number') { + this.border = Math.max(size, 0); + } else { + newSize = parseFloat(size); + if (!isNaN(newSize)) { + this.border = Math.max(newSize, 0); + } + } + this.drawNew(); + this.changed(); +}; + +BoxMorph.prototype.setBorderColor = function (color) { + // for context menu demo purposes + if (color) { + this.borderColor = color; + this.drawNew(); + this.changed(); + } +}; + +BoxMorph.prototype.setCornerSize = function (size) { + // for context menu demo purposes + var newSize; + if (typeof size === 'number') { + this.edge = Math.max(size, 0); + } else { + newSize = parseFloat(size); + if (!isNaN(newSize)) { + this.edge = Math.max(newSize, 0); + } + } + this.drawNew(); + this.changed(); +}; + +BoxMorph.prototype.colorSetters = function () { + // for context menu demo purposes + return ['color', 'borderColor']; +}; + +BoxMorph.prototype.numericalSetters = function () { + // for context menu demo purposes + var list = BoxMorph.uber.numericalSetters.call(this); + list.push('setBorderWidth', 'setCornerSize'); + return list; +}; + +// SpeechBubbleMorph /////////////////////////////////////////////////// + +/* + I am a comic-style speech bubble that can display either a string, + a Morph, a Canvas or a toString() representation of anything else. + If I am invoked using popUp() I behave like a tool tip. +*/ + +// SpeechBubbleMorph: referenced constructors + +var SpeechBubbleMorph; + +// SpeechBubbleMorph inherits from BoxMorph: + +SpeechBubbleMorph.prototype = new BoxMorph(); +SpeechBubbleMorph.prototype.constructor = SpeechBubbleMorph; +SpeechBubbleMorph.uber = BoxMorph.prototype; + +// SpeechBubbleMorph instance creation: + +function SpeechBubbleMorph( + contents, + color, + edge, + border, + borderColor, + padding, + isThought +) { + this.init(contents, color, edge, border, borderColor, padding, isThought); +} + +SpeechBubbleMorph.prototype.init = function ( + contents, + color, + edge, + border, + borderColor, + padding, + isThought +) { + this.isPointingRight = true; // orientation of text + this.contents = contents || ''; + this.padding = padding || 0; // additional vertical pixels + this.isThought = isThought || false; // draw "think" bubble + this.isClickable = false; + SpeechBubbleMorph.uber.init.call( + this, + edge || 6, + border || ((border === 0) ? 0 : 1), + borderColor || new Color(140, 140, 140) + ); + this.color = color || new Color(230, 230, 230); + this.drawNew(); +}; + +// SpeechBubbleMorph invoking: + +SpeechBubbleMorph.prototype.popUp = function (world, pos, isClickable) { + this.drawNew(); + this.setPosition(pos.subtract(new Point(0, this.height()))); + this.addShadow(new Point(2, 2), 80); + this.keepWithin(world); + world.add(this); + this.changed(); + world.hand.destroyTemporaries(); + world.hand.temporaries.push(this); + + if (!isClickable) { + this.mouseEnter = function () { + this.destroy(); + }; + } else { + this.isClickable = true; + } +}; + +// SpeechBubbleMorph drawing: + +SpeechBubbleMorph.prototype.drawNew = function () { + // re-build my contents + if (this.contentsMorph) { + this.contentsMorph.destroy(); + } + if (this.contents instanceof Morph) { + this.contentsMorph = this.contents; + } else if (isString(this.contents)) { + this.contentsMorph = new TextMorph( + this.contents, + MorphicPreferences.bubbleHelpFontSize, + null, + false, + true, + 'center' + ); + } else if (this.contents instanceof HTMLCanvasElement) { + this.contentsMorph = new Morph(); + this.contentsMorph.silentSetWidth(this.contents.width); + this.contentsMorph.silentSetHeight(this.contents.height); + this.contentsMorph.image = this.contents; + } else { + this.contentsMorph = new TextMorph( + this.contents.toString(), + MorphicPreferences.bubbleHelpFontSize, + null, + false, + true, + 'center' + ); + } + this.add(this.contentsMorph); + + // adjust my layout + this.silentSetWidth(this.contentsMorph.width() + + (this.padding ? this.padding * 2 : this.edge * 2)); + this.silentSetHeight(this.contentsMorph.height() + + this.edge + + this.border * 2 + + this.padding * 2 + + 2); + + // draw my outline + SpeechBubbleMorph.uber.drawNew.call(this); + + // position my contents + this.contentsMorph.setPosition(this.position().add( + new Point( + this.padding || this.edge, + this.border + this.padding + 1 + ) + )); +}; + +SpeechBubbleMorph.prototype.outlinePath = function ( + context, + radius, + inset +) { + var offset = radius + inset, + w = this.width(), + h = this.height(), + rad; + + function circle(x, y, r) { + context.moveTo(x + r, y); + context.arc(x, y, r, radians(0), radians(360)); + } + + // top left: + context.arc( + offset, + offset, + radius, + radians(-180), + radians(-90), + false + ); + // top right: + context.arc( + w - offset, + offset, + radius, + radians(-90), + radians(-0), + false + ); + // bottom right: + context.arc( + w - offset, + h - offset - radius, + radius, + radians(0), + radians(90), + false + ); + if (!this.isThought) { // draw speech bubble hook + if (this.isPointingRight) { + context.lineTo( + offset + radius, + h - offset + ); + context.lineTo( + radius / 2 + inset, + h - inset + ); + } else { // pointing left + context.lineTo( + w - (radius / 2 + inset), + h - inset + ); + context.lineTo( + w - (offset + radius), + h - offset + ); + } + } + // bottom left: + context.arc( + offset, + h - offset - radius, + radius, + radians(90), + radians(180), + false + ); + if (this.isThought) { + // close large bubble: + context.lineTo( + inset, + offset + ); + // draw thought bubbles: + if (this.isPointingRight) { + // tip bubble: + rad = radius / 4; + circle(rad + inset, h - rad - inset, rad); + // middle bubble: + rad = radius / 3.2; + circle(rad * 2 + inset, h - rad - inset * 2, rad); + // top bubble: + rad = radius / 2.8; + circle(rad * 3 + inset * 2, h - rad - inset * 4, rad); + } else { // pointing left + // tip bubble: + rad = radius / 4; + circle(w - (rad + inset), h - rad - inset, rad); + // middle bubble: + rad = radius / 3.2; + circle(w - (rad * 2 + inset), h - rad - inset * 2, rad); + // top bubble: + rad = radius / 2.8; + circle(w - (rad * 3 + inset * 2), h - rad - inset * 4, rad); + } + } +}; + +// SpeechBubbleMorph shadow + +/* + only take the 'plain' image, so the box rounding and the + shadow doesn't become conflicted by embedded scrolling panes +*/ + +SpeechBubbleMorph.prototype.shadowImage = function (off, color) { + // fallback for Windows Chrome-Shadow bug + var fb, img, outline, sha, ctx, + offset = off || new Point(7, 7), + clr = color || new Color(0, 0, 0); + fb = this.extent(); + img = this.image; + outline = newCanvas(fb); + ctx = outline.getContext('2d'); + ctx.drawImage(img, 0, 0); + ctx.globalCompositeOperation = 'destination-out'; + ctx.drawImage( + img, + -offset.x, + -offset.y + ); + sha = newCanvas(fb); + ctx = sha.getContext('2d'); + ctx.drawImage(outline, 0, 0); + ctx.globalCompositeOperation = 'source-atop'; + ctx.fillStyle = clr.toString(); + ctx.fillRect(0, 0, fb.x, fb.y); + return sha; +}; + +SpeechBubbleMorph.prototype.shadowImageBlurred = function (off, color) { + var fb, img, sha, ctx, + offset = off || new Point(7, 7), + blur = this.shadowBlur, + clr = color || new Color(0, 0, 0); + fb = this.extent().add(blur * 2); + img = this.image; + sha = newCanvas(fb); + ctx = sha.getContext('2d'); + ctx.shadowOffsetX = offset.x; + ctx.shadowOffsetY = offset.y; + ctx.shadowBlur = blur; + ctx.shadowColor = clr.toString(); + ctx.drawImage( + img, + blur - offset.x, + blur - offset.y + ); + ctx.shadowOffsetX = 0; + ctx.shadowOffsetY = 0; + ctx.shadowBlur = 0; + ctx.globalCompositeOperation = 'destination-out'; + ctx.drawImage( + img, + blur - offset.x, + blur - offset.y + ); + return sha; +}; + +// SpeechBubbleMorph resizing + +SpeechBubbleMorph.prototype.fixLayout = function () { + this.removeShadow(); + this.drawNew(); + this.addShadow(new Point(2, 2), 80); +}; + +// CircleBoxMorph ////////////////////////////////////////////////////// + +// I can be used for sliders + +var CircleBoxMorph; + +// CircleBoxMorph inherits from Morph: + +CircleBoxMorph.prototype = new Morph(); +CircleBoxMorph.prototype.constructor = CircleBoxMorph; +CircleBoxMorph.uber = Morph.prototype; + +function CircleBoxMorph(orientation) { + this.init(orientation || 'vertical'); +} + +CircleBoxMorph.prototype.init = function (orientation) { + CircleBoxMorph.uber.init.call(this); + this.orientation = orientation; + this.autoOrient = true; + this.setExtent(new Point(20, 100)); +}; + +CircleBoxMorph.prototype.autoOrientation = function () { + if (this.height() > this.width()) { + this.orientation = 'vertical'; + } else { + this.orientation = 'horizontal'; + } +}; + +CircleBoxMorph.prototype.drawNew = function () { + var radius, center1, center2, rect, points, x, y, + context, ext, + myself = this; + + if (this.autoOrient) { + this.autoOrientation(); + } + this.image = newCanvas(this.extent()); + context = this.image.getContext('2d'); + + if (this.orientation === 'vertical') { + radius = this.width() / 2; + x = this.center().x; + center1 = new Point(x, this.top() + radius); + center2 = new Point(x, this.bottom() - radius); + rect = this.bounds.origin.add(new Point(0, radius)).corner( + this.bounds.corner.subtract(new Point(0, radius)) + ); + } else { + radius = this.height() / 2; + y = this.center().y; + center1 = new Point(this.left() + radius, y); + center2 = new Point(this.right() - radius, y); + rect = this.bounds.origin.add(new Point(radius, 0)).corner( + this.bounds.corner.subtract(new Point(radius, 0)) + ); + } + points = [ center1.subtract(this.bounds.origin), + center2.subtract(this.bounds.origin)]; + points.forEach(function (center) { + context.fillStyle = myself.color.toString(); + context.beginPath(); + context.arc( + center.x, + center.y, + radius, + 0, + 2 * Math.PI, + false + ); + context.closePath(); + context.fill(); + }); + rect = rect.translateBy(this.bounds.origin.neg()); + ext = rect.extent(); + if (ext.x > 0 && ext.y > 0) { + context.fillRect( + rect.origin.x, + rect.origin.y, + rect.width(), + rect.height() + ); + } +}; + +// CircleBoxMorph menu: + +CircleBoxMorph.prototype.developersMenu = function () { + var menu = CircleBoxMorph.uber.developersMenu.call(this); + menu.addLine(); + if (this.orientation === 'vertical') { + menu.addItem( + "horizontal...", + 'toggleOrientation', + 'toggle the\norientation' + ); + } else { + menu.addItem( + "vertical...", + 'toggleOrientation', + 'toggle the\norientation' + ); + } + return menu; +}; + +CircleBoxMorph.prototype.toggleOrientation = function () { + var center = this.center(); + this.changed(); + if (this.orientation === 'vertical') { + this.orientation = 'horizontal'; + } else { + this.orientation = 'vertical'; + } + this.silentSetExtent(new Point(this.height(), this.width())); + this.setCenter(center); + this.drawNew(); + this.changed(); +}; + +// SliderButtonMorph /////////////////////////////////////////////////// + +var SliderButtonMorph; + +// SliderButtonMorph inherits from CircleBoxMorph: + +SliderButtonMorph.prototype = new CircleBoxMorph(); +SliderButtonMorph.prototype.constructor = SliderButtonMorph; +SliderButtonMorph.uber = CircleBoxMorph.prototype; + +function SliderButtonMorph(orientation) { + this.init(orientation); +} + +SliderButtonMorph.prototype.init = function (orientation) { + this.color = new Color(80, 80, 80); + this.highlightColor = new Color(90, 90, 140); + this.pressColor = new Color(80, 80, 160); + this.is3D = false; + this.hasMiddleDip = true; + SliderButtonMorph.uber.init.call(this, orientation); +}; + +SliderButtonMorph.prototype.autoOrientation = function () { + nop(); +}; + +SliderButtonMorph.prototype.drawNew = function () { + var colorBak = this.color.copy(); + + SliderButtonMorph.uber.drawNew.call(this); + if (this.is3D || !MorphicPreferences.isFlat) { + this.drawEdges(); + } + this.normalImage = this.image; + + this.color = this.highlightColor.copy(); + SliderButtonMorph.uber.drawNew.call(this); + if (this.is3D || !MorphicPreferences.isFlat) { + this.drawEdges(); + } + this.highlightImage = this.image; + + this.color = this.pressColor.copy(); + SliderButtonMorph.uber.drawNew.call(this); + if (this.is3D || !MorphicPreferences.isFlat) { + this.drawEdges(); + } + this.pressImage = this.image; + + this.color = colorBak; + this.image = this.normalImage; + +}; + +SliderButtonMorph.prototype.drawEdges = function () { + var context = this.image.getContext('2d'), + gradient, + radius, + w = this.width(), + h = this.height(); + + context.lineJoin = 'round'; + context.lineCap = 'round'; + + if (this.orientation === 'vertical') { + context.lineWidth = w / 3; + gradient = context.createLinearGradient( + 0, + 0, + context.lineWidth, + 0 + ); + gradient.addColorStop(0, 'white'); + gradient.addColorStop(1, this.color.toString()); + + context.strokeStyle = gradient; + context.beginPath(); + context.moveTo(context.lineWidth * 0.5, w / 2); + context.lineTo(context.lineWidth * 0.5, h - w / 2); + context.stroke(); + + gradient = context.createLinearGradient( + w - context.lineWidth, + 0, + w, + 0 + ); + gradient.addColorStop(0, this.color.toString()); + gradient.addColorStop(1, 'black'); + + context.strokeStyle = gradient; + context.beginPath(); + context.moveTo(w - context.lineWidth * 0.5, w / 2); + context.lineTo(w - context.lineWidth * 0.5, h - w / 2); + context.stroke(); + + if (this.hasMiddleDip) { + gradient = context.createLinearGradient( + context.lineWidth, + 0, + w - context.lineWidth, + 0 + ); + + radius = w / 4; + gradient.addColorStop(0, 'black'); + gradient.addColorStop(0.35, this.color.toString()); + gradient.addColorStop(0.65, this.color.toString()); + gradient.addColorStop(1, 'white'); + + context.fillStyle = gradient; + context.beginPath(); + context.arc( + w / 2, + h / 2, + radius, + radians(0), + radians(360), + false + ); + context.closePath(); + context.fill(); + } + } else if (this.orientation === 'horizontal') { + context.lineWidth = h / 3; + gradient = context.createLinearGradient( + 0, + 0, + 0, + context.lineWidth + ); + gradient.addColorStop(0, 'white'); + gradient.addColorStop(1, this.color.toString()); + + context.strokeStyle = gradient; + context.beginPath(); + context.moveTo(h / 2, context.lineWidth * 0.5); + context.lineTo(w - h / 2, context.lineWidth * 0.5); + context.stroke(); + + gradient = context.createLinearGradient( + 0, + h - context.lineWidth, + 0, + h + ); + gradient.addColorStop(0, this.color.toString()); + gradient.addColorStop(1, 'black'); + + context.strokeStyle = gradient; + context.beginPath(); + context.moveTo(h / 2, h - context.lineWidth * 0.5); + context.lineTo(w - h / 2, h - context.lineWidth * 0.5); + context.stroke(); + + if (this.hasMiddleDip) { + gradient = context.createLinearGradient( + 0, + context.lineWidth, + 0, + h - context.lineWidth + ); + + radius = h / 4; + gradient.addColorStop(0, 'black'); + gradient.addColorStop(0.35, this.color.toString()); + gradient.addColorStop(0.65, this.color.toString()); + gradient.addColorStop(1, 'white'); + + context.fillStyle = gradient; + context.beginPath(); + context.arc( + this.width() / 2, + this.height() / 2, + radius, + radians(0), + radians(360), + false + ); + context.closePath(); + context.fill(); + } + } +}; + +//SliderButtonMorph events: + +SliderButtonMorph.prototype.mouseEnter = function () { + this.image = this.highlightImage; + this.changed(); +}; + +SliderButtonMorph.prototype.mouseLeave = function () { + this.image = this.normalImage; + this.changed(); +}; + +SliderButtonMorph.prototype.mouseDownLeft = function (pos) { + this.image = this.pressImage; + this.changed(); + this.escalateEvent('mouseDownLeft', pos); +}; + +SliderButtonMorph.prototype.mouseClickLeft = function () { + this.image = this.highlightImage; + this.changed(); +}; + +SliderButtonMorph.prototype.mouseMove = function () { + // prevent my parent from getting picked up + nop(); +}; + +// SliderMorph /////////////////////////////////////////////////// + +// SliderMorph inherits from CircleBoxMorph: + +SliderMorph.prototype = new CircleBoxMorph(); +SliderMorph.prototype.constructor = SliderMorph; +SliderMorph.uber = CircleBoxMorph.prototype; + +function SliderMorph(start, stop, value, size, orientation, color) { + this.init( + start || 1, + stop || 100, + value || 50, + size || 10, + orientation || 'vertical', + color + ); +} + +SliderMorph.prototype.init = function ( + start, + stop, + value, + size, + orientation, + color +) { + this.target = null; + this.action = null; + this.start = start; + this.stop = stop; + this.value = value; + this.size = size; + this.offset = null; + this.button = new SliderButtonMorph(); + this.button.isDraggable = false; + this.button.color = new Color(200, 200, 200); + this.button.highlightColor = new Color(210, 210, 255); + this.button.pressColor = new Color(180, 180, 255); + SliderMorph.uber.init.call(this, orientation); + this.add(this.button); + this.alpha = 0.3; + this.color = color || new Color(0, 0, 0); + this.setExtent(new Point(20, 100)); + // this.drawNew(); +}; + +SliderMorph.prototype.autoOrientation = function () { + nop(); +}; + +SliderMorph.prototype.rangeSize = function () { + return this.stop - this.start; +}; + +SliderMorph.prototype.ratio = function () { + return this.size / this.rangeSize(); +}; + +SliderMorph.prototype.unitSize = function () { + if (this.orientation === 'vertical') { + return (this.height() - this.button.height()) / + this.rangeSize(); + } + return (this.width() - this.button.width()) / + this.rangeSize(); +}; + +SliderMorph.prototype.drawNew = function () { + var bw, bh, posX, posY; + + SliderMorph.uber.drawNew.call(this); + this.button.orientation = this.orientation; + if (this.orientation === 'vertical') { + bw = this.width() - 2; + bh = Math.max(bw, Math.round(this.height() * this.ratio())); + this.button.silentSetExtent(new Point(bw, bh)); + posX = 1; + posY = Math.min( + Math.round((this.value - this.start) * this.unitSize()), + this.height() - this.button.height() + ); + } else { + bh = this.height() - 2; + bw = Math.max(bh, Math.round(this.width() * this.ratio())); + this.button.silentSetExtent(new Point(bw, bh)); + posY = 1; + posX = Math.min( + Math.round((this.value - this.start) * this.unitSize()), + this.width() - this.button.width() + ); + } + this.button.setPosition( + new Point(posX, posY).add(this.bounds.origin) + ); + this.button.drawNew(); + this.button.changed(); +}; + +SliderMorph.prototype.updateValue = function () { + var relPos; + if (this.orientation === 'vertical') { + relPos = this.button.top() - this.top(); + } else { + relPos = this.button.left() - this.left(); + } + this.value = Math.round(relPos / this.unitSize() + this.start); + this.updateTarget(); +}; + +SliderMorph.prototype.updateTarget = function () { + if (this.action) { + if (typeof this.action === 'function') { + this.action.call(this.target, this.value); + } else { // assume it's a String + this.target[this.action](this.value); + } + } +}; + +// SliderMorph duplicating: + +SliderMorph.prototype.copyRecordingReferences = function (dict) { + // inherited, see comment in Morph + var c = SliderMorph.uber.copyRecordingReferences.call( + this, + dict + ); + if (c.target && dict[this.target]) { + c.target = (dict[this.target]); + } + if (c.button && dict[this.button]) { + c.button = (dict[this.button]); + } + return c; +}; + +// SliderMorph menu: + +SliderMorph.prototype.developersMenu = function () { + var menu = SliderMorph.uber.developersMenu.call(this); + menu.addItem( + "show value...", + 'showValue', + 'display a dialog box\nshowing the selected number' + ); + menu.addItem( + "floor...", + function () { + this.prompt( + menu.title + '\nfloor:', + this.setStart, + this, + this.start.toString(), + null, + 0, + this.stop - this.size, + true + ); + }, + 'set the minimum value\nwhich can be selected' + ); + menu.addItem( + "ceiling...", + function () { + this.prompt( + menu.title + '\nceiling:', + this.setStop, + this, + this.stop.toString(), + null, + this.start + this.size, + this.size * 100, + true + ); + }, + 'set the maximum value\nwhich can be selected' + ); + menu.addItem( + "button size...", + function () { + this.prompt( + menu.title + '\nbutton size:', + this.setSize, + this, + this.size.toString(), + null, + 1, + this.stop - this.start, + true + ); + }, + 'set the range\ncovered by\nthe slider button' + ); + menu.addLine(); + menu.addItem( + 'set target', + "setTarget", + 'select another morph\nwhose numerical property\nwill be ' + + 'controlled by this one' + ); + return menu; +}; + +SliderMorph.prototype.showValue = function () { + this.inform(this.value); +}; + +SliderMorph.prototype.userSetStart = function (num) { + // for context menu demo purposes + this.start = Math.max(num, this.stop); +}; + +SliderMorph.prototype.setStart = function (num) { + // for context menu demo purposes + var newStart; + if (typeof num === 'number') { + this.start = Math.min( + Math.max(num, 0), + this.stop - this.size + ); + } else { + newStart = parseFloat(num); + if (!isNaN(newStart)) { + this.start = Math.min( + Math.max(newStart, 0), + this.stop - this.size + ); + } + } + this.value = Math.max(this.value, this.start); + this.updateTarget(); + this.drawNew(); + this.changed(); +}; + +SliderMorph.prototype.setStop = function (num) { + // for context menu demo purposes + var newStop; + if (typeof num === 'number') { + this.stop = Math.max(num, this.start + this.size); + } else { + newStop = parseFloat(num); + if (!isNaN(newStop)) { + this.stop = Math.max(newStop, this.start + this.size); + } + } + this.value = Math.min(this.value, this.stop); + this.updateTarget(); + this.drawNew(); + this.changed(); +}; + +SliderMorph.prototype.setSize = function (num) { + // for context menu demo purposes + var newSize; + if (typeof num === 'number') { + this.size = Math.min( + Math.max(num, 1), + this.stop - this.start + ); + } else { + newSize = parseFloat(num); + if (!isNaN(newSize)) { + this.size = Math.min( + Math.max(newSize, 1), + this.stop - this.start + ); + } + } + this.value = Math.min(this.value, this.stop - this.size); + this.updateTarget(); + this.drawNew(); + this.changed(); +}; + +SliderMorph.prototype.setTarget = function () { + var choices = this.overlappedMorphs(), + menu = new MenuMorph(this, 'choose target:'), + myself = this; + + choices.push(this.world()); + choices.forEach(function (each) { + menu.addItem(each.toString().slice(0, 50), function () { + myself.target = each; + myself.setTargetSetter(); + }); + }); + if (choices.length === 1) { + this.target = choices[0]; + this.setTargetSetter(); + } else if (choices.length > 0) { + menu.popUpAtHand(this.world()); + } +}; + +SliderMorph.prototype.setTargetSetter = function () { + var choices = this.target.numericalSetters(), + menu = new MenuMorph(this, 'choose target property:'), + myself = this; + + choices.forEach(function (each) { + menu.addItem(each, function () { + myself.action = each; + }); + }); + if (choices.length === 1) { + this.action = choices[0]; + } else if (choices.length > 0) { + menu.popUpAtHand(this.world()); + } +}; + +SliderMorph.prototype.numericalSetters = function () { + // for context menu demo purposes + var list = SliderMorph.uber.numericalSetters.call(this); + list.push('setStart', 'setStop', 'setSize'); + return list; +}; + +// SliderMorph stepping: + +SliderMorph.prototype.step = null; + +SliderMorph.prototype.mouseDownLeft = function (pos) { + var world, myself = this; + + if (!this.button.bounds.containsPoint(pos)) { + this.offset = new Point(); // return null; + } else { + this.offset = pos.subtract(this.button.bounds.origin); + } + world = this.root(); + this.step = function () { + var mousePos, newX, newY; + if (world.hand.mouseButton) { + mousePos = world.hand.bounds.origin; + if (myself.orientation === 'vertical') { + newX = myself.button.bounds.origin.x; + newY = Math.max( + Math.min( + mousePos.y - myself.offset.y, + myself.bottom() - myself.button.height() + ), + myself.top() + ); + } else { + newY = myself.button.bounds.origin.y; + newX = Math.max( + Math.min( + mousePos.x - myself.offset.x, + myself.right() - myself.button.width() + ), + myself.left() + ); + } + myself.button.setPosition(new Point(newX, newY)); + myself.updateValue(); + } else { + this.step = null; + } + }; +}; + +// MouseSensorMorph //////////////////////////////////////////////////// + +// for demo and debuggin purposes only, to be removed later + +var MouseSensorMorph; + +// MouseSensorMorph inherits from BoxMorph: + +MouseSensorMorph.prototype = new BoxMorph(); +MouseSensorMorph.prototype.constructor = MouseSensorMorph; +MouseSensorMorph.uber = BoxMorph.prototype; + +// MouseSensorMorph instance creation: + +function MouseSensorMorph(edge, border, borderColor) { + this.init(edge, border, borderColor); +} + +MouseSensorMorph.prototype.init = function (edge, border, borderColor) { + MouseSensorMorph.uber.init.call(this); + this.edge = edge || 4; + this.border = border || 2; + this.color = new Color(255, 255, 255); + this.borderColor = borderColor || new Color(); + this.isTouched = false; + this.upStep = 0.05; + this.downStep = 0.02; + this.noticesTransparentClick = false; + this.drawNew(); +}; + +MouseSensorMorph.prototype.touch = function () { + var myself = this; + if (!this.isTouched) { + this.isTouched = true; + this.alpha = 0.6; + + this.step = function () { + if (myself.isTouched) { + if (myself.alpha < 1) { + myself.alpha = myself.alpha + myself.upStep; + } + } else if (myself.alpha > (myself.downStep)) { + myself.alpha = myself.alpha - myself.downStep; + } else { + myself.alpha = 0; + myself.step = null; + } + myself.changed(); + }; + } +}; + +MouseSensorMorph.prototype.unTouch = function () { + this.isTouched = false; +}; + +MouseSensorMorph.prototype.mouseEnter = function () { + this.touch(); +}; + +MouseSensorMorph.prototype.mouseLeave = function () { + this.unTouch(); +}; + +MouseSensorMorph.prototype.mouseDownLeft = function () { + this.touch(); +}; + +MouseSensorMorph.prototype.mouseClickLeft = function () { + this.unTouch(); +}; + +// InspectorMorph ////////////////////////////////////////////////////// + +// InspectorMorph: referenced constructors + +var ListMorph; +var TriggerMorph; + +// InspectorMorph inherits from BoxMorph: + +InspectorMorph.prototype = new BoxMorph(); +InspectorMorph.prototype.constructor = InspectorMorph; +InspectorMorph.uber = BoxMorph.prototype; + +// InspectorMorph instance creation: + +function InspectorMorph(target) { + this.init(target); +} + +InspectorMorph.prototype.init = function (target) { + // additional properties: + this.target = target; + this.currentProperty = null; + this.showing = 'attributes'; + this.markOwnProperties = false; + + // initialize inherited properties: + InspectorMorph.uber.init.call(this); + + // override inherited properties: + this.silentSetExtent( + new Point( + MorphicPreferences.handleSize * 20, + MorphicPreferences.handleSize * 20 * 2 / 3 + ) + ); + this.isDraggable = true; + this.border = 1; + this.edge = MorphicPreferences.isFlat ? 1 : 5; + this.color = new Color(60, 60, 60); + this.borderColor = new Color(95, 95, 95); + this.drawNew(); + + // panes: + this.label = null; + this.list = null; + this.detail = null; + this.work = null; + this.buttonInspect = null; + this.buttonClose = null; + this.buttonSubset = null; + this.buttonEdit = null; + this.resizer = null; + + if (this.target) { + this.buildPanes(); + } +}; + +InspectorMorph.prototype.setTarget = function (target) { + this.target = target; + this.currentProperty = null; + this.buildPanes(); +}; + +InspectorMorph.prototype.buildPanes = function () { + var attribs = [], property, myself = this, ctrl, ev, doubleClickAction; + + // remove existing panes + this.children.forEach(function (m) { + if (m !== this.work) { // keep work pane around + m.destroy(); + } + }); + this.children = []; + + // label + this.label = new TextMorph(this.target.toString()); + this.label.fontSize = MorphicPreferences.menuFontSize; + this.label.isBold = true; + this.label.color = new Color(255, 255, 255); + this.label.drawNew(); + this.add(this.label); + + // properties list + for (property in this.target) { + if (property) { // dummy condition, to be refined + attribs.push(property); + } + } + if (this.showing === 'attributes') { + attribs = attribs.filter(function (prop) { + return typeof myself.target[prop] !== 'function'; + }); + } else if (this.showing === 'methods') { + attribs = attribs.filter(function (prop) { + return typeof myself.target[prop] === 'function'; + }); + } // otherwise show all properties + + doubleClickAction = function () { + var world, inspector; + if (!isObject(myself.currentProperty)) {return; } + world = myself.world(); + inspector = new InspectorMorph( + myself.currentProperty + ); + inspector.setPosition(world.hand.position()); + inspector.keepWithin(world); + world.add(inspector); + inspector.changed(); + }; + + this.list = new ListMorph( + this.target instanceof Array ? attribs : attribs.sort(), + null, // label getter + this.markOwnProperties ? + [ // format list + [ // format element: [color, predicate(element] + new Color(0, 0, 180), + function (element) { + return Object.prototype.hasOwnProperty.call( + myself.target, + element + ); + } + ] + ] + : null, + doubleClickAction + ); + + this.list.action = function (selected) { + var val, txt, cnts; + if (selected === undefined) {return; } + val = myself.target[selected]; + myself.currentProperty = val; + if (val === null) { + txt = 'NULL'; + } else if (isString(val)) { + txt = val; + } else { + txt = val.toString(); + } + cnts = new TextMorph(txt); + cnts.isEditable = true; + cnts.enableSelecting(); + cnts.setReceiver(myself.target); + myself.detail.setContents(cnts); + }; + + this.list.hBar.alpha = 0.6; + this.list.vBar.alpha = 0.6; + this.list.contents.step = null; + this.add(this.list); + + // details pane + this.detail = new ScrollFrameMorph(); + this.detail.acceptsDrops = false; + this.detail.contents.acceptsDrops = false; + this.detail.isTextLineWrapping = true; + this.detail.color = new Color(255, 255, 255); + this.detail.hBar.alpha = 0.6; + this.detail.vBar.alpha = 0.6; + ctrl = new TextMorph(''); + ctrl.isEditable = true; + ctrl.enableSelecting(); + ctrl.setReceiver(this.target); + this.detail.setContents(ctrl); + this.add(this.detail); + + // work ('evaluation') pane + // don't refresh the work pane if it already exists + if (this.work === null) { + this.work = new ScrollFrameMorph(); + this.work.acceptsDrops = false; + this.work.contents.acceptsDrops = false; + this.work.isTextLineWrapping = true; + this.work.color = new Color(255, 255, 255); + this.work.hBar.alpha = 0.6; + this.work.vBar.alpha = 0.6; + ev = new TextMorph(''); + ev.isEditable = true; + ev.enableSelecting(); + ev.setReceiver(this.target); + this.work.setContents(ev); + } + this.add(this.work); + + // properties button + this.buttonSubset = new TriggerMorph(); + this.buttonSubset.labelString = 'show...'; + this.buttonSubset.action = function () { + var menu; + menu = new MenuMorph(); + menu.addItem( + 'attributes', + function () { + myself.showing = 'attributes'; + myself.buildPanes(); + } + ); + menu.addItem( + 'methods', + function () { + myself.showing = 'methods'; + myself.buildPanes(); + } + ); + menu.addItem( + 'all', + function () { + myself.showing = 'all'; + myself.buildPanes(); + } + ); + menu.addLine(); + menu.addItem( + (myself.markOwnProperties ? + 'un-mark own' : 'mark own'), + function () { + myself.markOwnProperties = !myself.markOwnProperties; + myself.buildPanes(); + }, + 'highlight\n\'own\' properties' + ); + menu.popUpAtHand(myself.world()); + }; + this.add(this.buttonSubset); + + // inspect button + this.buttonInspect = new TriggerMorph(); + this.buttonInspect.labelString = 'inspect...'; + this.buttonInspect.action = function () { + var menu, world, inspector; + if (isObject(myself.currentProperty)) { + menu = new MenuMorph(); + menu.addItem( + 'in new inspector...', + function () { + world = myself.world(); + inspector = new InspectorMorph( + myself.currentProperty + ); + inspector.setPosition(world.hand.position()); + inspector.keepWithin(world); + world.add(inspector); + inspector.changed(); + } + ); + menu.addItem( + 'here...', + function () { + myself.setTarget(myself.currentProperty); + } + ); + menu.popUpAtHand(myself.world()); + } else { + myself.inform( + (myself.currentProperty === null ? + 'null' : typeof myself.currentProperty) + + '\nis not inspectable' + ); + } + }; + this.add(this.buttonInspect); + + // edit button + + this.buttonEdit = new TriggerMorph(); + this.buttonEdit.labelString = 'edit...'; + this.buttonEdit.action = function () { + var menu; + menu = new MenuMorph(myself); + menu.addItem("save", 'save', 'accept changes'); + menu.addLine(); + menu.addItem("add property...", 'addProperty'); + menu.addItem("rename...", 'renameProperty'); + menu.addItem("remove...", 'removeProperty'); + menu.popUpAtHand(myself.world()); + }; + this.add(this.buttonEdit); + + // close button + this.buttonClose = new TriggerMorph(); + this.buttonClose.labelString = 'close'; + this.buttonClose.action = function () { + myself.destroy(); + }; + this.add(this.buttonClose); + + // resizer + this.resizer = new HandleMorph( + this, + 150, + 100, + this.edge, + this.edge + ); + + // update layout + this.fixLayout(); +}; + +InspectorMorph.prototype.fixLayout = function () { + var x, y, r, b, w, h; + + Morph.prototype.trackChanges = false; + + // label + x = this.left() + this.edge; + y = this.top() + this.edge; + r = this.right() - this.edge; + w = r - x; + this.label.setPosition(new Point(x, y)); + this.label.setWidth(w); + if (this.label.height() > (this.height() - 50)) { + this.silentSetHeight(this.label.height() + 50); + this.drawNew(); + this.changed(); + this.resizer.drawNew(); + } + + // list + y = this.label.bottom() + 2; + w = Math.min( + Math.floor(this.width() / 3), + this.list.listContents.width() + ); + + w -= this.edge; + b = this.bottom() - (2 * this.edge) - + MorphicPreferences.handleSize; + h = b - y; + this.list.setPosition(new Point(x, y)); + this.list.setExtent(new Point(w, h)); + + // detail + x = this.list.right() + this.edge; + r = this.right() - this.edge; + w = r - x; + this.detail.setPosition(new Point(x, y)); + this.detail.setExtent(new Point(w, (h * 2 / 3) - this.edge)); + + // work + y = this.detail.bottom() + this.edge; + this.work.setPosition(new Point(x, y)); + this.work.setExtent(new Point(w, h / 3)); + + // properties button + x = this.list.left(); + y = this.list.bottom() + this.edge; + w = this.list.width(); + h = MorphicPreferences.handleSize; + this.buttonSubset.setPosition(new Point(x, y)); + this.buttonSubset.setExtent(new Point(w, h)); + + // inspect button + x = this.detail.left(); + w = this.detail.width() - this.edge - + MorphicPreferences.handleSize; + w = w / 3 - this.edge / 3; + this.buttonInspect.setPosition(new Point(x, y)); + this.buttonInspect.setExtent(new Point(w, h)); + + // edit button + x = this.buttonInspect.right() + this.edge; + this.buttonEdit.setPosition(new Point(x, y)); + this.buttonEdit.setExtent(new Point(w, h)); + + // close button + x = this.buttonEdit.right() + this.edge; + r = this.detail.right() - this.edge - + MorphicPreferences.handleSize; + w = r - x; + this.buttonClose.setPosition(new Point(x, y)); + this.buttonClose.setExtent(new Point(w, h)); + + Morph.prototype.trackChanges = true; + this.changed(); + +}; + +InspectorMorph.prototype.setExtent = function (aPoint) { + InspectorMorph.uber.setExtent.call(this, aPoint); + this.fixLayout(); +}; + +//InspectorMorph editing ops: + +InspectorMorph.prototype.save = function () { + var txt = this.detail.contents.children[0].text.toString(), + prop = this.list.selected; + try { + // this.target[prop] = evaluate(txt); + this.target.evaluateString('this.' + prop + ' = ' + txt); + if (this.target.drawNew) { + this.target.changed(); + this.target.drawNew(); + this.target.changed(); + } + } catch (err) { + this.inform(err); + } +}; + +InspectorMorph.prototype.addProperty = function () { + var myself = this; + this.prompt( + 'new property name:', + function (prop) { + if (prop) { + myself.target[prop] = null; + myself.buildPanes(); + if (myself.target.drawNew) { + myself.target.changed(); + myself.target.drawNew(); + myself.target.changed(); + } + } + }, + this, + 'property' // Chrome cannot handle empty strings (others do) + ); +}; + +InspectorMorph.prototype.renameProperty = function () { + var myself = this, + propertyName = this.list.selected; + this.prompt( + 'property name:', + function (prop) { + try { + delete (myself.target[propertyName]); + myself.target[prop] = myself.currentProperty; + } catch (err) { + myself.inform(err); + } + myself.buildPanes(); + if (myself.target.drawNew) { + myself.target.changed(); + myself.target.drawNew(); + myself.target.changed(); + } + }, + this, + propertyName + ); +}; + +InspectorMorph.prototype.removeProperty = function () { + var prop = this.list.selected; + try { + delete (this.target[prop]); + this.currentProperty = null; + this.buildPanes(); + if (this.target.drawNew) { + this.target.changed(); + this.target.drawNew(); + this.target.changed(); + } + } catch (err) { + this.inform(err); + } +}; + +// MenuMorph /////////////////////////////////////////////////////////// + +// MenuMorph: referenced constructors + +var MenuItemMorph; + +// MenuMorph inherits from BoxMorph: + +MenuMorph.prototype = new BoxMorph(); +MenuMorph.prototype.constructor = MenuMorph; +MenuMorph.uber = BoxMorph.prototype; + +// MenuMorph instance creation: + +function MenuMorph(target, title, environment, fontSize) { + this.init(target, title, environment, fontSize); + + /* + if target is a function, use it as callback: + execute target as callback function with the action property + of the triggered MenuItem as argument. + Use the environment, if it is specified. + Note: if action is also a function, instead of becoming + the argument itself it will be called to answer the argument. + For selections, Yes/No Choices etc. + + else (if target is not a function): + + if action is a function: + execute the action with target as environment (can be null) + for lambdafied (inline) actions + + else if action is a String: + treat it as function property of target and execute it + for selector-like actions + */ +} + +MenuMorph.prototype.init = function (target, title, environment, fontSize) { + // additional properties: + this.target = target; + this.title = title || null; + this.environment = environment || null; + this.fontSize = fontSize || null; + this.items = []; + this.label = null; + this.world = null; + this.isListContents = false; + + // initialize inherited properties: + MenuMorph.uber.init.call(this); + + // override inherited properties: + this.isDraggable = false; + + // immutable properties: + this.border = null; + this.edge = null; +}; + +MenuMorph.prototype.addItem = function ( + labelString, + action, + hint, + color, + bold, // bool + italic, // bool + doubleClickAction // optional, when used as list contents +) { + /* + labelString is normally a single-line string. But it can also be one + of the following: + + * a multi-line string (containing line breaks) + * an icon (either a Morph or a Canvas) + * a tuple of format: [icon, string] + */ + this.items.push([ + localize(labelString || 'close'), + action || nop, + hint, + color, + bold || false, + italic || false, + doubleClickAction]); +}; + +MenuMorph.prototype.addLine = function (width) { + this.items.push([0, width || 1]); +}; + +MenuMorph.prototype.createLabel = function () { + var text; + if (this.label !== null) { + this.label.destroy(); + } + text = new TextMorph( + localize(this.title), + this.fontSize || MorphicPreferences.menuFontSize, + MorphicPreferences.menuFontName, + true, + false, + 'center' + ); + text.alignment = 'center'; + text.color = new Color(255, 255, 255); + text.backgroundColor = this.borderColor; + text.drawNew(); + this.label = new BoxMorph(3, 0); + if (MorphicPreferences.isFlat) { + this.label.edge = 0; + } + this.label.color = this.borderColor; + this.label.borderColor = this.borderColor; + this.label.setExtent(text.extent().add(4)); + this.label.drawNew(); + this.label.add(text); + this.label.text = text; +}; + +MenuMorph.prototype.drawNew = function () { + var myself = this, + item, + fb, + x, + y, + isLine = false; + + this.children.forEach(function (m) { + m.destroy(); + }); + this.children = []; + if (!this.isListContents) { + this.edge = MorphicPreferences.isFlat ? 0 : 5; + this.border = MorphicPreferences.isFlat ? 1 : 2; + } + this.color = new Color(255, 255, 255); + this.borderColor = new Color(60, 60, 60); + this.silentSetExtent(new Point(0, 0)); + + y = 2; + x = this.left() + 4; + if (!this.isListContents) { + if (this.title) { + this.createLabel(); + this.label.setPosition(this.bounds.origin.add(4)); + this.add(this.label); + y = this.label.bottom(); + } else { + y = this.top() + 4; + } + } + y += 1; + this.items.forEach(function (tuple) { + isLine = false; + if (tuple instanceof StringFieldMorph || + tuple instanceof ColorPickerMorph || + tuple instanceof SliderMorph) { + item = tuple; + } else if (tuple[0] === 0) { + isLine = true; + item = new Morph(); + item.color = myself.borderColor; + item.setHeight(tuple[1]); + } else { + item = new MenuItemMorph( + myself.target, + tuple[1], + tuple[0], + myself.fontSize || MorphicPreferences.menuFontSize, + MorphicPreferences.menuFontName, + myself.environment, + tuple[2], // bubble help hint + tuple[3], // color + tuple[4], // bold + tuple[5], // italic + tuple[6] // doubleclick action + ); + } + if (isLine) { + y += 1; + } + item.setPosition(new Point(x, y)); + myself.add(item); + y = y + item.height(); + if (isLine) { + y += 1; + } + }); + + fb = this.fullBounds(); + this.silentSetExtent(fb.extent().add(4)); + this.adjustWidths(); + MenuMorph.uber.drawNew.call(this); +}; + +MenuMorph.prototype.maxWidth = function () { + var w = 0; + + if (this.parent instanceof FrameMorph) { + if (this.parent.scrollFrame instanceof ScrollFrameMorph) { + w = this.parent.scrollFrame.width(); + } + } + this.children.forEach(function (item) { + + if (item instanceof MenuItemMorph) { + w = Math.max(w, item.children[0].width() + 8); + } else if ((item instanceof StringFieldMorph) || + (item instanceof ColorPickerMorph) || + (item instanceof SliderMorph)) { + w = Math.max(w, item.width()); + } + }); + if (this.label) { + w = Math.max(w, this.label.width()); + } + return w; +}; + +MenuMorph.prototype.adjustWidths = function () { + var w = this.maxWidth(), + isSelected, + myself = this; + this.children.forEach(function (item) { + item.silentSetWidth(w); + if (item instanceof MenuItemMorph) { + isSelected = (item.image === item.pressImage); + item.createBackgrounds(); + if (isSelected) { + item.image = item.pressImage; + } + } else { + item.drawNew(); + if (item === myself.label) { + item.text.setPosition( + item.center().subtract( + item.text.extent().floorDivideBy(2) + ) + ); + } + } + }); +}; + +MenuMorph.prototype.unselectAllItems = function () { + this.children.forEach(function (item) { + if (item instanceof MenuItemMorph) { + item.image = item.normalImage; + } + }); + this.changed(); +}; + +MenuMorph.prototype.popup = function (world, pos) { + this.drawNew(); + this.setPosition(pos); + this.addShadow(new Point(2, 2), 80); + this.keepWithin(world); + if (world.activeMenu) { + world.activeMenu.destroy(); + } + world.add(this); + world.activeMenu = this; + this.fullChanged(); +}; + +MenuMorph.prototype.popUpAtHand = function (world) { + var wrrld = world || this.world; + this.popup(wrrld, wrrld.hand.position()); +}; + +MenuMorph.prototype.popUpCenteredAtHand = function (world) { + var wrrld = world || this.world; + this.drawNew(); + this.popup( + wrrld, + wrrld.hand.position().subtract( + this.extent().floorDivideBy(2) + ) + ); +}; + +MenuMorph.prototype.popUpCenteredInWorld = function (world) { + var wrrld = world || this.world; + this.drawNew(); + this.popup( + wrrld, + wrrld.center().subtract( + this.extent().floorDivideBy(2) + ) + ); +}; + +// StringMorph ///////////////////////////////////////////////////////// + +// I am a single line of text + +// StringMorph inherits from Morph: + +StringMorph.prototype = new Morph(); +StringMorph.prototype.constructor = StringMorph; +StringMorph.uber = Morph.prototype; + +// StringMorph instance creation: + +function StringMorph( + text, + fontSize, + fontStyle, + bold, + italic, + isNumeric, + shadowOffset, + shadowColor, + color, + fontName +) { + this.init( + text, + fontSize, + fontStyle, + bold, + italic, + isNumeric, + shadowOffset, + shadowColor, + color, + fontName + ); +} + +StringMorph.prototype.init = function ( + text, + fontSize, + fontStyle, + bold, + italic, + isNumeric, + shadowOffset, + shadowColor, + color, + fontName +) { + // additional properties: + this.text = text || ((text === '') ? '' : 'StringMorph'); + this.fontSize = fontSize || 12; + this.fontName = fontName || MorphicPreferences.globalFontFamily; + this.fontStyle = fontStyle || 'sans-serif'; + this.isBold = bold || false; + this.isItalic = italic || false; + this.isEditable = false; + this.isNumeric = isNumeric || false; + this.isPassword = false; + this.shadowOffset = shadowOffset || new Point(0, 0); + this.shadowColor = shadowColor || null; + this.isShowingBlanks = false; + this.blanksColor = new Color(180, 140, 140); + + // additional properties for text-editing: + this.isScrollable = true; // scrolls into view when edited + this.currentlySelecting = false; + this.startMark = 0; + this.endMark = 0; + this.markedTextColor = new Color(255, 255, 255); + this.markedBackgoundColor = new Color(60, 60, 120); + + // initialize inherited properties: + StringMorph.uber.init.call(this); + + // override inherited properites: + this.color = color || new Color(0, 0, 0); + this.noticesTransparentClick = true; + this.drawNew(); +}; + +StringMorph.prototype.toString = function () { + // e.g. 'a StringMorph("Hello World")' + return 'a ' + + (this.constructor.name || + this.constructor.toString().split(' ')[1].split('(')[0]) + + '("' + this.text.slice(0, 30) + '...")'; +}; + +StringMorph.prototype.password = function (letter, length) { + var ans = '', + i; + for (i = 0; i < length; i += 1) { + ans += letter; + } + return ans; +}; + +StringMorph.prototype.font = function () { + // answer a font string, e.g. 'bold italic 12px sans-serif' + var font = ''; + if (this.isBold) { + font = font + 'bold '; + } + if (this.isItalic) { + font = font + 'italic '; + } + return font + + this.fontSize + 'px ' + + (this.fontName ? this.fontName + ', ' : '') + + this.fontStyle; +}; + +StringMorph.prototype.drawNew = function () { + var context, width, start, stop, i, p, c, x, y, + shadowOffset = this.shadowOffset || new Point(), + txt = this.isPassword ? + this.password('*', this.text.length) : this.text; + + // initialize my surface property + this.image = newCanvas(); + context = this.image.getContext('2d'); + context.font = this.font(); + + // set my extent + width = Math.max( + context.measureText(txt).width + Math.abs(shadowOffset.x), + 1 + ); + this.bounds.corner = this.bounds.origin.add( + new Point( + width, + fontHeight(this.fontSize) + Math.abs(shadowOffset.y) + ) + ); + this.image.width = width; + this.image.height = this.height(); + + // prepare context for drawing text + context.font = this.font(); + context.textAlign = 'left'; + context.textBaseline = 'bottom'; + + // first draw the shadow, if any + if (this.shadowColor) { + x = Math.max(shadowOffset.x, 0); + y = Math.max(shadowOffset.y, 0); + context.fillStyle = this.shadowColor.toString(); + context.fillText(txt, x, fontHeight(this.fontSize) + y); + } + + // now draw the actual text + x = Math.abs(Math.min(shadowOffset.x, 0)); + y = Math.abs(Math.min(shadowOffset.y, 0)); + context.fillStyle = this.color.toString(); + + if (this.isShowingBlanks) { + this.renderWithBlanks(context, x, fontHeight(this.fontSize) + y); + } else { + context.fillText(txt, x, fontHeight(this.fontSize) + y); + } + + // draw the selection + start = Math.min(this.startMark, this.endMark); + stop = Math.max(this.startMark, this.endMark); + for (i = start; i < stop; i += 1) { + p = this.slotPosition(i).subtract(this.position()); + c = txt.charAt(i); + context.fillStyle = this.markedBackgoundColor.toString(); + context.fillRect(p.x, p.y, context.measureText(c).width + 1 + x, + fontHeight(this.fontSize) + y); + context.fillStyle = this.markedTextColor.toString(); + context.fillText(c, p.x + x, fontHeight(this.fontSize) + y); + } + + // notify my parent of layout change + if (this.parent) { + if (this.parent.fixLayout) { + this.parent.fixLayout(); + } + } +}; + +StringMorph.prototype.renderWithBlanks = function (context, startX, y) { + var space = context.measureText(' ').width, + blank = newCanvas(new Point(space, this.height())), + ctx = blank.getContext('2d'), + words = this.text.split(' '), + x = startX || 0, + isFirst = true; + + // create the blank form + ctx.fillStyle = this.blanksColor.toString(); + ctx.arc( + space / 2, + blank.height / 2, + space / 2, + radians(0), + radians(360) + ); + ctx.fill(); + + function drawBlank() { + context.drawImage(blank, x, 0); + x += space; + } + + // render my text inserting blanks + words.forEach(function (word) { + if (!isFirst) { + drawBlank(); + } + isFirst = false; + if (word !== '') { + context.fillText(word, x, y); + x += context.measureText(word).width; + } + }); +}; + +// StringMorph mesuring: + +StringMorph.prototype.slotPosition = function (slot) { + // answer the position point of the given index ("slot") + // where the cursor should be placed + var txt = this.isPassword ? + this.password('*', this.text.length) : this.text, + dest = Math.min(Math.max(slot, 0), txt.length), + context = this.image.getContext('2d'), + xOffset, + x, + y, + idx; + + xOffset = 0; + for (idx = 0; idx < dest; idx += 1) { + xOffset += context.measureText(txt[idx]).width; + } + this.pos = dest; + x = this.left() + xOffset; + y = this.top(); + return new Point(x, y); +}; + +StringMorph.prototype.slotAt = function (aPoint) { + // answer the slot (index) closest to the given point + // so the cursor can be moved accordingly + var txt = this.isPassword ? + this.password('*', this.text.length) : this.text, + idx = 0, + charX = 0, + context = this.image.getContext('2d'); + + while (aPoint.x - this.left() > charX) { + charX += context.measureText(txt[idx]).width; + idx += 1; + if (idx === txt.length) { + if ((context.measureText(txt).width - + (context.measureText(txt[idx - 1]).width / 2)) < + (aPoint.x - this.left())) { + return idx; + } + } + } + return idx - 1; +}; + +StringMorph.prototype.upFrom = function (slot) { + // answer the slot above the given one + return slot; +}; + +StringMorph.prototype.downFrom = function (slot) { + // answer the slot below the given one + return slot; +}; + +StringMorph.prototype.startOfLine = function () { + // answer the first slot (index) of the line for the given slot + return 0; +}; + +StringMorph.prototype.endOfLine = function () { + // answer the slot (index) indicating the EOL for the given slot + return this.text.length; +}; + +StringMorph.prototype.rawHeight = function () { + // answer my corrected fontSize + return this.height() / 1.2; +}; + +// StringMorph menus: + +StringMorph.prototype.developersMenu = function () { + var menu = StringMorph.uber.developersMenu.call(this); + + menu.addLine(); + menu.addItem("edit", 'edit'); + menu.addItem( + "font size...", + function () { + this.prompt( + menu.title + '\nfont\nsize:', + this.setFontSize, + this, + this.fontSize.toString(), + null, + 6, + 500, + true + ); + }, + 'set this String\'s\nfont point size' + ); + if (this.fontStyle !== 'serif') { + menu.addItem("serif", 'setSerif'); + } + if (this.fontStyle !== 'sans-serif') { + menu.addItem("sans-serif", 'setSansSerif'); + } + if (this.isBold) { + menu.addItem("normal weight", 'toggleWeight'); + } else { + menu.addItem("bold", 'toggleWeight'); + } + if (this.isItalic) { + menu.addItem("normal style", 'toggleItalic'); + } else { + menu.addItem("italic", 'toggleItalic'); + } + if (this.isShowingBlanks) { + menu.addItem("hide blanks", 'toggleShowBlanks'); + } else { + menu.addItem("show blanks", 'toggleShowBlanks'); + } + if (this.isPassword) { + menu.addItem("show characters", 'toggleIsPassword'); + } else { + menu.addItem("hide characters", 'toggleIsPassword'); + } + return menu; +}; + +StringMorph.prototype.toggleIsDraggable = function () { + // for context menu demo purposes + this.isDraggable = !this.isDraggable; + if (this.isDraggable) { + this.disableSelecting(); + } else { + this.enableSelecting(); + } +}; + +StringMorph.prototype.toggleShowBlanks = function () { + this.isShowingBlanks = !this.isShowingBlanks; + this.changed(); + this.drawNew(); + this.changed(); +}; + +StringMorph.prototype.toggleWeight = function () { + this.isBold = !this.isBold; + this.changed(); + this.drawNew(); + this.changed(); +}; + +StringMorph.prototype.toggleItalic = function () { + this.isItalic = !this.isItalic; + this.changed(); + this.drawNew(); + this.changed(); +}; + +StringMorph.prototype.toggleIsPassword = function () { + this.isPassword = !this.isPassword; + this.changed(); + this.drawNew(); + this.changed(); +}; + +StringMorph.prototype.setSerif = function () { + this.fontStyle = 'serif'; + this.changed(); + this.drawNew(); + this.changed(); +}; + +StringMorph.prototype.setSansSerif = function () { + this.fontStyle = 'sans-serif'; + this.changed(); + this.drawNew(); + this.changed(); +}; + +StringMorph.prototype.setFontSize = function (size) { + // for context menu demo purposes + var newSize; + if (typeof size === 'number') { + this.fontSize = Math.round(Math.min(Math.max(size, 4), 500)); + } else { + newSize = parseFloat(size); + if (!isNaN(newSize)) { + this.fontSize = Math.round( + Math.min(Math.max(newSize, 4), 500) + ); + } + } + this.changed(); + this.drawNew(); + this.changed(); +}; + +StringMorph.prototype.setText = function (size) { + // for context menu demo purposes + this.text = Math.round(size).toString(); + this.changed(); + this.drawNew(); + this.changed(); +}; + +StringMorph.prototype.numericalSetters = function () { + // for context menu demo purposes + return [ + 'setLeft', + 'setTop', + 'setAlphaScaled', + 'setFontSize', + 'setText' + ]; +}; + +// StringMorph editing: + +StringMorph.prototype.edit = function () { + this.root().edit(this); +}; + +StringMorph.prototype.selection = function () { + var start, stop; + start = Math.min(this.startMark, this.endMark); + stop = Math.max(this.startMark, this.endMark); + return this.text.slice(start, stop); +}; + +StringMorph.prototype.selectionStartSlot = function () { + return Math.min(this.startMark, this.endMark); +}; + +StringMorph.prototype.clearSelection = function () { + this.currentlySelecting = false; + this.startMark = 0; + this.endMark = 0; + this.drawNew(); + this.changed(); +}; + +StringMorph.prototype.deleteSelection = function () { + var start, stop, text; + text = this.text; + start = Math.min(this.startMark, this.endMark); + stop = Math.max(this.startMark, this.endMark); + this.text = text.slice(0, start) + text.slice(stop); + this.changed(); + this.clearSelection(); +}; + +StringMorph.prototype.selectAll = function () { + if (this.isEditable) { + this.startMark = 0; + this.endMark = this.text.length; + this.drawNew(); + this.changed(); + } +}; + +StringMorph.prototype.mouseDownLeft = function (pos) { + if (this.isEditable) { + this.clearSelection(); + } else { + this.escalateEvent('mouseDownLeft', pos); + } +}; + +StringMorph.prototype.mouseClickLeft = function (pos) { + var cursor; + if (this.isEditable) { + if (!this.currentlySelecting) { + this.edit(); // creates a new cursor + } + cursor = this.root().cursor; + if (cursor) { + cursor.gotoPos(pos); + } + this.currentlySelecting = true; + } else { + this.escalateEvent('mouseClickLeft', pos); + } +}; + +StringMorph.prototype.enableSelecting = function () { + this.mouseDownLeft = function (pos) { + this.clearSelection(); + if (this.isEditable && (!this.isDraggable)) { + this.edit(); + this.root().cursor.gotoPos(pos); + this.startMark = this.slotAt(pos); + this.endMark = this.startMark; + this.currentlySelecting = true; + } + }; + this.mouseMove = function (pos) { + if (this.isEditable && + this.currentlySelecting && + (!this.isDraggable)) { + var newMark = this.slotAt(pos); + if (newMark !== this.endMark) { + this.endMark = newMark; + this.drawNew(); + this.changed(); + } + } + }; +}; + +StringMorph.prototype.disableSelecting = function () { + this.mouseDownLeft = StringMorph.prototype.mouseDownLeft; + delete this.mouseMove; +}; + +// TextMorph //////////////////////////////////////////////////////////////// + +// I am a multi-line, word-wrapping String, quasi-inheriting from StringMorph + +// TextMorph inherits from Morph: + +TextMorph.prototype = new Morph(); +TextMorph.prototype.constructor = TextMorph; +TextMorph.uber = Morph.prototype; + +// TextMorph instance creation: + +function TextMorph( + text, + fontSize, + fontStyle, + bold, + italic, + alignment, + width, + fontName, + shadowOffset, + shadowColor +) { + this.init(text, + fontSize, + fontStyle, + bold, + italic, + alignment, + width, + fontName, + shadowOffset, + shadowColor); +} + +TextMorph.prototype.init = function ( + text, + fontSize, + fontStyle, + bold, + italic, + alignment, + width, + fontName, + shadowOffset, + shadowColor +) { + // additional properties: + this.text = text || (text === '' ? text : 'TextMorph'); + this.words = []; + this.lines = []; + this.lineSlots = []; + this.fontSize = fontSize || 12; + this.fontName = fontName || MorphicPreferences.globalFontFamily; + this.fontStyle = fontStyle || 'sans-serif'; + this.isBold = bold || false; + this.isItalic = italic || false; + this.alignment = alignment || 'left'; + this.shadowOffset = shadowOffset || new Point(0, 0); + this.shadowColor = shadowColor || null; + this.maxWidth = width || 0; + this.maxLineWidth = 0; + this.backgroundColor = null; + this.isEditable = false; + + //additional properties for ad-hoc evaluation: + this.receiver = null; + + // additional properties for text-editing: + this.isScrollable = true; // scrolls into view when edited + this.currentlySelecting = false; + this.startMark = 0; + this.endMark = 0; + this.markedTextColor = new Color(255, 255, 255); + this.markedBackgoundColor = new Color(60, 60, 120); + + // initialize inherited properties: + TextMorph.uber.init.call(this); + + // override inherited properites: + this.color = new Color(0, 0, 0); + this.noticesTransparentClick = true; + this.drawNew(); +}; + +TextMorph.prototype.toString = function () { + // e.g. 'a TextMorph("Hello World")' + return 'a TextMorph' + '("' + this.text.slice(0, 30) + '...")'; +}; + +TextMorph.prototype.font = StringMorph.prototype.font; + +TextMorph.prototype.parse = function () { + var myself = this, + paragraphs = this.text.split('\n'), + canvas = newCanvas(), + context = canvas.getContext('2d'), + oldline = '', + newline, + w, + slot = 0; + + context.font = this.font(); + this.maxLineWidth = 0; + this.lines = []; + this.lineSlots = [0]; + this.words = []; + + paragraphs.forEach(function (p) { + myself.words = myself.words.concat(p.split(' ')); + myself.words.push('\n'); + }); + + this.words.forEach(function (word) { + if (word === '\n') { + myself.lines.push(oldline); + myself.lineSlots.push(slot); + myself.maxLineWidth = Math.max( + myself.maxLineWidth, + context.measureText(oldline).width + ); + oldline = ''; + } else { + if (myself.maxWidth > 0) { + newline = oldline + word + ' '; + w = context.measureText(newline).width; + if (w > myself.maxWidth) { + myself.lines.push(oldline); + myself.lineSlots.push(slot); + myself.maxLineWidth = Math.max( + myself.maxLineWidth, + context.measureText(oldline).width + ); + oldline = word + ' '; + } else { + oldline = newline; + } + } else { + oldline = oldline + word + ' '; + } + slot += word.length + 1; + } + }); +}; + +TextMorph.prototype.drawNew = function () { + var context, height, i, line, width, shadowHeight, shadowWidth, + offx, offy, x, y, start, stop, p, c; + + this.image = newCanvas(); + context = this.image.getContext('2d'); + context.font = this.font(); + this.parse(); + + // set my extent + shadowWidth = Math.abs(this.shadowOffset.x); + shadowHeight = Math.abs(this.shadowOffset.y); + height = this.lines.length * (fontHeight(this.fontSize) + shadowHeight); + if (this.maxWidth === 0) { + this.bounds = this.bounds.origin.extent( + new Point(this.maxLineWidth + shadowWidth, height) + ); + } else { + this.bounds = this.bounds.origin.extent( + new Point(this.maxWidth + shadowWidth, height) + ); + } + this.image.width = this.width(); + this.image.height = this.height(); + + // prepare context for drawing text + context = this.image.getContext('2d'); + context.font = this.font(); + context.textAlign = 'left'; + context.textBaseline = 'bottom'; + + // fill the background, if desired + if (this.backgroundColor) { + context.fillStyle = this.backgroundColor.toString(); + context.fillRect(0, 0, this.width(), this.height()); + } + + // draw the shadow, if any + if (this.shadowColor) { + offx = Math.max(this.shadowOffset.x, 0); + offy = Math.max(this.shadowOffset.y, 0); + context.fillStyle = this.shadowColor.toString(); + + for (i = 0; i < this.lines.length; i = i + 1) { + line = this.lines[i]; + width = context.measureText(line).width + shadowWidth; + if (this.alignment === 'right') { + x = this.width() - width; + } else if (this.alignment === 'center') { + x = (this.width() - width) / 2; + } else { // 'left' + x = 0; + } + y = (i + 1) * (fontHeight(this.fontSize) + shadowHeight) + - shadowHeight; + context.fillText(line, x + offx, y + offy); + } + } + + // now draw the actual text + offx = Math.abs(Math.min(this.shadowOffset.x, 0)); + offy = Math.abs(Math.min(this.shadowOffset.y, 0)); + context.fillStyle = this.color.toString(); + + for (i = 0; i < this.lines.length; i = i + 1) { + line = this.lines[i]; + width = context.measureText(line).width + shadowWidth; + if (this.alignment === 'right') { + x = this.width() - width; + } else if (this.alignment === 'center') { + x = (this.width() - width) / 2; + } else { // 'left' + x = 0; + } + y = (i + 1) * (fontHeight(this.fontSize) + shadowHeight) + - shadowHeight; + context.fillText(line, x + offx, y + offy); + } + + // draw the selection + start = Math.min(this.startMark, this.endMark); + stop = Math.max(this.startMark, this.endMark); + for (i = start; i < stop; i += 1) { + p = this.slotPosition(i).subtract(this.position()); + c = this.text.charAt(i); + context.fillStyle = this.markedBackgoundColor.toString(); + context.fillRect(p.x, p.y, context.measureText(c).width + 1, + fontHeight(this.fontSize)); + context.fillStyle = this.markedTextColor.toString(); + context.fillText(c, p.x, p.y + fontHeight(this.fontSize)); + } + + // notify my parent of layout change + if (this.parent) { + if (this.parent.layoutChanged) { + this.parent.layoutChanged(); + } + } +}; + +TextMorph.prototype.setExtent = function (aPoint) { + this.maxWidth = Math.max(aPoint.x, 0); + this.changed(); + this.drawNew(); +}; + +// TextMorph mesuring: + +TextMorph.prototype.columnRow = function (slot) { + // answer the logical position point of the given index ("slot") + var row, + col, + idx = 0; + + for (row = 0; row < this.lines.length; row += 1) { + idx = this.lineSlots[row]; + for (col = 0; col < this.lines[row].length; col += 1) { + if (idx === slot) { + return new Point(col, row); + } + idx += 1; + } + } + // return new Point(0, 0); + return new Point( + this.lines[this.lines.length - 1].length - 1, + this.lines.length - 1 + ); +}; + +TextMorph.prototype.slotPosition = function (slot) { + // answer the physical position point of the given index ("slot") + // where the cursor should be placed + var colRow = this.columnRow(slot), + context = this.image.getContext('2d'), + shadowHeight = Math.abs(this.shadowOffset.y), + xOffset = 0, + yOffset, + x, + y, + idx; + + yOffset = colRow.y * (fontHeight(this.fontSize) + shadowHeight); + for (idx = 0; idx < colRow.x; idx += 1) { + xOffset += context.measureText(this.lines[colRow.y][idx]).width; + } + x = this.left() + xOffset; + y = this.top() + yOffset; + return new Point(x, y); +}; + +TextMorph.prototype.slotAt = function (aPoint) { + // answer the slot (index) closest to the given point + // so the cursor can be moved accordingly + var charX = 0, + row = 0, + col = 0, + shadowHeight = Math.abs(this.shadowOffset.y), + context = this.image.getContext('2d'); + + while (aPoint.y - this.top() > + ((fontHeight(this.fontSize) + shadowHeight) * row)) { + row += 1; + } + row = Math.max(row, 1); + while (aPoint.x - this.left() > charX) { + charX += context.measureText(this.lines[row - 1][col]).width; + col += 1; + } + return this.lineSlots[Math.max(row - 1, 0)] + col - 1; +}; + +TextMorph.prototype.upFrom = function (slot) { + // answer the slot above the given one + var above, + colRow = this.columnRow(slot); + if (colRow.y < 1) { + return slot; + } + above = this.lines[colRow.y - 1]; + if (above.length < colRow.x - 1) { + return this.lineSlots[colRow.y - 1] + above.length; + } + return this.lineSlots[colRow.y - 1] + colRow.x; +}; + +TextMorph.prototype.downFrom = function (slot) { + // answer the slot below the given one + var below, + colRow = this.columnRow(slot); + if (colRow.y > this.lines.length - 2) { + return slot; + } + below = this.lines[colRow.y + 1]; + if (below.length < colRow.x - 1) { + return this.lineSlots[colRow.y + 1] + below.length; + } + return this.lineSlots[colRow.y + 1] + colRow.x; +}; + +TextMorph.prototype.startOfLine = function (slot) { + // answer the first slot (index) of the line for the given slot + return this.lineSlots[this.columnRow(slot).y]; +}; + +TextMorph.prototype.endOfLine = function (slot) { + // answer the slot (index) indicating the EOL for the given slot + return this.startOfLine(slot) + + this.lines[this.columnRow(slot).y].length - 1; +}; + +// TextMorph editing: + +TextMorph.prototype.edit = StringMorph.prototype.edit; + +TextMorph.prototype.selection = StringMorph.prototype.selection; + +TextMorph.prototype.selectionStartSlot + = StringMorph.prototype.selectionStartSlot; + +TextMorph.prototype.clearSelection = StringMorph.prototype.clearSelection; + +TextMorph.prototype.deleteSelection = StringMorph.prototype.deleteSelection; + +TextMorph.prototype.selectAll = StringMorph.prototype.selectAll; + +TextMorph.prototype.mouseDownLeft = StringMorph.prototype.mouseDownLeft; + +TextMorph.prototype.mouseClickLeft = StringMorph.prototype.mouseClickLeft; + +TextMorph.prototype.enableSelecting = StringMorph.prototype.enableSelecting; + +TextMorph.prototype.disableSelecting = StringMorph.prototype.disableSelecting; + +TextMorph.prototype.selectAllAndEdit = function () { + this.edit(); + this.selectAll(); +}; + +// TextMorph menus: + +TextMorph.prototype.developersMenu = function () { + var menu = TextMorph.uber.developersMenu.call(this); + menu.addLine(); + menu.addItem("edit", 'edit'); + menu.addItem( + "font size...", + function () { + this.prompt( + menu.title + '\nfont\nsize:', + this.setFontSize, + this, + this.fontSize.toString(), + null, + 6, + 100, + true + ); + }, + 'set this Text\'s\nfont point size' + ); + if (this.alignment !== 'left') { + menu.addItem("align left", 'setAlignmentToLeft'); + } + if (this.alignment !== 'right') { + menu.addItem("align right", 'setAlignmentToRight'); + } + if (this.alignment !== 'center') { + menu.addItem("align center", 'setAlignmentToCenter'); + } + menu.addLine(); + if (this.fontStyle !== 'serif') { + menu.addItem("serif", 'setSerif'); + } + if (this.fontStyle !== 'sans-serif') { + menu.addItem("sans-serif", 'setSansSerif'); + } + if (this.isBold) { + menu.addItem("normal weight", 'toggleWeight'); + } else { + menu.addItem("bold", 'toggleWeight'); + } + if (this.isItalic) { + menu.addItem("normal style", 'toggleItalic'); + } else { + menu.addItem("italic", 'toggleItalic'); + } + return menu; +}; + +TextMorph.prototype.setAlignmentToLeft = function () { + this.alignment = 'left'; + this.drawNew(); + this.changed(); +}; + +TextMorph.prototype.setAlignmentToRight = function () { + this.alignment = 'right'; + this.drawNew(); + this.changed(); +}; + +TextMorph.prototype.setAlignmentToCenter = function () { + this.alignment = 'center'; + this.drawNew(); + this.changed(); +}; + +TextMorph.prototype.toggleIsDraggable + = StringMorph.prototype.toggleIsDraggable; + +TextMorph.prototype.toggleWeight = StringMorph.prototype.toggleWeight; + +TextMorph.prototype.toggleItalic = StringMorph.prototype.toggleItalic; + +TextMorph.prototype.setSerif = StringMorph.prototype.setSerif; + +TextMorph.prototype.setSansSerif = StringMorph.prototype.setSansSerif; + +TextMorph.prototype.setText = StringMorph.prototype.setText; + +TextMorph.prototype.setFontSize = StringMorph.prototype.setFontSize; + +TextMorph.prototype.numericalSetters = StringMorph.prototype.numericalSetters; + +// TextMorph evaluation: + +TextMorph.prototype.evaluationMenu = function () { + var menu = new MenuMorph(this, null); + menu.addItem( + "do it", + 'doIt', + 'evaluate the\nselected expression' + ); + menu.addItem( + "show it", + 'showIt', + 'evaluate the\nselected expression\nand show the result' + ); + menu.addItem( + "inspect it", + 'inspectIt', + 'evaluate the\nselected expression\nand inspect the result' + ); + menu.addLine(); + menu.addItem("select all", 'selectAllAndEdit'); + return menu; +}; + +TextMorph.prototype.setReceiver = function (obj) { + this.receiver = obj; + this.customContextMenu = this.evaluationMenu(); +}; + +TextMorph.prototype.doIt = function () { + this.receiver.evaluateString(this.selection()); + this.edit(); +}; + +TextMorph.prototype.showIt = function () { + var result = this.receiver.evaluateString(this.selection()); + if (result !== null) { + this.inform(result); + } +}; + +TextMorph.prototype.inspectIt = function () { + var result = this.receiver.evaluateString(this.selection()), + world = this.world(), + inspector; + if (result !== null) { + inspector = new InspectorMorph(result); + inspector.setPosition(world.hand.position()); + inspector.keepWithin(world); + world.add(inspector); + inspector.changed(); + } +}; + +// TriggerMorph //////////////////////////////////////////////////////// + +// I provide basic button functionality + +// TriggerMorph inherits from Morph: + +TriggerMorph.prototype = new Morph(); +TriggerMorph.prototype.constructor = TriggerMorph; +TriggerMorph.uber = Morph.prototype; + +// TriggerMorph instance creation: + +function TriggerMorph( + target, + action, + labelString, + fontSize, + fontStyle, + environment, + hint, + labelColor, + labelBold, + labelItalic, + doubleClickAction +) { + this.init( + target, + action, + labelString, + fontSize, + fontStyle, + environment, + hint, + labelColor, + labelBold, + labelItalic, + doubleClickAction + ); +} + +TriggerMorph.prototype.init = function ( + target, + action, + labelString, + fontSize, + fontStyle, + environment, + hint, + labelColor, + labelBold, + labelItalic, + doubleClickAction +) { + // additional properties: + this.target = target || null; + this.action = action || null; + this.doubleClickAction = doubleClickAction || null; + this.environment = environment || null; + this.labelString = labelString || null; + this.label = null; + this.hint = hint || null; + this.fontSize = fontSize || MorphicPreferences.menuFontSize; + this.fontStyle = fontStyle || 'sans-serif'; + this.highlightColor = new Color(192, 192, 192); + this.pressColor = new Color(128, 128, 128); + this.labelColor = labelColor || new Color(0, 0, 0); + this.labelBold = labelBold || false; + this.labelItalic = labelItalic || false; + + // initialize inherited properties: + TriggerMorph.uber.init.call(this); + + // override inherited properites: + this.color = new Color(255, 255, 255); + this.drawNew(); +}; + +// TriggerMorph drawing: + +TriggerMorph.prototype.drawNew = function () { + this.createBackgrounds(); + if (this.labelString !== null) { + this.createLabel(); + } +}; + +TriggerMorph.prototype.createBackgrounds = function () { + var context, + ext = this.extent(); + + this.normalImage = newCanvas(ext); + context = this.normalImage.getContext('2d'); + context.fillStyle = this.color.toString(); + context.fillRect(0, 0, ext.x, ext.y); + + this.highlightImage = newCanvas(ext); + context = this.highlightImage.getContext('2d'); + context.fillStyle = this.highlightColor.toString(); + context.fillRect(0, 0, ext.x, ext.y); + + this.pressImage = newCanvas(ext); + context = this.pressImage.getContext('2d'); + context.fillStyle = this.pressColor.toString(); + context.fillRect(0, 0, ext.x, ext.y); + + this.image = this.normalImage; +}; + +TriggerMorph.prototype.createLabel = function () { + if (this.label !== null) { + this.label.destroy(); + } + this.label = new StringMorph( + this.labelString, + this.fontSize, + this.fontStyle, + this.labelBold, + this.labelItalic, + false, // numeric + null, // shadow offset + null, // shadow color + this.labelColor + ); + this.label.setPosition( + this.center().subtract( + this.label.extent().floorDivideBy(2) + ) + ); + this.add(this.label); +}; + +// TriggerMorph duplicating: + +TriggerMorph.prototype.copyRecordingReferences = function (dict) { + // inherited, see comment in Morph + var c = TriggerMorph.uber.copyRecordingReferences.call( + this, + dict + ); + if (c.label && dict[this.label]) { + c.label = (dict[this.label]); + } + return c; +}; + +// TriggerMorph action: + +TriggerMorph.prototype.trigger = function () { + /* + if target is a function, use it as callback: + execute target as callback function with action as argument + in the environment as optionally specified. + Note: if action is also a function, instead of becoming + the argument itself it will be called to answer the argument. + for selections, Yes/No Choices etc. As second argument pass + myself, so I can be modified to reflect status changes, e.g. + inside a list box: + + else (if target is not a function): + + if action is a function: + execute the action with target as environment (can be null) + for lambdafied (inline) actions + + else if action is a String: + treat it as function property of target and execute it + for selector-like actions + */ + if (typeof this.target === 'function') { + if (typeof this.action === 'function') { + this.target.call(this.environment, this.action.call(), this); + } else { + this.target.call(this.environment, this.action, this); + } + } else { + if (typeof this.action === 'function') { + this.action.call(this.target); + } else { // assume it's a String + this.target[this.action](); + } + } +}; + +TriggerMorph.prototype.triggerDoubleClick = function () { + // same as trigger() but use doubleClickAction instead of action property + // note that specifying a doubleClickAction is optional + if (!this.doubleClickAction) {return; } + if (typeof this.target === 'function') { + if (typeof this.doubleClickAction === 'function') { + this.target.call( + this.environment, + this.doubleClickAction.call(), + this + ); + } else { + this.target.call(this.environment, this.doubleClickAction, this); + } + } else { + if (typeof this.doubleClickAction === 'function') { + this.doubleClickAction.call(this.target); + } else { // assume it's a String + this.target[this.doubleClickAction](); + } + } +}; + +// TriggerMorph events: + +TriggerMorph.prototype.mouseEnter = function () { + this.image = this.highlightImage; + this.changed(); + if (this.hint) { + this.bubbleHelp(this.hint); + } +}; + +TriggerMorph.prototype.mouseLeave = function () { + this.image = this.normalImage; + this.changed(); + if (this.hint) { + this.world().hand.destroyTemporaries(); + } +}; + +TriggerMorph.prototype.mouseDownLeft = function () { + this.image = this.pressImage; + this.changed(); +}; + +TriggerMorph.prototype.mouseClickLeft = function () { + this.image = this.highlightImage; + this.changed(); + this.trigger(); +}; + +TriggerMorph.prototype.mouseDoubleClick = function () { + this.triggerDoubleClick(); +}; + +TriggerMorph.prototype.rootForGrab = function () { + return this.isDraggable ? TriggerMorph.uber.rootForGrab.call(this) : null; +}; + +// TriggerMorph bubble help: + +TriggerMorph.prototype.bubbleHelp = function (contents) { + var myself = this; + this.fps = 2; + this.step = function () { + if (this.bounds.containsPoint(this.world().hand.position())) { + myself.popUpbubbleHelp(contents); + } + myself.fps = 0; + delete myself.step; + }; +}; + +TriggerMorph.prototype.popUpbubbleHelp = function (contents) { + new SpeechBubbleMorph( + localize(contents), + null, + null, + 1 + ).popUp(this.world(), this.rightCenter().add(new Point(-8, 0))); +}; + +// MenuItemMorph /////////////////////////////////////////////////////// + +// I automatically determine my bounds + +var MenuItemMorph; + +// MenuItemMorph inherits from TriggerMorph: + +MenuItemMorph.prototype = new TriggerMorph(); +MenuItemMorph.prototype.constructor = MenuItemMorph; +MenuItemMorph.uber = TriggerMorph.prototype; + +// MenuItemMorph instance creation: + +function MenuItemMorph( + target, + action, + labelString, // can also be a Morph or a Canvas or a tuple: [icon, string] + fontSize, + fontStyle, + environment, + hint, + color, + bold, + italic, + doubleClickAction // optional when used as list morph item +) { + this.init( + target, + action, + labelString, + fontSize, + fontStyle, + environment, + hint, + color, + bold, + italic, + doubleClickAction + ); +} + +MenuItemMorph.prototype.createLabel = function () { + var icon, lbl, np; + if (this.label !== null) { + this.label.destroy(); + } + if (isString(this.labelString)) { + this.label = this.createLabelString(this.labelString); + } else if (this.labelString instanceof Array) { + // assume its pattern is: [icon, string] + this.label = new Morph(); + this.label.alpha = 0; // transparent + icon = this.createIcon(this.labelString[0]); + this.label.add(icon); + lbl = this.createLabelString(this.labelString[1]); + this.label.add(lbl); + lbl.setCenter(icon.center()); + lbl.setLeft(icon.right() + 4); + this.label.bounds = (icon.bounds.merge(lbl.bounds)); + this.label.drawNew(); + } else { // assume it's either a Morph or a Canvas + this.label = this.createIcon(this.labelString); + } + this.silentSetExtent(this.label.extent().add(new Point(8, 0))); + np = this.position().add(new Point(4, 0)); + this.label.bounds = np.extent(this.label.extent()); + this.add(this.label); +}; + +MenuItemMorph.prototype.createIcon = function (source) { + // source can be either a Morph or an HTMLCanvasElement + var icon = new Morph(), + src; + icon.image = source instanceof Morph ? source.fullImage() : source; + // adjust shadow dimensions + if (source instanceof Morph && source.getShadow()) { + src = icon.image; + icon.image = newCanvas( + source.fullBounds().extent().subtract( + this.shadowBlur * (useBlurredShadows ? 1 : 2) + ) + ); + icon.image.getContext('2d').drawImage(src, 0, 0); + } + icon.silentSetWidth(icon.image.width); + icon.silentSetHeight(icon.image.height); + return icon; +}; + +MenuItemMorph.prototype.createLabelString = function (string) { + var lbl = new TextMorph( + string, + this.fontSize, + this.fontStyle, + this.labelBold, + this.labelItalic + ); + lbl.setColor(this.labelColor); + return lbl; +}; + +// MenuItemMorph events: + +MenuItemMorph.prototype.mouseEnter = function () { + if (!this.isListItem()) { + this.image = this.highlightImage; + this.changed(); + } + if (this.hint) { + this.bubbleHelp(this.hint); + } +}; + +MenuItemMorph.prototype.mouseLeave = function () { + if (!this.isListItem()) { + this.image = this.normalImage; + this.changed(); + } + if (this.hint) { + this.world().hand.destroyTemporaries(); + } +}; + +MenuItemMorph.prototype.mouseDownLeft = function (pos) { + if (this.isListItem()) { + this.parent.unselectAllItems(); + this.escalateEvent('mouseDownLeft', pos); + } + this.image = this.pressImage; + this.changed(); +}; + +MenuItemMorph.prototype.mouseMove = function () { + if (this.isListItem()) { + this.escalateEvent('mouseMove'); + } +}; + +MenuItemMorph.prototype.mouseClickLeft = function () { + if (!this.isListItem()) { + this.parent.destroy(); + this.root().activeMenu = null; + } + this.trigger(); +}; + +MenuItemMorph.prototype.isListItem = function () { + if (this.parent) { + return this.parent.isListContents; + } + return false; +}; + +MenuItemMorph.prototype.isSelectedListItem = function () { + if (this.isListItem()) { + return this.image === this.pressImage; + } + return false; +}; + +// FrameMorph ////////////////////////////////////////////////////////// + +// I clip my submorphs at my bounds + +// Frames inherit from Morph: + +FrameMorph.prototype = new Morph(); +FrameMorph.prototype.constructor = FrameMorph; +FrameMorph.uber = Morph.prototype; + +function FrameMorph(aScrollFrame) { + this.init(aScrollFrame); +} + +FrameMorph.prototype.init = function (aScrollFrame) { + this.scrollFrame = aScrollFrame || null; + + FrameMorph.uber.init.call(this); + this.color = new Color(255, 250, 245); + this.drawNew(); + this.acceptsDrops = true; + + if (this.scrollFrame) { + this.isDraggable = false; + this.noticesTransparentClick = false; + this.alpha = 0; + } +}; + +FrameMorph.prototype.fullBounds = function () { + var shadow = this.getShadow(); + if (shadow !== null) { + return this.bounds.merge(shadow.bounds); + } + return this.bounds; +}; + +FrameMorph.prototype.fullImage = function () { + // use only for shadows + return this.image; +}; + +FrameMorph.prototype.fullDrawOn = function (aCanvas, aRect) { + var rectangle, dirty; + if (!this.isVisible) { + return null; + } + rectangle = aRect || this.fullBounds(); + dirty = this.bounds.intersect(rectangle); + if (!dirty.extent().gt(new Point(0, 0))) { + return null; + } + this.drawOn(aCanvas, dirty); + this.children.forEach(function (child) { + if (child instanceof ShadowMorph) { + child.fullDrawOn(aCanvas, rectangle); + } else { + child.fullDrawOn(aCanvas, dirty); + } + }); +}; + +// FrameMorph scrolling optimization: + +FrameMorph.prototype.moveBy = function (delta) { + this.changed(); + this.bounds = this.bounds.translateBy(delta); + this.children.forEach(function (child) { + child.silentMoveBy(delta); + }); + this.changed(); +}; + +// FrameMorph scrolling support: + +FrameMorph.prototype.submorphBounds = function () { + var result = null; + + if (this.children.length > 0) { + result = this.children[0].bounds; + this.children.forEach(function (child) { + result = result.merge(child.fullBounds()); + }); + } + return result; +}; + +FrameMorph.prototype.keepInScrollFrame = function () { + if (this.scrollFrame === null) { + return null; + } + if (this.left() > this.scrollFrame.left()) { + this.moveBy( + new Point(this.scrollFrame.left() - this.left(), 0) + ); + } + if (this.right() < this.scrollFrame.right()) { + this.moveBy( + new Point(this.scrollFrame.right() - this.right(), 0) + ); + } + if (this.top() > this.scrollFrame.top()) { + this.moveBy( + new Point(0, this.scrollFrame.top() - this.top()) + ); + } + if (this.bottom() < this.scrollFrame.bottom()) { + this.moveBy( + 0, + new Point(this.scrollFrame.bottom() - this.bottom(), 0) + ); + } +}; + +FrameMorph.prototype.adjustBounds = function () { + var subBounds, + newBounds, + myself = this; + + if (this.scrollFrame === null) { + return null; + } + + subBounds = this.submorphBounds(); + if (subBounds && (!this.scrollFrame.isTextLineWrapping)) { + newBounds = subBounds + .expandBy(this.scrollFrame.padding) + .growBy(this.scrollFrame.growth) + .merge(this.scrollFrame.bounds); + } else { + newBounds = this.scrollFrame.bounds.copy(); + } + if (!this.bounds.eq(newBounds)) { + this.bounds = newBounds; + this.drawNew(); + this.keepInScrollFrame(); + } + + if (this.scrollFrame.isTextLineWrapping) { + this.children.forEach(function (morph) { + if (morph instanceof TextMorph) { + morph.setWidth(myself.width()); + myself.setHeight( + Math.max(morph.height(), myself.scrollFrame.height()) + ); + } + }); + } + + this.scrollFrame.adjustScrollBars(); +}; + +// FrameMorph dragging & dropping of contents: + +FrameMorph.prototype.reactToDropOf = function () { + this.adjustBounds(); +}; + +FrameMorph.prototype.reactToGrabOf = function () { + this.adjustBounds(); +}; + +// FrameMorph duplicating: + +FrameMorph.prototype.copyRecordingReferences = function (dict) { + // inherited, see comment in Morph + var c = FrameMorph.uber.copyRecordingReferences.call( + this, + dict + ); + if (c.frame && dict[this.scrollFrame]) { + c.frame = (dict[this.scrollFrame]); + } + return c; +}; + +// FrameMorph menus: + +FrameMorph.prototype.developersMenu = function () { + var menu = FrameMorph.uber.developersMenu.call(this); + if (this.children.length > 0) { + menu.addLine(); + menu.addItem( + "move all inside...", + 'keepAllSubmorphsWithin', + 'keep all submorphs\nwithin and visible' + ); + } + return menu; +}; + +FrameMorph.prototype.keepAllSubmorphsWithin = function () { + var myself = this; + this.children.forEach(function (m) { + m.keepWithin(myself); + }); +}; + +// ScrollFrameMorph //////////////////////////////////////////////////// + +ScrollFrameMorph.prototype = new FrameMorph(); +ScrollFrameMorph.prototype.constructor = ScrollFrameMorph; +ScrollFrameMorph.uber = FrameMorph.prototype; + +function ScrollFrameMorph(scroller, size, sliderColor) { + this.init(scroller, size, sliderColor); +} + +ScrollFrameMorph.prototype.init = function (scroller, size, sliderColor) { + var myself = this; + + ScrollFrameMorph.uber.init.call(this); + this.scrollBarSize = size || MorphicPreferences.scrollBarSize; + this.autoScrollTrigger = null; + this.isScrollingByDragging = true; // change if desired + this.hasVelocity = true; // dto. + this.padding = 0; // around the scrollable area + this.growth = 0; // pixels or Point to grow right/left when near edge + this.isTextLineWrapping = false; + this.contents = scroller || new FrameMorph(this); + this.add(this.contents); + this.hBar = new SliderMorph( + null, // start + null, // stop + null, // value + null, // size + 'horizontal', + sliderColor + ); + this.hBar.setHeight(this.scrollBarSize); + this.hBar.action = function (num) { + myself.contents.setPosition( + new Point( + myself.left() - num, + myself.contents.position().y + ) + ); + }; + this.hBar.isDraggable = false; + this.add(this.hBar); + this.vBar = new SliderMorph( + null, // start + null, // stop + null, // value + null, // size + 'vertical', + sliderColor + ); + this.vBar.setWidth(this.scrollBarSize); + this.vBar.action = function (num) { + myself.contents.setPosition( + new Point( + myself.contents.position().x, + myself.top() - num + ) + ); + }; + this.vBar.isDraggable = false; + this.add(this.vBar); +}; + +ScrollFrameMorph.prototype.adjustScrollBars = function () { + var hWidth = this.width() - this.scrollBarSize, + vHeight = this.height() - this.scrollBarSize; + + this.changed(); + if (this.contents.width() > this.width() + + MorphicPreferences.scrollBarSize) { + this.hBar.show(); + if (this.hBar.width() !== hWidth) { + this.hBar.setWidth(hWidth); + } + + this.hBar.setPosition( + new Point( + this.left(), + this.bottom() - this.hBar.height() + ) + ); + this.hBar.start = 0; + this.hBar.stop = this.contents.width() - this.width(); + this.hBar.size = + this.width() / this.contents.width() * this.hBar.stop; + this.hBar.value = this.left() - this.contents.left(); + this.hBar.drawNew(); + } else { + this.hBar.hide(); + } + + if (this.contents.height() > this.height() + + this.scrollBarSize) { + this.vBar.show(); + if (this.vBar.height() !== vHeight) { + this.vBar.setHeight(vHeight); + } + + this.vBar.setPosition( + new Point( + this.right() - this.vBar.width(), + this.top() + ) + ); + this.vBar.start = 0; + this.vBar.stop = this.contents.height() - this.height(); + this.vBar.size = + this.height() / this.contents.height() * this.vBar.stop; + this.vBar.value = this.top() - this.contents.top(); + this.vBar.drawNew(); + } else { + this.vBar.hide(); + } +}; + +ScrollFrameMorph.prototype.addContents = function (aMorph) { + this.contents.add(aMorph); + this.contents.adjustBounds(); +}; + +ScrollFrameMorph.prototype.setContents = function (aMorph) { + this.contents.children.forEach(function (m) { + m.destroy(); + }); + this.contents.children = []; + aMorph.setPosition(this.position().add(this.padding + 2)); + this.addContents(aMorph); +}; + +ScrollFrameMorph.prototype.setExtent = function (aPoint) { + if (this.isTextLineWrapping) { + this.contents.setPosition(this.position().copy()); + } + ScrollFrameMorph.uber.setExtent.call(this, aPoint); + this.contents.adjustBounds(); +}; + +// ScrollFrameMorph scrolling by dragging: + +ScrollFrameMorph.prototype.scrollX = function (steps) { + var cl = this.contents.left(), + l = this.left(), + cw = this.contents.width(), + r = this.right(), + newX; + + newX = cl + steps; + if (newX + cw < r) { + newX = r - cw; + } + if (newX > l) { + newX = l; + } + if (newX !== cl) { + this.contents.setLeft(newX); + } +}; + +ScrollFrameMorph.prototype.scrollY = function (steps) { + var ct = this.contents.top(), + t = this.top(), + ch = this.contents.height(), + b = this.bottom(), + newY; + + newY = ct + steps; + if (newY + ch < b) { + newY = b - ch; + } + if (newY > t) { + newY = t; + } + if (newY !== ct) { + this.contents.setTop(newY); + } +}; + +ScrollFrameMorph.prototype.step = function () { + nop(); +}; + +ScrollFrameMorph.prototype.mouseDownLeft = function (pos) { + if (!this.isScrollingByDragging) { + return null; + } + var world = this.root(), + oldPos = pos, + myself = this, + deltaX = 0, + deltaY = 0, + friction = 0.8; + + this.step = function () { + var newPos; + if (world.hand.mouseButton && + (world.hand.children.length === 0) && + (myself.bounds.containsPoint(world.hand.position()))) { + newPos = world.hand.bounds.origin; + deltaX = newPos.x - oldPos.x; + if (deltaX !== 0) { + myself.scrollX(deltaX); + } + deltaY = newPos.y - oldPos.y; + if (deltaY !== 0) { + myself.scrollY(deltaY); + } + oldPos = newPos; + } else { + if (!myself.hasVelocity) { + myself.step = function () { + nop(); + }; + } else { + if ((Math.abs(deltaX) < 0.5) && + (Math.abs(deltaY) < 0.5)) { + myself.step = function () { + nop(); + }; + } else { + deltaX = deltaX * friction; + myself.scrollX(Math.round(deltaX)); + deltaY = deltaY * friction; + myself.scrollY(Math.round(deltaY)); + } + } + } + this.adjustScrollBars(); + }; +}; + +ScrollFrameMorph.prototype.startAutoScrolling = function () { + var myself = this, + inset = MorphicPreferences.scrollBarSize * 3, + world = this.world(), + hand, + inner, + pos; + + if (!world) { + return null; + } + hand = world.hand; + if (!this.autoScrollTrigger) { + this.autoScrollTrigger = Date.now(); + } + this.step = function () { + pos = hand.bounds.origin; + inner = myself.bounds.insetBy(inset); + if ((myself.bounds.containsPoint(pos)) && + (!(inner.containsPoint(pos))) && + (hand.children.length > 0)) { + myself.autoScroll(pos); + } else { + myself.step = function () { + nop(); + }; + myself.autoScrollTrigger = null; + } + }; +}; + +ScrollFrameMorph.prototype.autoScroll = function (pos) { + var inset, area; + + if (Date.now() - this.autoScrollTrigger < 500) { + return null; + } + + inset = MorphicPreferences.scrollBarSize * 3; + area = this.topLeft().extent(new Point(this.width(), inset)); + if (area.containsPoint(pos)) { + this.scrollY(inset - (pos.y - this.top())); + } + area = this.topLeft().extent(new Point(inset, this.height())); + if (area.containsPoint(pos)) { + this.scrollX(inset - (pos.x - this.left())); + } + area = (new Point(this.right() - inset, this.top())) + .extent(new Point(inset, this.height())); + if (area.containsPoint(pos)) { + this.scrollX(-(inset - (this.right() - pos.x))); + } + area = (new Point(this.left(), this.bottom() - inset)) + .extent(new Point(this.width(), inset)); + if (area.containsPoint(pos)) { + this.scrollY(-(inset - (this.bottom() - pos.y))); + } + this.adjustScrollBars(); +}; + +// ScrollFrameMorph scrolling by editing text: + +ScrollFrameMorph.prototype.scrollCursorIntoView = function (morph) { + var txt = morph.target, + offset = txt.position().subtract(this.contents.position()), + ft = this.top() + this.padding, + fb = this.bottom() - this.padding; + this.contents.setExtent(txt.extent().add(offset).add(this.padding)); + if (morph.top() < ft) { + this.contents.setTop(this.contents.top() + ft - morph.top()); + morph.setTop(ft); + } else if (morph.bottom() > fb) { + this.contents.setBottom(this.contents.bottom() + fb - morph.bottom()); + morph.setBottom(fb); + } + this.adjustScrollBars(); +}; + +// ScrollFrameMorph events: + +ScrollFrameMorph.prototype.mouseScroll = function (y, x) { + if (y) { + this.scrollY(y * MorphicPreferences.mouseScrollAmount); + } + if (x) { + this.scrollX(x * MorphicPreferences.mouseScrollAmount); + } + this.adjustScrollBars(); +}; + +// ScrollFrameMorph duplicating: + +ScrollFrameMorph.prototype.copyRecordingReferences = function (dict) { + // inherited, see comment in Morph + var c = ScrollFrameMorph.uber.copyRecordingReferences.call( + this, + dict + ); + if (c.contents && dict[this.contents]) { + c.contents = (dict[this.contents]); + } + if (c.hBar && dict[this.hBar]) { + c.hBar = (dict[this.hBar]); + c.hBar.action = function (num) { + c.contents.setPosition( + new Point(c.left() - num, c.contents.position().y) + ); + }; + } + if (c.vBar && dict[this.vBar]) { + c.vBar = (dict[this.vBar]); + c.vBar.action = function (num) { + c.contents.setPosition( + new Point(c.contents.position().x, c.top() - num) + ); + }; + } + return c; +}; + +// ScrollFrameMorph menu: + +ScrollFrameMorph.prototype.developersMenu = function () { + var menu = ScrollFrameMorph.uber.developersMenu.call(this); + if (this.isTextLineWrapping) { + menu.addItem( + "auto line wrap off...", + 'toggleTextLineWrapping', + 'turn automatic\nline wrapping\noff' + ); + } else { + menu.addItem( + "auto line wrap on...", + 'toggleTextLineWrapping', + 'enable automatic\nline wrapping' + ); + } + return menu; +}; + + +ScrollFrameMorph.prototype.toggleTextLineWrapping = function () { + this.isTextLineWrapping = !this.isTextLineWrapping; +}; + +// ListMorph /////////////////////////////////////////////////////////// + +ListMorph.prototype = new ScrollFrameMorph(); +ListMorph.prototype.constructor = ListMorph; +ListMorph.uber = ScrollFrameMorph.prototype; + +function ListMorph(elements, labelGetter, format, doubleClickAction) { +/* + passing a format is optional. If the format parameter is specified + it has to be of the following pattern: + + [ + [, ], + ['bold', ], + ['italic', ], + ... + ] + + multiple conditions can be passed in such a format list, the + last predicate to evaluate true when given the list element sets + the given format category (color, bold, italic). + If no condition is met, the default format (color black, non-bold, + non-italic) will be assigned. + + An example of how to use fomats can be found in the InspectorMorph's + "markOwnProperties" mechanism. +*/ + this.init( + elements || [], + labelGetter || function (element) { + if (isString(element)) { + return element; + } + if (element.toSource) { + return element.toSource(); + } + return element.toString(); + }, + format || [], + doubleClickAction // optional callback + ); +} + +ListMorph.prototype.init = function ( + elements, + labelGetter, + format, + doubleClickAction +) { + ListMorph.uber.init.call(this); + + this.contents.acceptsDrops = false; + this.color = new Color(255, 255, 255); + this.hBar.alpha = 0.6; + this.vBar.alpha = 0.6; + this.elements = elements || []; + this.labelGetter = labelGetter; + this.format = format; + this.listContents = null; + this.selected = null; // actual element currently selected + this.active = null; // menu item representing the selected element + this.action = null; + this.doubleClickAction = doubleClickAction || null; + this.acceptsDrops = false; + this.buildListContents(); +}; + +ListMorph.prototype.buildListContents = function () { + var myself = this; + if (this.listContents) { + this.listContents.destroy(); + } + this.listContents = new MenuMorph( + this.select, + null, + this + ); + if (this.elements.length === 0) { + this.elements = ['(empty)']; + } + this.elements.forEach(function (element) { + var color = null, + bold = false, + italic = false; + + myself.format.forEach(function (pair) { + if (pair[1].call(null, element)) { + if (pair[0] === 'bold') { + bold = true; + } else if (pair[0] === 'italic') { + italic = true; + } else { // assume it's a color + color = pair[0]; + } + } + }); + myself.listContents.addItem( + myself.labelGetter(element), // label string + element, // action + null, // hint + color, + bold, + italic, + myself.doubleClickAction + ); + }); + this.listContents.setPosition(this.contents.position()); + this.listContents.isListContents = true; + this.listContents.drawNew(); + this.addContents(this.listContents); +}; + +ListMorph.prototype.select = function (item, trigger) { + this.selected = item; + this.active = trigger; + if (this.action) { + this.action.call(null, item); + } +}; + +ListMorph.prototype.setExtent = function (aPoint) { + var lb = this.listContents.bounds, + nb = this.bounds.origin.copy().corner( + this.bounds.origin.add(aPoint) + ); + + if (nb.right() > lb.right() && nb.width() <= lb.width()) { + this.listContents.setRight(nb.right()); + } + if (nb.bottom() > lb.bottom() && nb.height() <= lb.height()) { + this.listContents.setBottom(nb.bottom()); + } + ListMorph.uber.setExtent.call(this, aPoint); +}; + +// StringFieldMorph //////////////////////////////////////////////////// + +// StringFieldMorph inherit from FrameMorph: + +StringFieldMorph.prototype = new FrameMorph(); +StringFieldMorph.prototype.constructor = StringFieldMorph; +StringFieldMorph.uber = FrameMorph.prototype; + +function StringFieldMorph( + defaultContents, + minWidth, + fontSize, + fontStyle, + bold, + italic, + isNumeric +) { + this.init( + defaultContents || '', + minWidth || 100, + fontSize || 12, + fontStyle || 'sans-serif', + bold || false, + italic || false, + isNumeric + ); +} + +StringFieldMorph.prototype.init = function ( + defaultContents, + minWidth, + fontSize, + fontStyle, + bold, + italic, + isNumeric +) { + this.defaultContents = defaultContents; + this.minWidth = minWidth; + this.fontSize = fontSize; + this.fontStyle = fontStyle; + this.isBold = bold; + this.isItalic = italic; + this.isNumeric = isNumeric || false; + this.text = null; + StringFieldMorph.uber.init.call(this); + this.color = new Color(255, 255, 255); + this.isEditable = true; + this.acceptsDrops = false; + this.drawNew(); +}; + +StringFieldMorph.prototype.drawNew = function () { + var txt; + txt = this.text ? this.string() : this.defaultContents; + this.text = null; + this.children.forEach(function (child) { + child.destroy(); + }); + this.children = []; + this.text = new StringMorph( + txt, + this.fontSize, + this.fontStyle, + this.isBold, + this.isItalic, + this.isNumeric + ); + + this.text.isNumeric = this.isNumeric; // for whichever reason... + this.text.setPosition(this.bounds.origin.copy()); + this.text.isEditable = this.isEditable; + this.text.isDraggable = false; + this.text.enableSelecting(); + this.silentSetExtent( + new Point( + Math.max(this.width(), this.minWidth), + this.text.height() + ) + ); + StringFieldMorph.uber.drawNew.call(this); + this.add(this.text); +}; + +StringFieldMorph.prototype.string = function () { + return this.text.text; +}; + +StringFieldMorph.prototype.mouseClickLeft = function (pos) { + if (this.isEditable) { + this.text.edit(); + } else { + this.escalateEvent('mouseClickLeft', pos); + } +}; + +// StringFieldMorph duplicating: + +StringFieldMorph.prototype.copyRecordingReferences = function (dict) { + // inherited, see comment in Morph + var c = StringFieldMorph.uber.copyRecordingReferences.call( + this, + dict + ); + if (c.text && dict[this.text]) { + c.text = (dict[this.text]); + } + return c; +}; + +// BouncerMorph //////////////////////////////////////////////////////// + +// I am a Demo of a stepping custom Morph + +var BouncerMorph; + +// Bouncers inherit from Morph: + +BouncerMorph.prototype = new Morph(); +BouncerMorph.prototype.constructor = BouncerMorph; +BouncerMorph.uber = Morph.prototype; + +// BouncerMorph instance creation: + +function BouncerMorph() { + this.init(); +} + +// BouncerMorph initialization: + +BouncerMorph.prototype.init = function (type, speed) { + BouncerMorph.uber.init.call(this); + this.fps = 50; + + // additional properties: + this.isStopped = false; + this.type = type || 'vertical'; + if (this.type === 'vertical') { + this.direction = 'down'; + } else { + this.direction = 'right'; + } + this.speed = speed || 1; +}; + +// BouncerMorph moving: + +BouncerMorph.prototype.moveUp = function () { + this.moveBy(new Point(0, -this.speed)); +}; + +BouncerMorph.prototype.moveDown = function () { + this.moveBy(new Point(0, this.speed)); +}; + +BouncerMorph.prototype.moveRight = function () { + this.moveBy(new Point(this.speed, 0)); +}; + +BouncerMorph.prototype.moveLeft = function () { + this.moveBy(new Point(-this.speed, 0)); +}; + +// BouncerMorph stepping: + +BouncerMorph.prototype.step = function () { + if (!this.isStopped) { + if (this.type === 'vertical') { + if (this.direction === 'down') { + this.moveDown(); + } else { + this.moveUp(); + } + if (this.fullBounds().top() < this.parent.top() && + this.direction === 'up') { + this.direction = 'down'; + } + if (this.fullBounds().bottom() > this.parent.bottom() && + this.direction === 'down') { + this.direction = 'up'; + } + } else if (this.type === 'horizontal') { + if (this.direction === 'right') { + this.moveRight(); + } else { + this.moveLeft(); + } + if (this.fullBounds().left() < this.parent.left() && + this.direction === 'left') { + this.direction = 'right'; + } + if (this.fullBounds().right() > this.parent.right() && + this.direction === 'right') { + this.direction = 'left'; + } + } + } +}; + +// HandMorph /////////////////////////////////////////////////////////// + +// I represent the Mouse cursor + +// HandMorph inherits from Morph: + +HandMorph.prototype = new Morph(); +HandMorph.prototype.constructor = HandMorph; +HandMorph.uber = Morph.prototype; + +// HandMorph instance creation: + +function HandMorph(aWorld) { + this.init(aWorld); +} + +// HandMorph initialization: + +HandMorph.prototype.init = function (aWorld) { + HandMorph.uber.init.call(this); + this.bounds = new Rectangle(); + + // additional properties: + this.world = aWorld; + this.mouseButton = null; + this.mouseOverList = []; + this.mouseDownMorph = null; + this.morphToGrab = null; + this.grabOrigin = null; + this.temporaries = []; + this.touchHoldTimeout = null; +}; + +HandMorph.prototype.changed = function () { + var b; + if (this.world !== null) { + b = this.fullBounds(); + if (!b.extent().eq(new Point())) { + this.world.broken.push(this.fullBounds().spread()); + } + } + +}; + +// HandMorph navigation: + +HandMorph.prototype.morphAtPointer = function () { + var morphs = this.world.allChildren().slice(0).reverse(), + myself = this, + result = null; + + morphs.forEach(function (m) { + if (m.visibleBounds().containsPoint(myself.bounds.origin) && + result === null && + m.isVisible && + (m.noticesTransparentClick || + (!m.isTransparentAt(myself.bounds.origin))) && + (!(m instanceof ShadowMorph))) { + result = m; + } + }); + if (result !== null) { + return result; + } + return this.world; +}; + +/* + alternative - more elegant and possibly more + performant - solution for morphAtPointer. + Has some issues, commented out for now + +HandMorph.prototype.morphAtPointer = function () { + var myself = this; + return this.world.topMorphSuchThat(function (m) { + return m.visibleBounds().containsPoint(myself.bounds.origin) && + m.isVisible && + (m.noticesTransparentClick || + (! m.isTransparentAt(myself.bounds.origin))) && + (! (m instanceof ShadowMorph)); + }); +}; +*/ + +HandMorph.prototype.allMorphsAtPointer = function () { + var morphs = this.world.allChildren(), + myself = this; + return morphs.filter(function (m) { + return m.isVisible && + m.visibleBounds().containsPoint(myself.bounds.origin); + }); +}; + +// HandMorph dragging and dropping: +/* + drag 'n' drop events, method(arg) -> receiver: + + prepareToBeGrabbed(handMorph) -> grabTarget + reactToGrabOf(grabbedMorph) -> oldParent + wantsDropOf(morphToDrop) -> newParent + justDropped(handMorph) -> droppedMorph + reactToDropOf(droppedMorph, handMorph) -> newParent +*/ + +HandMorph.prototype.dropTargetFor = function (aMorph) { + var target = this.morphAtPointer(); + while (!target.wantsDropOf(aMorph)) { + target = target.parent; + } + return target; +}; + +HandMorph.prototype.grab = function (aMorph) { + var oldParent = aMorph.parent; + if (aMorph instanceof WorldMorph) { + return null; + } + if (this.children.length === 0) { + this.world.stopEditing(); + this.grabOrigin = aMorph.situation(); + aMorph.addShadow(); + if (aMorph.prepareToBeGrabbed) { + aMorph.prepareToBeGrabbed(this); + } + this.add(aMorph); + this.changed(); + if (oldParent && oldParent.reactToGrabOf) { + oldParent.reactToGrabOf(aMorph); + } + } +}; + +HandMorph.prototype.drop = function () { + var target, morphToDrop; + if (this.children.length !== 0) { + morphToDrop = this.children[0]; + target = this.dropTargetFor(morphToDrop); + this.changed(); + target.add(morphToDrop); + morphToDrop.changed(); + morphToDrop.removeShadow(); + this.children = []; + this.setExtent(new Point()); + if (morphToDrop.justDropped) { + morphToDrop.justDropped(this); + } + if (target.reactToDropOf) { + target.reactToDropOf(morphToDrop, this); + } + this.dragOrigin = null; + } +}; + +// HandMorph event dispatching: +/* + mouse events: + + mouseDownLeft + mouseDownRight + mouseClickLeft + mouseClickRight + mouseDoubleClick + mouseEnter + mouseLeave + mouseEnterDragging + mouseLeaveDragging + mouseMove + mouseScroll +*/ + +HandMorph.prototype.processMouseDown = function (event) { + var morph, expectedClick, actualClick; + + this.destroyTemporaries(); + this.morphToGrab = null; + if (this.children.length !== 0) { + this.drop(); + this.mouseButton = null; + } else { + morph = this.morphAtPointer(); + if (this.world.activeMenu) { + if (!contains( + morph.allParents(), + this.world.activeMenu + )) { + this.world.activeMenu.destroy(); + } else { + clearInterval(this.touchHoldTimeout); + } + } + if (this.world.activeHandle) { + if (morph !== this.world.activeHandle) { + this.world.activeHandle.destroy(); + } + } + if (this.world.cursor) { + if (morph !== this.world.cursor.target) { + this.world.stopEditing(); + } + } + if (!morph.mouseMove) { + this.morphToGrab = morph.rootForGrab(); + } + if (event.button === 2 || event.ctrlKey) { + this.mouseButton = 'right'; + actualClick = 'mouseDownRight'; + expectedClick = 'mouseClickRight'; + } else { + this.mouseButton = 'left'; + actualClick = 'mouseDownLeft'; + expectedClick = 'mouseClickLeft'; + } + this.mouseDownMorph = morph; + while (!this.mouseDownMorph[expectedClick]) { + this.mouseDownMorph = this.mouseDownMorph.parent; + } + while (!morph[actualClick]) { + morph = morph.parent; + } + morph[actualClick](this.bounds.origin); + } +}; + +HandMorph.prototype.processTouchStart = function (event) { + var myself = this; + MorphicPreferences.isTouchDevice = true; + clearInterval(this.touchHoldTimeout); + if (event.touches.length === 1) { + this.touchHoldTimeout = setInterval( // simulate mouseRightClick + function () { + myself.processMouseDown({button: 2}); + myself.processMouseUp({button: 2}); + event.preventDefault(); + clearInterval(myself.touchHoldTimeout); + }, + 400 + ); + this.processMouseMove(event.touches[0]); // update my position + this.processMouseDown({button: 0}); + event.preventDefault(); + } +}; + +HandMorph.prototype.processTouchMove = function (event) { + MorphicPreferences.isTouchDevice = true; + if (event.touches.length === 1) { + var touch = event.touches[0]; + this.processMouseMove(touch); + clearInterval(this.touchHoldTimeout); + } +}; + +HandMorph.prototype.processTouchEnd = function (event) { + MorphicPreferences.isTouchDevice = true; + clearInterval(this.touchHoldTimeout); + nop(event); + this.processMouseUp({button: 0}); +}; + +HandMorph.prototype.processMouseUp = function () { + var morph = this.morphAtPointer(), + context, + contextMenu, + expectedClick; + + this.destroyTemporaries(); + if (this.children.length !== 0) { + this.drop(); + } else { + if (this.mouseButton === 'left') { + expectedClick = 'mouseClickLeft'; + } else { + expectedClick = 'mouseClickRight'; + if (this.mouseButton) { + context = morph; + contextMenu = context.contextMenu(); + while ((!contextMenu) && + context.parent) { + context = context.parent; + contextMenu = context.contextMenu(); + } + if (contextMenu) { + contextMenu.popUpAtHand(this.world); + } + } + } + while (!morph[expectedClick]) { + morph = morph.parent; + } + morph[expectedClick](this.bounds.origin); + } + this.mouseButton = null; +}; + +HandMorph.prototype.processDoubleClick = function () { + var morph = this.morphAtPointer(); + + this.destroyTemporaries(); + if (this.children.length !== 0) { + this.drop(); + } else { + while (morph && !morph.mouseDoubleClick) { + morph = morph.parent; + } + if (morph) { + morph.mouseDoubleClick(this.bounds.origin); + } + } + this.mouseButton = null; +}; + +HandMorph.prototype.processMouseMove = function (event) { + var pos, + posInDocument = getDocumentPositionOf(this.world.worldCanvas), + mouseOverNew, + myself = this, + morph, + topMorph, + fb; + + pos = new Point( + event.pageX - posInDocument.x, + event.pageY - posInDocument.y + ); + + this.setPosition(pos); + + // determine the new mouse-over-list: + // mouseOverNew = this.allMorphsAtPointer(); + mouseOverNew = this.morphAtPointer().allParents(); + + if ((this.children.length === 0) && + (this.mouseButton === 'left')) { + topMorph = this.morphAtPointer(); + morph = topMorph.rootForGrab(); + if (topMorph.mouseMove) { + topMorph.mouseMove(pos); + } + + // if a morph is marked for grabbing, just grab it + if (this.morphToGrab) { + if (this.morphToGrab.isDraggable) { + morph = this.morphToGrab; + this.grab(morph); + } else if (this.morphToGrab.isTemplate) { + morph = this.morphToGrab.fullCopy(); + morph.isTemplate = false; + morph.isDraggable = true; + this.grab(morph); + this.grabOrigin = this.morphToGrab.situation(); + } + if (morph) { + // if the mouse has left its fullBounds, center it + fb = morph.fullBounds(); + if (!fb.containsPoint(pos)) { + this.bounds.origin = fb.center(); + this.grab(morph); + this.setPosition(pos); + } + } + } + +/* + original, more cautious code for grabbing Morphs, + retained in case of needing to fall back: + + if (morph === this.morphToGrab) { + if (morph.isDraggable) { + this.grab(morph); + } else if (morph.isTemplate) { + morph = morph.fullCopy(); + morph.isTemplate = false; + morph.isDraggable = true; + this.grab(morph); + } + } +*/ + + } + + this.mouseOverList.forEach(function (old) { + if (!contains(mouseOverNew, old)) { + if (old.mouseLeave) { + old.mouseLeave(); + } + if (old.mouseLeaveDragging && myself.mouseButton) { + old.mouseLeaveDragging(); + } + } + }); + mouseOverNew.forEach(function (newMorph) { + if (!contains(myself.mouseOverList, newMorph)) { + if (newMorph.mouseEnter) { + newMorph.mouseEnter(); + } + if (newMorph.mouseEnterDragging && myself.mouseButton) { + newMorph.mouseEnterDragging(); + } + } + + // autoScrolling support: + if (myself.children.length > 0) { + if (newMorph instanceof ScrollFrameMorph) { + if (!newMorph.bounds.insetBy( + MorphicPreferences.scrollBarSize * 3 + ).containsPoint(myself.bounds.origin)) { + newMorph.startAutoScrolling(); + } + } + } + }); + this.mouseOverList = mouseOverNew; +}; + +HandMorph.prototype.processMouseScroll = function (event) { + var morph = this.morphAtPointer(); + while (morph && !morph.mouseScroll) { + morph = morph.parent; + } + if (morph) { + morph.mouseScroll( + (event.detail / -3) || ( + Object.prototype.hasOwnProperty.call( + event, + 'wheelDeltaY' + ) ? + event.wheelDeltaY / 120 : + event.wheelDelta / 120 + ), + event.wheelDeltaX / 120 || 0 + ); + } +}; + +/* + drop event: + + droppedImage + droppedSVG + droppedAudio + droppedText +*/ + +HandMorph.prototype.processDrop = function (event) { +/* + find out whether an external image or audio file was dropped + onto the world canvas, turn it into an offscreen canvas or audio + element and dispatch the + + droppedImage(canvas, name) + droppedSVG(image, name) + droppedAudio(audio, name) + + events to interested Morphs at the mouse pointer +*/ + var files = event instanceof FileList ? event + : event.target.files || event.dataTransfer.files, + file, + url = event.dataTransfer ? + event.dataTransfer.getData('URL') : null, + txt = event.dataTransfer ? + event.dataTransfer.getData('Text/HTML') : null, + src, + target = this.morphAtPointer(), + img = new Image(), + canvas, + i; + + function readSVG(aFile) { + var pic = new Image(), + frd = new FileReader(); + while (!target.droppedSVG) { + target = target.parent; + } + pic.onload = function () { + target.droppedSVG(pic, aFile.name); + }; + frd = new FileReader(); + frd.onloadend = function (e) { + pic.src = e.target.result; + }; + frd.readAsDataURL(aFile); + } + + function readImage(aFile) { + var pic = new Image(), + frd = new FileReader(); + while (!target.droppedImage) { + target = target.parent; + } + pic.onload = function () { + canvas = newCanvas(new Point(pic.width, pic.height)); + canvas.getContext('2d').drawImage(pic, 0, 0); + target.droppedImage(canvas, aFile.name); + }; + frd = new FileReader(); + frd.onloadend = function (e) { + pic.src = e.target.result; + }; + frd.readAsDataURL(aFile); + } + + function readAudio(aFile) { + var snd = new Audio(), + frd = new FileReader(); + while (!target.droppedAudio) { + target = target.parent; + } + frd.onloadend = function (e) { + snd.src = e.target.result; + target.droppedAudio(snd, aFile.name); + }; + frd.readAsDataURL(aFile); + } + + function readText(aFile) { + var frd = new FileReader(); + while (!target.droppedText) { + target = target.parent; + } + frd.onloadend = function (e) { + target.droppedText(e.target.result, aFile.name); + }; + frd.readAsText(aFile); + } + + function readBinary(aFile) { + var frd = new FileReader(); + while (!target.droppedBinary) { + target = target.parent; + } + frd.onloadend = function (e) { + target.droppedBinary(e.target.result, aFile.name); + }; + frd.readAsArrayBuffer(aFile); + } + + function parseImgURL(html) { + var iurl = '', + idx, + c, + start = html.indexOf(' 0) { + for (i = 0; i < files.length; i += 1) { + file = files[i]; + if (file.type.indexOf("svg") !== -1 + && !MorphicPreferences.rasterizeSVGs) { + readSVG(file); + } else if (file.type.indexOf("image") === 0) { + readImage(file); + } else if (file.type.indexOf("audio") === 0) { + readAudio(file); + } else if (file.type.indexOf("text") === 0) { + readText(file); + } else { // assume it's meant to be binary + readBinary(file); + } + } + } else if (url) { + if ( + contains( + ['gif', 'png', 'jpg', 'jpeg', 'bmp'], + url.slice(url.lastIndexOf('.') + 1).toLowerCase() + ) + ) { + while (!target.droppedImage) { + target = target.parent; + } + img = new Image(); + img.onload = function () { + canvas = newCanvas(new Point(img.width, img.height)); + canvas.getContext('2d').drawImage(img, 0, 0); + target.droppedImage(canvas); + }; + img.src = url; + } + } else if (txt) { + while (!target.droppedImage) { + target = target.parent; + } + img = new Image(); + img.onload = function () { + canvas = newCanvas(new Point(img.width, img.height)); + canvas.getContext('2d').drawImage(img, 0, 0); + target.droppedImage(canvas); + }; + src = parseImgURL(txt); + if (src) {img.src = src; } + } +}; + +// HandMorph tools + +HandMorph.prototype.destroyTemporaries = function () { +/* + temporaries are just an array of morphs which will be deleted upon + the next mouse click, or whenever another temporary Morph decides + that it needs to remove them. The primary purpose of temporaries is + to display tools tips of speech bubble help. +*/ + var myself = this; + this.temporaries.forEach(function (morph) { + if (!(morph.isClickable + && morph.bounds.containsPoint(myself.position()))) { + morph.destroy(); + myself.temporaries.splice(myself.temporaries.indexOf(morph), 1); + } + }); +}; + +// HandMorph dragging optimization + +HandMorph.prototype.moveBy = function (delta) { + Morph.prototype.trackChanges = false; + HandMorph.uber.moveBy.call(this, delta); + Morph.prototype.trackChanges = true; + this.fullChanged(); +}; + + +// WorldMorph ////////////////////////////////////////////////////////// + +// I represent the element + +// WorldMorph inherits from FrameMorph: + +WorldMorph.prototype = new FrameMorph(); +WorldMorph.prototype.constructor = WorldMorph; +WorldMorph.uber = FrameMorph.prototype; + +// WorldMorph instance creation: + +function WorldMorph(aCanvas, fillPage) { + this.init(aCanvas, fillPage); +} + +// WorldMorph initialization: + +WorldMorph.prototype.init = function (aCanvas, fillPage) { + WorldMorph.uber.init.call(this); + this.color = new Color(205, 205, 205); // (130, 130, 130) + this.alpha = 1; + this.bounds = new Rectangle(0, 0, aCanvas.width, aCanvas.height); + this.drawNew(); + this.isVisible = true; + this.isDraggable = false; + this.currentKey = null; // currently pressed key code + this.worldCanvas = aCanvas; + + // additional properties: + this.stamp = Date.now(); // reference in multi-world setups + while (this.stamp === Date.now()) {nop(); } + this.stamp = Date.now(); + + this.useFillPage = fillPage; + if (this.useFillPage === undefined) { + this.useFillPage = true; + } + this.isDevMode = false; + this.broken = []; + this.hand = new HandMorph(this); + this.keyboardReceiver = null; + this.lastEditedText = null; + this.cursor = null; + this.activeMenu = null; + this.activeHandle = null; + this.virtualKeyboard = null; + + this.initEventListeners(); +}; + +// World Morph display: + +WorldMorph.prototype.brokenFor = function (aMorph) { + // private + var fb = aMorph.fullBounds(); + return this.broken.filter(function (rect) { + return rect.intersects(fb); + }); +}; + +WorldMorph.prototype.fullDrawOn = function (aCanvas, aRect) { + WorldMorph.uber.fullDrawOn.call(this, aCanvas, aRect); + this.hand.fullDrawOn(aCanvas, aRect); +}; + +WorldMorph.prototype.updateBroken = function () { + var myself = this; + this.broken.forEach(function (rect) { + if (rect.extent().gt(new Point(0, 0))) { + myself.fullDrawOn(myself.worldCanvas, rect); + } + }); + this.broken = []; +}; + +WorldMorph.prototype.doOneCycle = function () { + this.stepFrame(); + this.updateBroken(); +}; + +WorldMorph.prototype.fillPage = function () { + var pos = getDocumentPositionOf(this.worldCanvas), + clientHeight = window.innerHeight, + clientWidth = window.innerWidth, + myself = this; + + + if (pos.x > 0) { + this.worldCanvas.style.position = "absolute"; + this.worldCanvas.style.left = "0px"; + pos.x = 0; + } + if (pos.y > 0) { + this.worldCanvas.style.position = "absolute"; + this.worldCanvas.style.top = "0px"; + pos.y = 0; + } + if (document.body.scrollTop) { // scrolled down b/c of viewport scaling + clientHeight = document.documentElement.clientHeight; + } + if (document.body.scrollLeft) { // scrolled left b/c of viewport scaling + clientWidth = document.documentElement.clientWidth; + } + + if (this.worldCanvas.width !== clientWidth) { + this.worldCanvas.width = clientWidth; + this.setWidth(clientWidth); + } + if (this.worldCanvas.height !== clientHeight) { + this.worldCanvas.height = clientHeight; + this.setHeight(clientHeight); + } + this.children.forEach(function (child) { + if (child.reactToWorldResize) { + child.reactToWorldResize(myself.bounds.copy()); + } + }); +}; + +// WorldMorph global pixel access: + +WorldMorph.prototype.getGlobalPixelColor = function (point) { +/* + answer the color at the given point. + + Note: for some strange reason this method works fine if the page is + opened via HTTP, but *not*, if it is opened from a local uri + (e.g. from a directory), in which case it's always null. + + This behavior is consistent throughout several browsers. I have no + clue what's behind this, apparently the imageData attribute of + canvas context only gets filled with meaningful data if transferred + via HTTP ??? + + This is somewhat of a showstopper for color detection in a planned + offline version of Snap. + + The issue has also been discussed at: (join lines before pasting) + http://stackoverflow.com/questions/4069400/ + canvas-getimagedata-doesnt-work-when-running-locally-on-windows- + security-excep + + The suggestion solution appears to work, since the settings are + applied globally. +*/ + var dta = this.worldCanvas.getContext('2d').getImageData( + point.x, + point.y, + 1, + 1 + ).data; + return new Color(dta[0], dta[1], dta[2]); +}; + +// WorldMorph events: + +WorldMorph.prototype.initVirtualKeyboard = function () { + var myself = this; + + if (this.virtualKeyboard) { + document.body.removeChild(this.virtualKeyboard); + this.virtualKeyboard = null; + } + if (!MorphicPreferences.isTouchDevice + || !MorphicPreferences.useVirtualKeyboard) { + return; + } + this.virtualKeyboard = document.createElement("input"); + this.virtualKeyboard.type = "text"; + this.virtualKeyboard.style.color = "transparent"; + this.virtualKeyboard.style.backgroundColor = "transparent"; + this.virtualKeyboard.style.border = "none"; + this.virtualKeyboard.style.outline = "none"; + this.virtualKeyboard.style.position = "absolute"; + this.virtualKeyboard.style.top = "0px"; + this.virtualKeyboard.style.left = "0px"; + this.virtualKeyboard.style.width = "0px"; + this.virtualKeyboard.style.height = "0px"; + this.virtualKeyboard.autocapitalize = "none"; // iOS specific + document.body.appendChild(this.virtualKeyboard); + + this.virtualKeyboard.addEventListener( + "keydown", + function (event) { + // remember the keyCode in the world's currentKey property + myself.currentKey = event.keyCode; + if (myself.keyboardReceiver) { + myself.keyboardReceiver.processKeyDown(event); + } + // supress backspace override + if (event.keyIdentifier === 'U+0008' || + event.keyIdentifier === 'Backspace') { + event.preventDefault(); + } + // supress tab override and make sure tab gets + // received by all browsers + if (event.keyIdentifier === 'U+0009' || + event.keyIdentifier === 'Tab') { + if (myself.keyboardReceiver) { + myself.keyboardReceiver.processKeyPress(event); + } + event.preventDefault(); + } + }, + false + ); + + this.virtualKeyboard.addEventListener( + "keyup", + function (event) { + // flush the world's currentKey property + myself.currentKey = null; + // dispatch to keyboard receiver + if (myself.keyboardReceiver) { + if (myself.keyboardReceiver.processKeyUp) { + myself.keyboardReceiver.processKeyUp(event); + } + } + event.preventDefault(); + }, + false + ); + + this.virtualKeyboard.addEventListener( + "keypress", + function (event) { + if (myself.keyboardReceiver) { + myself.keyboardReceiver.processKeyPress(event); + } + event.preventDefault(); + }, + false + ); +}; + +WorldMorph.prototype.initEventListeners = function () { + var canvas = this.worldCanvas, myself = this; + + if (myself.useFillPage) { + myself.fillPage(); + } else { + this.changed(); + } + + canvas.addEventListener( + "mousedown", + function (event) { + event.preventDefault(); + canvas.focus(); + myself.hand.processMouseDown(event); + }, + false + ); + + canvas.addEventListener( + "touchstart", + function (event) { + myself.hand.processTouchStart(event); + }, + false + ); + + canvas.addEventListener( + "mouseup", + function (event) { + event.preventDefault(); + myself.hand.processMouseUp(event); + }, + false + ); + + canvas.addEventListener( + "dblclick", + function (event) { + event.preventDefault(); + myself.hand.processDoubleClick(event); + }, + false + ); + + canvas.addEventListener( + "touchend", + function (event) { + myself.hand.processTouchEnd(event); + }, + false + ); + + canvas.addEventListener( + "mousemove", + function (event) { + myself.hand.processMouseMove(event); + }, + false + ); + + canvas.addEventListener( + "touchmove", + function (event) { + myself.hand.processTouchMove(event); + }, + false + ); + + canvas.addEventListener( + "contextmenu", + function (event) { + // suppress context menu for Mac-Firefox + event.preventDefault(); + }, + false + ); + + canvas.addEventListener( + "keydown", + function (event) { + // remember the keyCode in the world's currentKey property + myself.currentKey = event.keyCode; + if (myself.keyboardReceiver) { + myself.keyboardReceiver.processKeyDown(event); + } + // supress backspace override + if (event.keyIdentifier === 'U+0008' || + event.keyIdentifier === 'Backspace') { + event.preventDefault(); + } + // supress tab override and make sure tab gets + // received by all browsers + if (event.keyIdentifier === 'U+0009' || + event.keyIdentifier === 'Tab') { + if (myself.keyboardReceiver) { + myself.keyboardReceiver.processKeyPress(event); + } + event.preventDefault(); + } + }, + false + ); + + canvas.addEventListener( + "keyup", + function (event) { + // flush the world's currentKey property + myself.currentKey = null; + // dispatch to keyboard receiver + if (myself.keyboardReceiver) { + if (myself.keyboardReceiver.processKeyUp) { + myself.keyboardReceiver.processKeyUp(event); + } + } + event.preventDefault(); + }, + false + ); + + canvas.addEventListener( + "keypress", + function (event) { + if (myself.keyboardReceiver) { + myself.keyboardReceiver.processKeyPress(event); + } + event.preventDefault(); + }, + false + ); + + canvas.addEventListener( // Safari, Chrome + "mousewheel", + function (event) { + myself.hand.processMouseScroll(event); + event.preventDefault(); + }, + false + ); + canvas.addEventListener( // Firefox + "DOMMouseScroll", + function (event) { + myself.hand.processMouseScroll(event); + event.preventDefault(); + }, + false + ); + + document.body.addEventListener( + "paste", + function (event) { + var txt = event.clipboardData.getData("Text"); + if (txt && myself.cursor) { + myself.cursor.insert(txt); + } + }, + false + ); + + window.addEventListener( + "dragover", + function (event) { + event.preventDefault(); + }, + false + ); + window.addEventListener( + "drop", + function (event) { + myself.hand.processDrop(event); + event.preventDefault(); + }, + false + ); + + window.addEventListener( + "resize", + function () { + if (myself.useFillPage) { + myself.fillPage(); + } + }, + false + ); + + window.onbeforeunload = function (evt) { + var e = evt || window.event, + msg = "Are you sure you want to leave?"; + // For IE and Firefox + if (e) { + e.returnValue = msg; + } + // For Safari / chrome + return msg; + }; +}; + +WorldMorph.prototype.mouseDownLeft = function () { + nop(); +}; + +WorldMorph.prototype.mouseClickLeft = function () { + nop(); +}; + +WorldMorph.prototype.mouseDownRight = function () { + nop(); +}; + +WorldMorph.prototype.mouseClickRight = function () { + nop(); +}; + +WorldMorph.prototype.wantsDropOf = function () { + // allow handle drops if any drops are allowed + return this.acceptsDrops; +}; + +WorldMorph.prototype.droppedImage = function () { + return null; +}; + +WorldMorph.prototype.droppedSVG = function () { + return null; +}; + +// WorldMorph text field tabbing: + +WorldMorph.prototype.nextTab = function (editField) { + var next = this.nextEntryField(editField); + if (next) { + editField.clearSelection(); + next.selectAll(); + next.edit(); + } +}; + +WorldMorph.prototype.previousTab = function (editField) { + var prev = this.previousEntryField(editField); + if (prev) { + editField.clearSelection(); + prev.selectAll(); + prev.edit(); + } +}; + +// WorldMorph menu: + +WorldMorph.prototype.contextMenu = function () { + var menu; + + if (this.isDevMode) { + menu = new MenuMorph(this, this.constructor.name || + this.constructor.toString().split(' ')[1].split('(')[0]); + } else { + menu = new MenuMorph(this, 'Morphic'); + } + if (this.isDevMode) { + menu.addItem("demo...", 'userCreateMorph', 'sample morphs'); + menu.addLine(); + menu.addItem("hide all...", 'hideAll'); + menu.addItem("show all...", 'showAllHiddens'); + menu.addItem( + "move all inside...", + 'keepAllSubmorphsWithin', + 'keep all submorphs\nwithin and visible' + ); + menu.addItem( + "inspect...", + 'inspect', + 'open a window on\nall properties' + ); + menu.addLine(); + menu.addItem( + "restore display", + 'changed', + 'redraw the\nscreen once' + ); + menu.addItem( + "fill page...", + 'fillPage', + 'let the World automatically\nadjust to browser resizings' + ); + if (useBlurredShadows) { + menu.addItem( + "sharp shadows...", + 'toggleBlurredShadows', + 'sharp drop shadows\nuse for old browsers' + ); + } else { + menu.addItem( + "blurred shadows...", + 'toggleBlurredShadows', + 'blurry shades,\n use for new browsers' + ); + } + menu.addItem( + "color...", + function () { + this.pickColor( + menu.title + '\ncolor:', + this.setColor, + this, + this.color + ); + }, + 'choose the World\'s\nbackground color' + ); + if (MorphicPreferences === standardSettings) { + menu.addItem( + "touch screen settings", + 'togglePreferences', + 'bigger menu fonts\nand sliders' + ); + } else { + menu.addItem( + "standard settings", + 'togglePreferences', + 'smaller menu fonts\nand sliders' + ); + } + menu.addLine(); + } + if (this.isDevMode) { + menu.addItem( + "user mode...", + 'toggleDevMode', + 'disable developers\'\ncontext menus' + ); + } else { + menu.addItem("development mode...", 'toggleDevMode'); + } + menu.addItem("about morphic.js...", 'about'); + return menu; +}; + +WorldMorph.prototype.userCreateMorph = function () { + var myself = this, menu, newMorph; + + function create(aMorph) { + aMorph.isDraggable = true; + aMorph.pickUp(myself); + } + + menu = new MenuMorph(this, 'make a morph'); + menu.addItem('rectangle', function () { + create(new Morph()); + }); + menu.addItem('box', function () { + create(new BoxMorph()); + }); + menu.addItem('circle box', function () { + create(new CircleBoxMorph()); + }); + menu.addLine(); + menu.addItem('slider', function () { + create(new SliderMorph()); + }); + menu.addItem('frame', function () { + newMorph = new FrameMorph(); + newMorph.setExtent(new Point(350, 250)); + create(newMorph); + }); + menu.addItem('scroll frame', function () { + newMorph = new ScrollFrameMorph(); + newMorph.contents.acceptsDrops = true; + newMorph.contents.adjustBounds(); + newMorph.setExtent(new Point(350, 250)); + create(newMorph); + }); + menu.addItem('handle', function () { + create(new HandleMorph()); + }); + menu.addLine(); + menu.addItem('string', function () { + newMorph = new StringMorph('Hello, World!'); + newMorph.isEditable = true; + create(newMorph); + }); + menu.addItem('text', function () { + newMorph = new TextMorph( + "Ich wei\u00DF nicht, was soll es bedeuten, dass ich so " + + "traurig bin, ein M\u00E4rchen aus uralten Zeiten, das " + + "kommt mir nicht aus dem Sinn. Die Luft ist k\u00FChl " + + "und es dunkelt, und ruhig flie\u00DFt der Rhein; der " + + "Gipfel des Berges funkelt im Abendsonnenschein. " + + "Die sch\u00F6nste Jungfrau sitzet dort oben wunderbar, " + + "ihr gold'nes Geschmeide blitzet, sie k\u00E4mmt ihr " + + "goldenes Haar, sie k\u00E4mmt es mit goldenem Kamme, " + + "und singt ein Lied dabei; das hat eine wundersame, " + + "gewalt'ge Melodei. Den Schiffer im kleinen " + + "Schiffe, ergreift es mit wildem Weh; er schaut " + + "nicht die Felsenriffe, er schaut nur hinauf in " + + "die H\u00F6h'. Ich glaube, die Wellen verschlingen " + + "am Ende Schiffer und Kahn, und das hat mit ihrem " + + "Singen, die Loreley getan." + ); + newMorph.isEditable = true; + newMorph.maxWidth = 300; + newMorph.drawNew(); + create(newMorph); + }); + menu.addItem('speech bubble', function () { + newMorph = new SpeechBubbleMorph('Hello, World!'); + create(newMorph); + }); + menu.addLine(); + menu.addItem('gray scale palette', function () { + create(new GrayPaletteMorph()); + }); + menu.addItem('color palette', function () { + create(new ColorPaletteMorph()); + }); + menu.addItem('color picker', function () { + create(new ColorPickerMorph()); + }); + menu.addLine(); + menu.addItem('sensor demo', function () { + newMorph = new MouseSensorMorph(); + newMorph.setColor(new Color(230, 200, 100)); + newMorph.edge = 35; + newMorph.border = 15; + newMorph.borderColor = new Color(200, 100, 50); + newMorph.alpha = 0.2; + newMorph.setExtent(new Point(100, 100)); + create(newMorph); + }); + menu.addItem('animation demo', function () { + var foo, bar, baz, garply, fred; + + foo = new BouncerMorph(); + foo.setPosition(new Point(50, 20)); + foo.setExtent(new Point(300, 200)); + foo.alpha = 0.9; + foo.speed = 3; + + bar = new BouncerMorph(); + bar.setColor(new Color(50, 50, 50)); + bar.setPosition(new Point(80, 80)); + bar.setExtent(new Point(80, 250)); + bar.type = 'horizontal'; + bar.direction = 'right'; + bar.alpha = 0.9; + bar.speed = 5; + + baz = new BouncerMorph(); + baz.setColor(new Color(20, 20, 20)); + baz.setPosition(new Point(90, 140)); + baz.setExtent(new Point(40, 30)); + baz.type = 'horizontal'; + baz.direction = 'right'; + baz.speed = 3; + + garply = new BouncerMorph(); + garply.setColor(new Color(200, 20, 20)); + garply.setPosition(new Point(90, 140)); + garply.setExtent(new Point(20, 20)); + garply.type = 'vertical'; + garply.direction = 'up'; + garply.speed = 8; + + fred = new BouncerMorph(); + fred.setColor(new Color(20, 200, 20)); + fred.setPosition(new Point(120, 140)); + fred.setExtent(new Point(20, 20)); + fred.type = 'vertical'; + fred.direction = 'down'; + fred.speed = 4; + + bar.add(garply); + bar.add(baz); + foo.add(fred); + foo.add(bar); + + create(foo); + }); + menu.addItem('pen', function () { + create(new PenMorph()); + }); + if (myself.customMorphs) { + menu.addLine(); + myself.customMorphs().forEach(function (morph) { + menu.addItem(morph.toString(), function () { + create(morph); + }); + }); + } + menu.popUpAtHand(this); +}; + +WorldMorph.prototype.toggleDevMode = function () { + this.isDevMode = !this.isDevMode; +}; + +WorldMorph.prototype.hideAll = function () { + this.children.forEach(function (child) { + child.hide(); + }); +}; + +WorldMorph.prototype.showAllHiddens = function () { + this.forAllChildren(function (child) { + if (!child.isVisible) { + child.show(); + } + }); +}; + +WorldMorph.prototype.about = function () { + var versions = '', module; + + for (module in modules) { + if (Object.prototype.hasOwnProperty.call(modules, module)) { + versions += ('\n' + module + ' (' + modules[module] + ')'); + } + } + if (versions !== '') { + versions = '\n\nmodules:\n\n' + + 'morphic (' + morphicVersion + ')' + + versions; + } + + this.inform( + 'morphic.js\n\n' + + 'a lively Web GUI\ninspired by Squeak\n' + + morphicVersion + + '\n\nwritten by Jens M\u00F6nig\njens@moenig.org' + + versions + ); +}; + +WorldMorph.prototype.edit = function (aStringOrTextMorph) { + var pos = getDocumentPositionOf(this.worldCanvas); + + if (!aStringOrTextMorph.isEditable) { + return null; + } + if (this.cursor) { + this.cursor.destroy(); + } + if (this.lastEditedText) { + this.lastEditedText.clearSelection(); + } + this.cursor = new CursorMorph(aStringOrTextMorph); + aStringOrTextMorph.parent.add(this.cursor); + this.keyboardReceiver = this.cursor; + + this.initVirtualKeyboard(); + if (MorphicPreferences.isTouchDevice + && MorphicPreferences.useVirtualKeyboard) { + this.virtualKeyboard.style.top = this.cursor.top() + pos.y + "px"; + this.virtualKeyboard.style.left = this.cursor.left() + pos.x + "px"; + this.virtualKeyboard.focus(); + } + + if (MorphicPreferences.useSliderForInput) { + if (!aStringOrTextMorph.parentThatIsA(MenuMorph)) { + this.slide(aStringOrTextMorph); + } + } +}; + +WorldMorph.prototype.slide = function (aStringOrTextMorph) { + // display a slider for numeric text entries + var val = parseFloat(aStringOrTextMorph.text), + menu, + slider; + + if (isNaN(val)) { + val = 0; + } + menu = new MenuMorph(); + slider = new SliderMorph( + val - 25, + val + 25, + val, + 10, + 'horizontal' + ); + slider.alpha = 1; + slider.color = new Color(225, 225, 225); + slider.button.color = menu.borderColor; + slider.button.highlightColor = slider.button.color.copy(); + slider.button.highlightColor.b += 100; + slider.button.pressColor = slider.button.color.copy(); + slider.button.pressColor.b += 150; + slider.silentSetHeight(MorphicPreferences.scrollBarSize); + slider.silentSetWidth(MorphicPreferences.menuFontSize * 10); + slider.drawNew(); + slider.action = function (num) { + aStringOrTextMorph.changed(); + aStringOrTextMorph.text = Math.round(num).toString(); + aStringOrTextMorph.drawNew(); + aStringOrTextMorph.changed(); + aStringOrTextMorph.escalateEvent( + 'reactToSliderEdit', + aStringOrTextMorph + ); + }; + menu.items.push(slider); + menu.popup(this, aStringOrTextMorph.bottomLeft().add(new Point(0, 5))); +}; + +WorldMorph.prototype.stopEditing = function () { + if (this.cursor) { + this.lastEditedText = this.cursor.target; + this.cursor.destroy(); + this.cursor = null; + this.lastEditedText.escalateEvent('reactToEdit', this.lastEditedText); + } + this.keyboardReceiver = null; + if (this.virtualKeyboard) { + this.virtualKeyboard.blur(); + document.body.removeChild(this.virtualKeyboard); + this.virtualKeyboard = null; + } + this.worldCanvas.focus(); +}; + +WorldMorph.prototype.toggleBlurredShadows = function () { + useBlurredShadows = !useBlurredShadows; +}; + +WorldMorph.prototype.togglePreferences = function () { + if (MorphicPreferences === standardSettings) { + MorphicPreferences = touchScreenSettings; + } else { + MorphicPreferences = standardSettings; + } +}; diff --git a/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/morphic.txt b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/morphic.txt new file mode 100644 index 0000000..042b7e5 --- /dev/null +++ b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/morphic.txt @@ -0,0 +1,1031 @@ + + morphic.js + + a lively Web-GUI + inspired by Squeak + + written by Jens Mönig + jens@moenig.org + + Copyright (C) 2012 by Jens Mönig + + this documentation last changed: April 07, 2013 + + This file is part of Snap!. + + Snap! is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + + + documentation contents + ---------------------- + I. inheritance hierarchy + II. object definition toc + III. yet to implement + IV. open issues + V. browser compatibility + VI. the big picture + VII. programming guide + (1) setting up a web page + (a) single world + (b) multiple worlds + (c) an application + (2) manipulating morphs + (3) events + (a) mouse events + (b) context menu + (c) dragging + (d) dropping + (e) keyboard events + (f) resize event + (g) combined mouse-keyboard events + (h) text editing events + (4) stepping + (5) creating new kinds of morphs + (6) development and user modes + (7) turtle graphics + (8) damage list housekeeping + (9) minifying morphic.js + VIII. acknowledgements + IX. contributors + + + I. hierarchy + ------------- + the following tree lists all constructors hierarchically, + indentation indicating inheritance. Refer to this list to get a + contextual overview: + + Color + Node + Morph + BlinkerMorph + CursorMorph + BouncerMorph* + BoxMorph + InspectorMorph + MenuMorph + MouseSensorMorph* + SpeechBubbleMorph + CircleBoxMorph + SliderButtonMorph + SliderMorph + ColorPaletteMorph + GrayPaletteMorph + ColorPickerMorph + FrameMorph + ScrollFrameMorph + ListMorph + StringFieldMorph + WorldMorph + HandleMorph + HandMorph + PenMorph + ShadowMorph + StringMorph + TextMorph + TriggerMorph + MenuItemMorph + Point + Rectangle + + + II. toc + ------- + the following list shows the order in which all constructors are + defined. Use this list to locate code in this document: + + + Global settings + Global functions + + Color + Point + Rectangle + Node + Morph + ShadowMorph + HandleMorph + PenMorph + ColorPaletteMorph + GrayPaletteMorph + ColorPickerMorph + BlinkerMorph + CursorMorph + BoxMorph + SpeechBubbleMorph + CircleBoxMorph + SliderButtonMorph + SliderMorph + MouseSensorMorph* + InspectorMorph + MenuMorph + StringMorph + TextMorph + TriggerMorph + MenuItemMorph + FrameMorph + ScrollFrameMorph + ListMorph + StringFieldMorph + BouncerMorph* + HandMorph + WorldMorph + + * included only for demo purposes + + + III. yet to implement + --------------------- + - keyboard support for scroll frames and lists + - virtual keyboard support for Android and IE + + + IV. open issues + ---------------- + - blurry shadows don't work well in Chrome + + + V. browser compatibility + ------------------------ + I have taken great care and considerable effort to make morphic.js + runnable and appearing exactly the same on all current browsers + available to me: + + - Firefox for Windows + - Firefox for Mac + - Chrome for Windows (blurry shadows have some issues) + - Chrome for Mac + - Safari for Windows + - safari for Mac + - Safari for iOS (mobile) + - IE for Windows + - Opera for Windows + - Opera for Mac + + + VI. the big picture + ------------------- + Morphic.js is completely based on Canvas and JavaScript, it is just + Morphic, nothing else. Morphic.js is very basic and covers only the + bare essentials: + + * a stepping mechanism (a time-sharing multiplexer for lively + user interaction ontop of a single OS/browser thread) + * progressive display updates (only dirty rectangles are + redrawn in each display cycle) + * a tree structure + * a single World per Canvas element (although you can have + multiple worlds in multiple Canvas elements on the same web + page) + * a single Hand per World (but you can support multi-touch + events) + * a single text entry focus per World + + In its current state morphic.js doesn't support Transforms (you + cannot rotate Morphs), but with PenMorph there already is a simple + LOGO-like turtle that you can use to draw onto any Morph it is + attached to. I'm planning to add special Morphs that support these + operations later on, but not for every Morph in the system. + Therefore these additions ("sprites" etc.) are likely to be part of + other libraries ("microworld.js") in separate files. + + the purpose of morphic.js is to provide a malleable framework that + will let me experiment with lively GUIs for my hobby horse, which + is drag-and-drop, blocks based programming languages. Those things + (BYOB4 - http://byob.berkeley.edu) will be written using morphic.js + as a library. + + + VII. programming guide + ---------------------- + Morphic.js provides a library for lively GUIs inside single HTML + Canvas elements. Each such canvas element functions as a "world" in + which other visible shapes ("morphs") can be positioned and + manipulated, often directly and interactively by the user. Morphs + are tree nodes and may contain any number of submorphs ("children"). + + All things visible in a morphic World are morphs themselves, i.e. + all text rendering, blinking cursors, entry fields, menus, buttons, + sliders, windows and dialog boxes etc. are created with morphic.js + rather than using HTML DOM elements, and as a consequence can be + changed and adjusted by the programmer regardless of proprietary + browser behavior. + + Each World has an - invisible - "Hand" resembling the mouse cursor + (or the user's finger on touch screens) which handles mouse events, + and may also have a keyboardReceiver to handle key events. + + The basic idea of Morphic is to continuously run display cycles and + to incrementally update the screen by only redrawing those World + regions which have been "dirtied" since the last redraw. Before + each shape is processed for redisplay it gets the chance to perform + a "step" procedure, thus allowing for an illusion of concurrency. + + + (1) setting up a web page + ------------------------- + Setting up a web page for Morphic always involves three steps: + adding one or more Canvas elements, defining one or more worlds, + initializing and starting the main loop. + + + (a) single world + ----------------- + Most commonly you will want your World to fill the browsers's whole + client area. This default situation is easiest and most straight + forward. + + example html file: + + + + + Morphic! + + + + + +

Your browser doesn't support canvas.

+
+ + + + if you use ScrollFrames or otherwise plan to support mouse wheel + scrolling events, you might also add the following inline-CSS + attribute to the Canvas element: + + style="position: absolute;" + + which will prevent the World to be scrolled around instead of the + elements inside of it in some browsers. + + + (b) multiple worlds + ------------------- + If you wish to create a web page with more than one world, make + sure to prevent each world from auto-filling the whole page and + include it in the main loop. It's also a good idea to give each + world its own tabindex: + + example html file: + + + + + Morphic! + + + + +

first world:

+ +

Your browser doesn't support canvas.

+
+

second world:

+ +

Your browser doesn't support canvas.

+
+ + + + + (c) an application + ------------------- + Of course, most of the time you don't want to just plain use the + standard Morhic World "as is" out of the box, but write your own + application (something like Scratch!) in it. For such an + application you'll create your own morph prototypes, perhaps + assemble your own "window frame" and bring it all to life in a + customized World state. the following example creates a simple + snake-like mouse drawing game. + + example html file: + + + + + touch me! + + + + + +

Your browser doesn't support canvas.

+
+ + + + To get an idea how you can craft your own custom morph prototypes + I've included two examples which should give you an idea how to add + properties, override inherited methods and use the stepping + mechanism for "livelyness": + + BouncerMorph + MouseSensorMorph + + For the sake of sharing a single file I've included those examples + in morphic.js itself. Usually you'll define your additions in a + separate file and keep morphic.js untouched. + + + (2) manipulating morphs + ----------------------- + There are many methods to programmatically manipulate morphs. Among + the most important and common ones among all morphs are the + following nine: + + * hide() + * show() + + * setPosition(aPoint) + * setExtent(aPoint) + * setColor(aColor) + + * add(submorph) - attaches submorph ontop + * addBack(submorph) - attaches submorph underneath + + * fullCopy() - duplication + * destroy() - deletion + + + (3) events + ---------- + All user (and system) interaction is triggered by events, which are + passed on from the root element - the World - to its submorphs. The + World contains a list of system (browser) events it reacts to in its + + initEventListeners() + + method. Currently there are + + - mouse + - drop + - keyboard + - (window) resize + + events. + + These system events are dispatched within the morphic World by the + World's Hand and its keyboardReceiver (usually the active text + cursor). + + + (a) mouse events: + ----------------- + The Hand dispatches the following mouse events to relevant morphs: + + mouseDownLeft + mouseDownRight + mouseClickLeft + mouseClickRight + mouseDoubleClick + mouseEnter + mouseLeave + mouseEnterDragging + mouseLeaveDragging + mouseMove + mouseScroll + + If you wish your morph to react to any such event, simply add a + method of the same name as the event, e.g: + + MyMorph.prototype.mouseMove = function(pos) {}; + + The only optional parameter of such a method is a Point object + indicating the current position of the Hand inside the World's + coordinate system. + + Events may be "bubbled" up a morph's owner chain by calling + + this.escalateEvent(functionName, arg) + + in the event handler method's code. + + Likewise, removing the event handler method will render your morph + passive to the event in question. + + + (b) context menu: + ----------------- + By default right-clicking (or single-finger tap-and-hold) on a morph + also invokes its context menu (in addition to firing the + mouseClickRight event). A morph's context menu can be customized by + assigning a Menu instance to its + + customContextMenu + + property, or altogether suppressed by overriding its inherited + + contextMenu() + + method. + + + (c) dragging: + ------------- + Dragging a morph is initiated when the left mouse button is pressed, + held and the mouse is moved. + + You can control whether a morph is draggable by setting its + + isDraggable + + property either to false or true. If a morph isn't draggable itself + it will pass the pick-up request up its owner chain. This lets you + create draggable composite morphs like Windows, DialogBoxes, + Sliders etc. + + Sometimes it is desireable to make "template" shapes which cannot be + moved themselves, but from which instead duplicates can be peeled + off. This is especially useful for building blocks in construction + kits, e.g. the MIT-Scratch palette. Morphic.js lets you control this + functionality by setting the + + isTemplate + + property flag to true for any morph whose "isDraggable" property is + turned off. When dragging such a Morph the hand will instead grab + a duplicate of the template whose "isDraggable" flag is true and + whose "isTemplate" flag is false, in other words: a non-template. + + Dragging is indicated by adding a drop shadow to the morph in hand. + If a morph follows the hand without displaying a drop shadow it is + merely being moved about without changing its parent (owner morph), + e.g. when "dragging" a morph handle to resize its owner, or when + "dragging" a slider button. + + Right before a morph is picked up its + + prepareToBeGrabbed(handMorph) + + method is invoked, if it is present. Immediately after the pick-up + the former parent's + + reactToGrabOf(grabbedMorph) + + method is called, again only if it exists. + + Similar to events, these methods are optional and don't exist by + default. For a simple example of how they can be used to adjust + scroll bars in a scroll frame please have a look at their + implementation in FrameMorph. + + + (d) dropping: + ------------- + Dropping is triggered when the left mouse button is either pressed + or released while the Hand is dragging a morph. + + Dropping a morph causes it to become embedded in a new owner morph. + You can control this embedding behavior by setting the prospective + drop target's + + acceptsDrops + + property to either true or false, or by overriding its inherited + + wantsDropOf(aMorph) + + method. + + Right after a morph has been dropped its + + justDropped(handMorph) + + method is called, and its new parent's + + reactToDropOf(droppedMorph, handMorph) + + method is invoked, again only if each method exists. + + Similar to events, these methods are optional and by default are + not present in morphs by default (watch out for inheritance, + though!). For a simple example of how they can be used to adjust + scroll bars in a scroll frame please have a look at their + implementation in FrameMorph. + + Drops of image elements from outside the world canvas are dispatched as + + droppedImage(aCanvas, name) + droppedSVG(anImage, name) + + events to interested Morphs at the mouse pointer. If you want you Morph + to e.g. import outside images you can add the droppedImage() and / or the + droppedSVG() methods to it. The parameter passed to the event handles is + a new offscreen canvas element representing a copy of the original image + element which can be directly used, e.g. by assigning it to another + Morph's image property. In the case of a dropped SVG it is an image + element (not a canvas), which has to be rasterized onto a canvas before + it can be used. The benefit of handling SVGs as image elements is that + rasterization can be deferred until the destination scale is known, taking + advantage of SVG's ability for smooth scaling. If instead SVGs are to be + rasterized right away, you can set the + + MorphicPreferences.rasterizeSVGs + + preference to . In this case dropped SVGs also trigger the + droppedImage() event with a canvas containing a rasterized version of the + SVG. + + The same applies to drops of audio or text files from outside the world + canvas. + + Those are dispatched as + + droppedAudio(anAudio, name) + droppedText(aString, name) + + events to interested Morphs at the mouse pointer. + + if none of the above content types can be determined, the file contents + is dispatched as an ArrayBuffer to interested Morphs: + + droppedBinary(anArrayBuffer, name) + + + (e) keyboard events + ------------------- + The World dispatches the following key events to its active + keyboardReceiver: + + keypress + keydown + keyup + + Currently the only morph which acts as keyboard receiver is + CursorMorph, the basic text editing widget. If you wish to add + keyboard support to your morph you need to add event handling + methods for + + processKeyPress(event) + processKeyDown(event) + processKeyUp(event) + + and activate them by assigning your morph to the World's + + keyboardReceiver + + property. + + Note that processKeyUp() is optional and doesn't have to be present + if your morph doesn't require it. + + + (f) resize event + ---------------- + The Window resize event is handled by the World and allows the + World's extent to be adjusted so that it always completely fills + the browser's visible page. You can turn off this default behavior + by setting the World's + + useFillPage + + property to false. + + Alternatively you can also initialize the World with the + useFillPage switch turned off from the beginning by passing the + false value as second parameter to the World's constructor: + + world = new World(aCanvas, false); + + Use this when creating a web page with multiple Worlds. + + if "useFillPage" is turned on the World dispatches an + + reactToWorldResize(newBounds) + + events to all of its children (toplevel only), allowing each to + adjust to the new World bounds by implementing a corresponding + method, the passed argument being the World's new dimensions after + completing the resize. By default, the "reactToWorldResize" Method + does not exist. + + Example: + + Add the following method to your Morph to let it automatically + fill the whole World, but leave a 10 pixel border uncovered: + + MyMorph.prototype.reactToWorldResize = function (rect) { + this.changed(); + this.bounds = rect.insetBy(10); + this.drawNew(); + this.changed(); + }; + + + (g) combined mouse-keyboard events + ---------------------------------- + Occasionally you'll want an object to react differently to a mouse + click or to some other mouse event while the user holds down a key + on the keyboard. Such "shift-click", "ctl-click", or "alt-click" + events can be implemented by querying the World's + + currentKey + + property inside the function that reacts to the mouse event. This + property stores the keyCode of the key that's currently pressed. + Once the key is released by the user it reverts to null. + + + (h) text editing events + ----------------------- + Much of Morphic's "liveliness" comes out of allowing text elements + (instances of either single-lined StringMorph or multi-lined TextMorph) + to be directly manipulated and edited by users. This requires other + objects which may have an interest in the text element's state to react + appropriately. Therefore text elements and their manipulators emit + a stream of events, mostly by "bubbling" them up the text element's + owner chain. Text elements' parents are notified about the following + events: + + Whenever the user presses a key on the keyboard while a text element + is being edited, a + + reactToKeystroke(event) + + is escalated up its parent chain, the "event" parameter being the + original one received by the World. + + Once the user has completed the edit, the following events are + dispatched: + + accept() - was pressed on a single line of text + cancel() - was pressed on any text element + + Note that "accept" only gets triggered by single-line texte elements, + as the key is used to insert line breaks in multi-line + elements. Therefore, whenever a text edit is terminated by the user + (accepted, cancelled or otherwise), + + reactToEdit(StringOrTextMorph) + + is triggered. + + If the MorphicPreference's + + useSliderForInput + + setting is turned on, a slider is popped up underneath the currently + edited text element letting the user insert numbers out of the given + slider range. Whenever this happens, i.e. whenever the slider is moved + or while the slider button is pressed, a stream of + + reactToSliderEdit(StringOrTextMorph) + + events is dispatched, allowing for "Bret-Victor" style "live coding" + applications. + + In addition to user-initiated events text elements also emit + change notifications to their direct parents whenever their drawNew() + method is invoked. That way complex Morphs containing text elements + get a chance to react if something about the embedded text has been + modified programmatically. These events are: + + layoutChanged() - sent from instances of TextMorph + fixLayout() - sent from instances of StringMorph + + they are different so that Morphs which contain both multi-line and + single-line text elements can hold them apart. + + + (4) stepping + ------------ + Stepping is what makes Morphic "magical". Two properties control + a morph's stepping behavior: the fps attribute and the step() + method. + + By default the + + step() + + method does nothing. As you can see in the examples of BouncerMorph + and MouseSensorMorph you can easily override this inherited method + to suit your needs. + + By default the step() method is called once per display cycle. + Depending on the number of actively stepping morphs and the + complexity of your step() methods this can cause quite a strain on + your CPU, and also result in your application behaving differently + on slower computers than on fast ones. + + setting + + myMorph.fps + + to a number lower than the interval for the main loop lets you free + system resources (albeit at the cost of a less responsive or slower + behavior for this particular morph). + + + (5) creating new kinds of morphs + -------------------------------- + The real fun begins when you start to create new kinds of morphs + with customized shapes. Imagine, e.g. jigsaw puzzle pieces or + musical notes. For this you have to override the default + + drawNew() + + method. + + This method creates a new offscreen Canvas and stores it in + the morph's + + image + + property. + + Use the following template for a start: + + MyMorph.prototype.drawNew = function() { + var context; + this.image = newCanvas(this.extent()); + context = this.image.getContext('2d'); + // use context to paint stuff here + }; + + If your new morph stores or references other morphs outside of the + submorph tree in other properties, be sure to also override the + default + + copyRecordingReferences() + + method accordingly if you want it to support duplication. + + + (6) development and user modes + ------------------------------ + When working with Squeak on Scratch or BYOB among the features I + like the best and use the most is inspecting what's going on in + the World while it is up and running. That's what development mode + is for (you could also call it debug mode). In essence development + mode controls which context menu shows up. In user mode right + clicking (or double finger tapping) a morph invokes its + + customContextMenu + + property, whereas in development mode only the general + + developersMenu() + + method is called and the resulting menu invoked. The developers' + menu features Gui-Builder-wise functionality to directly inspect, + take apart, reassamble and otherwise manipulate morphs and their + contents. + + Instead of using the "customContextMenu" property you can also + assign a more dynamic contextMenu by overriding the general + + userMenu() + + method with a customized menu constructor. The difference between + the customContextMenu property and the userMenu() method is that + the former is also present in development mode and overrides the + developersMenu() result. For an example of how to use the + customContextMenu property have a look at TextMorph's evaluation + menu, which is used for the Inspector's evaluation pane. + + When in development mode you can inspect every Morph's properties + with the inspector, including all of its methods. The inspector + also lets you add, remove and rename properties, and even edit + their values at runtime. Like in a Smalltalk environment the inspect + features an evaluation pane into which you can type in arbitrary + JavaScript code and evaluate it in the context of the inspectee. + + Use switching between user and development modes while you are + developing an application and disable switching to development once + you're done and deploying, because generally you don't want to + confuse end-users with inspectors and meta-level stuff. + + + (7) turtle graphics + ------------------- + + The basic Morphic kernel features a simple LOGO turtle constructor + called + + PenMorph + + which you can use to draw onto its parent Morph. By default every + Morph in the system (including the World) is able to act as turtle + canvas and can display pen trails. Pen trails will be lost whenever + the trails morph (the pen's parent) performs a "drawNew()" + operation. If you want to create your own pen trails canvas, you + may wish to modify its + + penTrails() + + property, so that it keeps a separate offscreen canvas for pen + trails (and doesn't loose these on redraw). + + the following properties of PenMorph are relevant for turtle + graphics: + + color - a Color + size - line width of pen trails + heading - degrees + isDown - drawing state + + the following commands can be used to actually draw something: + + up() - lift the pen up, further movements leave no trails + down() - set down, further movements leave trails + clear() - remove all trails from the current parent + forward(n) - move n steps in the current direction (heading) + turn(n) - turn right n degrees + + Turtle graphics can best be explored interactively by creating a + new PenMorph object and by manipulating it with the inspector + widget. + + NOTE: PenMorph has a special optimization for recursive operations + called + + warp(function) + + You can significantly speed up recursive ops and increase the depth + of recursion that's displayable by wrapping WARP around your + recursive function call: + + example: + + myPen.warp(function () { + myPen.tree(12, 120, 20); + }) + + will be much faster than just invoking the tree function, because it + prevents the parent's parent from keeping track of every single line + segment and instead redraws the outcome in a single pass. + + + (8) damage list housekeeping + ---------------------------- + Morphic's progressive display update comes at the cost of having to + cycle through a list of "broken rectangles" every display cycle. If + this list gets very long working this damage list can lead to a + seemingly dramatic slow-down of the Morphic system. Typically this + occurs when updating the layout of complex Morphs with very many + submorphs, e.g. when resizing an inspector window. + + An effective strategy to cope with this is to use the inherited + + trackChanges + + property of the Morph prototype for damage list housekeeping. + + The trackChanges property of the Morph prototype is a Boolean switch + that determines whether the World's damage list ('broken' rectangles) + tracks changes. By default the switch is always on. If set to false + changes are not stored. This can be very useful for housekeeping of + the damage list in situations where a large number of (sub-) morphs + are changed more or less at once. Instead of keeping track of every + single submorph's changes tremendous performance improvements can be + achieved by setting the trackChanges flag to false before propagating + the layout changes, setting it to true again and then storing the full + bounds of the surrounding morph. An an example refer to the + + moveBy() + + method of HandMorph, and to the + + fixLayout() + + method of InspectorMorph, or the + + startLayout() + endLayout() + + methods of SyntaxElementMorph in the Snap application. + + + (9) minifying morphic.js + ------------------------ + Coming from Smalltalk and being a Squeaker at heart I am a huge fan + of browsing the code itself to make sense of it. Therefore I have + included this documentation and (too little) inline comments so all + you need to get going is this very file. + + Nowadays with live streaming HD video even on mobile phones 250 KB + shouldn't be a big strain on bandwith, still minifying and even + compressing morphic.js down do about 100 KB may sometimes improve + performance in production use. + + Being an attorney-at-law myself you programmer folk keep harassing + me with rabulistic nitpickings about free software licenses. I'm + releasing morphic.js under an AGPL license. Therefore please make + sure to adhere to that license in any minified or compressed version. + + + VIII. acknowledgements + ---------------------- + The original Morphic was designed and written by Randy Smith and + John Maloney for the SELF programming language, and later ported to + Squeak (Smalltalk) by John Maloney and Dan Ingalls, who has also + ported it to JavaScript (the Lively Kernel), once again setting + a "Gold Standard" for self sustaining systems which morphic.js + cannot and does not aspire to meet. + + This Morphic implementation for JavaScript is not a direct port of + Squeak's Morphic, but still many individual functions have been + ported almost literally from Squeak, sometimes even including their + comments, e.g. the morph duplication mechanism fullCopy(). Squeak + has been a treasure trove, and if morphic.js looks, feels and + smells a lot like Squeak, I'll take it as a compliment. + + Evelyn Eastmond has inspired and encouraged me with her wonderful + implementation of DesignBlocksJS. Thanks for sharing code, ideas + and enthusiasm for programming. + + John Maloney has been my mentor and my source of inspiration for + these Morphic experiments. Thanks for the critique, the suggestions + and explanations for all things Morphic and for being my all time + programming hero. + + I have originally written morphic.js in Florian Balmer's Notepad2 + editor for Windows and later switched to Apple's Dashcode. I've also + come to depend on both Douglas Crockford's JSLint, Mozilla's Firebug + and Google's Chrome to get it right. + + + IX. contributors + ---------------------- + Joe Otto found and fixed many early bugs and taught me some tricks. + Nathan Dinsmore contributed mouse wheel scrolling, cached + background texture handling and countless bug fixes. + Ian Reynolds contributed backspace key handling for Chrome. + Davide Della Casa contributed performance optimizations for Firefox. + + - Jens Mönig diff --git a/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/multiple.html b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/multiple.html new file mode 100644 index 0000000..340573a --- /dev/null +++ b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/multiple.html @@ -0,0 +1,33 @@ + + + + Morphic! + + + + +

first world:

+ +

Your browser doesn't support canvas.

+
+

second world:

+ +

Your browser doesn't support canvas.

+
+ + \ No newline at end of file diff --git a/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/objects.js b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/objects.js new file mode 100644 index 0000000..022594b --- /dev/null +++ b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/objects.js @@ -0,0 +1,6407 @@ +/* + + objects.js + + a scriptable microworld + based on morphic.js, blocks.js and threads.js + inspired by Scratch + + written by Jens Mönig + jens@moenig.org + + Copyright (C) 2013 by Jens Mönig + + This file is part of Snap!. + + Snap! is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + + + prerequisites: + -------------- + needs blocks.js, threads.js, morphic.js and widgets.js + + + toc + --- + the following list shows the order in which all constructors are + defined. Use this list to locate code in this document: + + SpriteMorph + SpriteHighlightMorph + StageMorph + Costume + SVG_Costume + CostumeEditorMorph + Sound + Note + CellMorph + WatcherMorph + StagePrompterMorph + + SpeechBubbleMorph* + SpriteBubbleMorph + + * defined in Morphic.js + + + credits + ------- + Ian Reynolds contributed initial porting of primitives from Squeak and + sound handling + Achal Dave contributed research and prototyping for creating music + using the Web Audio API + +*/ + +// globals from paint.js: +/*global PaintEditorMorph*/ + +// globals from lists.js: + +/*global ListWatcherMorph*/ + +// gloabls from widgets.js: + +/*global PushButtonMorph, ToggleMorph, DialogBoxMorph, InputFieldMorph*/ + +// gloabls from gui.js: + +/*global WatcherMorph, SpriteIconMorph*/ + +// globals from threads.js: + +/*global ArgMorph, BlockMorph, Process, StackFrame, ThreadManager, +VariableFrame, detect, threadsVersion*/ + +// globals from blocks.js: + +/*global ArgMorph, ArrowMorph, BlockHighlightMorph, BlockMorph, +BooleanSlotMorph, BoxMorph, Color, ColorPaletteMorph, ColorSlotMorph, +CommandBlockMorph, CommandSlotMorph, FrameMorph, HatBlockMorph, +InputSlotMorph, MenuMorph, Morph, MultiArgMorph, Point, +ReporterBlockMorph, ScriptsMorph, ShaAwMorph, StringMorph, +SyntaxElementMorph, TextMorph, WorldMorph, blocksVersion, contains, +degrees, detect, getDocumentPositionOf, newCanvas, nop, radians, +useBlurredShadows*/ + +// globals from morphic.js: + +/*global Array, BlinkerMorph, BouncerMorph, BoxMorph, CircleBoxMorph, +Color, ColorPaletteMorph, ColorPickerMorph, CursorMorph, Date, +FrameMorph, Function, GrayPaletteMorph, HandMorph, HandleMorph, +InspectorMorph, ListMorph, Math, MenuItemMorph, MenuMorph, Morph, +MorphicPreferences, MouseSensorMorph, Node, Object, PenMorph, Point, +Rectangle, ScrollFrameMorph, ShadowMorph, SliderButtonMorph, +SliderMorph, String, StringFieldMorph, StringMorph, TextMorph, +TriggerMorph, WorldMorph, clone, contains, copy, degrees, detect, +document, getDocumentPositionOf, isNaN, isObject, isString, newCanvas, +nop, parseFloat, radians, standardSettings, touchScreenSettings, +useBlurredShadows, version, window, modules, IDE_Morph, VariableDialogMorph, +HTMLCanvasElement, Context, List, SpeechBubbleMorph, RingMorph, isNil, +FileReader*/ + +// globals from byob.js: + +/*global CustomBlockDefinition, BlockEditorMorph, BlockDialogMorph, +PrototypeHatBlockMorph*/ + +// globals from locale.js: + +/*global localize*/ + +// temporary globals + +// Global stuff //////////////////////////////////////////////////////// + +modules.objects = '2013-August-12'; + +var SpriteMorph; +var StageMorph; +var SpriteBubbleMorph; +var Costume; +var SVG_Costume; +var CostumeEditorMorph; +var Sound; +var Note; +var CellMorph; +var WatcherMorph; +var StagePrompterMorph; +var Note; +var SpriteHighlightMorph; + +// SpriteMorph ///////////////////////////////////////////////////////// + +// I am a scriptable object + +// SpriteMorph inherits from PenMorph: + +SpriteMorph.prototype = new PenMorph(); +SpriteMorph.prototype.constructor = SpriteMorph; +SpriteMorph.uber = PenMorph.prototype; + +// SpriteMorph settings + +SpriteMorph.prototype.categories = + [ + 'motion', + 'control', + 'looks', + 'sensing', + 'sound', + 'operators', + 'pen', + 'variables', + 'lists', + 'other' + ]; + +SpriteMorph.prototype.blockColor = { + motion : new Color(74, 108, 212), + looks : new Color(143, 86, 227), + sound : new Color(207, 74, 217), + pen : new Color(0, 161, 120), + control : new Color(230, 168, 34), + sensing : new Color(4, 148, 220), + operators : new Color(98, 194, 19), + variables : new Color(243, 118, 29), + lists : new Color(217, 77, 17), + other: new Color(150, 150, 150) +}; + +SpriteMorph.prototype.paletteColor = new Color(55, 55, 55); +SpriteMorph.prototype.paletteTextColor = new Color(230, 230, 230); +SpriteMorph.prototype.sliderColor + = SpriteMorph.prototype.paletteColor.lighter(30); +SpriteMorph.prototype.isCachingPrimitives = true; + +SpriteMorph.prototype.enableNesting = true; +SpriteMorph.prototype.highlightColor = new Color(250, 200, 130); +SpriteMorph.prototype.highlightBorder = 8; + +SpriteMorph.prototype.bubbleColor = new Color(255, 255, 255); +SpriteMorph.prototype.bubbleFontSize = 14; +SpriteMorph.prototype.bubbleFontIsBold = true; +SpriteMorph.prototype.bubbleCorner = 10; +SpriteMorph.prototype.bubbleBorder = 3; +SpriteMorph.prototype.bubbleBorderColor = new Color(190, 190, 190); +SpriteMorph.prototype.bubbleMaxTextWidth = 130; + +SpriteMorph.prototype.initBlocks = function () { + SpriteMorph.prototype.blocks = { + + // Motion + forward: { + type: 'command', + category: 'motion', + spec: 'move %n steps', + defaults: [10] + }, + turn: { + type: 'command', + category: 'motion', + spec: 'turn %clockwise %n degrees', + defaults: [15] + }, + turnLeft: { + type: 'command', + category: 'motion', + spec: 'turn %counterclockwise %n degrees', + defaults: [15] + }, + setHeading: { + type: 'command', + category: 'motion', + spec: 'point in direction %dir' + }, + doFaceTowards: { + type: 'command', + category: 'motion', + spec: 'point towards %dst' + }, + gotoXY: { + type: 'command', + category: 'motion', + spec: 'go to x: %n y: %n', + defaults: [0, 0] + }, + doGotoObject: { + type: 'command', + category: 'motion', + spec: 'go to %dst' + }, + doGlide: { + type: 'command', + category: 'motion', + spec: 'glide %n secs to x: %n y: %n', + defaults: [1, 0, 0] + }, + changeXPosition: { + type: 'command', + category: 'motion', + spec: 'change x by %n', + defaults: [10] + }, + setXPosition: { + type: 'command', + category: 'motion', + spec: 'set x to %n', + defaults: [0] + }, + changeYPosition: { + type: 'command', + category: 'motion', + spec: 'change y by %n', + defaults: [10] + }, + setYPosition: { + type: 'command', + category: 'motion', + spec: 'set y to %n', + defaults: [0] + }, + bounceOffEdge: { + type: 'command', + category: 'motion', + spec: 'if on edge, bounce' + }, + xPosition: { + type: 'reporter', + category: 'motion', + spec: 'x position' + }, + yPosition: { + type: 'reporter', + category: 'motion', + spec: 'y position' + }, + direction: { + type: 'reporter', + category: 'motion', + spec: 'direction' + }, + + // Looks + doSwitchToCostume: { + type: 'command', + category: 'looks', + spec: 'switch to costume %cst' + }, + doWearNextCostume: { + type: 'command', + category: 'looks', + spec: 'next costume' + }, + getCostumeIdx: { + type: 'reporter', + category: 'looks', + spec: 'costume #' + }, + doSayFor: { + type: 'command', + category: 'looks', + spec: 'say %s for %n secs', + defaults: [localize('Hello!'), 2] + }, + bubble: { + type: 'command', + category: 'looks', + spec: 'say %s', + defaults: [localize('Hello!')] + }, + doThinkFor: { + type: 'command', + category: 'looks', + spec: 'think %s for %n secs', + defaults: [localize('Hmm...'), 2] + }, + doThink: { + type: 'command', + category: 'looks', + spec: 'think %s', + defaults: [localize('Hmm...')] + }, + changeEffect: { + type: 'command', + category: 'looks', + spec: 'change %eff effect by %n', + defaults: [null, 25] + }, + setEffect: { + type: 'command', + category: 'looks', + spec: 'set %eff effect to %n', + defaults: [null, 0] + }, + clearEffects: { + type: 'command', + category: 'looks', + spec: 'clear graphic effects' + }, + changeScale: { + type: 'command', + category: 'looks', + spec: 'change size by %n', + defaults: [10] + }, + setScale: { + type: 'command', + category: 'looks', + spec: 'set size to %n %', + defaults: [100] + }, + getScale: { + type: 'reporter', + category: 'looks', + spec: 'size' + }, + show: { + type: 'command', + category: 'looks', + spec: 'show' + }, + hide: { + type: 'command', + category: 'looks', + spec: 'hide' + }, + comeToFront: { + type: 'command', + category: 'looks', + spec: 'go to front' + }, + goBack: { + type: 'command', + category: 'looks', + spec: 'go back %n layers', + defaults: [1] + }, + + // Looks - Debugging primitives for development mode + alert: { + type: 'command', + category: 'looks', + spec: 'alert %mult%s' + }, + log: { + type: 'command', + category: 'looks', + spec: 'console log %mult%s' + }, + + // Sound + playSound: { + type: 'command', + category: 'sound', + spec: 'play sound %snd' + }, + doPlaySoundUntilDone: { + type: 'command', + category: 'sound', + spec: 'play sound %snd until done' + }, + doStopAllSounds: { + type: 'command', + category: 'sound', + spec: 'stop all sounds' + }, + doRest: { + type: 'command', + category: 'sound', + spec: 'rest for %n beats', + defaults: [0.2] + }, + doPlayNote: { + type: 'command', + category: 'sound', + spec: 'play note %n for %n beats', + defaults: [60, 0.5] + }, + doChangeTempo: { + type: 'command', + category: 'sound', + spec: 'change tempo by %n', + defaults: [20] + }, + doSetTempo: { + type: 'command', + category: 'sound', + spec: 'set tempo to %n bpm', + defaults: [60] + }, + getTempo: { + type: 'reporter', + category: 'sound', + spec: 'tempo' + }, + + // Pen + clear: { + type: 'command', + category: 'pen', + spec: 'clear' + }, + down: { + type: 'command', + category: 'pen', + spec: 'pen down' + }, + up: { + type: 'command', + category: 'pen', + spec: 'pen up' + }, + setColor: { + type: 'command', + category: 'pen', + spec: 'set pen color to %clr' + }, + changeHue: { + type: 'command', + category: 'pen', + spec: 'change pen color by %n', + defaults: [10] + }, + setHue: { + type: 'command', + category: 'pen', + spec: 'set pen color to %n', + defaults: [0] + }, + changeBrightness: { + type: 'command', + category: 'pen', + spec: 'change pen shade by %n', + defaults: [10] + }, + setBrightness: { + type: 'command', + category: 'pen', + spec: 'set pen shade to %n', + defaults: [100] + }, + changeSize: { + type: 'command', + category: 'pen', + spec: 'change pen size by %n', + defaults: [1] + }, + setSize: { + type: 'command', + category: 'pen', + spec: 'set pen size to %n', + defaults: [1] + }, + doStamp: { + type: 'command', + category: 'pen', + spec: 'stamp' + }, + + // Control + receiveGo: { + type: 'hat', + category: 'control', + spec: 'when %greenflag clicked' + }, + receiveKey: { + type: 'hat', + category: 'control', + spec: 'when %keyHat key pressed' + }, + receiveClick: { + type: 'hat', + category: 'control', + spec: 'when I am clicked' + }, + receiveMessage: { + type: 'hat', + category: 'control', + spec: 'when I receive %msgHat' + }, + doBroadcast: { + type: 'command', + category: 'control', + spec: 'broadcast %msg' + }, + doBroadcastAndWait: { + type: 'command', + category: 'control', + spec: 'broadcast %msg and wait' + }, + getLastMessage: { + type: 'reporter', + category: 'control', + spec: 'message' + }, + doWait: { + type: 'command', + category: 'control', + spec: 'wait %n secs', + defaults: [1] + }, + doWaitUntil: { + type: 'command', + category: 'control', + spec: 'wait until %b' + }, + doForever: { + type: 'command', + category: 'control', + spec: 'forever %c' + }, + doRepeat: { + type: 'command', + category: 'control', + spec: 'repeat %n %c', + defaults: [10] + }, + doUntil: { + type: 'command', + category: 'control', + spec: 'repeat until %b %c' + }, + doIf: { + type: 'command', + category: 'control', + spec: 'if %b %c' + }, + doIfElse: { + type: 'command', + category: 'control', + spec: 'if %b %c else %c' + }, + doStop: { + type: 'command', + category: 'control', + spec: 'stop script' + }, + doStopAll: { + type: 'command', + category: 'control', + spec: 'stop all %stop' + }, + doRun: { + type: 'command', + category: 'control', + spec: 'run %cmdRing %inputs' + }, + fork: { + type: 'command', + category: 'control', + spec: 'launch %cmdRing %inputs' + }, + evaluate: { + type: 'reporter', + category: 'control', + spec: 'call %repRing %inputs' + }, + /* + doRunWithInputList: { + type: 'command', + category: 'control', + spec: 'run %cmd with input list %l' + }, + + forkWithInputList: { + type: 'command', + category: 'control', + spec: 'launch %cmd with input list %l' + }, + + evaluateWithInputList: { + type: 'reporter', + category: 'control', + spec: 'call %r with input list %l' + }, + */ + doReport: { + type: 'command', + category: 'control', + spec: 'report %s' + }, + doStopBlock: { + type: 'command', + category: 'control', + spec: 'stop block' + }, + doCallCC: { + type: 'command', + category: 'control', + spec: 'run %cmdRing w/continuation' + }, + reportCallCC: { + type: 'reporter', + category: 'control', + spec: 'call %cmdRing w/continuation' + }, + doWarp: { + type: 'command', + category: 'other', + spec: 'warp %c' + }, + + // Cloning - very experimental + receiveOnClone: { + type: 'hat', + category: 'control', + spec: 'when I start as a clone' + }, + createClone: { + type: 'command', + category: 'control', + spec: 'create a clone of %cln' + }, + removeClone: { + type: 'command', + category: 'control', + spec: 'delete this clone' + }, + + // Debugging - pausing + + doPauseAll: { + type: 'command', + category: 'control', + spec: 'pause all %pause' + }, + + // Sensing + + reportTouchingObject: { + type: 'predicate', + category: 'sensing', + spec: 'touching %col ?' + }, + reportTouchingColor: { + type: 'predicate', + category: 'sensing', + spec: 'touching %clr ?' + }, + reportColorIsTouchingColor: { + type: 'predicate', + category: 'sensing', + spec: 'color %clr is touching %clr ?' + }, + colorFiltered: { + type: 'reporter', + category: 'sensing', + spec: 'filtered for %clr' + }, + reportStackSize: { + type: 'reporter', + category: 'sensing', + spec: 'stack size' + }, + reportFrameCount: { + type: 'reporter', + category: 'sensing', + spec: 'frames' + }, + doAsk: { + type: 'command', + category: 'sensing', + spec: 'ask %s and wait', + defaults: [localize('what\'s your name?')] + }, + reportLastAnswer: { // retained for legacy compatibility + type: 'reporter', + category: 'sensing', + spec: 'answer' + }, + getLastAnswer: { + type: 'reporter', + category: 'sensing', + spec: 'answer' + }, + reportMouseX: { + type: 'reporter', + category: 'sensing', + spec: 'mouse x' + }, + reportMouseY: { + type: 'reporter', + category: 'sensing', + spec: 'mouse y' + }, + reportMouseDown: { + type: 'predicate', + category: 'sensing', + spec: 'mouse down?' + }, + reportKeyPressed: { + type: 'predicate', + category: 'sensing', + spec: 'key %key pressed?' + }, + reportDistanceTo: { + type: 'reporter', + category: 'sensing', + spec: 'distance to %dst' + }, + doResetTimer: { + type: 'command', + category: 'sensing', + spec: 'reset timer' + }, + reportTimer: { // retained for legacy compatibility + type: 'reporter', + category: 'sensing', + spec: 'timer' + }, + getTimer: { + type: 'reporter', + category: 'sensing', + spec: 'timer' + }, + reportAttributeOf: { + type: 'reporter', + category: 'sensing', + spec: '%att of %spr', + defaults: [['costume #']] + }, + reportURL: { + type: 'reporter', + category: 'sensing', + spec: 'http:// %s', + defaults: ['snap.berkeley.edu'] + }, + reportIsFastTracking: { + type: 'predicate', + category: 'sensing', + spec: 'turbo mode?' + }, + doSetFastTracking: { + type: 'command', + category: 'sensing', + spec: 'set turbo mode to %b' + }, + + // Operators + reifyScript: { + type: 'ring', + category: 'other', + spec: '%rc %ringparms' + }, + reifyReporter: { + type: 'ring', + category: 'other', + spec: '%rr %ringparms' + }, + reifyPredicate: { + type: 'ring', + category: 'other', + spec: '%rp %ringparms' + }, + reportSum: { + type: 'reporter', + category: 'operators', + spec: '%n + %n' + }, + reportDifference: { + type: 'reporter', + category: 'operators', + spec: '%n \u2212 %n' + }, + reportProduct: { + type: 'reporter', + category: 'operators', + spec: '%n \u00D7 %n' + }, + reportQuotient: { + type: 'reporter', + category: 'operators', + spec: '%n / %n' // '%n \u00F7 %n' + }, + reportRound: { + type: 'reporter', + category: 'operators', + spec: 'round %n' + }, + reportMonadic: { + type: 'reporter', + category: 'operators', + spec: '%fun of %n', + defaults: [null, 10] + }, + reportModulus: { + type: 'reporter', + category: 'operators', + spec: '%n mod %n' + }, + reportRandom: { + type: 'reporter', + category: 'operators', + spec: 'pick random %n to %n', + defaults: [1, 10] + }, + reportLessThan: { + type: 'predicate', + category: 'operators', + spec: '%s < %s' + }, + reportEquals: { + type: 'predicate', + category: 'operators', + spec: '%s = %s' + }, + reportGreaterThan: { + type: 'predicate', + category: 'operators', + spec: '%s > %s' + }, + reportAnd: { + type: 'predicate', + category: 'operators', + spec: '%b and %b' + }, + reportOr: { + type: 'predicate', + category: 'operators', + spec: '%b or %b' + }, + reportNot: { + type: 'predicate', + category: 'operators', + spec: 'not %b' + }, + reportTrue: { + type: 'predicate', + category: 'operators', + spec: 'true' + }, + reportFalse: { + type: 'predicate', + category: 'operators', + spec: 'false' + }, + reportJoinWords: { + type: 'reporter', + category: 'operators', + spec: 'join %words', + defaults: [localize('hello') + ' ', localize('world')] + }, + reportLetter: { + type: 'reporter', + category: 'operators', + spec: 'letter %n of %s', + defaults: [1, localize('world')] + }, + reportStringSize: { + type: 'reporter', + category: 'operators', + spec: 'length of %s', + defaults: [localize('world')] + }, + reportUnicode: { + type: 'reporter', + category: 'operators', + spec: 'unicode of %s', + defaults: ['a'] + }, + reportUnicodeAsLetter: { + type: 'reporter', + category: 'operators', + spec: 'unicode %n as letter', + defaults: [65] + }, + reportIsA: { + type: 'predicate', + category: 'operators', + spec: 'is %s a %typ ?', + defaults: [5] + }, + reportIsIdentical: { + type: 'predicate', + category: 'operators', + spec: 'is %s identical to %s ?' + }, + reportTypeOf: { // only in dev mode for debugging + type: 'reporter', + category: 'operators', + spec: 'type of %s', + defaults: [5] + }, + reportTextFunction: { // only in dev mode - experimental + type: 'reporter', + category: 'operators', + spec: '%txtfun of %s', + defaults: [null, "Abelson & Sussman"] + }, + reportTextSplit: { // only in dev mode - experimental + type: 'reporter', + category: 'operators', + spec: 'split %s by %delim', + defaults: ["foo bar baz", " "] + }, + + /* + reportScript: { + type: 'reporter', + category: 'operators', + spec: 'the script %parms %c' + }, + reify: { + type: 'reporter', + category: 'operators', + spec: 'the %f block %parms' + }, + */ + + // Variables + doSetVar: { + type: 'command', + category: 'variables', + spec: 'set %var to %s', + defaults: [null, 0] + }, + doChangeVar: { + type: 'command', + category: 'variables', + spec: 'change %var by %n', + defaults: [null, 1] + }, + doShowVar: { + type: 'command', + category: 'variables', + spec: 'show variable %var' + }, + doHideVar: { + type: 'command', + category: 'variables', + spec: 'hide variable %var' + }, + doDeclareVariables: { + type: 'command', + category: 'other', + spec: 'script variables %scriptVars' + }, + + // Lists + reportNewList: { + type: 'reporter', + category: 'lists', + spec: 'list %exp' + }, + reportCONS: { + type: 'reporter', + category: 'lists', + spec: '%s in front of %l' + }, + reportListItem: { + type: 'reporter', + category: 'lists', + spec: 'item %idx of %l', + defaults: [1] + }, + reportCDR: { + type: 'reporter', + category: 'lists', + spec: 'all but first of %l' + }, + reportListLength: { + type: 'reporter', + category: 'lists', + spec: 'length of %l' + }, + reportListContainsItem: { + type: 'predicate', + category: 'lists', + spec: '%l contains %s', + defaults: [null, localize('thing')] + }, + doAddToList: { + type: 'command', + category: 'lists', + spec: 'add %s to %l', + defaults: [localize('thing')] + }, + doDeleteFromList: { + type: 'command', + category: 'lists', + spec: 'delete %ida of %l', + defaults: [1] + }, + doInsertInList: { + type: 'command', + category: 'lists', + spec: 'insert %s at %idx of %l', + defaults: [localize('thing'), 1] + }, + doReplaceInList: { + type: 'command', + category: 'lists', + spec: 'replace item %idx of %l with %s', + defaults: [1, null, localize('thing')] + }, + + // Code mapping - experimental + doMapCodeOrHeader: { // experimental + type: 'command', + category: 'other', + spec: 'map %cmdRing to %codeKind %code' + }, + doMapStringCode: { // experimental + type: 'command', + category: 'other', + spec: 'map String to code %code', + defaults: ['<#1>'] + }, + doMapListCode: { // experimental + type: 'command', + category: 'other', + spec: 'map %codeListPart of %codeListKind to code %code' + }, + reportMappedCode: { // experimental + type: 'reporter', + category: 'other', + spec: 'code of %cmdRing' + } + }; +}; + +SpriteMorph.prototype.initBlocks(); + +SpriteMorph.prototype.blockAlternatives = { + // motion: + turn: ['turnLeft'], + turnLeft: ['turn'], + changeXPosition: ['changeYPosition', 'setXPosition', 'setYPosition'], + setXPosition: ['setYPosition', 'changeXPosition', 'changeYPosition'], + changeYPosition: ['changeXPosition', 'setYPosition', 'setXPosition'], + setYPosition: ['setXPosition', 'changeYPosition', 'changeXPosition'], + xPosition: ['yPosition'], + yPosition: ['xPosition'], + + // looks: + doSayFor: ['doThinkFor'], + doThinkFor: ['doSayFor'], + bubble: ['doThink'], + doThink: ['bubble'], + show: ['hide'], + hide: ['show'], + changeEffect: ['setEffect'], + setEffect: ['changeEffect'], + changeScale: ['setScale'], + setScale: ['changeScale'], + + // sound: + playSound: ['doPlaySoundUntilDone'], + doPlaySoundUntilDone: ['playSound'], + doChangeTempo: ['doSetTempo'], + doSetTempo: ['doChangeTempo'], + + // pen: + clear: ['down', 'up', 'doStamp'], + down: ['up', 'clear', 'doStamp'], + up: ['down', 'clear', 'doStamp'], + doStamp: ['clear', 'down', 'up'], + changeHue: ['setHue', 'changeBrightness', 'setBrightness'], + setHue: ['changeHue', 'changeBrightness', 'setBrightness'], + changeBrightness: ['setBrightness', 'setHue', 'changeHue'], + setBrightness: ['changeBrightness', 'setHue', 'changeHue'], + changeSize: ['setSize'], + setSize: ['changeSize'], + + // control: + receiveGo: ['receiveClick'], + receiveClick: ['receiveGo'], + doBroadcast: ['doBroadcastAndWait'], + doBroadcastAndWait: ['doBroadcast'], + doStopBlock: ['doStop', 'doStopAll'], + doStop: ['doStopBlock', 'doStopAll'], + doStopAll: ['doStopBlock', 'doStop'], + + // sensing: + getLastAnswer: ['getTimer'], + getTimer: ['getLastAnswer'], + reportMouseX: ['reportMouseY'], + reportMouseY: ['reportMouseX'], + + // operators: + reportSum: ['reportDifference', 'reportProduct', 'reportQuotient'], + reportDifference: ['reportSum', 'reportProduct', 'reportQuotient'], + reportProduct: ['reportDifference', 'reportSum', 'reportQuotient'], + reportQuotient: ['reportDifference', 'reportProduct', 'reportSum'], + reportLessThan: ['reportEquals', 'reportGreaterThan'], + reportEquals: ['reportLessThan', 'reportGreaterThan'], + reportGreaterThan: ['reportEquals', 'reportLessThan'], + reportAnd: ['reportOr'], + reportOr: ['reportAnd'], + reportTrue: ['reportFalse'], + reportFalse: ['reportTrue'], + + // variables + doSetVar: ['doChangeVar'], + doChangeVar: ['doSetVar'], + doShowVar: ['doHideVar'], + doHideVar: ['doShowVar'] +}; + +// SpriteMorph instance creation + +function SpriteMorph(globals) { + this.init(globals); +} + +SpriteMorph.prototype.init = function (globals) { + this.name = localize('Sprite'); + this.variables = new VariableFrame(globals || null, this); + this.scripts = new ScriptsMorph(this); + this.customBlocks = []; + this.costumes = new List(); + this.costume = null; + this.sounds = new List(); + this.normalExtent = new Point(60, 60); // only for costume-less situation + this.scale = 1; + this.rotationStyle = 1; // 1 = full, 2 = left/right, 0 = off + this.version = Date.now(); // for observer optimization + this.isClone = false; // indicate a "temporary" Scratch-style clone + this.cloneOriginName = ''; + + // sprite nesting properties + this.parts = []; // not serialized, only anchor (name) + this.anchor = null; + this.nestingScale = 1; + this.rotatesWithAnchor = true; + + this.blocksCache = {}; // not to be serialized (!) + this.paletteCache = {}; // not to be serialized (!) + this.rotationOffset = new Point(); // not to be serialized (!) + this.idx = 0; // not to be serialized (!) - used for de-serialization + this.wasWarped = false; // not to be serialized, used for fast-tracking + + SpriteMorph.uber.init.call(this); + + this.isDraggable = true; + this.isDown = false; + + this.heading = 90; + this.changed(); + this.drawNew(); + this.changed(); +}; + +// SpriteMorph duplicating (fullCopy) + +SpriteMorph.prototype.fullCopy = function () { + var c = SpriteMorph.uber.fullCopy.call(this), + arr = [], + cb; + + c.stopTalking(); + c.color = this.color.copy(); + c.blocksCache = {}; + c.paletteCache = {}; + c.scripts = this.scripts.fullCopy(); + c.scripts.owner = c; + c.variables = this.variables.copy(); + c.variables.owner = c; + + c.customBlocks = []; + this.customBlocks.forEach(function (def) { + cb = def.copyAndBindTo(c); + c.customBlocks.push(cb); + c.allBlockInstances(def).forEach(function (block) { + block.definition = cb; + }); + }); + this.costumes.asArray().forEach(function (costume) { + arr.push(costume.copy()); + }); + c.costumes = new List(arr); + arr = []; + this.sounds.asArray().forEach(function (sound) { + arr.push(sound); + }); + c.sounds = new List(arr); + + c.parts = []; + c.anchor = null; + c.nestingScale = 1; + c.rotatesWithAnchor = true; + + return c; +}; + +// SpriteMorph versioning + +SpriteMorph.prototype.setName = function (string) { + this.name = string || this.name; + this.version = Date.now(); +}; + +// SpriteMorph rendering + +SpriteMorph.prototype.drawNew = function () { + var myself = this, + currentCenter = this.center(), + facing, // actual costume heading based on my rotation style + isFlipped, + pic, // (flipped copy of) actual costume based on my rotation style + stageScale = this.parent instanceof StageMorph ? + this.parent.scale : 1, + newX, + corners = [], + origin, + shift, + corner, + costumeExtent, + ctx; + + if (this.isWarped) { + this.wantsRedraw = true; + return; + } + facing = this.rotationStyle ? this.heading : 90; + if (this.rotationStyle === 2) { + facing = 90; + if ((this.heading > 180 && (this.heading < 360)) + || (this.heading < 0 && (this.heading > -180))) { + isFlipped = true; + } + } + if (this.costume) { + pic = isFlipped ? this.costume.flipped() : this.costume; + + // determine the rotated costume's bounding box + corners = pic.bounds().corners().map(function (point) { + return point.rotateBy( + radians(facing - 90), + myself.costume.center() + ); + }); + origin = corners[0]; + corner = corners[0]; + corners.forEach(function (point) { + origin = origin.min(point); + corner = corner.max(point); + }); + costumeExtent = origin.corner(corner) + .extent().multiplyBy(this.scale * stageScale); + + // determine the new relative origin of the rotated shape + shift = new Point(0, 0).rotateBy( + radians(-(facing - 90)), + pic.center() + ).subtract(origin); + + // create a new, adequately dimensioned canvas + // and draw the costume on it + this.image = newCanvas(costumeExtent); + this.silentSetExtent(costumeExtent); + ctx = this.image.getContext('2d'); + ctx.scale(this.scale * stageScale, this.scale * stageScale); + ctx.translate(shift.x, shift.y); + ctx.rotate(radians(facing - 90)); + ctx.drawImage(pic.contents, 0, 0); + + // adjust my position to the rotation + this.setCenter(currentCenter, true); // just me + + // determine my rotation offset + this.rotationOffset = shift + .translateBy(pic.rotationCenter) + .rotateBy(radians(-(facing - 90)), shift) + .scaleBy(this.scale * stageScale); + } else { + facing = isFlipped ? -90 : facing; + newX = Math.min( + Math.max( + this.normalExtent.x * this.scale * stageScale, + 5 + ), + 1000 + ); + this.silentSetExtent(new Point(newX, newX)); + this.image = newCanvas(this.extent()); + this.setCenter(currentCenter, true); // just me + SpriteMorph.uber.drawNew.call(this, facing); + this.rotationOffset = this.extent().divideBy(2); + } + this.version = Date.now(); +}; + +SpriteMorph.prototype.endWarp = function () { + this.isWarped = false; + if (this.wantsRedraw) { + var x = this.xPosition(), + y = this.yPosition(); + this.drawNew(); + this.silentGotoXY(x, y, true); // just me + this.wantsRedraw = false; + } + this.parent.changed(); +}; + +SpriteMorph.prototype.rotationCenter = function () { + return this.position().add(this.rotationOffset); +}; + +SpriteMorph.prototype.colorFiltered = function (aColor) { + // answer a new Morph containing my image filtered by aColor + // ignore transparency (alpha) + var morph = new Morph(), + ext = this.extent(), + ctx, + src, + clr, + i, + dta; + + src = this.image.getContext('2d').getImageData(0, 0, ext.x, ext.y); + morph.image = newCanvas(ext); + morph.bounds = this.bounds.copy(); + ctx = morph.image.getContext('2d'); + dta = ctx.createImageData(ext.x, ext.y); + for (i = 0; i < ext.x * ext.y * 4; i += 4) { + clr = new Color( + src.data[i], + src.data[i + 1], + src.data[i + 2] + ); + if (clr.eq(aColor)) { + dta.data[i] = src.data[i]; + dta.data[i + 1] = src.data[i + 1]; + dta.data[i + 2] = src.data[i + 2]; + dta.data[i + 3] = 255; + } + } + ctx.putImageData(dta, 0, 0); + return morph; +}; + +// SpriteMorph block instantiation + +SpriteMorph.prototype.blockForSelector = function (selector, setDefaults) { + var info, block, defaults, inputs, i; + info = this.blocks[selector]; + if (!info) {return null; } + block = info.type === 'command' ? new CommandBlockMorph() + : info.type === 'hat' ? new HatBlockMorph() + : info.type === 'ring' ? new RingMorph() + : new ReporterBlockMorph(info.type === 'predicate'); + block.color = this.blockColor[info.category]; + block.category = info.category; + block.selector = selector; + if (contains(['reifyReporter', 'reifyPredicate'], block.selector)) { + block.isStatic = true; + } + block.setSpec(localize(info.spec)); + if (setDefaults && info.defaults) { + defaults = info.defaults; + block.defaults = defaults; + inputs = block.inputs(); + if (inputs[0] instanceof MultiArgMorph) { + inputs[0].setContents(defaults); + inputs[0].defaults = defaults; + } else { + for (i = 0; i < defaults.length; i += 1) { + if (defaults[i] !== null) { + inputs[i].setContents(defaults[i]); + } + } + } + } + return block; +}; + +SpriteMorph.prototype.variableBlock = function (varName) { + var block = new ReporterBlockMorph(false); + block.selector = 'reportGetVar'; + block.color = this.blockColor.variables; + block.category = 'variables'; + block.setSpec(varName); + block.isDraggable = true; + return block; +}; + +// SpriteMorph block templates + +SpriteMorph.prototype.blockTemplates = function (category) { + var blocks = [], myself = this, varNames, button, + cat = category || 'motion', txt; + + function block(selector) { + if (StageMorph.prototype.hiddenPrimitives[selector]) { + return null; + } + var newBlock = SpriteMorph.prototype.blockForSelector(selector, true); + newBlock.isTemplate = true; + return newBlock; + } + + function variableBlock(varName) { + var newBlock = SpriteMorph.prototype.variableBlock(varName); + newBlock.isDraggable = false; + newBlock.isTemplate = true; + return newBlock; + } + + function watcherToggle(selector) { + if (StageMorph.prototype.hiddenPrimitives[selector]) { + return null; + } + var info = SpriteMorph.prototype.blocks[selector]; + return new ToggleMorph( + 'checkbox', + this, + function () { + myself.toggleWatcher( + selector, + localize(info.spec), + myself.blockColor[info.category] + ); + }, + null, + function () { + return myself.showingWatcher(selector); + }, + null + ); + } + + function variableWatcherToggle(varName) { + return new ToggleMorph( + 'checkbox', + this, + function () { + myself.toggleVariableWatcher(varName); + }, + null, + function () { + return myself.showingVariableWatcher(varName); + }, + null + ); + } + + function helpMenu() { + var menu = new MenuMorph(this); + menu.addItem('help...', 'showHelp'); + return menu; + } + + if (cat === 'motion') { + + blocks.push(block('forward')); + blocks.push(block('turn')); + blocks.push(block('turnLeft')); + blocks.push('-'); + blocks.push(block('setHeading')); + blocks.push(block('doFaceTowards')); + blocks.push('-'); + blocks.push(block('gotoXY')); + blocks.push(block('doGotoObject')); + blocks.push(block('doGlide')); + blocks.push('-'); + blocks.push(block('changeXPosition')); + blocks.push(block('setXPosition')); + blocks.push(block('changeYPosition')); + blocks.push(block('setYPosition')); + blocks.push('-'); + blocks.push(block('bounceOffEdge')); + blocks.push('-'); + blocks.push(watcherToggle('xPosition')); + blocks.push(block('xPosition')); + blocks.push(watcherToggle('yPosition')); + blocks.push(block('yPosition')); + blocks.push(watcherToggle('direction')); + blocks.push(block('direction')); + + } else if (cat === 'looks') { + + blocks.push(block('doSwitchToCostume')); + blocks.push(block('doWearNextCostume')); + blocks.push(watcherToggle('getCostumeIdx')); + blocks.push(block('getCostumeIdx')); + blocks.push('-'); + blocks.push(block('doSayFor')); + blocks.push(block('bubble')); + blocks.push(block('doThinkFor')); + blocks.push(block('doThink')); + blocks.push('-'); + blocks.push(block('changeEffect')); + blocks.push(block('setEffect')); + blocks.push(block('clearEffects')); + blocks.push('-'); + blocks.push(block('changeScale')); + blocks.push(block('setScale')); + blocks.push(watcherToggle('getScale')); + blocks.push(block('getScale')); + blocks.push('-'); + blocks.push(block('show')); + blocks.push(block('hide')); + blocks.push('-'); + blocks.push(block('comeToFront')); + blocks.push(block('goBack')); + + // for debugging: /////////////// + + if (this.world().isDevMode) { + blocks.push('-'); + txt = new TextMorph(localize( + 'development mode \ndebugging primitives:' + )); + txt.fontSize = 9; + txt.setColor(this.paletteTextColor); + blocks.push(txt); + blocks.push('-'); + blocks.push(block('log')); + blocks.push(block('alert')); + } + + ///////////////////////////////// + + } else if (cat === 'sound') { + + blocks.push(block('playSound')); + blocks.push(block('doPlaySoundUntilDone')); + blocks.push(block('doStopAllSounds')); + blocks.push('-'); + blocks.push(block('doRest')); + blocks.push('-'); + blocks.push(block('doPlayNote')); + blocks.push('-'); + blocks.push(block('doChangeTempo')); + blocks.push(block('doSetTempo')); + blocks.push(watcherToggle('getTempo')); + blocks.push(block('getTempo')); + + } else if (cat === 'pen') { + + blocks.push(block('clear')); + blocks.push('-'); + blocks.push(block('down')); + blocks.push(block('up')); + blocks.push('-'); + blocks.push(block('setColor')); + blocks.push(block('changeHue')); + blocks.push(block('setHue')); + blocks.push('-'); + blocks.push(block('changeBrightness')); + blocks.push(block('setBrightness')); + blocks.push('-'); + blocks.push(block('changeSize')); + blocks.push(block('setSize')); + blocks.push('-'); + blocks.push(block('doStamp')); + + } else if (cat === 'control') { + + blocks.push(block('receiveGo')); + blocks.push(block('receiveKey')); + blocks.push(block('receiveClick')); + blocks.push(block('receiveMessage')); + blocks.push('-'); + blocks.push(block('doBroadcast')); + blocks.push(block('doBroadcastAndWait')); + blocks.push(watcherToggle('getLastMessage')); + blocks.push(block('getLastMessage')); + blocks.push('-'); + blocks.push(block('doWarp')); + blocks.push('-'); + blocks.push(block('doWait')); + blocks.push(block('doWaitUntil')); + blocks.push('-'); + blocks.push(block('doForever')); + blocks.push(block('doRepeat')); + blocks.push(block('doUntil')); + blocks.push('-'); + blocks.push(block('doIf')); + blocks.push(block('doIfElse')); + blocks.push('-'); + blocks.push(block('doReport')); + blocks.push('-'); + blocks.push(block('doStopBlock')); + blocks.push(block('doStop')); + blocks.push(block('doStopAll')); + blocks.push('-'); + blocks.push(block('doRun')); + blocks.push(block('fork')); + blocks.push(block('evaluate')); + blocks.push('-'); + /* + // list variants commented out for now (redundant) + blocks.push(block('doRunWithInputList')); + blocks.push(block('forkWithInputList')); + blocks.push(block('evaluateWithInputList')); + blocks.push('-'); + */ + blocks.push(block('doCallCC')); + blocks.push(block('reportCallCC')); + blocks.push('-'); + blocks.push(block('receiveOnClone')); + blocks.push(block('createClone')); + blocks.push(block('removeClone')); + blocks.push('-'); + blocks.push(block('doPauseAll')); + + } else if (cat === 'sensing') { + + blocks.push(block('reportTouchingObject')); + blocks.push(block('reportTouchingColor')); + blocks.push(block('reportColorIsTouchingColor')); + blocks.push('-'); + blocks.push(block('doAsk')); + blocks.push(watcherToggle('getLastAnswer')); + blocks.push(block('getLastAnswer')); + blocks.push('-'); + blocks.push(block('reportMouseX')); + blocks.push(block('reportMouseY')); + blocks.push(block('reportMouseDown')); + blocks.push('-'); + blocks.push(block('reportKeyPressed')); + blocks.push('-'); + blocks.push(block('reportDistanceTo')); + blocks.push('-'); + blocks.push(block('doResetTimer')); + blocks.push(watcherToggle('getTimer')); + blocks.push(block('getTimer')); + blocks.push('-'); + blocks.push(block('reportAttributeOf')); + blocks.push('-'); + blocks.push(block('reportURL')); + blocks.push('-'); + blocks.push(block('reportIsFastTracking')); + blocks.push(block('doSetFastTracking')); + + // for debugging: /////////////// + + if (this.world().isDevMode) { + + blocks.push('-'); + txt = new TextMorph(localize( + 'development mode \ndebugging primitives:' + )); + txt.fontSize = 9; + txt.setColor(this.paletteTextColor); + blocks.push(txt); + blocks.push('-'); + blocks.push(block('colorFiltered')); + blocks.push(block('reportStackSize')); + blocks.push(block('reportFrameCount')); + } + + } else if (cat === 'operators') { + + blocks.push(block('reifyScript')); + blocks.push(block('reifyReporter')); + blocks.push(block('reifyPredicate')); + blocks.push('#'); + blocks.push('-'); + blocks.push(block('reportSum')); + blocks.push(block('reportDifference')); + blocks.push(block('reportProduct')); + blocks.push(block('reportQuotient')); + blocks.push('-'); + blocks.push(block('reportModulus')); + blocks.push(block('reportRound')); + blocks.push(block('reportMonadic')); + blocks.push(block('reportRandom')); + blocks.push('-'); + blocks.push(block('reportLessThan')); + blocks.push(block('reportEquals')); + blocks.push(block('reportGreaterThan')); + blocks.push('-'); + blocks.push(block('reportAnd')); + blocks.push(block('reportOr')); + blocks.push(block('reportNot')); + blocks.push('-'); + blocks.push(block('reportTrue')); + blocks.push(block('reportFalse')); + blocks.push('-'); + blocks.push(block('reportJoinWords')); + blocks.push(block('reportLetter')); + blocks.push(block('reportStringSize')); + blocks.push('-'); + blocks.push(block('reportUnicode')); + blocks.push(block('reportUnicodeAsLetter')); + blocks.push('-'); + blocks.push(block('reportIsA')); + blocks.push(block('reportIsIdentical')); + + // for debugging: /////////////// + + if (this.world().isDevMode) { + blocks.push('-'); + txt = new TextMorph( + 'development mode \ndebugging primitives:' + ); + txt.fontSize = 9; + txt.setColor(this.paletteTextColor); + blocks.push(txt); + blocks.push('-'); + blocks.push(block('reportTypeOf')); + blocks.push(block('reportTextFunction')); + blocks.push(block('reportTextSplit')); + } + + ///////////////////////////////// + + } else if (cat === 'variables') { + + button = new PushButtonMorph( + null, + function () { + new VariableDialogMorph( + null, + function (pair) { + if (pair && !myself.variables.silentFind(pair[0])) { + myself.addVariable(pair[0], pair[1]); + myself.toggleVariableWatcher(pair[0], pair[1]); + myself.blocksCache[cat] = null; + myself.paletteCache[cat] = null; + myself.parentThatIsA(IDE_Morph).refreshPalette(); + } + }, + myself + ).prompt( + 'Variable name', + null, + myself.world() + ); + }, + 'Make a variable' + ); + button.userMenu = helpMenu; + button.selector = 'addVariable'; + button.showHelp = BlockMorph.prototype.showHelp; + blocks.push(button); + + if (this.variables.allNames().length > 0) { + button = new PushButtonMorph( + null, + function () { + var menu = new MenuMorph( + myself.deleteVariable, + null, + myself + ); + myself.variables.allNames().forEach(function (name) { + menu.addItem(name, name); + }); + menu.popUpAtHand(myself.world()); + }, + 'Delete a variable' + ); + button.userMenu = helpMenu; + button.selector = 'deleteVariable'; + button.showHelp = BlockMorph.prototype.showHelp; + blocks.push(button); + } + + blocks.push('-'); + + varNames = this.variables.allNames(); + if (varNames.length > 0) { + varNames.forEach(function (name) { + blocks.push(variableWatcherToggle(name)); + blocks.push(variableBlock(name)); + }); + blocks.push('-'); + } + + blocks.push(block('doSetVar')); + blocks.push(block('doChangeVar')); + blocks.push(block('doShowVar')); + blocks.push(block('doHideVar')); + blocks.push(block('doDeclareVariables')); + + blocks.push('='); + + blocks.push(block('reportNewList')); + blocks.push('-'); + blocks.push(block('reportCONS')); + blocks.push(block('reportListItem')); + blocks.push(block('reportCDR')); + blocks.push('-'); + blocks.push(block('reportListLength')); + blocks.push(block('reportListContainsItem')); + blocks.push('-'); + blocks.push(block('doAddToList')); + blocks.push(block('doDeleteFromList')); + blocks.push(block('doInsertInList')); + blocks.push(block('doReplaceInList')); + + blocks.push('='); + + if (StageMorph.prototype.enableCodeMapping) { + blocks.push(block('doMapCodeOrHeader')); + blocks.push(block('doMapStringCode')); + blocks.push(block('doMapListCode')); + blocks.push('-'); + blocks.push(block('reportMappedCode')); + blocks.push('='); + } + + button = new PushButtonMorph( + null, + function () { + var ide = myself.parentThatIsA(IDE_Morph), + stage = myself.parentThatIsA(StageMorph); + new BlockDialogMorph( + null, + function (definition) { + if (definition.spec !== '') { + if (definition.isGlobal) { + stage.globalBlocks.push(definition); + } else { + myself.customBlocks.push(definition); + } + ide.flushPaletteCache(); + ide.refreshPalette(); + new BlockEditorMorph(definition, myself).popUp(); + } + }, + myself + ).prompt( + 'Make a block', + null, + myself.world() + ); + }, + 'Make a block' + ); + button.userMenu = helpMenu; + button.selector = 'addCustomBlock'; + button.showHelp = BlockMorph.prototype.showHelp; + blocks.push(button); + } + return blocks; +}; + +SpriteMorph.prototype.palette = function (category) { + if (!this.paletteCache[category]) { + this.paletteCache[category] = this.freshPalette(category); + } + return this.paletteCache[category]; +}; + +SpriteMorph.prototype.freshPalette = function (category) { + var palette = new ScrollFrameMorph(null, null, this.sliderColor), + unit = SyntaxElementMorph.prototype.fontSize, + x = 0, + y = 5, + ry = 0, + blocks, + hideNextSpace = false, + myself = this, + stage = this.parentThatIsA(StageMorph), + oldFlag = Morph.prototype.trackChanges; + + Morph.prototype.trackChanges = false; + + palette.owner = this; + palette.padding = unit / 2; + palette.color = this.paletteColor; + palette.growth = new Point(0, MorphicPreferences.scrollBarSize); + + // menu: + + palette.userMenu = function () { + var menu = new MenuMorph(), + ide = this.parentThatIsA(IDE_Morph), + more = { + operators: + ['reifyScript', 'reifyReporter', 'reifyPredicate'], + control: + ['doWarp'], + variables: + [ + 'doDeclareVariables', + 'reportNewList', + 'reportCONS', + 'reportListItem', + 'reportCDR', + 'reportListLength', + 'reportListContainsItem', + 'doAddToList', + 'doDeleteFromList', + 'doInsertInList', + 'doReplaceInList' + ] + }; + + function hasHiddenPrimitives() { + var defs = SpriteMorph.prototype.blocks, + hiddens = StageMorph.prototype.hiddenPrimitives; + return Object.keys(hiddens).some(function (any) { + return defs[any].category === category || + contains((more[category] || []), any); + }); + } + + function canHidePrimitives() { + return palette.contents.children.some(function (any) { + return contains( + Object.keys(SpriteMorph.prototype.blocks), + any.selector + ); + }); + } + + if (canHidePrimitives()) { + menu.addItem( + 'hide primitives', + function () { + var defs = SpriteMorph.prototype.blocks; + Object.keys(defs).forEach(function (sel) { + if (defs[sel].category === category) { + StageMorph.prototype.hiddenPrimitives[sel] = true; + } + }); + (more[category] || []).forEach(function (sel) { + StageMorph.prototype.hiddenPrimitives[sel] = true; + }); + ide.flushBlocksCache(category); + ide.refreshPalette(); + } + ); + } + if (hasHiddenPrimitives()) { + menu.addItem( + 'show primitives', + function () { + var hiddens = StageMorph.prototype.hiddenPrimitives, + defs = SpriteMorph.prototype.blocks; + Object.keys(hiddens).forEach(function (sel) { + if (defs[sel].category === category) { + delete StageMorph.prototype.hiddenPrimitives[sel]; + } + }); + (more[category] || []).forEach(function (sel) { + delete StageMorph.prototype.hiddenPrimitives[sel]; + }); + ide.flushBlocksCache(category); + ide.refreshPalette(); + } + ); + } + return menu; + }; + + // primitives: + + blocks = this.blocksCache[category]; + if (!blocks) { + blocks = myself.blockTemplates(category); + if (this.isCachingPrimitives) { + myself.blocksCache[category] = blocks; + } + } + + blocks.forEach(function (block) { + if (block === null) { + return; + } + if (block === '-') { + if (hideNextSpace) {return; } + y += unit * 0.8; + hideNextSpace = true; + } else if (block === '=') { + if (hideNextSpace) {return; } + y += unit * 1.6; + hideNextSpace = true; + } else if (block === '#') { + x = 0; + y = ry; + } else { + hideNextSpace = false; + if (x === 0) { + y += unit * 0.3; + } + block.setPosition(new Point(x, y)); + palette.addContents(block); + if (block instanceof ToggleMorph + || (block instanceof RingMorph)) { + x = block.right() + unit / 2; + ry = block.bottom(); + } else { + if (block.fixLayout) {block.fixLayout(); } + x = 0; + y += block.height(); + } + } + }); + + // global custom blocks: + + if (stage) { + y += unit * 1.6; + + stage.globalBlocks.forEach(function (definition) { + var block; + if (definition.category === category || + (category === 'variables' + && contains( + ['lists', 'other'], + definition.category + ))) { + block = definition.templateInstance(); + y += unit * 0.3; + block.setPosition(new Point(x, y)); + palette.addContents(block); + x = 0; + y += block.height(); + } + }); + } + + // local custom blocks: + + y += unit * 1.6; + this.customBlocks.forEach(function (definition) { + var block; + if (definition.category === category || + (category === 'variables' + && contains( + ['lists', 'other'], + definition.category + ))) { + block = definition.templateInstance(); + y += unit * 0.3; + block.setPosition(new Point(x, y)); + palette.addContents(block); + x = 0; + y += block.height(); + } + }); + + Morph.prototype.trackChanges = oldFlag; + return palette; +}; + +// SpriteMorph variable management + +SpriteMorph.prototype.addVariable = function (name, isGlobal) { + var ide = this.parentThatIsA(IDE_Morph); + if (isGlobal) { + this.variables.parentFrame.addVar(name); + if (ide) { + ide.flushBlocksCache('variables'); + } + } else { + this.variables.addVar(name); + this.blocksCache.variables = null; + } +}; + +SpriteMorph.prototype.deleteVariable = function (varName) { + var ide = this.parentThatIsA(IDE_Morph); + this.deleteVariableWatcher(varName); + this.variables.deleteVar(varName); + if (ide) { + ide.flushBlocksCache('variables'); // b/c the var could be global + ide.refreshPalette(); + } +}; + +// SpriteMorph costume management + +SpriteMorph.prototype.addCostume = function (costume) { + if (!costume.name) { + costume.name = 'costume' + (this.costumes.length() + 1); + } + this.costumes.add(costume); +}; + +SpriteMorph.prototype.wearCostume = function (costume) { + var x = this.xPosition ? this.xPosition() : null, + y = this.yPosition ? this.yPosition() : null, + isWarped = this.isWarped; + if (isWarped) { + this.endWarp(); + } + this.changed(); + this.costume = costume; + this.drawNew(); + this.changed(); + if (isWarped) { + this.startWarp(); + } + if (x !== null) { + this.silentGotoXY(x, y, true); // just me + } + if (this.positionTalkBubble) { // the stage doesn't talk + this.positionTalkBubble(); + } + this.version = Date.now(); +}; + +SpriteMorph.prototype.getCostumeIdx = function () { + return this.costumes.asArray().indexOf(this.costume) + 1; +}; + +SpriteMorph.prototype.doWearNextCostume = function () { + var arr = this.costumes.asArray(), + idx; + if (arr.length > 1) { + idx = arr.indexOf(this.costume); + if (idx > -1) { + idx += 1; + if (idx > (arr.length - 1)) { + idx = 0; + } + this.wearCostume(arr[idx]); + } + } +}; + +SpriteMorph.prototype.doWearPreviousCostume = function () { + var arr = this.costumes.asArray(), + idx; + if (arr.length > 1) { + idx = arr.indexOf(this.costume); + if (idx > -1) { + idx -= 1; + if (idx < 0) { + idx = arr.length - 1; + } + this.wearCostume(arr[idx]); + } + } +}; + +SpriteMorph.prototype.doSwitchToCostume = function (id) { + var num, + arr = this.costumes.asArray(), + costume; + if ( + contains( + [localize('Turtle'), localize('Empty')], + (id instanceof Array ? id[0] : null) + ) + ) { + costume = null; + } else { + if (id === -1) { + this.doWearPreviousCostume(); + return; + } + costume = detect(arr, function (cst) { + return cst.name === id; + }); + if (costume === null) { + num = parseFloat(id); + if (num === 0) { + costume = null; + } else { + costume = arr[num - 1] || null; + } + } + } + this.wearCostume(costume); +}; + +// SpriteMorph sound management + +SpriteMorph.prototype.addSound = function (audio, name) { + this.sounds.add(new Sound(audio, name)); +}; + +SpriteMorph.prototype.playSound = function (name) { + var stage = this.parentThatIsA(StageMorph), + sound = detect( + this.sounds.asArray(), + function (s) {return s.name === name; } + ), + active; + if (sound) { + active = sound.play(); + if (stage) { + stage.activeSounds.push(active); + stage.activeSounds = stage.activeSounds.filter(function (aud) { + return !aud.ended && !aud.terminated; + }); + } + return active; + } +}; + +// SpriteMorph user menu + +SpriteMorph.prototype.userMenu = function () { + var ide = this.parentThatIsA(IDE_Morph), + menu = new MenuMorph(this); + + if (ide && ide.isAppMode) { + menu.addItem('help', 'nop'); + return menu; + } + menu.addItem("duplicate", 'duplicate'); + menu.addItem("delete", 'remove'); + menu.addItem("edit", 'edit'); + menu.addLine(); + if (this.anchor) { + menu.addItem( + localize('detach from') + ' ' + this.anchor.name, + 'detachFromAnchor' + ); + } + if (this.parts.length) { + menu.addItem('detach all parts', 'detachAllParts'); + } + menu.addItem("export...", 'exportSprite'); + return menu; +}; + +SpriteMorph.prototype.exportSprite = function () { + var ide = this.parentThatIsA(IDE_Morph); + if (ide) { + ide.exportSprite(this); + } +}; + +SpriteMorph.prototype.edit = function () { + var ide = this.parentThatIsA(IDE_Morph); + if (ide) { + ide.selectSprite(this); + } +}; + +SpriteMorph.prototype.showOnStage = function () { + var stage = this.parentThatIsA(StageMorph); + if (stage) { + this.keepWithin(stage); + stage.add(this); + } + this.show(); +}; + +SpriteMorph.prototype.duplicate = function () { + var ide = this.parentThatIsA(IDE_Morph); + if (ide) { + ide.duplicateSprite(this); + } +}; + +SpriteMorph.prototype.remove = function () { + var ide = this.parentThatIsA(IDE_Morph); + if (ide) { + ide.removeSprite(this); + } +}; + +// SpriteMorph cloning (experimental) + +SpriteMorph.prototype.createClone = function () { + var clone, + hats, + stage = this.parentThatIsA(StageMorph); + if (stage) { + if (stage.cloneCount > 128) {return; } + stage.cloneCount += 1; + clone = this.fullCopy(); + clone.isClone = true; + clone.name = ''; + clone.cloneOriginName = this.isClone ? + this.cloneOriginName : this.name; + stage.add(clone); + hats = clone.allHatBlocksFor('__clone__init__'); + hats.forEach(function (block) { + stage.threads.startProcess(block, stage.isThreadSafe); + }); + } +}; + +SpriteMorph.prototype.removeClone = function () { + if (this.isClone) { + // this.stopTalking(); + this.parent.threads.stopAllForReceiver(this); + this.destroy(); + this.parent.cloneCount -= 1; + } +}; + +// SpriteMorph primitives + +// SpriteMorph pen color + +SpriteMorph.prototype.setColor = function (aColor) { + var x = this.xPosition(), + y = this.yPosition(); + if (!this.color.eq(aColor)) { + this.color = aColor; + this.drawNew(); + this.gotoXY(x, y); + } +}; + +SpriteMorph.prototype.getHue = function () { + return this.color.hsv()[0] * 100; +}; + +SpriteMorph.prototype.setHue = function (num) { + var hsv = this.color.hsv(), + x = this.xPosition(), + y = this.yPosition(); + + hsv[0] = Math.max(Math.min(+num || 0, 100), 0) / 100; + hsv[1] = 1; // we gotta fix this at some time + this.color.set_hsv.apply(this.color, hsv); + if (!this.costume) { + this.drawNew(); + this.changed(); + } + this.gotoXY(x, y); +}; + +SpriteMorph.prototype.changeHue = function (delta) { + this.setHue(this.getHue() + (+delta || 0)); +}; + +SpriteMorph.prototype.getBrightness = function () { + return this.color.hsv()[2] * 100; +}; + +SpriteMorph.prototype.setBrightness = function (num) { + var hsv = this.color.hsv(), + x = this.xPosition(), + y = this.yPosition(); + + hsv[1] = 1; // we gotta fix this at some time + hsv[2] = Math.max(Math.min(+num || 0, 100), 0) / 100; + this.color.set_hsv.apply(this.color, hsv); + if (!this.costume) { + this.drawNew(); + this.changed(); + } + this.gotoXY(x, y); +}; + +SpriteMorph.prototype.changeBrightness = function (delta) { + this.setBrightness(this.getBrightness() + (+delta || 0)); +}; + +// SpriteMorph layers + +SpriteMorph.prototype.comeToFront = function () { + if (this.parent) { + this.parent.add(this); + this.changed(); + } +}; + +SpriteMorph.prototype.goBack = function (layers) { + var layer, newLayer = +layers || 0; + if (!this.parent) {return null; } + layer = this.parent.children.indexOf(this); + if (layer < newLayer) {return null; } + this.parent.removeChild(this); + this.parent.children.splice(layer - newLayer, null, this); + this.parent.changed(); +}; + +// SpriteMorph collision detection optimization + +SpriteMorph.prototype.overlappingImage = function (otherSprite) { + // overrides method from Morph because Sprites aren't nested Morphs + var oRect = this.bounds.intersect(otherSprite.bounds), + oImg = newCanvas(oRect.extent()), + ctx = oImg.getContext('2d'); + + if (oRect.width() < 1 || oRect.height() < 1) { + return newCanvas(new Point(1, 1)); + } + ctx.drawImage( + this.image, + this.left() - oRect.left(), + this.top() - oRect.top() + ); + ctx.globalCompositeOperation = 'source-in'; + ctx.drawImage( + otherSprite.image, + otherSprite.left() - oRect.left(), + otherSprite.top() - oRect.top() + ); + return oImg; +}; + +// SpriteMorph stamping + +SpriteMorph.prototype.doStamp = function () { + var stage = this.parent, + context = stage.penTrails().getContext('2d'), + isWarped = this.isWarped; + if (isWarped) { + this.endWarp(); + } + context.save(); + context.scale(1 / stage.scale, 1 / stage.scale); + context.drawImage( + this.image, + (this.left() - stage.left()), + (this.top() - stage.top()) + ); + context.restore(); + this.changed(); + if (isWarped) { + this.startWarp(); + } +}; + +SpriteMorph.prototype.clear = function () { + this.parent.clearPenTrails(); +}; + +// SpriteMorph pen size + +SpriteMorph.prototype.setSize = function (size) { + // pen size + if (!isNaN(size)) { + this.size = Math.min(Math.max(+size, 0.0001), 1000); + } +}; + +SpriteMorph.prototype.changeSize = function (delta) { + this.setSize(this.size + (+delta || 0)); +}; + +// SpriteMorph scale + +SpriteMorph.prototype.getScale = function () { + // answer my scale in percent + return this.scale * 100; +}; + +SpriteMorph.prototype.setScale = function (percentage) { + // set my (absolute) scale in percent + var x = this.xPosition(), + y = this.yPosition(), + isWarped = this.isWarped, + realScale, + growth; + + if (isWarped) { + this.endWarp(); + } + realScale = (+percentage || 0) / 100; + growth = realScale / this.nestingScale; + this.nestingScale = realScale; + this.scale = Math.max(realScale, 0.01); + + // apply to myself + this.changed(); + this.drawNew(); + this.changed(); + if (isWarped) { + this.startWarp(); + } + this.silentGotoXY(x, y, true); // just me + this.positionTalkBubble(); + + // propagate to nested parts + this.parts.forEach(function (part) { + var xDist = part.xPosition() - x, + yDist = part.yPosition() - y; + part.setScale(part.scale * 100 * growth); + part.silentGotoXY( + x + (xDist * growth), + y + (yDist * growth) + ); + }); +}; + +SpriteMorph.prototype.changeScale = function (delta) { + this.setScale(this.getScale() + (+delta || 0)); +}; + +// SpriteMorph graphic effects + +SpriteMorph.prototype.setEffect = function (effect, value) { + var eff = effect instanceof Array ? effect[0] : null; + if (eff === 'ghost') { + this.alpha = 1 - Math.min(Math.max(+value || 0, 0), 100) / 100; + this.changed(); + } +}; + +SpriteMorph.prototype.getGhostEffect = function () { + return (1 - this.alpha) * 100; +}; + +SpriteMorph.prototype.changeEffect = function (effect, value) { + var eff = effect instanceof Array ? effect[0] : null; + if (eff === 'ghost') { + this.setEffect(effect, this.getGhostEffect() + (+value || 0)); + } +}; + +SpriteMorph.prototype.clearEffects = function () { + this.setEffect(['ghost'], 0); +}; + +// SpriteMorph talk bubble + +SpriteMorph.prototype.stopTalking = function () { + var bubble = this.talkBubble(); + if (bubble) {bubble.destroy(); } +}; + +SpriteMorph.prototype.doThink = function (data) { + this.bubble(data, true); +}; + +SpriteMorph.prototype.bubble = function (data, isThought, isQuestion) { + var bubble, + stage = this.parentThatIsA(StageMorph); + + this.stopTalking(); + if (data === '' || isNil(data)) {return; } + bubble = new SpriteBubbleMorph( + data, + stage ? stage.scale : 1, + isThought, + isQuestion + ); + this.add(bubble); + this.positionTalkBubble(); +}; + +SpriteMorph.prototype.talkBubble = function () { + return detect( + this.children, + function (morph) {return morph instanceof SpeechBubbleMorph; } + ); +}; + +SpriteMorph.prototype.positionTalkBubble = function () { + var stage = this.parentThatIsA(StageMorph), + stageScale = stage ? stage.scale : 1, + bubble = this.talkBubble(), + middle = this.center().y; + if (!bubble) {return null; } + bubble.show(); + if (!bubble.isPointingRight) { + bubble.isPointingRight = true; + bubble.drawNew(); + bubble.changed(); + } + bubble.setLeft(this.right()); + bubble.setBottom(this.top()); + while (!this.isTouching(bubble) && bubble.bottom() < middle) { + bubble.silentMoveBy(new Point(-1, 1).scaleBy(stageScale)); + } + if (!stage) {return null; } + if (bubble.right() > stage.right()) { + bubble.isPointingRight = false; + bubble.drawNew(); + bubble.setRight(this.center().x); + } + bubble.keepWithin(stage); + bubble.changed(); +}; + +// dragging and dropping adjustments b/c of talk bubbles + +SpriteMorph.prototype.prepareToBeGrabbed = function (hand) { + var bubble = this.talkBubble(); + if (!bubble) {return null; } + this.removeShadow(); + bubble.hide(); + if (!this.bounds.containsPoint(hand.position())) { + this.setCenter(hand.position()); + } + this.addShadow(); +}; + +SpriteMorph.prototype.justDropped = function () { + this.positionTalkBubble(); +}; + +// SpriteMorph drawing: + +SpriteMorph.prototype.drawLine = function (start, dest) { + var stagePos = this.parent.bounds.origin, + stageScale = this.parent.scale, + context = this.parent.penTrails().getContext('2d'), + from = start.subtract(stagePos).divideBy(stageScale), + to = dest.subtract(stagePos).divideBy(stageScale), + damagedFrom = from.multiplyBy(stageScale).add(stagePos), + damagedTo = to.multiplyBy(stageScale).add(stagePos), + damaged = damagedFrom.rectangle(damagedTo).expandBy( + Math.max(this.size * stageScale / 2, 1) + ).intersect(this.parent.visibleBounds()).spread(); + + if (this.isDown) { + context.lineWidth = this.size; + context.strokeStyle = this.color.toString(); + context.lineCap = 'round'; + context.lineJoin = 'round'; + context.beginPath(); + context.moveTo(from.x, from.y); + context.lineTo(to.x, to.y); + context.stroke(); + if (this.isWarped === false) { + this.world().broken.push(damaged); + } + } +}; + +// SpriteMorph motion - adjustments due to nesting + +SpriteMorph.prototype.moveBy = function (delta, justMe) { + // override the inherited default to make sure my parts follow + // unless it's justMe (a correction) + var start = this.isDown && !justMe && this.parent ? + this.rotationCenter() : null; + SpriteMorph.uber.moveBy.call(this, delta); + if (start) { + this.drawLine(start, this.rotationCenter()); + } + if (!justMe) { + this.parts.forEach(function (part) { + part.moveBy(delta); + }); + } +}; + +SpriteMorph.prototype.slideBackTo = function (situation, inSteps) { + // override the inherited default to make sure my parts follow + var steps = inSteps || 5, + pos = situation.origin.position().add(situation.position), + xStep = -(this.left() - pos.x) / steps, + yStep = -(this.top() - pos.y) / steps, + stepCount = 0, + oldStep = this.step, + oldFps = this.fps, + myself = this; + + this.fps = 0; + this.step = function () { + myself.moveBy(new Point(xStep, yStep)); + stepCount += 1; + if (stepCount === steps) { + situation.origin.add(myself); + if (situation.origin.reactToDropOf) { + situation.origin.reactToDropOf(myself); + } + myself.step = oldStep; + myself.fps = oldFps; + } + }; +}; + +SpriteMorph.prototype.setCenter = function (aPoint, justMe) { + // override the inherited default to make sure my parts follow + // unless it's justMe + var delta = aPoint.subtract(this.center()); + this.moveBy(delta, justMe); +}; + +SpriteMorph.prototype.nestingBounds = function () { + // same as fullBounds(), except that it uses "parts" instead of children + var result; + result = this.bounds; + this.parts.forEach(function (part) { + if (part.isVisible) { + result = result.merge(part.nestingBounds()); + } + }); + return result; +}; + +// SpriteMorph motion primitives + +Morph.prototype.setPosition = function (aPoint, justMe) { + // override the inherited default to make sure my parts follow + // unless it's justMe + var delta = aPoint.subtract(this.topLeft()); + if ((delta.x !== 0) || (delta.y !== 0)) { + this.moveBy(delta, justMe); + } +}; + +SpriteMorph.prototype.forward = function (steps) { + var dest, + dist = steps * this.parent.scale || 0; + + if (dist >= 0) { + dest = this.position().distanceAngle(dist, this.heading); + } else { + dest = this.position().distanceAngle( + Math.abs(dist), + (this.heading - 180) + ); + } + this.setPosition(dest); + this.positionTalkBubble(); +}; + +SpriteMorph.prototype.setHeading = function (degrees) { + var x = this.xPosition(), + y = this.yPosition(), + turn = degrees - this.heading; + + // apply to myself + this.changed(); + SpriteMorph.uber.setHeading.call(this, degrees); + this.silentGotoXY(x, y, true); // just me + this.positionTalkBubble(); + + // propagate to my parts + this.parts.forEach(function (part) { + var pos = new Point(part.xPosition(), part.yPosition()), + trg = pos.rotateBy(radians(turn), new Point(x, y)); + if (part.rotatesWithAnchor) { + part.turn(turn); + } + part.gotoXY(trg.x, trg.y); + }); +}; + +SpriteMorph.prototype.faceToXY = function (x, y) { + var deltaX = (x - this.xPosition()) * this.parent.scale, + deltaY = (y - this.yPosition()) * this.parent.scale, + angle = Math.abs(deltaX) < 0.001 ? (deltaY < 0 ? 90 : 270) + : Math.round( + (deltaX >= 0 ? 0 : 180) + - (Math.atan(deltaY / deltaX) * 57.2957795131) + ); + this.setHeading(angle + 90); +}; + +SpriteMorph.prototype.turn = function (degrees) { + this.setHeading(this.heading + (+degrees || 0)); +}; + +SpriteMorph.prototype.turnLeft = function (degrees) { + this.setHeading(this.heading - (+degrees || 0)); +}; + +SpriteMorph.prototype.xPosition = function () { + var stage = this.parentThatIsA(StageMorph); + + if (!stage && this.parent.grabOrigin) { // I'm currently being dragged + stage = this.parent.grabOrigin.origin; + } + if (stage) { + return (this.rotationCenter().x - stage.center().x) / stage.scale; + } + return this.rotationCenter().x; +}; + +SpriteMorph.prototype.yPosition = function () { + var stage = this.parentThatIsA(StageMorph); + + if (!stage && this.parent.grabOrigin) { // I'm currently being dragged + stage = this.parent.grabOrigin.origin; + } + if (stage) { + return (stage.center().y - this.rotationCenter().y) / stage.scale; + } + return this.rotationCenter().y; +}; + +SpriteMorph.prototype.direction = function () { + return this.heading; +}; + +SpriteMorph.prototype.penSize = function () { + return this.size; +}; + +SpriteMorph.prototype.gotoXY = function (x, y, justMe) { + var stage = this.parentThatIsA(StageMorph), + newX, + newY, + dest; + + newX = stage.center().x + (+x || 0) * stage.scale; + newY = stage.center().y - (+y || 0) * stage.scale; + if (this.costume) { + dest = new Point(newX, newY).subtract(this.rotationOffset); + } else { + dest = new Point(newX, newY).subtract(this.extent().divideBy(2)); + } + this.setPosition(dest, justMe); + this.positionTalkBubble(); +}; + +SpriteMorph.prototype.silentGotoXY = function (x, y, justMe) { + // move without drawing + var penState = this.isDown; + this.isDown = false; + this.gotoXY(x, y, justMe); + this.isDown = penState; +}; + +SpriteMorph.prototype.setXPosition = function (num) { + this.gotoXY(+num || 0, this.yPosition()); +}; + +SpriteMorph.prototype.changeXPosition = function (delta) { + this.setXPosition(this.xPosition() + (+delta || 0)); +}; + +SpriteMorph.prototype.setYPosition = function (num) { + this.gotoXY(this.xPosition(), +num || 0); +}; + +SpriteMorph.prototype.changeYPosition = function (delta) { + this.setYPosition(this.yPosition() + (+delta || 0)); +}; + +SpriteMorph.prototype.glide = function ( + duration, + endX, + endY, + elapsed, + startPoint +) { + var fraction, endPoint, rPos; + endPoint = new Point(endX, endY); + fraction = Math.max(Math.min(elapsed / duration, 1), 0); + rPos = startPoint.add( + endPoint.subtract(startPoint).multiplyBy(fraction) + ); + this.gotoXY(rPos.x, rPos.y); +}; + +SpriteMorph.prototype.bounceOffEdge = function () { + // taking nested parts into account + var stage = this.parentThatIsA(StageMorph), + fb = this.nestingBounds(), + dirX, + dirY; + + if (!stage) {return null; } + if (stage.bounds.containsRectangle(fb)) {return null; } + + dirX = Math.cos(radians(this.heading - 90)); + dirY = -(Math.sin(radians(this.heading - 90))); + + if (fb.left() < stage.left()) { + dirX = Math.abs(dirX); + } + if (fb.right() > stage.right()) { + dirX = -(Math.abs(dirX)); + } + if (fb.top() < stage.top()) { + dirY = -(Math.abs(dirY)); + } + if (fb.bottom() > stage.bottom()) { + dirY = Math.abs(dirY); + } + + this.setHeading(degrees(Math.atan2(-dirY, dirX)) + 90); + this.setPosition(this.position().add( + fb.amountToTranslateWithin(stage.bounds) + )); + this.positionTalkBubble(); +}; + +// SpriteMorph message broadcasting + +SpriteMorph.prototype.allMessageNames = function () { + var msgs = []; + this.scripts.allChildren().forEach(function (morph) { + var txt; + if (morph.selector) { + if (contains( + ['receiveMessage', 'doBroadcast', 'doBroadcastAndWait'], + morph.selector + )) { + txt = morph.inputs()[0].evaluate(); + if (isString(txt) && txt !== '') { + if (!contains(msgs, txt)) { + msgs.push(txt); + } + } + } + } + }); + return msgs; +}; + +SpriteMorph.prototype.allHatBlocksFor = function (message) { + return this.scripts.children.filter(function (morph) { + var event; + if (morph.selector) { + if (morph.selector === 'receiveMessage') { + event = morph.inputs()[0].evaluate(); + return event === message || (event instanceof Array); + } + if (morph.selector === 'receiveGo') { + return message === '__shout__go__'; + } + if (morph.selector === 'receiveOnClone') { + return message === '__clone__init__'; + } + if (morph.selector === 'receiveClick') { + return message === '__click__'; + } + } + return false; + }); +}; + +SpriteMorph.prototype.allHatBlocksForKey = function (key) { + return this.scripts.children.filter(function (morph) { + if (morph.selector) { + if (morph.selector === 'receiveKey') { + return morph.inputs()[0].evaluate()[0] === key; + } + } + return false; + }); +}; + +// SpriteMorph events + +SpriteMorph.prototype.mouseClickLeft = function () { + var stage = this.parentThatIsA(StageMorph), + hats = this.allHatBlocksFor('__click__'), + procs = []; + + hats.forEach(function (block) { + procs.push(stage.threads.startProcess(block, stage.isThreadSafe)); + }); + return procs; +}; + +// SpriteMorph timer + +SpriteMorph.prototype.getTimer = function () { + var stage = this.parentThatIsA(StageMorph); + if (stage) { + return stage.getTimer(); + } + return 0; +}; + +// SpriteMorph tempo + +SpriteMorph.prototype.getTempo = function () { + var stage = this.parentThatIsA(StageMorph); + if (stage) { + return stage.getTempo(); + } + return 0; +}; + +// SpriteMorph last message + +SpriteMorph.prototype.getLastMessage = function () { + var stage = this.parentThatIsA(StageMorph); + if (stage) { + return stage.getLastMessage(); + } + return ''; +}; + +// SpriteMorph user prompting + +SpriteMorph.prototype.getLastAnswer = function () { + return this.parentThatIsA(StageMorph).lastAnswer; +}; + +// SpriteMorph variable watchers (for palette checkbox toggling) + +SpriteMorph.prototype.findVariableWatcher = function (varName) { + var stage = this.parentThatIsA(StageMorph), + myself = this; + if (stage === null) { + return null; + } + return detect( + stage.children, + function (morph) { + return morph instanceof WatcherMorph + && (morph.target === myself.variables + || morph.target === myself.variables.parentFrame) + && morph.getter === varName; + } + ); +}; + +SpriteMorph.prototype.toggleVariableWatcher = function (varName, isGlobal) { + var stage = this.parentThatIsA(StageMorph), + watcher, + others; + if (stage === null) { + return null; + } + watcher = this.findVariableWatcher(varName); + if (watcher !== null) { + if (watcher.isVisible) { + watcher.hide(); + } else { + watcher.show(); + watcher.fixLayout(); // re-hide hidden parts + } + return; + } + + // if no watcher exists, create a new one + watcher = new WatcherMorph( + varName, + this.blockColor.variables, + isGlobal ? this.variables.parentFrame : this.variables, + varName + ); + watcher.setPosition(stage.position().add(10)); + others = stage.watchers(watcher.left()); + if (others.length > 0) { + watcher.setTop(others[others.length - 1].bottom()); + } + stage.add(watcher); + watcher.fixLayout(); +}; + +SpriteMorph.prototype.showingVariableWatcher = function (varName) { + var stage = this.parentThatIsA(StageMorph), + watcher; + if (stage === null) { + return false; + } + watcher = this.findVariableWatcher(varName); + if (watcher) { + return watcher.isVisible; + } + return false; +}; + +SpriteMorph.prototype.deleteVariableWatcher = function (varName) { + var stage = this.parentThatIsA(StageMorph), + watcher; + if (stage === null) { + return null; + } + watcher = this.findVariableWatcher(varName); + if (watcher !== null) { + watcher.destroy(); + } +}; + +// SpriteMorph non-variable watchers + +SpriteMorph.prototype.toggleWatcher = function (selector, label, color) { + var stage = this.parentThatIsA(StageMorph), + watcher, + others; + if (!stage) { return; } + watcher = this.watcherFor(stage, selector); + if (watcher) { + if (watcher.isVisible) { + watcher.hide(); + } else { + watcher.show(); + watcher.fixLayout(); // re-hide hidden parts + } + return; + } + + // if no watcher exists, create a new one + watcher = new WatcherMorph( + label, + color, + WatcherMorph.prototype.isGlobal(selector) ? stage : this, + selector + ); + watcher.setPosition(stage.position().add(10)); + others = stage.watchers(watcher.left()); + if (others.length > 0) { + watcher.setTop(others[others.length - 1].bottom()); + } + stage.add(watcher); + watcher.fixLayout(); +}; + +SpriteMorph.prototype.showingWatcher = function (selector) { + var stage = this.parentThatIsA(StageMorph), + watcher; + if (stage === null) { + return false; + } + watcher = this.watcherFor(stage, selector); + if (watcher) { + return watcher.isVisible; + } + return false; +}; + +SpriteMorph.prototype.watcherFor = function (stage, selector) { + var myself = this; + return detect(stage.children, function (morph) { + return morph instanceof WatcherMorph && + morph.getter === selector && + morph.target === (morph.isGlobal(selector) ? stage : myself); + }); +}; + +// SpriteMorph custom blocks + +SpriteMorph.prototype.deleteAllBlockInstances = function (definition) { + this.allBlockInstances(definition).forEach(function (each) { + each.deleteBlock(); + }); + this.customBlocks.forEach(function (def) { + if (def.body && def.body.expression.isCorpse) { + def.body = null; + } + }); +}; + +SpriteMorph.prototype.allBlockInstances = function (definition) { + var stage, objects, blocks = [], inDefinitions; + if (definition.isGlobal) { + stage = this.parentThatIsA(StageMorph); + objects = stage.children.filter(function (morph) { + return morph instanceof SpriteMorph; + }); + objects.push(stage); + objects.forEach(function (sprite) { + blocks = blocks.concat(sprite.allLocalBlockInstances(definition)); + }); + inDefinitions = []; + stage.globalBlocks.forEach(function (def) { + if (def.body) { + def.body.expression.allChildren().forEach(function (c) { + if (c.definition && (c.definition === definition)) { + inDefinitions.push(c); + } + }); + } + }); + return blocks.concat(inDefinitions); + } + return this.allLocalBlockInstances(definition); +}; + +SpriteMorph.prototype.allLocalBlockInstances = function (definition) { + var inScripts, inDefinitions, inBlockEditors, inPalette, result; + + inScripts = this.scripts.allChildren().filter(function (c) { + return c.definition && (c.definition === definition); + }); + + inDefinitions = []; + this.customBlocks.forEach(function (def) { + if (def.body) { + def.body.expression.allChildren().forEach(function (c) { + if (c.definition && (c.definition === definition)) { + inDefinitions.push(c); + } + }); + } + }); + + inBlockEditors = this.allEditorBlockInstances(definition); + inPalette = this.paletteBlockInstance(definition); + + result = inScripts.concat(inDefinitions).concat(inBlockEditors); + if (inPalette) { + result.push(inPalette); + } + return result; +}; + +SpriteMorph.prototype.allEditorBlockInstances = function (definition) { + var inBlockEditors = [], + world = this.world(); + + if (!world) {return []; } // when copying a sprite + + this.world().children.forEach(function (morph) { + if (morph instanceof BlockEditorMorph) { + morph.body.contents.allChildren().forEach(function (block) { + if (!block.isPrototype + && !(block instanceof PrototypeHatBlockMorph) + && (block.definition === definition)) { + inBlockEditors.push(block); + } + }); + } + }); + return inBlockEditors; +}; + + +SpriteMorph.prototype.paletteBlockInstance = function (definition) { + var ide = this.parentThatIsA(IDE_Morph); + if (!ide) {return null; } + return detect( + ide.palette.contents.children, + function (block) { + return block.definition === definition; + } + ); +}; + +SpriteMorph.prototype.usesBlockInstance = function (definition) { + var inDefinitions, + inScripts = detect( + this.scripts.allChildren(), + function (c) { + return c.definition && (c.definition === definition); + } + ); + + if (inScripts) {return true; } + + inDefinitions = []; + this.customBlocks.forEach(function (def) { + if (def.body) { + def.body.expression.allChildren().forEach(function (c) { + if (c.definition && (c.definition === definition)) { + inDefinitions.push(c); + } + }); + } + }); + return (inDefinitions.length > 0); +}; + +SpriteMorph.prototype.doubleDefinitionsFor = function (definition) { + var spec = definition.blockSpec(), + blockList, + idx, + stage; + + if (definition.isGlobal) { + stage = this.parentThatIsA(StageMorph); + if (!stage) {return []; } + blockList = stage.globalBlocks; + } else { + blockList = this.customBlocks; + } + idx = blockList.indexOf(definition); + if (idx === -1) {return []; } + return blockList.filter(function (def, i) { + return def.blockSpec() === spec && (i !== idx); + }); +}; + +SpriteMorph.prototype.replaceDoubleDefinitionsFor = function (definition) { + var doubles = this.doubleDefinitionsFor(definition), + myself = this, + stage, + ide; + doubles.forEach(function (double) { + myself.allBlockInstances(double).forEach(function (block) { + block.definition = definition; + block.refresh(); + }); + }); + if (definition.isGlobal) { + stage = this.parentThatIsA(StageMorph); + stage.globalBlocks = stage.globalBlocks.filter(function (def) { + return !contains(doubles, def); + }); + } else { + this.customBlocks = this.customBlocks.filter(function (def) { + return !contains(doubles, def); + }); + } + ide = this.parentThatIsA(IDE_Morph); + if (ide) { + ide.flushPaletteCache(); + ide.refreshPalette(); + } +}; + +// SpriteMorph thumbnail + +SpriteMorph.prototype.thumbnail = function (extentPoint) { +/* + answer a new Canvas of extentPoint dimensions containing + my thumbnail representation keeping the originial aspect ratio +*/ + var src = this.image, // at this time sprites aren't composite morphs + scale = Math.min( + (extentPoint.x / src.width), + (extentPoint.y / src.height) + ), + xOffset = (extentPoint.x - (src.width * scale)) / 2, + yOffset = (extentPoint.y - (src.height * scale)) / 2, + trg = newCanvas(extentPoint), + ctx = trg.getContext('2d'); + + ctx.save(); + ctx.scale(scale, scale); + ctx.drawImage( + src, + Math.floor(xOffset / scale), + Math.floor(yOffset / scale) + ); + return trg; +}; + +SpriteMorph.prototype.fullThumbnail = function (extentPoint) { + // containing parts and anchor symbols, if any + var thumb = this.thumbnail(extentPoint), + ctx = thumb.getContext('2d'), + ext = extentPoint.divideBy(3), + i = 0; + + ctx.restore(); + if (this.anchor) { + ctx.drawImage( + this.anchor.thumbnail(ext), + 0, + 0 + ); + } + for (i = 0; i < 3; i += 1) { + if (this.parts[i]) { + ctx.drawImage( + this.parts[i].thumbnail(ext), + i * ext.x, + extentPoint.y - ext.y + ); + } + } + return thumb; +}; + +// SpriteMorph Boolean visual representation + +SpriteMorph.prototype.booleanMorph = function (bool) { + // answer a block which can be shown in watchers, speech bubbles etc. + var block = new ReporterBlockMorph(true); + block.color = SpriteMorph.prototype.blockColor.operators; + block.setSpec(localize(bool.toString())); + return block; +}; + +// SpriteMorph nesting +/* + simulate Morphic trees +*/ + +SpriteMorph.prototype.attachPart = function (aSprite) { + var v = Date.now(); + if (aSprite.anchor) { + aSprite.anchor.detachPart(aSprite); + } + this.parts.push(aSprite); + this.version = v; + aSprite.anchor = this; + this.allParts().forEach(function (part) { + part.nestingScale = part.scale; + }); + aSprite.version = v; +}; + +SpriteMorph.prototype.detachPart = function (aSprite) { + var idx = this.parts.indexOf(aSprite), + v; + if (idx !== -1) { + v = Date.now(); + this.parts.splice(idx, 1); + this.version = v; + aSprite.anchor = null; + aSprite.version = v; + } +}; + +SpriteMorph.prototype.detachAllParts = function () { + var v = Date.now(); + + this.parts.forEach(function (part) { + part.anchor = null; + part.version = v; + }); + this.parts = []; + this.version = v; +}; + +SpriteMorph.prototype.detachFromAnchor = function () { + if (this.anchor) { + this.anchor.detachPart(this); + } +}; + +SpriteMorph.prototype.allParts = function () { + // includes myself + var result = [this]; + this.parts.forEach(function (part) { + result = result.concat(part.allParts()); + }); + return result; +}; + +SpriteMorph.prototype.allAnchors = function () { + // includes myself + var result = [this]; + if (this.anchor !== null) { + result = result.concat(this.anchor.allAnchors()); + } + return result; +}; + +// SpriteMorph highlighting + +SpriteMorph.prototype.addHighlight = function (oldHighlight) { + var isHidden = !this.isVisible, + highlight; + + if (isHidden) {this.show(); } + highlight = this.highlight( + oldHighlight ? oldHighlight.color : this.highlightColor, + this.highlightBorder + ); + this.addBack(highlight); + this.fullChanged(); + if (isHidden) {this.hide(); } + return highlight; +}; + +SpriteMorph.prototype.removeHighlight = function () { + var highlight = this.getHighlight(); + if (highlight !== null) { + this.fullChanged(); + this.removeChild(highlight); + } + return highlight; +}; + +SpriteMorph.prototype.toggleHighlight = function () { + if (this.getHighlight()) { + this.removeHighlight(); + } else { + this.addHighlight(); + } +}; + +SpriteMorph.prototype.highlight = function (color, border) { + var highlight = new SpriteHighlightMorph(), + fb = this.bounds, // sprites are not nested in a Morphic way + edge = border, + ctx; + + highlight.setExtent(fb.extent().add(edge * 2)); + highlight.color = color; + highlight.image = this.highlightImage(color, border); + ctx = highlight.image.getContext('2d'); + ctx.drawImage( + this.highlightImage(new Color(255, 255, 255), 4), + border - 4, + border - 4 + ); + ctx.drawImage( + this.highlightImage(new Color(50, 50, 50), 2), + border - 2, + border - 2 + ); + ctx.drawImage( + this.highlightImage(new Color(255, 255, 255), 1), + border - 1, + border - 1 + ); + highlight.setPosition(fb.origin.subtract(new Point(edge, edge))); + return highlight; +}; + +SpriteMorph.prototype.highlightImage = function (color, border) { + var fb, img, hi, ctx, out; + fb = this.extent(); + img = this.image; + + hi = newCanvas(fb.add(border * 2)); + ctx = hi.getContext('2d'); + + ctx.drawImage(img, 0, 0); + ctx.drawImage(img, border, 0); + ctx.drawImage(img, border * 2, 0); + ctx.drawImage(img, border * 2, border); + ctx.drawImage(img, border * 2, border * 2); + ctx.drawImage(img, border, border * 2); + ctx.drawImage(img, 0, border * 2); + ctx.drawImage(img, 0, border); + + ctx.globalCompositeOperation = 'destination-out'; + ctx.drawImage(img, border, border); + + out = newCanvas(fb.add(border * 2)); + ctx = out.getContext('2d'); + ctx.drawImage(hi, 0, 0); + ctx.globalCompositeOperation = 'source-atop'; + ctx.fillStyle = color.toString(); + ctx.fillRect(0, 0, out.width, out.height); + + return out; +}; + +SpriteMorph.prototype.getHighlight = function () { + var highlights; + highlights = this.children.slice(0).reverse().filter( + function (child) { + return child instanceof SpriteHighlightMorph; + } + ); + if (highlights.length !== 0) { + return highlights[0]; + } + return null; +}; + +// SpriteMorph nesting events + +SpriteMorph.prototype.mouseEnterDragging = function () { + var obj; + if (!this.enableNesting) {return; } + obj = this.world().hand.children[0]; + if (this.wantsDropOf(obj)) { + this.addHighlight(); + } +}; + +SpriteMorph.prototype.mouseLeave = function () { + if (!this.enableNesting) {return; } + this.removeHighlight(); +}; + +SpriteMorph.prototype.wantsDropOf = function (morph) { + // allow myself to be the anchor of another sprite + // by drag & drop + return this.enableNesting + && morph instanceof SpriteIconMorph + && !contains(morph.object.allParts(), this); +}; + +SpriteMorph.prototype.reactToDropOf = function (morph, hand) { + this.removeHighlight(); + this.attachPart(morph.object); + this.world().add(morph); + morph.slideBackTo(hand.grabOrigin); +}; + +// SpriteHighlightMorph ///////////////////////////////////////////////// + +// SpriteHighlightMorph inherits from Morph: + +SpriteHighlightMorph.prototype = new Morph(); +SpriteHighlightMorph.prototype.constructor = SpriteHighlightMorph; +SpriteHighlightMorph.uber = Morph.prototype; + +// SpriteHighlightMorph instance creation: + +function SpriteHighlightMorph() { + this.init(); +} + +// StageMorph ///////////////////////////////////////////////////////// + +/* + I inherit from FrameMorph and copy from SpriteMorph. +*/ + +// StageMorph inherits from FrameMorph: + +StageMorph.prototype = new FrameMorph(); +StageMorph.prototype.constructor = StageMorph; +StageMorph.uber = FrameMorph.prototype; + +// StageMorph preferences settings + +StageMorph.prototype.dimensions = new Point(480, 360); // unscaled extent + +StageMorph.prototype.frameRate = 0; // unscheduled per default + +StageMorph.prototype.isCachingPrimitives + = SpriteMorph.prototype.isCachingPrimitives; + +StageMorph.prototype.sliderColor + = SpriteMorph.prototype.sliderColor; + +StageMorph.prototype.paletteTextColor + = SpriteMorph.prototype.paletteTextColor; + +StageMorph.prototype.hiddenPrimitives = {}; +StageMorph.prototype.codeMappings = {}; +StageMorph.prototype.codeHeaders = {}; +StageMorph.prototype.enableCodeMapping = false; + +// StageMorph instance creation + +function StageMorph(globals) { + this.init(globals); +} + +StageMorph.prototype.init = function (globals) { + this.name = localize('Stage'); + this.threads = new ThreadManager(); + this.variables = new VariableFrame(globals || null, this); + this.scripts = new ScriptsMorph(this); + this.customBlocks = []; + this.globalBlocks = []; + this.costumes = new List(); + this.costume = null; + this.sounds = new List(); + this.version = Date.now(); // for observers + this.isFastTracked = false; + this.cloneCount = 0; + + this.timerStart = Date.now(); + this.tempo = 60; // bpm + this.lastMessage = ''; + + this.watcherUpdateFrequency = 2; + this.lastWatcherUpdate = Date.now(); + + this.scale = 1; // for display modes, do not persist + + this.keysPressed = {}; // for handling keyboard events, do not persist + this.blocksCache = {}; // not to be serialized (!) + this.paletteCache = {}; // not to be serialized (!) + this.lastAnswer = null; // last user input, do not persist + this.activeSounds = []; // do not persist + + this.trailsCanvas = null; + this.isThreadSafe = false; + + StageMorph.uber.init.call(this); + + this.acceptsDrops = false; + this.setColor(new Color(255, 255, 255)); + this.fps = this.frameRate; +}; + +// StageMorph scaling + +StageMorph.prototype.setScale = function (number) { + var delta = number / this.scale, + pos = this.position(), + relativePos, + bubble, + oldFlag = Morph.prototype.trackChanges, + myself = this; + + if (delta === 1) {return; } + Morph.prototype.trackChanges = false; + this.scale = number; + this.setExtent(this.dimensions.multiplyBy(number)); + + // now move and resize all children - sprites, bubbles, watchers etc.. + this.children.forEach(function (morph) { + relativePos = morph.position().subtract(pos); + morph.drawNew(); + morph.setPosition( + relativePos.multiplyBy(delta).add(pos), + true // just me (for nested sprites) + ); + if (morph instanceof SpriteMorph) { + bubble = morph.talkBubble(); + if (bubble) { + bubble.setScale(number); + morph.positionTalkBubble(); + } + } else if (morph instanceof StagePrompterMorph) { + if (myself.scale < 1) { + morph.setWidth(myself.width() - 10); + } else { + morph.setWidth(myself.dimensions.x - 20); + } + morph.fixLayout(); + morph.setCenter(myself.center()); + morph.setBottom(myself.bottom()); + } + }); + Morph.prototype.trackChanges = oldFlag; + this.changed(); +}; + +// StageMorph rendering + +StageMorph.prototype.drawNew = function () { + var ctx; + StageMorph.uber.drawNew.call(this); + if (this.costume) { + ctx = this.image.getContext('2d'); + ctx.scale(this.scale, this.scale); + ctx.drawImage( + this.costume.contents, + (this.width() / this.scale - this.costume.width()) / 2, + (this.height() / this.scale - this.costume.height()) / 2 + ); + } +}; + +StageMorph.prototype.drawOn = function (aCanvas, aRect) { + // make sure to draw the pen trails canvas as well + var rectangle, area, delta, src, context, w, h, sl, st, ws, hs; + if (!this.isVisible) { + return null; + } + rectangle = aRect || this.bounds; + area = rectangle.intersect(this.bounds).round(); + if (area.extent().gt(new Point(0, 0))) { + delta = this.position().neg(); + src = area.copy().translateBy(delta).round(); + context = aCanvas.getContext('2d'); + context.globalAlpha = this.alpha; + + sl = src.left(); + st = src.top(); + w = Math.min(src.width(), this.image.width - sl); + h = Math.min(src.height(), this.image.height - st); + + if (w < 1 || h < 1) { + return null; + } + context.drawImage( + this.image, + src.left(), + src.top(), + w, + h, + area.left(), + area.top(), + w, + h + ); + + // pen trails + ws = w / this.scale; + hs = h / this.scale; + context.save(); + context.scale(this.scale, this.scale); + context.drawImage( + this.penTrails(), + src.left() / this.scale, + src.top() / this.scale, + ws, + hs, + area.left() / this.scale, + area.top() / this.scale, + ws, + hs + ); + context.restore(); + } +}; + +StageMorph.prototype.clearPenTrails = function () { + this.trailsCanvas = newCanvas(this.dimensions); + this.changed(); +}; + +StageMorph.prototype.penTrails = function () { + if (!this.trailsCanvas) { + this.trailsCanvas = newCanvas(this.dimensions); + } + return this.trailsCanvas; +}; + +StageMorph.prototype.penTrailsMorph = function () { + // for collision detection purposes + var morph = new Morph(), + trails = this.penTrails(), + ctx; + morph.bounds = this.bounds.copy(); + morph.image = newCanvas(this.extent()); + ctx = morph.image.getContext('2d'); + ctx.drawImage( + trails, + 0, + 0, + trails.width, + trails.height, + 0, + 0, + this.image.width, + this.image.height + ); + return morph; +}; + +StageMorph.prototype.colorFiltered = function (aColor, excludedSprite) { + // answer a new Morph containing my image filtered by aColor + // ignore the excludedSprite, because its collision is checked + // ignore transparency (alpha) + var morph = new Morph(), + ext = this.extent(), + img = this.thumbnail(ext, excludedSprite), + ctx, + src, + clr, + i, + dta; + + src = img.getContext('2d').getImageData(0, 0, ext.x, ext.y); + morph.bounds = this.bounds.copy(); + morph.image = newCanvas(ext); + ctx = morph.image.getContext('2d'); + dta = ctx.createImageData(ext.x, ext.y); + for (i = 0; i < ext.x * ext.y * 4; i += 4) { + clr = new Color( + src.data[i], + src.data[i + 1], + src.data[i + 2] + ); + if (clr.eq(aColor)) { + dta.data[i] = src.data[i]; + dta.data[i + 1] = src.data[i + 1]; + dta.data[i + 2] = src.data[i + 2]; + dta.data[i + 3] = 255; + } + } + ctx.putImageData(dta, 0, 0); + return morph; +}; + +// StageMorph accessing + +StageMorph.prototype.watchers = function (leftPos) { +/* + answer an array of all currently visible watchers. + If leftPos is specified, filter the list for all + shown or hidden watchers whose left side equals + the given border (for automatic positioning) +*/ + return this.children.filter(function (morph) { + if (morph instanceof WatcherMorph) { + if (leftPos) { + return morph.left() === leftPos; + } + return morph.isVisible; + } + return false; + }); +}; + +// StageMorph timer + +StageMorph.prototype.resetTimer = function () { + this.timerStart = Date.now(); +}; + +StageMorph.prototype.getTimer = function () { + var elapsed = Math.floor((Date.now() - this.timerStart) / 100); + return elapsed / 10; +}; + +// StageMorph tempo + +StageMorph.prototype.setTempo = function (bpm) { + this.tempo = Math.max(20, (+bpm || 0)); +}; + +StageMorph.prototype.changeTempo = function (delta) { + this.setTempo(this.getTempo() + (+delta || 0)); +}; + +StageMorph.prototype.getTempo = function () { + return +this.tempo; +}; + +// StageMorph messages + +StageMorph.prototype.getLastMessage = function () { + return this.lastMessage || ''; +}; + +// StageMorph drag & drop + +StageMorph.prototype.wantsDropOf = function (aMorph) { + return aMorph instanceof SpriteMorph || + aMorph instanceof WatcherMorph || + aMorph instanceof ListWatcherMorph || + aMorph instanceof SpriteIconMorph; +}; + +StageMorph.prototype.reactToDropOf = function (morph, hand) { + if (morph instanceof SpriteIconMorph) { // detach sprite from anchor + if (morph.object.anchor) { + morph.object.anchor.detachPart(morph.object); + } + this.world().add(morph); + morph.slideBackTo(hand.grabOrigin); + } +}; + +// StageMorph stepping + +StageMorph.prototype.step = function () { + var current, elapsed, leftover, world = this.world(); + + // handle keyboard events + if (world.keyboardReceiver === null) { + world.keyboardReceiver = this; + } + if (world.currentKey === null) { + this.keyPressed = null; + } + + // manage threads + if (this.isFastTracked && this.threads.processes.length) { + this.children.forEach(function (morph) { + if (morph instanceof SpriteMorph) { + morph.wasWarped = morph.isWarped; + if (!morph.isWarped) { + morph.startWarp(); + } + } + }); + while ((Date.now() - this.lastTime) < 100) { + this.threads.step(); + } + this.children.forEach(function (morph) { + if (morph instanceof SpriteMorph) { + if (!morph.wasWarped) { + morph.endWarp(); + } + } + }); + this.changed(); + } else { + this.threads.step(); + } + + // update watchers + current = Date.now(); + elapsed = current - this.lastWatcherUpdate; + leftover = (1000 / this.watcherUpdateFrequency) - elapsed; + if (leftover < 1) { + this.watchers().forEach(function (w) { + w.update(); + }); + this.lastWatcherUpdate = Date.now(); + } +}; + +StageMorph.prototype.developersMenu = function () { + var myself = this, + menu = StageMorph.uber.developersMenu.call(this); + menu.addItem( + "stop", + function () { + myself.threads.stopAll(); + }, + 'terminate all running threads' + ); + return menu; +}; + +// StageMorph keyboard events + +StageMorph.prototype.processKeyDown = function (event) { + this.processKeyEvent( + event, + this.fireKeyEvent + ); +}; + +StageMorph.prototype.processKeyUp = function (event) { + this.processKeyEvent( + event, + this.removePressedKey + ); +}; + +StageMorph.prototype.processKeyEvent = function (event, action) { + var keyName; + + // this.inspectKeyEvent(event); + switch (event.keyCode) { + case 13: + keyName = 'enter'; + break; + case 27: + keyName = 'esc'; + break; + case 32: + keyName = 'space'; + break; + case 37: + keyName = 'left arrow'; + break; + case 39: + keyName = 'right arrow'; + break; + case 38: + keyName = 'up arrow'; + break; + case 40: + keyName = 'down arrow'; + break; + default: + keyName = String.fromCharCode(event.keyCode || event.charCode); + } + action.call(this, keyName); +}; + +StageMorph.prototype.fireKeyEvent = function (key) { + var evt = key.toLowerCase(), + hats = [], + procs = [], + myself = this; + + this.keysPressed[evt] = true; + if (evt === 'enter') { + return this.fireGreenFlagEvent(); + } + if (evt === 'esc') { + return this.fireStopAllEvent(); + } + this.children.concat(this).forEach(function (morph) { + if (morph instanceof SpriteMorph || morph instanceof StageMorph) { + hats = hats.concat(morph.allHatBlocksForKey(evt)); + } + }); + hats.forEach(function (block) { + procs.push(myself.threads.startProcess(block, myself.isThreadSafe)); + }); + return procs; +}; + +StageMorph.prototype.removePressedKey = function (key) { + delete this.keysPressed[key.toLowerCase()]; +}; + +StageMorph.prototype.processKeyPress = function (event) { + nop(event); +}; + +StageMorph.prototype.inspectKeyEvent + = CursorMorph.prototype.inspectKeyEvent; + +StageMorph.prototype.fireGreenFlagEvent = function () { + var procs = [], + hats = [], + ide = this.parentThatIsA(IDE_Morph), + myself = this; + + this.children.concat(this).forEach(function (morph) { + if (morph instanceof SpriteMorph || morph instanceof StageMorph) { + hats = hats.concat(morph.allHatBlocksFor('__shout__go__')); + } + }); + hats.forEach(function (block) { + procs.push(myself.threads.startProcess( + block, + myself.isThreadSafe + )); + }); + if (ide) { + ide.controlBar.pauseButton.refresh(); + } + return procs; +}; + +StageMorph.prototype.fireStopAllEvent = function () { + var ide = this.parentThatIsA(IDE_Morph); + this.threads.resumeAll(this.stage); + this.keysPressed = {}; + this.threads.stopAll(); + this.stopAllActiveSounds(); + this.children.forEach(function (morph) { + if (morph.stopTalking) { + morph.stopTalking(); + } + }); + this.removeAllClones(); + if (ide) { + ide.nextSteps([ + nop, + function () {ide.controlBar.pauseButton.refresh(); } + ]); + } +}; + +StageMorph.prototype.removeAllClones = function () { + var myself = this, + clones = this.children.filter( + function (morph) {return morph.isClone; } + ); + clones.forEach(function (clone) { + myself.threads.stopAllForReceiver(clone); + clone.destroy(); + }); + this.cloneCount = 0; +}; + +// StageMorph block templates + +StageMorph.prototype.blockTemplates = function (category) { + var blocks = [], myself = this, varNames, button, + cat = category || 'motion', txt; + + function block(selector) { + if (myself.hiddenPrimitives[selector]) { + return null; + } + var newBlock = SpriteMorph.prototype.blockForSelector(selector, true); + newBlock.isTemplate = true; + return newBlock; + } + + function variableBlock(varName) { + var newBlock = SpriteMorph.prototype.variableBlock(varName); + newBlock.isDraggable = false; + newBlock.isTemplate = true; + return newBlock; + } + + function watcherToggle(selector) { + if (myself.hiddenPrimitives[selector]) { + return null; + } + var info = SpriteMorph.prototype.blocks[selector]; + return new ToggleMorph( + 'checkbox', + this, + function () { + myself.toggleWatcher( + selector, + localize(info.spec), + myself.blockColor[info.category] + ); + }, + null, + function () { + return myself.showingWatcher(selector); + }, + null + ); + } + + function variableWatcherToggle(varName) { + return new ToggleMorph( + 'checkbox', + this, + function () { + myself.toggleVariableWatcher(varName); + }, + null, + function () { + return myself.showingVariableWatcher(varName); + }, + null + ); + } + + if (cat === 'motion') { + + txt = new TextMorph(localize( + 'Stage selected:\nno motion primitives' + )); + txt.fontSize = 9; + txt.setColor(this.paletteTextColor); + blocks.push(txt); + + } else if (cat === 'looks') { + + blocks.push(block('doSwitchToCostume')); + blocks.push(block('doWearNextCostume')); + blocks.push(watcherToggle('getCostumeIdx')); + blocks.push(block('getCostumeIdx')); + blocks.push('-'); + blocks.push(block('changeEffect')); + blocks.push(block('setEffect')); + blocks.push(block('clearEffects')); + + // for debugging: /////////////// + + if (this.world().isDevMode) { + blocks.push('-'); + txt = new TextMorph(localize( + 'development mode \ndebugging primitives:' + )); + txt.fontSize = 9; + txt.setColor(this.paletteTextColor); + blocks.push(txt); + blocks.push('-'); + blocks.push(block('log')); + blocks.push(block('alert')); + } + + ///////////////////////////////// + + } else if (cat === 'sound') { + + blocks.push(block('playSound')); + blocks.push(block('doPlaySoundUntilDone')); + blocks.push(block('doStopAllSounds')); + blocks.push('-'); + blocks.push(block('doRest')); + blocks.push('-'); + blocks.push(block('doPlayNote')); + blocks.push('-'); + blocks.push(block('doChangeTempo')); + blocks.push(block('doSetTempo')); + blocks.push(watcherToggle('getTempo')); + blocks.push(block('getTempo')); + + } else if (cat === 'pen') { + + blocks.push(block('clear')); + + } else if (cat === 'control') { + + blocks.push(block('receiveGo')); + blocks.push(block('receiveKey')); + blocks.push(block('receiveClick')); + blocks.push(block('receiveMessage')); + blocks.push('-'); + blocks.push(block('doBroadcast')); + blocks.push(block('doBroadcastAndWait')); + blocks.push(watcherToggle('getLastMessage')); + blocks.push(block('getLastMessage')); + blocks.push('-'); + blocks.push(block('doWarp')); + blocks.push('-'); + blocks.push(block('doWait')); + blocks.push(block('doWaitUntil')); + blocks.push('-'); + blocks.push(block('doForever')); + blocks.push(block('doRepeat')); + blocks.push(block('doUntil')); + blocks.push('-'); + blocks.push(block('doIf')); + blocks.push(block('doIfElse')); + blocks.push('-'); + blocks.push(block('doReport')); + blocks.push('-'); + blocks.push(block('doStopBlock')); + blocks.push(block('doStop')); + blocks.push(block('doStopAll')); + blocks.push('-'); + blocks.push(block('doRun')); + blocks.push(block('fork')); + blocks.push(block('evaluate')); + blocks.push('-'); + /* + // list variants commented out for now (redundant) + blocks.push(block('doRunWithInputList')); + blocks.push(block('forkWithInputList')); + blocks.push(block('evaluateWithInputList')); + blocks.push('-'); + */ + blocks.push(block('doCallCC')); + blocks.push(block('reportCallCC')); + blocks.push('-'); + blocks.push(block('createClone')); + blocks.push('-'); + blocks.push(block('doPauseAll')); + + } else if (cat === 'sensing') { + + blocks.push(block('doAsk')); + blocks.push(watcherToggle('getLastAnswer')); + blocks.push(block('getLastAnswer')); + blocks.push('-'); + blocks.push(block('reportMouseX')); + blocks.push(block('reportMouseY')); + blocks.push(block('reportMouseDown')); + blocks.push('-'); + blocks.push(block('reportKeyPressed')); + blocks.push('-'); + blocks.push(block('doResetTimer')); + blocks.push(watcherToggle('getTimer')); + blocks.push(block('getTimer')); + blocks.push('-'); + blocks.push(block('reportAttributeOf')); + blocks.push('-'); + blocks.push(block('reportURL')); + blocks.push('-'); + blocks.push(block('reportIsFastTracking')); + blocks.push(block('doSetFastTracking')); + + // for debugging: /////////////// + + if (this.world().isDevMode) { + + blocks.push('-'); + txt = new TextMorph(localize( + 'development mode \ndebugging primitives:' + )); + txt.fontSize = 9; + txt.setColor(this.paletteTextColor); + blocks.push(txt); + blocks.push('-'); + blocks.push(block('colorFiltered')); + blocks.push(block('reportStackSize')); + blocks.push(block('reportFrameCount')); + } + + ///////////////////////////////// + + } else if (cat === 'operators') { + + blocks.push(block('reifyScript')); + blocks.push(block('reifyReporter')); + blocks.push(block('reifyPredicate')); + blocks.push('#'); + blocks.push('-'); + blocks.push(block('reportSum')); + blocks.push(block('reportDifference')); + blocks.push(block('reportProduct')); + blocks.push(block('reportQuotient')); + blocks.push('-'); + blocks.push(block('reportModulus')); + blocks.push(block('reportRound')); + blocks.push(block('reportMonadic')); + blocks.push(block('reportRandom')); + blocks.push('-'); + blocks.push(block('reportLessThan')); + blocks.push(block('reportEquals')); + blocks.push(block('reportGreaterThan')); + blocks.push('-'); + blocks.push(block('reportAnd')); + blocks.push(block('reportOr')); + blocks.push(block('reportNot')); + blocks.push('-'); + blocks.push(block('reportTrue')); + blocks.push(block('reportFalse')); + blocks.push('-'); + blocks.push(block('reportJoinWords')); + blocks.push(block('reportLetter')); + blocks.push(block('reportStringSize')); + blocks.push('-'); + blocks.push(block('reportUnicode')); + blocks.push(block('reportUnicodeAsLetter')); + blocks.push('-'); + blocks.push(block('reportIsA')); + blocks.push(block('reportIsIdentical')); + + // for debugging: /////////////// + + if (this.world().isDevMode) { + blocks.push('-'); + txt = new TextMorph( + 'development mode \ndebugging primitives:' + ); + txt.fontSize = 9; + txt.setColor(this.paletteTextColor); + blocks.push(txt); + blocks.push('-'); + blocks.push(block('reportTypeOf')); + blocks.push(block('reportTextFunction')); + blocks.push(block('reportTextSplit')); + } + + ////////////////////////////////// + + } else if (cat === 'variables') { + + button = new PushButtonMorph( + null, + function () { + new VariableDialogMorph( + null, + function (pair) { + if (pair && !myself.variables.silentFind(pair[0])) { + myself.addVariable(pair[0], pair[1]); + myself.toggleVariableWatcher(pair[0], pair[1]); + myself.blocksCache[cat] = null; + myself.paletteCache[cat] = null; + myself.parentThatIsA(IDE_Morph).refreshPalette(); + } + }, + myself + ).prompt( + 'Variable name', + null, + myself.world() + ); + }, + 'Make a variable' + ); + blocks.push(button); + + if (this.variables.allNames().length > 0) { + button = new PushButtonMorph( + null, + function () { + var menu = new MenuMorph( + myself.deleteVariable, + null, + myself + ); + myself.variables.allNames().forEach(function (name) { + menu.addItem(name, name); + }); + menu.popUpAtHand(myself.world()); + }, + 'Delete a variable' + ); + blocks.push(button); + } + + blocks.push('-'); + + varNames = this.variables.allNames(); + if (varNames.length > 0) { + varNames.forEach(function (name) { + blocks.push(variableWatcherToggle(name)); + blocks.push(variableBlock(name)); + }); + blocks.push('-'); + } + + blocks.push(block('doSetVar')); + blocks.push(block('doChangeVar')); + blocks.push(block('doShowVar')); + blocks.push(block('doHideVar')); + blocks.push(block('doDeclareVariables')); + + blocks.push('='); + + blocks.push(block('reportNewList')); + blocks.push('-'); + blocks.push(block('reportCONS')); + blocks.push(block('reportListItem')); + blocks.push(block('reportCDR')); + blocks.push('-'); + blocks.push(block('reportListLength')); + blocks.push(block('reportListContainsItem')); + blocks.push('-'); + blocks.push(block('doAddToList')); + blocks.push(block('doDeleteFromList')); + blocks.push(block('doInsertInList')); + blocks.push(block('doReplaceInList')); + + blocks.push('='); + + if (StageMorph.prototype.enableCodeMapping) { + blocks.push(block('doMapCodeOrHeader')); + blocks.push(block('doMapStringCode')); + blocks.push(block('doMapListCode')); + blocks.push('-'); + blocks.push(block('reportMappedCode')); + blocks.push('='); + } + + button = new PushButtonMorph( + null, + function () { + var ide = myself.parentThatIsA(IDE_Morph); + new BlockDialogMorph( + null, + function (definition) { + if (definition.spec !== '') { + if (definition.isGlobal) { + myself.globalBlocks.push(definition); + } else { + myself.customBlocks.push(definition); + } + ide.flushPaletteCache(); + ide.refreshPalette(); + new BlockEditorMorph(definition, myself).popUp(); + } + }, + myself + ).prompt( + 'Make a block', + null, + myself.world() + ); + }, + 'Make a block' + ); + blocks.push(button); + } + return blocks; +}; + +// StageMorph primitives + +StageMorph.prototype.clear = function () { + this.clearPenTrails(); +}; + +// StageMorph user menu + +StageMorph.prototype.userMenu = function () { + var ide = this.parentThatIsA(IDE_Morph), + menu = new MenuMorph(this), + shiftClicked = this.world().currentKey === 16, + myself = this; + + if (ide && ide.isAppMode) { + menu.addItem('help', 'nop'); + return menu; + } + menu.addItem("edit", 'edit'); + menu.addItem("show all", 'showAll'); + menu.addItem( + "pic...", + function () { + window.open(myself.fullImageClassic().toDataURL()); + }, + 'open a new window\nwith a picture of the stage' + ); + if (shiftClicked) { + menu.addLine(); + menu.addItem( + "turn pen trails into new costume...", + function () { + var costume = new Costume( + myself.trailsCanvas, + Date.now().toString() + ).copy(); + ide.currentSprite.addCostume(costume); + ide.currentSprite.wearCostume(costume); + ide.hasChangedMedia = true; + }, + 'turn all pen trails and stamps\n' + + 'into a new costume for the\ncurrently selected sprite', + new Color(100, 0, 0) + ); + } + return menu; +}; + +StageMorph.prototype.showAll = function () { + var myself = this; + this.children.forEach(function (m) { + m.show(); + m.keepWithin(myself); + if (m.fixLayout) {m.fixLayout(); } + }); +}; + +StageMorph.prototype.edit = SpriteMorph.prototype.edit; + +// StageMorph thumbnail + +StageMorph.prototype.thumbnail = function (extentPoint, excludedSprite) { +/* + answer a new Canvas of extentPoint dimensions containing + my thumbnail representation keeping the originial aspect ratio +*/ + var myself = this, + src = this.image, + scale = Math.min( + (extentPoint.x / src.width), + (extentPoint.y / src.height) + ), + trg = newCanvas(extentPoint), + ctx = trg.getContext('2d'), + fb; + + ctx.scale(scale, scale); + ctx.drawImage( + src, + 0, + 0 + ); + ctx.drawImage( + this.penTrails(), + 0, + 0, + this.dimensions.x * this.scale, + this.dimensions.y * this.scale + ); + this.children.forEach(function (morph) { + if (morph !== excludedSprite) { + fb = morph.fullBounds(); + ctx.drawImage( + morph.fullImage(), + fb.origin.x - myself.bounds.origin.x, + fb.origin.y - myself.bounds.origin.y + ); + } + }); + return trg; +}; + +// StageMorph cloning overrice + +StageMorph.prototype.createClone = nop; + +// StageMorph pseudo-inherited behavior + +StageMorph.prototype.categories = SpriteMorph.prototype.categories; +StageMorph.prototype.blockColor = SpriteMorph.prototype.blockColor; +StageMorph.prototype.paletteColor = SpriteMorph.prototype.paletteColor; +StageMorph.prototype.setName = SpriteMorph.prototype.setName; +StageMorph.prototype.palette = SpriteMorph.prototype.palette; +StageMorph.prototype.freshPalette = SpriteMorph.prototype.freshPalette; +StageMorph.prototype.showingWatcher = SpriteMorph.prototype.showingWatcher; +StageMorph.prototype.addVariable = SpriteMorph.prototype.addVariable; +StageMorph.prototype.deleteVariable = SpriteMorph.prototype.deleteVariable; + +// StageMorph block rendering + +StageMorph.prototype.blockForSelector + = SpriteMorph.prototype.blockForSelector; + +// StageMorph variable watchers (for palette checkbox toggling) + +StageMorph.prototype.findVariableWatcher + = SpriteMorph.prototype.findVariableWatcher; + +StageMorph.prototype.toggleVariableWatcher + = SpriteMorph.prototype.toggleVariableWatcher; + +StageMorph.prototype.showingVariableWatcher + = SpriteMorph.prototype.showingVariableWatcher; + +StageMorph.prototype.deleteVariableWatcher + = SpriteMorph.prototype.deleteVariableWatcher; + +// StageMorph background management + +StageMorph.prototype.addCostume + = SpriteMorph.prototype.addCostume; + +StageMorph.prototype.wearCostume + = SpriteMorph.prototype.wearCostume; + +StageMorph.prototype.getCostumeIdx + = SpriteMorph.prototype.getCostumeIdx; + +StageMorph.prototype.doWearNextCostume + = SpriteMorph.prototype.doWearNextCostume; + +StageMorph.prototype.doWearPreviousCostume + = SpriteMorph.prototype.doWearPreviousCostume; + +StageMorph.prototype.doSwitchToCostume + = SpriteMorph.prototype.doSwitchToCostume; + +// StageMorph graphic effects + +StageMorph.prototype.setEffect + = SpriteMorph.prototype.setEffect; + +StageMorph.prototype.getGhostEffect + = SpriteMorph.prototype.getGhostEffect; + +StageMorph.prototype.changeEffect + = SpriteMorph.prototype.changeEffect; + +StageMorph.prototype.clearEffects + = SpriteMorph.prototype.clearEffects; + +// StageMorph sound management + +StageMorph.prototype.addSound + = SpriteMorph.prototype.addSound; + +StageMorph.prototype.playSound + = SpriteMorph.prototype.playSound; + +StageMorph.prototype.stopAllActiveSounds = function () { + this.activeSounds.forEach(function (audio) { + audio.pause(); + }); + this.activeSounds = []; +}; + +StageMorph.prototype.pauseAllActiveSounds = function () { + this.activeSounds.forEach(function (audio) { + audio.pause(); + }); +}; + +StageMorph.prototype.resumeAllActiveSounds = function () { + this.activeSounds.forEach(function (audio) { + audio.play(); + }); +}; + +// StageMorph non-variable watchers + +StageMorph.prototype.toggleWatcher + = SpriteMorph.prototype.toggleWatcher; + +StageMorph.prototype.showingWatcher + = SpriteMorph.prototype.showingWatcher; + +StageMorph.prototype.watcherFor = + SpriteMorph.prototype.watcherFor; + +StageMorph.prototype.getLastAnswer + = SpriteMorph.prototype.getLastAnswer; + +// StageMorph message broadcasting + +StageMorph.prototype.allMessageNames + = SpriteMorph.prototype.allMessageNames; + +StageMorph.prototype.allHatBlocksFor + = SpriteMorph.prototype.allHatBlocksFor; + +StageMorph.prototype.allHatBlocksForKey + = SpriteMorph.prototype.allHatBlocksForKey; + +// StageMorph events + +StageMorph.prototype.mouseClickLeft + = SpriteMorph.prototype.mouseClickLeft; + +// StageMorph custom blocks + +StageMorph.prototype.deleteAllBlockInstances + = SpriteMorph.prototype.deleteAllBlockInstances; + +StageMorph.prototype.allBlockInstances + = SpriteMorph.prototype.allBlockInstances; + +StageMorph.prototype.allLocalBlockInstances + = SpriteMorph.prototype.allLocalBlockInstances; + +StageMorph.prototype.allEditorBlockInstances + = SpriteMorph.prototype.allEditorBlockInstances; + +StageMorph.prototype.paletteBlockInstance + = SpriteMorph.prototype.paletteBlockInstance; + +StageMorph.prototype.usesBlockInstance + = SpriteMorph.prototype.usesBlockInstance; + +StageMorph.prototype.doubleDefinitionsFor + = SpriteMorph.prototype.doubleDefinitionsFor; + +StageMorph.prototype.replaceDoubleDefinitionsFor + = SpriteMorph.prototype.replaceDoubleDefinitionsFor; + +// SpriteBubbleMorph //////////////////////////////////////////////////////// + +/* + I am a sprite's scaleable speech bubble. I rely on SpriteMorph + for my preferences settings +*/ + +// SpriteBubbleMorph inherits from SpeechBubbleMorph: + +SpriteBubbleMorph.prototype = new SpeechBubbleMorph(); +SpriteBubbleMorph.prototype.constructor = SpriteBubbleMorph; +SpriteBubbleMorph.uber = SpeechBubbleMorph.prototype; + +// SpriteBubbleMorph instance creation: + +function SpriteBubbleMorph(data, scale, isThought, isQuestion) { + this.init(data, scale, isThought, isQuestion); +} + +SpriteBubbleMorph.prototype.init = function ( + data, + scale, + isThought, + isQuestion +) { + var sprite = SpriteMorph.prototype; + this.scale = scale || 1; + this.data = data; + this.isQuestion = isQuestion; + + SpriteBubbleMorph.uber.init.call( + this, + this.dataAsMorph(data), + sprite.bubbleColor, + null, + null, + isQuestion ? sprite.blockColor.sensing : sprite.bubbleBorderColor, + null, + isThought + ); +}; + +// SpriteBubbleMorph contents formatting + +SpriteBubbleMorph.prototype.dataAsMorph = function (data) { + var contents, + sprite = SpriteMorph.prototype, + isText, + img, + scaledImg, + width; + + if (data instanceof Morph) { + contents = data; + } else if (isString(data)) { + isText = true; + contents = new TextMorph( + data, + sprite.bubbleFontSize * this.scale, + null, // fontStyle + sprite.bubbleFontIsBold, + false, // italic + 'center' + ); + } else if (typeof data === 'boolean') { + img = sprite.booleanMorph(data).fullImage(); + contents = new Morph(); + contents.silentSetWidth(img.width); + contents.silentSetHeight(img.height); + contents.image = img; + } else if (data instanceof HTMLCanvasElement) { + contents = new Morph(); + contents.silentSetWidth(data.width); + contents.silentSetHeight(data.height); + contents.image = data; + } else if (data instanceof List) { + contents = new ListWatcherMorph(data); + contents.isDraggable = false; + contents.update(true); + contents.step = contents.update; + } else if (data instanceof Context) { + img = data.image(); + contents = new Morph(); + contents.silentSetWidth(img.width); + contents.silentSetHeight(img.height); + contents.image = img; + } else { + contents = new TextMorph( + data.toString(), + sprite.bubbleFontSize * this.scale, + null, // fontStyle + sprite.bubbleFontIsBold, + false, // italic + 'center' + ); + } + if (contents instanceof TextMorph) { + // reflow text boundaries + width = Math.max( + contents.width(), + sprite.bubbleCorner * 2 * this.scale + ); + if (isText) { + width = Math.min(width, sprite.bubbleMaxTextWidth * this.scale); + } + contents.setWidth(width); + } else if (!(data instanceof List)) { + // scale contents image + scaledImg = newCanvas(contents.extent().multiplyBy(this.scale)); + scaledImg.getContext('2d').drawImage( + contents.image, + 0, + 0, + scaledImg.width, + scaledImg.height + ); + contents.image = scaledImg; + contents.bounds = contents.bounds.scaleBy(this.scale); + } + return contents; +}; + +// SpriteBubbleMorph scaling + +SpriteBubbleMorph.prototype.setScale = function (scale) { + this.scale = scale; + this.changed(); + this.drawNew(); + this.changed(); +}; + +// SpriteBubbleMorph drawing: + +SpriteBubbleMorph.prototype.drawNew = function () { + var sprite = SpriteMorph.prototype; + + // scale my settings + this.edge = sprite.bubbleCorner * this.scale; + this.border = sprite.bubbleBorder * this.scale; + this.padding = sprite.bubbleCorner / 2 * this.scale; + + // re-build my contents + if (this.contentsMorph) { + this.contentsMorph.destroy(); + } + this.contentsMorph = this.dataAsMorph(this.data); + this.add(this.contentsMorph); + + // adjust my layout + this.silentSetWidth(this.contentsMorph.width() + + (this.padding ? this.padding * 2 : this.edge * 2)); + this.silentSetHeight(this.contentsMorph.height() + + this.edge + + this.border * 2 + + this.padding * 2 + + 2); + + // draw my outline + SpeechBubbleMorph.uber.drawNew.call(this); + + // position my contents + this.contentsMorph.setPosition(this.position().add( + new Point( + this.padding || this.edge, + this.border + this.padding + 1 + ) + )); +}; + +// SpriteBubbleMorph resizing: + +SpriteBubbleMorph.prototype.fixLayout = function () { + // to be used when resizing list watchers + // otherwise use drawNew() to force re-layout + + var sprite = SpriteMorph.prototype; + + this.changed(); + // scale my settings + this.edge = sprite.bubbleCorner * this.scale; + this.border = sprite.bubbleBorder * this.scale; + this.padding = sprite.bubbleCorner / 2 * this.scale; + + // adjust my layout + this.silentSetWidth(this.contentsMorph.width() + + (this.padding ? this.padding * 2 : this.edge * 2)); + this.silentSetHeight(this.contentsMorph.height() + + this.edge + + this.border * 2 + + this.padding * 2 + + 2); + + // draw my outline + SpeechBubbleMorph.uber.drawNew.call(this); + + // position my contents + this.contentsMorph.setPosition(this.position().add( + new Point( + this.padding || this.edge, + this.border + this.padding + 1 + ) + )); + this.changed(); +}; + +// Costume ///////////////////////////////////////////////////////////// + +/* + I am a picture that's "wearable" by a sprite. My rotationCenter is + relative to my contents position. +*/ + +// Costume instance creation + +function Costume(canvas, name, rotationCenter) { + this.contents = canvas || newCanvas(); + this.shrinkToFit(this.maxExtent); + this.name = name || null; + this.rotationCenter = rotationCenter || this.center(); + this.version = Date.now(); // for observer optimization + this.loaded = null; // for de-serialization only +} + +Costume.prototype.maxExtent = StageMorph.prototype.dimensions; + +Costume.prototype.toString = function () { + return 'a Costume(' + this.name + ')'; +}; + +// Costume dimensions - all relative + +Costume.prototype.extent = function () { + return new Point(this.contents.width, this.contents.height); +}; + +Costume.prototype.center = function () { + return this.extent().divideBy(2); +}; + +Costume.prototype.width = function () { + return this.contents.width; +}; + +Costume.prototype.height = function () { + return this.contents.height; +}; + +Costume.prototype.bounds = function () { + return new Rectangle(0, 0, this.width(), this.height()); +}; + +// Costume shrink-wrapping + +Costume.prototype.shrinkWrap = function () { + // adjust my contents' bounds to my visible bounding box + var bb = this.boundingBox(), + ext = bb.extent(), + pic = newCanvas(ext), + ctx = pic.getContext('2d'); + + ctx.drawImage( + this.contents, + bb.origin.x, + bb.origin.y, + ext.x, + ext.y, + 0, + 0, + ext.x, + ext.y + ); + this.rotationCenter = this.rotationCenter.subtract(bb.origin); + this.contents = pic; + this.version = Date.now(); +}; + +Costume.prototype.boundingBox = function () { + // answer the rectangle surrounding my contents' non-transparent pixels + var row, + col, + pic = this.contents, + w = pic.width, + h = pic.height, + ctx = pic.getContext('2d'), + dta = ctx.getImageData(0, 0, w, h); + + function getAlpha(x, y) { + return dta.data[((y * w * 4) + (x * 4)) + 3]; + } + + function getLeft() { + for (col = 0; col <= w; col += 1) { + for (row = 0; row <= h; row += 1) { + if (getAlpha(col, row)) { + return col; + } + } + } + return 0; + } + + function getTop() { + for (row = 0; row <= h; row += 1) { + for (col = 0; col <= h; col += 1) { + if (getAlpha(col, row)) { + return row; + } + } + } + return 0; + } + + function getRight() { + for (col = w; col >= 0; col -= 1) { + for (row = h; row >= 0; row -= 1) { + if (getAlpha(col, row)) { + return Math.min(col + 1, w); + } + } + } + return w; + } + + function getBottom() { + for (row = h; row >= 0; row -= 1) { + for (col = w; col >= 0; col -= 1) { + if (getAlpha(col, row)) { + return Math.min(row + 1, h); + } + } + } + return h; + } + + return new Rectangle(getLeft(), getTop(), getRight(), getBottom()); +}; + +// Costume duplication + +Costume.prototype.copy = function () { + var canvas = newCanvas(this.extent()), + cpy, + ctx; + + ctx = canvas.getContext('2d'); + ctx.drawImage(this.contents, 0, 0); + cpy = new Costume(canvas, this.name ? copy(this.name) : null); + cpy.rotationCenter = this.rotationCenter.copy(); + return cpy; +}; + +// Costume flipping + +Costume.prototype.flipped = function () { +/* + answer a copy of myself flipped horizontally + (mirrored along a vertical axis), used for + SpriteMorph's rotation style type 2 +*/ + var canvas = newCanvas(this.extent()), + ctx = canvas.getContext('2d'), + flipped; + + ctx.translate(this.width(), 0); + ctx.scale(-1, 1); + ctx.drawImage(this.contents, 0, 0); + flipped = new Costume( + canvas, + new Point( + this.width() - this.rotationCenter.x, + this.rotationCenter.y + ) + ); + return flipped; +}; + +// Costume actions + +Costume.prototype.edit = function (aWorld, anIDE, isnew, oncancel, onsubmit) { + var myself = this, + editor = new PaintEditorMorph(); + editor.oncancel = oncancel || nop; + editor.openIn( + aWorld, + isnew ? + newCanvas(new Point(480, 360)) : + this.contents, + isnew ? + new Point(240, 180) : + this.rotationCenter, + function (img, rc) { + myself.contents = img; + myself.rotationCenter = rc; + myself.shrinkWrap(); + myself.version = Date.now(); + aWorld.changed(); + if (anIDE) { + anIDE.currentSprite.wearCostume(myself); + anIDE.hasChangedMedia = true; + } + (onsubmit || nop)(); + } + ); +}; + +Costume.prototype.editRotationPointOnly = function (aWorld) { + var editor = new CostumeEditorMorph(this), + action, + dialog, + txt; + + action = function () {editor.accept(); }; + dialog = new DialogBoxMorph(this, action); + txt = new TextMorph( + localize('click or drag crosshairs to move the rotation center'), + dialog.fontSize, + dialog.fontStyle, + true, + false, + 'center', + null, + null, + new Point(1, 1), + new Color(255, 255, 255) + ); + + dialog.labelString = 'Costume Editor'; + dialog.createLabel(); + dialog.setPicture(editor); + dialog.addBody(txt); + dialog.addButton('ok', 'Ok'); + dialog.addButton('cancel', 'Cancel'); + dialog.fixLayout(); + dialog.drawNew(); + dialog.fixLayout(); + dialog.popUp(aWorld); +}; + +// Costume thumbnail + +Costume.prototype.shrinkToFit = function (extentPoint) { + if (extentPoint.x < this.width() || (extentPoint.y < this.height())) { + this.contents = this.thumbnail(extentPoint); + } +}; + +Costume.prototype.thumbnail = function (extentPoint) { +/* + answer a new Canvas of extentPoint dimensions containing + my thumbnail representation keeping the originial aspect ratio +*/ + var src = this.contents, // at this time sprites aren't composite morphs + scale = Math.min( + (extentPoint.x / src.width), + (extentPoint.y / src.height) + ), + xOffset = (extentPoint.x - (src.width * scale)) / 2, + yOffset = (extentPoint.y - (src.height * scale)) / 2, + trg = newCanvas(extentPoint), + ctx = trg.getContext('2d'); + + ctx.scale(scale, scale); + ctx.drawImage( + src, + Math.floor(xOffset / scale), + Math.floor(yOffset / scale) + ); + return trg; +}; + +// SVG_Costume ///////////////////////////////////////////////////////////// + +/* + I am a costume containing an SVG image. +*/ + +// SVG_Costume inherits from Costume: + +SVG_Costume.prototype = new Costume(); +SVG_Costume.prototype.constructor = SVG_Costume; +SVG_Costume.uber = Costume.prototype; + +// SVG_Costume instance creation + +function SVG_Costume(svgImage, name, rotationCenter) { + this.contents = svgImage; + this.shrinkToFit(this.maxExtent); + this.name = name || null; + this.rotationCenter = rotationCenter || this.center(); + this.version = Date.now(); // for observer optimization + this.loaded = null; // for de-serialization only +} + +SVG_Costume.prototype.toString = function () { + return 'an SVG_Costume(' + this.name + ')'; +}; + +// SVG_Costume duplication + +SVG_Costume.prototype.copy = function () { + var img = new Image(), + cpy; + img.src = this.contents.src; + cpy = new SVG_Costume(img, this.name ? copy(this.name) : null); + cpy.rotationCenter = this.rotationCenter.copy(); + return cpy; +}; + +// SVG_Costume flipping + +/* + flipping is currently inherited from Costume, which rasterizes it. + Therefore flipped SVG costumes may appear pixelated until we add + a method to either truly flip SVGs or change the Sprite's drawNew() + method to scale the costume before flipping it +*/ + +// SVG_Costume thumbnail + +SVG_Costume.prototype.shrinkToFit = function (extentPoint) { + // overridden for unrasterized SVGs + nop(extentPoint); + return; +}; + +// CostumeEditorMorph //////////////////////////////////////////////////////// + +// CostumeEditorMorph inherits from Morph: + +CostumeEditorMorph.prototype = new Morph(); +CostumeEditorMorph.prototype.constructor = CostumeEditorMorph; +CostumeEditorMorph.uber = Morph.prototype; + +// CostumeEditorMorph preferences settings: +CostumeEditorMorph.prototype.size = Costume.prototype.maxExtent; + +// CostumeEditorMorph instance creation + +function CostumeEditorMorph(costume) { + this.init(costume); +} + +CostumeEditorMorph.prototype.init = function (costume) { + this.costume = costume || new Costume(); + this.rotationCenter = this.costume.rotationCenter.copy(); + this.margin = new Point(0, 0); + CostumeEditorMorph.uber.init.call(this); + this.noticesTransparentClick = true; +}; + +// CostumeEditorMorph edit ops + +CostumeEditorMorph.prototype.accept = function () { + this.costume.rotationCenter = this.rotationCenter.copy(); + this.costume.version = Date.now(); +}; + +// CostumeEditorMorph displaying + +CostumeEditorMorph.prototype.drawNew = function () { + var rp, ctx; + + this.margin = this.size.subtract(this.costume.extent()).divideBy(2); + rp = this.rotationCenter.add(this.margin); + + this.silentSetExtent(this.size); + + this.image = newCanvas(this.extent()); + + // draw the background + if (!this.cachedTexture) { + this.cachedTexture = this.createTexture(); + + } + this.drawCachedTexture(); + +/* + pattern = ctx.createPattern(this.background, 'repeat'); + ctx.fillStyle = pattern; + ctx.fillRect(0, 0, this.size.x, this.size.y); +*/ + + ctx = this.image.getContext('2d'); + + // draw the costume + ctx.drawImage(this.costume.contents, this.margin.x, this.margin.y); + + // draw crosshairs: + ctx.globalAlpha = 0.5; + + // circle around center: + ctx.fillStyle = 'white'; + ctx.beginPath(); + ctx.arc( + rp.x, + rp.y, + 20, + radians(0), + radians(360), + false + ); + ctx.closePath(); + ctx.fill(); + ctx.stroke(); + + ctx.beginPath(); + ctx.arc( + rp.x, + rp.y, + 10, + radians(0), + radians(360), + false + ); + ctx.stroke(); + + // horizontal line: + ctx.beginPath(); + ctx.moveTo(0, rp.y); + ctx.lineTo(this.costume.width() + this.margin.x * 2, rp.y); + ctx.stroke(); + + // vertical line: + ctx.beginPath(); + ctx.moveTo(rp.x, 0); + ctx.lineTo(rp.x, this.costume.height() + this.margin.y * 2); + ctx.stroke(); +}; + +CostumeEditorMorph.prototype.createTexture = function () { + var size = 5, + texture = newCanvas(new Point(size * 2, size * 2)), + ctx = texture.getContext('2d'), + grey = new Color(230, 230, 230); + + ctx.fillStyle = 'white'; + ctx.fillRect(0, 0, size * 2, size * 2); + ctx.fillStyle = grey.toString(); + ctx.fillRect(0, 0, size, size); + ctx.fillRect(size, size, size, size); + return texture; +}; + + +// CostumeEditorMorph events + +CostumeEditorMorph.prototype.mouseDownLeft = function (pos) { + this.rotationCenter = pos.subtract( + this.position().add(this.margin) + ); + this.drawNew(); + this.changed(); +}; + +CostumeEditorMorph.prototype.mouseMove + = CostumeEditorMorph.prototype.mouseDownLeft; + +// Sound ///////////////////////////////////////////////////////////// + +// Sound instance creation + +function Sound(audio, name) { + this.audio = audio; // mandatory + this.name = name || "Sound"; +} + +Sound.prototype.play = function () { + // return an instance of an audio element which can be terminated + // externally (i.e. by the stage) + var aud = document.createElement('audio'); + aud.src = this.audio.src; + aud.play(); + return aud; +}; + +Sound.prototype.copy = function () { + var snd = document.createElement('audio'), + cpy; + + snd.src = this.audio.src; + cpy = new Sound(snd, this.name ? copy(this.name) : null); + return cpy; +}; + +Sound.prototype.toDataURL = function () { + return this.audio.src; +}; + +// Note ///////////////////////////////////////////////////////// + +// I am a single musical note + +// Note instance creation + +function Note(pitch) { + this.pitch = pitch === 0 ? 0 : pitch || 69; + this.setupContext(); + this.oscillator = null; +} + +// Note shared properties + +Note.prototype.audioContext = null; +Note.prototype.gainNode = null; + +// Note audio context + +Note.prototype.setupContext = function () { + if (this.audioContext) { return; } + var AudioContext = (function () { + // cross browser some day? + return window.AudioContext || + window.mozAudioContext || + window.msAudioContext || + window.oAudioContext || + window.webkitAudioContext; + }()); + if (!AudioContext) { + throw new Error('Web Audio API is not supported\nin this browser'); + } + Note.prototype.audioContext = new AudioContext(); + Note.prototype.gainNode = Note.prototype.audioContext.createGainNode(); + Note.prototype.gainNode.gain.value = 0.25; // reduce volume by 1/4 +}; + +// Note playing + +Note.prototype.play = function () { + this.oscillator = this.audioContext.createOscillator(); + this.oscillator.type = 0; + this.oscillator.frequency.value = + Math.pow(2, (this.pitch - 69) / 12) * 440; + this.oscillator.connect(this.gainNode); + this.gainNode.connect(this.audioContext.destination); + this.oscillator.noteOn(0); // deprecated, renamed to start() +}; + +Note.prototype.stop = function () { + if (this.oscillator) { + this.oscillator.noteOff(0); // deprecated, renamed to stop() + this.oscillator = null; + } +}; + +// CellMorph ////////////////////////////////////////////////////////// + +/* + I am a spreadsheet style cell that can display either a string, + a Morph, a Canvas or a toString() representation of anything else. + I can be used in variable watchers or list view element cells. +*/ + +// CellMorph inherits from BoxMorph: + +CellMorph.prototype = new BoxMorph(); +CellMorph.prototype.constructor = CellMorph; +CellMorph.uber = BoxMorph.prototype; + +// CellMorph instance creation: + +function CellMorph(contents, color, idx, parentCell) { + this.init(contents, color, idx, parentCell); +} + +CellMorph.prototype.init = function (contents, color, idx, parentCell) { + this.contents = (contents === 0 ? 0 + : contents === false ? false + : contents || ''); + this.isEditable = isNil(idx) ? false : true; + this.idx = idx || null; // for list watchers + this.parentCell = parentCell || null; // for list circularity detection + CellMorph.uber.init.call( + this, + SyntaxElementMorph.prototype.corner, + 1.000001, // shadow bug in Chrome, + new Color(255, 255, 255) + ); + this.color = color || new Color(255, 140, 0); + this.isBig = false; + this.drawNew(); +}; + +// CellMorph accessing: + +CellMorph.prototype.big = function () { + this.isBig = true; + this.changed(); + this.drawNew(); + this.changed(); +}; + +CellMorph.prototype.normal = function () { + this.isBig = false; + this.changed(); + this.drawNew(); + this.changed(); +}; + +// CellMorph circularity testing: + + +CellMorph.prototype.isCircular = function (list) { + if (!this.parentCell) {return false; } + if (list instanceof List) { + return this.contents === list || this.parentCell.isCircular(list); + } + return this.parentCell.isCircular(this.contents); +}; + +// CellMorph layout: + +CellMorph.prototype.fixLayout = function () { + var listwatcher; + this.changed(); + this.drawNew(); + this.changed(); + if (this.parent && this.parent.fixLayout) { // variable watcher + this.parent.fixLayout(); + } else { + listwatcher = this.parentThatIsA(ListWatcherMorph); + if (listwatcher) { + listwatcher.fixLayout(); + } + } +}; + +// CellMorph drawing: + +CellMorph.prototype.drawNew = function () { + var context, + txt, + img, + fontSize = SyntaxElementMorph.prototype.fontSize, + isSameList = this.contentsMorph instanceof ListWatcherMorph + && (this.contentsMorph.list === this.contents); + + if (this.isBig) { + fontSize = fontSize * 1.5; + } + + // re-build my contents + if (this.contentsMorph && !isSameList) { + this.contentsMorph.destroy(); + } + + if (!isSameList) { + if (this.contents instanceof Morph) { + this.contentsMorph = this.contents; + } else if (isString(this.contents)) { + txt = this.contents.length > 500 ? + this.contents.slice(0, 500) + '...' : this.contents; + this.contentsMorph = new TextMorph( + txt, + fontSize, + null, + true, + false, + 'left' // was formerly 'center', reverted b/c of code-mapping + ); + if (this.isEditable) { + this.contentsMorph.isEditable = true; + this.contentsMorph.enableSelecting(); + } + this.contentsMorph.setColor(new Color(255, 255, 255)); + } else if (typeof this.contents === 'boolean') { + this.contentsMorph = SpriteMorph.prototype.booleanMorph.call( + null, + this.contents + ); + } else if (this.contents instanceof HTMLCanvasElement) { + this.contentsMorph = new Morph(); + this.contentsMorph.silentSetWidth(this.contents.width); + this.contentsMorph.silentSetHeight(this.contents.height); + this.contentsMorph.image = this.contents; + } else if (this.contents instanceof Context) { + img = this.contents.image(); + this.contentsMorph = new Morph(); + this.contentsMorph.silentSetWidth(img.width); + this.contentsMorph.silentSetHeight(img.height); + this.contentsMorph.image = img; + } else if (this.contents instanceof List) { + if (this.isCircular()) { + this.contentsMorph = new TextMorph( + '(...)', + fontSize, + null, + false, // bold + true, // italic + 'center' + ); + this.contentsMorph.setColor(new Color(255, 255, 255)); + } else { + this.contentsMorph = new ListWatcherMorph( + this.contents, + this + ); + this.contentsMorph.isDraggable = false; + } + } else { + this.contentsMorph = new TextMorph( + !isNil(this.contents) ? this.contents.toString() : '', + fontSize, + null, + true, + false, + 'center' + ); + if (this.isEditable) { + this.contentsMorph.isEditable = true; + this.contentsMorph.enableSelecting(); + } + this.contentsMorph.setColor(new Color(255, 255, 255)); + } + this.add(this.contentsMorph); + } + + // adjust my layout + this.silentSetHeight(this.contentsMorph.height() + + this.edge + + this.border * 2); + this.silentSetWidth(Math.max( + this.contentsMorph.width() + this.edge * 2, + (this.contents instanceof Context || + this.contents instanceof List ? 0 : this.height() * 2) + )); + + // draw my outline + this.image = newCanvas(this.extent()); + context = this.image.getContext('2d'); + if ((this.edge === 0) && (this.border === 0)) { + BoxMorph.uber.drawNew.call(this); + return null; + } + context.fillStyle = this.color.toString(); + context.beginPath(); + this.outlinePath( + context, + Math.max(this.edge - this.border, 0), + this.border + ); + context.closePath(); + context.fill(); + if (this.border > 0 && !MorphicPreferences.isFlat) { + context.lineWidth = this.border; + context.strokeStyle = this.borderColor.toString(); + context.beginPath(); + this.outlinePath(context, this.edge, this.border / 2); + context.closePath(); + context.stroke(); + + context.shadowOffsetX = this.border; + context.shadowOffsetY = this.border; + context.shadowBlur = this.border; + context.shadowColor = this.color.darker(80).toString(); + this.drawShadow(context, this.edge, this.border / 2); + } + + // position my contents + if (!isSameList) { + this.contentsMorph.setCenter(this.center()); + } +}; + +CellMorph.prototype.drawShadow = function (context, radius, inset) { + var offset = radius + inset, + w = this.width(), + h = this.height(); + + // bottom left: + context.beginPath(); + context.moveTo(0, h - offset); + context.lineTo(0, offset); + context.stroke(); + + // top left: + context.beginPath(); + context.arc( + offset, + offset, + radius, + radians(-180), + radians(-90), + false + ); + context.stroke(); + + // top right: + context.beginPath(); + context.moveTo(offset, 0); + context.lineTo(w - offset, 0); + context.stroke(); +}; + +// CellMorph editing (inside list watchers): + +CellMorph.prototype.layoutChanged = function () { + var context, + fontSize = SyntaxElementMorph.prototype.fontSize, + listWatcher = this.parentThatIsA(ListWatcherMorph); + + if (this.isBig) { + fontSize = fontSize * 1.5; + } + + // adjust my layout + this.silentSetHeight(this.contentsMorph.height() + + this.edge + + this.border * 2); + this.silentSetWidth(Math.max( + this.contentsMorph.width() + this.edge * 2, + (this.contents instanceof Context || + this.contents instanceof List ? 0 : this.height() * 2) + )); + + + // draw my outline + this.image = newCanvas(this.extent()); + context = this.image.getContext('2d'); + if ((this.edge === 0) && (this.border === 0)) { + BoxMorph.uber.drawNew.call(this); + return null; + } + context.fillStyle = this.color.toString(); + context.beginPath(); + this.outlinePath( + context, + Math.max(this.edge - this.border, 0), + this.border + ); + context.closePath(); + context.fill(); + if (this.border > 0 && !MorphicPreferences.isFlat) { + context.lineWidth = this.border; + context.strokeStyle = this.borderColor.toString(); + context.beginPath(); + this.outlinePath(context, this.edge, this.border / 2); + context.closePath(); + context.stroke(); + + context.shadowOffsetX = this.border; + context.shadowOffsetY = this.border; + context.shadowBlur = this.border; + context.shadowColor = this.color.darker(80).toString(); + this.drawShadow(context, this.edge, this.border / 2); + } + + // position my contents + this.contentsMorph.setCenter(this.center()); + + if (listWatcher) { + listWatcher.fixLayout(); + } +}; + +CellMorph.prototype.reactToEdit = function (textMorph) { + var listWatcher; + if (!isNil(this.idx)) { + listWatcher = this.parentThatIsA(ListWatcherMorph); + if (listWatcher) { + listWatcher.list.put(textMorph.text, this.idx); + } + } +}; + +CellMorph.prototype.mouseClickLeft = function (pos) { + if (this.isEditable && this.contentsMorph instanceof TextMorph) { + this.contentsMorph.selectAllAndEdit(); + } else { + this.escalateEvent('mouseClickLeft', pos); + } +}; + +// WatcherMorph ////////////////////////////////////////////////////////// + +/* + I am a little window which observes some value and continuously + updates itself accordingly. + + My target can be either a SpriteMorph or a VariableFrame. +*/ + +// WatcherMorph inherits from BoxMorph: + +WatcherMorph.prototype = new BoxMorph(); +WatcherMorph.prototype.constructor = WatcherMorph; +WatcherMorph.uber = BoxMorph.prototype; + +// WatcherMorph instance creation: + +function WatcherMorph(label, color, target, getter, isHidden) { + this.init(label, color, target, getter, isHidden); +} + +WatcherMorph.prototype.init = function ( + label, + color, + target, + getter, + isHidden +) { + // additional properties + this.labelText = label || ''; + this.version = null; + this.objName = ''; + + // initialize inherited properties + WatcherMorph.uber.init.call( + this, + SyntaxElementMorph.prototype.rounding, + 1.000001, // shadow bug in Chrome, + new Color(120, 120, 120) + ); + + // override inherited behavior + this.color = new Color(220, 220, 220); + this.readoutColor = color; + this.style = 'normal'; + this.target = target || null; // target obj (Sprite) or VariableFrame + this.getter = getter || null; // callback or variable name (string) + this.currentValue = null; + this.labelMorph = null; + this.sliderMorph = null; + this.cellMorph = null; + this.isDraggable = true; + this.fixLayout(); + this.update(); + if (isHidden) { // for de-serializing + this.hide(); + } +}; + +// WatcherMorph accessing: + +WatcherMorph.prototype.isTemporary = function () { + var stage = this.parentThatIsA(StageMorph); + if (this.target instanceof VariableFrame) { + if (stage) { + if (this.target === stage.variables.parentFrame) { + return false; // global + } + } + return this.target.owner === null; + } + return false; +}; + +WatcherMorph.prototype.object = function () { + // answer the actual sprite I refer to + return this.target instanceof VariableFrame ? + this.target.owner : this.target; +}; + +WatcherMorph.prototype.isGlobal = function (selector) { + return contains( + ['getTimer', 'getLastAnswer', 'getTempo', 'getLastMessage'], + selector + ); +}; + +// WatcherMorph slider accessing: + +WatcherMorph.prototype.setSliderMin = function (num) { + if (this.target instanceof VariableFrame) { + this.sliderMorph.setSize(1); + this.sliderMorph.setStart(num); + this.sliderMorph.setSize(this.sliderMorph.rangeSize() / 5); + } +}; + +WatcherMorph.prototype.setSliderMax = function (num) { + if (this.target instanceof VariableFrame) { + this.sliderMorph.setSize(1); + this.sliderMorph.setStop(num); + this.sliderMorph.setSize(this.sliderMorph.rangeSize() / 5); + } +}; + +// WatcherMorph updating: + +WatcherMorph.prototype.update = function () { + var newValue, + num; + if (this.target && this.getter) { + this.updateLabel(); + if (this.target instanceof VariableFrame) { + newValue = this.target.vars[this.getter]; + } else { + newValue = this.target[this.getter](); + } + if (newValue !== this.currentValue) { + this.changed(); + this.cellMorph.contents = newValue; + this.cellMorph.drawNew(); + num = parseFloat(newValue); + if (!isNaN(num)) { + this.sliderMorph.value = num; + this.sliderMorph.drawNew(); + } + this.fixLayout(); + this.currentValue = newValue; + } + } + if (this.cellMorph.contentsMorph instanceof ListWatcherMorph) { + this.cellMorph.contentsMorph.update(); + } +}; + +WatcherMorph.prototype.updateLabel = function () { + // check whether the target object's name has been changed + var obj = this.object(); + + if (!obj || this.isGlobal(this.getter)) { return; } + if (obj.version !== this.version) { + this.objName = obj.name ? obj.name + ' ' : ' '; + if (this.labelMorph) { + this.labelMorph.destroy(); + this.labelMorph = null; + this.fixLayout(); + } + } +}; + +// WatcherMorph layout: + +WatcherMorph.prototype.fixLayout = function () { + var fontSize = SyntaxElementMorph.prototype.fontSize, isList, + myself = this; + + this.changed(); + + // create my parts + if (this.labelMorph === null) { + this.labelMorph = new StringMorph( + this.objName + this.labelText, + fontSize, + null, + true, + false, + false, + MorphicPreferences.isFlat ? new Point() : new Point(1, 1), + new Color(255, 255, 255) + ); + this.add(this.labelMorph); + } + if (this.cellMorph === null) { + this.cellMorph = new CellMorph('', this.readoutColor); + this.add(this.cellMorph); + } + if (this.sliderMorph === null) { + this.sliderMorph = new SliderMorph( + 0, + 100, + 0, + 20, + 'horizontal' + ); + this.sliderMorph.alpha = 1; + this.sliderMorph.button.color = this.color.darker(); + this.sliderMorph.color = this.color.lighter(60); + this.sliderMorph.button.highlightColor = this.color.darker(); + this.sliderMorph.button.highlightColor.b += 50; + this.sliderMorph.button.pressColor = this.color.darker(); + this.sliderMorph.button.pressColor.b += 100; + this.sliderMorph.setHeight(fontSize); + this.sliderMorph.action = function (num) { + myself.target.vars[myself.getter] = Math.round(num); + }; + this.add(this.sliderMorph); + } + + // adjust my layout + isList = this.cellMorph.contents instanceof List; + if (isList) { this.style = 'normal'; } + + if (this.style === 'large') { + this.labelMorph.hide(); + this.sliderMorph.hide(); + this.cellMorph.big(); + this.cellMorph.setPosition(this.position()); + this.setExtent(this.cellMorph.extent().subtract(1)); + return; + } + + this.labelMorph.show(); + this.sliderMorph.show(); + this.cellMorph.normal(); + this.labelMorph.setPosition(this.position().add(new Point( + this.edge, + this.border + SyntaxElementMorph.prototype.typeInPadding + ))); + + if (isList) { + this.cellMorph.setPosition(this.labelMorph.bottomLeft().add( + new Point(0, SyntaxElementMorph.prototype.typeInPadding) + )); + } else { + this.cellMorph.setPosition(this.labelMorph.topRight().add(new Point( + fontSize / 3, + 0 + ))); + this.labelMorph.setTop( + this.cellMorph.top() + + (this.cellMorph.height() - this.labelMorph.height()) / 2 + ); + } + + if (this.style === 'slider') { + this.sliderMorph.silentSetPosition(new Point( + this.labelMorph.left(), + this.cellMorph.bottom() + + SyntaxElementMorph.prototype.typeInPadding + )); + this.sliderMorph.setWidth(this.cellMorph.right() + - this.labelMorph.left()); + this.silentSetHeight( + this.cellMorph.height() + + this.sliderMorph.height() + + this.border * 2 + + SyntaxElementMorph.prototype.typeInPadding * 3 + ); + } else { + this.sliderMorph.hide(); + this.bounds.corner.y = this.cellMorph.bottom() + + this.border + + SyntaxElementMorph.prototype.typeInPadding; + } + this.bounds.corner.x = Math.max( + this.cellMorph.right(), + this.labelMorph.right() + ) + this.edge + + SyntaxElementMorph.prototype.typeInPadding; + this.drawNew(); + this.changed(); +}; + +// WatcherMorph events: + +/* +// Scratch-like watcher-toggling, commented out b/c we have a drop-down menu + +WatcherMorph.prototype.mouseClickLeft = function () { + if (this.style === 'normal') { + if (this.target instanceof VariableFrame) { + this.style = 'slider'; + } else { + this.style = 'large'; + } + } else if (this.style === 'slider') { + this.style = 'large'; + } else { + this.style = 'normal'; + } + this.fixLayout(); +}; +*/ + +// WatcherMorph user menu: + +WatcherMorph.prototype.userMenu = function () { + var myself = this, + menu = new MenuMorph(this), + on = '\u25CF', + off = '\u25CB'; + menu.addItem( + (this.style === 'normal' ? on : off) + ' ' + localize('normal'), + 'styleNormal' + ); + menu.addItem( + (this.style === 'large' ? on : off) + ' ' + localize('large'), + 'styleLarge' + ); + if (this.target instanceof VariableFrame) { + menu.addItem( + (this.style === 'slider' ? on : off) + ' ' + localize('slider'), + 'styleSlider' + ); + menu.addLine(); + menu.addItem( + 'slider min...', + 'userSetSliderMin' + ); + menu.addItem( + 'slider max...', + 'userSetSliderMax' + ); + menu.addLine(); + menu.addItem( + 'import...', + function () { + var inp = document.createElement('input'), + ide = myself.parentThatIsA(IDE_Morph); + if (ide.filePicker) { + document.body.removeChild(ide.filePicker); + ide.filePicker = null; + } + inp.type = 'file'; + inp.style.color = "transparent"; + inp.style.backgroundColor = "transparent"; + inp.style.border = "none"; + inp.style.outline = "none"; + inp.style.position = "absolute"; + inp.style.top = "0px"; + inp.style.left = "0px"; + inp.style.width = "0px"; + inp.style.height = "0px"; + inp.addEventListener( + "change", + function () { + var file, i; + + function readText(aFile) { + var frd = new FileReader(); + frd.onloadend = function (e) { + myself.target.setVar( + myself.getter, + e.target.result + ); + }; + frd.readAsText(aFile); + } + + document.body.removeChild(inp); + ide.filePicker = null; + if (inp.files.length > 0) { + for (i = 0; i < inp.files.length; i += 1) { + file = inp.files[i]; + if (file.type.indexOf("text") === 0) { + readText(file); + } + } + } + }, + false + ); + document.body.appendChild(inp); + ide.filePicker = inp; + inp.click(); + } + ); + if (this.currentValue && + (isString(this.currentValue) || !isNaN(+this.currentValue))) { + menu.addItem( + 'export...', + function () { + window.open( + 'data:text/plain,' + + encodeURIComponent(this.currentValue.toString()) + ); + } + ); + } + } + return menu; +}; + +WatcherMorph.prototype.setStyle = function (style) { + this.style = style; + this.fixLayout(); +}; + +WatcherMorph.prototype.styleNormal = function () { + this.setStyle('normal'); +}; + +WatcherMorph.prototype.styleLarge = function () { + this.setStyle('large'); +}; + +WatcherMorph.prototype.styleSlider = function () { + this.setStyle('slider'); +}; + +WatcherMorph.prototype.userSetSliderMin = function () { + new DialogBoxMorph( + this, + this.setSliderMin, + this + ).prompt( + "Slider minimum value", + this.sliderMorph.start.toString(), + this.world(), + null, // pic + null, // choices + null, // read only + true // numeric + ); +}; + +WatcherMorph.prototype.userSetSliderMax = function () { + new DialogBoxMorph( + this, + this.setSliderMax, + this + ).prompt( + "Slider maximum value", + this.sliderMorph.stop.toString(), + this.world(), + null, // pic + null, // choices + null, // read only + true // numeric + ); +}; + +// WatcherMorph drawing: + +WatcherMorph.prototype.drawNew = function () { + var context, + gradient; + this.image = newCanvas(this.extent()); + context = this.image.getContext('2d'); + if (MorphicPreferences.isFlat || (this.edge === 0 && this.border === 0)) { + BoxMorph.uber.drawNew.call(this); + return; + } + gradient = context.createLinearGradient(0, 0, 0, this.height()); + gradient.addColorStop(0, this.color.lighter().toString()); + gradient.addColorStop(1, this.color.darker().toString()); + context.fillStyle = gradient; + context.beginPath(); + this.outlinePath( + context, + Math.max(this.edge - this.border, 0), + this.border + ); + context.closePath(); + context.fill(); + if (this.border > 0) { + gradient = context.createLinearGradient(0, 0, 0, this.height()); + gradient.addColorStop(0, this.borderColor.lighter().toString()); + gradient.addColorStop(1, this.borderColor.darker().toString()); + context.lineWidth = this.border; + context.strokeStyle = gradient; + context.beginPath(); + this.outlinePath(context, this.edge, this.border / 2); + context.closePath(); + context.stroke(); + } +}; + +// StagePrompterMorph //////////////////////////////////////////////////////// + +/* + I am a sensor-category-colored input box at the bottom of the stage + which lets the user answer to a question. If I am opened from within + the context of a sprite, my question can be anything that is displayable + in a SpeechBubble and will be, if I am opened from within the stage + my question will be shown as a single line of text within my label morph. +*/ + +// StagePrompterMorph inherits from BoxMorph: + +StagePrompterMorph.prototype = new BoxMorph(); +StagePrompterMorph.prototype.constructor = StagePrompterMorph; +StagePrompterMorph.uber = BoxMorph.prototype; + +// StagePrompterMorph instance creation: + +function StagePrompterMorph(question) { + this.init(question); +} + +StagePrompterMorph.prototype.init = function (question) { + // question is optional in case the Stage is asking + var myself = this; + + // additional properties + this.isDone = false; + if (question) { + this.label = new StringMorph( + question, + SpriteMorph.prototype.bubbleFontSize, + null, // fontStyle + SpriteMorph.prototype.bubbleFontIsBold, + false, // italic + 'left' + ); + } else { + this.label = null; + } + this.inputField = new InputFieldMorph(); + this.button = new PushButtonMorph( + null, + function () {myself.accept(); }, + '\u2713' + ); + + // initialize inherited properties + StagePrompterMorph.uber.init.call( + this, + SyntaxElementMorph.prototype.rounding, + SpriteMorph.prototype.bubbleBorder, + SpriteMorph.prototype.blockColor.sensing + ); + + // override inherited behavior + this.color = new Color(255, 255, 255); + if (this.label) {this.add(this.label); } + this.add(this.inputField); + this.add(this.button); + this.setWidth(480 - 20); + this.fixLayout(); +}; + +// StagePrompterMorph layout: + +StagePrompterMorph.prototype.fixLayout = function () { + var y = 0; + if (this.label) { + this.label.setPosition(new Point( + this.left() + this.edge, + this.top() + this.edge + )); + y = this.label.bottom() - this.top(); + } + this.inputField.setPosition(new Point( + this.left() + this.edge, + this.top() + y + this.edge + )); + this.inputField.setWidth( + this.width() + - this.edge * 2 + - this.button.width() + - this.border + ); + this.button.setCenter(this.inputField.center()); + this.button.setLeft(this.inputField.right() + this.border); + this.setHeight( + this.inputField.bottom() + - this.top() + + this.edge + ); +}; + +// StagePrompterMorph events: + +StagePrompterMorph.prototype.mouseClickLeft = function () { + this.inputField.edit(); +}; + +StagePrompterMorph.prototype.accept = function () { + this.isDone = true; +}; diff --git a/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/paint.js b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/paint.js new file mode 100644 index 0000000..5179b20 --- /dev/null +++ b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/paint.js @@ -0,0 +1,932 @@ +/* + paint.js + + a paint editor for Snap! + inspired by the Scratch paint editor. + + written by Kartik Chandra + Copyright (C) 2013 by Kartik Chandra + + This file is part of Snap!. + + Snap! is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + + + toc + --- + the following list shows the order in which all constructors are + defined. Use this list to locate code in this document: + + PaintEditorMorph + PaintColorPickerMorph + PaintCanvasMorph + + + credits + ------- + Nathan Dinsmore contributed a fully working prototype, + Nathan's brilliant flood-fill tool has been more or less + directly imported into this paint implementation. + + Jens Mönig has contributed icons and bugfixes and says he has probably + introduced many other bugs in that process. :-) + + + revision history + ---------------- + May 10 - first full release (Kartik) + May 14 - bugfixes, Snap integration (Jens) + May 16 - flat design adjustments (Jens) + July 12 - pipette tool, code formatting adjustments (Jens) + + */ + +/*global Point, Rectangle, DialogBoxMorph, fontHeight, AlignmentMorph, + FrameMorph, PushButtonMorph, Color, SymbolMorph, newCanvas, Morph, TextMorph, + CostumeIconMorph, IDE_Morph, Costume, SpriteMorph, nop, Image, WardrobeMorph, + TurtleIconMorph, localize, MenuMorph, InputFieldMorph, SliderMorph, + ToggleMorph, ToggleButtonMorph, BoxMorph, modules, radians, + MorphicPreferences, getDocumentPositionOf + */ + +// Global stuff //////////////////////////////////////////////////////// + +modules.paint = '2013-July-13'; + +// Declarations + +var PaintEditorMorph; +var PaintCanvasMorph; +var PaintColorPickerMorph; + +// PaintEditorMorph ////////////////////////// + +// A complete paint editor + +PaintEditorMorph.prototype = new DialogBoxMorph(); +PaintEditorMorph.prototype.constructor = PaintEditorMorph; +PaintEditorMorph.uber = DialogBoxMorph.prototype; + +PaintEditorMorph.prototype.padding = 10; + +function PaintEditorMorph() { + this.init(); +} + +PaintEditorMorph.prototype.init = function () { + // additional properties: + this.paper = null; // paint canvas + this.oncancel = null; + + // initialize inherited properties: + PaintEditorMorph.uber.init.call(this); + + // override inherited properties: + this.labelString = "Paint Editor"; + this.createLabel(); + + // build contents: + this.buildContents(); +}; + +PaintEditorMorph.prototype.buildContents = function () { + var myself = this; + + this.paper = new PaintCanvasMorph(function () {return myself.shift; }); + this.paper.setExtent(new Point(480, 360)); + + this.addBody(new AlignmentMorph('row', this.padding)); + this.controls = new AlignmentMorph('column', this.padding); + this.controls.alignment = 'left'; + + this.edits = new AlignmentMorph('row', this.padding); + this.buildEdits(); + this.controls.add(this.edits); + + this.body.color = this.color; + + this.body.add(this.controls); + this.body.add(this.paper); + + this.toolbox = new BoxMorph(); + this.toolbox.color = SpriteMorph.prototype.paletteColor.lighter(8); + this.toolbox.borderColor = this.toolbox.color.lighter(40); + if (MorphicPreferences.isFlat) { + this.toolbox.edge = 0; + } + + this.buildToolbox(); + this.controls.add(this.toolbox); + + this.propertiesControls = { + colorpicker: null, + penSizeSlider: null, + penSizeField: null, + primaryColorButton: null, + primaryColorViewer: null, + constrain: null + }; + this.populatePropertiesMenu(); + + this.addButton("ok", "OK"); + this.addButton("cancel", "Cancel"); + + this.refreshToolButtons(); + this.fixLayout(); + this.drawNew(); +}; + +PaintEditorMorph.prototype.buildToolbox = function () { + var tools = { + brush: + "Paintbrush tool\n(free draw)", + rectangle: + "Stroked Rectangle\n(shift: square)", + circle: + "Stroked Ellipse\n(shift: circle)", + eraser: + "Eraser tool", + crosshairs: + "Set the rotation center", + + line: + "Line tool\n(shift: vertical/horizontal)", + rectangleSolid: + "Filled Rectangle\n(shift: square)", + circleSolid: + "Filled Ellipse\n(shift: circle)", + paintbucket: + "Fill a region", + pipette: + "Pipette tool\n(pick a color anywhere)" + }, + myself = this, + left = this.toolbox.left(), + top = this.toolbox.top(), + padding = 2, + inset = 5, + x = 0, + y = 0; + + Object.keys(tools).forEach(function (tool) { + var btn = myself.toolButton(tool, tools[tool]); + btn.setPosition(new Point( + left + x, + top + y + )); + x += btn.width() + padding; + if (tool === "crosshairs") { + x = 0; + y += btn.height() + padding; + myself.paper.drawcrosshair(); + } + myself.toolbox[tool] = btn; + myself.toolbox.add(btn); + }); + + this.toolbox.bounds = this.toolbox.fullBounds().expandBy(inset * 2); + this.toolbox.drawNew(); +}; + +PaintEditorMorph.prototype.buildEdits = function () { + var paper = this.paper; + + this.edits.add(this.pushButton( + "undo", + function () {paper.undo(); } + )); + + this.edits.add(this.pushButton( + "clear", + function () {paper.clearCanvas(); } + )); + this.edits.fixLayout(); +}; + +PaintEditorMorph.prototype.openIn = function (world, oldim, oldrc, callback) { + // Open the editor in a world with an optional image to edit + this.oldim = oldim; + this.oldrc = oldrc.copy(); + this.callback = callback || nop; + + this.processKeyUp = function () { + this.shift = false; + this.propertiesControls.constrain.refresh(); + }; + + this.processKeyDown = function () { + this.shift = this.world().currentKey === 16; + this.propertiesControls.constrain.refresh(); + }; + + //merge oldim: + if (this.oldim) { + this.paper.centermerge(this.oldim, this.paper.paper); + this.paper.rotationCenter = + this.oldrc.add( + new Point( + (this.paper.paper.width - this.oldim.width) / 2, + (this.paper.paper.height - this.oldim.height) / 2 + ) + ); + this.paper.drawNew(); + } + + this.key = 'paint'; + this.popUp(world); +}; + +PaintEditorMorph.prototype.fixLayout = function () { + var oldFlag = Morph.prototype.trackChanges; + + this.changed(); + oldFlag = Morph.prototype.trackChanges; + Morph.prototype.trackChanges = false; + + if (this.paper) { + this.paper.buildContents(); + this.paper.drawNew(); + } + if (this.controls) {this.controls.fixLayout(); } + if (this.body) {this.body.fixLayout(); } + PaintEditorMorph.uber.fixLayout.call(this); + + Morph.prototype.trackChanges = oldFlag; + this.changed(); +}; + +PaintEditorMorph.prototype.refreshToolButtons = function () { + this.toolbox.children.forEach(function (toggle) { + toggle.refresh(); + }); +}; + +PaintEditorMorph.prototype.ok = function () { + this.callback( + this.paper.paper, + this.paper.rotationCenter + ); + this.destroy(); +}; + +PaintEditorMorph.prototype.cancel = function () { + if (this.oncancel) {this.oncancel(); } + this.destroy(); +}; + +PaintEditorMorph.prototype.populatePropertiesMenu = function () { + var c = this.controls, + myself = this, + pc = this.propertiesControls, + alpen = new AlignmentMorph("row", this.padding); + + pc.primaryColorViewer = new Morph(); + pc.primaryColorViewer.setExtent(new Point(180, 50)); + pc.primaryColorViewer.color = new Color(0, 0, 0); + pc.colorpicker = new PaintColorPickerMorph( + new Point(180, 100), + function (color) { + var ni = newCanvas(pc.primaryColorViewer.extent()), + ctx = ni.getContext("2d"), + i, + j; + myself.paper.settings.primarycolor = color; + if (color === "transparent") { + for (i = 0; i < 180; i += 5) { + for (j = 0; j < 15; j += 5) { + ctx.fillStyle = + ((j + i) / 5) % 2 === 0 ? + "rgba(0, 0, 0, 0.2)" : + "rgba(0, 0, 0, 0.5)"; + ctx.fillRect(i, j, 5, 5); + + } + } + } else { + ctx.fillStyle = color.toString(); + ctx.fillRect(0, 0, 180, 15); + } + ctx.strokeStyle = "black"; + ctx.lineWidth = Math.min(myself.paper.settings.linewidth, 20); + ctx.beginPath(); + ctx.lineCap = "round"; + ctx.moveTo(20, 30); + ctx.lineTo(160, 30); + ctx.stroke(); + pc.primaryColorViewer.image = ni; + pc.primaryColorViewer.changed(); + } + ); + pc.colorpicker.action(new Color(0, 0, 0)); + + pc.penSizeSlider = new SliderMorph(0, 20, 5, 5); + pc.penSizeSlider.orientation = "horizontal"; + pc.penSizeSlider.setHeight(15); + pc.penSizeSlider.setWidth(150); + pc.penSizeSlider.action = function (num) { + if (pc.penSizeField) { + pc.penSizeField.setContents(num); + } + myself.paper.settings.linewidth = num; + pc.colorpicker.action(myself.paper.settings.primarycolor); + }; + pc.penSizeField = new InputFieldMorph("5", true, null, false); + pc.penSizeField.contents().minWidth = 20; + pc.penSizeField.setWidth(25); + pc.penSizeField.accept = function () { + var val = parseFloat(pc.penSizeField.getValue()); + pc.penSizeSlider.value = val; + pc.penSizeSlider.drawNew(); + pc.penSizeSlider.updateValue(); + this.setContents(val); + myself.paper.settings.linewidth = val; + this.world().keyboardReceiver = myself; + pc.colorpicker.action(myself.paper.settings.primarycolor); + }; + alpen.add(pc.penSizeSlider); + alpen.add(pc.penSizeField); + alpen.color = myself.color; + alpen.fixLayout(); + pc.penSizeField.drawNew(); + pc.constrain = new ToggleMorph( + "checkbox", + this, + function () {myself.shift = !myself.shift; }, + "Constrain proportions of shapes?\n(you can also hold shift)", + function () {return myself.shift; } + ); + c.add(pc.colorpicker); + //c.add(pc.primaryColorButton); + c.add(pc.primaryColorViewer); + c.add(new TextMorph("Brush size")); + c.add(alpen); + c.add(pc.constrain); +}; + +PaintEditorMorph.prototype.toolButton = function (icon, hint) { + var button, myself = this; + + button = new ToggleButtonMorph( + null, + this, + function () { // action + myself.paper.currentTool = icon; + myself.paper.toolChanged(icon); + myself.refreshToolButtons(); + if (icon === 'pipette') { + myself.getUserColor(); + } + }, + new SymbolMorph(icon, 18), + function () {return myself.paper.currentTool === icon; } + ); + + button.hint = hint; + button.drawNew(); + button.fixLayout(); + return button; +}; + +PaintEditorMorph.prototype.pushButton = function (title, action, hint) { + return new PushButtonMorph( + this, + action, + title, + null, + hint + ); +}; + +PaintEditorMorph.prototype.getUserColor = function () { + var myself = this, + world = this.world(), + hand = world.hand, + posInDocument = getDocumentPositionOf(world.worldCanvas), + mouseMoveBak = hand.processMouseMove, + mouseDownBak = hand.processMouseDown, + mouseUpBak = hand.processMouseUp; + + hand.processMouseMove = function (event) { + var color; + hand.setPosition(new Point( + event.pageX - posInDocument.x, + event.pageY - posInDocument.y + )); + color = world.getGlobalPixelColor(hand.position()); + color.a = 255; + myself.propertiesControls.colorpicker.action(color); + }; + + hand.processMouseDown = nop; + + hand.processMouseUp = function () { + myself.paper.currentTool = 'brush'; + myself.paper.toolChanged('brush'); + myself.refreshToolButtons(); + hand.processMouseMove = mouseMoveBak; + hand.processMouseDown = mouseDownBak; + hand.processMouseUp = mouseUpBak; + }; +}; + +// AdvancedColorPickerMorph ////////////////// + +// A large hsl color picker + +PaintColorPickerMorph.prototype = new Morph(); +PaintColorPickerMorph.prototype.constructor = PaintColorPickerMorph; +PaintColorPickerMorph.uber = Morph.prototype; + +function PaintColorPickerMorph(extent, action) { + this.init(extent, action); +} + +PaintColorPickerMorph.prototype.init = function (extent, action) { + this.setExtent(extent || new Point(200, 100)); + this.action = action || nop; + this.drawNew(); +}; + +PaintColorPickerMorph.prototype.drawNew = function () { + var x = 0, + y = 0, + can = newCanvas(this.extent()), + ctx = can.getContext("2d"), + colorselection, + r; + for (x = 0; x < this.width(); x += 1) { + for (y = 0; y < this.height() - 20; y += 1) { + ctx.fillStyle = "hsl(" + + (360 * x / this.width()) + + "," + + "100%," + + (y * 100 / (this.height() - 20)) + + "%)"; + ctx.fillRect(x, y, 1, 1); + } + } + for (x = 0; x < this.width(); x += 1) { + r = Math.floor(255 * x / this.width()); + ctx.fillStyle = "rgb(" + r + ", " + r + ", " + r + ")"; + ctx.fillRect(x, this.height() - 20, 1, 10); + } + colorselection = ["black", "white", "gray"]; + for (x = 0; x < colorselection.length; x += 1) { + ctx.fillStyle = colorselection[x]; + ctx.fillRect( + x * this.width() / colorselection.length, + this.height() - 10, + this.width() / colorselection.length, + 10 + ); + } + for (x = this.width() * 2 / 3; x < this.width(); x += 2) { + for (y = this.height() - 10; y < this.height(); y += 2) { + if ((x + y) / 2 % 2 === 0) { + ctx.fillStyle = "#DDD"; + ctx.fillRect(x, y, 2, 2); + } + } + } + this.image = can; +}; + +PaintColorPickerMorph.prototype.mouseDownLeft = function (pos) { + if ((pos.subtract(this.position()).x > this.width() * 2 / 3) && + (pos.subtract(this.position()).y > this.height() - 10)) { + this.action("transparent"); + } else { + this.action(this.getPixelColor(pos)); + } +}; + +PaintColorPickerMorph.prototype.mouseMove = + PaintColorPickerMorph.prototype.mouseDownLeft; + +// PaintCanvasMorph /////////////////////////// +/* + A canvas which reacts to drag events to + modify its image, based on a 'tool' property. +*/ + +PaintCanvasMorph.prototype = new Morph(); +PaintCanvasMorph.prototype.constructor = PaintCanvasMorph; +PaintCanvasMorph.uber = Morph.prototype; + +function PaintCanvasMorph(shift) { + this.init(shift); +} + +PaintCanvasMorph.prototype.init = function (shift) { + this.rotationCenter = new Point(240, 180); + this.dragRect = null; + this.previousDragPoint = null; + this.currentTool = "brush"; + this.dragRect = new Rectangle(); + // rectangle with origin being the starting drag position and + // corner being the current drag position + this.mask = newCanvas(this.extent()); // Temporary canvas + this.paper = newCanvas(this.extent()); // Actual canvas + this.erasermask = newCanvas(this.extent()); // eraser memory + this.background = newCanvas(this.extent()); // checkers + this.settings = { + "primarycolor": new Color(0, 0, 0, 255), // usually fill color + "secondarycolor": new Color(0, 0, 0, 255), // (unused) + "linewidth": 3 // stroke width + }; + this.brushBuffer = []; + this.undoBuffer = []; + this.isShiftPressed = shift || function () { + var key = this.world().currentKey; + return (key === 16); + }; + this.buildContents(); +}; + +PaintCanvasMorph.prototype.cacheUndo = function () { + var cachecan = newCanvas(this.extent()); + this.merge(this.paper, cachecan); + this.undoBuffer.push(cachecan); +}; + +PaintCanvasMorph.prototype.undo = function () { + if (this.undoBuffer.length > 0) { + this.paper = newCanvas(this.extent()); + this.mask.width = this.mask.width + 1 - 1; + this.merge(this.undoBuffer.pop(), this.paper); + this.drawNew(); + this.changed(); + } +}; + +PaintCanvasMorph.prototype.merge = function (a, b) { + b.getContext("2d").drawImage(a, 0, 0); +}; + +PaintCanvasMorph.prototype.centermerge = function (a, b) { + b.getContext("2d").drawImage( + a, + (b.width - a.width) / 2, + (b.height - a.height) / 2 + ); +}; + +PaintCanvasMorph.prototype.clearCanvas = function () { + this.buildContents(); + this.drawNew(); + this.changed(); +}; + +PaintCanvasMorph.prototype.toolChanged = function (tool) { + this.mask = newCanvas(this.extent()); + if (tool === "crosshairs") { + this.drawcrosshair(); + } + this.drawNew(); + this.changed(); +}; + +PaintCanvasMorph.prototype.drawcrosshair = function (context) { + var ctx = context || this.mask.getContext("2d"), + rp = this.rotationCenter; + + ctx.lineWidth = 1; + ctx.strokeStyle = 'black'; + ctx.clearRect(0, 0, this.mask.width, this.mask.height); + + // draw crosshairs: + ctx.globalAlpha = 0.5; + + // circle around center: + ctx.fillStyle = 'white'; + ctx.beginPath(); + ctx.arc( + rp.x, + rp.y, + 20, + radians(0), + radians(360), + false + ); + ctx.closePath(); + ctx.fill(); + ctx.stroke(); + + ctx.beginPath(); + ctx.arc( + rp.x, + rp.y, + 10, + radians(0), + radians(360), + false + ); + ctx.stroke(); + + // horizontal line: + ctx.beginPath(); + ctx.moveTo(0, rp.y); + ctx.lineTo(this.mask.width, rp.y); + ctx.stroke(); + + // vertical line: + ctx.beginPath(); + ctx.moveTo(rp.x, 0); + ctx.lineTo(rp.x, this.mask.height); + ctx.stroke(); + + this.drawNew(); + this.changed(); +}; + +PaintCanvasMorph.prototype.floodfill = function (sourcepoint) { + var width = this.paper.width, + height = this.paper.height, + ctx = this.paper.getContext("2d"), + img = ctx.getImageData(0, 0, width, height), + data = img.data, + stack = [Math.round(sourcepoint.y) * width + sourcepoint.x], + currentpoint, + read, + sourcecolor, + checkpoint; + read = function (p) { + var d = p * 4; + return [data[d], data[d + 1], data[d + 2], data[d + 3]]; + }; + sourcecolor = read(stack[0]); + checkpoint = function (p) { + return p[0] === sourcecolor[0] && + p[1] === sourcecolor[1] && + p[2] === sourcecolor[2] && + p[3] === sourcecolor[3]; + }; + while (stack.length > 0) { + currentpoint = stack.pop(); + if (checkpoint(read(currentpoint))) { + if (currentpoint % 480 > 1) { + stack.push(currentpoint + 1); + stack.push(currentpoint - 1); + } + if (currentpoint > 0 && currentpoint < 360 * 480) { + stack.push(currentpoint + width); + stack.push(currentpoint - width); + } + } + if (this.settings.primarycolor === "transparent") { + data[currentpoint * 4 + 3] = 0; + } else { + data[currentpoint * 4] = this.settings.primarycolor.r; + data[currentpoint * 4 + 1] = this.settings.primarycolor.g; + data[currentpoint * 4 + 2] = this.settings.primarycolor.b; + data[currentpoint * 4 + 3] = this.settings.primarycolor.a; + } + } + ctx.putImageData(img, 0, 0); + this.drawNew(); + this.changed(); +}; + +PaintCanvasMorph.prototype.mouseDownLeft = function (pos) { + this.cacheUndo(); + this.dragRect.origin = pos.subtract(this.bounds.origin); + this.dragRect.corner = pos.subtract(this.bounds.origin); + this.previousDragPoint = this.dragRect.corner.copy(); + if (this.currentTool === 'crosshairs') { + this.rotationCenter = pos.subtract(this.bounds.origin); + this.drawcrosshair(); + return; + } + if (this.currentTool === "paintbucket") { + return this.floodfill(pos.subtract(this.bounds.origin)); + } + if (this.settings.primarycolor === "transparent" && + this.currentTool !== "crosshairs") { + this.erasermask = newCanvas(this.extent()); + this.merge(this.paper, this.erasermask); + } +}; + +PaintCanvasMorph.prototype.mouseMove = function (pos) { + if (this.currentTool === "paintbucket") { + return; + } + + var relpos = pos.subtract(this.bounds.origin), + mctx = this.mask.getContext("2d"), + pctx = this.paper.getContext("2d"), + x = this.dragRect.origin.x, // original drag X + y = this.dragRect.origin.y, // original drag y + p = relpos.x, // current drag x + q = relpos.y, // current drag y + w = (p - x) / 2, // half the rect width + h = (q - y) / 2, // half the rect height + i; // iterator number + mctx.save(); + function newW() { + return Math.max(Math.abs(w), Math.abs(h)) * (w / Math.abs(w)); + } + function newH() { + return Math.max(Math.abs(w), Math.abs(h)) * (h / Math.abs(h)); + } + this.brushBuffer.push([p, q]); + mctx.lineWidth = this.settings.linewidth; + mctx.clearRect(0, 0, this.bounds.width(), this.bounds.height()); // mask + + this.dragRect.corner = relpos.subtract(this.dragRect.origin); // reset crn + + if (this.settings.primarycolor === "transparent" && + this.currentTool !== "crosshairs") { + this.merge(this.erasermask, this.mask); + pctx.clearRect(0, 0, this.bounds.width(), this.bounds.height()); + mctx.globalCompositeOperation = "destination-out"; + } else { + mctx.fillStyle = this.settings.primarycolor.toString(); + mctx.strokeStyle = this.settings.primarycolor.toString(); + } + switch (this.currentTool) { + case "rectangle": + if (this.isShiftPressed()) { + mctx.strokeRect(x, y, newW() * 2, newH() * 2); + } else { + mctx.strokeRect(x, y, w * 2, h * 2); + } + break; + case "rectangleSolid": + if (this.isShiftPressed()) { + mctx.fillRect(x, y, newW() * 2, newH() * 2); + } else { + mctx.fillRect(x, y, w * 2, h * 2); + } + break; + case "brush": + mctx.lineCap = "round"; + mctx.lineJoin = "round"; + mctx.beginPath(); + mctx.moveTo(this.brushBuffer[0][0], this.brushBuffer[0][1]); + for (i = 0; i < this.brushBuffer.length; i += 1) { + mctx.lineTo(this.brushBuffer[i][0], this.brushBuffer[i][1]); + } + mctx.stroke(); + break; + case "line": + mctx.beginPath(); + mctx.moveTo(x, y); + if (this.isShiftPressed()) { + if (Math.abs(h) > Math.abs(w)) { + mctx.lineTo(x, q); + } else { + mctx.lineTo(p, y); + } + } else { + mctx.lineTo(p, q); + } + mctx.stroke(); + break; + case "circle": + case "circleSolid": + mctx.beginPath(); + if (this.isShiftPressed()) { + mctx.arc( + x, + y, + new Point(x, y).distanceTo(new Point(p, q)), + 0, + Math.PI * 2, + false + ); + } else { + for (i = 0; i < 480; i += 1) { + mctx.lineTo( + i, + (2 * h) * Math.sqrt(2 - Math.pow( + (i - x) / (2 * w), + 2 + )) + y + ); + } + for (i = 480; i > 0; i -= 1) { + mctx.lineTo( + i, + -1 * (2 * h) * Math.sqrt(2 - Math.pow( + (i - x) / (2 * w), + 2 + )) + y + ); + } + } + mctx.closePath(); + if (this.currentTool === "circleSolid") { + mctx.fill(); + } else { + if (this.currentTool === "circle") { + mctx.stroke(); + } + } + break; + case "crosshairs": + this.rotationCenter = relpos.copy(); + this.drawcrosshair(mctx); + break; + case "eraser": + this.merge(this.paper, this.mask); + mctx.save(); + mctx.globalCompositeOperation = "destination-out"; + mctx.beginPath(); + mctx.moveTo(this.brushBuffer[0][0], this.brushBuffer[0][1]); + for (i = 0; i < this.brushBuffer.length; i += 1) { + mctx.lineTo(this.brushBuffer[i][0], this.brushBuffer[i][1]); + } + mctx.stroke(); + mctx.restore(); + this.paper = newCanvas(this.extent()); + this.merge(this.mask, this.paper); + break; + default: + nop(); + } + this.previousDragPoint = relpos; + this.drawNew(); + this.changed(); + mctx.restore(); +}; + +PaintCanvasMorph.prototype.mouseClickLeft = function () { + if (this.currentTool !== "crosshairs") { + this.merge(this.mask, this.paper); + } + this.brushBuffer = []; +}; + +PaintCanvasMorph.prototype.buildContents = function () { + this.background = newCanvas(this.extent()); + this.paper = newCanvas(this.extent()); + this.mask = newCanvas(this.extent()); + this.erasermask = newCanvas(this.extent()); + var i, j, bkctx = this.background.getContext("2d"); + for (i = 0; i < this.background.width; i += 5) { + for (j = 0; j < this.background.height; j += 5) { + if ((i + j) / 5 % 2 === 1) { + bkctx.fillStyle = "rgba(255, 255, 255, 1)"; + } else { + bkctx.fillStyle = "rgba(255, 255, 255, 0.3)"; + } + bkctx.fillRect(i, j, 5, 5); + } + } +}; + +PaintCanvasMorph.prototype.drawNew = function () { + var can = newCanvas(this.extent()); + this.merge(this.background, can); + this.merge(this.paper, can); + this.merge(this.mask, can); + this.image = can; + this.drawFrame(); +}; + +PaintCanvasMorph.prototype.drawFrame = function () { + var context, borderColor; + + context = this.image.getContext('2d'); + if (this.parent) { + this.color = this.parent.color.lighter(this.contrast * 0.75); + borderColor = this.parent.color; + } else { + borderColor = new Color(120, 120, 120); + } + context.fillStyle = this.color.toString(); + + // cache my border colors + this.cachedClr = borderColor.toString(); + this.cachedClrBright = borderColor.lighter(this.contrast) + .toString(); + this.cachedClrDark = borderColor.darker(this.contrast).toString(); + this.drawRectBorder(context); +}; + +PaintCanvasMorph.prototype.drawRectBorder + = InputFieldMorph.prototype.drawRectBorder; + +PaintCanvasMorph.prototype.edge + = InputFieldMorph.prototype.edge; + +PaintCanvasMorph.prototype.fontSize + = InputFieldMorph.prototype.fontSize; + +PaintCanvasMorph.prototype.typeInPadding + = InputFieldMorph.prototype.typeInPadding; + +PaintCanvasMorph.prototype.contrast + = InputFieldMorph.prototype.contrast; diff --git a/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/readme.txt b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/readme.txt new file mode 100644 index 0000000..0f11b5c --- /dev/null +++ b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/readme.txt @@ -0,0 +1,24 @@ +readme - Snap! 4.0 +------------------ + +this document is just a stub as of now. For the first official release of Snap! 4.0 it will contain release notes covering the following issues: + +saving, loading, exporting, importing + +artefacts: projects, blocks, sprites + +URL options: #run: #open: #lang: #signup + +dynamic content (currently deactivated): + http://snap.berkeley.edu/cloudmsg.txt + http://snap.berkeley.edu/motd.txt + +supported browsers, problems with the Android virtual keyboard + +supported browser / OS settings + +SVG support limitations + +OS/X: turn LCD font smoothing off + +Beware of third-party Chrome toolbars and iFrames diff --git a/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/scriptsPaneTexture.gif b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/scriptsPaneTexture.gif new file mode 100644 index 0000000..846c77f Binary files /dev/null and b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/scriptsPaneTexture.gif differ diff --git a/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/sha512.js b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/sha512.js new file mode 100644 index 0000000..f9cf313 --- /dev/null +++ b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/sha512.js @@ -0,0 +1,347 @@ +/* + + sha512.js + + encryption for SNAP! + This file is derived from crypto-js. + + © 2009–2012 by Jeff Mott. All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + Redistributions of source code must retain the above copyright notice, this list of conditions, and the following disclaimer. + Redistributions in binary form must reproduce the above copyright notice, this list of conditions, and the following disclaimer in the documentation or other materials provided with the distribution. + Neither the name CryptoJS nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS," AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +var hex_sha512 = (function (hex_sha512) { + + var hexcase = 0; + + function hex_sha512(s) + { + return CryptoJS.SHA512(str2rstr_utf8(s)).toString(CryptoJS.enc.Hex); + } + + function rstr_sha512(s) + { + return binb2rstr(binb_sha512(rstr2binb(s), s.length * 8)); + } + + var CryptoJS=CryptoJS||function(a,g){var m={},e=m.lib={},q=e.Base=function(){function a(){}return{extend:function(b){a.prototype=this;var d=new a;b&&d.mixIn(b);d.$super=this;return d},create:function(){var a=this.extend();a.init.apply(a,arguments);return a},init:function(){},mixIn:function(a){for(var k in a)a.hasOwnProperty(k)&&(this[k]=a[k]);a.hasOwnProperty("toString")&&(this.toString=a.toString)},clone:function(){return this.$super.extend(this)}}}(),r=e.WordArray=q.extend({init:function(a,b){a= + this.words=a||[];this.sigBytes=b!=g?b:4*a.length},toString:function(a){return(a||n).stringify(this)},concat:function(a){var b=this.words,d=a.words,c=this.sigBytes,a=a.sigBytes;this.clamp();if(c%4)for(var i=0;i>>2]|=(d[i>>>2]>>>24-8*(i%4)&255)<<24-8*((c+i)%4);else if(65535>>2]=d[i>>>2];else b.push.apply(b,d);this.sigBytes+=a;return this},clamp:function(){var k=this.words,b=this.sigBytes;k[b>>>2]&=4294967295<<32-8*(b%4);k.length=a.ceil(b/4)},clone:function(){var a= + q.clone.call(this);a.words=this.words.slice(0);return a},random:function(k){for(var b=[],d=0;d>>2]>>>24-8*(c%4)&255;d.push((i>>>4).toString(16));d.push((i&15).toString(16))}return d.join("")},parse:function(a){for(var b=a.length,d=[],c=0;c>>3]|=parseInt(a.substr(c,2),16)<<24-4*(c%8);return r.create(d,b/2)}},l=y.Latin1={stringify:function(a){for(var b= + a.words,a=a.sigBytes,d=[],c=0;c>>2]>>>24-8*(c%4)&255));return d.join("")},parse:function(a){for(var b=a.length,d=[],c=0;c>>2]|=(a.charCodeAt(c)&255)<<24-8*(c%4);return r.create(d,b)}},da=y.Utf8={stringify:function(a){try{return decodeURIComponent(escape(l.stringify(a)))}catch(b){throw Error("Malformed UTF-8 data");}},parse:function(a){return l.parse(unescape(encodeURIComponent(a)))}},h=e.BufferedBlockAlgorithm=q.extend({reset:function(){this._data= + r.create();this._nDataBytes=0},_append:function(a){"string"==typeof a&&(a=da.parse(a));this._data.concat(a);this._nDataBytes+=a.sigBytes},_process:function(k){var b=this._data,d=b.words,c=b.sigBytes,i=this.blockSize,l=c/(4*i),l=k?a.ceil(l):a.max((l|0)-this._minBufferSize,0),k=l*i,c=a.min(4*k,c);if(k){for(var h=0;hl;l++)n[l]=a()})();e=e.SHA512=m.extend({_doReset:function(){this._hash=r.create([a(1779033703,4089235720),a(3144134277,2227873595),a(1013904242,4271175723),a(2773480762,1595750129),a(1359893119,2917565137),a(2600822924,725511199),a(528734635,4215389547),a(1541459225,327033209)])},_doProcessBlock:function(a,e){for(var h=this._hash.words,g=h[0],k=h[1],b=h[2],d=h[3],c=h[4],i=h[5],m= + h[6],h=h[7],q=g.high,r=g.low,W=k.high,K=k.low,X=b.high,L=b.low,Y=d.high,M=d.low,Z=c.high,N=c.low,$=i.high,O=i.low,aa=m.high,P=m.low,ba=h.high,Q=h.low,t=q,o=r,E=W,C=K,F=X,D=L,T=Y,G=M,u=Z,p=N,R=$,H=O,S=aa,I=P,U=ba,J=Q,v=0;80>v;v++){var z=n[v];if(16>v)var s=z.high=a[e+2*v]|0,f=z.low=a[e+2*v+1]|0;else{var s=n[v-15],f=s.high,w=s.low,s=(w<<31|f>>>1)^(w<<24|f>>>8)^f>>>7,w=(f<<31|w>>>1)^(f<<24|w>>>8)^(f<<25|w>>>7),B=n[v-2],f=B.high,j=B.low,B=(j<<13|f>>>19)^(f<<3|j>>>29)^f>>>6,j=(f<<13|j>>>19)^(j<<3|f>>>29)^ + (f<<26|j>>>6),f=n[v-7],V=f.high,A=n[v-16],x=A.high,A=A.low,f=w+f.low,s=s+V+(f>>>0>>0?1:0),f=f+j,s=s+B+(f>>>0>>0?1:0),f=f+A,s=s+x+(f>>>0>>0?1:0);z.high=s;z.low=f}var V=u&R^~u&S,A=p&H^~p&I,z=t&E^t&F^E&F,fa=o&C^o&D^C&D,w=(o<<4|t>>>28)^(t<<30|o>>>2)^(t<<25|o>>>7),B=(t<<4|o>>>28)^(o<<30|t>>>2)^(o<<25|t>>>7),j=y[v],ga=j.high,ca=j.low,j=J+((u<<18|p>>>14)^(u<<14|p>>>18)^(p<<23|u>>>9)),x=U+((p<<18|u>>>14)^(p<<14|u>>>18)^(u<<23|p>>>9))+(j>>>0>>0?1:0),j=j+A,x=x+V+(j>>>0>>0?1:0),j=j+ca,x=x+ga+ + (j>>>0>>0?1:0),j=j+f,x=x+s+(j>>>0>>0?1:0),f=B+fa,z=w+z+(f>>>0>>0?1:0),U=S,J=I,S=R,I=H,R=u,H=p,p=G+j|0,u=T+x+(p>>>0>>0?1:0)|0,T=F,G=D,F=E,D=C,E=t,C=o,o=j+f|0,t=x+z+(o>>>0>>0?1:0)|0}r=g.low=r+o|0;g.high=q+t+(r>>>0>>0?1:0)|0;K=k.low=K+C|0;k.high=W+E+(K>>>0>>0?1:0)|0;L=b.low=L+D|0;b.high=X+F+(L>>>0>>0?1:0)|0;M=d.low=M+G|0;d.high=Y+T+(M>>>0>>0?1:0)|0;N=c.low=N+p|0;c.high=Z+u+(N>>>0

>>0?1:0)|0;O=i.low=O+H|0;i.high=$+R+(O>>>0>>0?1:0)|0;P=m.low=P+I|0;m.high=aa+S+(P>>>0>> + 0?1:0)|0;Q=h.low=Q+J|0;h.high=ba+U+(Q>>>0>>0?1:0)|0},_doFinalize:function(){var a=this._data,e=a.words,h=8*this._nDataBytes,g=8*a.sigBytes;e[g>>>5]|=128<<24-g%32;e[(g+128>>>10<<5)+31]=h;a.sigBytes=4*e.length;this._process();this._hash=this._hash.toX32()},blockSize:32});g.SHA512=m._createHelper(e);g.HmacSHA512=m._createHmacHelper(e)})(); + + function rstr2hex(input) + { + try { hexcase } catch(e) { hexcase=0; } + var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef"; + var output = ""; + var x; + for(var i = 0; i < input.length; i++) + { + x = input.charCodeAt(i); + output += hex_tab.charAt((x >>> 4) & 0x0F) + + hex_tab.charAt( x & 0x0F); + } + return output; + } + + function str2rstr_utf8(input) + { + var output = ""; + var i = -1; + var x, y; + + while(++i < input.length) + { + x = input.charCodeAt(i); + y = i + 1 < input.length ? input.charCodeAt(i + 1) : 0; + if(0xD800 <= x && x <= 0xDBFF && 0xDC00 <= y && y <= 0xDFFF) + { + x = 0x10000 + ((x & 0x03FF) << 10) + (y & 0x03FF); + i++; + } + + if(x <= 0x7F) + output += String.fromCharCode(x); + else if(x <= 0x7FF) + output += String.fromCharCode(0xC0 | ((x >>> 6 ) & 0x1F), + 0x80 | ( x & 0x3F)); + else if(x <= 0xFFFF) + output += String.fromCharCode(0xE0 | ((x >>> 12) & 0x0F), + 0x80 | ((x >>> 6 ) & 0x3F), + 0x80 | ( x & 0x3F)); + else if(x <= 0x1FFFFF) + output += String.fromCharCode(0xF0 | ((x >>> 18) & 0x07), + 0x80 | ((x >>> 12) & 0x3F), + 0x80 | ((x >>> 6 ) & 0x3F), + 0x80 | ( x & 0x3F)); + } + return output; + } + + function rstr2binb(input) + { + var output = Array(input.length >> 2); + for(var i = 0; i < output.length; i++) + output[i] = 0; + for(var i = 0; i < input.length * 8; i += 8) + output[i>>5] |= (input.charCodeAt(i / 8) & 0xFF) << (24 - i % 32); + return output; + } + + function binb2rstr(input) + { + var output = ""; + for(var i = 0; i < input.length * 32; i += 8) + output += String.fromCharCode((input[i>>5] >>> (24 - i % 32)) & 0xFF); + return output; + } + + var sha512_k; + function binb_sha512(x, len) + { + if(sha512_k == undefined) + { + sha512_k = new Array( + new int64(0x428a2f98, -685199838), new int64(0x71374491, 0x23ef65cd), + new int64(-1245643825, -330482897), new int64(-373957723, -2121671748), + new int64(0x3956c25b, -213338824), new int64(0x59f111f1, -1241133031), + new int64(-1841331548, -1357295717), new int64(-1424204075, -630357736), + new int64(-670586216, -1560083902), new int64(0x12835b01, 0x45706fbe), + new int64(0x243185be, 0x4ee4b28c), new int64(0x550c7dc3, -704662302), + new int64(0x72be5d74, -226784913), new int64(-2132889090, 0x3b1696b1), + new int64(-1680079193, 0x25c71235), new int64(-1046744716, -815192428), + new int64(-459576895, -1628353838), new int64(-272742522, 0x384f25e3), + new int64(0xfc19dc6, -1953704523), new int64(0x240ca1cc, 0x77ac9c65), + new int64(0x2de92c6f, 0x592b0275), new int64(0x4a7484aa, 0x6ea6e483), + new int64(0x5cb0a9dc, -1119749164), new int64(0x76f988da, -2096016459), + new int64(-1740746414, -295247957), new int64(-1473132947, 0x2db43210), + new int64(-1341970488, -1728372417), new int64(-1084653625, -1091629340), + new int64(-958395405, 0x3da88fc2), new int64(-710438585, -1828018395), + new int64(0x6ca6351, -536640913), new int64(0x14292967, 0xa0e6e70), + new int64(0x27b70a85, 0x46d22ffc), new int64(0x2e1b2138, 0x5c26c926), + new int64(0x4d2c6dfc, 0x5ac42aed), new int64(0x53380d13, -1651133473), + new int64(0x650a7354, -1951439906), new int64(0x766a0abb, 0x3c77b2a8), + new int64(-2117940946, 0x47edaee6), new int64(-1838011259, 0x1482353b), + new int64(-1564481375, 0x4cf10364), new int64(-1474664885, -1136513023), + new int64(-1035236496, -789014639), new int64(-949202525, 0x654be30), + new int64(-778901479, -688958952), new int64(-694614492, 0x5565a910), + new int64(-200395387, 0x5771202a), new int64(0x106aa070, 0x32bbd1b8), + new int64(0x19a4c116, -1194143544), new int64(0x1e376c08, 0x5141ab53), + new int64(0x2748774c, -544281703), new int64(0x34b0bcb5, -509917016), + new int64(0x391c0cb3, -976659869), new int64(0x4ed8aa4a, -482243893), + new int64(0x5b9cca4f, 0x7763e373), new int64(0x682e6ff3, -692930397), + new int64(0x748f82ee, 0x5defb2fc), new int64(0x78a5636f, 0x43172f60), + new int64(-2067236844, -1578062990), new int64(-1933114872, 0x1a6439ec), + new int64(-1866530822, 0x23631e28), new int64(-1538233109, -561857047), + new int64(-1090935817, -1295615723), new int64(-965641998, -479046869), + new int64(-903397682, -366583396), new int64(-779700025, 0x21c0c207), + new int64(-354779690, -840897762), new int64(-176337025, -294727304), + new int64(0x6f067aa, 0x72176fba), new int64(0xa637dc5, -1563912026), + new int64(0x113f9804, -1090974290), new int64(0x1b710b35, 0x131c471b), + new int64(0x28db77f5, 0x23047d84), new int64(0x32caab7b, 0x40c72493), + new int64(0x3c9ebe0a, 0x15c9bebc), new int64(0x431d67c4, -1676669620), + new int64(0x4cc5d4be, -885112138), new int64(0x597f299c, -60457430), + new int64(0x5fcb6fab, 0x3ad6faec), new int64(0x6c44198c, 0x4a475817)); + } + + var H = new Array( + new int64(0x6a09e667, -205731576), + new int64(-1150833019, -2067093701), + new int64(0x3c6ef372, -23791573), + new int64(-1521486534, 0x5f1d36f1), + new int64(0x510e527f, -1377402159), + new int64(-1694144372, 0x2b3e6c1f), + new int64(0x1f83d9ab, -79577749), + new int64(0x5be0cd19, 0x137e2179)); + + var T1 = new int64(0, 0), + T2 = new int64(0, 0), + a = new int64(0,0), + b = new int64(0,0), + c = new int64(0,0), + d = new int64(0,0), + e = new int64(0,0), + f = new int64(0,0), + g = new int64(0,0), + h = new int64(0,0), + + s0 = new int64(0, 0), + s1 = new int64(0, 0), + Ch = new int64(0, 0), + Maj = new int64(0, 0), + r1 = new int64(0, 0), + r2 = new int64(0, 0), + r3 = new int64(0, 0); + var j, i; + var W = new Array(80); + for(i=0; i<80; i++) + W[i] = new int64(0, 0); + + x[len >> 5] |= 0x80 << (24 - (len & 0x1f)); + x[((len + 128 >> 10)<< 5) + 31] = len; + + for(i = 0; i>> shift) | (x.h << (32-shift)); + dst.h = (x.h >>> shift) | (x.l << (32-shift)); + } + + function int64revrrot(dst, x, shift) + { + dst.l = (x.h >>> shift) | (x.l << (32-shift)); + dst.h = (x.l >>> shift) | (x.h << (32-shift)); + } + + function int64shr(dst, x, shift) + { + dst.l = (x.l >>> shift) | (x.h << (32-shift)); + dst.h = (x.h >>> shift); + } + + function int64add(dst, x, y) + { + var w0 = (x.l & 0xffff) + (y.l & 0xffff); + var w1 = (x.l >>> 16) + (y.l >>> 16) + (w0 >>> 16); + var w2 = (x.h & 0xffff) + (y.h & 0xffff) + (w1 >>> 16); + var w3 = (x.h >>> 16) + (y.h >>> 16) + (w2 >>> 16); + dst.l = (w0 & 0xffff) | (w1 << 16); + dst.h = (w2 & 0xffff) | (w3 << 16); + } + + function int64add4(dst, a, b, c, d) + { + var w0 = (a.l & 0xffff) + (b.l & 0xffff) + (c.l & 0xffff) + (d.l & 0xffff); + var w1 = (a.l >>> 16) + (b.l >>> 16) + (c.l >>> 16) + (d.l >>> 16) + (w0 >>> 16); + var w2 = (a.h & 0xffff) + (b.h & 0xffff) + (c.h & 0xffff) + (d.h & 0xffff) + (w1 >>> 16); + var w3 = (a.h >>> 16) + (b.h >>> 16) + (c.h >>> 16) + (d.h >>> 16) + (w2 >>> 16); + dst.l = (w0 & 0xffff) | (w1 << 16); + dst.h = (w2 & 0xffff) | (w3 << 16); + } + + function int64add5(dst, a, b, c, d, e) + { + var w0 = (a.l & 0xffff) + (b.l & 0xffff) + (c.l & 0xffff) + (d.l & 0xffff) + (e.l & 0xffff); + var w1 = (a.l >>> 16) + (b.l >>> 16) + (c.l >>> 16) + (d.l >>> 16) + (e.l >>> 16) + (w0 >>> 16); + var w2 = (a.h & 0xffff) + (b.h & 0xffff) + (c.h & 0xffff) + (d.h & 0xffff) + (e.h & 0xffff) + (w1 >>> 16); + var w3 = (a.h >>> 16) + (b.h >>> 16) + (c.h >>> 16) + (d.h >>> 16) + (e.h >>> 16) + (w2 >>> 16); + dst.l = (w0 & 0xffff) | (w1 << 16); + dst.h = (w2 & 0xffff) | (w3 << 16); + } + + return hex_sha512; + +})({}); diff --git a/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/snap.html b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/snap.html new file mode 100644 index 0000000..66604c5 --- /dev/null +++ b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/snap.html @@ -0,0 +1,36 @@ + + + + + Snap! Build Your Own Blocks. Beta + + + + + + + + + + + + + + + + + + + + + diff --git a/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/snap_logo_sm.png b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/snap_logo_sm.png new file mode 100644 index 0000000..500559b Binary files /dev/null and b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/snap_logo_sm.png differ diff --git a/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/store.js b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/store.js new file mode 100644 index 0000000..41e198a --- /dev/null +++ b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/store.js @@ -0,0 +1,1819 @@ +/* + + store.js + + saving and loading Snap! projects + + written by Jens Mönig + jens@moenig.org + + Copyright (C) 2013 by Jens Mönig + + This file is part of Snap!. + + Snap! is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + + + prerequisites: + -------------- + needs morphic.js, xml.js, and most of Snap!'s other modules + + + hierarchy + --------- + the following tree lists all constructors hierarchically, + indentation indicating inheritance. Refer to this list to get a + contextual overview: + + XML_Serializer + SnapSerializer + + + credits + ------- + Nathan Dinsmore contributed to the design and implemented a first + working version of a complete XMLSerializer. I have taken much of the + overall design and many of the functions and methods in this file from + Nathan's fine original prototype. + +*/ + +/*global modules, XML_Element, XML_Serializer, VariableFrame, StageMorph, +SpriteMorph, WatcherMorph, Point, CustomBlockDefinition, Context, +ReporterBlockMorph, CommandBlockMorph, HatBlockMorph, RingMorph, contains, +detect, CustomCommandBlockMorph, CustomReporterBlockMorph, Color, List, +newCanvas, Costume, Sound, Audio, IDE_Morph, ScriptsMorph, BlockMorph, +ArgMorph, InputSlotMorph, TemplateSlotMorph, CommandSlotMorph, +FunctionSlotMorph, MultiArgMorph, ColorSlotMorph, nop, CommentMorph, isNil, +localize, sizeOf, ArgLabelMorph, SVG_Costume, MorphicPreferences, +SyntaxElementMorph*/ + +// Global stuff //////////////////////////////////////////////////////// + +modules.store = '2013-August-10'; + + +// XML_Serializer /////////////////////////////////////////////////////// +/* + I am an abstract protype for my heirs. + + I manage object identities and keep track of circular data structures. + Objects are "touched" and a property named "serializationID" is added + to each, representing an index integer in the list, starting with 1. +*/ + +// XML_Serializer instance creation: + +function XML_Serializer() { + this.contents = []; + this.media = []; + this.isCollectingMedia = false; +} + +// XML_Serializer preferences settings: + +XML_Serializer.prototype.idProperty = 'serializationID'; +XML_Serializer.prototype.mediaIdProperty = 'serializationMediaID'; +XML_Serializer.prototype.mediaDetectionProperty = 'isMedia'; +XML_Serializer.prototype.version = 1; // increment on structural change + +// XML_Serializer accessing: + +XML_Serializer.prototype.serialize = function (object) { + // public: answer an XML string representing the given object + var xml; + this.flush(); // in case an error occurred in an earlier attempt + this.flushMedia(); + xml = this.store(object); + this.flush(); + return xml; +}; + +XML_Serializer.prototype.store = function (object, mediaID) { + // private - mediaID is optional + if (isNil(object) || !object.toXML) { + // unsupported type, to be checked before calling store() + // when debugging, be sure to throw an error at this point + return ''; + } + if (this.isCollectingMedia && object[this.mediaDetectionProperty]) { + this.addMedia(object, mediaID); + return this.format( + '', + object[this.mediaIdProperty] + ); + } + if (object[this.idProperty]) { + return this.format('', object[this.idProperty]); + } + this.add(object); + return object.toXML(this, mediaID).replace( + '~', + this.format('id="@"', object[this.idProperty]) + ); +}; + +XML_Serializer.prototype.mediaXML = function () { + // answer a project's collected media module as XML + var xml = '', + myself = this; + this.media.forEach(function (object) { + var str = object.toXML(myself).replace( + '~', + myself.format('mediaID="@"', object[myself.mediaIdProperty]) + ); + xml = xml + str; + }); + return xml + ''; +}; + +XML_Serializer.prototype.add = function (object) { + // private - mark the object with a serializationID property and add it + if (object[this.idProperty]) { // already present + return -1; + } + this.contents.push(object); + object[this.idProperty] = this.contents.length; + return this.contents.length; +}; + +XML_Serializer.prototype.addMedia = function (object, mediaID) { + // private - mark the object with a serializationMediaID property + // and add it to media + // if a mediaID is given, take it, otherwise generate one + if (object[this.mediaIdProperty]) { // already present + return -1; + } + this.media.push(object); + if (mediaID) { + object[this.mediaIdProperty] = mediaID + '_' + object.name; + } else { + object[this.mediaIdProperty] = this.media.length; + } + return this.media.length; +}; + +XML_Serializer.prototype.at = function (integer) { + // private + return this.contents[integer - 1]; +}; + +XML_Serializer.prototype.flush = function () { + // private - free all objects and empty my contents + var myself = this; + this.contents.forEach(function (obj) { + delete obj[myself.idProperty]; + }); + this.contents = []; +}; + +XML_Serializer.prototype.flushMedia = function () { + // private - free all media objects and empty my media + var myself = this; + if (this.media instanceof Array) { + this.media.forEach(function (obj) { + delete obj[myself.mediaIdProperty]; + }); + } + this.media = []; +}; + +// XML_Serializer formatting: + +XML_Serializer.prototype.escape = XML_Element.prototype.escape; +XML_Serializer.prototype.unescape = XML_Element.prototype.unescape; + + +XML_Serializer.prototype.format = function (string) { + // private + var myself = this, + i = -1, + values = arguments, + value; + + return string.replace(/[@$%]([\d]+)?/g, function (spec, index) { + index = parseInt(index, 10); + + if (isNaN(index)) { + i += 1; + value = values[i + 1]; + } else { + value = values[index + 1]; + } + // original line of code - now frowned upon by JSLint: + // value = values[(isNaN(index) ? (i += 1) : index) + 1]; + + return spec === '@' ? + myself.escape(value) + : spec === '$' ? + myself.escape(value, true) + : value; + }); +}; + +// XML_Serializer loading: + +XML_Serializer.prototype.load = function (xmlString) { + // public - answer a new object which is represented by the given + // XML string. + nop(xmlString); + throw new Error( + 'loading should be implemented in heir of XML_Serializer' + ); +}; + +XML_Serializer.prototype.parse = function (xmlString) { + // private - answer an XML_Element representing the given XML String + var element = new XML_Element(); + element.parseString(xmlString); + return element; +}; + +// SnapSerializer //////////////////////////////////////////////////////////// + +var SnapSerializer; + +// SnapSerializer inherits from XML_Serializer: + +SnapSerializer.prototype = new XML_Serializer(); +SnapSerializer.prototype.constructor = SnapSerializer; +SnapSerializer.uber = XML_Serializer.prototype; + +// SnapSerializer constants: + +SnapSerializer.prototype.app = 'Snap! 4.0, http://snap.berkeley.edu'; + +SnapSerializer.prototype.thumbnailSize = new Point(160, 120); + +SnapSerializer.prototype.watcherLabels = { + xPosition: 'x position', + yPosition: 'y position', + direction: 'direction', + getScale: 'size', + getLastAnswer: 'answer', + getTimer: 'timer', + getCostumeIdx: 'costume #' +}; + +// SnapSerializer instance creation: + +function SnapSerializer() { + this.init(); +} + +// SnapSerializer initialization: + +SnapSerializer.prototype.init = function () { + this.project = {}; + this.objects = {}; + this.mediaDict = {}; +}; + +// SnapSerializer saving: + +XML_Serializer.prototype.mediaXML = function (name) { + // under construction.... + var xml = '', + myself = this; + this.media.forEach(function (object) { + var str = object.toXML(myself).replace( + '~', + myself.format('mediaID="@"', object[myself.mediaIdProperty]) + ); + xml = xml + str; + }); + return xml + ''; +}; + + +// SnapSerializer loading: + +SnapSerializer.prototype.load = function (xmlString) { + // public - answer a new Project represented by the given XML String + return this.loadProjectModel(this.parse(xmlString)); +}; + +SnapSerializer.prototype.loadProjectModel = function (xmlNode) { + // public - answer a new Project represented by the given XML top node + var myself = this, + project = {sprites: {}}, + model, + nameID; + + this.project = project; + + model = {project: xmlNode }; + if (+xmlNode.attributes.version > this.version) { + throw 'Project uses newer version of Serializer'; + } + + /* Project Info */ + + this.objects = {}; + project.name = model.project.attributes.name; + if (!project.name) { + nameID = 1; + while ( + Object.prototype.hasOwnProperty.call( + localStorage, + '-snap-project-Untitled ' + nameID + ) + ) { + nameID += 1; + } + project.name = 'Untitled ' + nameID; + } + model.notes = model.project.childNamed('notes'); + if (model.notes) { + project.notes = model.notes.contents; + } + model.globalVariables = model.project.childNamed('variables'); + project.globalVariables = new VariableFrame(); + + /* Stage */ + + model.stage = model.project.require('stage'); + StageMorph.prototype.frameRate = 0; + project.stage = new StageMorph(project.globalVariables); + if (Object.prototype.hasOwnProperty.call( + model.stage.attributes, + 'id' + )) { + this.objects[model.stage.attributes.id] = project.stage; + } + if (model.stage.attributes.name) { + project.stage.name = model.stage.attributes.name; + } + if (model.stage.attributes.scheduled === 'true') { + project.stage.fps = 30; + StageMorph.prototype.frameRate = 30; + } + model.pentrails = model.stage.childNamed('pentrails'); + if (model.pentrails) { + project.pentrails = new Image(); + project.pentrails.onload = function () { + var context = project.stage.trailsCanvas.getContext('2d'); + context.drawImage(project.pentrails, 0, 0); + project.stage.changed(); + }; + project.pentrails.src = model.pentrails.contents; + } + project.stage.setTempo(model.stage.attributes.tempo); + project.stage.setExtent(StageMorph.prototype.dimensions); + project.stage.isThreadSafe = + model.stage.attributes.threadsafe === 'true'; + StageMorph.prototype.enableCodeMapping = + model.stage.attributes.codify === 'true'; + + model.hiddenPrimitives = model.project.childNamed('hidden'); + if (model.hiddenPrimitives) { + model.hiddenPrimitives.contents.split(' ').forEach( + function (sel) { + if (sel) { + StageMorph.prototype.hiddenPrimitives[sel] = true; + } + } + ); + } + + model.codeHeaders = model.project.childNamed('headers'); + if (model.codeHeaders) { + model.codeHeaders.children.forEach(function (xml) { + StageMorph.prototype.codeHeaders[xml.tag] = xml.contents; + }); + } + + model.codeMappings = model.project.childNamed('code'); + if (model.codeMappings) { + model.codeMappings.children.forEach(function (xml) { + StageMorph.prototype.codeMappings[xml.tag] = xml.contents; + }); + } + + model.globalBlocks = model.project.childNamed('blocks'); + if (model.globalBlocks) { + this.loadCustomBlocks(project.stage, model.globalBlocks, true); + this.populateCustomBlocks( + project.stage, + model.globalBlocks, + true + ); + } + this.loadObject(project.stage, model.stage); + + /* Sprites */ + + model.sprites = model.stage.require('sprites'); + project.sprites[project.stage.name] = project.stage; + + model.sprites.childrenNamed('sprite').forEach(function (model) { + myself.loadValue(model); + }); + + // restore nesting associations + myself.project.stage.children.forEach(function (sprite) { + var anchor; + if (sprite.nestingInfo) { // only sprites may have nesting info + anchor = myself.project.sprites[sprite.nestingInfo.anchor]; + if (anchor) { + anchor.attachPart(sprite); + } + sprite.rotatesWithAnchor = (sprite.nestingInfo.synch === 'true'); + } + }); + myself.project.stage.children.forEach(function (sprite) { + if (sprite.nestingInfo) { // only sprites may have nesting info + sprite.nestingScale = +(sprite.nestingInfo.scale || sprite.scale); + delete sprite.nestingInfo; + } + }); + + this.objects = {}; + + /* Global Variables */ + + if (model.globalVariables) { + this.loadVariables( + project.globalVariables, + model.globalVariables + ); + } + + /* Watchers */ + + model.sprites.childrenNamed('watcher').forEach(function (model) { + var watcher, color, target, hidden, extX, extY; + + color = myself.loadColor(model.attributes.color); + target = Object.prototype.hasOwnProperty.call( + model.attributes, + 'scope' + ) ? project.sprites[model.attributes.scope] : null; + + // determine whether the watcher is hidden, slightly + // complicated to retain backward compatibility + // with former tag format: hidden="hidden" + // now it's: hidden="true" + hidden = Object.prototype.hasOwnProperty.call( + model.attributes, + 'hidden' + ) && (model.attributes.hidden !== 'false'); + + if (Object.prototype.hasOwnProperty.call( + model.attributes, + 'var' + )) { + watcher = new WatcherMorph( + model.attributes['var'], + color, + isNil(target) ? project.globalVariables + : target.variables, + model.attributes['var'], + hidden + ); + } else { + watcher = new WatcherMorph( + localize(myself.watcherLabels[model.attributes.s]), + color, + target, + model.attributes.s, + hidden + ); + } + watcher.setStyle(model.attributes.style || 'normal'); + if (watcher.style === 'slider') { + watcher.setSliderMin(model.attributes.min || '1'); + watcher.setSliderMax(model.attributes.max || '100'); + } + watcher.setPosition( + project.stage.topLeft().add(new Point( + +model.attributes.x || 0, + +model.attributes.y || 0 + )) + ); + project.stage.add(watcher); + watcher.update(); + + // set watcher's contentsMorph's extent if it is showing a list and + // its monitor dimensions are given + if (watcher.currentValue instanceof List) { + extX = model.attributes.extX; + if (extX) { + watcher.cellMorph.contentsMorph.setWidth(+extX); + } + extY = model.attributes.extY; + if (extY) { + watcher.cellMorph.contentsMorph.setHeight(+extY); + } + // adjust my contentsMorph's handle position + watcher.cellMorph.contentsMorph.handle.drawNew(); + } + }); + this.objects = {}; + return project; +}; + +SnapSerializer.prototype.loadBlocks = function (xmlString, targetStage) { + // public - answer a new Array of custom block definitions + // represented by the given XML String + var stage = new StageMorph(), + model; + + this.project = { + stage: stage, + sprites: {}, + targetStage: targetStage // for secondary custom block def look-up + }; + model = this.parse(xmlString); + if (+model.attributes.version > this.version) { + throw 'Module uses newer version of Serializer'; + } + this.loadCustomBlocks(stage, model, true); + this.populateCustomBlocks( + stage, + model, + true + ); + this.objects = {}; + stage.globalBlocks.forEach(function (def) { + def.receiver = null; + }); + this.objects = {}; + this.project = {}; + this.mediaDict = {}; + return stage.globalBlocks; +}; + +SnapSerializer.prototype.loadSprites = function (xmlString, ide) { + // public - import a set of sprites represented by xmlString + // into the current project of the ide + var model, project, myself = this; + + project = this.project = { + globalVariables: ide.globalVariables, + stage: ide.stage, + sprites: {} + }; + project.sprites[project.stage.name] = project.stage; + + model = this.parse(xmlString); + if (+model.attributes.version > this.version) { + throw 'Module uses newer version of Serializer'; + } + model.childrenNamed('sprite').forEach(function (model) { + var sprite = new SpriteMorph(project.globalVariables); + + if (model.attributes.id) { + myself.objects[model.attributes.id] = sprite; + } + if (model.attributes.name) { + sprite.name = model.attributes.name; + project.sprites[model.attributes.name] = sprite; + } + if (model.attributes.color) { + sprite.color = myself.loadColor(model.attributes.color); + } + if (model.attributes.pen) { + sprite.penPoint = model.attributes.pen; + } + project.stage.add(sprite); + ide.sprites.add(sprite); + sprite.scale = parseFloat(model.attributes.scale || '1'); + sprite.rotationStyle = parseFloat( + model.attributes.rotation || '1' + ); + sprite.isDraggable = model.attributes.draggable !== 'false'; + sprite.isVisible = model.attributes.hidden !== 'true'; + sprite.heading = parseFloat(model.attributes.heading) || 0; + sprite.drawNew(); + sprite.gotoXY(+model.attributes.x || 0, +model.attributes.y || 0); + myself.loadObject(sprite, model); + }); + this.objects = {}; + this.project = {}; + this.mediaDict = {}; + +// ide.stage.drawNew(); + ide.createCorral(); + ide.fixLayout(); +}; + +SnapSerializer.prototype.loadMedia = function (xmlString) { + // public - load the media represented by xmlString into memory + // to be referenced by a media-less project later + return this.loadMediaModel(this.parse(xmlString)); +}; + +SnapSerializer.prototype.loadMediaModel = function (xmlNode) { + // public - load the media represented by xmlNode into memory + // to be referenced by a media-less project later + var myself = this, + model = xmlNode; + this.mediaDict = {}; + if (+model.attributes.version > this.version) { + throw 'Module uses newer version of Serializer'; + } + model.children.forEach(function (model) { + myself.loadValue(model); + }); + return this.mediaDict; +}; + +SnapSerializer.prototype.loadObject = function (object, model) { + // private + var blocks = model.require('blocks'); + this.loadNestingInfo(object, model); + this.loadCostumes(object, model); + this.loadSounds(object, model); + this.loadCustomBlocks(object, blocks); + this.populateCustomBlocks(object, blocks); + this.loadVariables(object.variables, model.require('variables')); + this.loadScripts(object.scripts, model.require('scripts')); +}; + +SnapSerializer.prototype.loadNestingInfo = function (object, model) { + // private + var info = model.childNamed('nest'); + if (info) { + object.nestingInfo = info.attributes; + } +}; + +SnapSerializer.prototype.loadCostumes = function (object, model) { + // private + var costumes = model.childNamed('costumes'), + costume; + if (costumes) { + object.costumes = this.loadValue(costumes.require('list')); + } + if (Object.prototype.hasOwnProperty.call( + model.attributes, + 'costume' + )) { + costume = object.costumes.asArray()[model.attributes.costume - 1]; + if (costume) { + if (costume.loaded) { + object.wearCostume(costume); + } else { + costume.loaded = function () { + object.wearCostume(costume); + }; + } + } + } +}; + +SnapSerializer.prototype.loadSounds = function (object, model) { + // private + var sounds = model.childNamed('sounds'); + if (sounds) { + object.sounds = this.loadValue(sounds.require('list')); + } +}; + +SnapSerializer.prototype.loadVariables = function (varFrame, element) { + // private + var myself = this; + + element.children.forEach(function (child) { + var value; + if (child.tag !== 'variable') { + return; + } + value = child.children[0]; + varFrame.vars[child.attributes.name] = value ? + myself.loadValue(value) : 0; + }); +}; + +SnapSerializer.prototype.loadCustomBlocks = function ( + object, + element, + isGlobal +) { + // private + var myself = this; + element.children.forEach(function (child) { + var definition, names, inputs, header, code, comment, i; + if (child.tag !== 'block-definition') { + return; + } + definition = new CustomBlockDefinition( + child.attributes.s || '', + object + ); + definition.category = child.attributes.category || 'other'; + definition.type = child.attributes.type || 'command'; + definition.isGlobal = (isGlobal === true); + if (definition.isGlobal) { + object.globalBlocks.push(definition); + } else { + object.customBlocks.push(definition); + } + + names = definition.parseSpec(definition.spec).filter( + function (str) { + return str.charAt(0) === '%'; + } + ).map(function (str) { + return str.substr(1); + }); + + definition.names = names; + inputs = child.childNamed('inputs'); + if (inputs) { + i = -1; + inputs.children.forEach(function (child) { + if (child.tag !== 'input') { + return; + } + i += 1; + definition.declarations[names[i]] + = [child.attributes.type, child.contents]; + }); + } + + header = child.childNamed('header'); + if (header) { + definition.codeHeader = header.contents; + } + + code = child.childNamed('code'); + if (code) { + definition.codeMapping = code.contents; + } + + comment = child.childNamed('comment'); + if (comment) { + definition.comment = myself.loadComment(comment); + } + }); +}; + +SnapSerializer.prototype.populateCustomBlocks = function ( + object, + element, + isGlobal +) { + // private + var myself = this; + element.children.forEach(function (child, index) { + var definition, script, scripts; + if (child.tag !== 'block-definition') { + return; + } + definition = isGlobal ? object.globalBlocks[index] + : object.customBlocks[index]; + script = child.childNamed('script'); + if (script) { + definition.body = new Context( + null, + script ? myself.loadScript(script) : null, + null, + object + ); + definition.body.inputs = definition.names.slice(0); + } + scripts = child.childNamed('scripts'); + if (scripts) { + definition.scripts = myself.loadScriptsArray(scripts); + } + + delete definition.names; + }); +}; + +SnapSerializer.prototype.loadScripts = function (scripts, model) { + // private + var myself = this, + scale = SyntaxElementMorph.prototype.scale; + scripts.texture = 'scriptsPaneTexture.gif'; + model.children.forEach(function (child) { + var element; + if (child.tag === 'script') { + element = myself.loadScript(child); + if (!element) { + return; + } + element.setPosition(new Point( + (+child.attributes.x || 0) * scale, + (+child.attributes.y || 0) * scale + ).add(scripts.topLeft())); + scripts.add(element); + element.fixBlockColor(null, true); // force zebra coloring + element.allComments().forEach(function (comment) { + comment.align(element); + }); + } else if (child.tag === 'comment') { + element = myself.loadComment(child); + if (!element) { + return; + } + element.setPosition(new Point( + (+child.attributes.x || 0) * scale, + (+child.attributes.y || 0) * scale + ).add(scripts.topLeft())); + scripts.add(element); + } + }); +}; + +SnapSerializer.prototype.loadScriptsArray = function (model) { + // private - answer an array containting the model's scripts + var myself = this, + scale = SyntaxElementMorph.prototype.scale, + scripts = []; + model.children.forEach(function (child) { + var element; + if (child.tag === 'script') { + element = myself.loadScript(child); + if (!element) { + return; + } + element.setPosition(new Point( + (+child.attributes.x || 0) * scale, + (+child.attributes.y || 0) * scale + )); + scripts.push(element); + element.fixBlockColor(null, true); // force zebra coloring + } else if (child.tag === 'comment') { + element = myself.loadComment(child); + if (!element) { + return; + } + element.setPosition(new Point( + (+child.attributes.x || 0) * scale, + (+child.attributes.y || 0) * scale + )); + scripts.push(element); + } + }); + return scripts; +}; + +SnapSerializer.prototype.loadScript = function (model) { + // private + var topBlock, block, nextBlock, + myself = this; + model.children.forEach(function (child) { + nextBlock = myself.loadBlock(child); + if (!nextBlock) { + return; + } + if (block) { + block.nextBlock(nextBlock); + } else { + topBlock = nextBlock; + } + block = nextBlock; + }); + return topBlock; +}; + +SnapSerializer.prototype.loadComment = function (model) { + // private + var comment = new CommentMorph(model.contents), + scale = SyntaxElementMorph.prototype.scale; + comment.isCollapsed = (model.attributes.collapsed === 'true'); + comment.setTextWidth(+model.attributes.w * scale); + return comment; +}; + +SnapSerializer.prototype.loadBlock = function (model, isReporter) { + // private + var block, info, inputs, isGlobal, rm, receiver; + if (model.tag === 'block') { + if (Object.prototype.hasOwnProperty.call( + model.attributes, + 'var' + )) { + return SpriteMorph.prototype.variableBlock( + model.attributes['var'] + ); + } + block = SpriteMorph.prototype.blockForSelector(model.attributes.s); + } else if (model.tag === 'custom-block') { + isGlobal = model.attributes.scope ? false : true; + receiver = isGlobal ? this.project.stage + : this.project.sprites[model.attributes.scope]; + rm = model.childNamed('receiver'); + if (rm && rm.children[0]) { + receiver = this.loadValue( + model.childNamed('receiver').children[0] + ); + } + if (!receiver) { + return this.obsoleteBlock(isReporter); + } + if (isGlobal) { + info = detect(receiver.globalBlocks, function (block) { + return block.blockSpec() === model.attributes.s; + }); + if (!info && this.project.targetStage) { // importing block files + info = detect( + this.project.targetStage.globalBlocks, + function (block) { + return block.blockSpec() === model.attributes.s; + } + ); + } + } else { + info = detect(receiver.customBlocks, function (block) { + return block.blockSpec() === model.attributes.s; + }); + } + if (!info) { + return this.obsoleteBlock(isReporter); + } + block = info.type === 'command' ? new CustomCommandBlockMorph( + info, + false + ) : new CustomReporterBlockMorph( + info, + info.type === 'predicate', + false + ); + } + if (block === null) { + block = this.obsoleteBlock(isReporter); + } + block.isDraggable = true; + inputs = block.inputs(); + model.children.forEach(function (child, i) { + if (child.tag === 'comment') { + block.comment = this.loadComment(child); + block.comment.block = block; + } else if (child.tag === 'receiver') { + nop(); // ignore + } else { + this.loadInput(child, inputs[i], block); + } + }, this); + return block; +}; + +SnapSerializer.prototype.obsoleteBlock = function (isReporter) { + // private + var block = isReporter ? new ReporterBlockMorph() + : new CommandBlockMorph(); + block.selector = 'nop'; + block.color = new Color(200, 0, 20); + block.setSpec('Obsolete!'); + block.isDraggable = true; + return block; +}; + +SnapSerializer.prototype.loadInput = function (model, input, block) { + // private + var inp, val, myself = this; + if (model.tag === 'script') { + inp = this.loadScript(model); + if (inp) { + input.add(inp); + input.fixLayout(); + } + } else if (model.tag === 'autolambda' && model.children[0]) { + inp = this.loadBlock(model.children[0], true); + if (inp) { + input.silentReplaceInput(input.children[0], inp); + input.fixLayout(); + } + } else if (model.tag === 'list') { + while (input.inputs().length > 0) { + input.removeInput(); + } + model.children.forEach(function (item) { + input.addInput(); + myself.loadInput( + item, + input.children[input.children.length - 2], + input + ); + }); + input.fixLayout(); + } else if (model.tag === 'block' || model.tag === 'custom-block') { + block.silentReplaceInput(input, this.loadBlock(model, true)); + } else if (model.tag === 'color') { + input.setColor(this.loadColor(model.contents)); + } else { + val = this.loadValue(model); + if (val) { + input.setContents(this.loadValue(model)); + } + } +}; + +SnapSerializer.prototype.loadValue = function (model) { + // private + var v, items, el, center, image, name, audio, option, + myself = this; + + function record() { + if (Object.prototype.hasOwnProperty.call( + model.attributes, + 'id' + )) { + myself.objects[model.attributes.id] = v; + } + if (Object.prototype.hasOwnProperty.call( + model.attributes, + 'mediaID' + )) { + myself.mediaDict[model.attributes.mediaID] = v; + } + } + switch (model.tag) { + case 'ref': + if (Object.prototype.hasOwnProperty.call(model.attributes, 'id')) { + return this.objects[model.attributes.id]; + } + if (Object.prototype.hasOwnProperty.call( + model.attributes, + 'mediaID' + )) { + return this.mediaDict[model.attributes.mediaID]; + } + throw new Error('expecting a reference id'); + case 'l': + option = model.childNamed('option'); + return option ? [option.contents] : model.contents; + case 'bool': + return model.contents === 'true'; + case 'list': + if (model.attributes.hasOwnProperty('linked')) { + items = model.childrenNamed('item'); + if (items.length === 0) { + v = new List(); + record(); + return v; + } + items.forEach(function (item) { + var value = item.children[0]; + if (v === undefined) { + v = new List(); + record(); + } else { + v = v.rest = new List(); + } + v.isLinked = true; + if (!value) { + v.first = 0; + } else { + v.first = myself.loadValue(value); + } + }); + return v; + } + v = new List(); + record(); + v.contents = model.childrenNamed('item').map(function (item) { + var value = item.children[0]; + if (!value) { + return 0; + } + return myself.loadValue(value); + }); + return v; + case 'sprite': + v = new SpriteMorph(myself.project.globalVariables); + if (model.attributes.id) { + myself.objects[model.attributes.id] = v; + } + if (model.attributes.name) { + v.name = model.attributes.name; + myself.project.sprites[model.attributes.name] = v; + } + if (model.attributes.idx) { + v.idx = +model.attributes.idx; + } + if (model.attributes.color) { + v.color = myself.loadColor(model.attributes.color); + } + if (model.attributes.pen) { + v.penPoint = model.attributes.pen; + } + myself.project.stage.add(v); + v.scale = parseFloat(model.attributes.scale || '1'); + v.rotationStyle = parseFloat( + model.attributes.rotation || '1' + ); + v.isDraggable = model.attributes.draggable !== 'false'; + v.isVisible = model.attributes.hidden !== 'true'; + v.heading = parseFloat(model.attributes.heading) || 0; + v.drawNew(); + v.gotoXY(+model.attributes.x || 0, +model.attributes.y || 0); + myself.loadObject(v, model); + return v; + case 'context': + v = new Context(null); + record(); + el = model.childNamed('script'); + if (el) { + v.expression = this.loadScript(el); + } else { + el = model.childNamed('block') || + model.childNamed('custom-block'); + if (el) { + v.expression = this.loadBlock(el); + } else { + el = model.childNamed('l'); + if (el) { + v.expression = new InputSlotMorph(el.contents); + } + } + } + el = model.childNamed('receiver'); + if (el) { + el = el.childNamed('ref') || el.childNamed('sprite'); + if (el) { + v.receiver = this.loadValue(el); + } + } + el = model.childNamed('inputs'); + if (el) { + el.children.forEach(function (item) { + if (item.tag === 'input') { + v.inputs.push(item.contents); + } + }); + } + el = model.childNamed('variables'); + if (el) { + this.loadVariables(v.variables, el); + } + el = model.childNamed('context'); + if (el) { + v.outerContext = this.loadValue(el); + } + return v; + case 'costume': + center = new Point(); + if (Object.prototype.hasOwnProperty.call( + model.attributes, + 'center-x' + )) { + center.x = parseFloat(model.attributes['center-x']); + } + if (Object.prototype.hasOwnProperty.call( + model.attributes, + 'center-y' + )) { + center.y = parseFloat(model.attributes['center-y']); + } + if (Object.prototype.hasOwnProperty.call( + model.attributes, + 'name' + )) { + name = model.attributes.name; + } + if (Object.prototype.hasOwnProperty.call( + model.attributes, + 'image' + )) { + image = new Image(); + if (model.attributes.image.indexOf('data:image/svg+xml') === 0 + && !MorphicPreferences.rasterizeSVGs) { + v = new SVG_Costume(null, name, center); + image.onload = function () { + v.contents = image; + v.version = +new Date(); + if (typeof v.loaded === 'function') { + v.loaded(); + } else { + v.loaded = true; + } + }; + } else { + v = new Costume(null, name, center); + image.onload = function () { + var canvas = newCanvas( + new Point(image.width, image.height) + ), + context = canvas.getContext('2d'); + context.drawImage(image, 0, 0); + v.contents = canvas; + v.version = +new Date(); + if (typeof v.loaded === 'function') { + v.loaded(); + } else { + v.loaded = true; + } + }; + } + image.src = model.attributes.image; + } + record(); + return v; + case 'sound': + audio = new Audio(); + audio.src = model.attributes.sound; + v = new Sound(audio, model.attributes.name); + if (Object.prototype.hasOwnProperty.call( + model.attributes, + 'mediaID' + )) { + myself.mediaDict[model.attributes.mediaID] = v; + } + return v; + } + return undefined; +}; + +SnapSerializer.prototype.loadColor = function (colorString) { + // private + var c = (colorString || '').split(','); + return new Color( + parseFloat(c[0]), + parseFloat(c[1]), + parseFloat(c[2]), + parseFloat(c[3]) + ); +}; + +SnapSerializer.prototype.openProject = function (project, ide) { + var stage = ide.stage, + sprites = [], + sprite; + if (!project || !project.stage) { + return; + } + ide.projectName = project.name; + ide.projectNotes = project.notes || ''; + if (ide.globalVariables) { + ide.globalVariables = project.globalVariables; + } + if (stage) { + stage.destroy(); + } + ide.add(project.stage); + ide.stage = project.stage; + sprites = ide.stage.children.filter(function (child) { + return child instanceof SpriteMorph; + }); + sprites.sort(function (x, y) { + return x.idx - y.idx; + }); + + ide.sprites = new List(sprites); + sprite = sprites[0] || project.stage; + + if (sizeOf(this.mediaDict) > 0) { + ide.hasChangedMedia = false; + this.mediaDict = {}; + } else { + ide.hasChangedMedia = true; + } + project.stage.drawNew(); + ide.createCorral(); + ide.selectSprite(sprite); + ide.fixLayout(); + ide.world().keyboardReceiver = project.stage; +}; + +// SnapSerializer XML-representation of objects: + +// Generics + +Array.prototype.toXML = function (serializer) { + return this.reduce(function (xml, item) { + return xml + serializer.store(item); + }, ''); +}; + +// Sprites + +StageMorph.prototype.toXML = function (serializer) { + var thumbnail = this.thumbnail(SnapSerializer.prototype.thumbnailSize), + thumbdata, + ide = this.parentThatIsA(IDE_Morph); + + // catch cross-origin tainting exception when using SVG costumes + try { + thumbdata = thumbnail.toDataURL('image/png'); + } catch (error) { + thumbdata = null; + } + + function code(key) { + var str = ''; + Object.keys(StageMorph.prototype[key]).forEach( + function (selector) { + str += ( + '<' + selector + '>' + + XML_Element.prototype.escape( + StageMorph.prototype[key][selector] + ) + + '' + ); + } + ); + return str; + } + + this.removeAllClones(); + return serializer.format( + '' + + '$' + + '$' + + '' + + '$' + + '%' + + '%' + + '%' + + '%' + + '%%' + + '' + + '$' + + '%' + + '%' + + '%' + + '%' + + '', + (ide && ide.projectName) ? ide.projectName : 'Untitled', + serializer.app, + serializer.version, + (ide && ide.projectNotes) ? ide.projectNotes : '', + thumbdata, + this.name, + this.getCostumeIdx(), + this.getTempo(), + this.isThreadSafe, + this.enableCodeMapping, + StageMorph.prototype.frameRate !== 0, + this.trailsCanvas.toDataURL('image/png'), + serializer.store(this.costumes, this.name + '_cst'), + serializer.store(this.sounds, this.name + '_snd'), + serializer.store(this.variables), + serializer.store(this.customBlocks), + serializer.store(this.scripts), + serializer.store(this.children), + Object.keys(StageMorph.prototype.hiddenPrimitives).reduce( + function (a, b) {return a + ' ' + b; }, + '' + ), + code('codeHeaders'), + code('codeMappings'), + serializer.store(this.globalBlocks), + (ide && ide.globalVariables) ? + serializer.store(ide.globalVariables) : '' + ); +}; + +SpriteMorph.prototype.toXML = function (serializer) { + var stage = this.parentThatIsA(StageMorph), + ide = stage ? stage.parentThatIsA(IDE_Morph) : null, + idx = ide ? ide.sprites.asArray().indexOf(this) + 1 : 0; + return serializer.format( + '' + + '%' + // nesting info + '%' + + '%' + + '%' + + '%' + + '%' + + '', + this.name, + idx, + this.xPosition(), + this.yPosition(), + this.heading, + this.scale, + this.rotationStyle, + this.isDraggable, + this.isVisible ? '' : ' hidden="true"', + this.getCostumeIdx(), + this.color.r, + this.color.g, + this.color.b, + this.penPoint, + + // nesting info + this.anchor + ? '' + : '', + + serializer.store(this.costumes, this.name + '_cst'), + serializer.store(this.sounds, this.name + '_snd'), + serializer.store(this.variables), + !this.customBlocks ? + '' : serializer.store(this.customBlocks), + serializer.store(this.scripts) + ); +}; + +Costume.prototype[XML_Serializer.prototype.mediaDetectionProperty] = true; + +Costume.prototype.toXML = function (serializer) { + return serializer.format( + '', + this.name, + this.rotationCenter.x, + this.rotationCenter.y, + this instanceof SVG_Costume ? + this.contents.src : this.contents.toDataURL('image/png') + ); +}; + +Sound.prototype[XML_Serializer.prototype.mediaDetectionProperty] = true; + +Sound.prototype.toXML = function (serializer) { + return serializer.format( + '', + this.name, + this.toDataURL() + ); +}; + +VariableFrame.prototype.toXML = function (serializer) { + var myself = this; + return Object.keys(this.vars).reduce(function (vars, v) { + var val = myself.vars[v], + dta; + if (val === undefined || val === null) { + dta = serializer.format('', v); + } else { + dta = serializer.format( + '%', + v, + typeof val === 'object' ? serializer.store(val) + : typeof val === 'boolean' ? + serializer.format('$', val) + : serializer.format('$', val) + ); + } + return vars + dta; + }, ''); +}; + + + +// Watchers + +WatcherMorph.prototype.toXML = function (serializer) { + var isVar = this.target instanceof VariableFrame, + isList = this.currentValue instanceof List, + color = this.readoutColor, + position = this.parent ? + this.topLeft().subtract(this.parent.topLeft()) + : this.topLeft(); + + return serializer.format( + '', + (isVar && this.target.owner) || (!isVar && this.target) ? + serializer.format(' scope="@"', + isVar ? this.target.owner.name : this.target.name) + : '', + serializer.format(isVar ? 'var="@"' : 's="@"', this.getter), + this.style, + isVar && this.style === 'slider' ? serializer.format( + ' min="@" max="@"', + this.sliderMorph.start, + this.sliderMorph.stop + ) : '', + position.x, + position.y, + color.r, + color.g, + color.b, + !isList ? '' + : serializer.format( + ' extX="@" extY="@"', + this.cellMorph.contentsMorph.width(), + this.cellMorph.contentsMorph.height() + ), + this.isVisible ? '' : ' hidden="true"' + ); +}; + +// Scripts + +ScriptsMorph.prototype.toXML = function (serializer) { + return this.children.reduce(function (xml, child) { + if (child instanceof BlockMorph) { + return xml + child.toScriptXML(serializer, true); + } + if (child instanceof CommentMorph && !child.block) { // unattached + return xml + child.toXML(serializer); + } + return xml; + }, ''); +}; + +BlockMorph.prototype.toXML = BlockMorph.prototype.toScriptXML = function ( + serializer, + savePosition +) { + var position, + xml, + scale = SyntaxElementMorph.prototype.scale, + block = this; + + // determine my position + if (this.parent) { + position = this.topLeft().subtract(this.parent.topLeft()); + } else { + position = this.topLeft(); + } + + // save my position to xml + if (savePosition) { + xml = serializer.format( + ''; + return xml; +}; + +BlockMorph.prototype.toBlockXML = function (serializer) { + return serializer.format( + '%%', + this.selector, + serializer.store(this.inputs()), + this.comment ? this.comment.toXML(serializer) : '' + ); +}; + +ReporterBlockMorph.prototype.toXML = function (serializer) { + return this.selector === 'reportGetVar' ? serializer.format( + '', + this.blockSpec + ) : this.toBlockXML(serializer); +}; + +ReporterBlockMorph.prototype.toScriptXML = function ( + serializer, + savePosition +) { + var position, + scale = SyntaxElementMorph.prototype.scale; + + // determine my save-position + if (this.parent) { + position = this.topLeft().subtract(this.parent.topLeft()); + } else { + position = this.topLeft(); + } + + if (savePosition) { + return serializer.format( + '', + position.x / scale, + position.y / scale, + this.toXML(serializer) + ); + } + return serializer.format('', this.toXML(serializer)); +}; + +CustomCommandBlockMorph.prototype.toBlockXML = function (serializer) { + var scope = this.definition.isGlobal ? undefined + : this.definition.receiver.name; + return serializer.format( + '%%%', + this.blockSpec, + this.definition.isGlobal ? + '' : serializer.format(' scope="@"', scope), + serializer.store(this.inputs()), + this.comment ? this.comment.toXML(serializer) : '', + scope && !this.definition.receiver[serializer.idProperty] ? + '' + + serializer.store(this.definition.receiver) + + '' + : '' + ); +}; + +CustomReporterBlockMorph.prototype.toBlockXML + = CustomCommandBlockMorph.prototype.toBlockXML; + +CustomBlockDefinition.prototype.toXML = function (serializer) { + var myself = this; + + function encodeScripts(array) { + return array.reduce(function (xml, element) { + if (element instanceof BlockMorph) { + return xml + element.toScriptXML(serializer, true); + } + if (element instanceof CommentMorph && !element.block) { + return xml + element.toXML(serializer); + } + return xml; + }, ''); + } + + return serializer.format( + '' + + '%' + + '

@
' + + '@' + + '%%%' + + '', + this.spec, + this.type, + this.category || 'other', + this.comment ? this.comment.toXML(serializer) : '', + this.codeHeader || '', + this.codeMapping || '', + Object.keys(this.declarations).reduce(function (xml, decl) { + return xml + serializer.format( + '$', + myself.declarations[decl][0], + myself.declarations[decl][1] + ); + }, ''), + this.body ? serializer.store(this.body.expression) : '', + this.scripts.length > 0 ? + '' + encodeScripts(this.scripts) + '' + : '' + ); +}; + +// Scripts - Inputs + +ArgMorph.prototype.toXML = function () { + return ''; // empty by default +}; + +InputSlotMorph.prototype.toXML = function (serializer) { + if (this.constant) { + return serializer.format( + '', + this.constant + ); + } + return serializer.format('$', this.contents().text); +}; + +TemplateSlotMorph.prototype.toXML = function (serializer) { + return serializer.format('$', this.contents()); +}; + +CommandSlotMorph.prototype.toXML = function (serializer) { + var block = this.children[0]; + if (block instanceof BlockMorph) { + if (block instanceof ReporterBlockMorph) { + return serializer.format( + '%', + serializer.store(block) + ); + } + return serializer.store(block); + } + return ''; +}; + +FunctionSlotMorph.prototype.toXML = CommandSlotMorph.prototype.toXML; + +MultiArgMorph.prototype.toXML = function (serializer) { + return serializer.format( + '%', + serializer.store(this.inputs()) + ); +}; + +ArgLabelMorph.prototype.toXML = function (serializer) { + return serializer.format( + '%', + serializer.store(this.inputs()[0]) + ); +}; + +ColorSlotMorph.prototype.toXML = function (serializer) { + return serializer.format( + '$,$,$,$', + this.color.r, + this.color.g, + this.color.b, + this.color.a + ); +}; + +// Values + +List.prototype.toXML = function (serializer, mediaContext) { + // mediaContext is an optional name-stub + // when collecting media into a separate module + var xml, item; + if (this.isLinked) { + xml = ''; + item = this; + do { + xml += serializer.format( + '%', + serializer.store(item.first) + ); + item = item.rest; + } while (item !== undefined && (item !== null)); + return xml + ''; + } + return serializer.format( + '%', + this.contents.reduce(function (xml, item) { + return xml + serializer.format( + '%', + typeof item === 'object' ? + serializer.store(item, mediaContext) + : typeof item === 'boolean' ? + serializer.format('$', item) + : serializer.format('$', item) + ); + }, '') + ); +}; + + +Context.prototype.toXML = function (serializer) { + if (this.isContinuation) { // continuations are transient in Snap! + return ''; + } + return serializer.format( + '%%' + + '%%%', + this.isLambda ? ' lambda="lambda"' : '', + this.inputs.reduce( + function (xml, input) { + return xml + serializer.format('$', input); + }, + '' + ), + this.variables ? serializer.store(this.variables) : '', + this.expression ? serializer.store(this.expression) : '', + this.receiver ? serializer.store(this.receiver) : '', + this.outerContext ? serializer.store(this.outerContext) : '' + ); +}; + +// Comments + +CommentMorph.prototype.toXML = function (serializer) { + var position, + scale = SyntaxElementMorph.prototype.scale; + + if (this.block) { // attached to a block + return serializer.format( + '%', + this.textWidth() / scale, + this.isCollapsed, + serializer.escape(this.text()) + ); + } + + // free-floating, determine my save-position + if (this.parent) { + position = this.topLeft().subtract(this.parent.topLeft()); + } else { + position = this.topLeft(); + } + return serializer.format( + '%', + position.x / scale, + position.y / scale, + this.textWidth() / scale, + this.isCollapsed, + serializer.escape(this.text()) + ); +}; diff --git a/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/threads.js b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/threads.js new file mode 100644 index 0000000..ab36e87 --- /dev/null +++ b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/threads.js @@ -0,0 +1,2983 @@ +/* + + threads.js + + a tail call optimized blocks-based programming language interpreter + based on morphic.js and blocks.js + inspired by Scratch, Scheme and Squeak + + written by Jens Mönig + jens@moenig.org + + Copyright (C) 2013 by Jens Mönig + + This file is part of Snap!. + + Snap! is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + + + prerequisites: + -------------- + needs blocks.js and objects.js + + + toc + --- + the following list shows the order in which all constructors are + defined. Use this list to locate code in this document: + + ThreadManager + Process + Context + VariableFrame + UpvarReference + + + credits + ------- + John Maloney and Dave Feinberg designed the original Scratch evaluator + Ivan Motyashov contributed initial porting from Squeak + +*/ + +// globals from blocks.js: + +/*global ArgMorph, ArrowMorph, BlockHighlightMorph, BlockMorph, +BooleanSlotMorph, BoxMorph, Color, ColorPaletteMorph, ColorSlotMorph, +CommandBlockMorph, CommandSlotMorph, FrameMorph, HatBlockMorph, +InputSlotMorph, MenuMorph, Morph, MultiArgMorph, Point, +ReporterBlockMorph, ScriptsMorph, ShadowMorph, StringMorph, +SyntaxElementMorph, TextMorph, WorldMorph, blocksVersion, contains, +degrees, detect, getDocumentPositionOf, newCanvas, nop, radians, +useBlurredShadows, ReporterSlotMorph, CSlotMorph, RingMorph, IDE_Morph, +ArgLabelMorph, localize, XML_Element, hex_sha512*/ + +// globals from objects.js: + +/*global StageMorph, SpriteMorph, StagePrompterMorph, Note*/ + +// globals from morphic.js: + +/*global modules, isString, copy, isNil*/ + +// globals from gui.js: + +/*global WatcherMorph*/ + +// globals from lists.js: + +/*global List, ListWatcherMorph*/ + +/*global alert, console*/ + +// Global stuff //////////////////////////////////////////////////////// + +modules.threads = '2013-August-12'; + +var ThreadManager; +var Process; +var Context; +var VariableFrame; +var UpvarReference; + +function snapEquals(a, b) { + if (a instanceof List || (b instanceof List)) { + if (a instanceof List && (b instanceof List)) { + return a.equalTo(b); + } + return false; + } + var x = parseFloat(a), + y = parseFloat(b); + if (isNaN(x) || isNaN(y)) { + x = a; + y = b; + } + if (isString(x) && isString(y)) { + return x.toLowerCase() === y.toLowerCase(); + } + return x === y; +} + +// ThreadManager /////////////////////////////////////////////////////// + +function ThreadManager() { + this.processes = []; +} + +ThreadManager.prototype.toggleProcess = function (block) { + var active = this.findProcess(block); + if (active) { + active.stop(); + } else { + return this.startProcess(block); + } +}; + +ThreadManager.prototype.startProcess = function (block, isThreadSafe) { + var active = this.findProcess(block), + top = block.topBlock(), + newProc; + if (active) { + if (isThreadSafe) { + return active; + } + active.stop(); + this.removeTerminatedProcesses(); + } + top.addHighlight(); + newProc = new Process(block.topBlock()); + this.processes.push(newProc); + return newProc; +}; + +ThreadManager.prototype.stopAll = function () { + this.processes.forEach(function (proc) { + proc.stop(); + }); +}; + +ThreadManager.prototype.stopAllForReceiver = function (rcvr) { + this.processes.forEach(function (proc) { + if (proc.homeContext.receiver === rcvr) { + proc.stop(); + if (rcvr.isClone) { + proc.isDead = true; + } + } + }); +}; + +ThreadManager.prototype.stopProcess = function (block) { + var active = this.findProcess(block); + if (active) { + active.stop(); + } +}; + +ThreadManager.prototype.pauseAll = function (stage) { + this.processes.forEach(function (proc) { + proc.pause(); + }); + if (stage) { + stage.pauseAllActiveSounds(); + } +}; + +ThreadManager.prototype.isPaused = function () { + return detect(this.processes, function (proc) {return proc.isPaused; }) + !== null; +}; + +ThreadManager.prototype.resumeAll = function (stage) { + this.processes.forEach(function (proc) { + proc.resume(); + }); + if (stage) { + stage.resumeAllActiveSounds(); + } +}; + +ThreadManager.prototype.step = function () { +/* + run each process until it gives up control, skipping processes + for sprites that are currently picked up, then filter out any + processes that have been terminated +*/ + this.processes.forEach(function (proc) { + if (!proc.homeContext.receiver.isPickedUp() && !proc.isDead) { + proc.runStep(); + } + }); + this.removeTerminatedProcesses(); +}; + +ThreadManager.prototype.removeTerminatedProcesses = function () { + // and un-highlight their scripts + var remaining = []; + this.processes.forEach(function (proc) { + if (!proc.isRunning() && !proc.errorFlag && !proc.isDead) { + proc.topBlock.removeHighlight(); + + if (proc.prompter) { + proc.prompter.destroy(); + if (proc.homeContext.receiver.stopTalking) { + proc.homeContext.receiver.stopTalking(); + } + } + + if (proc.topBlock instanceof ReporterBlockMorph) { + if (proc.homeContext.inputs[0] instanceof List) { + proc.topBlock.showBubble(new ListWatcherMorph( + proc.homeContext.inputs[0] + )); + } else { + proc.topBlock.showBubble(proc.homeContext.inputs[0]); + } + } + } else { + remaining.push(proc); + } + }); + this.processes = remaining; +}; + +ThreadManager.prototype.findProcess = function (block) { + var top = block.topBlock(); + return detect( + this.processes, + function (each) { + return each.topBlock === top; + } + ); +}; + +// Process ///////////////////////////////////////////////////////////// + +/* + A Process is what brings a stack of blocks to life. The process + keeps track of which block to run next, evaluates block arguments, + handles control structures, and so forth. + + The ThreadManager is the (passive) scheduler, telling each process + when to run by calling its runStep() method. The runStep() method + will execute some number of blocks, then voluntarily yield control + so that the ThreadManager can run another process. + + The Scratch etiquette is that a process should yield control at the + end of every loop iteration, and while it is running a timed command + (e.g. "wait 5 secs") or a synchronous command (e.g. "broadcast xxx + and wait"). Since Snap also has lambda and custom blocks Snap adds + yields at the beginning of each non-atomic custom command block + execution, and - to let users escape infinite loops and recursion - + whenever the process runs into a timeout. + + a Process runs for a receiver, i.e. a sprite or the stage or any + blocks-scriptable object that we'll introduce. + + structure: + + topBlock the stack's first block, of which all others + are children + receiver object (sprite) to which the process applies, + cached from the top block + context the Context describing the current state + of this process + homeContext stores information relevant to the whole process, + i.e. its receiver, result etc. + isPaused boolean indicating whether to pause + readyToYield boolean indicating whether to yield control to + another process + readyToTerminate boolean indicating whether the stop method has + been called + isDead boolean indicating a terminated clone process + timeout msecs after which to force yield + lastYield msecs when the process last yielded + errorFlag boolean indicating whether an error was encountered + prompter active instance of StagePrompterMorph + httpRequest active instance of an HttpRequest or null + pauseOffset msecs between the start of an interpolated operation + and when the process was paused +*/ + +Process.prototype = {}; +Process.prototype.contructor = Process; +Process.prototype.timeout = 500; // msecs after which to force yield +Process.prototype.isCatchingErrors = true; + +function Process(topBlock) { + this.topBlock = topBlock || null; + + this.readyToYield = false; + this.readyToTerminate = false; + this.isDead = false; + this.errorFlag = false; + this.context = null; + this.homeContext = new Context(); + this.lastYield = Date.now(); + this.isAtomic = false; + this.prompter = null; + this.httpRequest = null; + this.isPaused = false; + this.pauseOffset = null; + this.frameCount = 0; + + if (topBlock) { + this.homeContext.receiver = topBlock.receiver(); + this.homeContext.variables.parentFrame = + this.homeContext.receiver.variables; + this.context = new Context( + null, + topBlock.blockSequence(), + this.homeContext + ); + this.pushContext('doYield'); // highlight top block + } +} + +// Process accessing + +Process.prototype.isRunning = function () { + return (this.context !== null) && (!this.readyToTerminate); +}; + +// Process entry points + +Process.prototype.runStep = function () { +/* + a step is an an uninterruptable 'atom', it can consist + of several contexts, even of several blocks +*/ + if (this.isPaused) { // allow pausing in between atomic steps: + return this.pauseStep(); + } + this.readyToYield = false; + while (!this.readyToYield + && this.context + && (this.isAtomic ? + (Date.now() - this.lastYield < this.timeout) : true) + ) { + // also allow pausing inside atomic steps - for PAUSE block primitive: + if (this.isPaused) { + return this.pauseStep(); + } + this.evaluateContext(); + } + this.lastYield = Date.now(); + + // make sure to redraw atomic things + if (this.isAtomic && + this.homeContext.receiver && + this.homeContext.receiver.endWarp) { + this.homeContext.receiver.endWarp(); + this.homeContext.receiver.startWarp(); + } + + if (this.readyToTerminate) { + while (this.context) { + this.popContext(); + } + if (this.homeContext.receiver) { + if (this.homeContext.receiver.endWarp) { + // pen optimization + this.homeContext.receiver.endWarp(); + } + } + } +}; + +Process.prototype.stop = function () { + this.readyToYield = true; + this.readyToTerminate = true; + this.errorFlag = false; + if (this.context) { + this.context.stopMusic(); + } +}; + +Process.prototype.pause = function () { + this.isPaused = true; + if (this.context && this.context.startTime) { + this.pauseOffset = Date.now() - this.context.startTime; + } +}; + +Process.prototype.resume = function () { + this.isPaused = false; + this.pauseOffset = null; +}; + +Process.prototype.pauseStep = function () { + this.lastYield = Date.now(); + if (this.context && this.context.startTime) { + this.context.startTime = this.lastYield - this.pauseOffset; + } +}; + +// Process evaluation + +Process.prototype.evaluateContext = function () { + var exp = this.context.expression; + + this.frameCount += 1; + if (exp instanceof Array) { + return this.evaluateSequence(exp); + } + if (exp instanceof MultiArgMorph) { + return this.evaluateMultiSlot(exp, exp.inputs().length); + } + if (exp instanceof ArgLabelMorph) { + return this.evaluateArgLabel(exp); + } + if (exp instanceof ArgMorph || exp.bindingID) { + return this.evaluateInput(exp); + } + if (exp instanceof BlockMorph) { + return this.evaluateBlock(exp, exp.inputs().length); + } + if (isString(exp)) { + return this[exp](); + } + this.popContext(); // default: just ignore it +}; + +Process.prototype.evaluateBlock = function (block, argCount) { + // check for special forms + if (contains(['reportOr', 'reportAnd'], block.selector)) { + return this[block.selector](block); + } + + // first evaluate all inputs, then apply the primitive + var rcvr = this.context.receiver || this.topBlock.receiver(), + inputs = this.context.inputs; + + if (argCount > inputs.length) { + this.evaluateNextInput(block); + } else { + if (this[block.selector]) { + rcvr = this; + } + if (this.isCatchingErrors) { + try { + this.returnValueToParentContext( + rcvr[block.selector].apply(rcvr, inputs) + ); + this.popContext(); + } catch (error) { + this.handleError(error, block); + } + } else { + this.returnValueToParentContext( + rcvr[block.selector].apply(rcvr, inputs) + ); + this.popContext(); + } + } +}; + +// Process: Special Forms Blocks Primitives + +Process.prototype.reportOr = function (block) { + var inputs = this.context.inputs; + + if (inputs.length < 1) { + this.evaluateNextInput(block); + } else if (inputs[0]) { + this.returnValueToParentContext(true); + this.popContext(); + } else if (inputs.length < 2) { + this.evaluateNextInput(block); + } else { + this.returnValueToParentContext(inputs[1] === true); + this.popContext(); + } +}; + +Process.prototype.reportAnd = function (block) { + var inputs = this.context.inputs; + + if (inputs.length < 1) { + this.evaluateNextInput(block); + } else if (!inputs[0]) { + this.returnValueToParentContext(false); + this.popContext(); + } else if (inputs.length < 2) { + this.evaluateNextInput(block); + } else { + this.returnValueToParentContext(inputs[1] === true); + this.popContext(); + } +}; + +// Process: Non-Block evaluation + +Process.prototype.evaluateMultiSlot = function (multiSlot, argCount) { + // first evaluate all subslots, then return a list of their values + var inputs = this.context.inputs, + ans; + if (multiSlot.bindingID) { + if (this.isCatchingErrors) { + try { + ans = this.context.variables.getVar(multiSlot.bindingID); + } catch (error) { + this.handleError(error, multiSlot); + } + } else { + ans = this.context.variables.getVar(multiSlot.bindingID); + } + this.returnValueToParentContext(ans); + this.popContext(); + } else { + if (argCount > inputs.length) { + this.evaluateNextInput(multiSlot); + } else { + this.returnValueToParentContext(new List(inputs)); + this.popContext(); + } + } +}; +Process.prototype.evaluateArgLabel = function (argLabel) { + // perform the ID function on an ArgLabelMorph element + var inputs = this.context.inputs; + if (inputs.length < 1) { + this.evaluateNextInput(argLabel); + } else { + this.returnValueToParentContext(inputs[0]); + this.popContext(); + } +}; + +Process.prototype.evaluateInput = function (input) { + // evaluate the input unless it is bound to an implicit parameter + var ans; + if (input.bindingID) { + if (this.isCatchingErrors) { + try { + ans = this.context.variables.getVar(input.bindingID); + } catch (error) { + this.handleError(error, input); + } + } else { + ans = this.context.variables.getVar(input.bindingID); + } + } else { + ans = input.evaluate(); + if (ans) { + if (contains( + [CommandSlotMorph, ReporterSlotMorph], + input.constructor + ) || (input instanceof CSlotMorph && !input.isStatic)) { + // I know, this still needs yet to be done right.... + ans = this.reify(ans, new List()); + ans.isImplicitLambda = true; + } + } + } + this.returnValueToParentContext(ans); + this.popContext(); +}; + +Process.prototype.evaluateSequence = function (arr) { + var pc = this.context.pc, + outer = this.context.outerContext, + isLambda = this.context.isLambda, + isImplicitLambda = this.context.isImplicitLambda, + isCustomBlock = this.context.isCustomBlock, + upvars = this.context.upvars; + if (pc === (arr.length - 1)) { // tail call elimination + this.context = new Context( + this.context.parentContext, + arr[pc], + this.context.outerContext, + this.context.receiver + ); + this.context.isLambda = isLambda; + this.context.isImplicitLambda = isImplicitLambda; + this.context.isCustomBlock = isCustomBlock; + if (upvars) { + this.context.upvars = new UpvarReference(upvars); + } + } else { + if (pc >= arr.length) { + this.popContext(); + } else { + this.context.pc += 1; + this.pushContext(arr[pc], outer); + } + } +}; + +/* +// version w/o tail call optimization: +-------------------------------------- +Caution: we cannot just revert to this version of the method, because to make +tail call elimination work many tweaks had to be done to various primitives. +For the most part these tweaks are about schlepping the outer context (for +the variable bindings) and the isLambda flag along, and are indicated by a +short comment in the code. But to really revert would take a good measure +of trial and error as well as debugging. In the developers file archive there +is a version of threads.js dated 120119(2) which basically resembles the +last version before introducing tail call optimization on 120123. + +Process.prototype.evaluateSequence = function (arr) { + var pc = this.context.pc; + if (pc >= arr.length) { + this.popContext(); + } else { + this.context.pc += 1; + this.pushContext(arr[pc]); + } +}; +*/ + +Process.prototype.evaluateNextInput = function (element) { + var nxt = this.context.inputs.length, + args = element.inputs(), + exp = args[nxt], + outer = this.context.outerContext; // for tail call elimination + + if (exp.isUnevaluated) { + if (exp.isUnevaluated === true || exp.isUnevaluated()) { + // just return the input as-is + /* + Note: we only reify the input here, if it's not an + input to a reification primitive itself (THE BLOCK, + THE SCRIPT), because those allow for additional + explicit parameter bindings. + */ + if (contains(['reify', 'reportScript'], + this.context.expression.selector)) { + this.context.addInput(exp); + } else { + this.context.addInput(this.reify(exp, new List())); + } + } else { + this.pushContext(exp, outer); + } + } else { + this.pushContext(exp, outer); + } +}; + +Process.prototype.doYield = function () { + this.popContext(); + if (!this.isAtomic) { + this.readyToYield = true; + } +}; + +// Process Exception Handling + +Process.prototype.handleError = function (error, element) { + this.stop(); + this.errorFlag = true; + this.topBlock.addErrorHighlight(); + (element || this.topBlock).showBubble( + (element ? '' : 'Inside: ') + + error.name + + '\n' + + error.message + ); +}; + +// Process Lambda primitives + +Process.prototype.reify = function (topBlock, parameterNames, isCustomBlock) { + var context = new Context( + null, + null, + this.context ? this.context.outerContext : null + ), + i = 0; + + if (topBlock) { + context.expression = topBlock.fullCopy(); + context.expression.show(); // be sure to make visible if in app mode + + if (!isCustomBlock) { + // mark all empty slots with an identifier + context.expression.allEmptySlots().forEach(function (slot) { + i += 1; + if (slot instanceof MultiArgMorph) { + slot.bindingID = ['arguments']; + } else { + slot.bindingID = i; + } + }); + // and remember the number of detected empty slots + context.emptySlots = i; + } + + } else { + context.expression = [this.context.expression.fullCopy()]; + } + + context.inputs = parameterNames.asArray(); + context.receiver + = this.context ? this.context.receiver : topBlock.receiver(); + + return context; +}; + +Process.prototype.reportScript = function (parameterNames, topBlock) { + return this.reify(topBlock, parameterNames); +}; + +Process.prototype.reifyScript = function (topBlock, parameterNames) { + return this.reify(topBlock, parameterNames); +}; + +Process.prototype.reifyReporter = function (topBlock, parameterNames) { + return this.reify(topBlock, parameterNames); +}; + +Process.prototype.reifyPredicate = function (topBlock, parameterNames) { + return this.reify(topBlock, parameterNames); +}; + +Process.prototype.doRun = function (context, args, isCustomBlock) { + return this.evaluate(context, args, true, isCustomBlock); +}; + +Process.prototype.evaluate = function ( + context, + args, + isCommand +) { + if (!context) {return null; } + if (context.isContinuation) { + return this.runContinuation(context, args); + } + if (!(context instanceof Context)) { + throw new Error('expecting a ring but getting ' + context); + } + + var outer = new Context(null, null, context.outerContext), + runnable, + extra, + parms = args.asArray(), + i, + value; + + if (!outer.receiver) { + outer.receiver = context.receiver; // for custom blocks + } + runnable = new Context( + this.context.parentContext, + context.expression, + outer, + context.receiver + ); + extra = new Context(runnable, 'doYield'); + + /* + Note: if the context's expression is a ReporterBlockMorph, + the extra context gets popped off immediately without taking + effect (i.e. it doesn't yield within evaluating a stack of + nested reporters) + */ + + if (isCommand || (context.expression instanceof ReporterBlockMorph)) { + this.context.parentContext = extra; + } else { + this.context.parentContext = runnable; + } + + runnable.isLambda = true; + runnable.isImplicitLambda = context.isImplicitLambda; + runnable.isCustomBlock = false; + + // assing parameters if any were passed + if (parms.length > 0) { + + // assign formal parameters + for (i = 0; i < context.inputs.length; i += 1) { + value = 0; + if (parms[i]) { + value = parms[i]; + } + outer.variables.addVar(context.inputs[i], value); + } + + // assign implicit parameters if there are no formal ones + if (context.inputs.length === 0) { + // assign the actual arguments list to the special + // parameter ID ['arguments'], to be used for variadic inputs + outer.variables.addVar(['arguments'], args); + + // in case there is only one input + // assign it to all empty slots + if (parms.length === 1) { + for (i = 1; i <= context.emptySlots; i += 1) { + outer.variables.addVar(i, parms[0]); + } + + // if the number of inputs matches the number + // of empty slots distribute them sequentially + } else if (parms.length === context.emptySlots) { + for (i = 1; i <= parms.length; i += 1) { + outer.variables.addVar(i, parms[i - 1]); + } + + } else if (context.emptySlots !== 1) { + throw new Error( + 'expecting ' + context.emptySlots + ' input(s), ' + + 'but getting ' + parms.length + ); + } + } + } + if (this.context.upvars) { + runnable.upvars = new UpvarReference(this.context.upvars); + } + + if (runnable.expression instanceof CommandBlockMorph) { + runnable.expression = runnable.expression.blockSequence(); + } +}; + +Process.prototype.fork = function (context, args) { + if (context.isContinuation) { + throw new Error( + 'continuations cannot be forked' + ); + } + + var outer = new Context(null, null, context.outerContext), + runnable = new Context(null, + context.expression, + outer + ), + parms = args.asArray(), + i, + value, + stage = this.homeContext.receiver.parentThatIsA(StageMorph), + proc = new Process(); + + runnable.isLambda = true; + + // assign parameters if any were passed + if (parms.length > 0) { + + // assign formal parameters + for (i = 0; i < context.inputs.length; i += 1) { + value = 0; + if (parms[i]) { + value = parms[i]; + } + outer.variables.addVar(context.inputs[i], value); + } + + // assign implicit parameters if there are no formal ones + if (context.inputs.length === 0) { + // assign the actual arguments list to the special + // parameter ID ['arguments'], to be used for variadic inputs + outer.variables.addVar(['arguments'], args); + + // in case there is only one input + // assign it to all empty slots + if (parms.length === 1) { + for (i = 1; i <= context.emptySlots; i += 1) { + outer.variables.addVar(i, parms[0]); + } + + // if the number of inputs matches the number + // of empty slots distribute them sequentially + } else if (parms.length === context.emptySlots) { + for (i = 1; i <= parms.length; i += 1) { + outer.variables.addVar(i, parms[i - 1]); + } + + } else if (context.emptySlots !== 1) { + throw new Error( + 'expecting ' + context.emptySlots + ' input(s), ' + + 'but getting ' + parms.length + ); + } + } + } + + if (runnable.expression instanceof CommandBlockMorph) { + runnable.expression = runnable.expression.blockSequence(); + } + + proc.homeContext = context.outerContext; + proc.topBlock = context.expression; + proc.context = runnable; + proc.pushContext('doYield'); + stage.threads.processes.push(proc); +}; + +Process.prototype.doReport = function (value, isCSlot) { + while (this.context && !this.context.isLambda) { + if (this.context.expression === 'doStopWarping') { + this.doStopWarping(); + } else { + this.popContext(); + } + } + if (this.context && this.context.isImplicitLambda) { + if (this.context.expression === 'doStopWarping') { + this.doStopWarping(); + } else { + this.popContext(); + } + return this.doReport(value, true); + } + if (this.context && this.context.isCustomBlock) { + // now I'm back at the custom block sequence. + // advance my pc to my expression's length + this.context.pc = this.context.expression.length - 1; + } + if (isCSlot) { + if (this.context.parentContext.expression instanceof Array) { + this.popContext(); + } + } + return value; +}; + +Process.prototype.doStopBlock = function () { + this.doReport(); +}; + +// Process evaluation variants, commented out for now (redundant) + +/* +Process.prototype.doRunWithInputList = function (context, args) { + // provide an extra selector for the palette + return this.doRun(context, args); +}; + +Process.prototype.evaluateWithInputList = function (context, args) { + // provide an extra selector for the palette + return this.evaluate(context, args); +}; + +Process.prototype.forkWithInputList = function (context, args) { + // provide an extra selector for the palette + return this.fork(context, args); +}; +*/ + +// Process continuations primitives + +Process.prototype.doCallCC = function (aContext) { + this.evaluate(aContext, new List([this.context.continuation()])); +}; + +Process.prototype.reportCallCC = function (aContext) { + this.doCallCC(aContext); +}; + +Process.prototype.runContinuation = function (aContext, args) { + var parms = args.asArray(); + this.context.parentContext = aContext.copyForContinuationCall(); + // passing parameter if any was passed + if (parms.length === 1) { + this.context.parentContext.outerContext.variables.addVar( + 1, + parms[0] + ); + } +}; + +// Process custom block primitives + +Process.prototype.evaluateCustomBlock = function () { + var context = this.context.expression.definition.body, + declarations = this.context.expression.definition.declarations, + args = new List(this.context.inputs), + parms = args.asArray(), + runnable, + extra, + i, + value, + upvars, + outer; + + if (!context) {return null; } + outer = new Context(); + outer.receiver = this.context.receiver; // || this.homeContext.receiver; + outer.variables.parentFrame = outer.receiver.variables; + + runnable = new Context( + this.context.parentContext, + context.expression, + outer, + outer.receiver, + true // is custom block + ); + extra = new Context(runnable, 'doYield'); + + this.context.parentContext = extra; + + runnable.isLambda = true; + runnable.isCustomBlock = true; + + // passing parameters if any were passed + if (parms.length > 0) { + + // assign formal parameters + for (i = 0; i < context.inputs.length; i += 1) { + value = 0; + if (parms[i]) { + value = parms[i]; + } + outer.variables.addVar(context.inputs[i], value); + + // if the parameter is an upvar, + // create an UpvarReference to it + if (declarations[context.inputs[i]][0] === '%upvar') { + if (!upvars) { // lazy initialization + upvars = new UpvarReference(this.context.upvars); + } + upvars.addReference( + value, + context.inputs[i], + outer.variables + ); + } + } + } + + if (upvars) { + runnable.upvars = upvars; + } else if (this.context.upvars) { + runnable.upvars = new UpvarReference(this.context.upvars); + } + + if (runnable.expression instanceof CommandBlockMorph) { + runnable.expression = runnable.expression.blockSequence(); + } +}; + + +// Process variables primitives + +Process.prototype.doDeclareVariables = function (varNames) { + var varFrame = this.context.outerContext.variables; + varNames.asArray().forEach(function (name) { + varFrame.addVar(name); + }); +}; + +Process.prototype.doSetVar = function (varName, value) { + var varFrame = this.context.variables, + name = varName; + + if (name instanceof Context) { + if (name.expression.selector === 'reportGetVar') { + name = name.expression.blockSpec; + } + } + varFrame.setVar(name, value); +}; + +Process.prototype.doChangeVar = function (varName, value) { + var varFrame = this.context.variables, + name = varName; + + if (name instanceof Context) { + if (name.expression.selector === 'reportGetVar') { + name = name.expression.blockSpec; + } + } + varFrame.changeVar(name, value); +}; + +Process.prototype.reportGetVar = function () { + // assumes a getter block whose blockSpec is a variable name + var varName = this.context.expression.blockSpec; + + return this.context.variables.getVar( + varName, + this.context.upvars + ); +}; + +Process.prototype.doShowVar = function (varName) { + var varFrame = this.context.variables, + stage, + watcher, + target, + label, + others, + name = varName; + + if (name instanceof Context) { + if (name.expression.selector === 'reportGetVar') { + name = name.expression.blockSpec; + } + } + if (this.homeContext.receiver) { + stage = this.homeContext.receiver.parentThatIsA(StageMorph); + if (stage) { + target = varFrame.find(name); + // first try to find an existing (hidden) watcher + watcher = detect( + stage.children, + function (morph) { + return morph instanceof WatcherMorph + && morph.target === target + && morph.getter === name; + } + ); + if (watcher !== null) { + watcher.show(); + watcher.fixLayout(); // re-hide hidden parts + return; + } + // if no watcher exists, create a new one + if (target.owner) { + label = name; + } else { + label = name + ' (temporary)'; + } + watcher = new WatcherMorph( + label, + SpriteMorph.prototype.blockColor.variables, + target, + name + ); + watcher.setPosition(stage.position().add(10)); + others = stage.watchers(watcher.left()); + if (others.length > 0) { + watcher.setTop(others[others.length - 1].bottom()); + } + stage.add(watcher); + watcher.fixLayout(); + } + } +}; + +Process.prototype.doHideVar = function (varName) { + // if no varName is specified delete all watchers on temporaries + var varFrame = this.context.variables, + stage, + watcher, + target, + name = varName; + + if (name instanceof Context) { + if (name.expression.selector === 'reportGetVar') { + name = name.expression.blockSpec; + } + } + if (!name) { + this.doRemoveTemporaries(); + return; + } + if (this.homeContext.receiver) { + stage = this.homeContext.receiver.parentThatIsA(StageMorph); + if (stage) { + target = varFrame.find(name); + watcher = detect( + stage.children, + function (morph) { + return morph instanceof WatcherMorph + && morph.target === target + && morph.getter === name; + } + ); + if (watcher !== null) { + if (watcher.isTemporary()) { + watcher.destroy(); + } else { + watcher.hide(); + } + } + } + } +}; + +Process.prototype.doRemoveTemporaries = function () { + var stage; + if (this.homeContext.receiver) { + stage = this.homeContext.receiver.parentThatIsA(StageMorph); + if (stage) { + stage.watchers().forEach(function (watcher) { + if (watcher.isTemporary()) { + watcher.destroy(); + } + }); + } + } +}; + +// Process lists primitives + +Process.prototype.reportNewList = function (elements) { + return elements; +}; + +Process.prototype.reportCONS = function (car, cdr) { + return new List().cons(car, cdr); +}; + +Process.prototype.reportCDR = function (list) { + return list.cdr(); +}; + +Process.prototype.doAddToList = function (element, list) { + list.add(element); +}; + +Process.prototype.doDeleteFromList = function (index, list) { + var idx = index; + if (this.inputOption(index) === 'all') { + return list.clear(); + } + if (index === '') { + return null; + } + if (this.inputOption(index) === 'last') { + idx = list.length(); + } + list.remove(idx); +}; + +Process.prototype.doInsertInList = function (element, index, list) { + var idx = index; + if (index === '') { + return null; + } + if (this.inputOption(index) === 'any') { + idx = this.reportRandom(1, list.length()); + } + if (this.inputOption(index) === 'last') { + idx = list.length() + 1; + } + list.add(element, idx); +}; + +Process.prototype.doReplaceInList = function (index, list, element) { + var idx = index; + if (index === '') { + return null; + } + if (this.inputOption(index) === 'any') { + idx = this.reportRandom(1, list.length()); + } + if (this.inputOption(index) === 'last') { + idx = list.length(); + } + list.put(element, idx); +}; + +Process.prototype.reportListItem = function (index, list) { + var idx = index; + if (index === '') { + return ''; + } + if (this.inputOption(index) === 'any') { + idx = this.reportRandom(1, list.length()); + } + if (this.inputOption(index) === 'last') { + idx = list.length(); + } + return list.at(idx); +}; + +Process.prototype.reportListLength = function (list) { + return list.length(); +}; + +Process.prototype.reportListContainsItem = function (list, element) { + return list.contains(element); +}; + +// Process conditionals primitives + +Process.prototype.doIf = function () { + var args = this.context.inputs, + outer = this.context.outerContext, // for tail call elimination + isLambda = this.context.isLambda, + isImplicitLambda = this.context.isImplicitLambda, + isCustomBlock = this.context.isCustomBlock, + upvars = this.context.upvars; + + this.popContext(); + if (args[0]) { + if (args[1]) { + this.pushContext(args[1].blockSequence(), outer); + this.context.isLambda = isLambda; + this.context.isImplicitLambda = isImplicitLambda; + this.context.isCustomBlock = isCustomBlock; + this.context.upvars = new UpvarReference(upvars); + } + } + this.pushContext(); +}; + +Process.prototype.doIfElse = function () { + var args = this.context.inputs, + outer = this.context.outerContext, // for tail call elimination + isLambda = this.context.isLambda, + isImplicitLambda = this.context.isImplicitLambda, + isCustomBlock = this.context.isCustomBlock, + upvars = this.context.upvars; + + this.popContext(); + if (args[0]) { + if (args[1]) { + this.pushContext(args[1].blockSequence(), outer); + } + } else { + if (args[2]) { + this.pushContext(args[2].blockSequence(), outer); + } + } + if (this.context) { + this.context.isLambda = isLambda; + this.context.isImplicitLambda = isImplicitLambda; + this.context.isCustomBlock = isCustomBlock; + this.context.upvars = new UpvarReference(upvars); + } + + this.pushContext(); +}; + +// Process process related primitives + +Process.prototype.doStop = function () { + this.stop(); +}; + +Process.prototype.doStopAll = function () { + var stage, ide; + if (this.homeContext.receiver) { + stage = this.homeContext.receiver.parentThatIsA(StageMorph); + if (stage) { + stage.threads.resumeAll(stage); + stage.keysPressed = {}; + stage.threads.stopAll(); + stage.stopAllActiveSounds(); + stage.children.forEach(function (morph) { + if (morph.stopTalking) { + morph.stopTalking(); + } + }); + stage.removeAllClones(); + } + ide = stage.parentThatIsA(IDE_Morph); + if (ide) {ide.controlBar.pauseButton.refresh(); } + } +}; + +Process.prototype.doWarp = function (body) { + // execute my contents block atomically (more or less) + var outer = this.context.outerContext, // for tail call elimination + isLambda = this.context.isLambda, + isImplicitLambda = this.context.isImplicitLambda, + isCustomBlock = this.context.isCustomBlock, + stage; + + this.popContext(); + + if (body) { + if (this.homeContext.receiver) { + if (this.homeContext.receiver.startWarp) { + // pen optimization + this.homeContext.receiver.startWarp(); + } + stage = this.homeContext.receiver.parentThatIsA(StageMorph); + if (stage) { + stage.fps = 0; // variable frame rate + } + } + + this.pushContext('doYield'); + + this.context.isLambda = isLambda; + this.context.isImplicitLambda = isImplicitLambda; + this.context.isCustomBlock = isCustomBlock; + + if (!this.isAtomic) { + this.pushContext('doStopWarping'); + } + this.pushContext(body.blockSequence(), outer); + this.isAtomic = true; + } + this.pushContext(); +}; + +Process.prototype.doStopWarping = function () { + var stage; + this.popContext(); + this.isAtomic = false; + if (this.homeContext.receiver) { + if (this.homeContext.receiver.endWarp) { + // pen optimization + this.homeContext.receiver.endWarp(); + } + stage = this.homeContext.receiver.parentThatIsA(StageMorph); + if (stage) { + stage.fps = stage.frameRate; // back to fixed frame rate + } + } +}; + +Process.prototype.reportIsFastTracking = function () { + var ide; + if (this.homeContext.receiver) { + ide = this.homeContext.receiver.parentThatIsA(IDE_Morph); + if (ide) { + return ide.stage.isFastTracked; + } + } + return false; +}; + +Process.prototype.doSetFastTracking = function (bool) { + var ide; + if (!this.reportIsA(bool, 'Boolean')) { + return; + } + if (this.homeContext.receiver) { + ide = this.homeContext.receiver.parentThatIsA(IDE_Morph); + if (ide) { + if (ide.stage.isFastTracked) { + ide.stopFastTracking(); + } else { + ide.startFastTracking(); + } + } + } +}; + +Process.prototype.doPauseAll = function () { + var stage, ide; + if (this.homeContext.receiver) { + stage = this.homeContext.receiver.parentThatIsA(StageMorph); + if (stage) { + stage.threads.pauseAll(stage); + } + ide = stage.parentThatIsA(IDE_Morph); + if (ide) {ide.controlBar.pauseButton.refresh(); } + } +}; + +// Process loop primitives + +Process.prototype.doForever = function (body) { + this.pushContext('doYield'); + if (body) { + this.pushContext(body.blockSequence()); + } + this.pushContext(); +}; + +Process.prototype.doRepeat = function (counter, body) { + var block = this.context.expression, + outer = this.context.outerContext, // for tail call elimination + isLambda = this.context.isLambda, + isImplicitLambda = this.context.isImplicitLambda, + isCustomBlock = this.context.isCustomBlock, + upvars = this.context.upvars; + + if (counter < 1) { // was '=== 0', which caused infinite loops on non-ints + return null; + } + this.popContext(); + + this.pushContext(block, outer); + + this.context.isLambda = isLambda; + this.context.isImplicitLambda = isImplicitLambda; + this.context.isCustomBlock = isCustomBlock; + this.context.upvars = new UpvarReference(upvars); + + this.context.addInput(counter - 1); + + this.pushContext('doYield'); + if (body) { + this.pushContext(body.blockSequence()); + } + + this.pushContext(); +}; + +Process.prototype.doUntil = function (goalCondition, body) { + if (goalCondition) { + this.popContext(); + this.pushContext('doYield'); + return null; + } + this.context.inputs = []; + this.pushContext('doYield'); + if (body) { + this.pushContext(body.blockSequence()); + } + this.pushContext(); +}; + +Process.prototype.doWaitUntil = function (goalCondition) { + if (goalCondition) { + this.popContext(); + this.pushContext('doYield'); + return null; + } + this.context.inputs = []; + this.pushContext('doYield'); + this.pushContext(); +}; + +// Process interpolated primitives + +Process.prototype.doWait = function (secs) { + if (!this.context.startTime) { + this.context.startTime = Date.now(); + } + if ((Date.now() - this.context.startTime) >= (secs * 1000)) { + return null; + } + this.pushContext('doYield'); + this.pushContext(); +}; + +Process.prototype.doGlide = function (secs, endX, endY) { + if (!this.context.startTime) { + this.context.startTime = Date.now(); + this.context.startValue = new Point( + this.homeContext.receiver.xPosition(), + this.homeContext.receiver.yPosition() + ); + } + if ((Date.now() - this.context.startTime) >= (secs * 1000)) { + this.homeContext.receiver.gotoXY(endX, endY); + return null; + } + this.homeContext.receiver.glide( + secs * 1000, + endX, + endY, + Date.now() - this.context.startTime, + this.context.startValue + ); + + this.pushContext('doYield'); + this.pushContext(); +}; + +Process.prototype.doSayFor = function (data, secs) { + if (!this.context.startTime) { + this.context.startTime = Date.now(); + this.homeContext.receiver.bubble(data); + } + if ((Date.now() - this.context.startTime) >= (secs * 1000)) { + this.homeContext.receiver.stopTalking(); + return null; + } + this.pushContext('doYield'); + this.pushContext(); +}; + +Process.prototype.doThinkFor = function (data, secs) { + if (!this.context.startTime) { + this.context.startTime = Date.now(); + this.homeContext.receiver.doThink(data); + } + if ((Date.now() - this.context.startTime) >= (secs * 1000)) { + this.homeContext.receiver.stopTalking(); + return null; + } + this.pushContext('doYield'); + this.pushContext(); +}; + +// Process sound primitives (interpolated) + +Process.prototype.doPlaySoundUntilDone = function (name) { + var sprite = this.homeContext.receiver; + if (this.context.activeAudio === null) { + this.context.activeAudio = sprite.playSound(name); + } + if (this.context.activeAudio.ended + || this.context.activeAudio.terminated) { + return null; + } + this.pushContext('doYield'); + this.pushContext(); +}; + +Process.prototype.doStopAllSounds = function () { + var stage = this.homeContext.receiver.parentThatIsA(StageMorph); + if (stage) { + stage.threads.processes.forEach(function (thread) { + if (thread.context) { + thread.context.stopMusic(); + if (thread.context.activeAudio) { + thread.popContext(); + } + } + }); + stage.stopAllActiveSounds(); + } +}; + +// Process user prompting primitives (interpolated) + +Process.prototype.doAsk = function (data) { + var stage = this.homeContext.receiver.parentThatIsA(StageMorph), + isStage = this.homeContext.receiver instanceof StageMorph, + activePrompter; + + if (!this.prompter) { + activePrompter = detect( + stage.children, + function (morph) {return morph instanceof StagePrompterMorph; } + ); + if (!activePrompter) { + if (!isStage) { + this.homeContext.receiver.bubble(data, false, true); + } + this.prompter = new StagePrompterMorph(isStage ? data : null); + if (stage.scale < 1) { + this.prompter.setWidth(stage.width() - 10); + } else { + this.prompter.setWidth(stage.dimensions.x - 20); + } + this.prompter.fixLayout(); + this.prompter.setCenter(stage.center()); + this.prompter.setBottom(stage.bottom() - this.prompter.border); + stage.add(this.prompter); + this.prompter.inputField.edit(); + stage.changed(); + } + } else { + if (this.prompter.isDone) { + stage.lastAnswer = this.prompter.inputField.getValue(); + this.prompter.destroy(); + this.prompter = null; + if (!isStage) {this.homeContext.receiver.stopTalking(); } + return null; + } + } + this.pushContext('doYield'); + this.pushContext(); +}; + +Process.prototype.reportLastAnswer = function () { + return this.homeContext.receiver.parentThatIsA(StageMorph).lastAnswer; +}; + +// Process URI retrieval (interpolated) + +Process.prototype.reportURL = function (url) { + var response; + if (!this.httpRequest) { + this.httpRequest = new XMLHttpRequest(); + this.httpRequest.open("GET", 'http://' + url, true); + this.httpRequest.send(null); + } else if (this.httpRequest.readyState === 4) { + response = this.httpRequest.responseText; + this.httpRequest = null; + return response; + } + this.pushContext('doYield'); + this.pushContext(); +}; + +// Process event messages primitives + +Process.prototype.doBroadcast = function (message) { + var stage = this.homeContext.receiver.parentThatIsA(StageMorph), + hats = [], + procs = []; + + if (message !== '') { + stage.lastMessage = message; + stage.children.concat(stage).forEach(function (morph) { + if (morph instanceof SpriteMorph || morph instanceof StageMorph) { + hats = hats.concat(morph.allHatBlocksFor(message)); + } + }); + hats.forEach(function (block) { + procs.push(stage.threads.startProcess(block, stage.isThreadSafe)); + }); + } + return procs; +}; + +Process.prototype.doBroadcastAndWait = function (message) { + if (!this.context.activeSends) { + this.context.activeSends = this.doBroadcast(message); + } + this.context.activeSends = this.context.activeSends.filter( + function (proc) { + return proc.isRunning(); + } + ); + if (this.context.activeSends.length === 0) { + return null; + } + this.pushContext('doYield'); + this.pushContext(); +}; + +Process.prototype.getLastMessage = function () { + var stage; + if (this.homeContext.receiver) { + stage = this.homeContext.receiver.parentThatIsA(StageMorph); + if (stage) { + return stage.getLastMessage(); + } + } + return ''; +}; + +// Process type inference + +Process.prototype.reportIsA = function (thing, typeString) { + return this.reportTypeOf(thing) === this.inputOption(typeString); +}; + +Process.prototype.reportTypeOf = function (thing) { + // answer a string denoting the argument's type + var exp; + if (thing === null || (thing === undefined)) { + return 'nothing'; + } + if (thing === true || (thing === false)) { + return 'Boolean'; + } + if (!isNaN(parseFloat(thing))) { + return 'number'; + } + if (isString(thing)) { + return 'text'; + } + if (thing instanceof List) { + return 'list'; + } + if (thing instanceof Context) { + if (thing.expression instanceof RingMorph) { + return thing.expression.dataType(); + } + if (thing.expression instanceof ReporterBlockMorph) { + if (thing.expression.isPredicate) { + return 'predicate'; + } + return 'reporter'; + } + + if (thing.expression instanceof Array) { + exp = thing.expression[thing.pc || 0]; + if (exp.isPredicate) { + return 'predicate'; + } + if (exp instanceof RingMorph) { + return exp.dataType(); + } + if (exp instanceof ReporterBlockMorph) { + return 'reporter'; + } + if (exp instanceof CommandBlockMorph) { + return 'command'; + } + return 'reporter'; // 'ring'; + } + + if (thing.expression instanceof CommandBlockMorph) { + return 'command'; + } + return 'reporter'; // 'ring'; + } + return 'undefined'; +}; + +// Process math primtives + +Process.prototype.reportSum = function (a, b) { + return parseFloat(a) + parseFloat(b); +}; + +Process.prototype.reportDifference = function (a, b) { + return parseFloat(a) - parseFloat(b); +}; + +Process.prototype.reportProduct = function (a, b) { + return parseFloat(a) * parseFloat(b); +}; + +Process.prototype.reportQuotient = function (a, b) { + return parseFloat(a) / parseFloat(b); +}; + +Process.prototype.reportModulus = function (a, b) { + var x = parseFloat(a), + y = parseFloat(b); + return ((x % y) + y) % y; +}; + +Process.prototype.reportRandom = function (min, max) { + var floor = parseFloat(min), + ceil = parseFloat(max); + if ((floor % 1 !== 0) || (ceil % 1 !== 0)) { + return Math.random() * (ceil - floor) + floor; + } + return Math.floor(Math.random() * (ceil - floor + 1)) + floor; +}; + +Process.prototype.reportLessThan = function (a, b) { + var x = parseFloat(a), + y = parseFloat(b); + if (isNaN(x) || isNaN(y)) { + x = a; + y = b; + } + return x < y; +}; + +Process.prototype.reportNot = function (bool) { + return !bool; +}; + +Process.prototype.reportGreaterThan = function (a, b) { + var x = parseFloat(a), + y = parseFloat(b); + if (isNaN(x) || isNaN(y)) { + x = a; + y = b; + } + return x > y; +}; + +Process.prototype.reportEquals = function (a, b) { + return snapEquals(a, b); +}; + +Process.prototype.reportIsIdentical = function (a, b) { + var tag = 'idTag'; + if (this.isImmutable(a) || this.isImmutable(b)) { + return snapEquals(a, b); + } + + function clear() { + if (Object.prototype.hasOwnProperty.call(a, tag)) { + delete a[tag]; + } + if (Object.prototype.hasOwnProperty.call(b, tag)) { + delete b[tag]; + } + } + + clear(); + a[tag] = Date.now(); + if (b[tag] === a[tag]) { + clear(); + return true; + } + clear(); + return false; +}; + +Process.prototype.isImmutable = function (obj) { + // private + return contains( + ['nothing', 'Boolean', 'text', 'number', 'undefined'], + this.reportTypeOf(obj) + ); +}; + +Process.prototype.reportTrue = function () { + return true; +}; + +Process.prototype.reportFalse = function () { + return false; +}; + +Process.prototype.reportRound = function (n) { + return Math.round(parseFloat(n)); +}; + +Process.prototype.reportMonadic = function (fname, n) { + var x = parseFloat(n), + result = 0; + + switch (this.inputOption(fname)) { + case 'abs': + result = Math.abs(x); + break; + case 'floor': + result = Math.floor(x); + break; + case 'sqrt': + result = Math.sqrt(x); + break; + case 'sin': + result = Math.sin(radians(x)); + break; + case 'cos': + result = Math.cos(radians(x)); + break; + case 'tan': + result = Math.tan(radians(x)); + break; + case 'asin': + result = degrees(Math.asin(x)); + break; + case 'acos': + result = degrees(Math.acos(x)); + break; + case 'atan': + result = degrees(Math.atan(x)); + break; + case 'ln': + result = Math.log(x); + break; + case 'log': + result = 0; + break; + case 'e^': + result = Math.exp(x); + break; + case '10^': + result = 0; + break; + default: + } + return result; +}; + +Process.prototype.reportTextFunction = function (fname, string) { + var x = (isNil(string) ? '' : string).toString(), + result = ''; + + switch (this.inputOption(fname)) { + case 'encode URI': + result = encodeURI(x); + break; + case 'decode URI': + result = decodeURI(x); + break; + case 'encode URI component': + result = encodeURIComponent(x); + break; + case 'decode URI component': + result = decodeURIComponent(x); + break; + case 'XML escape': + result = new XML_Element().escape(x); + break; + case 'XML unescape': + result = new XML_Element().unescape(x); + break; + case 'hex sha512 hash': + result = hex_sha512(x); + break; + default: + } + return result; +}; + +Process.prototype.reportJoin = function (a, b) { + var x = (isNil(a) ? '' : a).toString(), + y = (isNil(b) ? '' : b).toString(); + return x.concat(y); +}; + +Process.prototype.reportJoinWords = function (aList) { + if (aList instanceof List) { + return aList.asText(); + } + return (aList || '').toString(); +}; + +// Process string ops + +Process.prototype.reportLetter = function (idx, string) { + var i = parseFloat(idx || 0), + str = (string || '').toString(); + return str[i - 1] || ''; +}; + +Process.prototype.reportStringSize = function (string) { + var str = (string || '').toString(); + return str.length; +}; + +Process.prototype.reportUnicode = function (string) { + var str = (string || '').toString()[0]; + return str ? str.charCodeAt(0) : 0; +}; + +Process.prototype.reportUnicodeAsLetter = function (num) { + var code = parseFloat(num || 0); + return String.fromCharCode(code); +}; + +Process.prototype.reportTextSplit = function (string, delimiter) { + var str = (string || '').toString(), + del; + switch (this.inputOption(delimiter)) { + case 'line': + del = '\n'; + break; + case 'tab': + del = '\t'; + break; + case 'cr': + del = '\r'; + break; + case 'whitespace': + return new List(str.trim().split(/[\t\r\n ]+/)); + default: + del = (delimiter || '').toString(); + } + return new List(str.split(del)); +}; + +// Process debugging + +Process.prototype.alert = function (data) { + // debugging primitives only work in dev mode, otherwise they're nop + var world; + if (this.homeContext.receiver) { + world = this.homeContext.receiver.world(); + if (world.isDevMode) { + alert('Snap! ' + data.asArray()); + } + } +}; + +Process.prototype.log = function (data) { + // debugging primitives only work in dev mode, otherwise they're nop + var world; + if (this.homeContext.receiver) { + world = this.homeContext.receiver.world(); + if (world.isDevMode) { + console.log('Snap! ' + data.asArray()); + } + } +}; + +// Process motion primitives + +Process.prototype.getOtherObject = function (name, thisObj, stageObj) { + // private, find the sprite indicated by the given name + // either onstage or in the World's hand + + var stage = isNil(stageObj) ? + thisObj.parentThatIsA(StageMorph) : stageObj, + thatObj = null; + + if (stage) { + // find the corresponding sprite on the stage + thatObj = detect( + stage.children, + function (morph) {return morph.name === name; } + ); + if (!thatObj) { + // check if the sprite in question is currently being + // dragged around + thatObj = detect( + stage.world().hand.children, + function (morph) { + return morph instanceof SpriteMorph + && morph.name === name; + } + ); + } + } + return thatObj; +}; + +Process.prototype.getObjectsNamed = function (name, thisObj, stageObj) { + // private, find all sprites and their clones indicated + // by the given name either onstage or in the World's hand + + var stage = isNil(stageObj) ? + thisObj.parentThatIsA(StageMorph) : stageObj, + those = []; + + function check(obj) { + return obj instanceof SpriteMorph && obj.isClone ? + obj.cloneOriginName === name : obj.name === name; + } + + if (stage) { + // find the corresponding sprite on the stage + those = stage.children.filter(check); + if (!those.length) { + // check if a sprite in question is currently being + // dragged around + those = stage.world().hand.children.filter(check); + } + } + return those; +}; + +Process.prototype.doFaceTowards = function (name) { + var thisObj = this.homeContext.receiver, + thatObj; + + if (thisObj) { + if (this.inputOption(name) === 'mouse-pointer') { + thisObj.faceToXY(this.reportMouseX(), this.reportMouseY()); + } else { + thatObj = this.getOtherObject(name, thisObj); + if (thatObj) { + thisObj.faceToXY( + thatObj.xPosition(), + thatObj.yPosition() + ); + } + } + } +}; + +Process.prototype.doGotoObject = function (name) { + var thisObj = this.homeContext.receiver, + thatObj; + + if (thisObj) { + if (this.inputOption(name) === 'mouse-pointer') { + thisObj.gotoXY(this.reportMouseX(), this.reportMouseY()); + } else { + thatObj = this.getOtherObject(name, thisObj); + if (thatObj) { + thisObj.gotoXY( + thatObj.xPosition(), + thatObj.yPosition() + ); + } + } + } +}; + +// Process temporary cloning (Scratch-style) + +Process.prototype.createClone = function (name) { + var thisObj = this.homeContext.receiver, + thatObj; + + if (!name) {return; } + if (thisObj) { + if (this.inputOption(name) === 'myself') { + thisObj.createClone(); + } else { + thatObj = this.getOtherObject(name, thisObj); + if (thatObj) { + thatObj.createClone(); + } + } + } +}; + +// Process sensing primitives + +Process.prototype.reportTouchingObject = function (name) { + var thisObj = this.homeContext.receiver; + + if (thisObj) { + return this.objectTouchingObject(thisObj, name); + } + return false; +}; + +Process.prototype.objectTouchingObject = function (thisObj, name) { + // helper function for reportTouchingObject() + // also check for temparary clones, as in Scratch 2.0, + // and for any parts (subsprites) + var myself = this, + those, + stage, + mouse; + + if (this.inputOption(name) === 'mouse-pointer') { + mouse = thisObj.world().hand.position(); + if (thisObj.bounds.containsPoint(mouse) && + !thisObj.isTransparentAt(mouse)) { + return true; + } + } else { + stage = thisObj.parentThatIsA(StageMorph); + if (stage) { + if (this.inputOption(name) === 'edge' && + !stage.bounds.containsRectangle(thisObj.bounds)) { + return true; + } + if (this.inputOption(name) === 'pen trails' && + thisObj.isTouching(stage.penTrailsMorph())) { + return true; + } + those = this.getObjectsNamed(name, thisObj, stage); // clones + if (those.some(function (any) { + return thisObj.isTouching(any); + })) { + return true; + } + } + } + return thisObj.parts.some( + function (any) { + return myself.objectTouchingObject(any, name); + } + ); +}; + +Process.prototype.reportTouchingColor = function (aColor) { + // also check for any parts (subsprites) + var thisObj = this.homeContext.receiver, + stage; + + if (thisObj) { + stage = thisObj.parentThatIsA(StageMorph); + if (stage) { + if (thisObj.isTouching(stage.colorFiltered(aColor, thisObj))) { + return true; + } + return thisObj.parts.some( + function (any) { + return any.isTouching(stage.colorFiltered(aColor, any)); + } + ); + } + } + return false; +}; + +Process.prototype.reportColorIsTouchingColor = function (color1, color2) { + // also check for any parts (subsprites) + var thisObj = this.homeContext.receiver, + stage; + + if (thisObj) { + stage = thisObj.parentThatIsA(StageMorph); + if (stage) { + if (thisObj.colorFiltered(color1).isTouching( + stage.colorFiltered(color2, thisObj) + )) { + return true; + } + return thisObj.parts.some( + function (any) { + return any.colorFiltered(color1).isTouching( + stage.colorFiltered(color2, any) + ); + } + ); + } + } + return false; +}; + +Process.prototype.reportDistanceTo = function (name) { + var thisObj = this.homeContext.receiver, + thatObj, + stage, + rc, + point; + + if (thisObj) { + rc = thisObj.rotationCenter(); + point = rc; + if (this.inputOption(name) === 'mouse-pointer') { + point = thisObj.world().hand.position(); + } + stage = thisObj.parentThatIsA(StageMorph); + thatObj = this.getOtherObject(name, thisObj, stage); + if (thatObj) { + point = thatObj.rotationCenter(); + } + return rc.distanceTo(point) / stage.scale; + } + return 0; +}; + +Process.prototype.reportAttributeOf = function (attribute, name) { + var thisObj = this.homeContext.receiver, + thatObj, + stage; + + if (thisObj) { + stage = thisObj.parentThatIsA(StageMorph); + if (stage.name === name) { + thatObj = stage; + } else { + thatObj = this.getOtherObject(name, thisObj, stage); + } + if (thatObj) { + if (isString(attribute)) { + return thatObj.variables.getVar(attribute); + } + switch (this.inputOption(attribute)) { + case 'x position': + return thatObj.xPosition ? thatObj.xPosition() : ''; + case 'y position': + return thatObj.yPosition ? thatObj.yPosition() : ''; + case 'direction': + return thatObj.direction ? thatObj.direction() : ''; + case 'costume #': + return thatObj.getCostumeIdx(); + case 'costume name': + return thatObj.costume ? thatObj.costume.name + : thatObj instanceof SpriteMorph ? localize('Turtle') + : localize('Empty'); + case 'size': + return thatObj.getScale ? thatObj.getScale() : ''; + } + } + } + return ''; +}; + +Process.prototype.reportMouseX = function () { + var stage, world; + if (this.homeContext.receiver) { + stage = this.homeContext.receiver.parentThatIsA(StageMorph); + if (stage) { + world = stage.world(); + if (world) { + return (world.hand.position().x - stage.center().x) + / stage.scale; + } + } + } + return 0; +}; + +Process.prototype.reportMouseY = function () { + var stage, world; + if (this.homeContext.receiver) { + stage = this.homeContext.receiver.parentThatIsA(StageMorph); + if (stage) { + world = stage.world(); + if (world) { + return (stage.center().y - world.hand.position().y) + / stage.scale; + } + } + } + return 0; +}; + +Process.prototype.reportMouseDown = function () { + var world; + if (this.homeContext.receiver) { + world = this.homeContext.receiver.world(); + if (world) { + return world.hand.mouseButton === 'left'; + } + } + return false; +}; + +Process.prototype.reportKeyPressed = function (keyString) { + var stage; + if (this.homeContext.receiver) { + stage = this.homeContext.receiver.parentThatIsA(StageMorph); + if (stage) { + return stage.keysPressed[keyString] !== undefined; + } + } + return false; +}; + +Process.prototype.doResetTimer = function () { + var stage; + if (this.homeContext.receiver) { + stage = this.homeContext.receiver.parentThatIsA(StageMorph); + if (stage) { + stage.resetTimer(); + } + } +}; + +Process.prototype.reportTimer = function () { + var stage; + if (this.homeContext.receiver) { + stage = this.homeContext.receiver.parentThatIsA(StageMorph); + if (stage) { + return stage.getTimer(); + } + } + return 0; +}; + +// Process code mapping + +/* + for generating textual source code using + blocks - not needed to run or debug Snap +*/ + +Process.prototype.doMapCodeOrHeader = function (aContext, anOption, aString) { + if (this.inputOption(anOption) === 'code') { + return this.doMapCode(aContext, aString); + } + if (this.inputOption(anOption) === 'header') { + return this.doMapHeader(aContext, aString); + } + throw new Error( + ' \'' + anOption + '\'\nis not a valid option' + ); +}; + +Process.prototype.doMapHeader = function (aContext, aString) { + if (aContext instanceof Context) { + if (aContext.expression instanceof SyntaxElementMorph) { + return aContext.expression.mapHeader(aString || ''); + } + } +}; + +Process.prototype.doMapCode = function (aContext, aString) { + if (aContext instanceof Context) { + if (aContext.expression instanceof SyntaxElementMorph) { + return aContext.expression.mapCode(aString || ''); + } + } +}; + +Process.prototype.doMapStringCode = function (aString) { + StageMorph.prototype.codeMappings.string = aString || '<#1>'; +}; + +Process.prototype.doMapListCode = function (part, kind, aString) { + var key1 = '', + key2 = 'delim'; + + if (this.inputOption(kind) === 'parameters') { + key1 = 'parms_'; + } else if (this.inputOption(kind) === 'variables') { + key1 = 'tempvars_'; + } + + if (this.inputOption(part) === 'list') { + key2 = 'list'; + } else if (this.inputOption(part) === 'item') { + key2 = 'item'; + } + + StageMorph.prototype.codeMappings[key1 + key2] = aString || ''; +}; + +Process.prototype.reportMappedCode = function (aContext) { + if (aContext instanceof Context) { + if (aContext.expression instanceof SyntaxElementMorph) { + return aContext.expression.mappedCode(); + } + } + return ''; +}; + +// Process music primitives + +Process.prototype.doRest = function (beats) { + var tempo = this.reportTempo(); + this.doWait(60 / tempo * beats); +}; + +Process.prototype.reportTempo = function () { + var stage; + if (this.homeContext.receiver) { + stage = this.homeContext.receiver.parentThatIsA(StageMorph); + if (stage) { + return stage.getTempo(); + } + } + return 0; +}; + +Process.prototype.doChangeTempo = function (delta) { + var stage; + if (this.homeContext.receiver) { + stage = this.homeContext.receiver.parentThatIsA(StageMorph); + if (stage) { + stage.changeTempo(delta); + } + } +}; + +Process.prototype.doSetTempo = function (bpm) { + var stage; + if (this.homeContext.receiver) { + stage = this.homeContext.receiver.parentThatIsA(StageMorph); + if (stage) { + stage.setTempo(bpm); + } + } +}; + +Process.prototype.doPlayNote = function (pitch, beats) { + var tempo = this.reportTempo(); + this.doPlayNoteForSecs( + parseFloat(pitch || '0'), + 60 / tempo * parseFloat(beats || '0') + ); +}; + +Process.prototype.doPlayNoteForSecs = function (pitch, secs) { + // interpolated + if (!this.context.startTime) { + this.context.startTime = Date.now(); + this.context.activeNote = new Note(pitch); + this.context.activeNote.play(); + } + if ((Date.now() - this.context.startTime) >= (secs * 1000)) { + if (this.context.activeNote) { + this.context.activeNote.stop(); + this.context.activeNote = null; + } + return null; + } + this.pushContext('doYield'); + this.pushContext(); +}; + +// Process constant input options + +Process.prototype.inputOption = function (dta) { + // private - for localization + return dta instanceof Array ? dta[0] : dta; +}; + +// Process stack + +Process.prototype.pushContext = function (expression, outerContext) { + var upvars = this.context ? this.context.upvars : null; + this.context = new Context( + this.context, + expression, + outerContext || (this.context ? this.context.outerContext : null), + // for tail call elimination + this.context ? // check needed due to tail call elimination + this.context.receiver : this.homeContext.receiver + ); + if (upvars) { + this.context.upvars = new UpvarReference(upvars); + } +}; + +Process.prototype.popContext = function () { + if (this.context) { + this.context.stopMusic(); + } + this.context = this.context ? this.context.parentContext : null; +}; + +Process.prototype.returnValueToParentContext = function (value) { + // if no parent context exists treat value as result + if (value !== undefined) { + var target = this.context ? // in case of tail call elimination + this.context.parentContext || this.homeContext + : this.homeContext; + target.addInput(value); + } +}; + +Process.prototype.reportStackSize = function () { + return this.context ? this.context.stackSize() : 0; +}; + +Process.prototype.reportFrameCount = function () { + return this.frameCount; +}; + +// Context ///////////////////////////////////////////////////////////// + +/* + A Context describes the state of a Process. + + Each Process has a pointer to a Context containing its + state. Whenever the Process yields control, its Context + tells it exactly where it left off. + + structure: + + parentContext the Context to return to when this one has + been evaluated. + outerContext the Context holding my lexical scope + expression SyntaxElementMorph, an array of blocks to evaluate, + null or a String denoting a selector, e.g. 'doYield' + receiver the object to which the expression applies, if any + variables the current VariableFrame, if any + upvars the current UpvarReference, if any (default: null) + inputs an array of input values computed so far + (if expression is a BlockMorph) + pc the index of the next block to evaluate + (if expression is an array) + startTime time when the context was first evaluated + startValue initial value for interpolated operations + activeAudio audio buffer for interpolated operations, don't persist + activeNote audio oscillator for interpolated ops, don't persist + isLambda marker for return ops + isImplicitLambda marker for return ops + isCustomBlock marker for return ops + emptySlots caches the number of empty slots for reification +*/ + +function Context( + parentContext, + expression, + outerContext, + receiver +) { + this.outerContext = outerContext || null; + this.parentContext = parentContext || null; + this.expression = expression || null; + this.receiver = receiver || null; + this.variables = new VariableFrame(); + if (this.outerContext) { + this.variables.parentFrame = this.outerContext.variables; + this.receiver = this.outerContext.receiver; + } + this.upvars = null; // set to an UpvarReference in custom blocks + this.inputs = []; + this.pc = 0; + this.startTime = null; + this.activeAudio = null; + this.activeNote = null; + this.isLambda = false; // marks the end of a lambda + this.isImplicitLambda = false; // marks the end of a C-shaped slot + this.isCustomBlock = false; // marks the end of a custom block's stack + this.emptySlots = 0; // used for block reification +} + +Context.prototype.toString = function () { + var pref = this.isLambda ? '\u03BB-' : '', + expr = this.expression; + + if (expr instanceof Array) { + if (expr.length > 0) { + expr = '[' + expr[0] + ']'; + } + } + return pref + 'Context >> ' + expr + ' ' + this.variables; +}; + +Context.prototype.image = function () { + var ring = new RingMorph(), + block, + cont; + + if (this.expression instanceof Morph) { + block = this.expression.fullCopy(); + + // replace marked call/cc block with empty slot + if (this.isContinuation) { + cont = detect(block.allInputs(), function (inp) { + return inp.bindingID === 1; + }); + if (cont) { + block.revertToDefaultInput(cont, true); + } + } + ring.embed(block, this.inputs); + return ring.fullImage(); + } + if (this.expression instanceof Array) { + block = this.expression[this.pc].fullCopy(); + if (block instanceof RingMorph && !block.contents()) { // empty ring + return block.fullImage(); + } + ring.embed(block, this.isContinuation ? [] : this.inputs); + return ring.fullImage(); + } + return newCanvas(); +}; + +// Context continuations: + +Context.prototype.continuation = function () { + var cont; + if (this.expression instanceof Array) { + cont = this; + } else if (this.parentContext) { + cont = this.parentContext; + } else { + return new Context(null, 'doStop'); + } + cont = cont.copyForContinuation(); + cont.isContinuation = true; + return cont; +}; + +Context.prototype.copyForContinuation = function () { + var cpy = copy(this), + cur = cpy, + isReporter = !(this.expression instanceof Array); + if (isReporter) { + cur.prepareContinuationForBinding(); + while (cur.parentContext) { + cur.parentContext = copy(cur.parentContext); + cur = cur.parentContext; + cur.inputs = []; + } + } + return cpy; +}; + +Context.prototype.copyForContinuationCall = function () { + var cpy = copy(this), + cur = cpy, + isReporter = !(this.expression instanceof Array); + if (isReporter) { + this.expression = this.expression.fullCopy(); + this.inputs = []; + while (cur.parentContext) { + cur.parentContext = copy(cur.parentContext); + cur = cur.parentContext; + cur.inputs = []; + } + } + return cpy; +}; + +Context.prototype.prepareContinuationForBinding = function () { + var pos = this.inputs.length, + slot; + this.expression = this.expression.fullCopy(); + slot = this.expression.inputs()[pos]; + if (slot) { + this.inputs = []; + // mark slot containing the call/cc reporter with an identifier + slot.bindingID = 1; + // and remember the number of detected empty slots + this.emptySlots = 1; + } +}; + +// Context accessing: + +Context.prototype.addInput = function (input) { + this.inputs.push(input); +}; + +// Context music + +Context.prototype.stopMusic = function () { + if (this.activeNote) { + this.activeNote.stop(); + this.activeNote = null; + } +}; + +// Context debugging + +Context.prototype.stackSize = function () { + if (!this.parentContext) { + return 1; + } + return 1 + this.parentContext.stackSize(); +}; + +// VariableFrame /////////////////////////////////////////////////////// + +function VariableFrame(parentFrame, owner) { + this.vars = {}; + this.parentFrame = parentFrame || null; + this.owner = owner || null; +} + +VariableFrame.prototype.toString = function () { + return 'a VariableFrame {' + this.names() + '}'; +}; + +VariableFrame.prototype.copy = function () { + var frame = new VariableFrame(this.parentFrame); + frame.vars = copy(this.vars); + return frame; +}; + +VariableFrame.prototype.deepCopy = function () { + // currently unused + var frame; + if (this.parentFrame) { + frame = new VariableFrame(this.parentFrame.deepCopy()); + } else { + frame = new VariableFrame(this.parentFrame); + } + frame.vars = copy(this.vars); + return frame; +}; + +VariableFrame.prototype.find = function (name) { +/* + answer the closest variable frame containing + the specified variable. otherwise throw an exception. +*/ + var frame = this.silentFind(name); + if (frame) {return frame; } + throw new Error( + 'a variable of name \'' + + name + + '\'\ndoes not exist in this context' + ); +}; + +VariableFrame.prototype.silentFind = function (name) { +/* + answer the closest variable frame containing + the specified variable. Otherwise return null. +*/ + if (this.vars[name] !== undefined) { + return this; + } + if (this.parentFrame) { + return this.parentFrame.silentFind(name); + } + return null; +}; + +VariableFrame.prototype.setVar = function (name, value) { +/* + change the specified variable if it exists + else throw an error, because variables need to be + declared explicitly (e.g. through a "script variables" block), + before they can be accessed. +*/ + var frame = this.find(name); + if (frame) { + frame.vars[name] = value; + } +}; + +VariableFrame.prototype.changeVar = function (name, delta) { +/* + change the specified variable if it exists + else throw an error, because variables need to be + declared explicitly (e.g. through a "script variables" block, + before they can be accessed. +*/ + var frame = this.find(name), + value; + if (frame) { + value = parseFloat(frame.vars[name]); + if (isNaN(value)) { + frame.vars[name] = delta; + } else { + frame.vars[name] = value + parseFloat(delta); + } + } +}; + +VariableFrame.prototype.getVar = function (name, upvars) { + var frame = this.silentFind(name), + value, + upvarReference; + if (frame) { + value = frame.vars[name]; + return (value === 0 ? 0 + : value === false ? false + : value === '' ? '' + : value || 0); // don't return null + } + if (typeof name === 'number') { + // empty input with a Binding-ID called without an argument + return ''; + } + if (upvars) { + upvarReference = upvars.find(name); + if (upvarReference) { + return upvarReference.getVar(name); + } + } + throw new Error( + 'a variable of name \'' + + name + + '\'\ndoes not exist in this context' + ); +}; + +VariableFrame.prototype.addVar = function (name, value) { + this.vars[name] = (value === 0 ? 0 : value || null); +}; + +VariableFrame.prototype.deleteVar = function (name) { + var frame = this.find(name); + if (frame) { + delete frame.vars[name]; + } +}; + +// VariableFrame tools + +VariableFrame.prototype.names = function () { + var each, names = []; + for (each in this.vars) { + if (Object.prototype.hasOwnProperty.call(this.vars, each)) { + names.push(each); + } + } + return names; +}; + +VariableFrame.prototype.allNamesDict = function () { + var dict = {}, current = this; + + function addKeysToDict(srcDict, trgtDict) { + var eachKey; + for (eachKey in srcDict) { + if (Object.prototype.hasOwnProperty.call(srcDict, eachKey)) { + trgtDict[eachKey] = eachKey; + } + } + } + + while (current) { + addKeysToDict(current.vars, dict); + current = current.parentFrame; + } + return dict; +}; + +VariableFrame.prototype.allNames = function () { +/* + only show the names of the lexical scope, hybrid scoping is + reserved to the daring ;-) +*/ + var answer = [], each, dict = this.allNamesDict(); + + for (each in dict) { + if (Object.prototype.hasOwnProperty.call(dict, each)) { + answer.push(each); + } + } + return answer; +}; + +// UpvarReference /////////////////////////////////////////////////////////// + +// ... quasi-inherits some features from VariableFrame + +function UpvarReference(parent) { + this.vars = {}; // structure: {upvarName : [varName, varFrame]} + this.parentFrame = parent || null; +} + +UpvarReference.prototype.addReference = function ( + upvarName, + varName, + varFrame +) { + this.vars[upvarName] = [varName, varFrame]; +}; + +UpvarReference.prototype.find = function (name) { +/* + answer the closest upvar reference containing + the specified variable, or answer null. +*/ + if (this.vars[name] !== undefined) { + return this; + } + if (this.parentFrame) { + return this.parentFrame.find(name); + } + return null; +}; + +UpvarReference.prototype.getVar = function (name) { + var varName = this.vars[name][0], + varFrame = this.vars[name][1], + value = varFrame.vars[varName]; + return (value === 0 ? 0 : value || 0); // don't return null +}; + +// UpvarReference tools + +UpvarReference.prototype.toString = function () { + return 'an UpvarReference {' + this.names() + '}'; +}; + +// UpvarReference quasi-inheritance from VariableFrame + +UpvarReference.prototype.names = VariableFrame.prototype.names; +UpvarReference.prototype.allNames = VariableFrame.prototype.allNames; +UpvarReference.prototype.allNamesDict = VariableFrame.prototype.allNamesDict; diff --git a/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/tools.xml b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/tools.xml new file mode 100644 index 0000000..6942dea --- /dev/null +++ b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/tools.xml @@ -0,0 +1 @@ +1datamapmany1data lists11110i1 contcatchtagcontcatchtag diff --git a/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/translating Snap.txt b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/translating Snap.txt new file mode 100644 index 0000000..f4b5ff6 --- /dev/null +++ b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/translating Snap.txt @@ -0,0 +1,143 @@ +***************************** +Translating BYOB4 / Snap! +by Jens Moenig +last changed: 12/10/16 +***************************** + +At this stage of development, Snap! can be translated to any LTR language +maintaining the current order of inputs (formal parameters in blocks). + +Translating Snap! is easy: + + +1. Download + +Download the sources and extract them into a local folder on your +computer: + + + +Use the German translation file (named 'lang-de.js') as template for your +own translations. Start with editing the original file, because that way +you will be able to immediately check the results in your browsers while +you're working on your translation (keep the local copy of snap.html open +in your web browser, and refresh it as you progress with your +translation). + + +2. Edit + +Edit the translation file with a regular text editor, or with your +favorite JavaScript editor. + +In the first non-commented line (the one right below this +note) replace "de" with the two-letter ISO 639-1 code for your language, +e.g. + + fr - French => SnapTranslator.dict.fr = { + it - Italian => SnapTranslator.dict.it = { + pl - Polish => SnapTranslator.dict.pl = { + pt - Portuguese => SnapTranslator.dict.pt = { + es - Spanish => SnapTranslator.dict.es = { + el - Greek => => SnapTranslator.dict.el = { + +etc. (see ) + + +3. Translate + +Then work through the dictionary, replacing the German strings against +your translations. The dictionary is a straight-forward JavaScript ad-hoc +object, for review purposes it should be formatted as follows: + + { + 'English string': + 'Translation string', + 'last key': + } 'last value' + +and you only edit the indented value strings. Note that each key-value +pair needs to be delimited by a comma, but that there shouldn't be a comma +after the last pair (again, just overwrite the template file and you'll be +fine). + +If something doesn't work, or if you're unsure about the formalities you +should check your file with + + + +This will inform you about any missed commas etc. + + +4. Accented characters + +Depending on which text editor and which file encoding you use you can +directly enter special characters (e.g. Umlaut, accented characters) on +your keyboard. However, I've noticed that some browsers may not display +special characters correctly, even if other browsers do. So it's best to +check your results in several browsers. If you want to be on the safe +side, it's even better to escape these characters using Unicode. + + see: + + +5. Block specs: + +At this time your translation of block specs will only work +correctly, if the order of formal parameters and their types +are unchanged. Placeholders for inputs (formal parameters) are +indicated by a preceding % prefix and followed by a type +abbreviation. + +For example: + + 'say %s for %n secs' + +can currently not be changed into + + 'say %n secs long %s' + +and still work as intended. + +Similarly + + 'point towards %dst' + +cannot be changed into + + 'point towards %cst' + +without breaking its functionality. + + +6. Submit + +When you're done, rename the edited file by replacing the "de" part of the +filename with the two-letter ISO 639-1 code for your language, e.g. + + fr - French => lang-fr.js + it - Italian => lang-it.js + pl - Polish => lang-pl.js + pt - Portuguese => lang-pt.js + es - Spanish => lang-es.js + el - Greek => => lang-el.js + +and send it to me for inclusion in the official Snap! distribution. +Once your translation has been included, Your name will the shown in the +"Translators" tab in the "About Snap!" dialog box, and you will be able to +directly launch a translated version of Snap! in your browser by appending + + lang:xx + +to the URL, xx representing your translations two-letter code. + + +7. Known issues + +In some browsers accents or ornaments located in typographic ascenders +above the cap height are currently (partially) cut-off. + + + +Enjoy! +-Jens diff --git a/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/widgets.js b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/widgets.js new file mode 100644 index 0000000..45ef2d1 --- /dev/null +++ b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/widgets.js @@ -0,0 +1,3177 @@ +/* + + widgets.js + + additional GUI elements for morphic.js + + written by Jens Mönig + jens@moenig.org + + Copyright (C) 2013 by Jens Mönig + + This file is part of Snap!. + + Snap! is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + + + prerequisites: + -------------- + needs blocks.js and objects.js + + + I. hierarchy + ------------- + the following tree lists all constructors hierarchically, + indentation indicating inheritance. Refer to this list to get a + contextual overview: + + Morph* + AlignmentMorph + DialogBoxMorph + InputFieldMorph + TriggerMorph* + PushButtonMorph + ToggleButtonMorph + TabMorph + ToggleMorph + ToggleElementMorph + + * from Morphic.js + + + II. toc + ------- + the following list shows the order in which all constructors are + defined. Use this list to locate code in this document: + + PushButtonMorph + ToggleButtonMorph + TabMorph + ToggleMorph + ToggleElementMorph + DialogBoxMorph + AlignmentMorph + InputFieldMorph + +*/ + +// Global settings ///////////////////////////////////////////////////// + +/*global TriggerMorph, modules, Color, Point, BoxMorph, radians, +newCanvas, StringMorph, Morph, TextMorph, nop, detect, StringFieldMorph, +HTMLCanvasElement, fontHeight, SymbolMorph, localize, SpeechBubbleMorph, +ArrowMorph, MenuMorph, isString, isNil, SliderMorph, MorphicPreferences, +ScrollFrameMorph*/ + +modules.widgets = '2013-July-04'; + +var PushButtonMorph; +var ToggleButtonMorph; +var TabMorph; +var ToggleMorph; +var ToggleElementMorph; +var DialogBoxMorph; +var AlignmentMorph; +var InputFieldMorph; + +// PushButtonMorph ///////////////////////////////////////////////////// + +// I am a Button with rounded corners and 3D-ish graphical effects + +// PushButtonMorph inherits from TriggerMorph: + +PushButtonMorph.prototype = new TriggerMorph(); +PushButtonMorph.prototype.constructor = PushButtonMorph; +PushButtonMorph.uber = TriggerMorph.prototype; + +// PushButtonMorph preferences settings: + +PushButtonMorph.prototype.fontSize = 10; +PushButtonMorph.prototype.fontStyle = 'sans-serif'; +PushButtonMorph.prototype.labelColor = new Color(0, 0, 0); +PushButtonMorph.prototype.labelShadowColor = new Color(255, 255, 255); +PushButtonMorph.prototype.labelShadowOffset = new Point(1, 1); + +PushButtonMorph.prototype.color = new Color(220, 220, 220); +PushButtonMorph.prototype.pressColor = new Color(115, 180, 240); +PushButtonMorph.prototype.highlightColor + = PushButtonMorph.prototype.pressColor.lighter(50); +PushButtonMorph.prototype.outlineColor = new Color(30, 30, 30); +PushButtonMorph.prototype.outlineGradient = false; +PushButtonMorph.prototype.contrast = 60; + +PushButtonMorph.prototype.edge = 2; +PushButtonMorph.prototype.corner = 5; +PushButtonMorph.prototype.outline = 1.00001; +PushButtonMorph.prototype.padding = 3; + +// PushButtonMorph instance creation: + +function PushButtonMorph( + target, + action, + labelString, + environment, + hint, + template +) { + this.init( + target, + action, + labelString, + environment, + hint, + template + ); +} + +PushButtonMorph.prototype.init = function ( + target, + action, + labelString, + environment, + hint, + template +) { + // additional properties: + this.is3D = false; // for "flat" design exceptions + this.target = target || null; + this.action = action || null; + this.environment = environment || null; + this.labelString = labelString || null; + this.label = null; + this.labelMinExtent = new Point(0, 0); + this.hint = hint || null; + this.template = template || null; // for pre-computed backbrounds + // if a template is specified, its background images are used as cache + + // initialize inherited properties: + TriggerMorph.uber.init.call(this); + + // override inherited properites: + this.color = PushButtonMorph.prototype.color; + this.drawNew(); + this.fixLayout(); +}; + +// PushButtonMorph layout: + +PushButtonMorph.prototype.fixLayout = function () { + // make sure I at least encompass my label + if (this.label !== null) { + var padding = this.padding * 2 + this.outline * 2 + this.edge * 2; + this.setExtent(new Point( + Math.max(this.label.width(), this.labelMinExtent.x) + padding, + Math.max(this.label instanceof StringMorph ? + this.label.rawHeight() : + this.label.height(), this.labelMinExtent.y) + padding + )); + this.label.setCenter(this.center()); + } +}; + +// PushButtonMorph events + +PushButtonMorph.prototype.mouseDownLeft = function () { + PushButtonMorph.uber.mouseDownLeft.call(this); + if (this.label) { + this.label.setCenter(this.center().add(1)); + } +}; + +PushButtonMorph.prototype.mouseClickLeft = function () { + PushButtonMorph.uber.mouseClickLeft.call(this); + if (this.label) { + this.label.setCenter(this.center()); + } +}; + +PushButtonMorph.prototype.mouseLeave = function () { + PushButtonMorph.uber.mouseLeave.call(this); + if (this.label) { + this.label.setCenter(this.center()); + } +}; + +// PushButtonMorph drawing: + +PushButtonMorph.prototype.outlinePath = BoxMorph.prototype.outlinePath; + +PushButtonMorph.prototype.drawOutline = function (context) { + var outlineStyle, + isFlat = MorphicPreferences.isFlat && !this.is3D; + + if (!this.outline || isFlat) {return null; } + if (this.outlineGradient) { + outlineStyle = context.createLinearGradient( + 0, + 0, + 0, + this.height() + ); + outlineStyle.addColorStop(1, 'white'); + outlineStyle.addColorStop(0, this.outlineColor.darker().toString()); + } else { + outlineStyle = this.outlineColor.toString(); + } + context.fillStyle = outlineStyle; + context.beginPath(); + this.outlinePath( + context, + isFlat ? 0 : this.corner, + 0 + ); + context.closePath(); + context.fill(); +}; + +PushButtonMorph.prototype.drawBackground = function (context, color) { + var isFlat = MorphicPreferences.isFlat && !this.is3D; + + context.fillStyle = color.toString(); + context.beginPath(); + this.outlinePath( + context, + isFlat ? 0 : Math.max(this.corner - this.outline, 0), + this.outline + ); + context.closePath(); + context.fill(); + context.lineWidth = this.outline; +}; + +PushButtonMorph.prototype.drawEdges = function ( + context, + color, + topColor, + bottomColor +) { + if (MorphicPreferences.isFlat && !this.is3D) {return; } + var minInset = Math.max(this.corner, this.outline + this.edge), + w = this.width(), + h = this.height(), + gradient; + + // top: + gradient = context.createLinearGradient( + 0, + this.outline, + 0, + this.outline + this.edge + ); + gradient.addColorStop(0, topColor.toString()); + gradient.addColorStop(1, color.toString()); + + context.strokeStyle = gradient; + context.lineCap = 'round'; + context.lineWidth = this.edge; + context.beginPath(); + context.moveTo(minInset, this.outline + this.edge / 2); + context.lineTo(w - minInset, this.outline + this.edge / 2); + context.stroke(); + + // top-left corner: + gradient = context.createRadialGradient( + this.corner, + this.corner, + Math.max(this.corner - this.outline - this.edge, 0), + this.corner, + this.corner, + Math.max(this.corner - this.outline, 0) + ); + gradient.addColorStop(1, topColor.toString()); + gradient.addColorStop(0, color.toString()); + + context.strokeStyle = gradient; + context.lineCap = 'round'; + context.lineWidth = this.edge; + context.beginPath(); + context.arc( + this.corner, + this.corner, + Math.max(this.corner - this.outline - this.edge / 2, 0), + radians(180), + radians(270), + false + ); + context.stroke(); + + // left: + gradient = context.createLinearGradient( + this.outline, + 0, + this.outline + this.edge, + 0 + ); + gradient.addColorStop(0, topColor.toString()); + gradient.addColorStop(1, color.toString()); + + context.strokeStyle = gradient; + context.lineCap = 'round'; + context.lineWidth = this.edge; + context.beginPath(); + context.moveTo(this.outline + this.edge / 2, minInset); + context.lineTo(this.outline + this.edge / 2, h - minInset); + context.stroke(); + + // bottom: + gradient = context.createLinearGradient( + 0, + h - this.outline, + 0, + h - this.outline - this.edge + ); + gradient.addColorStop(0, bottomColor.toString()); + gradient.addColorStop(1, color.toString()); + + context.strokeStyle = gradient; + context.lineCap = 'round'; + context.lineWidth = this.edge; + context.beginPath(); + context.moveTo(minInset, h - this.outline - this.edge / 2); + context.lineTo(w - minInset, h - this.outline - this.edge / 2); + context.stroke(); + + // bottom-right corner: + gradient = context.createRadialGradient( + w - this.corner, + h - this.corner, + Math.max(this.corner - this.outline - this.edge, 0), + w - this.corner, + h - this.corner, + Math.max(this.corner - this.outline, 0) + ); + gradient.addColorStop(1, bottomColor.toString()); + gradient.addColorStop(0, color.toString()); + + context.strokeStyle = gradient; + context.lineCap = 'round'; + context.lineWidth = this.edge; + context.beginPath(); + context.arc( + w - this.corner, + h - this.corner, + Math.max(this.corner - this.outline - this.edge / 2, 0), + radians(0), + radians(90), + false + ); + context.stroke(); + + // right: + gradient = context.createLinearGradient( + w - this.outline, + 0, + w - this.outline - this.edge, + 0 + ); + gradient.addColorStop(0, bottomColor.toString()); + gradient.addColorStop(1, color.toString()); + + context.strokeStyle = gradient; + context.lineCap = 'round'; + context.lineWidth = this.edge; + context.beginPath(); + context.moveTo(w - this.outline - this.edge / 2, minInset); + context.lineTo(w - this.outline - this.edge / 2, h - minInset); + context.stroke(); +}; + +PushButtonMorph.prototype.createBackgrounds = function () { + var context, + ext = this.extent(); + + if (this.template) { // take the backgrounds images from the template + this.image = this.template.image; + this.normalImage = this.template.normalImage; + this.highlightImage = this.template.highlightImage; + this.pressImage = this.template.pressImage; + return null; + } + + this.normalImage = newCanvas(ext); + context = this.normalImage.getContext('2d'); + this.drawOutline(context); + this.drawBackground(context, this.color); + this.drawEdges( + context, + this.color, + this.color.lighter(this.contrast), + this.color.darker(this.contrast) + ); + + this.highlightImage = newCanvas(ext); + context = this.highlightImage.getContext('2d'); + this.drawOutline(context); + this.drawBackground(context, this.highlightColor); + this.drawEdges( + context, + this.highlightColor, + this.highlightColor.lighter(this.contrast), + this.highlightColor.darker(this.contrast) + ); + + this.pressImage = newCanvas(ext); + context = this.pressImage.getContext('2d'); + this.drawOutline(context); + this.drawBackground(context, this.pressColor); + this.drawEdges( + context, + this.pressColor, + this.pressColor.darker(this.contrast), + this.pressColor.lighter(this.contrast) + ); + + this.image = this.normalImage; +}; + +PushButtonMorph.prototype.createLabel = function () { + var shading = !MorphicPreferences.isFlat || this.is3D; + + if (this.label !== null) { + this.label.destroy(); + } + if (this.labelString instanceof SymbolMorph) { + this.label = this.labelString.fullCopy(); + if (shading) { + this.label.shadowOffset = this.labelShadowOffset; + this.label.shadowColor = this.labelShadowColor; + } + this.label.color = this.labelColor; + this.label.drawNew(); + } else { + this.label = new StringMorph( + localize(this.labelString), + this.fontSize, + this.fontStyle, + true, + false, + false, + shading ? this.labelShadowOffset : null, + this.labelShadowColor, + this.labelColor + ); + } + this.add(this.label); +}; + +// ToggleButtonMorph /////////////////////////////////////////////////////// + +/* + I am a two-state PushButton. When my state is "true" I keep my "pressed" + background color. I can also be set to not auto-layout my bounds, in + which case my label will left-align. +*/ + +// ToggleButtonMorph inherits from PushButtonMorph: + +ToggleButtonMorph.prototype = new PushButtonMorph(); +ToggleButtonMorph.prototype.constructor = ToggleButtonMorph; +ToggleButtonMorph.uber = PushButtonMorph.prototype; + +// ToggleButton settings + +ToggleButtonMorph.prototype.contrast = 30; + +// ToggleButtonMorph instance creation: + +function ToggleButtonMorph( + colors, // color overrides, : [normal, highlight, pressed] + target, + action, // a toggle function + labelString, + query, // predicate/selector + environment, + hint, + template, // optional, for cached background images + minWidth, // optional, if specified label will left-align + hasPreview, // show press color on left edge (e.g. category) + isPicture // treat label as picture, i.e. don't apply typography +) { + this.init( + colors, + target, + action, + labelString, + query, + environment, + hint, + template, + minWidth, + hasPreview, + isPicture + ); +} + +ToggleButtonMorph.prototype.init = function ( + colors, + target, + action, + labelString, + query, + environment, + hint, + template, + minWidth, + hasPreview, + isPicture +) { + // additional properties: + this.state = false; + this.query = query || function () {return true; }; + this.minWidth = minWidth || null; + this.hasPreview = hasPreview || false; + this.isPicture = isPicture || false; + this.trueStateLabel = null; + + // initialize inherited properties: + ToggleButtonMorph.uber.init.call( + this, + target, + action, + labelString, + environment, + hint, + template + ); + + // override default colors if others are specified + if (colors) { + this.color = colors[0]; + this.highlightColor = colors[1]; + this.pressColor = colors[2]; + } + + this.refresh(); + this.drawNew(); +}; + +// ToggleButtonMorph events + +ToggleButtonMorph.prototype.mouseEnter = function () { + if (!this.state) { + this.image = this.highlightImage; + this.changed(); + } + if (this.hint) { + this.bubbleHelp(this.hint); + } +}; + +ToggleButtonMorph.prototype.mouseLeave = function () { + if (!this.state) { + this.image = this.normalImage; + this.changed(); + } + if (this.hint) { + this.world().hand.destroyTemporaries(); + } +}; + +ToggleButtonMorph.prototype.mouseDownLeft = function () { + if (!this.state) { + this.image = this.pressImage; + this.changed(); + } +}; + +ToggleButtonMorph.prototype.mouseClickLeft = function () { + if (!this.state) { + this.image = this.highlightImage; + this.changed(); + } + this.trigger(); // allow me to be triggered again to force-update others +}; + +// ToggleButtonMorph action + +ToggleButtonMorph.prototype.trigger = function () { + ToggleButtonMorph.uber.trigger.call(this); + this.refresh(); +}; + +ToggleButtonMorph.prototype.refresh = function () { +/* + if query is a function: + execute the query with target as environment (can be null) + for lambdafied (inline) actions + + else if query is a String: + treat it as function property of target and execute it + for selector-like queries +*/ + if (typeof this.query === 'function') { + this.state = this.query.call(this.target); + } else { // assume it's a String + this.state = this.target[this.query](); + } + if (this.state) { + this.image = this.pressImage; + if (this.trueStateLabel) { + this.label.hide(); + this.trueStateLabel.show(); + } + } else { + this.image = this.normalImage; + if (this.trueStateLabel) { + this.label.show(); + this.trueStateLabel.hide(); + } + } + this.changed(); +}; + +// ToggleButtonMorph layout: + +ToggleButtonMorph.prototype.fixLayout = function () { + if (this.label !== null) { + var lw = Math.max(this.label.width(), this.labelMinExtent.x), + padding = this.padding * 2 + this.outline * 2 + this.edge * 2; + this.setExtent(new Point( + (this.minWidth ? + Math.max(this.minWidth, lw) + padding + : lw + padding), + Math.max(this.label instanceof StringMorph ? + this.label.rawHeight() : + this.label.height(), this.labelMinExtent.y) + padding + )); + this.label.setCenter(this.center()); + if (this.trueStateLabel) { + this.trueStateLabel.setCenter(this.center()); + } + if (this.minWidth) { // left-align along my corner + this.label.setLeft( + this.left() + + this.outline + + this.edge + + this.corner + + this.padding + ); + } + } +}; + +// ToggleButtonMorph drawing + +ToggleButtonMorph.prototype.createBackgrounds = function () { +/* + basically the same as inherited from PushButtonMorph, except for + not inverting the pressImage 3D-ish border (because it stays that way), + and optionally coloring the left edge in the press-color, previewing + the selection color (e.g. in the case of Scratch palette-category + selector. the latter is done in the drawEdges() method. +*/ + var context, + ext = this.extent(); + + if (this.template) { // take the backgrounds images from the template + this.image = this.template.image; + this.normalImage = this.template.normalImage; + this.highlightImage = this.template.highlightImage; + this.pressImage = this.template.pressImage; + return null; + } + + this.normalImage = newCanvas(ext); + context = this.normalImage.getContext('2d'); + this.drawOutline(context); + this.drawBackground(context, this.color); + this.drawEdges( + context, + this.color, + this.color.lighter(this.contrast), + this.color.darker(this.contrast) + ); + + this.highlightImage = newCanvas(ext); + context = this.highlightImage.getContext('2d'); + this.drawOutline(context); + this.drawBackground(context, this.highlightColor); + this.drawEdges( + context, + this.highlightColor, + this.highlightColor.lighter(this.contrast), + this.highlightColor.darker(this.contrast) + ); + + // note: don't invert the 3D-ish edges for pressedImage, because + // it will stay that way, and should not look inverted (or should it?) + this.pressImage = newCanvas(ext); + context = this.pressImage.getContext('2d'); + this.drawOutline(context); + this.drawBackground(context, this.pressColor); + this.drawEdges( + context, + this.pressColor, + this.pressColor.lighter(40), + this.pressColor.darker(40) + ); + + this.image = this.normalImage; +}; + +ToggleButtonMorph.prototype.drawEdges = function ( + context, + color, + topColor, + bottomColor +) { + var gradient; + + ToggleButtonMorph.uber.drawEdges.call( + this, + context, + color, + topColor, + bottomColor + ); + + if (this.hasPreview) { // indicate the possible selection color + if (MorphicPreferences.isFlat && !this.is3D) { + context.fillStyle = this.pressColor.toString(); + context.fillRect( + this.outline, + this.outline, + this.corner, + this.height() - this.outline * 2 + ); + return; + } + gradient = context.createLinearGradient( + 0, + 0, + this.corner, + 0 + ); + gradient.addColorStop(0, this.pressColor.lighter(40).toString()); + gradient.addColorStop(1, this.pressColor.darker(40).toString()); + context.fillStyle = gradient; // this.pressColor.toString(); + context.beginPath(); + this.previewPath( + context, + Math.max(this.corner - this.outline, 0), + this.outline + ); + context.closePath(); + context.fill(); + } +}; + +ToggleButtonMorph.prototype.previewPath = function (context, radius, inset) { + var offset = radius + inset, + h = this.height(); + + // top left: + context.arc( + offset, + offset, + radius, + radians(-180), + radians(-90), + false + ); + // bottom left: + context.arc( + offset, + h - offset, + radius, + radians(90), + radians(180), + false + ); +}; + +ToggleButtonMorph.prototype.createLabel = function () { + var shading = !MorphicPreferences.isFlat || this.is3D, + none = new Point(); + + if (this.label !== null) { + this.label.destroy(); + } + if (this.trueStateLabel !== null) { + this.trueStateLabel.destroy(); + } + if (this.labelString instanceof Array && this.labelString.length === 2) { + if (this.labelString[0] instanceof SymbolMorph) { + this.label = this.labelString[0].fullCopy(); + this.trueStateLabel = this.labelString[1].fullCopy(); + if (!this.isPicture) { + this.label.shadowOffset = shading ? + this.labelShadowOffset : none; + this.label.shadowColor = this.labelShadowColor; + this.label.color = this.labelColor; + this.label.drawNew(); + + this.trueStateLabel.shadowOffset = shading ? + this.labelShadowOffset : none; + this.trueStateLabel.shadowColor = this.labelShadowColor; + this.trueStateLabel.color = this.labelColor; + this.trueStateLabel.drawNew(); + } + } else if (this.labelString[0] instanceof Morph) { + this.label = this.labelString[0].fullCopy(); + this.trueStateLabel = this.labelString[1].fullCopy(); + } else { + this.label = new StringMorph( + localize(this.labelString[0]), + this.fontSize, + this.fontStyle, + true, + false, + false, + shading ? this.labelShadowOffset : null, + this.labelShadowColor, + this.labelColor + ); + this.trueStateLabel = new StringMorph( + localize(this.labelString[1]), + this.fontSize, + this.fontStyle, + true, + false, + false, + shading ? this.labelShadowOffset : null, + this.labelShadowColor, + this.labelColor + ); + } + } else { + if (this.labelString instanceof SymbolMorph) { + this.label = this.labelString.fullCopy(); + if (!this.isPicture) { + this.label.shadowOffset = shading ? + this.labelShadowOffset : none; + this.label.shadowColor = this.labelShadowColor; + this.label.color = this.labelColor; + this.label.drawNew(); + } + } else if (this.labelString instanceof Morph) { + this.label = this.labelString.fullCopy(); + } else { + this.label = new StringMorph( + localize(this.labelString), + this.fontSize, + this.fontStyle, + true, + false, + false, + shading ? this.labelShadowOffset : none, + this.labelShadowColor, + this.labelColor + ); + } + } + this.add(this.label); + if (this.trueStateLabel) { + this.add(this.trueStateLabel); + } +}; + +// ToggleButtonMorph hiding and showing: + +/* + override the inherited behavior to recursively hide/show all + children, so that my instances get restored correctly when + hiding/showing my parent. +*/ + +ToggleButtonMorph.prototype.hide = function () { + this.isVisible = false; + this.changed(); +}; + +ToggleButtonMorph.prototype.show = function () { + this.isVisible = true; + this.changed(); +}; + +// TabMorph /////////////////////////////////////////////////////// + +// TabMorph inherits from ToggleButtonMorph: + +TabMorph.prototype = new ToggleButtonMorph(); +TabMorph.prototype.constructor = TabMorph; +TabMorph.uber = ToggleButtonMorph.prototype; + +// TabMorph instance creation: + +function TabMorph( + colors, // color overrides, : [normal, highlight, pressed] + target, + action, // a toggle function + labelString, + query, // predicate/selector + environment, + hint +) { + this.init( + colors, + target, + action, + labelString, + query, + environment, + hint + ); +} + +// TabMorph layout: + +TabMorph.prototype.fixLayout = function () { + if (this.label !== null) { + this.setExtent(new Point( + this.label.width() + + this.padding * 2 + + this.corner * 3 + + this.edge * 2, + (this.label instanceof StringMorph ? + this.label.rawHeight() : this.label.height()) + + this.padding * 2 + + this.edge + )); + this.label.setCenter(this.center()); + } +}; + +// TabMorph action: + +TabMorph.prototype.refresh = function () { + if (this.state) { // bring to front + if (this.parent) { + this.parent.add(this); + } + } + TabMorph.uber.refresh.call(this); +}; + +// TabMorph drawing: + +TabMorph.prototype.drawBackground = function (context, color) { + var w = this.width(), + h = this.height(), + c = this.corner; + + context.fillStyle = color.toString(); + context.beginPath(); + context.moveTo(0, h); + context.bezierCurveTo(c, h, c, 0, c * 2, 0); + context.lineTo(w - c * 2, 0); + context.bezierCurveTo(w - c, 0, w - c, h, w, h); + context.closePath(); + context.fill(); +}; + +TabMorph.prototype.drawOutline = function () { + nop(); +}; + +TabMorph.prototype.drawEdges = function ( + context, + color, + topColor, + bottomColor +) { + if (MorphicPreferences.isFlat && !this.is3D) {return; } + + var w = this.width(), + h = this.height(), + c = this.corner, + e = this.edge, + eh = e / 2, + gradient; + + nop(color); // argument not needed here + + gradient = context.createLinearGradient(0, 0, w, 0); + gradient.addColorStop(0, topColor.toString()); + gradient.addColorStop(1, bottomColor.toString()); + + context.strokeStyle = gradient; + context.lineCap = 'round'; + context.lineWidth = e; + + context.beginPath(); + context.moveTo(0, h + eh); + context.bezierCurveTo(c, h, c, 0, c * 2, eh); + context.lineTo(w - c * 2, eh); + context.bezierCurveTo(w - c, 0, w - c, h, w, h + eh); + context.stroke(); +}; + +// ToggleMorph /////////////////////////////////////////////////////// + +/* + I am a PushButton which toggles a check mark ( becoming check box) + or a bullet (becoming a radio button). I can have both or either an + additional label and an additional pictogram, whereas the pictogram + can be either an instance of (any) Morph, in which case the pictogram + will be an interactive toggle itself or a Canvas, in which case it + is just going to be a picture. +*/ + +// ToggleMorph inherits from PushButtonMorph: + +ToggleMorph.prototype = new PushButtonMorph(); +ToggleMorph.prototype.constructor = ToggleMorph; +ToggleMorph.uber = PushButtonMorph.prototype; + +// ToggleMorph instance creation: + +function ToggleMorph( + style, // 'checkbox' or 'radiobutton' + target, + action, // a toggle function + labelString, + query, // predicate/selector + environment, + hint, + template, + element, // optional Morph or Canvas to display + builder // method which constructs the element (only for Morphs) +) { + this.init( + style, + target, + action, + labelString, + query, + environment, + hint, + template, + element, + builder + ); +} + +ToggleMorph.prototype.init = function ( + style, + target, + action, + labelString, + query, + environment, + hint, + template, + element, + builder +) { + // additional properties: + this.padding = 1; + style = style || 'checkbox'; + this.corner = (style === 'checkbox' ? + 0 : fontHeight(this.fontSize) / 2 + this.outline + this.padding); + this.state = false; + this.query = query || function () {return true; }; + this.tick = null; + this.captionString = labelString || null; + this.labelAlignment = 'right'; + this.element = element || null; + this.builder = builder || null; + this.toggleElement = null; + + // initialize inherited properties: + ToggleMorph.uber.init.call( + this, + target, + action, + (style === 'checkbox' ? '\u2713' : '\u25CF'), + environment, + hint, + template + ); + this.refresh(); + this.drawNew(); +}; + +// ToggleMorph layout: + +ToggleMorph.prototype.fixLayout = function () { + var padding = this.padding * 2 + this.outline * 2, + y; + if (this.tick !== null) { + this.silentSetHeight(this.tick.rawHeight() + padding); + this.silentSetWidth(this.tick.width() + padding); + + this.setExtent(new Point( + Math.max(this.width(), this.height()), + Math.max(this.width(), this.height()) + )); + this.tick.setCenter(this.center()); + } + if (this.state) { + this.tick.show(); + } else { + this.tick.hide(); + } + if (this.toggleElement && (this.labelAlignment === 'right')) { + y = this.top() + (this.height() - this.toggleElement.height()) / 2; + this.toggleElement.setPosition(new Point( + this.right() + padding, + y + )); + } + if (this.label !== null) { + y = this.top() + (this.height() - this.label.height()) / 2; + if (this.labelAlignment === 'right') { + this.label.setPosition(new Point( + this.toggleElement ? + this.toggleElement instanceof ToggleElementMorph ? + this.toggleElement.right() + : this.toggleElement.right() + padding + : this.right() + padding, + y + )); + } else { + this.label.setPosition(new Point( + this.left() - this.label.width() - padding, + y + )); + } + } +}; + +ToggleMorph.prototype.createLabel = function () { + var shading = !MorphicPreferences.isFlat || this.is3D; + + if (this.label === null) { + if (this.captionString) { + this.label = new TextMorph( + localize(this.captionString), + this.fontSize, + this.fontStyle, + true + ); + this.add(this.label); + } + } + if (this.tick === null) { + this.tick = new StringMorph( + localize(this.labelString), + this.fontSize, + this.fontStyle, + true, + false, + false, + shading ? new Point(1, 1) : null, + new Color(240, 240, 240) + ); + this.add(this.tick); + } + if (this.toggleElement === null) { + if (this.element) { + if (this.element instanceof Morph) { + this.toggleElement = new ToggleElementMorph( + this.target, + this.action, + this.element, + this.query, + this.environment, + this.hint, + this.builder + ); + } else if (this.element instanceof HTMLCanvasElement) { + this.toggleElement = new Morph(); + this.toggleElement.silentSetExtent(new Point( + this.element.width, + this.element.height + )); + this.toggleElement.image = this.element; + } + this.add(this.toggleElement); + } + } +}; + +// ToggleMorph action: + +ToggleMorph.prototype.trigger = function () { + ToggleMorph.uber.trigger.call(this); + this.refresh(); +}; + +ToggleMorph.prototype.refresh = function () { + /* + if query is a function: + execute the query with target as environment (can be null) + for lambdafied (inline) actions + + else if query is a String: + treat it as function property of target and execute it + for selector-like queries + */ + if (typeof this.query === 'function') { + this.state = this.query.call(this.target); + } else { // assume it's a String + this.state = this.target[this.query](); + } + if (this.state) { + this.tick.show(); + } else { + this.tick.hide(); + } + if (this.toggleElement && this.toggleElement.refresh) { + this.toggleElement.refresh(); + } +}; + +// ToggleMorph events + +ToggleMorph.prototype.mouseDownLeft = function () { + PushButtonMorph.uber.mouseDownLeft.call(this); + if (this.tick) { + this.tick.setCenter(this.center().add(1)); + } +}; + +ToggleMorph.prototype.mouseClickLeft = function () { + PushButtonMorph.uber.mouseClickLeft.call(this); + if (this.tick) { + this.tick.setCenter(this.center()); + } +}; + +ToggleMorph.prototype.mouseLeave = function () { + PushButtonMorph.uber.mouseLeave.call(this); + if (this.tick) { + this.tick.setCenter(this.center()); + } +}; + +// ToggleMorph hiding and showing: + +/* + override the inherited behavior to recursively hide/show all + children, so that my instances get restored correctly when + hiding/showing my parent. +*/ + +ToggleMorph.prototype.hide = ToggleButtonMorph.prototype.hide; + +ToggleMorph.prototype.show = ToggleButtonMorph.prototype.show; + +// ToggleElementMorph ///////////////////////////////////////////////////// +/* + I am a picture of a Morph ("element") which acts as a toggle button. + I am different from ToggleButton in that I neither create a label nor + draw button outlines. Instead I display my element morph in specified + contrasts of a given color, symbolizing whether it is selected or not +*/ + +// ToggleElementMorph inherits from TriggerMorph: + +ToggleElementMorph.prototype = new TriggerMorph(); +ToggleElementMorph.prototype.constructor = ToggleElementMorph; +ToggleElementMorph.uber = TriggerMorph.prototype; + +// ToggleElementMorph preferences settings + +ToggleElementMorph.prototype.contrast = 50; +ToggleElementMorph.prototype.shadowOffset = new Point(2, 2); +ToggleElementMorph.prototype.shadowAlpha = 0.6; +ToggleElementMorph.prototype.fontSize = 10; // only for (optional) labels +ToggleElementMorph.prototype.inactiveColor = new Color(180, 180, 180); + +// ToggleElementMorph instance creation: + +function ToggleElementMorph( + target, + action, + element, + query, + environment, + hint, + builder, + labelString +) { + this.init( + target, + action, + element, + query, + environment, + hint, + builder, + labelString + ); +} + +ToggleElementMorph.prototype.init = function ( + target, + action, + element, // mandatory + query, + environment, + hint, + builder, // optional function name that rebuilds the element + labelString +) { + // additional properties: + this.target = target || null; + this.action = action || null; + this.element = element; + this.query = query || function () {return true; }; + this.environment = environment || null; + this.hint = hint || null; + this.builder = builder || 'nop'; + this.captionString = labelString || null; + this.labelAlignment = 'right'; + this.state = false; + + // initialize inherited properties: + TriggerMorph.uber.init.call(this); + + // override inherited properties: + this.color = element.color; + this.createLabel(); +}; + +// ToggleElementMorph drawing: + +ToggleElementMorph.prototype.createBackgrounds = function () { + var shading = !MorphicPreferences.isFlat || this.is3D; + + this.color = this.element.color; + this.element.removeShadow(); + this.element[this.builder](); + if (shading) { + this.element.addShadow(this.shadowOffset, this.shadowAlpha); + } + this.silentSetExtent(this.element.fullBounds().extent()); // w/ shadow + this.pressImage = this.element.fullImage(); + + this.element.removeShadow(); + this.element.setColor(this.inactiveColor); + this.element[this.builder](this.contrast); + if (shading) { + this.element.addShadow(this.shadowOffset, 0); + } + this.normalImage = this.element.fullImage(); + + this.element.removeShadow(); + this.element.setColor(this.color.lighter(this.contrast)); + this.element[this.builder](this.contrast); + if (shading) { + this.element.addShadow(this.shadowOffset, this.shadowAlpha); + } + this.highlightImage = this.element.fullImage(); + + this.element.removeShadow(); + this.element.setColor(this.color); + this.element[this.builder](); + this.image = this.normalImage; +}; + +ToggleElementMorph.prototype.setColor = function (aColor) { + this.element.setColor(aColor); + this.createBackgrounds(); + this.refresh(); +}; + +// ToggleElementMorph layout: + +ToggleElementMorph.prototype.createLabel = function () { + var y; + if (this.captionString) { + this.label = new StringMorph( + this.captionString, + this.fontSize, + this.fontStyle, + true + ); + this.add(this.label); + y = this.top() + (this.height() - this.label.height()) / 2; + if (this.labelAlignment === 'right') { + this.label.setPosition(new Point( + this.right(), + y + )); + } else { + this.label.setPosition(new Point( + this.left() - this.label.width(), + y + )); + } + } +}; + +// ToggleElementMorph action + +ToggleElementMorph.prototype.trigger + = ToggleButtonMorph.prototype.trigger; + +ToggleElementMorph.prototype.refresh + = ToggleButtonMorph.prototype.refresh; + +// ToggleElementMorph events + +ToggleElementMorph.prototype.mouseEnter + = ToggleButtonMorph.prototype.mouseEnter; + +ToggleElementMorph.prototype.mouseLeave + = ToggleButtonMorph.prototype.mouseLeave; + +ToggleElementMorph.prototype.mouseDownLeft + = ToggleButtonMorph.prototype.mouseDownLeft; + +ToggleElementMorph.prototype.mouseClickLeft + = ToggleButtonMorph.prototype.mouseClickLeft; + +// DialogBoxMorph ///////////////////////////////////////////////////// + +/* + I am a DialogBox frame. + + Note: + ----- + my key property keeps track of my purpose to prevent multiple instances + on the same or similar objects +*/ + +// DialogBoxMorph inherits from Morph: + +DialogBoxMorph.prototype = new Morph(); +DialogBoxMorph.prototype.constructor = DialogBoxMorph; +DialogBoxMorph.uber = Morph.prototype; + +// DialogBoxMorph preferences settings: + +DialogBoxMorph.prototype.fontSize = 12; +DialogBoxMorph.prototype.titleFontSize = 14; +DialogBoxMorph.prototype.fontStyle = 'sans-serif'; + +DialogBoxMorph.prototype.color = PushButtonMorph.prototype.color; +DialogBoxMorph.prototype.titleTextColor = new Color(255, 255, 255); +DialogBoxMorph.prototype.titleBarColor + = PushButtonMorph.prototype.pressColor; + +DialogBoxMorph.prototype.contrast = 40; + +DialogBoxMorph.prototype.corner = 12; +DialogBoxMorph.prototype.padding = 14; +DialogBoxMorph.prototype.titlePadding = 6; + +DialogBoxMorph.prototype.buttonContrast = 50; +DialogBoxMorph.prototype.buttonFontSize = 12; +DialogBoxMorph.prototype.buttonCorner = 12; +DialogBoxMorph.prototype.buttonEdge = 6; +DialogBoxMorph.prototype.buttonPadding = 0; +DialogBoxMorph.prototype.buttonOutline = 3; +DialogBoxMorph.prototype.buttonOutlineColor + = PushButtonMorph.prototype.color; +DialogBoxMorph.prototype.buttonOutlineGradient = true; + +DialogBoxMorph.prototype.instances = {}; // prevent multiple instances + +// DialogBoxMorph instance creation: + +function DialogBoxMorph(target, action, environment) { + this.init(target, action, environment); +} + +DialogBoxMorph.prototype.init = function (target, action, environment) { + // additional properties: + this.is3D = false; // for "flat" design exceptions + this.target = target || null; + this.action = action || null; + this.environment = environment || null; + this.key = null; // keep track of my purpose to prevent mulitple instances + + this.labelString = null; + this.label = null; + this.head = null; + this.body = null; + this.buttons = null; + + // initialize inherited properties: + DialogBoxMorph.uber.init.call(this); + + // override inherited properites: + this.isDraggable = true; + this.color = PushButtonMorph.prototype.color; + this.createLabel(); + this.createButtons(); + this.setExtent(new Point(300, 150)); +}; + +// DialogBoxMorph ops +DialogBoxMorph.prototype.inform = function ( + title, + textString, + world, + pic +) { + var txt = new TextMorph( + textString, + this.fontSize, + this.fontStyle, + true, + false, + 'center', + null, + null, + MorphicPreferences.isFlat ? null : new Point(1, 1), + new Color(255, 255, 255) + ); + + if (!this.key) { + this.key = 'inform' + title + textString; + } + + this.labelString = title; + this.createLabel(); + if (pic) {this.setPicture(pic); } + if (textString) { + this.addBody(txt); + } + this.addButton('ok', 'OK'); + this.drawNew(); + this.fixLayout(); + this.popUp(world); +}; + +DialogBoxMorph.prototype.askYesNo = function ( + title, + textString, + world, + pic +) { + var txt = new TextMorph( + textString, + this.fontSize, + this.fontStyle, + true, + false, + 'center', + null, + null, + MorphicPreferences.isFlat ? null : new Point(1, 1), + new Color(255, 255, 255) + ); + + if (!this.key) { + this.key = 'decide' + title + textString; + } + + this.labelString = title; + this.createLabel(); + if (pic) {this.setPicture(pic); } + this.addBody(txt); + this.addButton('ok', 'Yes'); + this.addButton('cancel', 'No'); + this.fixLayout(); + this.drawNew(); + this.fixLayout(); + this.popUp(world); +}; + +DialogBoxMorph.prototype.prompt = function ( + title, + defaultString, + world, + pic, + choices, // optional dictionary for drop-down of choices + isReadOnly, // optional when using choices + isNumeric, // optional + sliderMin, // optional for numeric sliders + sliderMax, // optional for numeric sliders + sliderAction // optional single-arg function for numeric slider +) { + var sld, + head, + txt = new InputFieldMorph( + defaultString, + isNumeric || false, // numeric? + choices || null, // drop-down dict, optional + choices ? isReadOnly || false : false + ); + txt.setWidth(250); + if (isNumeric) { + if (pic) { + head = new AlignmentMorph('column', this.padding); + pic.setPosition(head.position()); + head.add(pic); + } + if (!isNil(sliderMin) && !isNil(sliderMax)) { + sld = new SliderMorph( + sliderMin * 100, + sliderMax * 100, + parseFloat(defaultString) * 100, + (sliderMax - sliderMin) / 10 * 100, + 'horizontal' + ); + sld.alpha = 1; + sld.color = this.color.lighter(50); + sld.setHeight(txt.height() * 0.7); + sld.setWidth(txt.width()); + sld.action = function (num) { + if (sliderAction) { + sliderAction(num / 100); + } + txt.setContents(num / 100); + txt.edit(); + }; + if (!head) { + head = new AlignmentMorph('column', this.padding); + } + head.add(sld); + } + if (head) { + head.fixLayout(); + this.setPicture(head); + head.fixLayout(); + } + } else { + if (pic) {this.setPicture(pic); } + } + + this.reactToChoice = function (inp) { + if (sld) { + sld.value = inp * 100; + sld.drawNew(); + sld.changed(); + } + if (sliderAction) { + sliderAction(inp); + } + }; + + txt.reactToKeystroke = function () { + var inp = txt.getValue(); + if (sld) { + inp = Math.max(inp, sliderMin); + sld.value = inp * 100; + sld.drawNew(); + sld.changed(); + } + if (sliderAction) { + sliderAction(inp); + } + }; + + this.labelString = title; + this.createLabel(); + + if (!this.key) { + this.key = 'prompt' + title + defaultString; + } + + this.addBody(txt); + txt.drawNew(); + this.addButton('ok', 'OK'); + this.addButton('cancel', 'Cancel'); + this.fixLayout(); + this.drawNew(); + this.fixLayout(); + this.popUp(world); +}; + +DialogBoxMorph.prototype.promptCode = function ( + title, + defaultString, + world, + pic, + instructions +) { + var frame = new ScrollFrameMorph(), + text = new TextMorph(defaultString || ''), + bdy = new AlignmentMorph('column', this.padding), + size = pic ? Math.max(pic.width, 400) : 400; + + this.getInput = function () { + return text.text; + }; + + function remarkText(string) { + return new TextMorph( + string, + 10, + null, // style + false, // bold + null, // italic + null, // alignment + null, // width + null, // font name + MorphicPreferences.isFlat ? null : new Point(1, 1), + new Color(255, 255, 255) // shadowColor + ); + } + + frame.padding = 6; + frame.setWidth(size); + frame.acceptsDrops = false; + frame.contents.acceptsDrops = false; + + text.fontName = 'monospace'; + text.fontStyle = 'monospace'; + text.fontSize = 11; + text.setPosition(frame.topLeft().add(frame.padding)); + text.enableSelecting(); + text.isEditable = true; + + frame.setHeight(size / 4); + frame.fixLayout = nop; + frame.edge = InputFieldMorph.prototype.edge; + frame.fontSize = InputFieldMorph.prototype.fontSize; + frame.typeInPadding = InputFieldMorph.prototype.typeInPadding; + frame.contrast = InputFieldMorph.prototype.contrast; + frame.drawNew = InputFieldMorph.prototype.drawNew; + frame.drawRectBorder = InputFieldMorph.prototype.drawRectBorder; + + frame.addContents(text); + text.drawNew(); + + if (pic) {this.setPicture(pic); } + + this.labelString = title; + this.createLabel(); + + if (!this.key) { + this.key = 'promptCode' + title + defaultString; + } + + bdy.setColor(this.color); + bdy.add(frame); + if (instructions) { + bdy.add(remarkText(instructions)); + } + bdy.fixLayout(); + + this.addBody(bdy); + frame.drawNew(); + bdy.drawNew(); + + this.addButton('ok', 'OK'); + this.addButton('cancel', 'Cancel'); + this.fixLayout(); + this.drawNew(); + this.fixLayout(); + this.popUp(world); + text.edit(); +}; + +DialogBoxMorph.prototype.promptCredentials = function ( + title, + purpose, + tosURL, + tosLabel, + prvURL, + prvLabel, + checkBoxLabel, + world, + pic, + msg +) { + var usr = new InputFieldMorph(), + bmn, + byr, + emlLabel, + eml = new InputFieldMorph(), + pw1 = new InputFieldMorph(), + pw2 = new InputFieldMorph(), + opw = new InputFieldMorph(), + agree = false, + chk, + dof = new AlignmentMorph('row', 4), + mCol = new AlignmentMorph('column', 2), + yCol = new AlignmentMorph('column', 2), + inp = new AlignmentMorph('column', 2), + lnk = new AlignmentMorph('row', 4), + bdy = new AlignmentMorph('column', this.padding), + years = {}, + currentYear = new Date().getFullYear(), + firstYear = currentYear - 20, + myself = this; + + function labelText(string) { + return new TextMorph( + string, + 10, + null, // style + false, // bold + null, // italic + null, // alignment + null, // width + null, // font name + MorphicPreferences.isFlat ? null : new Point(1, 1), + new Color(255, 255, 255) // shadowColor + ); + } + + function linkButton(label, url) { + var btn = new PushButtonMorph( + myself, + function () { + window.open(url); + }, + ' ' + localize(label) + ' ' + ); + btn.fontSize = 10; + btn.corner = myself.buttonCorner; + btn.edge = myself.buttonEdge; + btn.outline = myself.buttonOutline; + btn.outlineColor = myself.buttonOutlineColor; + btn.outlineGradient = myself.buttonOutlineGradient; + btn.padding = myself.buttonPadding; + btn.contrast = myself.buttonContrast; + btn.drawNew(); + btn.fixLayout(); + return btn; + } + + function age() { + var today = new Date().getFullYear() + new Date().getMonth() / 12, + year = +byr.getValue() || 0, + monthName = bmn.getValue(), + month, + birthday; + if (monthName instanceof Array) { // translatable + monthName = monthName[0]; + } + if (isNaN(year)) { + year = 0; + } + month = [ + 'January', + 'February', + 'March', + 'April', + 'May', + 'June', + 'July', + 'August', + 'September', + 'October', + 'November', + 'December' + ].indexOf(monthName); + if (isNaN(month)) { + month = 0; + } + birthday = year + month / 12; + return today - birthday; + } + + bmn = new InputFieldMorph( + null, // text + false, // numeric? + { + 'January' : ['January'], + 'February' : ['February'], + 'March' : ['March'], + 'April' : ['April'], + 'May' : ['May'], + 'June' : ['June'], + 'July' : ['July'], + 'August' : ['August'], + 'September' : ['September'], + 'October' : ['October'], + 'November' : ['November'], + 'December' : ['December'] + }, + true // read-only + ); + for (currentYear; currentYear > firstYear; currentYear -= 1) { + years[currentYear.toString() + ' '] = currentYear; + } + years[firstYear + ' or before'] = '< ' + currentYear; + byr = new InputFieldMorph( + null, // text + false, // numeric? + years, + true // read-only + ); + + inp.alignment = 'left'; + inp.setColor(this.color); + bdy.setColor(this.color); + + mCol.alignment = 'left'; + mCol.setColor(this.color); + yCol.alignment = 'left'; + yCol.setColor(this.color); + + usr.setWidth(200); + bmn.setWidth(100); + byr.contents().minWidth = 80; + byr.setWidth(80); + eml.setWidth(200); + pw1.setWidth(200); + pw2.setWidth(200); + opw.setWidth(200); + pw1.contents().text.toggleIsPassword(); + pw2.contents().text.toggleIsPassword(); + opw.contents().text.toggleIsPassword(); + + if (purpose === 'login') { + inp.add(labelText('User name:')); + inp.add(usr); + } + + if (purpose === 'signup') { + inp.add(labelText('User name:')); + inp.add(usr); + mCol.add(labelText('Birth date:')); + mCol.add(bmn); + yCol.add(labelText('year:')); + yCol.add(byr); + dof.add(mCol); + dof.add(yCol); + inp.add(dof); + emlLabel = labelText('foo'); + inp.add(emlLabel); + inp.add(eml); + } + + if (purpose === 'login') { + inp.add(labelText('Password:')); + inp.add(pw1); + } + + if (purpose === 'changePassword') { + inp.add(labelText('Old password:')); + inp.add(opw); + inp.add(labelText('New password:')); + inp.add(pw1); + inp.add(labelText('Repeat new password:')); + inp.add(pw2); + } + + if (purpose === 'resetPassword') { + inp.add(labelText('User name:')); + inp.add(usr); + } + + if (msg) { + bdy.add(labelText(msg)); + } + + bdy.add(inp); + + if (tosURL || prvURL) { + bdy.add(lnk); + } + if (tosURL) { + lnk.add(linkButton(tosLabel, tosURL)); + } + if (prvURL) { + lnk.add(linkButton(prvLabel, prvURL)); + } + + if (checkBoxLabel) { + chk = new ToggleMorph( + 'checkbox', + this, + function () {agree = !agree; }, // action, + checkBoxLabel, + function () {return agree; } //query + ); + chk.edge = this.buttonEdge / 2; + chk.outline = this.buttonOutline / 2; + chk.outlineColor = this.buttonOutlineColor; + chk.outlineGradient = this.buttonOutlineGradient; + chk.contrast = this.buttonContrast; + chk.drawNew(); + chk.fixLayout(); + bdy.add(chk); + } + + dof.fixLayout(); + mCol.fixLayout(); + yCol.fixLayout(); + inp.fixLayout(); + lnk.fixLayout(); + bdy.fixLayout(); + + this.labelString = title; + this.createLabel(); + if (pic) {this.setPicture(pic); } + + this.addBody(bdy); + + usr.drawNew(); + dof.drawNew(); + mCol.drawNew(); + bmn.drawNew(); + yCol.drawNew(); + byr.drawNew(); + pw1.drawNew(); + pw2.drawNew(); + opw.drawNew(); + eml.drawNew(); + bdy.fixLayout(); + + this.addButton('ok', 'OK'); + this.addButton('cancel', 'Cancel'); + this.fixLayout(); + this.drawNew(); + this.fixLayout(); + + function validInputs() { + var checklist, + empty, + em = eml.getValue(); + + function indicate(morph, string) { + var bubble = new SpeechBubbleMorph(string); + bubble.isPointingRight = false; + bubble.drawNew(); + bubble.popUp( + world, + morph.leftCenter().subtract(new Point(bubble.width() + 2, 0)) + ); + if (morph.edit) { + morph.edit(); + } + } + + if (purpose === 'login') { + checklist = [usr, pw1]; + } else if (purpose === 'signup') { + checklist = [usr, bmn, byr, eml]; + } else if (purpose === 'changePassword') { + checklist = [opw, pw1, pw2]; + } else if (purpose === 'resetPassword') { + checklist = [usr]; + } + + empty = detect( + checklist, + function (inp) { + return !inp.getValue(); + } + ); + if (empty) { + indicate(empty, 'please fill out\nthis field'); + return false; + } + if (purpose === 'signup') { + if (usr.getValue().length < 4) { + indicate(usr, 'User name must be four\ncharacters or longer'); + return false; + } + if (em.indexOf(' ') > -1 || em.indexOf('@') === -1 + || em.indexOf('.') === -1) { + indicate(eml, 'please provide a valid\nemail address'); + return false; + } + } + if (purpose === 'changePassword') { + if (pw1.getValue().length < 6) { + indicate(pw1, 'password must be six\ncharacters or longer'); + return false; + } + if (pw1.getValue() !== pw2.getValue()) { + indicate(pw2, 'passwords do\nnot match'); + return false; + } + } + if (purpose === 'signup') { + if (!agree) { + indicate(chk, 'please agree to\nthe TOS'); + return false; + } + } + return true; + } + + this.accept = function () { + if (validInputs()) { + DialogBoxMorph.prototype.accept.call(myself); + } + }; + + this.edit = function () { + if (purpose === 'changePassword') { + opw.edit(); + } else { // 'signup', 'login', 'resetPassword' + usr.edit(); + } + }; + + this.getInput = function () { + return { + username: usr.getValue(), + email: eml.getValue(), + oldpassword: opw.getValue(), + password: pw1.getValue(), + choice: agree + }; + }; + + this.reactToChoice = function () { + if (purpose === 'signup') { + emlLabel.changed(); + emlLabel.text = age() <= 13 ? + 'E-mail address of parent or guardian:' + : 'E-mail address:'; + emlLabel.drawNew(); + emlLabel.changed(); + } + }; + + this.reactToChoice(); // initialize e-mail label + + if (!this.key) { + this.key = 'credentials' + title + purpose; + } + + this.popUp(world); +}; + +DialogBoxMorph.prototype.accept = function () { + /* + if target is a function, use it as callback: + execute target as callback function with action as argument + in the environment as optionally specified. + Note: if action is also a function, instead of becoming + the argument itself it will be called to answer the argument. + for selections, Yes/No Choices etc: + + else (if target is not a function): + + if action is a function: + execute the action with target as environment (can be null) + for lambdafied (inline) actions + + else if action is a String: + treat it as function property of target and execute it + for selector-like actions + */ + if (this.action) { + if (typeof this.target === 'function') { + if (typeof this.action === 'function') { + this.target.call(this.environment, this.action.call()); + } else { + this.target.call(this.environment, this.action); + } + } else { + if (typeof this.action === 'function') { + this.action.call(this.target, this.getInput()); + } else { // assume it's a String + this.target[this.action](this.getInput()); + } + } + } + this.destroy(); +}; + +DialogBoxMorph.prototype.withKey = function (key) { + this.key = key; + return this; +}; + +DialogBoxMorph.prototype.popUp = function (world) { + if (world) { + if (this.key) { + if (this.instances[world.stamp]) { + if (this.instances[world.stamp][this.key]) { + this.instances[world.stamp][this.key].destroy(); + } + this.instances[world.stamp][this.key] = this; + } else { + this.instances[world.stamp] = {}; + this.instances[world.stamp][this.key] = this; + } + } + world.add(this); + world.keyboardReceiver = this; + this.setCenter(world.center()); + this.edit(); + } +}; + +DialogBoxMorph.prototype.destroy = function () { + DialogBoxMorph.uber.destroy.call(this); + if (this.key) { + delete this.instances[this.key]; + } +}; + +DialogBoxMorph.prototype.ok = function () { + this.accept(); +}; + +DialogBoxMorph.prototype.cancel = function () { + this.destroy(); +}; + +DialogBoxMorph.prototype.edit = function () { + this.children.forEach(function (c) { + if (c.edit) { + return c.edit(); + } + }); +}; + +DialogBoxMorph.prototype.getInput = function () { + if (this.body instanceof InputFieldMorph) { + return this.body.getValue(); + } + return null; +}; + +DialogBoxMorph.prototype.justDropped = function (hand) { + hand.world.keyboardReceiver = this; + this.edit(); +}; + +DialogBoxMorph.prototype.destroy = function () { + var world = this.world(); + world.keyboardReceiver = null; + world.hand.destroyTemporaries(); + DialogBoxMorph.uber.destroy.call(this); +}; + +DialogBoxMorph.prototype.normalizeSpaces = function (string) { + var ans = '', i, c, flag = false; + + for (i = 0; i < string.length; i += 1) { + c = string[i]; + if (c === ' ') { + if (flag) { + ans += c; + flag = false; + } + } else { + ans += c; + flag = true; + } + } + return ans.trim(); +}; + +// DialogBoxMorph submorph construction + +DialogBoxMorph.prototype.createLabel = function () { + var shading = !MorphicPreferences.isFlat || this.is3D; + + if (this.label) { + this.label.destroy(); + } + if (this.labelString) { + this.label = new StringMorph( + localize(this.labelString), + this.titleFontSize, + this.fontStyle, + true, + false, + false, + shading ? new Point(2, 1) : null, + this.titleBarColor.darker(this.contrast) + ); + this.label.color = this.titleTextColor; + this.label.drawNew(); + this.add(this.label); + } +}; + +DialogBoxMorph.prototype.createButtons = function () { + if (this.buttons) { + this.buttons.destroy(); + } + this.buttons = new AlignmentMorph('row', this.padding); + this.add(this.buttons); +}; + +DialogBoxMorph.prototype.addButton = function (action, label) { + var button = new PushButtonMorph( + this, + action || 'ok', + ' ' + localize((label || 'OK')) + ' ' + ); + button.fontSize = this.buttonFontSize; + button.corner = this.buttonCorner; + button.edge = this.buttonEdge; + button.outline = this.buttonOutline; + button.outlineColor = this.buttonOutlineColor; + button.outlineGradient = this.buttonOutlineGradient; + button.padding = this.buttonPadding; + button.contrast = this.buttonContrast; + button.drawNew(); + button.fixLayout(); + this.buttons.add(button); + return button; +}; + +DialogBoxMorph.prototype.setPicture = function (aMorphOrCanvas) { + var morph; + if (aMorphOrCanvas instanceof Morph) { + morph = aMorphOrCanvas; + } else { + morph = new Morph(); + morph.image = aMorphOrCanvas; + morph.silentSetWidth(aMorphOrCanvas.width); + morph.silentSetHeight(aMorphOrCanvas.height); + } + this.addHead(morph); +}; + +DialogBoxMorph.prototype.addHead = function (aMorph) { + if (this.head) { + this.head.destroy(); + } + this.head = aMorph; + this.add(this.head); +}; + +DialogBoxMorph.prototype.addBody = function (aMorph) { + if (this.body) { + this.body.destroy(); + } + this.body = aMorph; + this.add(this.body); +}; + +// DialogBoxMorph layout + +DialogBoxMorph.prototype.addShadow = function () {nop(); }; +DialogBoxMorph.prototype.removeShadow = function () {nop(); }; + +DialogBoxMorph.prototype.fixLayout = function () { + var th = fontHeight(this.titleFontSize) + this.titlePadding * 2, w; + + if (this.head) { + this.head.setPosition(this.position().add(new Point( + this.padding, + th + this.padding + ))); + this.silentSetWidth(this.head.width() + this.padding * 2); + this.silentSetHeight( + this.head.height() + + this.padding * 2 + + th + ); + } + + if (this.body) { + if (this.head) { + this.body.setPosition(this.head.bottomLeft().add(new Point( + 0, + this.padding + ))); + this.silentSetWidth(Math.max( + this.width(), + this.body.width() + this.padding * 2 + )); + this.silentSetHeight( + this.height() + + this.body.height() + + this.padding + ); + w = this.width(); + this.head.setLeft( + this.left() + + Math.round((w - this.head.width()) / 2) + ); + this.body.setLeft( + this.left() + + Math.round((w - this.body.width()) / 2) + ); + } else { + this.body.setPosition(this.position().add(new Point( + this.padding, + th + this.padding + ))); + this.silentSetWidth(this.body.width() + this.padding * 2); + this.silentSetHeight( + this.body.height() + + this.padding * 2 + + th + ); + } + } + + if (this.label) { + this.label.setCenter(this.center()); + this.label.setTop(this.top() + (th - this.label.height()) / 2); + } + + if (this.buttons && (this.buttons.children.length > 0)) { + this.buttons.fixLayout(); + this.silentSetHeight( + this.height() + + this.buttons.height() + + this.padding + ); + this.buttons.setCenter(this.center()); + this.buttons.setBottom(this.bottom() - this.padding); + } +}; + +// DialogBoxMorph shadow + +/* + only take the 'plain' image, so the box rounding doesn't become + conflicted by the scrolling scripts pane +*/ + +DialogBoxMorph.prototype.shadowImage = function (off, color) { + // fallback for Windows Chrome-Shadow bug + var fb, img, outline, sha, ctx, + offset = off || new Point(7, 7), + clr = color || new Color(0, 0, 0); + fb = this.extent(); + img = this.image; + outline = newCanvas(fb); + ctx = outline.getContext('2d'); + ctx.drawImage(img, 0, 0); + ctx.globalCompositeOperation = 'destination-out'; + ctx.drawImage( + img, + -offset.x, + -offset.y + ); + sha = newCanvas(fb); + ctx = sha.getContext('2d'); + ctx.drawImage(outline, 0, 0); + ctx.globalCompositeOperation = 'source-atop'; + ctx.fillStyle = clr.toString(); + ctx.fillRect(0, 0, fb.x, fb.y); + return sha; +}; + +DialogBoxMorph.prototype.shadowImageBlurred = function (off, color) { + var fb, img, sha, ctx, + offset = off || new Point(7, 7), + blur = this.shadowBlur, + clr = color || new Color(0, 0, 0); + fb = this.extent().add(blur * 2); + img = this.image; + sha = newCanvas(fb); + ctx = sha.getContext('2d'); + ctx.shadowOffsetX = offset.x; + ctx.shadowOffsetY = offset.y; + ctx.shadowBlur = blur; + ctx.shadowColor = clr.toString(); + ctx.drawImage( + img, + blur - offset.x, + blur - offset.y + ); + ctx.shadowOffsetX = 0; + ctx.shadowOffsetY = 0; + ctx.shadowBlur = 0; + ctx.globalCompositeOperation = 'destination-out'; + ctx.drawImage( + img, + blur - offset.x, + blur - offset.y + ); + return sha; +}; + +// DialogBoxMorph keyboard events + +DialogBoxMorph.prototype.processKeyPress = function () {nop(); }; + +DialogBoxMorph.prototype.processKeyDown = function (event) { + // this.inspectKeyEvent(event); + switch (event.keyCode) { + case 13: + this.ok(); + break; + case 27: + this.cancel(); + break; + default: + // this.inspectKeyEvent(event); + } +}; + +// DialogBoxMorph drawing + +DialogBoxMorph.prototype.drawNew = function () { + this.fullChanged(); + Morph.prototype.trackChanges = false; + DialogBoxMorph.uber.removeShadow.call(this); + this.fixLayout(); + + var context, + gradient, + w = this.width(), + h = this.height(), + th = Math.floor( + fontHeight(this.titleFontSize) + this.titlePadding * 2 + ), + shift = this.corner / 2, + x, + y, + isFlat = MorphicPreferences.isFlat && !this.is3D; + + // this.alpha = isFlat ? 0.9 : 1; + + this.image = newCanvas(this.extent()); + context = this.image.getContext('2d'); + + // title bar + if (isFlat) { + context.fillStyle = this.titleBarColor.toString(); + } else { + gradient = context.createLinearGradient(0, 0, 0, th); + gradient.addColorStop( + 0, + this.titleBarColor.lighter(this.contrast / 2).toString() + ); + gradient.addColorStop( + 1, + this.titleBarColor.darker(this.contrast).toString() + ); + context.fillStyle = gradient; + } + context.beginPath(); + this.outlinePathTitle( + context, + isFlat ? 0 : this.corner + ); + context.closePath(); + context.fill(); + + // flat shape + // body + context.fillStyle = this.color.toString(); + context.beginPath(); + this.outlinePathBody( + context, + isFlat ? 0 : this.corner + ); + context.closePath(); + context.fill(); + + if (isFlat) { + DialogBoxMorph.uber.addShadow.call(this); + Morph.prototype.trackChanges = true; + this.fullChanged(); + return; + } + + // 3D-effect + // bottom left corner + gradient = context.createLinearGradient( + 0, + h - this.corner, + 0, + h + ); + gradient.addColorStop(0, this.color.toString()); + gradient.addColorStop(1, this.color.darker(this.contrast.toString())); + + context.lineWidth = this.corner; + context.lineCap = 'round'; + context.strokeStyle = gradient; + + context.beginPath(); + context.moveTo(this.corner, h - shift); + context.lineTo(this.corner + 1, h - shift); + context.stroke(); + + // bottom edge + gradient = context.createLinearGradient( + 0, + h - this.corner, + 0, + h + ); + gradient.addColorStop(0, this.color.toString()); + gradient.addColorStop(1, this.color.darker(this.contrast.toString())); + + context.lineWidth = this.corner; + context.lineCap = 'butt'; + context.strokeStyle = gradient; + + context.beginPath(); + context.moveTo(this.corner, h - shift); + context.lineTo(w - this.corner, h - shift); + context.stroke(); + + // right body edge + gradient = context.createLinearGradient( + w - this.corner, + 0, + w, + 0 + ); + gradient.addColorStop(0, this.color.toString()); + gradient.addColorStop(1, this.color.darker(this.contrast).toString()); + + context.lineWidth = this.corner; + context.lineCap = 'butt'; + context.strokeStyle = gradient; + + context.beginPath(); + context.moveTo(w - shift, th); + context.lineTo(w - shift, h - this.corner); + context.stroke(); + + // bottom right corner + x = w - this.corner; + y = h - this.corner; + + gradient = context.createRadialGradient( + x, + y, + 0, + x, + y, + this.corner + ); + gradient.addColorStop(0, this.color.toString()); + gradient.addColorStop(1, this.color.darker(this.contrast.toString())); + + context.lineCap = 'butt'; + + context.strokeStyle = gradient; + + context.beginPath(); + context.arc( + x, + y, + shift, + radians(90), + radians(0), + true + ); + context.stroke(); + + // left body edge + gradient = context.createLinearGradient( + 0, + 0, + this.corner, + 0 + ); + gradient.addColorStop(1, this.color.toString()); + gradient.addColorStop( + 0, + this.color.lighter(this.contrast).toString() + ); + + context.lineCap = 'butt'; + context.strokeStyle = gradient; + + context.beginPath(); + context.moveTo(shift, th); + context.lineTo(shift, h - this.corner * 2); + context.stroke(); + + // left vertical bottom corner + gradient = context.createLinearGradient( + 0, + 0, + this.corner, + 0 + ); + gradient.addColorStop(1, this.color.toString()); + gradient.addColorStop( + 0, + this.color.lighter(this.contrast).toString() + ); + + context.lineCap = 'round'; + context.strokeStyle = gradient; + + context.beginPath(); + context.moveTo(shift, h - this.corner * 2); + context.lineTo(shift, h - this.corner - shift); + context.stroke(); + + DialogBoxMorph.uber.addShadow.call(this); + Morph.prototype.trackChanges = true; + this.fullChanged(); +}; + +DialogBoxMorph.prototype.outlinePathTitle = function (context, radius) { + var w = this.width(), + h = Math.ceil(fontHeight(this.titleFontSize)) + this.titlePadding * 2; + + // top left: + context.arc( + radius, + radius, + radius, + radians(-180), + radians(-90), + false + ); + // top right: + context.arc( + w - radius, + radius, + radius, + radians(-90), + radians(-0), + false + ); + // bottom right: + context.lineTo(w, h); + + // bottom left: + context.lineTo(0, h); +}; + +DialogBoxMorph.prototype.outlinePathBody = function (context, radius) { + var w = this.width(), + h = this.height(), + th = Math.floor(fontHeight(this.titleFontSize)) + + this.titlePadding * 2; + + // top left: + context.moveTo(0, th); + + // top right: + context.lineTo(w, th); + + // bottom right: + context.arc( + w - radius, + h - radius, + radius, + radians(0), + radians(90), + false + ); + // bottom left: + context.arc( + radius, + h - radius, + radius, + radians(90), + radians(180), + false + ); +}; + +// AlignmentMorph ///////////////////////////////////////////////////// + +// I am a reified layout, either a row or a column of submorphs + +// AlignmentMorph inherits from Morph: + +AlignmentMorph.prototype = new Morph(); +AlignmentMorph.prototype.constructor = AlignmentMorph; +AlignmentMorph.uber = Morph.prototype; + +// AlignmentMorph instance creation: + +function AlignmentMorph(orientation, padding) { + this.init(orientation, padding); +} + +AlignmentMorph.prototype.init = function (orientation, padding) { + // additional properties: + this.orientation = orientation || 'row'; // or 'column' + this.alignment = 'center'; // or 'left' in a column + this.padding = padding || 0; + this.respectHiddens = false; + + // initialize inherited properties: + AlignmentMorph.uber.init.call(this); + + // override inherited properites: +}; + +// AlignmentMorph displaying and layout + +AlignmentMorph.prototype.drawNew = function () { + this.image = newCanvas(new Point(1, 1)); + this.fixLayout(); +}; + +AlignmentMorph.prototype.fixLayout = function () { + var myself = this, + last = null, + newBounds; + if (this.children.length === 0) { + return null; + } + this.children.forEach(function (c) { + var cfb = c.fullBounds(), + lfb; + if (c.isVisible || myself.respectHiddens) { + if (last) { + lfb = last.fullBounds(); + if (myself.orientation === 'row') { + c.setPosition( + lfb.topRight().add(new Point( + myself.padding, + (lfb.height() - cfb.height()) / 2 + )) + ); + } else { // orientation === 'column' + c.setPosition( + lfb.bottomLeft().add(new Point( + myself.alignment === 'center' ? + (lfb.width() - cfb.width()) / 2 + : 0, + myself.padding + )) + ); + } + newBounds = newBounds.merge(cfb); + } else { + newBounds = cfb; + } + last = c; + } + }); + this.bounds = newBounds; +}; + +// InputFieldMorph ////////////////////////////////////////////////////// + +// InputFieldMorph inherits from Morph: + +InputFieldMorph.prototype = new Morph(); +InputFieldMorph.prototype.constructor = InputFieldMorph; +InputFieldMorph.uber = Morph.prototype; + +// InputFieldMorph settings + +InputFieldMorph.prototype.edge = 2; +InputFieldMorph.prototype.fontSize = 12; +InputFieldMorph.prototype.typeInPadding = 2; +InputFieldMorph.prototype.contrast = 65; + +// InputFieldMorph instance creation: + +function InputFieldMorph(text, isNumeric, choiceDict, isReadOnly) { + this.init(text, isNumeric, choiceDict, isReadOnly); +} + +InputFieldMorph.prototype.init = function ( + text, + isNumeric, + choiceDict, + isReadOnly +) { + var contents = new StringFieldMorph(text || ''), + arrow = new ArrowMorph( + 'down', + 0, + Math.max(Math.floor(this.fontSize / 6), 1) + ); + + this.choices = choiceDict || null; // object, function or selector + this.isReadOnly = isReadOnly || false; + this.isNumeric = isNumeric || false; + + contents.alpha = 0; + contents.fontSize = this.fontSize; + contents.drawNew(); + + this.oldContentsExtent = contents.extent(); + this.isNumeric = isNumeric || false; + + InputFieldMorph.uber.init.call(this); + this.color = new Color(255, 255, 255); + this.add(contents); + this.add(arrow); + contents.isDraggable = false; + this.drawNew(); +}; + +// InputFieldMorph accessing: + +InputFieldMorph.prototype.contents = function () { + return detect( + this.children, + function (child) { + return (child instanceof StringFieldMorph); + } + ); +}; + +InputFieldMorph.prototype.arrow = function () { + return detect( + this.children, + function (child) { + return (child instanceof ArrowMorph); + } + ); +}; + +InputFieldMorph.prototype.setChoice = function (aStringOrFloat) { + this.setContents(aStringOrFloat); + this.escalateEvent('reactToChoice', aStringOrFloat); +}; + +InputFieldMorph.prototype.setContents = function (aStringOrFloat) { + var cnts = this.contents(); + cnts.text.text = aStringOrFloat; + if (aStringOrFloat === undefined) { + return null; + } + if (aStringOrFloat === null) { + cnts.text.text = ''; + } else if (aStringOrFloat.toString) { + cnts.text.text = aStringOrFloat.toString(); + } + cnts.drawNew(); + cnts.changed(); +}; + +InputFieldMorph.prototype.edit = function () { + var c = this.contents(); + c.text.edit(); + c.text.selectAll(); +}; + +InputFieldMorph.prototype.setIsNumeric = function (bool) { + var value; + + this.isNumeric = bool; + this.contents().isNumeric = bool; + this.contents().text.isNumeric = bool; + + // adjust my shown value to conform with the numeric flag + value = this.getValue(); + if (this.isNumeric) { + value = parseFloat(value); + if (isNaN(value)) { + value = null; + } + } + this.setContents(value); +}; + +// InputFieldMorph drop-down menu: + +InputFieldMorph.prototype.dropDownMenu = function () { + var choices = this.choices, + key, + menu = new MenuMorph( + this.setChoice, + null, + this, + this.fontSize + ); + + if (choices instanceof Function) { + choices = choices.call(this); + } else if (isString(choices)) { + choices = this[choices](); + } + if (!choices) { + return null; + } + menu.addItem(' ', null); + for (key in choices) { + if (Object.prototype.hasOwnProperty.call(choices, key)) { + if (key[0] === '~') { + menu.addLine(); + } else { + menu.addItem(key, choices[key]); + } + } + } + if (menu.items.length > 0) { + menu.popUpAtHand(this.world()); + } else { + return null; + } +}; + +// InputFieldMorph layout: + +InputFieldMorph.prototype.fixLayout = function () { + var contents = this.contents(), + arrow = this.arrow(); + + if (!contents) {return null; } + contents.isNumeric = this.isNumeric; + contents.isEditable = (!this.isReadOnly); + if (this.choices) { + arrow.setSize(this.fontSize); + arrow.show(); + } else { + arrow.setSize(0); + arrow.hide(); + } + this.silentSetHeight( + contents.height() + + this.edge * 2 + + this.typeInPadding * 2 + ); + this.silentSetWidth(Math.max( + contents.minWidth + + this.edge * 2 + + this.typeInPadding * 2, + this.width() + )); + + contents.setWidth( + this.width() - this.edge - this.typeInPadding - + (this.choices ? arrow.width() + this.typeInPadding : 0) + ); + + contents.silentSetPosition(new Point( + this.edge, + this.edge + ).add(this.typeInPadding).add(this.position())); + + arrow.silentSetPosition(new Point( + this.right() - arrow.width() - this.edge, + contents.top() + )); + +}; + +// InputFieldMorph events: + +InputFieldMorph.prototype.mouseClickLeft = function (pos) { + if (this.arrow().bounds.containsPoint(pos)) { + this.dropDownMenu(); + } else if (this.isReadOnly) { + this.dropDownMenu(); + } else { + this.escalateEvent('mouseClickLeft', pos); + } +}; + +// InputFieldMorph retrieving: + +InputFieldMorph.prototype.getValue = function () { +/* + answer my content's text string. If I am numerical convert that + string to a number. If the conversion fails answer the string + otherwise the numerical value. +*/ + var num, + contents = this.contents(); + if (this.isNumeric) { + num = parseFloat(contents.text); + if (!isNaN(num)) { + return num; + } + } + return this.normalizeSpaces(contents.string()); +}; + +InputFieldMorph.prototype.normalizeSpaces + = DialogBoxMorph.prototype.normalizeSpaces; + +// InputFieldMorph drawing: + +InputFieldMorph.prototype.drawNew = function () { + var context, borderColor; + + this.fixLayout(); + + // initialize my surface property + this.image = newCanvas(this.extent()); + context = this.image.getContext('2d'); + if (this.parent) { + if (this.parent.color.eq(new Color(255, 255, 255))) { + this.color = this.parent.color.darker(this.contrast * 0.1); + } else { + this.color = this.parent.color.lighter(this.contrast * 0.75); + } + borderColor = this.parent.color; + } else { + borderColor = new Color(120, 120, 120); + } + context.fillStyle = this.color.toString(); + + // cache my border colors + this.cachedClr = borderColor.toString(); + this.cachedClrBright = borderColor.lighter(this.contrast) + .toString(); + this.cachedClrDark = borderColor.darker(this.contrast).toString(); + + context.fillRect( + this.edge, + this.edge, + this.width() - this.edge * 2, + this.height() - this.edge * 2 + ); + + this.drawRectBorder(context); +}; + +InputFieldMorph.prototype.drawRectBorder = function (context) { + var shift = this.edge * 0.5, + gradient; + + if (MorphicPreferences.isFlat && !this.is3D) {return; } + + context.lineWidth = this.edge; + context.lineJoin = 'round'; + context.lineCap = 'round'; + + context.shadowOffsetY = shift; + context.shadowBlur = this.edge * 4; + context.shadowColor = this.cachedClrDark; + + gradient = context.createLinearGradient( + 0, + 0, + 0, + this.edge + ); + + gradient.addColorStop(0, this.cachedClr); + gradient.addColorStop(1, this.cachedClrDark); + context.strokeStyle = gradient; + context.beginPath(); + context.moveTo(this.edge, shift); + context.lineTo(this.width() - this.edge - shift, shift); + context.stroke(); + + context.shadowOffsetY = 0; + + gradient = context.createLinearGradient( + 0, + 0, + this.edge, + 0 + ); + gradient.addColorStop(0, this.cachedClr); + gradient.addColorStop(1, this.cachedClrDark); + context.strokeStyle = gradient; + context.beginPath(); + context.moveTo(shift, this.edge); + context.lineTo(shift, this.height() - this.edge - shift); + context.stroke(); + + context.shadowOffsetX = 0; + context.shadowOffsetY = 0; + context.shadowBlur = 0; + + gradient = context.createLinearGradient( + 0, + this.height() - this.edge, + 0, + this.height() + ); + gradient.addColorStop(0, this.cachedClrBright); + gradient.addColorStop(1, this.cachedClr); + context.strokeStyle = gradient; + context.beginPath(); + context.moveTo(this.edge, this.height() - shift); + context.lineTo(this.width() - this.edge, this.height() - shift); + context.stroke(); + + gradient = context.createLinearGradient( + this.width() - this.edge, + 0, + this.width(), + 0 + ); + gradient.addColorStop(0, this.cachedClrBright); + gradient.addColorStop(1, this.cachedClr); + context.strokeStyle = gradient; + context.beginPath(); + context.moveTo(this.width() - shift, this.edge); + context.lineTo(this.width() - shift, this.height() - this.edge); + context.stroke(); +}; diff --git a/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/xml.js b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/xml.js new file mode 100644 index 0000000..cc3c609 --- /dev/null +++ b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/xml.js @@ -0,0 +1,429 @@ +/* + + xml.js + + a simple XML DOM, encoder and parser for morphic.js + + written by Jens Mönig + jens@moenig.org + + Copyright (C) 2013 by Jens Mönig + + This file is part of Snap!. + + Snap! is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + + + prerequisites: + -------------- + needs morphic.js + + + hierarchy + --------- + the following tree lists all constructors hierarchically, + indentation indicating inheritance. Refer to this list to get a + contextual overview: + + Node* + XML_Element + ReadStream + + * defined in morphic.js + + + toc + --- + the following list shows the order in which all constructors are + defined. Use this list to locate code in this document: + + ReadStream + XML_Element + + + credits + ------- + Nathan Dinsmore contributed to the design and implemented a first + working version of a complete XMLSerializer. I have taken much of the + overall design and many of the functions and methods in this file from + Nathan's fine original prototype. + +*/ + +/*global modules, isString, detect, Node, isNil*/ + +// Global stuff //////////////////////////////////////////////////////// + +modules.xml = '2013-April-19'; + +// Declarations + +var ReadStream; +var XML_Element; + +// ReadStream //////////////////////////////////////////////////////////// + +// I am a sequential reading interface to an Array or String + +// ReadStream instance creation: + +function ReadStream(arrayOrString) { + this.contents = arrayOrString || ''; + this.index = 0; +} + +// ReadStream constants: + +ReadStream.prototype.space = /[\s]/; + +// ReadStream accessing: + +ReadStream.prototype.next = function (count) { + var element; + if (count === undefined) { + element = this.contents[this.index]; + this.index += 1; + return element; + } + return this.contents.slice(this.index, this.index += count); +}; + +ReadStream.prototype.peek = function () { + return this.contents[this.index]; +}; + +ReadStream.prototype.skip = function (count) { + this.index += count || 1; +}; + +ReadStream.prototype.atEnd = function () { + return this.index > (this.contents.length - 1); +}; + +// ReadStream accessing String contents: + +ReadStream.prototype.upTo = function (regex) { + if (!isString(this.contents)) {return ''; } + var i = this.contents.substr(this.index).search(regex); + if (i === -1) { + return ''; + } + return this.contents.substring(this.index, this.index += i); +}; + +ReadStream.prototype.peekUpTo = function (regex) { + if (!isString(this.contents)) {return ''; } + var i = this.contents.substr(this.index).search(regex); + if (i === -1) { + return ''; + } + return this.contents.substring(this.index, this.index + i); +}; + +ReadStream.prototype.skipSpace = function () { + if (!isString(this.contents)) {return ''; } + var ch; + while (this.space.test(ch = this.peek()) && ch !== '') { + this.skip(); + } +}; + +ReadStream.prototype.word = function () { + if (!isString(this.contents)) {return ''; } + var i = this.contents.substr(this.index).search(/[\s\>\/\=]|$/); + if (i === -1) { + return ''; + } + return this.contents.substring(this.index, this.index += i); +}; + +// XML_Element /////////////////////////////////////////////////////////// +/* + I am a DOM-Node which can encode itself to as well as parse itself + from a well-formed XML string. Note that there is no separate parser + object, all the parsing can be done in a single object. +*/ + +// XML_Element inherits from Node: + +XML_Element.prototype = new Node(); +XML_Element.prototype.constructor = XML_Element; +XML_Element.uber = Node.prototype; + +// XML_Element preferences settings: + +XML_Element.prototype.indentation = ' '; + +// XML_Element instance creation: + +function XML_Element(tag, contents, parent) { + this.init(tag, contents, parent); +} + +XML_Element.prototype.init = function (tag, contents, parent) { + // additional properties: + this.tag = tag || 'unnamed'; + this.attributes = {}; + this.contents = contents || ''; + + // initialize inherited properties: + XML_Element.uber.init.call(this); + + // override inherited properties + if (parent instanceof XML_Element) { + parent.addChild(this); + } +}; + +// XML_Element DOM navigation: (aside from what's inherited from Node) + +XML_Element.prototype.require = function (tagName) { + // answer the first direct child with the specified tagName, or throw + // an error if it doesn't exist + var child = this.childNamed(tagName); + if (!child) { + throw new Error('Missing required element <' + tagName + '>!'); + } + return child; +}; + +XML_Element.prototype.childNamed = function (tagName) { + // answer the first direct child with the specified tagName, or null + return detect( + this.children, + function (child) {return child.tag === tagName; } + ); +}; + +XML_Element.prototype.childrenNamed = function (tagName) { + // answer all direct children with the specified tagName + return this.children.filter( + function (child) {return child.tag === tagName; } + ); +}; + +XML_Element.prototype.parentNamed = function (tagName) { + // including myself + if (this.tag === tagName) { + return this; + } + if (!this.parent) { + return null; + } + return this.parent.parentNamed(tagName); +}; + +// XML_Element output: + +XML_Element.prototype.toString = function (isFormatted, indentationLevel) { + var result = '', + indent = '', + level = indentationLevel || 0, + key, + i; + + // spaces for indentation, if any + if (isFormatted) { + for (i = 0; i < level; i += 1) { + indent += this.indentation; + } + result += indent; + } + + // opening tag + result += ('<' + this.tag); + + // attributes, if any + for (key in this.attributes) { + if (Object.prototype.hasOwnProperty.call(this.attributes, key) + && this.attributes[key]) { + result += ' ' + key + '="' + this.attributes[key] + '"'; + } + } + + // contents, subnodes, and closing tag + if (!this.contents.length && !this.children.length) { + result += '/>'; + } else { + result += '>'; + result += this.contents; + this.children.forEach(function (element) { + if (isFormatted) { + result += '\n'; + } + result += element.toString(isFormatted, level + 1); + }); + if (isFormatted && this.children.length) { + result += ('\n' + indent); + } + result += ''; + } + return result; +}; + +XML_Element.prototype.escape = function (string, ignoreQuotes) { + var src = isNil(string) ? '' : string.toString(), + result = '', + i, + ch; + for (i = 0; i < src.length; i += 1) { + ch = src[i]; + switch (ch) { + case '\'': + result += '''; + break; + case '\"': + result += ignoreQuotes ? ch : '"'; + break; + case '<': + result += '<'; + break; + case '>': + result += '>'; + break; + case '&': + result += '&'; + break; + case '\n': // escape CR b/c of export to URL feature + result += ' '; + break; + case '~': // escape tilde b/c it's overloaded in serializer.store() + result += '~'; + break; + default: + result += ch; + } + } + return result; +}; + +XML_Element.prototype.unescape = function (string) { + var stream = new ReadStream(string), + result = '', + ch, + esc; + + function nextPut(str) { + result += str; + stream.upTo(';'); + stream.skip(); + } + + while (!stream.atEnd()) { + ch = stream.next(); + if (ch === '&') { + esc = stream.peekUpTo(';'); + switch (esc) { + case 'apos': + nextPut('\''); + break; + case 'quot': + nextPut('\"'); + break; + case 'lt': + nextPut('<'); + break; + case 'gt': + nextPut('>'); + break; + case 'amp': + nextPut('&'); + break; + case '#xD': + nextPut('\n'); + break; + case '#126': + nextPut('~'); + break; + default: + result += ch; + } + } else { + result += ch; + } + } + return result; +}; + +// XML_Element parsing: + +XML_Element.prototype.parseString = function (string) { + var stream = new ReadStream(string); + stream.upTo('<'); + stream.skip(); + this.parseStream(stream); +}; + +XML_Element.prototype.parseStream = function (stream) { + var key, + value, + ch, + child; + + // tag: + this.tag = stream.word(); + stream.skipSpace(); + + // attributes: + while ((ch = stream.peek()) !== '>' && ch !== '/') { + key = stream.word(); + stream.skipSpace(); + if (stream.next() !== '=') { + throw new Error('Expected "=" after attribute name'); + } + stream.skipSpace(); + if ((ch = stream.next()) !== '"' && ch !== "'") { + throw new Error( + 'Expected single- or double-quoted attribute value' + ); + } + value = stream.upTo(ch); + stream.skip(1); + stream.skipSpace(); + this.attributes[key] = this.unescape(value); + } + + // empty tag: + if (stream.peek() === '/') { + stream.skip(); + if (stream.next() !== '>') { + throw new Error('Expected ">" after "/" in empty tag'); + } + return; + } + if (stream.next() !== '>') { + throw new Error('Expected ">" after tag name and attributes'); + } + + // contents and children + while (!stream.atEnd()) { + ch = stream.next(); + if (ch === '<') { + if (stream.peek() === '/') { // closing tag + stream.skip(); + if (stream.word() !== this.tag) { + throw new Error('Expected to close ' + this.tag); + } + stream.upTo('>'); + stream.skip(); + this.contents = this.unescape(this.contents); + return; + } + child = new XML_Element(null, null, this); + child.parseStream(stream); + } else { + this.contents += ch; + } + } +}; diff --git a/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/ypr.js b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/ypr.js new file mode 100644 index 0000000..3f5f570 --- /dev/null +++ b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/SnapOffline/ypr.js @@ -0,0 +1,1444 @@ +/* + +Copyright 2012 Nathan Dinsmore + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Last changed 2013-04-03 by Jens Moenig (disabled text area overlay) + +*/ + +var sb = (function (sb) { + 'use strict'; + + function extend(o, p) { + var key; + for (key in p) if (p.hasOwnProperty(key)) { + o[key] = p[key]; + } + } + sb.$extend = extend; + + extend((sb.Ref = function (id) { + this.id = id; + }).prototype, { + isRef: true + }); + + extend((sb.Dictionary = function (keys, values) { + this.keys = keys; + this.values = values; + }).prototype, { + get: function (key) { + return this.values[this.keys.indexOf(key)]; + }, + set: function (key, value) { + var i = this.keys.indexOf(key); + if (i === -1) { + this.keys.push(key); + this.values.push(value); + } else { + this.values[i] = value; + } + } + }); + + sb.Color = function (rgb, a) { + this.r = (rgb / 0x100000 | 0) % 0x400 / (0x400 - 1); + this.g = (rgb / 0x400 | 0) % 0x400 / (0x400 - 1); + this.b = (rgb % 0x400) / (0x400 - 1); + this.a = a / (0x100 - 1); + }; + sb.Point = function (x, y) { + this.x = x; + this.y = y; + }; + sb.Rectangle = function (x, y, x2, y2) { + this.origin = new sb.Point(x, y); + this.corner = new sb.Point(x2, y2); + }; + + sb.indexedColors = [ + [1, 1, 1, 1], + [0, 0, 0, 1], + [1, 1, 1, 1], + [.5, .5, .5, 1], + [1, 0, 0, 1], + [0, 1, 0, 1], + [0, 0, 1, 1], + [0, 1, 1, 1], + [1, 1, 0, 1], + [1, 0, 1, 1], + [.125, .125, .125, 1], + [.25, .25, .25, 1], + [.375, .375, .375, 1], + [.625, .625, .625, 1], + [.75, .75, .75, 1], + [.875, .875, .875, 1] + ]; + (function () { + var i, r, g, b, grayVal; + for (i = 1; i <= 31; ++i) { + if (i % 4 != 0) { + grayVal = i / 32; + sb.indexedColors[i + 15] = [grayVal, grayVal, grayVal, 1]; + } + } + for (r = 0; r < 6; ++r) { + for (g = 0; g < 6; ++g) { + for (b = 0; b < 6; ++b) { + i = 40 + 36 * r + 6 * b + g; + sb.indexedColors[i] = [r / 5, g / 5, b / 5, 1]; + } + } + } + })(); + + sb.colorDepthLengths = { + 1: 1, + 2: 2, + 4: 4, + 8: 8, + 15: 5, + 16: 5, + 12: 4, + 9: 3 + }; + extend((sb.ColorStream = function (bitmap, depth) { + var i = 0, + l = bitmap.length, + b = this.bits = [], + n; + this.length = sb.colorDepthLengths[this.depth = depth]; + this.position = 0; + while (i < l) { + n = bitmap[i++]; + b.push(n / 0x80 & 1); + b.push(n / 0x40 & 1); + b.push(n / 0x20 & 1); + b.push(n / 0x10 & 1); + b.push(n / 0x8 & 1); + b.push(n / 0x4 & 1); + b.push(n / 0x2 & 1); + b.push(n & 1); + } + }).prototype, { + read: function () { + var i = this.length, + b = this.bits, + n = 0; + while (i--) { + n = n * 2 + b[this.position++]; + } + return n; + } + }); + + extend((sb.ColorStreamIndexed = function (bitmap, depth) { + sb.ColorStream.call(this, bitmap, depth); + }).prototype = Object.create(sb.ColorStream.prototype), { + next: function (b) { + var c = sb.indexedColors[this.read()]; + if (!c) return c; + b[0] = c[0]; + b[1] = c[1]; + b[2] = c[2]; + b[3] = c[3]; + } + }); + + extend((sb.ColorStreamRGB = function (bitmap, depth) { + sb.ColorStream.call(this, bitmap, depth); + this.max = Math.pow(2, this.length) - 1; + }).prototype = Object.create(sb.ColorStream.prototype), { + next: function (b) { + var c; + switch (this.depth) { + case 16: + ++this.position; + c = [this.read() / this.max, this.read() / this.max, this.read() / this.max, 1]; + if (c[0] + c[1] + c[2] === 0) { + return b[0] = b[1] = b[2] = b[3] = 0; + } + b[0] = c[0]; + b[1] = c[1]; + b[2] = c[2]; + b[3] = c[3]; + return; + case 12: + this.position += 3; + break; + } + b[0] = this.read() / this.max; + b[1] = this.read() / this.max; + b[2] = this.read() / this.max; + b[3] = 1; + } + }); + + extend((sb.ColorStream32 = function (bitmap, depth) { + this.bytes = bitmap; + if (depth === 32) { + this.next = this.nextAlpha; + } + this.position = 0; + }).prototype, { + next: function (b) { + b[0] = this.read() / 255; + b[1] = this.read() / 255; + b[2] = this.read() / 255; + b[3] = 1; + }, + nextAlpha: function (b) { + var a = this.read() / 255; + b[0] = this.read() / 255; + b[1] = this.read() / 255; + b[2] = this.read() / 255 + b[3] = a; + }, + read: function () { + return this.bytes[this.position++]; + } + }); + + sb.colorStreams = { + 1: sb.ColorStreamIndexed, + 2: sb.ColorStreamIndexed, + 4: sb.ColorStreamIndexed, + 8: sb.ColorStreamIndexed, + 15: sb.ColorStreamRGB, + 16: sb.ColorStreamRGB, + 32: sb.ColorStream32, + 24: sb.ColorStream32, + 12: sb.ColorStreamRGB, + 9: sb.ColorStreamRGB + }; + + sb.getColorStream = function (bitmap, depth) { + return new sb.colorStreams[depth](bitmap, depth); + }; + + extend((sb.Form = function (w, h, d, o, b) { + this.width = w; + this.height = h; + this.depth = d; + this.offset = o; + this.bits = b; + }).prototype, { + init: function (bm) { + if (this.bits.isBitmap) { + this.bitmap = this.bits; + } else { + this.decompress(bm); + } + this.image = document.createElement('canvas'); + this.image.width = this.width; + this.image.height = this.height; + }, + decompress: function (bm) { + var b = this.bits, + p = 0, + q = 0, + r = !!bm, + length = (i = b[p++], i <= 223 ? i : i < 255 ? (i - 224) * 256 + b[p++] : (b[p++] << 24) + (b[p++] << 16) + (b[p++] << 8) + b[p++]), + bm, i, n, d, e, f, g; + // stream = new sb.Reader().on(this.bits), + this.bitmap = bm || (bm = []); + while (p < b.length) { + i = b[p++]; + if (i > 223) { + i = i < 255 ? (i - 224) * 256 + b[p++] : (b[p++] << 24) + (b[p++] << 16) + (b[p++] << 8) + b[p++]; + } + n = i >> 2; + switch (i & 3) { + case 1: + d = b[p++]; + n *= 4; + while (n--) { + bm[q++] = d; + } + break; + case 2: + // d = stream.readBytes(4); + d = b[p++]; + e = b[p++]; + f = b[p++]; + g = b[p++]; + while (n--) { + if (r) { + bm[q++] = e; + bm[q++] = f; + bm[q++] = g; + bm[q++] = d; + } else { + bm[q++] = d; + bm[q++] = e; + bm[q++] = f; + bm[q++] = g; + } + } + break; + case 3: + while (n--) { + if (r) { + d = b[p++]; + bm[q++] = b[p++]; + bm[q++] = b[p++]; + bm[q++] = b[p++]; + bm[q++] = d; + } else { + bm[q++] = b[p++]; + bm[q++] = b[p++]; + bm[q++] = b[p++]; + bm[q++] = b[p++]; + } + } + // bm.push.apply(bm, stream.readBytes(n * 4)); + break; + } + } + }, + load: function () { + var w = this.width, + h = this.height, + x, y, i, imageData, data, context, colors, color, b; + if (this.depth === 32) { + this.image = document.createElement('canvas'); + this.image.width = this.width; + this.image.height = this.height; + data = (imageData = (context = this.image.getContext('2d')).createImageData(this.width, this.height)).data; + this.decompress(data); + context.putImageData(imageData, 0, 0); + return; + } + color = [0, 0, 0, 0]; + if (this.depth === 16) { + w = this.width += 1; + } + this.init(); + colors = sb.getColorStream(this.bitmap, this.depth); + data = (imageData = (context = this.image.getContext('2d')).createImageData(this.width, this.height)).data; + for (x = 0; x < w; ++x) { + for (y = 0; y < h; ++y) { + colors.next(color); + if (!color) continue; + i = (x * h + y) * 4; + data[i] = color[0] * 255; + data[i + 1] = color[1] * 255; + data[i + 2] = color[2] * 255; + data[i + 3] = color[3] * 255; + } + } + context.putImageData(imageData, 0, 0); + } + }); + + extend((sb.ColorForm = function (w, h, d, o, b, c) { + this.width = w; + this.height = h; + this.depth = d; + this.offset = o; + this.bits = b; + this.colors = c; + }).prototype = Object.create(sb.Form.prototype), { + load: function () { + var w = this.width, + h = this.height, + colors = this.colors, + bits, x, y, i, imageData, data, context, color; + this.init(); + data = (imageData = (context = this.image.getContext('2d')).createImageData(this.width, this.height)).data; + bits = this.bitmap; + for (x = 0; x < w; ++x) { + for (y = 0; y < h; ++y) { + color = colors[bits[x * h + y]]; + if (!color) continue; + i = (x * h + y) * 4; + data[i] = color.r * 255; + data[i + 1] = color.g * 255; + data[i + 2] = color.b * 255; + data[i + 3] = color.a * 255; + } + } + context.putImageData(imageData, 0, 0); + } + }); + + sb.fields = {}; + sb.classIDs = {}; + sb.classNames = {}; + sb.addFields = function (id, name, base, fields) { + sb.classIDs[name] = id; + sb.classNames[id] = name; + fields = fields.length ? fields.split(',') : []; + if (base) { + base = sb.fields[sb.classIDs[base]]; + if (!base) throw new Error('Initialization error'); + fields = base.concat(fields); + } + sb.fields[id] = fields; + }; + + sb.addFields(100, 'Morph', '', 'bounds,owner,submorphs,color,flags,properties'); + sb.addFields(101, 'BorderedMorph', 'Morph', 'borderWidth,borderColor'); + sb.addFields(102, 'RectangleMorph', 'BorderedMorph', ''); + sb.addFields(103, 'EllipseMorph', 'BorderedMorph', ''); + sb.addFields(104, 'AlignmentMorph', 'RectangleMorph', 'orientation,centering,hResizing,vResizing,inset'); + sb.addFields(105, 'StringMorph', 'Morph', 'fontSpec,emphasis,contents'); + + sb.addFields(-1, 'Slider', 'BorderedMorph', 'slider,value,setValueSelector,sliderShadow,sliderColor,descending,model'); + sb.addFields(-2, 'AbstractSound', '', ''); + sb.addFields(-3, 'ScriptableScratchMorph', 'Morph', 'objName,vars,blocksBin,customBlocks,isClone,media,costume'); + sb.addFields(-4, 'ArgMorph', 'BorderedMorph', 'labelMorph'); + sb.addFields(-5, 'PasteUpMorph', 'BorderedMorph', ''); + sb.addFields(-6, 'ScratchMedia', '', 'mediaName'); + sb.addFields(-7, 'ScrollFrameMorph', 'BorderedMorph', ''); + + sb.addFields(106, 'UpdatingStringMorph', 'StringMorph', 'format,target,getSelector,putSelector,parameter,floatPrecision,growable,stepTime'); + sb.addFields(107, 'SimpleSliderMorph', 'Slider', 'target,arguments,minVal,maxVal,truncate,sliderThickness'); + sb.addFields(108, 'SimpleButtonMorph', 'RectangleMorph', 'target,actionSelector,arguments,actWhen'); + sb.addFields(109, 'SampledSound', 'AbstractSound', 'envelopes,scaledVol,initialCount,samples,originalSamplingRate,samplesSize,scaledIncrement,scaledInitialIndex'); + sb.addFields(110, 'ImageMorph', 'Morph', 'form,transparency'); + sb.addFields(111, 'SketchMorph', 'Morph', 'originalForm,rotationCenter,rotationDegrees,rotationStyle,scalePoint,offsetWhenRotated'); + sb.addFields(123, 'SensorBoardMorph', 'Morph', 'portNum'); + sb.addFields(124, 'ScratchSpriteMorph', 'ScriptableScratchMorph', 'visibility,scalePoint,rotationDegrees,rotationStyle,volume,tempoBPM,draggable,sceneStates,lists,virtualScale,ownerSprite,subsprites,rotateWithOwner,refPos,prototype,deletedAttributes'); + sb.addFields(125, 'ScratchStageMorph', 'ScriptableScratchMorph', 'zoom,hPan,vPan,obsoleteSavedState,sprites,volume,tempoBPM,sceneStates,lists'); + sb.addFields(140, 'ChoiceArgMorph', 'ArgMorph', 'isBoolean,options,choice,getOptionsSelector'); + sb.addFields(141, 'ColorArgMorph', 'ArgMorph', ''); + sb.addFields(142, 'ExpressionArgMorph', 'ArgMorph', 'isNumber'); + sb.addFields(145, 'SpriteArgMorph', 'ArgMorph', 'morph'); + sb.addFields(147, 'BlockMorph', 'Morph', 'isSpecialForm,oldColor'); + sb.addFields(148, 'CommandBlockMorph', 'BlockMorph', 'commandSpec,argMorphs,titleMorph,receiver,selector,isReporter,isTimed,wantsName,wantsPossession'); + sb.addFields(149, 'CBlockMorph', 'CommandBlockMorph', 'nestedBlock,nextBlock'); + sb.addFields(151, 'HatBlockMorph', 'CommandBlockMorph', 'scriptNameMorph,indicatorMorph,scriptOwner,parameters,isClickable'); + sb.addFields(153, 'ScratchScriptsMorph', 'PasteUpMorph', ''); + sb.addFields(154, 'ScratchSliderMorph', 'AlignmentMorph', 'slider,sliderMin,sliderMax,variable'); + sb.addFields(155, 'WatcherMorph', 'AlignmentMorph', 'titleMorph,readout,readoutFrame,scratchSlider,watcher,isSpriteSpecific,unused,sliderMin,sliderMax,isLarge'); + sb.addFields(157, 'SetterBlockMorph', 'CommandBlockMorph', 'variable'); + sb.addFields(158, 'EventHatMorph', 'HatBlockMorph', ''); + sb.addFields(170, 'ReporterBlockMorph', 'CommandBlockMorph', 'isBoolean'); + sb.addFields(160, 'VariableBlockMorph', 'ReporterBlockMorph', ''); + sb.addFields(162, 'ImageMedia', 'ScratchMedia', 'form,rotationCenter,textBox,jpegBytes,compositeForm'); + sb.addFields(163, 'MovieMedia', 'ScratchMedia', 'fileName,fade,fadeColor,zoom,hPan,vPan,msecsPerFrame,currentFrame,moviePlaying'); + sb.addFields(164, 'SoundMedia', 'ScratchMedia', 'originalSound,volume,balance,compressedSampleRate,compressedBitsPerSample,compressedData'); + sb.addFields(165, 'KeyEventHatMorph', 'HatBlockMorph', ''); + sb.addFields(166, 'BooleanArgMorph', 'ArgMorph', ''); + sb.addFields(167, 'EventTitleMorph', 'ArgMorph', ''); + sb.addFields(168, 'MouseClickEventHatMorph', 'HatBlockMorph', ''); + sb.addFields(169, 'ExpressionArgMorphWithMenu', 'ExpressionArgMorph', 'menuMorph,getMenuSelector,specialValue'); + sb.addFields(171, 'MultilineStringMorph', 'BorderedMorph', 'fontSpec,textColor,selectionColor,lines'); + sb.addFields(172, 'ToggleButton', 'SimpleButtonMorph', 'onForm,offForm,overForm,disabledForm,isMomentary,toggleMode,isOn,isDisabled'); + sb.addFields(173, 'WatcherReadoutFrameMorph', 'BorderedMorph', ''); + sb.addFields(174, 'WatcherSliderMorph', 'SimpleSliderMorph', ''); + sb.addFields(175, 'ScratchListMorph', 'BorderedMorph', 'listName,strings,target,complex'); + sb.addFields(176, 'ScrollingStringMorph', 'BorderedMorph', 'fontSpec,showScrollbar,firstVisibleLine,textColor,selectionColor,lines'); + + sb.addFields(180, 'ScrollFrameMorph2', 'ScrollFrameMorph', ''); + sb.addFields(181, 'ListMultilineStringMorph', 'MultilineStringMorph', ''); + sb.addFields(182, 'ScratchScrollBar', 'Morph', ''); + + sb.addFields(200, 'CustomCommandBlockMorph', 'CommandBlockMorph', 'userSpec'); + sb.addFields(201, 'CustomBlockDefinition', '', 'userSpec,blockVars,isAtomic,isReporter,isBoolean,body,answer,type,category,declarations,defaults,isGlobal'); + sb.addFields(203, 'ReporterScriptBlockMorph', 'ReporterBlockMorph', ''); + sb.addFields(202, 'CommandScriptBlockMorph', 'ReporterScriptBlockMorph', ''); + sb.addFields(205, 'VariableFrame', '', 'vars'); + sb.addFields(206, 'CustomReporterBlockMorph', 'ReporterBlockMorph', 'userSpec'); + + sb.addFields(207, 'CReporterSlotMorph', 'ReporterScriptBlockMorph', ''); + + sb.addFields(300, 'StringFieldMorph', 'BorderedMorph', ''); + sb.addFields(301, 'MultiArgReporterBlockMorph', 'ReporterBlockMorph', ''); + + (function (C, p) { + + p.on = function (bytes) { + this.bytes = bytes; + if (!bytes.subarray) bytes.subarray = bytes.slice; + this.position = 0; + return this; + }; + p.readYPR = function (bytes) { + var version, infoSize, info, stage; + console.time('readSB'); + this.on(bytes); + + // skip header + this.matchBytes([66, 108, 111, 120, 69, 120, 112, 86]); + version = +(String.fromCharCode(this.next()) + String.fromCharCode(this.next())); + if (version < 1) { + throw new Error('Invalid version'); + } + + // read info + infoSize = this.uint32(); + info = this.read(); + + this.position = infoSize + 14; // header + uint32 + stage = this.read(); + + this.onload({ + reader: this, + info: info, + stage: stage + }); + console.timeEnd('readSB'); + }; + p.readInfo = function (bytes) { + var version, infoSize, info; + this.on(bytes); + + this.matchBytes([66, 108, 111, 120, 69, 120, 112, 86]); + version = +(String.fromCharCode(this.next()) + String.fromCharCode(this.next())); + if (version < 1) { + throw new Error('Invalid version'); + } + + // read info + infoSize = this.uint32(); + info = this.read(); + + this.onload({ + reader: this, + info: info + }); + }; + p.read = function () { + var i, objectCount; + this.objects = []; + this.readHeader(); + objectCount = this.uint32(); + + i = objectCount; + while (i--) { + this.objects.push(this.readObject()); + } + i = objectCount; + while (i--) { + this.fixReferences(this.objects[i]); + } + return this.objects[0][1]; + }; + p.fixReferences = function (object) { + var classID = object[0], + value = object[1], + i = 0, + fields, source; + if (classID < 99) { + this.fixFixedFormat(classID, value); + } else { + this.fixArray(object[3]); + fields = sb.fields[classID]; + if (!fields) + throw new Error('Invalid class ID ' + classID); + source = value.fields; + delete value.fields; + value.className = sb.classNames[classID]; + i = fields.length; + while (i--) { + value[fields[i]] = source[i]; + } + } + }; + p.fixArray = function (a) { + var i = a.length, + o; + while (i--) { + if ((o = a[i]) && o.isRef) { + if (o.id > this.objects.length) { + throw new Error('Invalid object reference'); + } + a[i] = this.objects[o.id - 1][1]; + } + } + }; + p.targetObjectFor = function (o) { + if (o && o.isRef) { + if (o.id > this.objects.length) + throw new Error('Invalid object reference'); + return this.objects[o.id - 1][1]; + } + return o; + }; + p.fixFixedFormat = function (classID, object) { + switch (classID) { + case 20: // Array + case 21: // OrderedCollection + case 22: // Set + case 23: // IdentitySet + this.fixArray(object); + return object; + case 24: // Dictionary + case 25: // IdentityDictionary + this.fixArray(object.keys); + this.fixArray(object.values); + break; + case 32: // Point + object.x = this.targetObjectFor(object.x); + object.y = this.targetObjectFor(object.y); + break; + case 33: // Rectangle + object.origin.x = this.targetObjectFor(object.origin.x); + object.origin.y = this.targetObjectFor(object.origin.y); + object.corner.x = this.targetObjectFor(object.corner.x); + object.corner.y = this.targetObjectFor(object.corner.y); + break; + case 34: // Form + object.offset = this.targetObjectFor(object.offset); + object.bits = this.targetObjectFor(object.bits); + object.load(); + break; + case 35: // ColorForm + object.offset = this.targetObjectFor(object.offset); + object.bits = this.targetObjectFor(object.bits); + object.colors = this.targetObjectFor(object.colors); + object.load(); + break; + } + return object; + }; + p.readObject = function () { + var classID = this.next(), + version, fieldCount, fields; + if (classID > 99) { + version = this.next(); + fieldCount = this.next(); + fields = []; + while (fieldCount--) { + fields.push(this.readField()); + } + return [classID, { fields: fields }, version, fields]; + } + return [classID, this.readFixedFormat(classID)]; + }; + p.readField = function () { + var classID = this.next(); + if (classID === 99) { + return new sb.Ref(this.uint24()); + } + return this.readFixedFormat(classID); + }; + p.readFixedFormat = function (classID) { + var a, n; + switch (classID) { + case 1: // UndefinedObject + return null; + case 2: // True + return true; + case 3: // False + return false; + case 4: // SmallInteger + return this.int32(); + case 5: // SmallInteger16 + return this.int16(); + case 6: // LargePositiveInteger + case 7: // LargeNegativeInteger + n = this.uint16(); + a = 0; + while (n--) { + a *= 0x100; + a += this.next(); + } + return a; + case 8: // Float + return this.float64(); + case 9: // String + case 10: // Symbol + case 14: // UTF8 + a = [].slice.call(this.readBytes(n = this.uint32())); + while (n--) { + a[n] = String.fromCharCode(a[n]); + } + return a.join(''); + case 11: // ByteArray + return this.readBytes(this.uint32()); + case 12: // SoundBuffer + a = []; + n = this.uint32(); + while (n--) { + a.push(this.int16()); + } + return a; + case 13: // Bitmap + a = []; + a.isBitmap = true; + n = this.uint32(); + while (n--) { + a.push(this.uint32()); + } + return a; + case 20: // Array + case 21: // OrderedCollection + case 22: // Set + case 23: // IdentitySet + a = []; + n = this.uint32(); + while (n--) { + a.push(this.readField()); + } + return a; + case 24: // Dictionary + case 25: // IdentityDictionary + a = new sb.Dictionary([], []); + n = this.uint32(); + while (n--) { + a.keys.push(this.readField()); + a.values.push(this.readField()); + } + return a; + case 30: // Color + return new sb.Color(this.uint32(), 255); + case 31: // TranslucentColor + return new sb.Color(this.uint32(), this.uint8()); + case 32: // Point + return new sb.Point(this.readField(), this.readField()); + case 33: // Rectangle + return new sb.Rectangle(this.readField(), this.readField(), this.readField(), this.readField()); + case 34: // Form + return new sb.Form(this.readField(), this.readField(), this.readField(), this.readField(), this.readField()); + case 35: // ColorForm + return new sb.ColorForm(this.readField(), this.readField(), this.readField(), this.readField(), this.readField(), this.readField()); + } + throw new Error('Invalid fixed-format class ID'); + }; + p.readHeader = function () { + this.matchBytes([79, 98, 106, 83, 1, 83, 116, 99, 104, 1]); + }; + p.readBytes = function (length) { + return this.bytes.subarray(this.position, this.position += length); + }; + p.matchBytes = function (bytes) { + var i = bytes.length, + r = this.readBytes(i); + while (i--) { + if (r[i] !== bytes[i]) { + throw new Error('Invalid format'); + } + } + }; + p.skip = function (length) { + this.position += length; + }; + p.next = p.uint8 = function () { + return this.bytes[this.position++]; + }; + p.hasNext = function () { + return this.position < this.bytes.length; + }; + p.uint16 = function () { + return this.next() * 0x100 + this.next(); + }; + p.uint24 = function () { + return this.next() * 0x10000 + this.next() * 0x100 + this.next(); + }; + p.uint32 = function () { + return this.next() * 0x1000000 + this.next() * 0x10000 + this.next() * 0x100 + this.next(); + }; + p.int8 = function () { + var v = this.bytes[++this.position]; + return v >= 0x80 ? v - 0x100 : v; + }; + p.int16 = function () { + var d = this.next(), + v = d * 0x100 + this.next(); + return d >= 0x80 ? v - 0x10000 : v; + }; + p.int24 = function () { + var d = this.next(), + v = d * 0x10000 + this.next() * 0x100 + this.next(); + return d >= 0x80 ? v - 0x1000000 : v; + }; + p.int32 = function () { + var d = this.next(), + v = d * 0x1000000 + this.next() * 0x10000 + this.next() * 0x100 + this.next(); + return d >= 0x80 ? v - 0x100000000 : v; + }; + p.string = function () { + var length = this.uint16(), + bytes = this.readBytes(length), + i = length; + while (i--) { + bytes[i] = String.fromCharCode(bytes[i]); + } + return bytes.join(''); + }; + p.float64 = function () { + return this.ieee(8, 11, 52, 1023); + }; + p.ieee = function (n, ebits, mbits) { + var bias = (1 << (ebits - 1)) - 1, + string = '', + i = n, + b, sign, exponent, mantissa, result; + while (i--) { + b = this.next().toString(2); + string = string + Array(9 - b.length).join('0') + b; + } + sign = string.charAt(0) === '0' ? 1 : -1; + exponent = parseInt(string.substr(1, ebits), 2); + mantissa = parseInt(string.substr(ebits + 1), 2); + if (exponent === 0) { + return mantissa === 0 ? sign * 0 : sign * Math.pow(2, 1 - bias) * mantissa / Math.pow(2, mbits); + } + if (exponent === (1 << ebits) - 1) { + return mantissa === 0 ? sign / 0 : NaN; + } + return sign * Math.pow(2, exponent - bias) * (1 + mantissa / Math.pow(2, mbits)); + }; + + })(sb.Reader = function () {}, sb.Reader.prototype); + + (function (C, p) { + + var rotationStyles = { + normal: 1, + leftRight: 2, + none: 0 + }, customBlockInputs = { + object: '%obj', + objectList: '%mult%obj', + number: '%n', + numberList: '%mult%n', + text: '%txt', + textList: '%mult%txt', + list: '%l', + listList: '%mult%l', + any: '%s', + anyList: '%mult%s', + 'boolean': '%b', + booleanList: '%mult%b', + command: '%cmdRing', + commandList: '%mult%cmdRing', + reporter: '%repRing', + reporterList: '%mult%repRing', + predicate: '%predRing', + predicateList: '%mult%predRing', + loop: '%cs', + loopList: '%mult%cs', + unevaluated: '%anyUE', + unevaluatedList: '%mult%anyUE', + unevaluatedBoolean: '%boolUE', + unevaluatedBooleanList: '%mult%boolUE', + template: '%upvar' + }, blockSelectors = { + // Motion': '', + 'forward:': 'forward', + 'turnLeft:': 'turnLeft', + 'turnRight:': 'turn', + 'heading:': 'setHeading', + 'pointTowards:': 'doFaceTowards', + 'gotoX:y:': 'gotoXY', + 'gotoSpriteOrMouse:': 'doGotoObject', + 'glideSecs:toX:y:elapsed:from:': 'doGlide', + 'changeXposBy:': 'changeXPosition', + 'xpos:': 'setXPosition', + 'changeYposBy:': 'changeYPosition', + 'ypos:': 'setYPosition', + 'bounceOffEdge': 'bounceOffEdge', + 'xpos': 'xPosition', + 'ypos': 'yPosition', + 'heading': 'direction', + + // Looks + 'lookLike:': 'doSwitchToCostume', + 'showBackground:': 'doSwitchToCostume', + 'nextCostume': 'doWearNextCostume', + 'nextBackground': 'doWearNextCostume', + 'costumeIndex': 'getCostumeIdx', + 'say:duration:elapsed:from:': 'doSayFor', + 'say:': 'bubble', + 'think:duration:elapsed:from:': 'doThinkFor', + 'think:': 'doThink', + 'changeGraphicEffect:by:': 'changeEffect', + 'setGraphicEffect:to:': 'setEffect', + 'filterReset': 'clearEffects', + 'setSizeTo:': 'setScale', + 'changeSizeBy:': 'changeScale', + 'scale': 'getScale', + 'show': 'show', + 'hide': 'hide', + 'comeToFront': 'comeToFront', + 'goBackByLayers:': 'goBack', + + // Sound + 'playSound:': 'playSound', + 'doPlaySoundAndWait': 'doPlaySoundUntilDone', + 'stopAllSounds': 'doStopAllSounds', + // 'drum:duration:elapsed:from:': '', + 'rest:elapsed:from:': 'doRest', + 'noteOn:duration:elapsed:from:': 'doPlayNote', + // 'midiInstrument:': '', + // 'changeVolumeBy:': '', + // 'setVolumeTo:': '', + // 'volume': '', + 'changeTempoBy:': 'doChangeTempo', + 'setTempoTo:': 'doSetTempo', + 'tempo': 'getTempo', + + // Pen + 'clearPenTrails': 'clear', + 'putPenDown': 'down', + 'putPenUp': 'up', + 'penColor:': 'setColor', + 'changePenHueBy:': 'changeHue', + 'setPenHueTo:': 'setHue', + '_changePenHueBy:': 'changeHue', + '_setPenHueTo:': 'setHue', + 'changePenShadeBy:': 'changeBrightness', + 'setPenShadeTo:': 'setBrightness', + 'changePenSizeBy:': 'changeSize', + 'penSize:': 'setSize', + 'stampCostume': 'doStamp', + + // Control + // 'whenStartClicked': '', + // 'whenKeyPressed:': '', + // 'whenSpriteClicked': '', + 'wait:elapsed:from:': 'doWait', + 'doForever': 'doForever', + 'doRepeat': 'doRepeat', + 'broadcast:': 'doBroadcast', + 'doBroadcastAndWait': 'doBroadcastAndWait', + // 'whenMessageReceived:': '', + // TODO 'doForeverIf': '', + 'doIf': 'doIf', + 'doIfElse': 'doIfElse', + 'doWaitUntil': 'doWaitUntil', + 'doUntil': 'doUntil', + 'doReturn': 'doStop', + 'stopAll': 'doStopAll', + 'doRun': 'doRun', + 'doRunBlockWithArgs': 'doRun', + 'doRunBlockWithArgList': 'doRun', + 'doFork': 'fork', + 'doForkBlockWithArgs': 'fork', + 'doForkBlockWithArgList': 'fork', + 'doReport': 'evaluate', + 'doCallBlockWithArgs': 'evaluate', + 'doCallBlockWithArgList': 'evaluate', + 'doAnswer': 'doReport', + 'doStopBlock': 'doStopBlock', + // 'doPauseThread': '', + // 'doPauseThreadReporter': '', + + // Sensing + 'touching:': 'reportTouchingObject', + 'touchingColor:': 'reportTouchingColor', + 'color:sees:': 'reportColorIsTouchingColor', + 'doAsk': 'doAsk', + 'answer': 'reportLastAnswer', + 'mouseX': 'reportMouseX', + 'mouseY': 'reportMouseY', + 'mousePressed': 'reportMouseDown', + 'keyPressed:': 'reportKeyPressed', + 'distanceTo:': 'reportDistanceTo', + 'timerReset': 'doResetTimer', + 'timer': 'reportTimer', + 'getAttribute:of:': 'reportAttributeOf', + // 'attribute:of:': '', + // 'soundLevel': '', + // 'isLoud': '', + // 'sensor:': '', + // 'sensorPressed:': '', + // 'getObject:': '', + // 'get:': '', + + // Operators + '+': 'reportSum', + '-': 'reportDifference', + '*': 'reportProduct', + '/': 'reportQuotient', + 'randomFrom:to:': 'reportRandom', + '<': 'reportLessThan', + '=': 'reportEquals', + '>': 'reportGreaterThan', + '&': 'reportAnd', + '|': 'reportOr', + 'not': 'reportNot', + 'getTrue': 'reportTrue', + 'getFalse': 'reportFalse', + 'concatenate:with:': 'reportJoinWords', + 'letter:of:': 'reportLetter', + 'stringLength:': 'reportStringSize', + 'asciiCodeOf:': 'reportUnicode', + 'asciiLetter:': 'reportUnicodeAsLetter', + '\\\\': 'reportModulus', + 'rounded': 'reportRound', + 'computeFunction:of:': 'reportMonadic', + 'isObject:type:': 'reportIsA', + // 'procedure': 'reifyScript', + // 'procedureWithArgs': 'reifyScript', + // 'function': 'reifyReporter', + // 'functionWithArgs': 'reifyReporter', + // 'spawn': '', + + // Variables + 'setVar:to:': 'doSetVar', + 'changeVar:by:': 'doChangeVar', + // 'deleteObject:': '', + 'showVariable:': 'doShowVar', + 'hideVariable:': 'doHideVar', + 'doDeclareVariables': 'doDeclareVariables', + 'newList:': 'reportNewList', + 'append:toList:': 'doAddToList', + 'deleteLine:ofList:': 'doDeleteFromList', + 'insert:at:ofList:': 'doInsertInList', + 'setLine:ofList:to:': 'doReplaceInList', + 'getLine:ofList:': 'reportListItem', + 'lineCountOfList:': 'reportListLength', + 'list:contains:': 'reportListContainsItem', + // 'contentsOfList:': '', + // 'copyOfList:': '' + + // Kludge + _warp: 'doWarp' + }; + + function escapeXML(string) { + // over-cautious due to some morphic bugs + return string.replace(/&/g, '&')/*.replace(/[\n\r]|\r\n/g, ' ')*/.replace(/'); + this.children.forEach(function (child) { + if (!child.toXML) { + console.error('Invalid', child); + } + child.toXML(string); + }); + string.push(''); + } else { + string.push('/>'); + } + }; + Element.prototype.toDOM = function () { + var el = document.createElement(this.tagName), + attributes = this.attributes, key, + children = this.children, i = 0, child; + for (key in attributes) if (attributes.hasOwnProperty(key)) { + el.setAttribute(key, attributes[key]); + } + while (child = children[i++]) { + el.appendChild(child.toDOM()); + } + return el; + }; + + function n(tagName, attributes, children) { + var el = new Element(tagName), key; + el.attributes = attributes || {}; + el.children = children || []; + return el; + } + function t(value) { + return new Text(value); + } + + p.write = function (projectName, project) { + var a, xml, out, objectID = 0, customBlocks = {}; + function id(n) { + return n.attributes.id ? n('ref', { id: n.attributes.id }) : n.attributes.id = ++objectID, n; + } + function preCustomBlocks(scope, blocks) { + var sc = customBlocks[scope] = {}; + if (!blocks) return; + blocks.forEach(function (ypr) { + sc[ypr.userSpec] = ypr; + ypr.userSpec = ypr.userSpec.replace(/^\s+|\s+$/g, '').replace(/\s{2,}/g, ' ').replace(/'/g, ''); + ypr.__blockSpec__ = ypr.userSpec.replace(/%(\S+)/g, function (_, name) { + var type = customBlockInputs[ypr.declarations.get(name)]; + if (type === undefined) { + if (ypr.declarations.get(name) === undefined) { + type = '%s'; + } else { + console.warn('Missing input type ' + ypr.declarations.get(name)); + } + } + return type; + }); + ypr.__types__ = (ypr.userSpec.match(/%\S+/g) || []).map(function (arg) { + return ypr.declarations.get(arg.substr(1)) || 'any'; + }); + }); + } + function nsCustomBlocks(blocks, globals) { + if (!blocks) return []; + return (globals ? blocks : blocks.filter(function (b) { return !b.isGlobal })).map(function (ypr) { + var args = (ypr.userSpec.match(/%\S+/g) || []).map(function (arg) { + var name = arg.substr(1), + type = customBlockInputs[ypr.declarations.get(name)]; + if (type === undefined) { + type = '%s'; + } + return n('input', { + type: type + }, [t(ypr.defaults.get(name))]); + }), + spec = ypr.userSpec.replace(/%(\S+)/g, '%\'$1\''); + ypr.body = ypr.body || []; + if (ypr.answer && ypr.answer.pop) { + ypr.body.push(['byob', '', 'doAnswer', ypr.answer[0]]); + } + if (ypr.blockVars.length) { + ypr.body.unshift(['byob', '', 'doDeclareVariables'].concat(ypr.blockVars.map(function (n) { + return [0, 0, 0, n]; + }))); + } + if (ypr.isAtomic) { + ypr.body = [['byob', '', '_warp', ypr.body]]; + } + return n('block-definition', { + s: spec, + // type: ypr.isBoolean ? 'predicate' : ypr.isReporter ? 'reporter' : 'command', + type: ypr.type === 'boolean' ? 'predicate' : ypr.answer !== null ? 'reporter' : 'command', + category: + ypr.category === 'none' ? 'other' : + ypr.category === 'list' ? 'lists' : ypr.category + }, [ + n('inputs', {}, args), + n('script', {}, nsScript(ypr.body)) + ]); + }); + } + function nsScript(script) { + return script.map(function (block) { + return nBlock(block); + }); + } + function nBlock(block) { + var node, a; + node = n('block'); + switch (block[2]) { + case 'EventHatMorph': + if (block[3] === 'Scratch-StartClicked') { + node.attributes.s = 'receiveGo'; + } else { + node.attributes.s = 'receiveMessage'; + node.children.push(n('l', {}, [t(block[3])])); + } + return node; + case 'MouseClickEventHatMorph': + node.attributes.s = 'receiveClick'; + return node; + case 'KeyEventHatMorph': + node.attributes.s = 'receiveKey'; + node.children.push(n('l', {}, [t(block[3])])); + return node; + case 'doRunBlockWithArgs': + case 'doCallBlockWithArgs': + case 'doForkBlockWithArgs': + a = block.length - 5; + while (a--) { + if (block[5 + a].pop) { + block[5 + a] = ['block', '', [block[5 + a]]]; + } + } + a = { + className: 'ScratchListMorph', + complex: block.slice(5) + }; + block = block.slice(0, 4); + block.push(a); + break; + case 'doRunBlockWithArgList': + case 'doCallBlockWithArgList': + case 'doForkBlockWithArgList': + a = block[5]; + block = block.slice(0, 4); + block.push(a); + break; + case 'doDeclareVariables': + block = ['byob', '', 'doDeclareVariables', { + className: 'ScratchListMorph', + complex: block.slice(3).map(function (block) { + return block[3]; + }) + }]; + break; + case 'getLine:ofList:': + case 'append:toList:': + case 'deleteLine:ofList:': + case 'setLine:ofList:to:': + case 'getLine:ofList:': + case 'insert:at:ofList:': + case 'lineCountOfList:': + case 'list:contains:': + a = block[2] === 'insert:at:ofList:' ? 5 : + block[2] === 'lineCountOfList:' || block[2] === 'list:contains:' ? 3 : 4; + if (typeof block[a] === 'string') { + block[a] = block[a] === '' ? null : ['byob', '', 'readVariable', block[a]]; + } + break; + case 'readBlockVariable': + case 'readVariable': + case 'listNamed:': + node.attributes['var'] = block[3]; + return node; + case 'function': + case 'functionWithArgs': + node.attributes.s = 'reifyReporter'; + block.pop(); + node.children.push(n('autolambda', {}, [nBlock(block[8])])); + if (block[2] === 'functionWithArgs') { + node.children.push(n('list', {}, block.slice(9).map(function (arg) { + return n('l', {}, [t(arg[3])]); + }))); + } + return node; + case 'procedure': + case 'procedureWithArgs': + node.attributes.s = 'reifyScript'; + node.children.push(n('script', {}, nsScript(block.pop()))); + node.children.push(n('list', {}, block.slice(8).map(function (arg) { + return n('l', {}, [t(arg[3])]); + }))); + return node; + case 'autoBlock': + case 'autoPredicate': + node.attributes.s = block[2] === 'autoPredicate' ? 'reifyPredicate' : 'reifyReporter'; + node.children.push(n('autolambda', {}, block[3] ? [nBlock(block[3][0])] : [])); + return node; + case 'concatenate:with:': + block = block.slice(0, 3).concat({ + className: 'ScratchListMorph', + complex: block.slice(3) + }); + a = 2; + while (a--) { + if (block[3].complex[a].pop) { + block[3].complex[a] = ['block', '', [block[3].complex[a]]]; + } + } + break; + case 'loopLambda': + return n('script', {}, block[8] ? nsScript(block[8]) : []); + case 'autoLambda': + // node.attributes.s = block[2] === 'loopLambda' ? 'reifyScript' : 'reifyReporter'; + node.attributes.s = 'reifyScript'; + node.children.push(n('script', {}, block[8] ? nsScript(block[8]) : [])); + return node; + case 'changeVariable': + case 'changeBlockVariable': + block = [0, 0, block[4], block[3], block[block[2] === 'changeBlockVariable' ? 6 : 5]]; + break; + case 'distanceTo:': + case 'gotoSpriteOrMouse:': + case 'pointTowards:': + case 'touching:': + if (block[3] === 'mouse') { + block[3] = 'mouse-pointer'; + } else { + block[3] = block[3].objName; + } + break; + case 'doForeverIf': + block = [0, 0, 'doForever', [[0, 0, 'doIf', block[3], block[4]]]] + break; + case '\\\\': + case '+': + case '-': + case '*': + case '/': + if (block[3] === ' ') { + block[3] = ''; + } + if (block[4] === ' ') { + block[4] = ''; + } + break; + case 'setPenHueTo:': + case 'changePenHueBy:': + block[3] = ['byob', 0, '/', block[3], '2']; + break; + } + if (block[2] === 'doCustomBlock') { + a = customBlocks[block[1] === 'Stage' ? '__global__' : block[1]][block[3]]; + node = n('custom-block', { + s: a.__blockSpec__ + }); + if (block[1] !== 'Stage' && !a.isGlobal) { + node.attributes.scope = block[1]; + } + a.__types__.forEach(function (type, i) { + if (type === 'template') { + block[4 + i] = block[4 + i][3]; + } + }); + block.shift(); + } else { + if ((node.attributes.s = blockSelectors[block[2]]) === undefined) { + console.warn('Missing selector mapping for "' + block[2] + '"', block); + } + } + block.slice(3).forEach(function (arg) { + if (arg && arg.pop) { + if (typeof arg[0] === 'string') { + node.children.push(nBlock(arg)); + } else { + node.children.push(n('script', {}, nsScript(arg))); + } + } else { + a = nValue(arg); + delete a.attributes.id; + node.children.push(a); + } + }); + return node; + } + function nsVariables(vars, lists) { + return vars.keys.map(function (key, i) { + return n('variable', { name: key }, [nValue(vars.values[i], true)]); + }).concat(lists.keys.map(function (key, i) { + return n('variable', { name: key }, [nValue(lists.values[i], true)]); + })); + } + function nValue(value, listHasItems) { + return typeof value === 'string' || typeof value === 'number' ? n('l', {}, [t(value)]) : + value == null || typeof value === 'boolean' ? n('l') : + value.className === 'ScratchListMorph' ? id(n('list', {}, value.complex.map(function (object, i) { + var item = object == null ? n('l', {}, [t((item = value.strings[i]) === 'nil' ? '' : item)]) : + typeof object === 'string' ? nValue(object) : + object && object[0] === 'block' ? nsScript(object[2])[0] : n('l'); + return listHasItems ? n('item', {}, [item]) : item; + }))) : + // (function () { throw 'Unimplemented' })(); + value.constructor === sb.Color ? n('color', {}, [t([value.r * 255, value.g * 255, value.b * 255, value.a * 255])]) : + value.constructor === sb.Dictionary ? id(n('list')) : + (function () { console.warn('No serializer for', value); return n('__undefined__') })(); + } + function nsCostumes(media) { + return [id(n('list', {}, media.filter(function (m) { return m.className === 'ImageMedia' }).map(function (c) { + return n('item', {}, [ + n('costume', { + name: c.mediaName, + 'center-x': c.rotationCenter.x, + 'center-y': c.rotationCenter.y, + image: c.form.image.toDataURL() + }) + ]); + })))]; + } + function nsSounds(media) { + if (media.filter(function (m) { return m.className === 'SoundMedia' }).length > 0) { + console.warn('Sounds are unimplemented'); + } + return [id(n('list'))]; + } + function nsScripts(scripts) { + return scripts.map(function (script) { + if (script[1][0] && script[1][0][2] === 'scratchComment') { + return n('comment', { + x: script[0].x, + y: script[0].y, + w: script[1][0][5], + collapsed: !script[1][0][4] + }, [t(script[1][0][3])]); + } + return n('script', { + x: script[0].x, + y: script[0].y + }, nsScript(script[1])); + }); + } + function nsSprites(sprites, vars, lists) { + return sprites.map(function (sprite, i) { + return id(n('sprite', { + name: sprite.objName, + x: (sprite.bounds.origin.x + sprite.bounds.corner.x) / 2 - 240, + y: 180 - (sprite.bounds.origin.y + sprite.bounds.corner.y) / 2, + hidden: sprite.flags === 1, + heading: sprite.rotationDegrees + 90, + scale: sprite.scalePoint.x, + rotation: rotationStyles[sprite.rotationStyle], + draggable: sprite.draggable, + costume: costumeIndex(sprite), + color: (sprite.color.r * 255 | 0) + ',' + (sprite.color.g * 255 | 0) + ',' + (sprite.color.b * 255 | 0), + pen: 'tip', + idx: i + }, [ + n('variables', {}, nsVariables(sprite.vars, sprite.lists)), + n('costumes', {}, nsCostumes(sprite.media)), + n('sounds', {}, nsSounds(sprite.media)), + n('blocks', {}, nsCustomBlocks(sprite.customBlocks, false)), + n('scripts', {}, nsScripts(sprite.blocksBin)) + ])); + }).concat(vars.keys.concat(lists.keys).map(function (name) { + return n('watcher', { + 'var': name, + style: 'normal', + x: 10, + y: 10, + color: '243,118,29', + hidden: true + }); + })); + } + + console.log(project); + preCustomBlocks('__global__', project.stage.customBlocks); + project.stage.sprites.forEach(function (sprite) { + preCustomBlocks(sprite.objName, sprite.customBlocks); + }); + xml = n('project', { + name: projectName, + app: 'Snap! 4.0, http://snap.berkeley.edu; serialized by yprxml, http://dl.dropbox.com/u/10715865/Web/snap/app/yprxml.html', + version: '1' + }, [ + n('notes', {}, [t(project.info.get('comment') || '')]), + n('thumbnail', {}, [t((a = project.info.get('thumbnail')) ? a.image.toDataURL() : '')]), + id(n('stage', { + name: 'Stage', + tempo: project.stage.tempoBPM, + costume: costumeIndex(project.stage), + threadsafe: 'false', + scheduled: true + }, [ + n('pentrails', {}, [t((a = project.info.get('penTrails')) ? a.image.toDataURL() : '')]), + n('costumes', {}, nsCostumes(project.stage.media)), + n('sounds', {}, nsSounds(project.stage.media)), + n('blocks', {}, []), + n('variables', {}, []), + n('scripts', {}, nsScripts(project.stage.blocksBin)), + n('sprites', {}, nsSprites(project.stage.sprites, project.stage.vars, project.stage.lists)) + ])), + n('variables', {}, nsVariables(project.stage.vars, project.stage.lists)), + n('blocks', {}, nsCustomBlocks(project.stage.customBlocks, true)) + ]); + xml.toXML(out = []); + // var d = document.createElement('textarea'); + // d.style.position = 'absolute'; + // document.body.appendChild(d).value = out.join(''); + return out.join(''); + }; + + })(sb.XMLWriter = function () {}, sb.XMLWriter.prototype); + + return sb; + +})({}); diff --git a/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/WEB-INF/web.xml b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/WEB-INF/web.xml new file mode 100644 index 0000000..0277f67 --- /dev/null +++ b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/WEB-INF/web.xml @@ -0,0 +1,33 @@ + + + BirdBrainRobotServer + + + index.html + FinchSnapBlocks.xml + HummingbirdSnapBlocks.xml + FinchHummingbirdSnapBlocks.xml + SayThisBlock.xml + + + + cross-origin + org.eclipse.jetty.servlets.CrossOriginFilter + + allowedOrigins + * + + + allowedMethods + * + + + allowedHeaders + * + + + + cross-origin + /* + + \ No newline at end of file diff --git a/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/index.html b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/index.html new file mode 100644 index 0000000..b5ab340 --- /dev/null +++ b/Debian Package Folder/usr/lib/birdbrainrobotserver/WebContent/index.html @@ -0,0 +1,40 @@ +Welcome to the Finch and Hummingbird Server
+If you have a Finch plugged in, use the following URLs to control it: + +
    +
  • finch/out/motor/left/right
  • +
  • finch/out/buzzer/frequency/duration
  • +
  • finch/out/led/red/green/blue
  • +
  • finch/in/lights
  • +
  • finch/in/lightLeft
  • +
  • finch/in/lightRight
  • +
  • finch/in/obstacles
  • +
  • finch/in/obstacleLeft
  • +
  • finch/in/obstacleRight
  • +
  • finch/in/accelerations
  • +
  • finch/in/accelerationX
  • +
  • finch/in/accelerationY
  • +
  • finch/in/accelerationZ
  • +
  • finch/in/temperature
  • +
+ +For Hummingbird, do: +
    +
  • hummingbird/out/motor/port/speed (port 1 or 2, speed -100 to 100)
  • +
  • hummingbird/out/servo/port/position (port 1 to 4, position 0 to 160)
  • +
  • hummingbird/out/vibration/port/speed (port 1 or 2, speed 0 to 100)
  • +
  • hummingbird/out/led/port/intensity (port 1 to 4, intensity 0 to 100)
  • +
  • hummingbird/out/triled/port/R/G/B (port 1 or 2, R, G, B are intensities 0 to 100)
  • +
  • hummingbird/in/sensors (all four sensors, scaled to 0 to 100)
  • +
  • hummingbird/in/sensor/position (value at position x)
  • +
  • hummingbird/in/distance/position (value at position x in cm if it's a distance sensor)
  • +
  • hummingbird/in/temperature/position (value at position x if it's a temperature sensor)
  • +
+More at
www.finchrobot.com and www.hummingbirdkit.com + +Here's a little easter egg you can run without Finch or Hummingbird; we've given you a text to speech converter: +
    +
  • speak/whatever you want to have the computer say
+ + + \ No newline at end of file diff --git a/Debian Package Folder/usr/lib/libhidapi32.so b/Debian Package Folder/usr/lib/libhidapi32.so new file mode 100644 index 0000000..27e2bb3 Binary files /dev/null and b/Debian Package Folder/usr/lib/libhidapi32.so differ diff --git a/Debian Package Folder/usr/lib/libhidapi64.so b/Debian Package Folder/usr/lib/libhidapi64.so new file mode 100644 index 0000000..3ae69d4 Binary files /dev/null and b/Debian Package Folder/usr/lib/libhidapi64.so differ diff --git a/Debian Package Folder/usr/share/applications/birdbrainrobotserver.desktop b/Debian Package Folder/usr/share/applications/birdbrainrobotserver.desktop new file mode 100644 index 0000000..dfb2aa1 --- /dev/null +++ b/Debian Package Folder/usr/share/applications/birdbrainrobotserver.desktop @@ -0,0 +1,10 @@ +[Desktop Entry] +Encoding=UTF-8 +Name=BirdBrainRobotServer +Comment=For programming Finch and Hummingbird in Snap! +Exec=/usr/bin/LaunchRobotServer +Icon=/usr/lib/birdbrainrobotserver/BirdBrainLogo.png +Terminal=false +Type=Application +Categories=GNOME;Application;Network +StartupNotify=true diff --git a/Debian Package Folder/usr/share/doc/applications/birdbrainrobotserver.desktop b/Debian Package Folder/usr/share/doc/applications/birdbrainrobotserver.desktop new file mode 100644 index 0000000..7f235de --- /dev/null +++ b/Debian Package Folder/usr/share/doc/applications/birdbrainrobotserver.desktop @@ -0,0 +1,12 @@ + +[Desktop Entry] +Encoding=UTF-8 +Name=BirdBrainRobotServer +Comment=For programming Finch and Hummingbird in Snap! +Exec=/usr/bin/LaunchRobotServer +Icon=/usr/lib/birdbrainrobotserver/BirdBrainLogo.png +Terminal=false +Type=Application +Categories=GNOME;Application;Network +StartupNotify=true +Comment[en_US.UTF-8]=For programming Finch and Hummingbird in Snap! diff --git a/Debian Package Folder/usr/share/doc/birdbrain-robot-server/copyright b/Debian Package Folder/usr/share/doc/birdbrain-robot-server/copyright new file mode 100644 index 0000000..77ca910 --- /dev/null +++ b/Debian Package Folder/usr/share/doc/birdbrain-robot-server/copyright @@ -0,0 +1,28 @@ +Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Name: BirdBrain Robot Server +Source: http://www.hummingbirdkit.com/learning/software/snap + +Files: * +Copyright: Copyright 2013 Tom Lauwers +License: GPL-2+ + This program is free software; you can redistribute it + and/or modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (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 package; if not, write to the Free + Software Foundation, Inc., 51 Franklin St, Fifth Floor, + Boston, MA 02110-1301 USA + . + On Debian systems, the full text of the GNU General Public + License version 2 can be found in the file + `/usr/share/common-licenses/GPL-2'. + diff --git a/README.md b/README.md new file mode 100644 index 0000000..5dde848 --- /dev/null +++ b/README.md @@ -0,0 +1,27 @@ +BirdBrainSnap +================ + +Snap! is a drag and drop programming environment developed by Jens Mönig and Brian Harvey. Snap! is a descendant of Scratch and adds a number of key features like creating custom blocks, recursion, and running in a browser. + +We have developed a utility, the BirdBrain Robot Server, that allows Finch and Hummingbird to be controlled from within Snap!. + +Much more information at http://www.hummingbirdkit.com/learning/software/snap + +#### Requirements + + Java + Web Browser (tested with Chrome and Firefox) + +#### Preparing for development + +This repo contains all of the files necessary for building the BirdBrain Robot Server. We created the project in Eclipse, so just import the project into Eclipse if you intend to work with it. + +#### Installing + +Installation methods will vary depending on platform. +Included is a source directory for creating a debian package. + +#### Contact + + Direct any questions to tlauwers@birdbraintechnologies.com +