diff --git a/.classpath b/.classpath new file mode 100755 index 0000000..1f4a216 --- /dev/null +++ b/.classpath @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/.externalToolBuilders/New_Builder (1).launch b/.externalToolBuilders/New_Builder (1).launch new file mode 100644 index 0000000..e0c0678 --- /dev/null +++ b/.externalToolBuilders/New_Builder (1).launch @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..685678b --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +bin/ +doc/ +Plugin_Telemicroscopia.jar diff --git a/.project b/.project new file mode 100644 index 0000000..82501f0 --- /dev/null +++ b/.project @@ -0,0 +1,27 @@ + + + Plugin_Telemicroscopia + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.ui.externaltools.ExternalToolBuilder + auto,full,incremental, + + + LaunchConfigHandle + <project>/.externalToolBuilders/New_Builder (1).launch + + + + + + org.eclipse.jdt.core.javanature + + diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..7341ab1 --- /dev/null +++ b/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,11 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=1.7 +org.eclipse.jdt.core.compiler.debug.lineNumber=generate +org.eclipse.jdt.core.compiler.debug.localVariable=generate +org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.7 diff --git a/LICENCSE.txt b/LICENCSE.txt new file mode 100755 index 0000000..94a9ed0 --- /dev/null +++ b/LICENCSE.txt @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/build.xml b/build.xml new file mode 100755 index 0000000..85bf059 --- /dev/null +++ b/build.xml @@ -0,0 +1,45 @@ + + + Plugin_Telemicroscopia: build file + + + + + + + + + + Building the .jar file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/commons-codec-1.9.jar b/lib/commons-codec-1.9.jar new file mode 100644 index 0000000..ef35f1c Binary files /dev/null and b/lib/commons-codec-1.9.jar differ diff --git a/lib/ij.jar b/lib/ij.jar new file mode 100755 index 0000000..656b9e6 Binary files /dev/null and b/lib/ij.jar differ diff --git a/lib/slf4j-api-1.7.5.jar b/lib/slf4j-api-1.7.5.jar new file mode 100644 index 0000000..8766455 Binary files /dev/null and b/lib/slf4j-api-1.7.5.jar differ diff --git a/src/ActivateCamera_.java b/src/ActivateCamera_.java new file mode 100644 index 0000000..57f8b85 --- /dev/null +++ b/src/ActivateCamera_.java @@ -0,0 +1,68 @@ +import ij.Macro; +import ij.plugin.PlugIn; + +import java.io.IOException; +import java.net.BindException; + +import javax.swing.JOptionPane; + +import util.StatusIndicator; + +import communication.v4l.CamServer; + +/** + * This plugin runs when the activate/deactivate camera button is pushed. + * + * @author ehas + * + */ +public class ActivateCamera_ implements PlugIn { + + public void run(String arg) { + + // This variable is used to avoid the execution of several run() + // threads. + // This happens when the activate/deactivate camera button is pushed + // many times quickly. + CamServer.LockCamServerInit.lock(); + + // This variable is: true if the camera has been enabled, false if + // disabled + String state = Macro.getOptions().trim(); + + if (state.equals("true") && !CamServer.active) { + + try { + CamServer.start(); + } catch (IOException e) { + if(e instanceof java.net.BindException){ + JOptionPane.showMessageDialog(null, + "No se ha podido inicializar el servidor de internet.\n" + + "Comprueba que el puerto " + CamServer.port + " está libre " + + "y que tienes los\npermisos necesarios para ocuparlo.", + "Fallo inicializando el servidor de internet", + JOptionPane.ERROR_MESSAGE); + } else { + JOptionPane.showMessageDialog(null, + "No se ha podido inicializar el servidor de internet", + "Fallo inicializando el servidor de internet", + JOptionPane.ERROR_MESSAGE); + } + if (CamServer.indicator != null) { + CamServer.indicator.setState(StatusIndicator.DOWN); + } + if (CamServer.cameraCheckBox != null){ + CamServer.cameraCheckBox.setSelected(false); + } + } + } + // If camera has been disabled, the CamServer instance stops + else if (state.equals("false")) { + if (CamServer.active) { + CamServer.stop(); + } + } + CamServer.LockCamServerInit.unlock(); + } + +} diff --git a/src/ActivateViewer_.java b/src/ActivateViewer_.java new file mode 100644 index 0000000..ecb5a04 --- /dev/null +++ b/src/ActivateViewer_.java @@ -0,0 +1,32 @@ +import ij.ImagePlus; +import ij.WindowManager; +import ij.gui.ImageCanvas; +import ij.plugin.PlugIn; + +import communication.v4l.CamServer; + +/** + * This plugin runs when the activate viewer button is pushed. If the CamServer + * has not been initialized, it does nothing. + * + * @author ehas + * + */ +public class ActivateViewer_ implements PlugIn { + + @Override + public void run(String arg0) { + + // If CamServer is active, it notifies that the button has been pushed + if (CamServer.active == true) { + try { + synchronized (CamServer.LockViewer) { + CamServer.LockViewer.notify(); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + } +} diff --git a/src/Capture_.java b/src/Capture_.java new file mode 100644 index 0000000..b02e52a --- /dev/null +++ b/src/Capture_.java @@ -0,0 +1,22 @@ +import ij.plugin.PlugIn; + +import communication.v4l.CamServer; + +/** + * This plugin runs when the Capture button is pushed. + * + * @author ehas + * + */ +public class Capture_ implements PlugIn { + public void run(String arg) { + + try { + synchronized (CamServer.LockLocal) { + CamServer.setLocalCapture(true); + } + } catch (Exception e) { + e.printStackTrace(); + } + } +} diff --git a/src/Close_.java b/src/Close_.java new file mode 100644 index 0000000..016df7e --- /dev/null +++ b/src/Close_.java @@ -0,0 +1,46 @@ +import gestion.GestorSave; +import ij.ImagePlus; +import ij.WindowManager; +import ij.macro.MacroRunner; +import ij.plugin.PlugIn; + +import javax.swing.JOptionPane; + +import edition.ImageManager; +import edition.RoiManager; + +/** + * This plugin runs when the close button is pushed. This is the right way to + * close the whole working area: Roi manager, Images manager and Displayer. + * + * @author ehas + * + */ +public class Close_ implements PlugIn { + + @Override + public void run(String arg0) { + ImagePlus current = WindowManager.getCurrentImage(); + if (current != null) { + // First ask for saving the current image + int result = JOptionPane.showConfirmDialog(null, + "¿Desea guardar el fichero actual?", "Guardar cambios", + JOptionPane.YES_NO_CANCEL_OPTION); + switch (result) { + case 0: + GestorSave saver = new GestorSave(); + saver.saveAsTifInZip(current, false); + case 1: + new MacroRunner("run(\"ForceClose \")\n"); + break; + default: + // Exit the dialog and do nothing + return; + } + } else { + JOptionPane.showMessageDialog(null, "No hay imágenes abiertas", + "Fallo cerrando ficheros", JOptionPane.ERROR_MESSAGE); + } + } + +} diff --git a/src/ForceClose_.java b/src/ForceClose_.java new file mode 100644 index 0000000..dab2fec --- /dev/null +++ b/src/ForceClose_.java @@ -0,0 +1,30 @@ +import edition.ImageManager; +import edition.RoiManager; +import ij.ImagePlus; +import ij.WindowManager; +import ij.plugin.PlugIn; + + +public class ForceClose_ implements PlugIn { + + @Override + public void run(String arg0) { + ImagePlus current = WindowManager.getCurrentImage(); + if (current != null) { + // Close everything related to the current session + if (RoiManager.getInstance() != null) { + RoiManager.getInstance().close(); + RoiManager.setInstance(null); + } + if (ImageManager.getInstance() != null) { + ImageManager.getInstance().close(); + } + while (WindowManager.getCurrentImage() != null) { + WindowManager.getCurrentImage().close(); + WindowManager.setTempCurrentImage(null); + } + } + + } + +} diff --git a/src/Initialize_.java b/src/Initialize_.java new file mode 100644 index 0000000..2d268d7 --- /dev/null +++ b/src/Initialize_.java @@ -0,0 +1,58 @@ +import ij.IJ; +import ij.Prefs; +import ij.plugin.PlugIn; + +import java.io.IOException; +import java.util.Hashtable; + +import au.edu.jcu.v4l4j.exceptions.V4L4JException; + +import communication.v4l.CamServer; +import communication.v4l.Settings; + +/** + * This plugin runs at the beginning of the telemicroscopy toolbar creation. + * + * @author ehas + * + */ +public class Initialize_ implements PlugIn { + + public void run(String arg) { + + // Set the useJFileChooser preference to false in order to avoid the + // change of Look&Feel when managing windows + Prefs.useJFileChooser = false; + + // Hide the main IJ instance + IJ.getInstance().setVisible(false); + + // Initialize the values with a device /dev/video0 + String defaultDevice = Settings.getDefaultVideoDevice(); + if (defaultDevice == null) { + // TODO print message + } else { + Hashtable defaultSettings = Settings + .getDefaultSettings(defaultDevice); + try { + String videoResolution = defaultSettings.get("videoResolution"); + String captureResolution = defaultSettings + .get("captureResolution"); + String interval = defaultSettings.get("interval"); + CamServer.initialize(defaultDevice, + Integer.parseInt(videoResolution.split("x")[0]), + Integer.parseInt(videoResolution.split("x")[1]), + Integer.parseInt(captureResolution.split("x")[0]), + Integer.parseInt(captureResolution.split("x")[1]), + CamServer.port, Integer.parseInt(interval.split("/")[0]), + Integer.parseInt(interval.split("/")[1])); + } catch (NumberFormatException e) { + e.printStackTrace(); + } catch (V4L4JException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } +} diff --git a/src/Menu_Customized.java b/src/Menu_Customized.java new file mode 100644 index 0000000..d4129be --- /dev/null +++ b/src/Menu_Customized.java @@ -0,0 +1,615 @@ +import gestion.GestorSave; +import ij.IJ; +import ij.ImagePlus; +import ij.Macro; +import ij.Prefs; +import ij.WindowManager; +import ij.gui.ImageWindow; +import ij.gui.Toolbar; +import ij.macro.MacroRunner; +import ij.plugin.MacroInstaller; +import ij.plugin.PlugIn; + +import java.awt.Dimension; +import java.awt.Font; +import java.awt.Frame; +import java.awt.GridLayout; +import java.awt.Image; +import java.awt.Insets; +import java.awt.MouseInfo; +import java.awt.Toolkit; +import java.awt.dnd.DropTarget; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.swing.ImageIcon; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JToolBar; +import javax.swing.UIDefaults; +import javax.swing.UIManager; + +import util.ConfigFile; +import util.StatusIndicator; + +import communication.v4l.CamServer; +import communication.v4l.SurveyForm; + +/** + * @author jerome.mutterer(at)ibmp.fr + * + */ + +public class Menu_Customized implements PlugIn, ActionListener { + + // private static final long serialVersionUID = 7436194992984622141L; + String macrodir = IJ.getDirectory("macros"); + String name, title, path; + String startupAction = ""; + String codeLibrary = ""; + String separator = System.getProperty("file.separator"); + JFrame frame = new JFrame(); + Frame frontframe; + int xfw = 0; + int yfw = 0; + int wfw = 0; + int hfw = 0; + int preferredSize = 0; + JToolBar toolBar = null; + boolean tbOpenned = false; + boolean grid = true; + boolean visible = true; + boolean shouldExit = false; + boolean showSurveyForm = false; + JButton button = null; + JCheckBox checkBox = null; + private boolean isPopup = false; + private boolean isSticky = false; + int nButtons = 0; + + public void run(String s) { + // s used if called from another plugin, or from an installed command. + // arg used when called from a run("command", arg) macro function + // if both are empty, we choose to run the assistant "createAB.txt" + + String arg = Macro.getOptions(); + + if (arg == null && s.equals("")) { + try { + File macro = new File(Menu_Customized.class.getResource( + "createAB.txt").getFile()); + new MacroRunner(macro); + return; + } catch (Exception e) { + IJ.error("createAB.txt file not found"); + } + + } else if (arg == null) { // call from an installed command + path = IJ.getDirectory("startup") + s; + try { + name = path.substring(path.lastIndexOf("/") + 1); + } catch (Exception e) { + } + } else { // called from a macro by run("Action Bar",arg) + path = IJ.getDirectory("startup") + arg; + try { + path = path.substring(0, path.indexOf(".txt") + 4); + name = path.substring(path.lastIndexOf("/") + 1); + } catch (Exception e) { + } + } + + // title = name.substring(0, name.indexOf(".")); + title = name.substring(0, name.indexOf(".")).replaceAll("_", " ") + .trim(); + frame.setTitle(title); + + if (WindowManager.getFrame(title) != null) { + WindowManager.getFrame(title).toFront(); + return; + } + + // Change the default font size + if (ConfigFile.configFile == null){ + ConfigFile.setConfigFile(CamServer.configFile); + } + String sizeS = ConfigFile.getValue(ConfigFile.FONT_SIZE); + if (sizeS != null){ + preferredSize = Integer.parseInt(sizeS); + + UIDefaults defaults = UIManager.getDefaults(); + List newDefaults = new ArrayList(); + Map newFonts = new HashMap(); + + Enumeration en = defaults.keys(); + while (en.hasMoreElements()) { + Object key = en.nextElement(); + Object value = defaults.get(key); + if (value instanceof Font) { + Font oldFont = (Font)value; + Font newFont = newFonts.get(oldFont); + if (newFont == null) { + newFont = new Font(oldFont.getName(), oldFont.getStyle(), preferredSize); + newFonts.put(oldFont, newFont); + } + newDefaults.add(key); + newDefaults.add(newFont); + } + } + defaults.putDefaults(newDefaults.toArray()); + } + + + String showSurveyFormS = ConfigFile.getValue(ConfigFile.SHOW_SURVEY_FORM); + if (showSurveyFormS != null && showSurveyFormS.equalsIgnoreCase("true")){ + showSurveyForm = true; + } + + frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); + + // this listener will save the bar's position and close it. + frame.addWindowListener(new WindowAdapter() { + public void windowClosing(WindowEvent e) { + int result = JOptionPane.showConfirmDialog(null, + "¿Seguro que desea salir?", "Salir", + JOptionPane.YES_NO_OPTION); + switch(result){ + case 0: + ImagePlus current = WindowManager.getCurrentImage(); + if (current != null){ + int resultSave = JOptionPane.showConfirmDialog(null, + "Guardar los cambios\n" + + "¿Desea guardar la sesión actual?", "Guardar cambios", + JOptionPane.YES_NO_OPTION); + switch(resultSave){ + case 0: + GestorSave saver = new GestorSave(); + saver.saveAsTifInZip(current, false); + new MacroRunner("run(\"ForceClose \")\n"); + case 1: + break; + } + } + break; + case 1: + return; + } + + rememberXYlocation(); + Prefs.savePreferences(); + e.getWindow().dispose(); + WindowManager.removeWindow((Frame) frame); + + if (showSurveyForm){ + new SurveyForm(); + } else { + System.exit(0); + } + } + }); + + frontframe = WindowManager.getFrontWindow(); + + // toolbars will be added as lines in a n(0) rows 1 column layout + frame.getContentPane().setLayout(new GridLayout(0, 1)); + + // set an application icon image + ImageIcon image = new ImageIcon("ImageJ.png"); + frame.setIconImage(image.getImage()); + + // read the config file, and add toolbars to the frame + designPanel(); + + // captures the ImageJ KeyListener + frame.setFocusable(true); + frame.addKeyListener(IJ.getInstance()); + + // setup the frame, and display it + frame.setResizable(false); + + if (!isPopup) { + + frame.setLocation( + (int) Prefs.get("actionbar" + title + ".xloc", 20), + (int) Prefs.get("actionbar" + title + ".yloc", 20)); + WindowManager.addWindow(frame); + } + + else { + frame.setLocation(MouseInfo.getPointerInfo().getLocation()); + frame.setUndecorated(true); + frame.addKeyListener(new KeyListener() { + public void keyReleased(KeyEvent e) { + } + + public void keyTyped(KeyEvent e) { + } + + public void keyPressed(KeyEvent e) { + int code = e.getKeyCode(); + if (code == KeyEvent.VK_ESCAPE) { + frame.dispose(); + WindowManager.removeWindow(frame); + } + } + }); + } + + if (isSticky) { + frame.setUndecorated(true); + } + frame.pack(); + frame.setVisible(true); + if (startupAction != "") + try { + new MacroRunner(startupAction + "\n" + codeLibrary); + } catch (Exception fe) { + } + + WindowManager.setWindow(frontframe); + + if (isSticky) { + stickToActiveWindow(); + while ((shouldExit == false) && (frame.getTitle() != "xxxx")) { + try { + + ImageWindow fw = WindowManager.getCurrentWindow(); + if (fw == null) + frame.setVisible(false); + if ((fw != null) && (fw.getLocation().x != xfw) + || (fw.getLocation().y != yfw) + || (fw.getWidth() != wfw) + || (fw.getHeight() != hfw)) { + xfw = fw.getLocation().x; + yfw = fw.getLocation().y; + wfw = fw.getWidth(); + hfw = fw.getHeight(); + stickToActiveWindow(); + } + } catch (Exception e) { + // TODO: handle exception + } + IJ.wait(20); + } + if (frame.getTitle() == "xxxx") + closeActionBar(); + if ((shouldExit)) + return; + } + + } + + private void stickToActiveWindow() { + ImageWindow fw = WindowManager.getCurrentWindow(); + try { + if (fw != null) { + if (!frame.isVisible()) + frame.setVisible(true); + frame.toFront(); + frame.setLocation(fw.getLocation().x + fw.getWidth(), + fw.getLocation().y); + fw.toFront(); + } + } catch (Exception e) { + // TODO: handle exception + } + + } + + private void closeActionBar() { + frame.dispose(); + WindowManager.removeWindow(frame); + WindowManager.setWindow(frontframe); + shouldExit = true; + } + + private void designPanel() { + try { + File file = new File(path); + if (!file.exists()) + IJ.error("Config File not found"); + BufferedReader r = new BufferedReader(new FileReader(file)); + while (true) { + String s = r.readLine(); + if (s.equals(null)) { + r.close(); + closeToolBar(); + break; + } else if (s.startsWith("
")) { + // setABasMain(); + hideIJ(); + } else if (s.startsWith("")) { + isPopup = true; + } else if (s.startsWith("")) { + isSticky = true; + } else if (s.startsWith("")) { + setABDnD(); + } else if (s.startsWith("")) { + setABonTop(); + } else if (s.startsWith("")) { + String code = ""; + while (true) { + String sc = r.readLine(); + if (sc.equals(null)) { + break; + } + if (!sc.startsWith("")) { + code = code + "" + sc; + } else { + startupAction = code; + break; + } + } + } else if (s.startsWith("")) { + String code = ""; + while (true) { + String sc = r.readLine(); + if (sc.equals(null)) { + break; + } + if (!sc.startsWith("")) { + code = code + "" + sc; + } else { + codeLibrary = code; + break; + } + } + } else if (s.startsWith("")) { + String frameiconName = r.readLine().substring(5); + setABIcon(frameiconName); + } else if (s.startsWith("") && tbOpenned == false) { + grid = false; + } else if (s.startsWith("") && tbOpenned == false) { + frame.getContentPane().add(new JLabel(s.substring(6))); + } else if (s.startsWith("") && tbOpenned == false) { + toolBar = new JToolBar(); + nButtons = 0; + tbOpenned = true; + } else if (s.startsWith("") && tbOpenned == true) { + closeToolBar(); + tbOpenned = false; + } else if (s.startsWith("") && tbOpenned == true) { + toolBar.addSeparator(); + } else if (s.startsWith("" + + ""; + + private static String refreshButton = "
" + + "" + + "
"; + + private static String mainPageHTML = "" + + "" + + "" + + "" + + CamServer.streamer.getHeadHTMLText() + + "Servidor de telemicroscopia" + + "" + + "" + + "" + + "" + //+ "" + //+ "" + + CamServer.streamer.getBodyHTMLText() + //+ "
" + //+ "
" + + captureButton + refreshButton + "" + ""; + + private static String zoomPage = "" + + "" + + "" + + "" + + CamServer.streamer.getHeadHTMLText() + + "Servidor de telemicroscopia" + + "" + + "" + + CamServer.streamer.getBodyHTMLText() + + "
" + + "
" + + captureButton + refreshButton + "" + ""; + + private static String mainPageHTMLOld = "" + + "" + + "" + + "Servidor de telemicroscopía" + + "" + + "" + + "" + + "" + + "" + + ""; + + private static String webcamPageHTML = "" + "" + "" + + "" + "" + "" + "" + + "
" + "
" + "" + ""; + + private static String controlPageHTMLHeader = "" + "" + + ""; + + private static String controlPageHTMLFooter = "
" + "" + + ""; + + private Socket clientSocket; + private BufferedReader inStream; + private DataOutputStream outStream; + + /** + * Builds an object handling a tcp connection to one client. Sends the MJPEG + * header straight away + * + * @param client + * the client who just connected to us + * @param in + * the input stream + * @param out + * the ouput stream + * @throws IOException + * if there is an error get in/output streams + */ + public ClientConnection(Socket client, BufferedReader in, + DataOutputStream out) throws IOException { + if ((client == null) || (in == null) || (out == null)) + throw new NullPointerException("client, in and out cannot be null"); + + clientSocket = client; + inStream = in; + outStream = out; + + // send mjpeg header + outStream.writeBytes(mjpegHeader); + } + + /** + * Close the input and output streams and closes the socket + */ + public void stop() { + try { + System.out.println("Disconnected from " + + clientSocket.getInetAddress().getHostAddress() + ":" + + clientSocket.getPort()); + + inStream.close(); + outStream.close(); + clientSocket.close(); + } catch (IOException e) { + // error closing connection with client + e.printStackTrace(); + } + } + + /** + * Send the given frame in an mpjeg frame header + * + * @param frame + * the frame to be send + * @throws IOException + * if there is an error writing over the socket + */ + public void sendNextFrame(VideoFrame frame) throws IOException { + outStream.writeBytes(mjpegFrameheader + + Integer.toString(frame.getFrameLength()) + "\r\n\r\n"); + outStream.write(frame.getBytes(), 0, frame.getFrameLength()); + } + + /** + * Send the given frame in an mpjeg frame header + * + * @param frame + * the frame to be send + * @throws IOException + * if there is an error writing over the socket + */ + public void sendNextFrameBufferedImage(BufferedImage frame) + throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ImageIO.write(frame, "jpg", baos); + baos.flush(); + byte[] imageInByte = baos.toByteArray(); + baos.close(); + outStream.writeBytes(mjpegFrameheader + + Integer.toString(imageInByte.length) + "\r\n\r\n"); + outStream.write(imageInByte, 0, imageInByte.length); + } + + /** + * Send the main page in html over the given output stream + * + * @param out + * the output stream + * @throws IOException + * if the stream is closed + */ + public static void sendMainPage(DataOutputStream out) + throws IOException { + out.writeBytes(textHeader); + out.writeBytes(mainPageHTML); + } + + public static void sendZoomPage(DataOutputStream out) throws IOException { + out.writeBytes(textHeader); + out.writeBytes(zoomPage); + } + + public static void sendWebcamPage(DataOutputStream out) + throws IOException { + out.writeUTF(textHeader); + out.writeUTF(webcamPageHTML); + + } + + public static void sendVideoOptionsPage(DataOutputStream out) + throws IOException { + out.writeBytes(textHeader); + out.writeBytes(""); + out.writeBytes(" "); + out.writeBytes(""); + out.writeBytes(""); + out.writeBytes(""); + out.writeBytes(""); + + out.writeBytes(""); + + out.writeBytes(""); + out.writeBytes(""); + out.writeBytes("
"); + out.writeUTF("Opciones de vídeo:
"); + out.writeUTF("Resolución:
"); + out.writeBytes(""); + String selectedResolution = CamServer.videoWidth + "x" + + CamServer.videoHeight; + if (!Settings.needFallbackMode(CamServer.videoDevice.getDevicefile())) { + out.writeBytes(""); + } else { + out.writeBytes(""); + } + out.writeBytes("
Ancho de banda:
"); + out.writeBytes(""); + String currentbps = CamServer.bps.split("k")[0]; + out.writeBytes(""); + out.writeBytes("" + currentbps + " kbps" + + ""); + out.writeBytes("
"); + + } + + public static void updateVideoOption(String httpLine, DataOutputStream out) + throws IOException { + String id = null; + String value = null; + + StringTokenizer tokens = new StringTokenizer(httpLine, "?=&", false); + + while (tokens.hasMoreTokens()) { + String next = tokens.nextToken(); + + if ((next.equalsIgnoreCase("id")) && tokens.hasMoreTokens()) { + id = tokens.nextToken(); + } else if ((next.equalsIgnoreCase("val")) && tokens.hasMoreTokens()) { + value = tokens.nextToken(); + } + } + + if (id.equalsIgnoreCase("videoResolution")) { + CamServer.videoWidth = Integer.parseInt(value.split("x")[0]); + CamServer.videoHeight = Integer.parseInt(value.split("x")[1]); + } else if (id.equalsIgnoreCase("anchoBanda")) { + CamServer.bps = value + "k"; + } + + if (CamServer.active) { + CamServer.refreshStreaming(); + // TODO send refresh page to all clients + sendRefreshPage(out); + } + + } + + /** + * Send a basic HTML table containing a form per control allowing the user + * to view the current value and it update it. + * + * @param ctrlList + * the list of control for which the HTML form should be created + * @param out + * the output stream + * @throws IOException + * if there is an error writing out the stream + */ + public static void sendControlListPage(ControlList ctrlList, + DataOutputStream out) throws IOException { + out.writeBytes(textHeader); + out.writeBytes(controlPageHTMLHeader); + + /** + // add a fake control to adjust the jpeg quality + out.writeBytes(""); + out.writeBytes("Calidad JPEG"); + out.writeBytes("
"); + out.writeBytes(""); + out.writeBytes(""); + out.writeBytes("
Min: 0 - Max: 100 - Intervalo: 1"); + out.writeBytes("
"); + */ + + // for each control, create an entry in the table + for (Control control : ctrlList.getList()) { + out.writeBytes(""); + // AQP + out.writeUTF(Dictionary.translateWord((control.getName())) + + ""); + out.writeBytes("
"); + out.writeBytes(""); + + try { + // Select the best HTML element to represent the control based + // on its type + switch (control.getType()) { + case V4L4JConstants.CTRL_TYPE_BUTTON: + out.writeBytes(""); + out.writeBytes(""); + break; + + case V4L4JConstants.CTRL_TYPE_SLIDER: + out.writeBytes(""); + out.writeBytes("
Min: " + control.getMinValue() + + " - Max: " + control.getMaxValue() + " - Step: " + + control.getStepValue()); + out.writeBytes(""); + break; + + case V4L4JConstants.CTRL_TYPE_DISCRETE: + out.writeBytes(""); + out.writeBytes(""); + break; + + case V4L4JConstants.CTRL_TYPE_SWITCH: + out.writeBytes(""); + else + out.writeBytes("1\">"); + // out.writeBytes(""); + out.writeBytes(""); + break; + } + } catch (Exception e) { + // error creating form + } + + out.writeBytes("
"); + out.writeBytes(""); + } + + out.writeBytes(controlPageHTMLFooter); + } + + /** + * Parses the given http line, expecting to find something along the lines + * of GET /control?id=ID&val=VAL&submit=set HTTP/1.1 where ID + * and VAL are integers. + * + * @param ctrlList + * the control list + * @param httpLine + * the http line to be parsed + * @throws ControlException + * if there is an error setting the new value + * @throws IOException + */ + public static void updateControlValue(ControlList ctrlList, + JPEGFrameGrabber fg, String httpLine, DataOutputStream outStream) + throws ControlException, IOException { + boolean hasValue = false; + boolean hasID = false; + int controlID = 0; + int value = 0; + + // parse the http line to find out the control index and + // its new value. Expected line: + // "GET /control?id=ID&val=VAL&submit=set HTTP/1.1" + StringTokenizer tokens = new StringTokenizer(httpLine, "?=&", false); + + while (tokens.hasMoreTokens()) { + String next = tokens.nextToken(); + + if ((next.equalsIgnoreCase("id")) && tokens.hasMoreTokens()) { + try { + controlID = Integer.parseInt(tokens.nextToken()); + hasID = true; + } catch (NumberFormatException e) { + // control id is not a number, ignore + } + } else if ((next.equalsIgnoreCase("val")) && tokens.hasMoreTokens()) { + try { + value = Integer.parseInt(tokens.nextToken()); + hasValue = true; + } catch (NumberFormatException e) { + // control value is not a number, ignore + } + } + } + + // HTML checkboxes dont return a value if unchecked, which means + // hasValue is false in this case. Check if ID is of type SWICTH + // and if hasValues == false, in which case, set it to true, + // and use default value of 0 + if (hasID + && !hasValue + && (ctrlList.getList().get(controlID).getType() == V4L4JConstants.CTRL_TYPE_SWITCH)) { + hasValue = true; + value = 0; + } + + // Set new value + if (hasValue && hasID) { + // catch the jpeg quality control which is not a real control + + if (ctrlList.getList().get(controlID).getType() == V4L4JConstants.CTRL_TYPE_SLIDER) { + if (value < ctrlList.getList().get(controlID).getMinValue() + || value > ctrlList.getList().get(controlID) + .getMaxValue()) { + + // Value out of range + String message = "El valor introducido para " + + Dictionary.translateWord((ctrlList.getList() + .get(controlID).getName())) + + " no es correcto. "; + message = message.concat("El valor debe estar entre " + + ctrlList.getList().get(controlID) + .getMinValue() + + " y " + + ctrlList.getList().get(controlID) + .getMaxValue()); + sendWarningMessage(outStream, message); + return; + } + } + ctrlList.getList().get(controlID).setValue(value); + + } + } + + /** + * This method send a warning message with text 'message' and refreshes the + * webpage + * + * @param outStream + * @param message + * @throws IOException + */ + public static void sendWarningMessage(DataOutputStream out, String message) + throws IOException { + out.writeBytes(textHeader); + out.writeBytes(""); + out.writeBytes(""); + out.writeBytes("window.alert() Method"); + out.writeBytes(""); + out.writeBytes(""); + out.writeBytes(""); + out.writeBytes(""); + out.writeBytes(""); + + } + + /** + * Send the current image to an external viewer. + * + * @param outStream DataOutputStream of the client. + * @param frame BufferedImage to send to the client. + * @throws IOException + */ + public static void sendCurrentImage(DataOutputStream outStream, + BufferedImage frame) throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ImageIO.write(frame, "jpg", baos); + baos.flush(); + byte[] imageInByte = baos.toByteArray(); + baos.close(); + outStream.writeBytes(JPEGImageHeader + + Integer.toString(imageInByte.length) + "\r\n\r\n"); + outStream.write(imageInByte, 0, imageInByte.length); + + } + + public static void sendRefreshPage(DataOutputStream outStream) + throws IOException { + outStream.writeBytes(textHeader); + outStream.writeBytes(""); + outStream.writeBytes(""); + outStream.writeBytes(""); + outStream.writeBytes(""); + outStream.writeBytes(""); + } + + public static void sendCurrentImage(DataOutputStream outStream, + VideoFrame frame) throws IOException { + outStream.writeBytes(JPEGImageHeader + + Integer.toString(frame.getFrameLength()) + "\r\n\r\n"); + outStream.write(frame.getBytes(), 0, frame.getFrameLength()); + } + + /** + * Send a javascript file the client. + * @param file Path to javascript (js) file. + * @param outStream DataOutputStream of the client. + * @throws IOException + */ + public static void sendJs(String file, DataOutputStream outStream) + throws IOException { + Path path = Paths.get(file); + outStream.writeBytes(jsHeader); + byte[] fileBytes = Files.readAllBytes(path); + outStream.write(fileBytes); + } + + /** + * Send a css file to the client. + * @param file Path to the css file. + * @param outStream DataOutputStream of the client. + * @throws IOException + */ + public static void sendCss(String file, DataOutputStream outStream) + throws IOException { + Path path = Paths.get(file); + outStream.writeBytes(cssHeader); + byte[] fileBytes = Files.readAllBytes(path); + outStream.write(fileBytes); + } + + /** + * Send a swf file to the client. + * @param file Path to the swf file. + * @param outStream DataOutputStream of the client. + * @throws IOException + */ + public static void sendSwf(String file, DataOutputStream outStream) + throws IOException { + Path path = Paths.get(file); + outStream.writeBytes(swfHeader); + byte[] fileBytes = Files.readAllBytes(path); + outStream.write(fileBytes); + } + + /** + * Send a woff file to the client. + * @param file Path to the woff file. + * @param outStream DataOutputStream of the client. + * @throws IOException + */ + public static void sendWoff(String file, DataOutputStream outStream) throws IOException{ + Path path = Paths.get(file); + outStream.writeBytes(woffHeader); + byte[] fileBytes = Files.readAllBytes(path); + outStream.write(fileBytes); + } + + /** + * Send a ttf file to the client. + * @param file Path to the ttf file. + * @param outStream DataOutputStream of the client. + * @throws IOException + */ + public static void sendTtf(String file, DataOutputStream outStream) throws IOException{ + Path path = Paths.get(file); + outStream.writeBytes(ttfHeader); + byte[] fileBytes = Files.readAllBytes(path); + outStream.write(fileBytes); + } + + public static void sendAuthPage (DataOutputStream outStream) throws IOException { + outStream.writeBytes(authHeader); + } +} diff --git a/src/communication/v4l/ConnectionThread.java b/src/communication/v4l/ConnectionThread.java new file mode 100644 index 0000000..bd2c25b --- /dev/null +++ b/src/communication/v4l/ConnectionThread.java @@ -0,0 +1,305 @@ +package communication.v4l; + +import java.io.BufferedReader; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.Socket; +import java.util.Vector; + +import org.apache.commons.codec.binary.Base64; + +import util.MyLogger; + +/** + * This class manages an incoming connection to the tcp server and delivers what + * it is asked for. It reads the http line from the client and deduce the kind + * of petition. It performs a different action for each kind of petition. + * + * @author ehas + * + */ +public class ConnectionThread implements Runnable { + + private static final int AUTH_PAGE = -1; + private static final int MAIN_PAGE = 0; + private static final int WEBCAM_PAGE = 1; + private static final int CONTROL_PAGE = 2; + private static final int VIDEO_STREAM = 3; + private static final int UPDATE_CONTROL_VALUE = 4; + private static final int CAPTURE_FRAME = 5; + private static final int VIDEO_OPTIONS = 6; + private static final int UPDATE_VIDEO_OPTIONS = 7; + private static final int ZOOM_IN = 8; + private static final int VIDEO_JS = 9; + private static final int VIDEO_JS_CSS = 10; + private static final int VIDEO_JS_SWF = 11; + private static final int VIDEO_JS_WOFF = 12; + private static final int VIDEO_JS_TTF = 13; + private static final int FLOWPLAYER_JS = 14; + private static final int FLOWPLAYER_SWF = 15; + private static final int FLOWPLAYER_RTMP_SWF = 16; + private static final int FLOWPLAYER_CONTROLS = 17; + + public static Vector clients; + private String httpLineFromClient; + private Socket clientSocket; + + /** + * Create a new ConnectionThread object for the socket. + * + * @param clientSocket Socket of the incoming connection. + */ + + public ConnectionThread(Socket clientSocket) { + this.clientSocket = clientSocket; + } + + /** + * Manages the incoming connection. First it reads the http line from the + * client. Then it deduces the action that is requested. Finally, it + * performs the action and closes the socket. + */ + @Override + public void run() { + + BufferedReader inStream = null; + DataOutputStream outStream = null; + int requestedAction = MAIN_PAGE; + + System.out.println("Connection from " + + clientSocket.getInetAddress().getHostAddress() + ":" + + clientSocket.getPort()); + + // Create input/output streams then check what action + // was requested + + try { + inStream = new BufferedReader(new InputStreamReader( + clientSocket.getInputStream())); + outStream = new DataOutputStream(clientSocket.getOutputStream()); + requestedAction = parseLine(inStream); + if (CamServer.authentication){ + if (!checkAuthorization(inStream)){ + requestedAction = AUTH_PAGE; + ClientConnection.sendAuthPage(outStream); + } + } + } catch (IOException e) { + // error setting up in and out streams with this client, abort + System.out.println("Hay un error"); + try { + inStream.close(); + outStream.close(); + clientSocket.close(); + } catch (IOException e1) { + e1.printStackTrace(); + } + return; + } + + // check what other page was requested + try { + switch (requestedAction) { + case AUTH_PAGE: + ClientConnection.sendAuthPage(outStream); + break; + case WEBCAM_PAGE: + // send webcam viewer page + ClientConnection.sendWebcamPage(outStream); + break; + case CAPTURE_FRAME: + try { + // GestorV4L.getInstance().setLocalCapture(true); + // Set the outstream as external capturer + synchronized (CamServer.LockCapture) { + CamServer.setExternalCapture(outStream); + CamServer.LockCapture.notify(); + } + // Wait until the image is sent to the user + synchronized (CamServer.LockExternal) { + while (CamServer.getExternalCapture() != null) { + CamServer.LockExternal.wait(); + } + } + } catch (Exception e) { + e.printStackTrace(); + } + break; + case VIDEO_OPTIONS: + ClientConnection.sendVideoOptionsPage(outStream); + break; + case UPDATE_VIDEO_OPTIONS: + ClientConnection.updateVideoOption(httpLineFromClient, + outStream); + break; + case ZOOM_IN: + ClientConnection.sendZoomPage(outStream); + break; + case UPDATE_CONTROL_VALUE: + // parse http line and update the requested control's value + ClientConnection.updateControlValue(CamServer.controlList, + CamServer.frameGrabber, httpLineFromClient, outStream); + // fallthrough so we re-send the control list + case CONTROL_PAGE: + // send the control list page + ClientConnection.sendControlListPage(CamServer.controlList, + outStream); + break; + case VIDEO_JS: + ClientConnection.sendJs("web/video-js/video.js", outStream); + break; + case VIDEO_JS_CSS: + ClientConnection.sendCss("web/video-js/video-js.min.css", + outStream); + break; + case VIDEO_JS_SWF: + ClientConnection.sendSwf("web/video-js/video-js.swf", outStream); + break; + case VIDEO_JS_WOFF: + ClientConnection.sendSwf("web/video-js/vjs.woff", outStream); + break; + case VIDEO_JS_TTF: + ClientConnection.sendSwf("web/video-js/vjs.ttf", outStream); + break; + case FLOWPLAYER_JS: + ClientConnection.sendJs( + "web/flowplayer/flowplayer-3.2.13.min.js", outStream); + break; + case FLOWPLAYER_SWF: + ClientConnection.sendSwf( + "web/flowplayer/flowplayer-3.2.17.swf", outStream); + break; + case FLOWPLAYER_RTMP_SWF: + ClientConnection.sendSwf( + "web/flowplayer/flowplayer.rtmp-3.2.13.swf", outStream); + break; + case FLOWPLAYER_CONTROLS: + ClientConnection.sendSwf( + "web/flowplayer/flowplayer.controls-3.2.16.swf", outStream); + break; + case MAIN_PAGE: + default: + // send the main page + ClientConnection.sendMainPage(outStream); + /** + CamServer.logger.info(MyLogger + .getLogRecord("INCOMING_CONNECTION") + + " " + + clientSocket.getInetAddress().getHostAddress()); + */ + break; + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + // close the connection with the client + try { + System.out.println("Disconnected from " + + clientSocket.getInetAddress().getHostAddress() + ":" + + clientSocket.getPort()); + + inStream.close(); + outStream.close(); + clientSocket.close(); + } catch (Exception e) { + } + } + } + + private int parseLine(BufferedReader in) throws IOException { + // read the first line to determine which page to send + httpLineFromClient = in.readLine(); + + System.out.println(httpLineFromClient); + if (httpLineFromClient == null) + throw new IOException("Read null line"); + + if (httpLineFromClient.indexOf("flowplayer-3.2.13.min.js") != -1) { + return FLOWPLAYER_JS; + } + if (httpLineFromClient.indexOf("flowplayer-3.2.17.swf") != -1) { + return FLOWPLAYER_SWF; + } + if (httpLineFromClient.indexOf("flowplayer.rtmp-3.2.13.swf") != -1) { + return FLOWPLAYER_RTMP_SWF; + } + if (httpLineFromClient.indexOf("flowplayer.controls-3.2.16.swf") != -1) { + return FLOWPLAYER_CONTROLS; + } + // if the line contains the word webcam, we want the video viewing page + if (httpLineFromClient.indexOf("webcam") != -1) { + return WEBCAM_PAGE; + } + + // if the line contains the word update, we want to update a control's + // value + if (httpLineFromClient.indexOf("updateControl") != -1){ + return UPDATE_CONTROL_VALUE; + } + + // if the line contains the word control, we want the control list page + if (httpLineFromClient.indexOf("control") != -1) { + return CONTROL_PAGE; + } + + // if the line contains the word stream, we want the control list page + if (httpLineFromClient.indexOf("stream") != -1) { + return VIDEO_STREAM; + } + + // if the line contains the word update, we want to update a control's + // value + if (httpLineFromClient.indexOf("capture") != -1) { + return CAPTURE_FRAME; + } + + if (httpLineFromClient.indexOf("Options") != -1) { + return VIDEO_OPTIONS; + } + + if (httpLineFromClient.indexOf("updateVideo") != -1) { + return UPDATE_VIDEO_OPTIONS; + } + if (httpLineFromClient.indexOf("zoomIn") != -1) { + return ZOOM_IN; + } + if (httpLineFromClient.indexOf("video-js.min.css") != -1) { + return VIDEO_JS_CSS; + } + if (httpLineFromClient.indexOf("video.js") != -1) { + return VIDEO_JS; + } + if (httpLineFromClient.indexOf("video-js.swf") != -1){ + return VIDEO_JS_SWF; + } + if (httpLineFromClient.indexOf("/font/vjs.woff") != -1){ + return VIDEO_JS_WOFF; + } + if (httpLineFromClient.indexOf("/font/vjs.ttf") != -1){ + return VIDEO_JS_TTF; + } + + return MAIN_PAGE; + } + + private boolean checkAuthorization (BufferedReader instream) throws IOException{ + String line; + while ((line = instream.readLine()) != null && !line.trim().isEmpty()){ + if (line.contains("Authorization")){ + line = line.split("Basic ")[1]; + String[] fullLine = new String(Base64.decodeBase64(line), "UTF-8").split(":"); + if (fullLine.length != 2){ + return false; + } + String userName = fullLine[0]; + String userPassword = fullLine[1]; + + if (userName.equals(CamServer.userName) && userPassword.equals(CamServer.userPassword)){ + return true; + } + } + } + return false; + } +} diff --git a/src/communication/v4l/DefaultStreamGobbler.java b/src/communication/v4l/DefaultStreamGobbler.java new file mode 100644 index 0000000..adc9986 --- /dev/null +++ b/src/communication/v4l/DefaultStreamGobbler.java @@ -0,0 +1,65 @@ +package communication.v4l; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; + +/** + * This class extends Thread and is used to read the output of system processes. When launching a + * system process is necessary to read both standard and error outputs to avoid buffers to overflow. + * @author ehas + * + */ +public class DefaultStreamGobbler extends Thread { + + InputStream is; + String type; + boolean print; + String name; + + /** + * Create a new DefaultStreamGobbler object. + * + * @param is InputStream of the system process. + * @param type Type of output (standard or error). It is only used as a tag. + * @param print True - messages are printed in console. + * @param name Name of the system process. + */ + public DefaultStreamGobbler(InputStream is, String type, boolean print, String name) { + this.is = is; + this.type = type; + this.print = print; + this.name = name; + this.setName(name); + } + + public void run() { + try { + InputStreamReader isr = new InputStreamReader(is); + BufferedReader br = new BufferedReader(isr); + String line = null; + while (!Thread.interrupted() && ((line = br.readLine()) != null)) { + + analyzeLine(line); + + if (print) { + System.out.println(name + "> " + line); + } + } + } catch (IOException ioe) { + if (ioe.getMessage().equalsIgnoreCase("stream closed")) { + // Do nothing + return; + } + ioe.printStackTrace(); + } + } + + /** + * This method is intended to be overridden by the classes that extends this class. + * @param line + */ + protected void analyzeLine (String line){ + } +} diff --git a/src/communication/v4l/FfmpegRtmpThread.java b/src/communication/v4l/FfmpegRtmpThread.java new file mode 100755 index 0000000..bdf88b7 --- /dev/null +++ b/src/communication/v4l/FfmpegRtmpThread.java @@ -0,0 +1,714 @@ +package communication.v4l; + +import edition.ImageManager; +import gestion.GestorSave; +import ij.ImagePlus; +import ij.ImageStack; +import ij.WindowManager; + +import java.awt.Toolkit; +import java.awt.image.BufferedImage; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +import javax.imageio.ImageIO; +import javax.swing.JOptionPane; + +import util.MyLogger; +import util.ProgressFrame; + +/** + * This class performs a video streaming over RTMP using Ffmpeg/x264 as encoder software and + * crtmpserver as video streaming server. + * + * @author ehas + * + */ +public class FfmpegRtmpThread implements Streamer { + + private String encoderName = "ffmpeg"; + private String serverName = "crtmpserver/crtmpserver/crtmpserver"; + private String serverConfigFile = "crtmpserver.lua"; + private String playerName = "ffplay"; + + private Process ffmpeg = null; + private Process crtmpserver; + private Process ffplay; + private DefaultStreamGobbler ffmpegstdout, ffmpegstderr, crtmpstdout, + crtmpstderr, ffplaystdout, ffplaystderr; + + private boolean ffplayActive = false; + private boolean captureTaken = false; + private GestorSave saver = new GestorSave(); + + private String capturePath = "capture.jpg"; + private String captureQuality = "2"; + + private HashMap clients = new HashMap(); + + private static boolean streamNameUpdated = false; + + /** + * It starts the video streaming by running crtmpserver and Ffmpeg processes. + * When the streaming is initialized, it waits for a image capture + * notification. + */ + @Override + public void run() { + + // Run avconv and avserver + try { + + if (!streamNameUpdated){ + updateStreamName(); + } + + startCrtmpserver(serverConfigFile); + // startFfplay(); + + synchronized (this) { + startFfmpeg(); + this.notify(); + } + // Start the libav processes + + Thread waitViewer = new Thread(new Runnable() { + @Override + public void run() { + while (!Thread.interrupted()) { + // There are two events that can launch the viewer: + // 1. If an image capture has been taken and the viewer + // was previously active + // 2. The "launch viewer button" has been pushed + if (captureTaken == true) { + captureTaken = false; + } else { + synchronized (CamServer.LockViewer) { + try { + // Wait for viewer button activation + CamServer.LockViewer.wait(); + } catch (InterruptedException e) { + break; + } + } + } + + try { + startFfplay(); + ffplay.waitFor(); + ffplayActive = false; + } catch (InterruptedException | IOException e) { + ffplay.destroy(); + ffplayActive = false; + break; + } + + } + } + + }, "waitViewer"); + + waitViewer.start(); + + while (!Thread.interrupted()) { + + synchronized (CamServer.LockCapture) { + + // It waits until a CamServer.LockCapture notification. It + // can be a local or an external capture. + try { + CamServer.LockCapture.wait(); + } catch (InterruptedException e) { + if (waitViewer.isAlive()) { + waitViewer.interrupt(); + try { + waitViewer.join(); + } catch (InterruptedException i) { + i.printStackTrace(); + } + } + break; + } + + if (CamServer.cameraCheckBox != null){ + CamServer.cameraCheckBox.setEnabled(false); + } + if (CamServer.indicator != null){ + CamServer.indicator.setEnabled(false); + } + + checkLocalCapture(); + checkExternalCapture(); + + if (CamServer.cameraCheckBox != null){ + CamServer.cameraCheckBox.setEnabled(true); + } + if (CamServer.indicator != null){ + CamServer.indicator.setEnabled(true); + } + + } + } + + stopStreamProcesses(); + disconnectClients(); + + } catch (IOException e) { + e.printStackTrace(); + } + } + + /** + * This method must be called before the thread exit. It destroys all + * remaining processes. + */ + private void stopStreamProcesses() { + System.out.println("Parando hilo ffmpeg"); + + // Stop the libav processes + try { + stopFfmpeg(); + stopCrtmpserver(); + } catch (Exception e) { + e.printStackTrace(); + } + + if (ffplay != null) { + try { + stopFfplay(); + } catch (IOException | InterruptedException e) { + e.printStackTrace(); + } + } + } + + /** + * This method must be called before the thread exit. It disconnects all + * the remaining clients and logs the event. + */ + private void disconnectClients(){ + synchronized (clients){ + Iterator it = clients.entrySet().iterator(); + while (it.hasNext()){ + Map.Entry pairs = (Map.Entry) it.next(); + logClientDisconnection((String) pairs.getKey()); + it.remove(); + } + } + } + + /** + * Starts a ffmpeg process with the parameters saved in the CamServer + * static instance. Two additional processes are created to read standard + * and error output. The methods waits until the ffmpeg process actually + * starts the video streaming + * + * @throws IOException + */ + private void startFfmpeg() throws IOException { + + String command = encoderName; + //command = command.concat(" -re"); + command = command.concat(" -f").concat(" video4linux2"); + command = command.concat(" -video_size").concat( + " " + CamServer.videoWidth + "x" + CamServer.videoHeight); + command = command.concat(" -framerate").concat( + " " + CamServer.getFramerate()); + command = command.concat(" -i").concat( + " " + CamServer.getVideoDevice().getDevicefile()); + + command = command.concat(" -vcodec").concat(" libx264"); + command = command.concat(" -r").concat(" ") + CamServer.getFramerate(); + command = command.concat(" -pix_fmt").concat(" yuv420p"); + command = command.concat(" -profile:v").concat(" high"); + command = command.concat(" -tune").concat(" zerolatency"); + command = command.concat(" -s").concat( + " " + CamServer.videoWidth + "x" + CamServer.videoHeight); + if (CamServer.gop > 0) { + command = command.concat(" -g").concat(" " + CamServer.gop); + } + if (!CamServer.bps.equalsIgnoreCase("-1")) { + command = command.concat(" -b:v").concat(" " + CamServer.bps); + command = command.concat(" -maxrate").concat(" " + CamServer.bps); + command = command.concat(" -bufsize").concat(" " + CamServer.bps); + } + command = command.concat(" -me_method").concat(" zero"); + command = command.concat(" -acodec").concat(" none"); + //command = command.concat(" -vbsf").concat(" h264_mp4toannexb"); + command = command.concat(" -f").concat(" mpegts"); + command = command.concat(" udp://127.0.0.1:10000"); + + ffmpeg = Runtime.getRuntime().exec(command); + + ffmpegstdout = new DefaultStreamGobbler(ffmpeg.getInputStream(), "OUTPUT", + false, "ffmpeg_out"); + ffmpegstdout.start(); + ffmpegstderr = new FfmpegStreamGobbler(ffmpeg.getErrorStream(), "ERROR", + true, "ffmpeg_err"); + ffmpegstderr.start(); + + // Wait until the video streaming is started + synchronized (ffmpegstderr) { + try { + ffmpegstderr.wait(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + + /** + * Stops the ffmpeg process + * + * @throws Exception + */ + private void stopFfmpeg() throws Exception { + + // It is necessary to kill the process in this way to be sure that + // SIGTERM (15) is sent + // as the kill signal. + String pid = "" + getUnixPID(ffmpeg); + Process killingProcess = Runtime.getRuntime().exec( + new String[] { "kill", "-15", pid }); + killingProcess.waitFor(); + ffmpeg.waitFor(); + ffmpegstdout.interrupt(); + ffmpegstderr.interrupt(); + } + + /** + * Starts a crtmpserver process with the configuration file given as parameter + * + * @param configFile + * - crtmpserver configuration file + * @throws IOException + */ + private void startCrtmpserver(String configFile) throws IOException { + crtmpserver = Runtime.getRuntime().exec( + new String[] { serverName, configFile }); + crtmpstdout = new CrtmpStreamGobbler(crtmpserver.getInputStream(), "OUTPUT", + true, "crtmpserver_out"); + crtmpstderr = new DefaultStreamGobbler(crtmpserver.getErrorStream(), "ERROR", + true, "crtmpserver_err"); + synchronized (crtmpstdout) { + crtmpstderr.start(); + crtmpstdout.start(); + + try { + crtmpstdout.wait(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + } + } + + /** + * Stops the crtmpserver process + * @throws InterruptedException + */ + private void stopCrtmpserver() throws InterruptedException { + crtmpserver.destroy(); + crtmpserver.waitFor(); + crtmpstdout.interrupt(); + crtmpstderr.interrupt(); + } + + /** + * Starts a ffplay process. The streaming address must be the same that the + * one specified in the crtmpserver configuration file - usually default + * "rtmp://localhost/AppTm4l" + * + * @throws IOException + * @throws InterruptedException + */ + public void startFfplay() throws IOException, InterruptedException { + ffplay = Runtime.getRuntime().exec( + new String[] { playerName, "-probesize", "3000", + "-window_title", "Visor Microscopio", + "rtmp://localhost/" + CamServer.streamName }); + + ProgressFrame progressFrame = new ProgressFrame("Iniciando visor","Iniciando visor..."); + progressFrame.start(); + + ffplaystdout = new DefaultStreamGobbler(ffplay.getInputStream(), "OUTPUT", + false, "ffplay_out"); + ffplaystderr = new FfplayStreamGobbler(ffplay.getErrorStream(), "ERROR", + false, "ffplay_err"); + + synchronized (ffplaystderr) { + ffplaystderr.start(); + ffplaystdout.start(); + + try { + ffplaystderr.wait(); + } catch (InterruptedException e){ + progressFrame.stop(); + throw new InterruptedException(); + } + + progressFrame.stop(); + } + ffplayActive = true; + } + + /** + * Stops the ffplay process + * + * @throws IOException + * @throws InterruptedException + */ + public void stopFfplay() throws IOException, InterruptedException { + ffplay.destroy(); + ffplay.waitFor(); + ffplaystdout.interrupt(); + ffplaystderr.interrupt(); + + ffplayActive = false; + } + + /** + * This method checks if the capture that has been notified is a local + * capture or not by checking the method CamServer.isLocalCapture(). If so, + * it shows a "save file" menu and starts a work session. + * + * @throws IOException + */ + private void checkLocalCapture() throws IOException { + + synchronized (CamServer.LockLocal) { + try { + if (CamServer.isLocalCapture()) { + + CamServer.setLocalCapture(false); + + BufferedImage currentImage = getCurrentImage(); + + CamServer.logger.info(MyLogger.getLogRecord("LOCAL_CAPTURE")); + + ImagePlus imp2 = WindowManager.getCurrentImage(); + ImagePlus imagen = new ImagePlus("title", Toolkit + .getDefaultToolkit().createImage( + currentImage.getSource())); + if (imp2 != null) { + + ImageStack stack = imp2.getStack(); + if (stack.getSize() == 1) { + String label = stack.getSliceLabel(1); + if (label != null && label.indexOf("\n") != -1) + stack.setSliceLabel(null, 1); + Object obj = imagen.getProperty("Label"); + if (obj != null && (obj instanceof String)) + stack.setSliceLabel((String) obj, 1); + } + + stack.addSlice(null, imagen.getChannelProcessor()); + imp2.setStack(null, stack); + imp2.setSlice(stack.getSize()); + imp2.unlock(); + + ImageManager.getInstance().refresh(); + + } else { + saver.saveAsTifInZip(imagen, true); + } + } + } catch (Exception e){ + if (e.getMessage().contains("Dimensions do not match")){ + // Error when trying to add a slide to the stack. The image dimensions do not match + JOptionPane.showMessageDialog(null, + "No se ha podido añadir la imagen a la pila de imágenes\n" + + "porque los tamaños son diferentes.", + "Error al añadir imagen", JOptionPane.ERROR_MESSAGE); + } + } + } + } + + /** + * This method checks if the capture that has been notified is an external + * capture or not by checking the method CamServer.getExternalCapture(). If + * so, the current image is sent to the client. + */ + private void checkExternalCapture() { + DataOutputStream outStream; + synchronized (CamServer.LockExternal) { + if (CamServer.getExternalCapture() != null) { + + outStream = CamServer.getExternalCapture(); + // Clear the DataOutputStream + CamServer.clearExternalCapture(); + + BufferedImage currentImage; + try { + currentImage = getCurrentImage(); + CamServer.logger.info(MyLogger + .getLogRecord("EXTERNAL_CAPTURE")); + ClientConnection.sendCurrentImage(outStream, currentImage); + } catch (IOException e) { + System.out.println("Fallo al capturar o enviar la imagen"); + e.printStackTrace(); + } + } + CamServer.LockExternal.notify(); + } + } + + /** + * This method return the current image with the parameters saved in the + * CamServer static instance (device, width, height). + * + * @return - the current image + * @throws IOException + */ + private BufferedImage getCurrentImage() throws IOException { + + ProgressFrame progressFrame = new ProgressFrame("Capturando", "Capturando..."); + try { + stopFfmpeg(); + stopCrtmpserver(); + + progressFrame.start(); + + Process capture = Runtime.getRuntime().exec( + new String[] { + encoderName, + "-y", + "-s", + CamServer.captureWidth + "x" + + CamServer.captureHeight, "-f", + "video4linux2", "-i", + CamServer.getVideoDevice().getDevicefile(), + "-vframes", "1", "-qmin", captureQuality, "-qmax", + captureQuality, capturePath }); + capture.waitFor(); + + BufferedImage currentImage = null; + currentImage = ImageIO.read(new File(capturePath)); + + startCrtmpserver(serverConfigFile); + startFfmpeg(); + + if (ffplayActive) { + captureTaken = true; + stopFfplay(); + } + progressFrame.stop(); + + return currentImage; + + } catch (Exception e) { + progressFrame.stop(); + e.printStackTrace(); + } + return null; + } + + private int getUnixPID(Process process) throws Exception { + + if (process.getClass().getName().equals("java.lang.UNIXProcess")) { + Class cl = process.getClass(); + Field field = cl.getDeclaredField("pid"); + field.setAccessible(true); + Object pidObject = field.get(process); + return (Integer) pidObject; + } else { + throw new IllegalArgumentException("Needs to be a UNIXProcess"); + } + } + + + @Override + public String getHeadHTMLText() { + String headHTML = ""; + return headHTML; + } + + + @Override + public String getBodyHTMLText() { + String bodyHTML = "" + + "" + + ""; + + return bodyHTML; + } + + + /** + @Override + public String getHeadHTMLText() { + String headHTML = "" + + ""; + return headHTML; + } + + @Override + public String getBodyHTMLText() { + String bodyHTML = ""; + return bodyHTML; + } + */ + + /** + * Remove a client from the client list, and return the connection time. + * @param client Ip and port of the client that has disconnected + * @return Client connection time. + */ + private void logClientDisconnection (String client){ + if (!(clients.get(client) == null)){ + long time = (System.currentTimeMillis() - clients.get(client)) /1000; + + CamServer.logger.info(MyLogger.getLogRecord("VIDEO_STREAMING_FINISH_CLIENT") + + " " + client); + + CamServer.logger.info(MyLogger.getLogRecord("VIDEO_STREAMING_CONNECTION_TIME") + + " " + time); + + } + } + + /** + * Modifies the server configuration file in order to match the streamName variable created by CamServer. + */ + private void updateStreamName(){ + String temp_file = "crtmpserver.lua.temp"; + BufferedReader br = null; + BufferedWriter bw = null; + try { + br = new BufferedReader(new FileReader(serverConfigFile)); + bw = new BufferedWriter(new FileWriter(temp_file)); + + String line; + while ((line = br.readLine()) != null){ + + if(line.contains("localStreamName") || line.contains("targetStreamName")){ + line = line.replaceAll("localStreamName=\".*\"", "localStreamName=\"" + CamServer.streamName + "\""); + line = line.replaceAll("targetStreamName=\".*\"", "targetStreamName=\"" + CamServer.streamName + "\""); + } + bw.write(line + "\n"); + } + } catch (IOException e) { + e.printStackTrace(); + return; + } finally { + try { + if(br != null){ + br.close(); + } + if (bw != null){ + bw.close(); + } + } catch (IOException e){ + } + } + + File oldFile = new File(serverConfigFile); + oldFile.delete(); + + File newFile = new File(temp_file); + newFile.renameTo(oldFile); + + streamNameUpdated = true; + } + + private class FfmpegStreamGobbler extends DefaultStreamGobbler { + + public FfmpegStreamGobbler (InputStream is, String type, boolean print, String name){ + super(is,type,print,name); + } + + @Override + public void analyzeLine (String line){ + if (line.startsWith("frame=")) { + synchronized (this) { + notify(); + } + } + } + } + + + private class CrtmpStreamGobbler extends DefaultStreamGobbler { + + public CrtmpStreamGobbler (InputStream is, String type, boolean print, String name){ + super(is,type,print,name); + } + + @Override + protected void analyzeLine (String line){ + if (line.contains("GO! GO! GO!")) { + synchronized (this) { + notify(); + return; + } + } + if (line.contains("Inbound connection accepted")){ + String clientIPandPort = line.split("Far:")[1].split(";")[0]; + + // Save a register in clients variable + synchronized (clients){ + clients.put(clientIPandPort, System.currentTimeMillis()); + } + + CamServer.logger.info(MyLogger.getLogRecord("VIDEO_STREAMING_START_CLIENT") + + " " + clientIPandPort); + return; + } + if (line.contains("Unable to read data from connection")){ + String clientIP = line.split("Far:")[1].split(";")[0]; + + synchronized (clients){ + logClientDisconnection(clientIP); + clients.remove(clientIP); + } + + return; + } + } + } + + private class FfplayStreamGobbler extends DefaultStreamGobbler { + + public FfplayStreamGobbler (InputStream is, String type, boolean print, String name){ + super(is,type,print,name); + } + + @Override + protected void analyzeLine (String line){ + if (line.contains("Input #0")) { + synchronized (this) { + notify(); + } + } + } + } + +} diff --git a/src/communication/v4l/FrameCamServer.java b/src/communication/v4l/FrameCamServer.java new file mode 100644 index 0000000..64e32a1 --- /dev/null +++ b/src/communication/v4l/FrameCamServer.java @@ -0,0 +1,76 @@ +package communication.v4l; + +import java.awt.Dimension; +import java.awt.Frame; +import java.awt.HeadlessException; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +import javax.swing.BoxLayout; +import javax.swing.JButton; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; + + +public class FrameCamServer extends JFrame implements ActionListener{ + + private static Frame instance; + private JLabel l = new JLabel(); + private JButton capturarButton = new JButton("Capturar"); + + public static FrameCamServer getInstance() { + return (FrameCamServer)instance; + } + + + public JLabel getL() { + return l; + } + + + public void setL(JLabel l) { + this.l = l; + } + + + public FrameCamServer(Integer width,Integer height) throws HeadlessException { + super(); + + capturarButton.addActionListener(this); + JPanel jPanelButton = new JPanel(); + jPanelButton.add(capturarButton); + + l.setPreferredSize(new Dimension(width, height)); + JPanel jPanelLabel = new JPanel(); + jPanelLabel.add(l); + + this.setLayout(new BoxLayout(this.getContentPane(),BoxLayout.PAGE_AXIS)); + this.getContentPane().add(jPanelLabel); + this.getContentPane().add(jPanelButton); + this.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); + //this.setDefaultCloseOperation(0); + this.setVisible(true); + instance = this; + + } + + + @Override + public void actionPerformed(ActionEvent e) { + if (e.getSource()==capturarButton){ + + CamServer.setLocalCapture(true); + + } + + } + + + + + + + +} diff --git a/src/communication/v4l/ImageCallback.java b/src/communication/v4l/ImageCallback.java new file mode 100644 index 0000000..9e13405 --- /dev/null +++ b/src/communication/v4l/ImageCallback.java @@ -0,0 +1,44 @@ +package communication.v4l; + +import java.awt.image.BufferedImage; + +import au.edu.jcu.v4l4j.CaptureCallback; +import au.edu.jcu.v4l4j.VideoFrame; +import au.edu.jcu.v4l4j.exceptions.V4L4JException; + +public class ImageCallback implements CaptureCallback{ + + BufferedImage currentImage = null; + Object frameReceived = new Object(); + + @Override + public void exceptionReceived(V4L4JException arg0) { + // TODO Auto-generated method stub + + } + + @Override + public void nextFrame(VideoFrame frame) { + if (this.currentImage == null){ + currentImage = frame.getBufferedImage(); + synchronized(frameReceived){ + frameReceived.notify(); + } + } + frame.recycle(); + } + + public BufferedImage getCurrentImage(){ + synchronized(frameReceived){ + try { + //Wait until the first frame arrives + frameReceived.wait(); + return currentImage; + } catch (InterruptedException e) { + e.printStackTrace(); + return null; + } + } + } + +} diff --git a/src/communication/v4l/LibavThread.java b/src/communication/v4l/LibavThread.java new file mode 100755 index 0000000..05b609a --- /dev/null +++ b/src/communication/v4l/LibavThread.java @@ -0,0 +1,577 @@ +package communication.v4l; + +import edition.ImageManager; +import edition.RoiManager; +import gestion.GestorSave; +import ij.ImagePlus; +import ij.ImageStack; +import ij.WindowManager; + +import java.awt.BorderLayout; +import java.awt.Container; +import java.awt.GraphicsEnvironment; +import java.awt.Toolkit; +import java.awt.image.BufferedImage; +import java.io.DataOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Field; + +import javax.imageio.ImageIO; +import javax.swing.BorderFactory; +import javax.swing.JFrame; +import javax.swing.JProgressBar; +import javax.swing.border.Border; + +import util.MyLogger; + +/** + * + * @author ehas + * + */ +public class LibavThread implements Streamer { + + private String encoderName = "ffmpeg"; + private String serverName = "ffserver"; + private String playerName = "ffplay"; + + private Process avconv = null; + private Process avserver; + private Process avplay; + private DefaultStreamGobbler avconvstdout, avconvstderr, avserverstdout, + avserverstderr, avplaystdout, avplaystderr; + + private boolean ffplayActive = false; + private boolean captureTaken = false; + private GestorSave saver = new GestorSave(); + + private String capturePath = "capture.jpg"; + private String captureQuality = "2"; + + private String avserverTempFilePath = "feed1.ffm"; + private String avserverConfigFile = "server.conf"; + + /** + * It starts the video streaming by running avserver and avconv processes. + * When the streaming is initialized, it waits for a image capture + * notification. + */ + @Override + public void run() { + + // Run avconv and avserver + try { + + // Start the libav processes + startAvserver(avserverConfigFile); + try { + Thread.sleep(1300); + } catch (InterruptedException e1) { + e1.printStackTrace(); + } + + synchronized (this) { + startAvconv(); + this.notify(); + } + // startFfplay(); + + Thread waitViewer = new Thread(new Runnable() { + @Override + public void run() { + while (!Thread.interrupted()) { + // There are two events that can launch the viewer: + // 1. If an image capture has been taken and the viewer + // was previously active + // 2. The "launch viewer button" has been pushed + if (captureTaken == true) { + captureTaken = false; + } else { + synchronized (CamServer.LockViewer) { + try { + // Wait for viewer button activation + CamServer.LockViewer.wait(); + } catch (InterruptedException e) { + break; + } + } + } + + try { + startAvplay(); + avplay.waitFor(); + ffplayActive = false; + } catch (InterruptedException | IOException e) { + avplay.destroy(); + ffplayActive = false; + break; + } + + } + } + + }, "waitViewer"); + + waitViewer.start(); + + while (!Thread.interrupted()) { + + synchronized (CamServer.LockCapture) { + + // It waits until a CamServer.LockCapture notification. It + // can be a local or an external capture. + try { + CamServer.LockCapture.wait(); + } catch (InterruptedException e) { + if (waitViewer.isAlive()) { + waitViewer.interrupt(); + try { + waitViewer.join(); + } catch (InterruptedException i) { + i.printStackTrace(); + } + } + break; + } + + if (CamServer.cameraCheckBox != null){ + CamServer.cameraCheckBox.setEnabled(false); + } + if (CamServer.indicator != null){ + CamServer.indicator.setEnabled(false); + } + + checkLocalCapture(); + checkExternalCapture(); + + if (CamServer.cameraCheckBox != null){ + CamServer.cameraCheckBox.setEnabled(true); + } + if (CamServer.indicator != null){ + CamServer.indicator.setEnabled(true); + } + } + } + + stopLibavProcesses(); + + } catch (IOException e) { + e.printStackTrace(); + } + } + + /** + * This method must be called before the thread exit. It destroys all + * remaining processes. + */ + private void stopLibavProcesses() { + System.out.println("Parando hilo ffmpeg"); + + // Stop the libav processes + try { + stopAvconv(); + stopAvserver(); + } catch (InterruptedException e1) { + e1.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } catch (Exception e) { + e.printStackTrace(); + } + + if (avplay != null) { + try { + stopAvplay(); + } catch (IOException | InterruptedException e) { + e.printStackTrace(); + } + } + + // Remove temporal file + try { + Runtime.getRuntime().exec( + new String[] { "rm", avserverTempFilePath }); + } catch (IOException e) { + e.printStackTrace(); + } + } + + /** + * Starts an avconv process with the parameters saved in the CamServer + * static instance. Two additional processes are created to read standard + * and error output. The methods waits until the avconv process actually + * starts the video streaming + * + * @throws IOException + */ + private void startAvconv() throws IOException { + + String command = encoderName; + command = command.concat(" -f").concat(" video4linux2"); + command = command.concat(" -video_size").concat( + " " + CamServer.videoWidth + "x" + CamServer.videoHeight); + command = command.concat(" -framerate").concat( + " " + CamServer.getFramerate()); + command = command.concat(" -i").concat( + " " + CamServer.getVideoDevice().getDevicefile()); + /** + * if (true){ command = command.concat(" -vf") .concat( + * " \"drawtext=fontfile=/usr/share/fonts/truetype/ttf-dejavu/DejaVuSans-Bold.ttf:" + * ) .concat(" text=\'\\%T\':").concat(" fontcolor=white@0.8:").concat( + * " x=7:").concat(" y=460\""); } + */ + command = command.concat(" -vcodec").concat(" flv"); + if (CamServer.gop > 0) { + command = command.concat(" -g").concat(" " + CamServer.gop); + } + if (CamServer.qmin > 0) { + command = command.concat(" -qmin").concat(" " + CamServer.qmin); + } + if (CamServer.qmax > 0) { + command = command.concat(" -qmax").concat(" " + CamServer.qmax); + } + if (!CamServer.bps.equalsIgnoreCase("-1")) { + command = command.concat(" -b:v").concat(" " + CamServer.bps); + command = command.concat(" -maxrate").concat(" " + CamServer.bps); + } + command = command.concat(" http://localhost:8090/feed1.ffm"); + + avconv = Runtime.getRuntime().exec(command); + + avconvstdout = new DefaultStreamGobbler(avconv.getInputStream(), "OUTPUT", + false, "avconv_out"); + avconvstdout.start(); + avconvstderr = new FfmpegStreamGobbler(avconv.getErrorStream(), "ERROR", + true, "avconv_err"); + avconvstderr.start(); + + // Wait until the video streaming is started + synchronized (avconvstderr) { + try { + avconvstderr.wait(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + + /** + * Stops the avconv process + * + * @throws Exception + */ + private void stopAvconv() throws Exception { + + // It is necessary to kill the process in this way to be sure that + // SIGTERM (15) is sent + // as the kill signal. + String pid = "" + getUnixPID(avconv); + Process killingProcess = Runtime.getRuntime().exec( + new String[] { "kill", "-15", pid }); + killingProcess.waitFor(); + avconv.waitFor(); + avconvstdout.interrupt(); + avconvstderr.interrupt(); + } + + /** + * Starts a avserver process with the configuration file given as parameter + * + * @param configFile + * - avserver configuration file + * @throws IOException + */ + private void startAvserver(String configFile) throws IOException { + avserver = Runtime.getRuntime().exec( + new String[] { serverName, "-f", configFile, "-v", "info" }); + avserverstdout = new DefaultStreamGobbler(avserver.getInputStream(), "OUTPUT", + true, "avserver_out"); + avserverstdout.start(); + avserverstderr = new DefaultStreamGobbler(avserver.getErrorStream(), "ERROR", + true, "avserver_err"); + avserverstderr.start(); + } + + private void stopAvserver() throws InterruptedException { + avserver.destroy(); + avserver.waitFor(); + avserverstdout.interrupt(); + avserverstderr.interrupt(); + } + + /** + * Starts a avplay process. The streaming address must be the same that the + * one specified in the avserver configuration file - usually default + * "http://localhost:8090/test.swf" + * + * @throws IOException + */ + public void startAvplay() throws IOException { + avplay = Runtime.getRuntime().exec( + new String[] { playerName, "-probesize", "3000", + "http://localhost:8090/test.swf" }); + avplaystdout = new DefaultStreamGobbler(avplay.getInputStream(), "OUTPUT", + false, "avplay_out"); + avplaystdout.start(); + avplaystderr = new DefaultStreamGobbler(avplay.getErrorStream(), "ERROR", + false, "avplay_err"); + avplaystderr.start(); + + ffplayActive = true; + } + + /** + * Stops the avplay process + * + * @throws IOException + * @throws InterruptedException + */ + public void stopAvplay() throws IOException, InterruptedException { + avplay.destroy(); + avplay.waitFor(); + avplaystdout.interrupt(); + avplaystderr.interrupt(); + + ffplayActive = false; + } + + /** + * This method checks if the capture that has been notified is a local + * capture or not by checking the method CamServer.isLocalCapture(). If so, + * it shows a "save file" menu and starts a work session. + * + * @throws IOException + */ + private void checkLocalCapture() throws IOException { + + synchronized (CamServer.LockLocal) { + if (CamServer.isLocalCapture()) { + + CamServer.setLocalCapture(false); + + BufferedImage currentImage = getCurrentImage(); + + CamServer.logger.info(MyLogger.getLogRecord("LOCAL_CAPTURE")); + + ImagePlus imp2 = WindowManager.getCurrentImage(); + ImagePlus imagen = new ImagePlus("title", Toolkit + .getDefaultToolkit().createImage( + currentImage.getSource())); + if (imp2 != null) { + + ImageStack stack = imp2.getStack(); + if (stack.getSize() == 1) { + String label = stack.getSliceLabel(1); + if (label != null && label.indexOf("\n") != -1) + stack.setSliceLabel(null, 1); + Object obj = imagen.getProperty("Label"); + if (obj != null && (obj instanceof String)) + stack.setSliceLabel((String) obj, 1); + } + + stack.addSlice(null, imagen.getChannelProcessor()); + imp2.setStack(null, stack); + imp2.setSlice(stack.getSize()); + imp2.unlock(); + + ImageManager.getInstance().refresh(); + } else { + saver.saveAsTifInZip(imagen, true); + } + } + } + } + + /** + * This method checks if the capture that has been notified is an external + * capture or not by checking the method CamServer.getExternalCapture(). If + * so, the current image is sent to the client. + */ + private void checkExternalCapture() { + DataOutputStream outStream; + synchronized (CamServer.LockExternal) { + if (CamServer.getExternalCapture() != null) { + + outStream = CamServer.getExternalCapture(); + // Clear the DataOutputStream + CamServer.clearExternalCapture(); + + BufferedImage currentImage; + try { + currentImage = getCurrentImage(); + CamServer.logger.info(MyLogger + .getLogRecord("EXTERNAL_CAPTURE")); + ClientConnection.sendCurrentImage(outStream, currentImage); + } catch (IOException e) { + System.out.println("Fallo al capturar o enviar la imagen"); + e.printStackTrace(); + } + } + CamServer.LockExternal.notify(); + } + } + + /** + * private BufferedImage getCurrentImage() throws IOException{ + * + * try { stopAvconv(); + * + * JFrame progressFrame = new JFrame("Capturando..."); + * progressFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); Container + * content = progressFrame.getContentPane(); JProgressBar progress = new + * JProgressBar(); progress.setIndeterminate(true); Border border = + * BorderFactory.createTitledBorder("Capturando..."); + * progress.setBorder(border); content.add(progress, BorderLayout.NORTH); + * GraphicsEnvironment graphics = + * GraphicsEnvironment.getLocalGraphicsEnvironment(); + * progressFrame.setLocation(graphics.getCenterPoint()); + * progressFrame.setSize(300,70); progressFrame.setVisible(true); + * + * FrameGrabber frameGrabber; //frameGrabber = + * CamServer.getVideoDevice().getJPEGFrameGrabber(CamServer.widthFrame, // + * CamServer.heightFrame, CamServer.input, CamServer.std, + * CamServer.getJpegQuality()); + * + * //Set the jpegQuality to 99 //frameGrabber = + * CamServer.getVideoDevice().getJPEGFrameGrabber(CamServer.captureWidth, // + * CamServer.captureHeight, CamServer.input, CamServer.std, 99); + * + * frameGrabber = + * CamServer.getVideoDevice().getYUVFrameGrabber(CamServer.captureWidth, + * CamServer.captureHeight, CamServer.input, CamServer.std); + * + * + * ImageCallback imageCallback = new ImageCallback(); + * frameGrabber.setCaptureCallback(imageCallback); + * frameGrabber.startCapture(); + * + * BufferedImage currentImage = imageCallback.getCurrentImage(); + * + * frameGrabber.stopCapture(); + * CamServer.getVideoDevice().releaseFrameGrabber(); + * + * //Thread.sleep(3000); + * + * startAvconv(); + * + * if (ffplayActive){ stopAvplay(); startAvplay(); } + * progressFrame.setVisible(false); progressFrame.dispose(); + * + * return currentImage; + * + * } catch (V4L4JException | InterruptedException e) { // TODO + * Auto-generated catch block e.printStackTrace(); return null; } + * + * } + */ + + /** + * This method return the current image with the parameters saved in the + * CamServer static instance (device, width, height). + * + * @return - the current image + * @throws IOException + */ + private BufferedImage getCurrentImage() throws IOException { + + try { + stopAvconv(); + + JFrame progressFrame = new JFrame("Capturando..."); + progressFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + Container content = progressFrame.getContentPane(); + JProgressBar progress = new JProgressBar(); + progress.setIndeterminate(true); + Border border = BorderFactory.createTitledBorder("Capturando..."); + progress.setBorder(border); + content.add(progress, BorderLayout.NORTH); + GraphicsEnvironment graphics = GraphicsEnvironment + .getLocalGraphicsEnvironment(); + progressFrame.setLocation(graphics.getCenterPoint()); + progressFrame.setSize(300, 70); + progressFrame.setVisible(true); + + Process capture = Runtime.getRuntime().exec( + new String[] { + encoderName, + "-y", + "-s", + CamServer.captureWidth + "x" + + CamServer.captureHeight, "-f", + "video4linux2", "-i", + CamServer.getVideoDevice().getDevicefile(), + "-vframes", "1", "-qmin", captureQuality, "-qmax", + captureQuality, capturePath }); + capture.waitFor(); + + BufferedImage currentImage = null; + currentImage = ImageIO.read(new File(capturePath)); + + startAvconv(); + + if (ffplayActive) { + captureTaken = true; + stopAvplay(); + } + progressFrame.setVisible(false); + progressFrame.dispose(); + + return currentImage; + + } catch (IOException | InterruptedException e) { + e.printStackTrace(); + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + private int getUnixPID(Process process) throws Exception { + + if (process.getClass().getName().equals("java.lang.UNIXProcess")) { + Class cl = process.getClass(); + Field field = cl.getDeclaredField("pid"); + field.setAccessible(true); + Object pidObject = field.get(process); + return (Integer) pidObject; + } else { + throw new IllegalArgumentException("Needs to be a UNIXProcess"); + } + } + + @Override + public String getHeadHTMLText() { + String headHTML = ""; + return headHTML; + } + + @Override + public String getBodyHTMLText() { + String bodyHTML = ""; + return bodyHTML; + } + + public class FfmpegStreamGobbler extends DefaultStreamGobbler { + + public FfmpegStreamGobbler (InputStream is, String type, boolean print, String name){ + super(is,type,print,name); + } + + @Override + protected void analyzeLine (String line){ + if (line.startsWith("frame=")) { + synchronized (this) { + notify(); + } + } + } + } + +} \ No newline at end of file diff --git a/src/communication/v4l/Settings.java b/src/communication/v4l/Settings.java new file mode 100755 index 0000000..3ea1d49 --- /dev/null +++ b/src/communication/v4l/Settings.java @@ -0,0 +1,683 @@ +package communication.v4l; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Component; +import java.awt.Dimension; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Rectangle; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.File; +import java.io.IOException; +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.util.Collections; +import java.util.Comparator; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.List; +import java.util.Vector; + +import javax.swing.DefaultComboBoxModel; +import javax.swing.JButton; +import javax.swing.JComboBox; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTextField; +import javax.swing.JToggleButton; +import javax.swing.SwingConstants; +import javax.swing.border.EmptyBorder; + +import au.edu.jcu.v4l4j.DeviceInfo; +import au.edu.jcu.v4l4j.FrameInterval.DiscreteInterval; +import au.edu.jcu.v4l4j.ImageFormat; +import au.edu.jcu.v4l4j.ImageFormatList; +import au.edu.jcu.v4l4j.ResolutionInfo.DiscreteResolution; +import au.edu.jcu.v4l4j.VideoDevice; +import au.edu.jcu.v4l4j.exceptions.V4L4JException; +import javax.swing.JCheckBox; +import java.awt.GridLayout; +import javax.swing.BoxLayout; + +import util.ConfigFile; + + +import java.awt.Insets; +import javax.swing.border.MatteBorder; +import java.awt.Font; + +/** + * This class represents a control panel of the streaming options: selection of + * the camera, selection of video streaming parameters and adjusting of camera + * controls (bright, gamma, etc). It shows a JFrame that is split into three + * parts: the top one shows information about the camera and allows to choose a + * different one (this is implemented in Settings class); the medium-left one is + * about streaming parameters such as resolution, framerate, group of pictures, + * etc (this is implemented in StreamingConfig or StreamingConfigFallback class, + * depending on the camera characteristics); and the medium-right one is about + * camera controls such as bright, gamma, contrast, etc (this is implemented in + * CameraAdvancedControls class). This class also provides some static methods + * that retrieves information about the cameras connected to the computer. + * + * @author ehas + * + */ +public class Settings extends JFrame { + + private static final int DEFAULT_WIDTH = 640; + private static final int DEFAULT_HEIGHT = 480; + private static final int DEFAULT_INTERVAL_NUM = 1; + private static final int DEFAULT_INTERVAL_DEN = 10; + + private static Vector deviceFiles; + + private JPanel contentPane; + private JPanel centerPanel; + private StreamingConfig videoOptionPanel; + private JScrollPane scrollPaneEast; + private JScrollPane scrollPaneWest; + private JTextField textVideoDeviceFile; + + private static String v4lSysfsPath = "/sys/class/video4linux/"; + private String videoDeviceFile = "---"; + private JComboBox deviceComboBox; + public Settings frame; + private JTextField textName; + private JTextField textIPAddress; + private JTextField textUserName; + private JTextField textUserPassword; + private JToggleButton security; + private JLabel securitySelected; + + private final int DEFAULT_FRAME_WIDTH = 420; + private final int DEFAULT_FRAME_HEIGHT = 420; + + /** + * Create the frame. + */ + public Settings() { + setTitle("Panel de Control"); + + // Check if any device has been selected + if (CamServer.getVideoDevice() != null) { + videoDeviceFile = CamServer.getVideoDevice().getDevicefile(); + } else { + Object[] devices = listV4LDeviceFiles(); + if (devices.length == 0) { + + } else { + videoDeviceFile = devices[0].toString(); + } + } + + // I suppose that 12 is the default font size. The frame is scaled accordingly. + String sizeS = ConfigFile.getValue(ConfigFile.FONT_SIZE); + int width = DEFAULT_FRAME_WIDTH; + int height = DEFAULT_FRAME_HEIGHT; + if (sizeS != null){ + int size = Integer.parseInt(sizeS); + width = width * size / 12; + height = (height/2) * (1 + (size / 12)); + } + + setBounds(100,100,width,height); + setLocationRelativeTo(null); + + setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); + contentPane = new JPanel(); + contentPane.setBackground(Color.LIGHT_GRAY); + contentPane.setBorder(new EmptyBorder(5, 5, 5, 5)); + setContentPane(contentPane); + + initGUI(); + setValues(); + + } + + private void initGUI() { + + JPanel deviceInfoPanel = new JPanel(); + deviceInfoPanel.setAlignmentY(Component.TOP_ALIGNMENT); + deviceInfoPanel.setAlignmentX(Component.LEFT_ALIGNMENT); + deviceInfoPanel.setBorder(new EmptyBorder(5, 5, 5, 5)); + deviceInfoPanel.setToolTipText("Dispositivo"); + contentPane.setLayout(new BorderLayout(0, 0)); + + GridBagLayout gbl_deviceInfoPanel = new GridBagLayout(); + gbl_deviceInfoPanel.columnWidths = new int[] {10, 20, 10, 20}; + gbl_deviceInfoPanel.columnWeights = new double[] { 1, 1, 1, 1}; + deviceInfoPanel.setLayout(gbl_deviceInfoPanel); + + deviceComboBox = new JComboBox(); + deviceComboBox.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent arg0) { + videoDeviceFile = deviceComboBox.getSelectedItem().toString(); + try { + VideoDevice dev = new VideoDevice(videoDeviceFile); + textName.setText(dev.getDeviceInfo().getName()); + } catch (V4L4JException e) { + e.printStackTrace(); + } + + if (videoOptionPanel != null) { + contentPane.remove(videoOptionPanel); + } + + // Add panel with the streaming parameters + if (needFallbackMode(videoDeviceFile)) { + videoOptionPanel = new StreamingConfigFallback( + videoDeviceFile); + } else { + videoOptionPanel = new StreamingConfig(videoDeviceFile); + } + // videoOptionPanel.setBounds(5, 75, 233, 292); + // videoOptionPanel.setLayout(null); + // contentPane.add(videoOptionPanel, BorderLayout.LINE_START); + // Set default settings + videoOptionPanel.setDefaultSettings(); + scrollPaneWest.setViewportView(videoOptionPanel); + + // Add panel with the camera controls + scrollPaneEast.setViewportView(new CameraAdvancedControls( + videoDeviceFile)); + + } + }); + + JLabel lblDispositivo = new JLabel("En uso:"); + lblDispositivo.setHorizontalAlignment(SwingConstants.LEFT); + GridBagConstraints gbc_lblDispositivo = new GridBagConstraints(); + gbc_lblDispositivo.fill = GridBagConstraints.BOTH; + gbc_lblDispositivo.insets = new Insets(0, 0, 5, 5); + gbc_lblDispositivo.gridx = 0; + gbc_lblDispositivo.gridy = 0; + deviceInfoPanel.add(lblDispositivo, gbc_lblDispositivo); + + textVideoDeviceFile = new JTextField(); + textVideoDeviceFile.setText(videoDeviceFile); + textVideoDeviceFile.setEditable(false); + GridBagConstraints gbc_textVideoDeviceFile = new GridBagConstraints(); + gbc_textVideoDeviceFile.fill = GridBagConstraints.BOTH; + gbc_textVideoDeviceFile.insets = new Insets(0, 0, 5, 5); + gbc_textVideoDeviceFile.gridx = 1; + gbc_textVideoDeviceFile.gridy = 0; + deviceInfoPanel.add(textVideoDeviceFile, gbc_textVideoDeviceFile); + + JLabel lblPosibles = new JLabel("Posibles:"); + GridBagConstraints gbc_lblPosibles = new GridBagConstraints(); + gbc_lblPosibles.fill = GridBagConstraints.BOTH; + gbc_lblPosibles.insets = new Insets(0, 0, 5, 5); + gbc_lblPosibles.gridx = 2; + gbc_lblPosibles.gridy = 0; + deviceInfoPanel.add(lblPosibles, gbc_lblPosibles); + + GridBagConstraints gbc_deviceComboBox = new GridBagConstraints(); + gbc_deviceComboBox.fill = GridBagConstraints.BOTH; + gbc_deviceComboBox.insets = new Insets(0, 0, 5, 0); + gbc_deviceComboBox.gridx = 3; + gbc_deviceComboBox.gridy = 0; + deviceInfoPanel.add(deviceComboBox, gbc_deviceComboBox); + + JLabel lblIp = new JLabel("IP (eth0):"); + GridBagConstraints gbc_lblIp = new GridBagConstraints(); + gbc_lblIp.fill = GridBagConstraints.BOTH; + gbc_lblIp.insets = new Insets(0, 0, 0, 5); + gbc_lblIp.gridx = 0; + gbc_lblIp.gridy = 1; + deviceInfoPanel.add(lblIp, gbc_lblIp); + + textIPAddress = new JTextField(); + textIPAddress.setEditable(false); + GridBagConstraints gbc_textIPAddress = new GridBagConstraints(); + gbc_textIPAddress.fill = GridBagConstraints.BOTH; + gbc_textIPAddress.insets = new Insets(0, 0, 0, 5); + gbc_textIPAddress.gridx = 1; + gbc_textIPAddress.gridy = 1; + deviceInfoPanel.add(textIPAddress, gbc_textIPAddress); + + JLabel lblNombre = new JLabel("Nombre:"); + GridBagConstraints gbc_lblNombre = new GridBagConstraints(); + gbc_lblNombre.fill = GridBagConstraints.BOTH; + gbc_lblNombre.insets = new Insets(0, 0, 0, 5); + gbc_lblNombre.gridx = 2; + gbc_lblNombre.gridy = 1; + deviceInfoPanel.add(lblNombre, gbc_lblNombre); + + textName = new JTextField(); + textName.setEditable(false); + GridBagConstraints gbc_textName = new GridBagConstraints(); + gbc_textName.fill = GridBagConstraints.BOTH; + gbc_textName.gridx = 3; + gbc_textName.gridy = 1; + deviceInfoPanel.add(textName, gbc_textName); + + // contentPane.setLayout(null); + contentPane.add(deviceInfoPanel, BorderLayout.NORTH); + + JPanel panel = new JPanel(); + + JButton btnAceptar = new JButton("Aceptar/reiniciar"); + btnAceptar.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent arg0) { + if (!saveValues()) { + return; + } + dispose(); + if (CamServer.active) { + CamServer.stop(); + try { + CamServer.start(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + }); + // btnAceptar.setBounds(12, 12, 163, 25); + panel.add(btnAceptar); + contentPane.add(panel, BorderLayout.SOUTH); + + centerPanel = new JPanel(); + GridBagLayout gbl_centerPanel = new GridBagLayout(); + gbl_centerPanel.columnWeights = new double[] { 1, 1 }; + gbl_centerPanel.rowWeights = new double[] { 1, 1, 1, 1, 0.5 }; + centerPanel.setLayout(gbl_centerPanel); + contentPane.add(centerPanel, BorderLayout.CENTER); + + GridBagConstraints c = new GridBagConstraints(); + + scrollPaneEast = new JScrollPane(); + scrollPaneEast.setBorder(new MatteBorder(5, 5, 5, 5, (Color) Color.LIGHT_GRAY)); + scrollPaneEast.getVerticalScrollBar().setBlockIncrement(40); + scrollPaneEast.getVerticalScrollBar().setUnitIncrement(25); + + c.fill = GridBagConstraints.BOTH; + c.gridx = 1; + c.gridy = 0; + c.gridheight = 4; + c.gridwidth = 1; + centerPanel.add(scrollPaneEast, c); + + scrollPaneWest = new JScrollPane(); + scrollPaneWest.setBorder(new MatteBorder(5, 5, 5, 5, (Color) Color.LIGHT_GRAY)); + scrollPaneWest.getVerticalScrollBar().setBlockIncrement(40); + scrollPaneWest.getVerticalScrollBar().setUnitIncrement(25); + + GridBagConstraints c2 = new GridBagConstraints(); + c2.fill = GridBagConstraints.BOTH; + c2.gridx = 0; + c2.gridy = 0; + c2.gridheight = 4; + centerPanel.add(scrollPaneWest, c2); + + JPanel authorizationPane = new JPanel(); + authorizationPane.setBorder(new MatteBorder(5, 5, 5, 5, (Color) Color.LIGHT_GRAY)); + + GridBagConstraints c3 = new GridBagConstraints(); + c3.fill = GridBagConstraints.BOTH; + c3.gridx = 0; + c3.gridy = 4; + c3.gridheight = 1; + c3.gridwidth = 2; + centerPanel.add(authorizationPane, c3); + + GridBagLayout gbl_auth = new GridBagLayout(); + gbl_auth.columnWeights = new double[]{0.2, 0.5, 1}; + authorizationPane.setLayout(gbl_auth); + + GridBagConstraints gbc_auth1 = new GridBagConstraints(); + gbc_auth1.fill = GridBagConstraints.BOTH; + gbc_auth1.gridx = 0; + gbc_auth1.gridy = 0; + gbc_auth1.insets = new Insets(5, 5, 5, 5); + + security = new JToggleButton("Seguridad"); + security.setHorizontalAlignment(SwingConstants.LEFT); + authorizationPane.add(security, gbc_auth1); + security.addActionListener(new ActionListener() { + + @Override + public void actionPerformed(ActionEvent arg0) { + if (((JToggleButton) arg0.getSource()).isSelected()) { + textUserName.setEnabled(true); + textUserPassword.setEnabled(true); + securitySelected.setText("Activada"); + } else { + textUserName.setEnabled(false); + textUserPassword.setEnabled(false); + securitySelected.setText("Desactivada"); + } + } + + }); + + GridBagConstraints gbc_auth2 = new GridBagConstraints(); + gbc_auth2.fill = GridBagConstraints.BOTH; + gbc_auth2.gridx = 1; + gbc_auth2.gridy = 0; + gbc_auth2.insets = new Insets(5, 5, 5, 5); + JLabel lblUserName = new JLabel("Nombre:"); + lblUserName.setHorizontalAlignment(SwingConstants.LEFT); + authorizationPane.add(lblUserName, gbc_auth2); + + GridBagConstraints gbc_auth3 = new GridBagConstraints(); + gbc_auth3.fill = GridBagConstraints.BOTH; + gbc_auth3.gridx = 2; + gbc_auth3.gridy = 0; + gbc_auth3.insets = new Insets(5, 5, 5, 5); + textUserName = new JTextField(); + authorizationPane.add(textUserName, gbc_auth3); + + GridBagConstraints gbc_auth4 = new GridBagConstraints(); + gbc_auth4.fill = GridBagConstraints.BOTH; + gbc_auth4.gridx = 1; + gbc_auth4.gridy = 1; + gbc_auth4.insets = new Insets(5, 5, 5, 5); + JLabel lblUserPassword = new JLabel("Contraseña:"); + lblUserPassword.setHorizontalAlignment(SwingConstants.LEFT); + authorizationPane.add(lblUserPassword, gbc_auth4); + + GridBagConstraints gbc_auth5 = new GridBagConstraints(); + gbc_auth5.fill = GridBagConstraints.BOTH; + gbc_auth5.gridx = 2; + gbc_auth5.gridy = 1; + gbc_auth5.insets = new Insets(5, 5, 5, 5); + textUserPassword = new JTextField(); + authorizationPane.add(textUserPassword, gbc_auth5); + + GridBagConstraints gbc_auth6 = new GridBagConstraints(); + gbc_auth6.fill = GridBagConstraints.BOTH; + gbc_auth6.gridx = 0; + gbc_auth6.gridy = 1; + gbc_auth6.anchor = GridBagConstraints.WEST; + gbc_auth6.insets = new Insets(5, 5, 5, 5); + securitySelected = new JLabel("Desactivada"); + securitySelected.setHorizontalAlignment(SwingConstants.LEFT); + authorizationPane.add(securitySelected, gbc_auth6); + + } + + private void setValues() { + + try { + String IPAddress = getIPAddress(); + textIPAddress.setText(IPAddress); + } catch (Exception e1) { + textIPAddress.setText("Error con IP en eth0"); + } + + Object[] devices = listV4LDeviceFiles(); + deviceComboBox.setModel(new DefaultComboBoxModel(devices)); + + // Highlight the device in use + for (Object device : devices) { + if (device.toString().equals(videoDeviceFile)) { + deviceComboBox.setSelectedItem(device); + try { + VideoDevice vd = new VideoDevice(videoDeviceFile); + String name = vd.getDeviceInfo().getName(); + textName.setText(name); + } catch (V4L4JException e) { + e.printStackTrace(); + } + } + } + videoOptionPanel.setValues(); + + // Authorization panel + if (CamServer.authentication) { + security.doClick(); + } else { + textUserName.setEnabled(false); + textUserPassword.setEnabled(false); + } + textUserName.setText(CamServer.userName); + textUserPassword.setText(CamServer.userPassword); + + } + + private static Object[] listV4LDeviceFiles() { + + if (deviceFiles == null) { + + deviceFiles = new Vector(); + File dir = new File(v4lSysfsPath); + String[] files = dir.list(); + + for (String file : files) { + // the following test the presence of "video" in + // each file name - not very portable - relying on HAL + // would be much better ... + if (file.indexOf("video") != -1) { + File inputDir = new File(v4lSysfsPath + "/" + file + + "/device/driver/module"); + if (inputDir.exists()) { + deviceFiles.add("/dev/" + file); + } + } + + } + + if (deviceFiles.size() == 0) { + System.err.println("Unable to detect any V4L device file\n" + + "Set the 'test.device' property to a valid\nvideo " + + "device file and run this program again "); + System.exit(0); + } + } + + return deviceFiles.toArray(); + } + + /** + * This class returns an array with the available resolutions for a camera. + * If it is not possible to retrieve that information, it returns null. + * + * @param dev + * Camera to obtain the available resolution for. + * @return Array of available resolutions. Null if not possible to retrieve + * that information. + */ + public static DiscreteResolution[] listAvailableResolutions(String dev) { + try { + VideoDevice vd = new VideoDevice(dev); + DeviceInfo devInfo = vd.getDeviceInfo(); + ImageFormatList formatList = devInfo.getFormatList(); + List imageFormats = formatList.getNativeFormats(); + List resolutionList = imageFormats.get(0) + .getResolutionInfo().getDiscreteResolutions(); + Collections.sort(resolutionList, new ResolutionComparator()); + DiscreteResolution[] resolutions = resolutionList + .toArray(new DiscreteResolution[resolutionList.size()]); + + return resolutions; + + } catch (V4L4JException e) { + return null; + } + } + + private static class ResolutionComparator implements + Comparator { + @Override + public int compare(DiscreteResolution s, DiscreteResolution t) { + if (s.getWidth() < t.getWidth()) { + return -1; + } else if (s.getWidth() > t.getWidth()) { + return 1; + } else { + if (s.getHeight() < t.getHeight()) { + return -1; + } else if (s.getHeight() > t.getHeight()) { + return 1; + } else { + return 0; + } + } + } + } + + /** + * This class returns an array with the available intervals associated to a + * DiscreteResolution object. If it is not possible to obtain that + * information, it returns null. + * + * @param disRes + * DiscreteResolution object that intervals are associated to. + * @return Array of available intervals. Null if not possible to retrieve + * that information. + */ + public static DiscreteInterval[] listAvailableIntervals( + DiscreteResolution disRes) { + + try { + List intervalList = disRes.getFrameInterval() + .getDiscreteIntervals(); + DiscreteInterval[] intervals = intervalList + .toArray(new DiscreteInterval[intervalList.size()]); + + return intervals; + } catch (Exception e) { + return null; + } + } + + private boolean saveValues() { + // Save authorization options + if (security.isSelected()) { + if (textUserName.getText().trim().isEmpty() + || textUserPassword.getText().trim().isEmpty()) { + JOptionPane.showMessageDialog(null, + "El usuario o la contraseña están vacíos.", + "Fallo al guardar", JOptionPane.ERROR_MESSAGE); + return false; + } + CamServer.authentication = true; + CamServer.userName = textUserName.getText(); + CamServer.userPassword = textUserPassword.getText(); + } else { + CamServer.authentication = false; + } + + // Save the new video device + CamServer.setVideoDevice(deviceComboBox.getSelectedItem().toString()); + + videoOptionPanel.saveValues(); + + return true; + } + + /** + * Returns a string with the path to the default device. It gets the list of + * available devices and chooses the first one. If no devices are available, + * it returns null. + * + * @return String with the default device path. Null if no devices are + * available. + */ + public static String getDefaultVideoDevice() { + Object[] devices = listV4LDeviceFiles(); + if (devices.length == 0) { + return null; + } else { + return devices[0].toString(); + } + } + + /** + * This class returns a hashtable with three elements identified by + * "videoResolution", "captureResolution" and "interval" labels that + * represent default settings for the camera. + * + * @param dev + * Camera to obtain default settings for. + * @return Hashtable with the default settings. + */ + public static Hashtable getDefaultSettings(String dev) { + + Hashtable settings = new Hashtable(); + + DiscreteResolution[] resolutions = listAvailableResolutions(dev); + + if (!needFallbackMode(dev)) { + + DiscreteResolution defaultVideoResolution = resolutions[0]; + DiscreteResolution defaultCaptureResolution = resolutions[resolutions.length - 1]; + + for (DiscreteResolution resolution : resolutions) { + // Look for a default video resolution. If 640x480 is present, + // it is selected + if (resolution.getWidth() == DEFAULT_WIDTH + && resolution.getHeight() == DEFAULT_HEIGHT) { + defaultVideoResolution = resolution; + } + } + + DiscreteInterval[] intervals = listAvailableIntervals(defaultVideoResolution); + DiscreteInterval defaultInterval = intervals[0]; + + for (DiscreteInterval interval : intervals) { + // Look for a default video interval. If 10 is present, it is + // selected + if (interval.getNum() == DEFAULT_INTERVAL_NUM + && interval.getDenom() == DEFAULT_INTERVAL_DEN) { + defaultInterval = interval; + } + } + + settings.put("videoResolution", defaultVideoResolution.getWidth() + + "x" + defaultVideoResolution.getHeight()); + settings.put("captureResolution", + defaultCaptureResolution.getWidth() + "x" + + defaultCaptureResolution.getHeight()); + settings.put("interval", defaultInterval.getNum() + "/" + + defaultInterval.getDenom()); + return settings; + } else { + settings.put("videoResolution", "640x480"); + settings.put("captureResolution", "640x480"); + settings.put("interval", "1/10"); + return settings; + } + + } + + private String getIPAddress() throws Exception { + Enumeration e = NetworkInterface.getNetworkInterfaces(); + while (e.hasMoreElements()) { + NetworkInterface n = (NetworkInterface) e.nextElement(); + if (n.getDisplayName().equals("eth0")) { + Enumeration ee = n.getInetAddresses(); + while (ee.hasMoreElements()) { + InetAddress i = (InetAddress) ee.nextElement(); + if (i.getHostAddress().indexOf(".") != -1) { + return i.getHostAddress(); + } + } + } + } + throw new Exception(); + } + + /** + * This method is used to check if the camera can provide information about + * the available resolutions and frame intervals. If that information cannot + * be retrieved, it is necessary to use the fallback mode, which means that + * it is not possible to create a combobox with the available resolutions. + * Instead of that, a JTextField object is created, so resolution value must + * be entered manually. + * + * @param dev + * Device to check if fallback mode is needed + * @return true if fallback mode is needed, false if not + */ + public static boolean needFallbackMode(String dev) { + DiscreteResolution[] resolutions = listAvailableResolutions(dev); + return (resolutions == null || listAvailableIntervals(resolutions[0]) == null); + } +} \ No newline at end of file diff --git a/src/communication/v4l/Streamer.java b/src/communication/v4l/Streamer.java new file mode 100644 index 0000000..a933323 --- /dev/null +++ b/src/communication/v4l/Streamer.java @@ -0,0 +1,22 @@ +package communication.v4l; + +public interface Streamer extends Runnable { + + /** + * This method must return the header HTML code needed to display the stream, + * typically javascript code. + * + * @return + */ + public String getHeadHTMLText(); + + /** + * This method must return the body HTML code needed to display the stream in the + * web browser. The video must be embedded in a HTML object with + * class='stream' + * + * @return + */ + public String getBodyHTMLText(); + +} diff --git a/src/communication/v4l/StreamingConfig.java b/src/communication/v4l/StreamingConfig.java new file mode 100644 index 0000000..9913f24 --- /dev/null +++ b/src/communication/v4l/StreamingConfig.java @@ -0,0 +1,334 @@ +package communication.v4l; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.Hashtable; + +import javax.swing.ComboBoxModel; +import javax.swing.DefaultComboBoxModel; +import javax.swing.JComboBox; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextField; + +import util.MyComboBoxRenderer; + +import au.edu.jcu.v4l4j.ResolutionInfo.DiscreteResolution; + +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.GridLayout; +import java.awt.Insets; + +public class StreamingConfig extends JPanel { + + private JComboBox textVideoResolution; + private JComboBox textInterval; + private JComboBox textCaptureResolution; + private JTextField textPort; + private JTextField textQmin; + private JTextField textQmax; + private JTextField textGop; + private JTextField textBps; + + protected String dev; + + public StreamingConfig(String device) { + dev = device; + initCommon(); + initComboBoxes(); + } + + private void initCommon() { + setLayout(new GridBagLayout()); + + GridBagConstraints gbc1 = new GridBagConstraints(); + gbc1.gridx = 0; + gbc1.gridy = 0; + gbc1.gridwidth = 2; + gbc1.anchor = GridBagConstraints.WEST; + gbc1.insets = new Insets(0, 0, 5, 0); + JLabel lblOpcionesDeVdeo = new JLabel("Opciones de vídeo:"); + this.add(lblOpcionesDeVdeo, gbc1); + + GridBagConstraints gbc2 = new GridBagConstraints(); + gbc2.gridx = 0; + gbc2.gridy = 1; + gbc2.anchor = GridBagConstraints.WEST; + JLabel lblResolucin_1 = new JLabel("Resolución:"); + this.add(lblResolucin_1, gbc2); + + GridBagConstraints gbc3 = new GridBagConstraints(); + gbc3.gridx = 0; + gbc3.gridy = 2; + gbc3.anchor = GridBagConstraints.WEST; + JLabel lblTasa = new JLabel("Tasa:"); + this.add(lblTasa, gbc3); + + GridBagConstraints gbc4 = new GridBagConstraints(); + gbc4.gridx = 0; + gbc4.gridy = 3; + gbc4.anchor = GridBagConstraints.WEST; + JLabel lblPuerto = new JLabel("Puerto:"); + this.add(lblPuerto, gbc4); + + GridBagConstraints gbc5 = new GridBagConstraints(); + gbc5.gridx = 1; + gbc5.gridy = 3; + gbc5.fill = GridBagConstraints.HORIZONTAL; + textPort = new JTextField(); + this.add(textPort, gbc5); + + GridBagConstraints gbc6 = new GridBagConstraints(); + gbc6.gridx = 0; + gbc6.gridy = 4; + gbc6.anchor = GridBagConstraints.WEST; + JLabel lblQmin = new JLabel("Qmin (0-31):"); + this.add(lblQmin, gbc6); + + GridBagConstraints gbc7 = new GridBagConstraints(); + gbc7.gridx = 1; + gbc7.gridy = 4; + gbc7.fill = GridBagConstraints.HORIZONTAL; + textQmin = new JTextField(); + this.add(textQmin, gbc7); + + GridBagConstraints gbc8 = new GridBagConstraints(); + gbc8.gridx = 0; + gbc8.gridy = 5; + gbc8.anchor = GridBagConstraints.WEST; + JLabel lblNewLabel = new JLabel("Qmax (0-31):"); + this.add(lblNewLabel, gbc8); + + GridBagConstraints gbc9 = new GridBagConstraints(); + gbc9.gridx = 1; + gbc9.gridy = 5; + gbc9.fill = GridBagConstraints.HORIZONTAL; + textQmax = new JTextField(); + this.add(textQmax, gbc9); + + GridBagConstraints gbc10 = new GridBagConstraints(); + gbc10.gridx = 0; + gbc10.gridy = 6; + gbc10.anchor = GridBagConstraints.WEST; + JLabel lblGop = new JLabel("GOP:"); + this.add(lblGop, gbc10); + + GridBagConstraints gbc11 = new GridBagConstraints(); + gbc11.gridx = 1; + gbc11.gridy = 6; + gbc11.fill = GridBagConstraints.HORIZONTAL; + textGop = new JTextField(); + this.add(textGop, gbc11); + + GridBagConstraints gbc12 = new GridBagConstraints(); + gbc12.gridx = 0; + gbc12.gridy = 7; + gbc12.anchor = GridBagConstraints.WEST; + JLabel lblBps = new JLabel("bps:"); + this.add(lblBps, gbc12); + + GridBagConstraints gbc13 = new GridBagConstraints(); + gbc13.gridx = 1; + gbc13.gridy = 7; + gbc13.fill = GridBagConstraints.HORIZONTAL; + textBps = new JTextField(); + textBps.setText("-1"); + this.add(textBps, gbc13); + + GridBagConstraints gbc14 = new GridBagConstraints(); + gbc14.gridx = 0; + gbc14.gridy = 8; + gbc14.gridwidth = 2; + gbc14.anchor = GridBagConstraints.WEST; + gbc14.insets = new Insets(10, 0, 5, 0); + JLabel lblOpcionesDeCaptura = new JLabel("Opciones de captura:"); + this.add(lblOpcionesDeCaptura, gbc14); + + GridBagConstraints gbc15 = new GridBagConstraints(); + gbc15.gridx = 0; + gbc15.gridy = 9; + gbc15.anchor = GridBagConstraints.WEST; + JLabel lblResolucin = new JLabel("Resolución:"); + this.add(lblResolucin, gbc15); + + } + + protected void initComboBoxes() { + + textVideoResolution = new JComboBox(); + textVideoResolution.setRenderer(new MyComboBoxRenderer()); + textVideoResolution.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent arg0) { + textInterval.setModel(new DefaultComboBoxModel( + Settings.listAvailableIntervals((DiscreteResolution) textVideoResolution + .getSelectedItem()))); + } + }); + + GridBagConstraints gbc1 = new GridBagConstraints(); + gbc1.gridx = 1; + gbc1.gridy = 1; + gbc1.fill = GridBagConstraints.HORIZONTAL; + this.add(textVideoResolution, gbc1); + textVideoResolution.setModel(new DefaultComboBoxModel(Settings + .listAvailableResolutions(dev))); + + + textInterval = new JComboBox(); + textInterval.setRenderer(new MyComboBoxRenderer()); + textInterval + .setModel(new DefaultComboBoxModel( + Settings.listAvailableIntervals((DiscreteResolution) textVideoResolution + .getSelectedItem()))); + + GridBagConstraints gbc2 = new GridBagConstraints(); + gbc2.gridx = 1; + gbc2.gridy = 2; + gbc2.fill = GridBagConstraints.HORIZONTAL; + this.add(textInterval, gbc2); + + textCaptureResolution = new JComboBox(); + textCaptureResolution.setRenderer(new MyComboBoxRenderer()); + + GridBagConstraints gbc3 = new GridBagConstraints(); + gbc3.gridx = 1; + gbc3.gridy = 9; + gbc3.fill = GridBagConstraints.HORIZONTAL; + this.add(textCaptureResolution, gbc3); + textCaptureResolution.setModel(new DefaultComboBoxModel(Settings + .listAvailableResolutions(dev))); + + } + + public void setValues() { + + // Write variables in use + textPort.setText("" + CamServer.getPort()); + textQmin.setText("" + CamServer.qmin); + textQmax.setText("" + CamServer.qmax); + textGop.setText("" + CamServer.gop); + textBps.setText("" + CamServer.bps); + + setValuesComboBoxes(); + } + + protected void setValuesComboBoxes() { + // Highlight the video resolution in use + String videoResolution = CamServer.videoWidth + "x" + + CamServer.videoHeight; + ComboBoxModel videoResolutions = textVideoResolution.getModel(); + for (int i = 0; i < videoResolutions.getSize(); i++) { + if (videoResolutions.getElementAt(i).toString() + .startsWith(videoResolution)) { + textVideoResolution.setSelectedItem(videoResolutions + .getElementAt(i)); + } + } + + // Highlight the frame interval in use + String frameInterval = CamServer.intervalNum + "/" + + CamServer.intervalDen; + ComboBoxModel frameIntervals = textInterval.getModel(); + for (int i = 0; i < frameIntervals.getSize(); i++) { + if (frameIntervals.getElementAt(i).toString() + .startsWith(frameInterval)) { + textInterval.setSelectedItem(frameIntervals.getElementAt(i)); + } + } + + // Highlight the capture resolution in use + String captureResolution = CamServer.captureWidth + "x" + + CamServer.captureHeight; + ComboBoxModel captureResolutions = textCaptureResolution.getModel(); + for (int i = 0; i < captureResolutions.getSize(); i++) { + if (captureResolutions.getElementAt(i).toString() + .startsWith(captureResolution)) { + textCaptureResolution.setSelectedItem(captureResolutions + .getElementAt(i)); + } + } + } + + public void setDefaultSettings() { + + // Set default values + textPort.setText("" + CamServer.DEFAULT_PORT); + textQmin.setText("" + CamServer.DEFAULT_QMIN); + textQmax.setText("" + CamServer.DEFAULT_QMAX); + textGop.setText("" + CamServer.DEFAULT_GOP); + textBps.setText(CamServer.DEFAULT_BPS); + + setDefaultSettingsComboBoxes(); + + } + + protected void setDefaultSettingsComboBoxes() { + Hashtable defaultSettings = Settings + .getDefaultSettings(dev); + String videoResolution = defaultSettings.get("videoResolution"); + String captureResolution = defaultSettings.get("captureResolution"); + String interval = defaultSettings.get("interval"); + + // Highlight the default video resolution + ComboBoxModel videoResolutions = textVideoResolution.getModel(); + for (int i = 0; i < videoResolutions.getSize(); i++) { + if (videoResolutions.getElementAt(i).toString() + .startsWith(videoResolution)) { + textVideoResolution.setSelectedItem(videoResolutions + .getElementAt(i)); + } + } + + // Highlight the default capture resolution + ComboBoxModel captureResolutions = textCaptureResolution.getModel(); + for (int i = 0; i < captureResolutions.getSize(); i++) { + if (captureResolutions.getElementAt(i).toString() + .startsWith(captureResolution)) { + textCaptureResolution.setSelectedItem(captureResolutions + .getElementAt(i)); + } + } + + // Highlight the frame interval in use + ComboBoxModel frameIntervals = textInterval.getModel(); + for (int i = 0; i < frameIntervals.getSize(); i++) { + if (frameIntervals.getElementAt(i).toString().startsWith(interval)) { + textInterval.setSelectedItem(frameIntervals.getElementAt(i)); + } + } + } + + public void saveValues() { + + // Save variables + CamServer.port = Integer.parseInt(textPort.getText()); + CamServer.qmin = Integer.parseInt(textQmin.getText()); + CamServer.qmax = Integer.parseInt(textQmax.getText()); + CamServer.gop = Integer.parseInt(textGop.getText()); + CamServer.bps = textBps.getText(); + + saveValuesComboBoxes(); + + } + + protected void saveValuesComboBoxes() { + // Save video resolution values + CamServer.videoWidth = Integer.parseInt(textVideoResolution + .getSelectedItem().toString().split("x")[0]); + CamServer.videoHeight = Integer.parseInt(textVideoResolution + .getSelectedItem().toString().split("x")[1].split(" ")[0]); + + // Save capture resolution values + CamServer.captureWidth = Integer.parseInt(textCaptureResolution + .getSelectedItem().toString().split("x")[0]); + CamServer.captureHeight = Integer.parseInt(textCaptureResolution + .getSelectedItem().toString().split("x")[1].split(" ")[0]); + + // Save frame rate values + CamServer.intervalNum = Integer.parseInt(textInterval + .getSelectedItem().toString().split("/")[0]); + CamServer.intervalDen = Integer.parseInt(textInterval + .getSelectedItem().toString().split("/")[1]); + } +} diff --git a/src/communication/v4l/StreamingConfigFallback.java b/src/communication/v4l/StreamingConfigFallback.java new file mode 100644 index 0000000..06bb8d1 --- /dev/null +++ b/src/communication/v4l/StreamingConfigFallback.java @@ -0,0 +1,89 @@ +package communication.v4l; + +import java.util.Hashtable; + +import javax.swing.JTextField; + +public class StreamingConfigFallback extends StreamingConfig { + + private JTextField textVideoResolution; + private JTextField textInterval; + private JTextField textCaptureResolution; + + public StreamingConfigFallback(String device) { + super(device); + } + + @Override + protected void initComboBoxes() { + this.textVideoResolution = new JTextField(); + textVideoResolution.setBounds(101, 39, 103, 19); + this.add(textVideoResolution); + + this.textInterval = new JTextField(); + textInterval.setBounds(101, 91, 103, 19); + this.add(textInterval); + + this.textCaptureResolution = new JTextField(); + textCaptureResolution.setBounds(101, 265, 103, 19); + this.add(textCaptureResolution); + } + + @Override + protected void setValuesComboBoxes() { + // Highlight the video resolution in use + String videoResolution = CamServer.videoWidth + "x" + + CamServer.videoHeight; + this.textVideoResolution.setText(videoResolution); + + // Highlight the frame interval in use + String frameInterval = CamServer.intervalNum + "/" + + CamServer.intervalDen; + this.textInterval.setText(frameInterval); + + // Highlight the capture resolution in use + String captureResolution = CamServer.captureWidth + "x" + + CamServer.captureHeight; + this.textCaptureResolution.setText(captureResolution); + } + + @Override + protected void setDefaultSettingsComboBoxes() { + Hashtable defaultSettings = Settings + .getDefaultSettings(dev); + String videoResolution = defaultSettings.get("videoResolution"); + String captureResolution = defaultSettings.get("captureResolution"); + String interval = defaultSettings.get("interval"); + + // Highlight the default video resolution + this.textVideoResolution.setText(videoResolution); + + // Highlight the default video resolution + this.textCaptureResolution.setText(captureResolution); + + // Highlight the frame interval in use + this.textInterval.setText(interval); + } + + @Override + protected void saveValuesComboBoxes() { + // Save video resolution values + CamServer.videoWidth = Integer.parseInt(textVideoResolution.getText() + .split("x")[0]); + CamServer.videoHeight = Integer.parseInt(textVideoResolution.getText() + .split("x")[1].split(" ")[0]); + + // Save capture resolution values + CamServer.captureWidth = Integer.parseInt(textCaptureResolution + .getText().split("x")[0]); + CamServer.captureHeight = Integer.parseInt(textCaptureResolution + .getText().split("x")[1].split(" ")[0]); + + // Save frame rate values + CamServer.intervalNum = Integer.parseInt(textInterval.getText().split( + "/")[0]); + CamServer.intervalDen = Integer.parseInt(textInterval.getText().split( + "/")[1]); + + } +} diff --git a/src/communication/v4l/SurveyForm.java b/src/communication/v4l/SurveyForm.java new file mode 100644 index 0000000..7db5e10 --- /dev/null +++ b/src/communication/v4l/SurveyForm.java @@ -0,0 +1,119 @@ +package communication.v4l; + +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.concurrent.locks.Lock; + +import javax.swing.JButton; +import javax.swing.JFrame; +import javax.swing.JPanel; +import javax.swing.JLabel; +import javax.swing.BoxLayout; +import javax.swing.JScrollPane; +import javax.swing.SwingConstants; + +import util.ConfigFile; +import util.MyLogger; +import java.awt.Component; +import javax.swing.border.EmptyBorder; + +/** + * This class represents a JFrame with a survey. It is intended to be used at the end of the program execution, so + * when the frame is closed it exits the program. Questions can be add by as SurveyFormQuestion objects. + * @author ehas + * + */ +public class SurveyForm extends JFrame { + + private final int DEFAULT_FRAME_WIDTH = 300; + private final int DEFAULT_FRAME_HEIGHT = 300; + + private SurveyFormQuestion[] questions = { + new SurveyFormQuestion("Funcionamiento","¿El equipo ha funcionado correctamente?",0), + new SurveyFormQuestion("Calidad","¿La calidad de imagen y sonido ha sido adecuada?",0), + new SurveyFormQuestion("Utilidad","¿Ha servido para obtener un diagnóstico?",0) + }; + + private final JPanel questionsPanel = new JPanel(); + + /** + * Creates and shows the form. + */ + public SurveyForm (){ + + setTitle("Formulario de satisfacción"); + + // I suppose that 12 is the default font size. The frame is scaled + // accordingly. + String sizeS = ConfigFile.getValue(ConfigFile.FONT_SIZE); + int width = DEFAULT_FRAME_WIDTH; + int height = DEFAULT_FRAME_HEIGHT; + if (sizeS != null) { + int size = Integer.parseInt(sizeS); + width = width * size / 12; + height = height * size / 12; + } + + setPreferredSize(new Dimension(width,height)); + setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); + + // Create the heading message + JLabel lblNewLabel = new JLabel("A continuación puede rellenar si lo desea este breve formulario de satisfacción del sistema " + + "de telemicroscopía."); + lblNewLabel.setBorder(new EmptyBorder(5, 5, 5, 5)); + lblNewLabel.setAlignmentX(Component.CENTER_ALIGNMENT); + lblNewLabel.setHorizontalAlignment(SwingConstants.CENTER); + getContentPane().add(lblNewLabel, BorderLayout.NORTH); + + // Create the questions panel + questionsPanel.setLayout(new BoxLayout(questionsPanel, BoxLayout.Y_AXIS)); + + for (SurveyFormQuestion ques : questions){ + questionsPanel.add(ques); + } + + JScrollPane questionsScroll = new JScrollPane(); + questionsScroll.setViewportView(questionsPanel); + questionsScroll.getVerticalScrollBar().setBlockIncrement(40); + questionsScroll.getVerticalScrollBar().setUnitIncrement(25); + getContentPane().add(questionsScroll, BorderLayout.CENTER); + + //getContentPane().add(questionsPanel, BorderLayout.CENTER); + + // Create the bottom buttons. + JPanel bottomPanel = new JPanel(); + JButton refuse = new JButton("No, gracias"); + refuse.addActionListener(new ActionListener(){ + + @Override + public void actionPerformed(ActionEvent arg0) { + System.exit(0); + } + + }); + + JButton accept = new JButton("Aceptar"); + accept.addActionListener(new ActionListener(){ + + @Override + public void actionPerformed(ActionEvent e) { + for (SurveyFormQuestion ques : questions){ + CamServer.logger.info(MyLogger.getLogRecord("SURVEY_FORM") + " " + + ques.getName() + "= " + ques.getAnswer()); + } + System.exit(0); + } + + }); + + bottomPanel.add(refuse, BorderLayout.LINE_END); + bottomPanel.add(accept, BorderLayout.LINE_END); + getContentPane().add(bottomPanel, BorderLayout.PAGE_END); + + pack(); + setLocationRelativeTo(null); + setVisible(true); + } +} diff --git a/src/communication/v4l/SurveyFormQuestion.java b/src/communication/v4l/SurveyFormQuestion.java new file mode 100644 index 0000000..846f25b --- /dev/null +++ b/src/communication/v4l/SurveyFormQuestion.java @@ -0,0 +1,121 @@ +package communication.v4l; + +import javax.swing.JPanel; +import java.awt.FlowLayout; +import javax.swing.JLabel; +import javax.swing.JRadioButton; +import java.awt.BorderLayout; + +import javax.swing.ButtonGroup; +import javax.swing.JTextArea; +import javax.swing.JTextField; +import javax.swing.JTable; +import javax.swing.JSplitPane; +import javax.swing.JToggleButton; + +import java.awt.GridBagLayout; +import java.awt.GridBagConstraints; +import java.awt.Insets; +import java.awt.Dimension; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import javax.swing.UIManager; +import javax.swing.border.CompoundBorder; +import javax.swing.border.EmptyBorder; +import javax.swing.border.LineBorder; +import java.awt.Color; + +public class SurveyFormQuestion extends JPanel { + + public static final int YES_NO = 0; + public static final int TEXT = 1; + + private String name; + private String question; + private int type; + private ButtonGroup bGroup; + private String answer = "null"; + + public SurveyFormQuestion (String name, String question, int type){ + setBorder(new CompoundBorder(new LineBorder(new Color(238, 238, 238), 3), new LineBorder(new Color(0, 0, 0), 2, true))); + setBackground(UIManager.getColor("Button.disabledToolBarBorderBackground")); + setPreferredSize(new Dimension(400, 100)); + this.name = name; + this.question = "" + question + ""; + //this.question = question; + this.type = type; + + createYesNoPanel(); + + } + + private void createYesNoPanel (){ + + GridBagLayout gridBagLayout = new GridBagLayout(); + gridBagLayout.columnWidths = new int[]{300,100}; + gridBagLayout.rowWeights = new double[]{0.9}; + setLayout(gridBagLayout); + + JLabel jQuestion = new JLabel(); + jQuestion.setText(question); + GridBagConstraints gbc_ques = new GridBagConstraints(); + gbc_ques.weighty = 1.0; + gbc_ques.weightx = 1.0; + gbc_ques.anchor = GridBagConstraints.WEST; + gbc_ques.gridx = 0; + gbc_ques.gridy = 0; + gbc_ques.fill = GridBagConstraints.HORIZONTAL; + gbc_ques.insets = new Insets(5,5,5,5); + add(jQuestion, gbc_ques); + + JPanel answer = new JPanel(); + answer.setBackground(UIManager.getColor("Button.disabledToolBarBorderBackground")); + + JToggleButton jTrue = new JToggleButton("SI"); + jTrue.setBackground(UIManager.getColor("Button.disabledToolBarBorderBackground")); + jTrue.addActionListener(new ActionListener(){ + @Override + public void actionPerformed(ActionEvent arg0) { + setAnswer("true"); + } + }); + + JToggleButton jFalse = new JToggleButton("NO"); + jFalse.setBackground(UIManager.getColor("Button.disabledToolBarBorderBackground")); + jFalse.addActionListener(new ActionListener(){ + @Override + public void actionPerformed(ActionEvent e) { + setAnswer("false"); + } + }); + + answer.add(jTrue, BorderLayout.LINE_START); + answer.add(jFalse, BorderLayout.LINE_END); + + GridBagConstraints gbc_ans = new GridBagConstraints(); + gbc_ans.anchor = GridBagConstraints.CENTER; + gbc_ans.gridx = 1; + gbc_ans.gridy = 0; + add(answer, gbc_ans); + + // Create the button group + bGroup = new ButtonGroup(); + bGroup.add(jTrue); + bGroup.add(jFalse); + + } + + + public String getAnswer(){ + return answer; + } + + public void setAnswer(String ans){ + answer = ans; + } + + public String getName(){ + return name; + } + +} diff --git a/src/communication/v4l/VLCThread.java b/src/communication/v4l/VLCThread.java new file mode 100755 index 0000000..f3c59f8 --- /dev/null +++ b/src/communication/v4l/VLCThread.java @@ -0,0 +1,486 @@ +package communication.v4l; + +import edition.ImageManager; +import edition.RoiManager; +import gestion.GestorSave; +import ij.ImagePlus; +import ij.ImageStack; +import ij.WindowManager; + +import java.awt.BorderLayout; +import java.awt.Container; +import java.awt.GraphicsEnvironment; +import java.awt.Toolkit; +import java.awt.image.BufferedImage; +import java.io.DataOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Field; + +import javax.imageio.ImageIO; +import javax.swing.BorderFactory; +import javax.swing.JFrame; +import javax.swing.JProgressBar; +import javax.swing.border.Border; + +import util.MyLogger; + +/** + * + * @author ehas + * + */ +public class VLCThread implements Streamer { + + private String encoderName = "cvlc"; + private String capturerName = "ffmpeg"; + private String playerName = "ffplay"; + + private Process vlc = null; + private Process avplay; + private DefaultStreamGobbler vlcstdout, vlcstderr, avplaystdout, avplaystderr; + + private boolean ffplayActive = false; + private boolean captureTaken = false; + private GestorSave saver = new GestorSave(); + + private String capturePath = "capture.jpg"; + private String captureQuality = "2"; + + /** + * It starts the video streaming by running vlc process. When the streaming + * is initialized, it waits for a image capture notification. + */ + @Override + public void run() { + + // Run avconv and avserver + try { + + // Start the libav processes + + synchronized (this) { + startVLC(); + this.notify(); + } + + Thread waitViewer = new Thread(new Runnable() { + @Override + public void run() { + while (!Thread.interrupted()) { + // There are two events that can launch the viewer: + // 1. If an image capture has been taken and the viewer + // was previously active + // 2. The "launch viewer button" has been pushed + if (captureTaken == true) { + captureTaken = false; + } else { + synchronized (CamServer.LockViewer) { + try { + // Wait for viewer button activation + CamServer.LockViewer.wait(); + } catch (InterruptedException e) { + break; + } + } + } + + try { + startAvplay(); + avplay.waitFor(); + ffplayActive = false; + } catch (InterruptedException | IOException e) { + avplay.destroy(); + ffplayActive = false; + break; + } + + } + } + + }, "waitViewer"); + + waitViewer.start(); + + while (!Thread.interrupted()) { + + synchronized (CamServer.LockCapture) { + + // It waits until a CamServer.LockCapture notification. It + // can be a local or an external capture. + try { + CamServer.LockCapture.wait(); + } catch (InterruptedException e) { + if (waitViewer.isAlive()) { + waitViewer.interrupt(); + try { + waitViewer.join(); + } catch (InterruptedException i) { + i.printStackTrace(); + } + } + break; + } + + if (CamServer.cameraCheckBox != null){ + CamServer.cameraCheckBox.setEnabled(false); + } + if (CamServer.indicator != null){ + CamServer.indicator.setEnabled(false); + } + + checkLocalCapture(); + checkExternalCapture(); + + if (CamServer.cameraCheckBox != null){ + CamServer.cameraCheckBox.setEnabled(true); + } + if (CamServer.indicator != null){ + CamServer.indicator.setEnabled(true); + } + + } + } + + stopVLCProcesses(); + + } catch (IOException e) { + e.printStackTrace(); + } + } + + /** + * This method must be called before the thread exit. It destroys all + * remaining processes. + */ + private void stopVLCProcesses() { + System.out.println("Parando hilo vlc"); + + // Stop the vlc processes + try { + stopVLC(); + } catch (InterruptedException e1) { + e1.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } catch (Exception e) { + e.printStackTrace(); + } + + if (avplay != null) { + try { + stopAvplay(); + } catch (IOException | InterruptedException e) { + e.printStackTrace(); + } + } + } + + /** + * Starts an avconv process with the parameters saved in the CamServer + * static instance. Two additional processes are created to read standard + * and error output. The methods waits until the avconv process actually + * starts the video streaming + * + * @throws IOException + */ + private void startVLC() throws IOException { + + String command = encoderName; + command = command.concat(" -vv --color"); + command = command.concat(" v4l://").concat( + CamServer.getVideoDevice().getDevicefile()); + // command = command.concat(":width=" + CamServer.videoWidth); + // command = command.concat(":height=" + CamServer.videoHeight); + command = command.concat(":size=" + CamServer.videoWidth + "x" + + CamServer.videoHeight); + command = command.concat(":fps=" + CamServer.getFramerate()); + command = command.concat(" --sout"); + // Start of x264 parameters definition + command = command.concat(" #transcode{venc=x264{"); + // Obtain the bps and keep it in a variable + int bps = Integer.parseInt(CamServer.bps.split("k")[0]); + command = command.concat("vbv-maxrate=" + bps + ","); + command = command.concat("vbv-bufsize=" + (2 * bps) + ","); + command = command.concat("fps=" + CamServer.getFramerate() + ","); + command = command.concat("keyint=" + CamServer.gop + ","); + command = command.concat("profile=high,"); + command = command.concat("tune=zerolatency"); + command = command.concat("},"); + // End of x264 definition + command = command.concat("vcodec=h264,"); + command = command.concat("width=" + CamServer.videoWidth + ","); + command = command.concat("height=" + CamServer.videoHeight + ","); + command = command.concat("acodec=none}"); + command = command + .concat(":std{access=http{mime/x-flv},dst=:8090/stream.flv}"); + command = command.concat(" --sout-keep"); + command = command.concat(" --no-sout-audio"); + System.out.println(command); + + vlc = Runtime.getRuntime().exec(command); + + vlcstdout = new DefaultStreamGobbler(vlc.getInputStream(), "OUTPUT", false, + "vlc_out"); + vlcstdout.start(); + vlcstderr = new VLCStreamGobbler(vlc.getErrorStream(), "ERROR", true, + "vlc_err"); + vlcstderr.start(); + + // Wait until the video streaming is started + synchronized (vlcstderr) { + try { + vlcstderr.wait(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + + /** + * Stops the vlc process + * + * @throws Exception + */ + private void stopVLC() throws Exception { + + // It is necessary to kill the process in this way to be sure that + // SIGTERM (15) is sent + // as the kill signal. + String pid = "" + getUnixPID(vlc); + // TODO change to signal -15 if -9 is not necessary + Process killingProcess = Runtime.getRuntime().exec( + new String[] { "kill", "-9", pid }); + killingProcess.waitFor(); + vlc.waitFor(); + vlcstdout.interrupt(); + vlcstderr.interrupt(); + } + + /** + * Starts a avplay process. The streaming address must be the same that the + * one specified in the avserver configuration file - usually default + * "http://localhost:8090/test.swf" + * + * @throws IOException + */ + public void startAvplay() throws IOException { + avplay = Runtime.getRuntime().exec( + new String[] { playerName, "-probesize", "3000", + "http://localhost:8090/stream.flv" }); + avplaystdout = new DefaultStreamGobbler(avplay.getInputStream(), "OUTPUT", + false, "avplay_out"); + avplaystdout.start(); + avplaystderr = new DefaultStreamGobbler(avplay.getErrorStream(), "ERROR", + false, "avplay_err"); + avplaystderr.start(); + + ffplayActive = true; + } + + /** + * Stops the avplay process + * + * @throws IOException + * @throws InterruptedException + */ + public void stopAvplay() throws IOException, InterruptedException { + avplay.destroy(); + avplay.waitFor(); + avplaystdout.interrupt(); + avplaystderr.interrupt(); + + ffplayActive = false; + } + + /** + * This method checks if the capture that has been notified is a local + * capture or not by checking the method CamServer.isLocalCapture(). If so, + * it shows a "save file" menu and starts a work session. + * + * @throws IOException + */ + private void checkLocalCapture() throws IOException { + + synchronized (CamServer.LockLocal) { + if (CamServer.isLocalCapture()) { + + CamServer.setLocalCapture(false); + + BufferedImage currentImage = getCurrentImage(); + + CamServer.logger.info(MyLogger.getLogRecord("LOCAL_CAPTURE")); + + ImagePlus imp2 = WindowManager.getCurrentImage(); + ImagePlus imagen = new ImagePlus("title", Toolkit + .getDefaultToolkit().createImage( + currentImage.getSource())); + if (imp2 != null) { + + ImageStack stack = imp2.getStack(); + if (stack.getSize() == 1) { + String label = stack.getSliceLabel(1); + if (label != null && label.indexOf("\n") != -1) + stack.setSliceLabel(null, 1); + Object obj = imagen.getProperty("Label"); + if (obj != null && (obj instanceof String)) + stack.setSliceLabel((String) obj, 1); + } + + stack.addSlice(null, imagen.getChannelProcessor()); + imp2.setStack(null, stack); + imp2.setSlice(stack.getSize()); + imp2.unlock(); + + ImageManager.getInstance().refresh(); + + } else { + saver.saveAsTifInZip(imagen, true); + } + } + } + } + + /** + * This method checks if the capture that has been notified is an external + * capture or not by checking the method CamServer.getExternalCapture(). If + * so, the current image is sent to the client. + */ + private void checkExternalCapture() { + DataOutputStream outStream; + synchronized (CamServer.LockExternal) { + if (CamServer.getExternalCapture() != null) { + + outStream = CamServer.getExternalCapture(); + // Clear the DataOutputStream + CamServer.clearExternalCapture(); + + BufferedImage currentImage; + try { + currentImage = getCurrentImage(); + CamServer.logger.info(MyLogger + .getLogRecord("EXTERNAL_CAPTURE")); + ClientConnection.sendCurrentImage(outStream, currentImage); + } catch (IOException e) { + System.out.println("Fallo al capturar o enviar la imagen"); + e.printStackTrace(); + } + } + CamServer.LockExternal.notify(); + } + } + + /** + * This method return the current image with the parameters saved in the + * CamServer static instance (device, width, height). + * + * @return - the current image + * @throws IOException + */ + private BufferedImage getCurrentImage() throws IOException { + + try { + stopVLC(); + + JFrame progressFrame = new JFrame("Capturando..."); + progressFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + Container content = progressFrame.getContentPane(); + JProgressBar progress = new JProgressBar(); + progress.setIndeterminate(true); + Border border = BorderFactory.createTitledBorder("Capturando..."); + progress.setBorder(border); + content.add(progress, BorderLayout.NORTH); + GraphicsEnvironment graphics = GraphicsEnvironment + .getLocalGraphicsEnvironment(); + progressFrame.setLocation(graphics.getCenterPoint()); + progressFrame.setSize(300, 70); + progressFrame.setVisible(true); + + Process capture = Runtime.getRuntime().exec( + new String[] { + capturerName, + "-y", + "-s", + CamServer.captureWidth + "x" + + CamServer.captureHeight, "-f", + "video4linux2", "-i", + CamServer.getVideoDevice().getDevicefile(), + "-vframes", "1", "-qmin", captureQuality, "-qmax", + captureQuality, capturePath }); + capture.waitFor(); + + BufferedImage currentImage = null; + currentImage = ImageIO.read(new File(capturePath)); + + startVLC(); + + if (ffplayActive) { + captureTaken = true; + stopAvplay(); + } + progressFrame.setVisible(false); + progressFrame.dispose(); + + return currentImage; + + } catch (IOException | InterruptedException e) { + e.printStackTrace(); + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + private int getUnixPID(Process process) throws Exception { + + if (process.getClass().getName().equals("java.lang.UNIXProcess")) { + Class cl = process.getClass(); + Field field = cl.getDeclaredField("pid"); + field.setAccessible(true); + Object pidObject = field.get(process); + return (Integer) pidObject; + } else { + throw new IllegalArgumentException("Needs to be a UNIXProcess"); + } + } + + @Override + public String getHeadHTMLText() { + String headHTML = "" + + ""; + String headHTML2 = ""; + return headHTML + headHTML2; + } + + @Override + public String getBodyHTMLText() { + String bodyHTML = "
"; + return bodyHTML; + } + + public class VLCStreamGobbler extends DefaultStreamGobbler { + + public VLCStreamGobbler (InputStream is, String type, boolean print, String name){ + super(is,type,print,name); + } + + @Override + protected void analyzeLine (String line){ + if (line.contains("avformat mux debug: writing header")) { + synchronized (this) { + notify(); + } + } + } + } + +} \ No newline at end of file diff --git a/src/edition/ImageManager.java b/src/edition/ImageManager.java new file mode 100755 index 0000000..6657c6c --- /dev/null +++ b/src/edition/ImageManager.java @@ -0,0 +1,586 @@ +package edition; + +import ij.IJ; +import ij.ImagePlus; +import ij.ImageStack; +import ij.WindowManager; +import ij.io.OpenDialog; +import ij.macro.MacroRunner; +import ij.plugin.PlugIn; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Component; +import java.awt.Container; +import java.awt.Cursor; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Image; +import java.awt.Point; +import java.awt.Toolkit; +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.Transferable; +import java.awt.datatransfer.UnsupportedFlavorException; +import java.awt.dnd.DnDConstants; +import java.awt.dnd.DragGestureEvent; +import java.awt.dnd.DragGestureListener; +import java.awt.dnd.DragGestureRecognizer; +import java.awt.dnd.DragSource; +import java.awt.dnd.DragSourceDragEvent; +import java.awt.dnd.DragSourceDropEvent; +import java.awt.dnd.DragSourceEvent; +import java.awt.dnd.DragSourceListener; +import java.awt.dnd.DropTarget; +import java.awt.dnd.DropTargetDragEvent; +import java.awt.dnd.DropTargetDropEvent; +import java.awt.dnd.DropTargetEvent; +import java.awt.dnd.DropTargetListener; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.ComponentEvent; +import java.awt.event.ComponentListener; +import java.awt.event.WindowEvent; +import java.awt.event.WindowListener; +import java.awt.image.BufferedImage; +import java.io.IOException; + +import javax.swing.AbstractButton; +import javax.swing.DefaultListCellRenderer; +import javax.swing.DefaultListModel; +import javax.swing.ImageIcon; +import javax.swing.JButton; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JList; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JRootPane; +import javax.swing.JScrollPane; +import javax.swing.border.LineBorder; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; + +import communication.v4l.FrameCamServer; + +/** + * This class creates a frame to group a list of pictures in a scroll view. They are the frames of the + * current working session, and the can be selected, deleted and drag up or down. + * + * @author ehas + * + */ +public class ImageManager implements PlugIn { + + private static ImageManager instance = new ImageManager(); + + GestorImagenesModel model; + GestorImagenesView view; + GestorImagenesController controller; + + public void run(String arg) { + model = new GestorImagenesModel(); + view = new GestorImagenesView("Gestor Imagenes", model); + controller = new GestorImagenesController(model, view); + } + + public static ImageManager getInstance() { + return instance; + } + + public void refresh() { + model.refresh(ImageManager.getInstance().view.list); + } + + public void close() { + if (view != null) { + view.dispose(); + } + controller = null; + view = null; + model = null; + } +} + +// Model contains the functionality (properties and methods) +class GestorImagenesModel extends DefaultListModel { + + StackEditorTM stackEditorTM = new StackEditorTM(); + Image[] imagesArray; + + public void exit() { + System.exit(0); + } + + public void loadImage(int indice) { + IJ.setSlice(indice + 1); + } + + public void addImage(int index) { + ImagePlus imp = readImage(); + if (imp != null) { + + ImagePlus imp2 = IJ.getImage(); + ImageStack stack = imp2.getStack(); + if (stack.getSize() == 1) { + String label = stack.getSliceLabel(1); + if (label != null && label.indexOf("\n") != -1) { + stack.setSliceLabel(null, 1); + } + Object obj = imp.getProperty("Label"); + if (obj != null && (obj instanceof String)) { + stack.setSliceLabel((String) obj, 1); + } + } + + stack.addSlice(null, imp.getChannelProcessor()); + imp2.setStack(null, stack); + imp2.setSlice(index + 1); + imp2.unlock(); + } + } + + private ImagePlus readImage() { + OpenDialog od = new OpenDialog("Open", ""); + String directory = od.getDirectory(); + String name = od.getFileName(); + ImagePlus imp = null; + if (name != null) { + String path = directory + name; + imp = new ImagePlus(path); + } + return imp; + } + + public void deleteImage(int index) { + stackEditorTM.deleteSlice(index + 1); + } + + public void refresh(JList list) { + // Image thumbImage; + imagesArray = stackEditorTM.getImages(); + list.setListData(imagesArray); + } + + public void addImage(int index, BufferedImage dragged) { + ImagePlus imp = new ImagePlus("title", Toolkit.getDefaultToolkit() + .createImage(dragged.getSource())); + if (imp != null) { + + ImagePlus imp2 = IJ.getImage(); + ImageStack stack = imp2.getStack(); + if (stack.getSize() == 1) { + String label = stack.getSliceLabel(1); + if (label != null && label.indexOf("\n") != -1) + stack.setSliceLabel(null, 1); + Object obj = imp.getProperty("Label"); + if (obj != null && (obj instanceof String)) + stack.setSliceLabel((String) obj, 1); + } + + stack.addSlice(null, imp.getChannelProcessor(), index); + imp2.setStack(null, stack); + imp2.setSlice(index + 1); + imp2.unlock(); + } + } +} + +// View is where the GUI is built +class GestorImagenesView extends JFrame { + StackEditorTM stackEditorTM = new StackEditorTM(); + + JButton deleteImage = new JButton("Borrar"); + JPanel buttonsPanel = new JPanel(); + ReorderableJList list; + + GestorImagenesView(String title, GestorImagenesModel model) // the + // constructor + { + super(title); + list = new ReorderableJList(model); + + setSize(350, 450); + setLayout(new BorderLayout()); + + // List + list.setLayoutOrientation(JList.VERTICAL); + list.setBorder(new LineBorder(Color.black)); + list.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_INTERVAL_SELECTION); + + JPanel panel = new JPanel(new BorderLayout()); + panel.add(list, BorderLayout.NORTH); + + JScrollPane jScrollPane = new JScrollPane(); + jScrollPane.getViewport().setView(panel); + jScrollPane.setPreferredSize(new Dimension(250, 250)); + + this.getContentPane().add(jScrollPane, BorderLayout.WEST); + + // Buttons + buttonsPanel.add(deleteImage); + buttonsPanel.setPreferredSize(new Dimension(105, 250)); + + this.getContentPane().add(buttonsPanel, BorderLayout.EAST); + + this.setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); + setVisible(true); + + addWindowListener(new WindowListener() { + + @Override + public void windowOpened(WindowEvent e) { + + } + + @Override + public void windowIconified(WindowEvent e) { + + } + + @Override + public void windowDeiconified(WindowEvent e) { + + } + + @Override + public void windowDeactivated(WindowEvent e) { + } + + @Override + public void windowClosing(WindowEvent e) { + new MacroRunner("run(\"Close \")\n"); + } + + @Override + public void windowClosed(WindowEvent e) { + + } + + @Override + public void windowActivated(WindowEvent e) { + + } + }); + + addComponentListener(new ComponentListener() { + + @Override + public void componentShown(ComponentEvent e) { + RoiManager rm = RoiManager.getInstance(); + rm.setVisible(true); + FrameCamServer fcs = FrameCamServer.getInstance(); + fcs.setVisible(true); + WindowManager.getCurrentImage().getWindow().setVisible(true); + } + + @Override + public void componentResized(ComponentEvent e) { + + } + + @Override + public void componentMoved(ComponentEvent e) { + + } + + @Override + public void componentHidden(ComponentEvent e) { + + } + }); + + } + + // method to add ActionListener passed by Controller to buttons + public void buttonActionListeners(ActionListener al) { + deleteImage.setActionCommand("borrarImagen"); + deleteImage.addActionListener(al); + } + + // method to add ActionListener passed by Controller to buttons + public void listActionListeners(ListSelectionListener al) { + list.addListSelectionListener(al); + } + + class ImageRenderer extends DefaultListCellRenderer { + public Component getListCellRendererComponent(JList list, Object value, + int index, boolean isSelected, boolean cellHasFocus) { + // for default cell renderer behavior + Component c = super.getListCellRendererComponent(list, value, + index, isSelected, cellHasFocus); + // set icon for cell image + Image image = (Image) value; + ImageIcon imageIcon = new ImageIcon(image.getScaledInstance(200, + -1, Image.SCALE_DEFAULT)); + ((JLabel) c).setIcon(imageIcon); + ((JLabel) c).setText(""); + return c; + } + + } +} + +class ReorderableJList extends JList implements DragSourceListener, + DropTargetListener, DragGestureListener { + + static DataFlavor localObjectFlavor; + static { + try { + localObjectFlavor = new DataFlavor( + DataFlavor.javaJVMLocalObjectMimeType); + } catch (ClassNotFoundException cnfe) { + cnfe.printStackTrace(); + } + } + static DataFlavor[] supportedFlavors = { localObjectFlavor }; + DragSource dragSource; + DropTarget dropTarget; + Object dropTargetCell; + int draggedIndex = -1; + GestorImagenesModel model; + + public ReorderableJList(GestorImagenesModel model) { + super(); + this.model = model; + setCellRenderer(new ReorderableListCellRenderer()); + setModel(new DefaultListModel()); + dragSource = new DragSource(); + DragGestureRecognizer dgr = dragSource + .createDefaultDragGestureRecognizer(this, + DnDConstants.ACTION_MOVE, this); + dropTarget = new DropTarget(this, this); + } + + // DragGestureListener + public void dragGestureRecognized(DragGestureEvent dge) { + System.out.println("dragGestureRecognized"); + // find object at this x,y + Point clickPoint = dge.getDragOrigin(); + int index = locationToIndex(clickPoint); + if (index == -1) + return; + Object target = getModel().getElementAt(index); + Transferable trans = new RJLTransferable(target); + draggedIndex = index; + dragSource.startDrag(dge, Cursor.getDefaultCursor(), trans, this); + } + + // DragSourceListener events + public void dragDropEnd(DragSourceDropEvent dsde) { + System.out.println("dragDropEnd()"); + dropTargetCell = null; + draggedIndex = -1; + repaint(); + + } + + public void dragEnter(DragSourceDragEvent dsde) { + } + + public void dragExit(DragSourceEvent dse) { + } + + public void dragOver(DragSourceDragEvent dsde) { + } + + public void dropActionChanged(DragSourceDragEvent dsde) { + } + + // DropTargetListener events + public void dragEnter(DropTargetDragEvent dtde) { + System.out.println("dragEnter"); + if (dtde.getSource() != dropTarget) + dtde.rejectDrag(); + else { + dtde.acceptDrag(DnDConstants.ACTION_COPY_OR_MOVE); + System.out.println("accepted dragEnter"); + } + } + + public void dragExit(DropTargetEvent dte) { + } + + public void dragOver(DropTargetDragEvent dtde) { + // figure out which cell it's over, no drag to self + if (dtde.getSource() != dropTarget) + dtde.rejectDrag(); + Point dragPoint = dtde.getLocation(); + int index = locationToIndex(dragPoint); + if (index == -1) + dropTargetCell = null; + else + dropTargetCell = getModel().getElementAt(index); + repaint(); + } + + public void drop(DropTargetDropEvent dtde) { + System.out.println("drop()!"); + if (dtde.getSource() != dropTarget) { + System.out.println("rejecting for bad source (" + + dtde.getSource().getClass().getName() + ")"); + dtde.rejectDrop(); + return; + } + Point dropPoint = dtde.getLocation(); + int index = locationToIndex(dropPoint); + System.out.println("drop index is " + index); + boolean dropped = false; + try { + if ((index == -1) || (index == draggedIndex)) { + System.out.println("dropped onto self"); + dtde.rejectDrop(); + return; + } + dtde.acceptDrop(DnDConstants.ACTION_MOVE); + System.out.println("accepted"); + Object dragged = dtde.getTransferable().getTransferData( + localObjectFlavor); + // move items - note that indicies for insert will + // change if [removed] source was before target + System.out.println("drop " + draggedIndex + " to " + index); + boolean sourceBeforeTarget = (draggedIndex < index); + System.out.println("source is" + (sourceBeforeTarget ? "" : " not") + + " before target"); + System.out.println("insert at " + + (sourceBeforeTarget ? index - 1 : index)); + // model.anadirImagen(view.list.getModel().getSize()); + model.deleteImage(draggedIndex); + model.addImage((sourceBeforeTarget ? index - 1 : index), + (BufferedImage) dragged); + model.refresh(this); + + // Reordenamos la lista de rois + RoiManager rm = RoiManager.getInstance(); + if (rm != null) { + rm.runCommand("reorder", draggedIndex, index); + } + dropped = true; + } catch (Exception e) { + e.printStackTrace(); + } + dtde.dropComplete(dropped); + } + + public void dropActionChanged(DropTargetDragEvent dtde) { + } + + // main() method to test - listed below + + class RJLTransferable implements Transferable { + Object object; + + public RJLTransferable(Object o) { + object = o; + } + + public Object getTransferData(DataFlavor df) + throws UnsupportedFlavorException, IOException { + if (isDataFlavorSupported(df)) + return object; + else + throw new UnsupportedFlavorException(df); + } + + public boolean isDataFlavorSupported(DataFlavor df) { + return (df.equals(localObjectFlavor)); + } + + public DataFlavor[] getTransferDataFlavors() { + return supportedFlavors; + } + } + + class ReorderableListCellRenderer extends DefaultListCellRenderer { + boolean isTargetCell; + boolean isLastItem; + + public ReorderableListCellRenderer() { + super(); + } + + public Component getListCellRendererComponent(JList list, Object value, + int index, boolean isSelected, boolean hasFocus) { + isTargetCell = (value == dropTargetCell); + isLastItem = (index == list.getModel().getSize() - 1); + boolean showSelected = isSelected & (dropTargetCell == null); + + Component c = super.getListCellRendererComponent(list, value, + index, showSelected, hasFocus); + Image image = (Image) value; + ImageIcon imageIcon = new ImageIcon(image.getScaledInstance(200, + -1, Image.SCALE_DEFAULT)); + ((JLabel) c).setIcon(imageIcon); + ((JLabel) c).setText(""); + + return c; + + } + + public void paintComponent(Graphics g) { + super.paintComponent(g); + if (isTargetCell) { + g.setColor(Color.black); + g.drawLine(0, 0, getSize().width, 0); + } + } + } + +} + + +// the controller listens for actions and reacts +class GestorImagenesController implements ActionListener, ListSelectionListener { + GestorImagenesModel model; + GestorImagenesView view; + + public GestorImagenesController(GestorImagenesModel model, + GestorImagenesView view) { + // create the model and the GUI view + this.model = model; + this.view = view; + + view.list.setSelectedIndex(0); + model.refresh(view.list); + + // Add action listener from this class to view buttons + view.buttonActionListeners(this); + view.listActionListeners(this); + + } + + // Provide interactions for actions performed in the view + public void actionPerformed(ActionEvent ae) { + String action_com = ae.getActionCommand(); + + if (action_com != null && action_com.equals("anadirImagen")) { + model.addImage(view.list.getModel().getSize()); + } else if (action_com != null && action_com.equals("borrarImagen")) { + if (!(view.list.isSelectionEmpty())) { + // If it is the only image in the stack, close all windows + if (view.list.getModel().getSize() == 1) { + if (RoiManager.getInstance() != null) { + RoiManager.getInstance().close(); + } + ImageManager.getInstance().close(); + if (WindowManager.getCurrentImage() != null) { + WindowManager.getCurrentImage().close(); + WindowManager.setTempCurrentImage(null); + } + return; + } + model.deleteImage(view.list.getSelectedIndex()); + } else { + IJ.error("No hay imagen seleccionada"); + } + } + model.refresh(view.list); + } + + @Override + public void valueChanged(ListSelectionEvent e) { + if (!(view.list.isSelectionEmpty())) { + model.loadImage(view.list.getSelectedIndex()); + } + } + +} diff --git a/src/edition/RoiManager.java b/src/edition/RoiManager.java new file mode 100755 index 0000000..08cb7ad --- /dev/null +++ b/src/edition/RoiManager.java @@ -0,0 +1,2174 @@ +package edition; + +import ij.IJ; +import ij.ImageJ; +import ij.ImagePlus; +import ij.Macro; +import ij.Prefs; +import ij.Undo; +import ij.WindowManager; +import ij.gui.ColorChooser; +import ij.gui.GUI; +import ij.gui.GenericDialog; +import ij.gui.ImageCanvas; +import ij.gui.ImageRoi; +import ij.gui.MessageDialog; +import ij.gui.PointRoi; +import ij.gui.Roi; +import ij.gui.RoiProperties; +import ij.gui.ShapeRoi; +import ij.gui.TextRoi; +import ij.gui.Toolbar; +import ij.gui.YesNoCancelDialog; +import ij.io.OpenDialog; +import ij.io.Opener; +import ij.io.RoiDecoder; +import ij.io.RoiEncoder; +import ij.io.SaveDialog; +import ij.macro.Interpreter; +import ij.macro.MacroRunner; +import ij.measure.Calibration; +import ij.measure.Measurements; +import ij.measure.ResultsTable; +import ij.plugin.Colors; +import ij.plugin.filter.Analyzer; +import ij.plugin.filter.Filler; +import ij.plugin.frame.PlugInFrame; +import ij.plugin.frame.Recorder; +import ij.process.ImageProcessor; +import ij.process.ImageStatistics; +import ij.util.StringSorter; +import ij.util.Tools; + +import java.awt.BorderLayout; +import java.awt.Button; +import java.awt.Checkbox; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.Frame; +import java.awt.GridBagConstraints; +import java.awt.GridLayout; +import java.awt.Insets; +import java.awt.List; +import java.awt.MenuItem; +import java.awt.Panel; +import java.awt.Point; +import java.awt.Polygon; +import java.awt.PopupMenu; +import java.awt.Rectangle; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; +import java.awt.event.KeyEvent; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.awt.event.MouseWheelEvent; +import java.awt.event.MouseWheelListener; +import java.awt.event.WindowEvent; +import java.io.BufferedOutputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.text.DecimalFormat; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; +import java.util.zip.ZipOutputStream; + +import javax.swing.JOptionPane; + +/** + * This class implements PlugInFrame and creates a frame that contains a list of the regions of + * interest for the current images. Each region of interest is composed of a visual region and + * a comment. ROI can be selected, deleted and edited. + * + * @author ehas + * + */ +public class RoiManager extends PlugInFrame implements ActionListener, + ItemListener, MouseListener, MouseWheelListener { + public static final String LOC_KEY = "manager.loc"; + private static final int BUTTONS = 5; + private static final int DRAW = 0, FILL = 1, LABEL = 2; + private static final int SHOW_ALL = 0, SHOW_NONE = 1, LABELS = 2, + NO_LABELS = 3; + private static final int MENU = 0, COMMAND = 1; + private static int rows = 15; + private static int lastNonShiftClick = -1; + private static boolean allowMultipleSelections = true; + private static String moreButtonLabel = "More " + '\u00bb'; + private Panel panel; + private static Frame instance; + private static int colorIndex = 4; + private java.awt.List list; + private Hashtable rois = new Hashtable(); + private Roi roiCopy; + private boolean canceled; + private boolean macro; + private boolean ignoreInterrupts; + private PopupMenu pm; + private Button moreButton, colorButton; + private Checkbox showAllCheckbox = new Checkbox("Show All", false); + private Checkbox labelsCheckbox = new Checkbox("Edit Mode", false); + + private static boolean measureAll = true; + private static boolean onePerSlice = true; + private static boolean restoreCentered; + private int prevID; + private boolean noUpdateMode; + private int defaultLineWidth = 1; + private Color defaultColor; + private boolean firstTime = true; + + public RoiManager() { + super("Gestor de Regiones de Interes"); + if (instance != null) { + instance.toFront(); + return; + } + instance = this; + list = new List(rows, allowMultipleSelections); + + showWindow(); + } + + public RoiManager(boolean hideWindow) { + super("Gestor de Regiones de Interes"); + list = new List(rows, allowMultipleSelections); + } + + void showWindow() { + ImageJ ij = IJ.getInstance(); + addKeyListener(ij); + addMouseListener(this); + addMouseWheelListener(this); + WindowManager.addWindow(this); + + // + + // setLayout(new FlowLayout(FlowLayout.CENTER,5,5)); + setLayout(new BorderLayout()); + + list.add("012345678901234"); + list.addItemListener(this); + list.addKeyListener(ij); + list.addMouseListener(this); + list.addMouseWheelListener(this); + if (IJ.isLinux()) + list.setBackground(Color.white); + add("Center", list); + panel = new Panel(); + int nButtons = BUTTONS; + panel.setLayout(new GridLayout(nButtons, 1, 5, 0)); + addButton("Añadir Region [t]"); + addButton("Actualizar"); + addButton("Borrar"); + addButton("Editar..."); + // addButton("Measure"); + addButton("Deseleccionar"); + + // addButton("Open..."); + + // addButton("Properties..."); + // addButton("Save ROIs..."); + // addButton("Send Mail"); + // addButton("Flatten [F]"); + // addButton(moreButtonLabel); + // showAllCheckbox.addItemListener(this); + // panel.add(showAllCheckbox); + // labelsCheckbox.addItemListener(this); + // panel.add(labelsCheckbox); + add("East", panel); + // addPopupMenu(); + pack(); + Dimension size = getSize(); + if (size.width > 270) + setSize(size.width - 40, size.height); + + setSize(350, size.height); + list.remove(0); + Point loc = Prefs.getLocation(LOC_KEY); + if (loc != null) + setLocation(loc); + else + GUI.center(this); + setVisible(true); + if (IJ.isMacOSX() && IJ.isJava16()) { + list.setMultipleMode(false); + list.setMultipleMode(true); + // EventQueue.invokeLater(new Runnable() { + // public void run() { + // list.setMultipleMode(false); + // list.setMultipleMode(true); + // } + // }); + } + } + + void addButton(String label) { + Button b = new Button(label); + b.addActionListener(this); + b.addKeyListener(IJ.getInstance()); + b.addMouseListener(this); + if (label.equals(moreButtonLabel)) + moreButton = b; + panel.add(b); + } + + void addPopupMenu() { + pm = new PopupMenu(); + // addPopupItem("Select All"); + addPopupItem("Open..."); + addPopupItem("Save..."); + addPopupItem("Fill"); + addPopupItem("Draw"); + addPopupItem("AND"); + addPopupItem("OR (Combine)"); + addPopupItem("Split"); + addPopupItem("Add Particles"); + addPopupItem("Multi Measure"); + addPopupItem("Sort"); + addPopupItem("Specify..."); + addPopupItem("Remove Slice Info"); + addPopupItem("Help"); + addPopupItem("Options..."); + add(pm); + } + + void addPopupItem(String s) { + MenuItem mi = new MenuItem(s); + mi.addActionListener(this); + pm.add(mi); + } + + public void actionPerformed(ActionEvent e) { + int modifiers = e.getModifiers(); + boolean altKeyDown = (modifiers & ActionEvent.ALT_MASK) != 0 + || IJ.altKeyDown(); + boolean shiftKeyDown = (modifiers & ActionEvent.SHIFT_MASK) != 0 + || IJ.shiftKeyDown(); + IJ.setKeyUp(KeyEvent.VK_ALT); + IJ.setKeyUp(KeyEvent.VK_SHIFT); + String label = e.getActionCommand(); + if (label == null) + return; + String command = label; + if (command.equals("Añadir Region [t]")) + add(shiftKeyDown, altKeyDown); + else if (command.equals("Actualizar")) + update(true); + else if (command.equals("Borrar")) + delete(false); + else if (command.equals("Editar...")) + rename(null); + else if (command.equals("Properties...")) + setProperties(null, 0, null); + else if (command.equals("Flatten [F]")) + flatten(); + else if (command.equals("Measure")) + measure(MENU); + else if (command.equals("Open...")) + open(null); + // else if (command.equals("Save ROIs...")) + // save(); + else if (command.equals("Fill")) + drawOrFill(FILL); + else if (command.equals("Draw")) + drawOrFill(DRAW); + else if (command.equals("Deseleccionar")) + select(-1); + else if (command.equals(moreButtonLabel)) { + Point ploc = panel.getLocation(); + Point bloc = moreButton.getLocation(); + pm.show(this, ploc.x, bloc.y); + } else if (command.equals("OR (Combine)")) + combine(); + else if (command.equals("Split")) + split(); + else if (command.equals("AND")) + and(); + else if (command.equals("Add Particles")) + addParticles(); + else if (command.equals("Multi Measure")) + multiMeasure(); + else if (command.equals("Sort")) + sort(); + else if (command.equals("Specify...")) + specify(); + else if (command.equals("Remove Slice Info")) + removeSliceInfo(); + else if (command.equals("Help")) + help(); + else if (command.equals("Options...")) + options(); + else if (command.equals("\"Show All\" Color...")) + setShowAllColor(); + // else if (command.equals("Send Mail")) + // sendMail(); + } + + /* + * public void sendMail(){ //getMeasureROIs(); saveAllROIs(); + * //IJ.error("Hello world"); } + */ + + boolean saveAllROIs(String path) { + if (list.getItemCount() == 0) + return error("The selection list is empty."); + + int[] indexes = getAllIndexes(); + // if (indexes.length>1){ + try { + ZipOutputStream zos = new ZipOutputStream( + new FileOutputStream(path)); + DataOutputStream out = new DataOutputStream( + new BufferedOutputStream(zos)); + RoiEncoder re = new RoiEncoder(out); + for (int i = 0; i < indexes.length; i++) { + String label = list.getItem(indexes[i]); + Roi roi = (Roi) rois.get(label); + if (!label.endsWith(".roi")) + label += ".roi"; + zos.putNextEntry(new ZipEntry(label)); + re.write(roi); + out.flush(); + } + out.close(); + } catch (IOException e) { + error("" + e); + return false; + } + if (record()) + Recorder.record("roiManager", "Save", path); + return true; + } + + // PARA BORRAR + public boolean getMeasureROIs() { + // Check Image exists + ImagePlus imp = getImage(); + if (imp == null) + return false; + // Check if any index select (we will select all of them) + /* + * int[] indexes = list.getSelectedIndexes(); if (indexes.length==0) + * indexes = getAllIndexes(); + */ + int[] indexes = getAllIndexes(); + if (indexes.length == 0) + return false; + + // boolean allSliceOne = true; + for (int i = 0; i < indexes.length; i++) { + String label = list.getItem(indexes[i]); + // Returns the slice number associated with the specified name, + // or -1 if the name does not include a slice number. + // if (getSliceNumber(label)>1) allSliceOne = false; + Roi roi = (Roi) rois.get(label); + + String typeShape = roi.getTypeAsString(); + if (typeShape.equals("Rectangle")) { + // x,y,width,height + } else if (typeShape.equals("Oval")) { + // x,y,width,height + } else if (typeShape.equals("Straight Line")) { + // x,y,width,height + } else if (typeShape.equals("Point") + && ((PointRoi) roi).getNCoordinates() == 1) { + // x,y + } else if (typeShape.equals("Point") + && ((PointRoi) roi).getNCoordinates() > 1) { + // 10 & Multipoint + } else if (typeShape.equals("Polygon")) { + // 2 & Multipoint + } else { + return false; + } + + } + + /* + * // Returns the measurement options defined in the Set Measurements + * dialog. int measurements = Analyzer.getMeasurements(); + * + * //Check if there are more than one slide if (imp.getStackSize()>1) + * Analyzer.setMeasurements(measurements|Measurements.SLICE); //Choose + * Current Slice int currentSlice = imp.getCurrentSlice(); + * + * for (int i=0; i1) IJ.run("Select None"); if (record()) + * Recorder.record("roiManager", "Measure"); + */ + return true; + } + + public void itemStateChanged(ItemEvent e) { + Object source = e.getSource(); + if (source == showAllCheckbox) { + if (firstTime) + labelsCheckbox.setState(true); + showAll(showAllCheckbox.getState() ? SHOW_ALL : SHOW_NONE); + firstTime = false; + return; + } + if (source == labelsCheckbox) { + if (firstTime) + showAllCheckbox.setState(true); + showAll(labelsCheckbox.getState() ? LABELS : NO_LABELS); + firstTime = false; + return; + } + if (e.getStateChange() == ItemEvent.SELECTED && !ignoreInterrupts) { + int index = 0; + // IJ.log("item="+e.getItem()+" shift="+IJ.shiftKeyDown()+" ctrl="+IJ. + // controlKeyDown()); + try { + index = Integer.parseInt(e.getItem().toString()); + } catch (NumberFormatException ex) { + } + if (index < 0) + index = 0; + if (!IJ.isMacintosh()) { // handle shift-click, ctrl-click (on Mac, + // OS takes care of this) + if (!IJ.shiftKeyDown()) + lastNonShiftClick = index; + if (!IJ.shiftKeyDown() && !IJ.controlKeyDown()) { // simple + // click, + // deselect + // everything + // else + int[] indexes = list.getSelectedIndexes(); + for (int i = 0; i < indexes.length; i++) { + if (indexes[i] != index) + list.deselect(indexes[i]); + } + } else if (IJ.shiftKeyDown() && lastNonShiftClick >= 0 + && lastNonShiftClick < list.getItemCount()) { + int firstIndex = Math.min(index, lastNonShiftClick); + int lastIndex = Math.max(index, lastNonShiftClick); + int[] indexes = list.getSelectedIndexes(); + for (int i = 0; i < indexes.length; i++) + if (indexes[i] < firstIndex || indexes[i] > lastIndex) + list.deselect(indexes[i]); // deselect everything + // else + for (int i = firstIndex; i <= lastIndex; i++) + list.select(i); // select range + } + } + if (WindowManager.getCurrentImage() != null) { + restore(getImage(), index, true); + if (record()) { + if (Recorder.scriptMode()) + Recorder.recordCall("rm.select(imp, " + index + ");"); + else + Recorder.record("roiManager", "Select", index); + } + } + } + } + + void add(boolean shiftKeyDown, boolean altKeyDown) { + if (shiftKeyDown) + addAndDraw(altKeyDown); + else if (altKeyDown) + addRoi(true); + else + addRoi(false); + } + + /** Adds the specified ROI. */ + public void addRoi(Roi roi) { + addRoi(roi, false, null, -1); + } + + boolean addRoi(boolean promptForName) { + return addRoi(null, promptForName, null, -1); + } + + boolean addRoi(Roi roi, boolean promptForName, Color color, int lineWidth) { + ImagePlus imp = roi == null ? getImage() : WindowManager + .getCurrentImage(); + if (roi == null) { + if (imp == null) + return false; + roi = imp.getRoi(); + if (roi == null) { + error("The active image does not have a selection."); + return false; + } + } + if (color == null && roi.getStrokeColor() != null) + color = roi.getStrokeColor(); + else if (color == null && defaultColor != null) + color = defaultColor; + if (lineWidth < 0) { + int sw = (int) roi.getStrokeWidth(); + lineWidth = sw > 1 ? sw : defaultLineWidth; + } + if (lineWidth > 100) + lineWidth = 1; + int n = list.getItemCount(); + if (n > 0 && !IJ.isMacro() && imp != null) { + // check for duplicate + String label = list.getItem(n - 1); + Roi roi2 = (Roi) rois.get(label); + if (roi2 != null) { + int slice2 = getSliceNumber(label); + if (roi.equals(roi2) + && (slice2 == -1 || slice2 == imp.getCurrentSlice()) + && imp.getID() == prevID && !Interpreter.isBatchMode()) + return false; + } + } + prevID = imp != null ? imp.getID() : 0; + String name = roi.getName(); + if (isStandardName(name)) + name = null; + String label = name != null ? name : getLabel(imp, roi, -1); + if (promptForName) + label = promptForName(label); + else + label = getUniqueName(label); + if (label == null) + return false; + list.add(label); + roi.setName(label); + roiCopy = (Roi) roi.clone(); + if (imp != null) { + Calibration cal = imp.getCalibration(); + if (cal.xOrigin != 0.0 || cal.yOrigin != 0.0) { + Rectangle r = roiCopy.getBounds(); + roiCopy.setLocation(r.x - (int) cal.xOrigin, r.y + - (int) cal.yOrigin); + } + } + roiCopy.setStrokeWidth(lineWidth); + if (color != null) + roiCopy.setStrokeColor(color); + rois.put(label, roiCopy); + updateShowAll(); + if (record()) + recordAdd(defaultColor, defaultLineWidth); + + list.select(getCount() - 1); + rename(null); + return true; + } + + void recordAdd(Color color, int lineWidth) { + if (Recorder.scriptMode()) + Recorder.recordCall("rm.addRoi(imp.getRoi());"); + else if (color != null && lineWidth == 1) + Recorder.recordString("roiManager(\"Add\", \"" + getHex(color) + + "\");\n"); + else if (lineWidth > 1) + Recorder.recordString("roiManager(\"Add\", \"" + getHex(color) + + "\", " + lineWidth + ");\n"); + else + Recorder.record("roiManager", "Add"); + } + + String getHex(Color color) { + if (color == null) + color = ImageCanvas.getShowAllColor(); + String hex = Integer.toHexString(color.getRGB()); + if (hex.length() == 8) + hex = hex.substring(2); + return hex; + } + + /** + * Adds the specified ROI to the list. The third argument ('n') will be used + * to form the first part of the ROI lable if it is >= 0. + */ + public void add(ImagePlus imp, Roi roi, int n) { + if (roi == null) + return; + String label = getLabel(imp, roi, n); + if (label == null) + return; + list.add(label); + roi.setName(label); + roiCopy = (Roi) roi.clone(); + Calibration cal = imp.getCalibration(); + if (cal.xOrigin != 0.0 || cal.yOrigin != 0.0) { + Rectangle r = roiCopy.getBounds(); + roiCopy.setLocation(r.x - (int) cal.xOrigin, r.y + - (int) cal.yOrigin); + } + rois.put(label, roiCopy); + } + + boolean isStandardName(String name) { + if (name == null) + return false; + boolean isStandard = false; + int len = name.length(); + if (len >= 14 && name.charAt(4) == '-' && name.charAt(9) == '-') + isStandard = true; + else if (len >= 17 && name.charAt(5) == '-' && name.charAt(11) == '-') + isStandard = true; + else if (len >= 9 && name.charAt(4) == '-') + isStandard = true; + else if (len >= 11 && name.charAt(5) == '-') + isStandard = true; + return isStandard; + } + + String getLabel(ImagePlus imp, Roi roi, int n) { + Rectangle r = roi.getBounds(); + int xc = r.x + r.width / 2; + int yc = r.y + r.height / 2; + if (n >= 0) { + xc = yc; + yc = n; + } + if (xc < 0) + xc = 0; + if (yc < 0) + yc = 0; + int digits = 4; + String xs = "" + xc; + if (xs.length() > digits) + digits = xs.length(); + String ys = "" + yc; + if (ys.length() > digits) + digits = ys.length(); + if (digits == 4 && imp != null && imp.getStackSize() >= 10000) + digits = 5; + xs = "000000" + xc; + ys = "000000" + yc; + String label = ys.substring(ys.length() - digits) + "-" + + xs.substring(xs.length() - digits); + if (imp != null && imp.getStackSize() > 1) { + String zs = "000000" + imp.getCurrentSlice(); + label = zs.substring(zs.length() - digits) + "-" + label; + } + return label; + } + + void addAndDraw(boolean altKeyDown) { + if (altKeyDown) { + if (!addRoi(true)) + return; + } else if (!addRoi(false)) + return; + ImagePlus imp = WindowManager.getCurrentImage(); + if (imp != null) { + Undo.setup(Undo.COMPOUND_FILTER, imp); + IJ.run(imp, "Draw", "slice"); + Undo.setup(Undo.COMPOUND_FILTER_DONE, imp); + } + if (record()) + Recorder.record("roiManager", "Add & Draw"); + } + + boolean delete(boolean replacing) { + int count = list.getItemCount(); + if (count == 0) + return error("The list is empty."); + int index[] = list.getSelectedIndexes(); + if (index.length == 0 || (replacing && count > 1)) { + String msg = "Delete all items on the list?"; + if (replacing) + msg = "Replace items on the list?"; + canceled = false; + if (!IJ.isMacro() && !macro) { + YesNoCancelDialog d = new YesNoCancelDialog(this, + "ROI Manager", msg); + if (d.cancelPressed()) { + canceled = true; + return false; + } + if (!d.yesPressed()) + return false; + } + index = getAllIndexes(); + } + for (int i = count - 1; i >= 0; i--) { + boolean delete = false; + for (int j = 0; j < index.length; j++) { + if (index[j] == i) + delete = true; + } + if (delete) { + rois.remove(list.getItem(i)); + list.remove(i); + } + } + ImagePlus imp = WindowManager.getCurrentImage(); + if (count > 1 && index.length == 1 && imp != null) + imp.killRoi(); + updateShowAll(); + if (record()) + Recorder.record("roiManager", "Delete"); + return true; + } + + boolean update(boolean clone) { + ImagePlus imp = getImage(); + if (imp == null) + return false; + ImageCanvas ic = imp.getCanvas(); + boolean showingAll = ic != null && ic.getShowAllROIs(); + Roi roi = imp.getRoi(); + if (roi == null) { + error("The active image does not have a selection."); + return false; + } + int index = list.getSelectedIndex(); + if (index < 0 && !showingAll) + return error("Exactly one item in the list must be selected."); + if (index >= 0) { + String name = list.getItem(index); + rois.remove(name); + if (clone) + rois.put(name, (Roi) roi.clone()); + else + rois.put(name, roi); + } + if (record()) + Recorder.record("roiManager", "Update"); + if (showingAll) + imp.draw(); + return true; + } + + boolean rename(String name2) { + int index = list.getSelectedIndex(); + if (index < 0) + return error("Exactly one item in the list must be selected."); + String name = list.getItem(index); + if (name2 == null) + name2 = promptForName(name); + if (name2 == null) + return false; + Roi roi = (Roi) rois.get(name); + rois.remove(name); + // roi.setName(name2); + if (name.contains("##")) { + name2 = name.split("##")[0] + name2; + } else { + String nameSplit = name.split("##")[0]; + if (nameSplit.split("-").length == 2) + name = "0001-" + name; + name2 = name + "##" + name2; + } + + rois.put(name2, roi); + list.replaceItem(name2, index); + // list.select(index); + if (record()) + Recorder.record("roiManager", "Rename", name2); + return true; + } + + boolean reorder(Integer sliceSourcePos, Integer sliceDestinationPos) { + /* + * if (sliceSourcePos= sliceNumber + && sliceSourcePos < sliceNumber) { + list.add( + formatter.format(sliceNumber - 1) + + item.substring(item.indexOf("-")), index); + } else if (!isForward && sliceDestinationPos < sliceNumber + && sliceSourcePos >= sliceNumber) { + list.add( + formatter.format(sliceNumber + 1) + + item.substring(item.indexOf("-")), index); + } else { + list.add(item); + } + + index++; + } + + return true; + } + + String promptForName(String name) { + GenericDialog gd = new GenericDialog("Gestor de Regiones"); + + gd.addStringField("Comentario:", + (name.contains("##") ? name.split("##")[1] : ""), 20); + gd.showDialog(); + if (gd.wasCanceled()) + return null; + String name2 = gd.getNextString(); + name2 = getUniqueName(name2); + return name2; + } + + boolean restore(ImagePlus imp, int index, boolean setSlice) { + String label = list.getItem(index); + Roi roi = (Roi) rois.get(label); + if (imp == null || roi == null) + return false; + if (setSlice) { + int n = getSliceNumber(label); + if (n >= 1 && n <= imp.getStackSize()) { + if (imp.isHyperStack() || imp.isComposite()) + imp.setPosition(n); + else + imp.setSlice(n); + } + } + Roi roi2 = (Roi) roi.clone(); + Calibration cal = imp.getCalibration(); + Rectangle r = roi2.getBounds(); + if (cal.xOrigin != 0.0 || cal.yOrigin != 0.0) + roi2.setLocation(r.x + (int) cal.xOrigin, r.y + (int) cal.yOrigin); + int width = imp.getWidth(), height = imp.getHeight(); + if (restoreCentered) { + ImageCanvas ic = imp.getCanvas(); + if (ic != null) { + Rectangle r1 = ic.getSrcRect(); + Rectangle r2 = roi2.getBounds(); + roi2.setLocation(r1.x + r1.width / 2 - r2.width / 2, r1.y + + r1.height / 2 - r2.height / 2); + } + } + if (r.x >= width || r.y >= height || (r.x + r.width) <= 0 + || (r.y + r.height) <= 0) + roi2.setLocation((width - r.width) / 2, (height - r.height) / 2); + if (noUpdateMode) { + imp.setRoi(roi2, false); + noUpdateMode = false; + } else + imp.setRoi(roi2, true); + return true; + } + + boolean restoreWithoutUpdate(int index) { + noUpdateMode = true; + return restore(getImage(), index, false); + } + + /** + * Returns the slice number associated with the specified name, or -1 if the + * name does not include a slice number. + */ + public int getSliceNumber(String label) { + int slice = -1; + if (label.length() >= 14 && label.charAt(4) == '-' + && label.charAt(9) == '-') + slice = (int) Tools.parseDouble(label.substring(0, 4), -1); + else if (label.length() >= 17 && label.charAt(5) == '-' + && label.charAt(11) == '-') + slice = (int) Tools.parseDouble(label.substring(0, 5), -1); + else if (label.length() >= 20 && label.charAt(6) == '-' + && label.charAt(13) == '-') + slice = (int) Tools.parseDouble(label.substring(0, 6), -1); + return slice; + } + + void open(String path) { + Macro.setOptions(null); + String name = null; + if (path == null || path.equals("")) { + OpenDialog od = new OpenDialog("Open Selection(s)...", ""); + String directory = od.getDirectory(); + name = od.getFileName(); + if (name == null) + return; + path = directory + name; + } + if (record()) + Recorder.record("roiManager", "Open", path); + if (path.endsWith(".zip")) { + openZip(path); + return; + } + Opener o = new Opener(); + if (name == null) + name = o.getName(path); + Roi roi = o.openRoi(path); + if (roi != null) { + if (name.endsWith(".roi")) + name = name.substring(0, name.length() - 4); + name = getUniqueName(name); + list.add(name); + rois.put(name, roi); + } + updateShowAll(); + } + + // Modified on 2005/11/15 by Ulrik Stervbo to only read .roi files and to + // not empty the current list + void openZip(String path) { + ZipInputStream in = null; + ByteArrayOutputStream out; + int nRois = 0; + try { + in = new ZipInputStream(new FileInputStream(path)); + byte[] buf = new byte[1024]; + int len; + ZipEntry entry = in.getNextEntry(); + while (entry != null) { + String name = entry.getName(); + if (name.endsWith(".roi")) { + out = new ByteArrayOutputStream(); + while ((len = in.read(buf)) > 0) + out.write(buf, 0, len); + out.close(); + byte[] bytes = out.toByteArray(); + RoiDecoder rd = new RoiDecoder(bytes, name); + Roi roi = rd.getRoi(); + if (roi != null) { + name = name.substring(0, name.length() - 4); + name = getUniqueName(name); + list.add(name); + rois.put(name, roi); + nRois++; + } + } + entry = in.getNextEntry(); + } + in.close(); + } catch (IOException e) { + error(e.toString()); + } + if (nRois == 0) + // error("This ZIP archive does not appear to contain \".roi\" files"); + updateShowAll(); + } + + String getUniqueName(String name) { + String name2 = name; + int n = 1; + Roi roi2 = (Roi) rois.get(name2); + while (roi2 != null) { + roi2 = (Roi) rois.get(name2); + if (roi2 != null) { + int lastDash = name2.lastIndexOf("-"); + if (lastDash != -1 && name2.length() - lastDash < 5) + name2 = name2.substring(0, lastDash); + name2 = name2 + "-" + n; + n++; + } + roi2 = (Roi) rois.get(name2); + } + return name2; + } + + boolean save() { + if (list.getItemCount() == 0) + return error("The selection list is empty."); + int[] indexes = list.getSelectedIndexes(); + if (indexes.length == 0) + indexes = getAllIndexes(); + if (indexes.length > 1) + return saveMultiple(indexes, null); + String name = list.getItem(indexes[0]); + Macro.setOptions(null); + SaveDialog sd = new SaveDialog("Save Selection...", name, ".roi"); + String name2 = sd.getFileName(); + if (name2 == null) + return false; + String dir = sd.getDirectory(); + Roi roi = (Roi) rois.get(name); + rois.remove(name); + if (!name2.endsWith(".roi")) + name2 = name2 + ".roi"; + String newName = name2.substring(0, name2.length() - 4); + rois.put(newName, roi); + roi.setName(newName); + list.replaceItem(newName, indexes[0]); + RoiEncoder re = new RoiEncoder(dir + name2); + try { + re.write(roi); + } catch (IOException e) { + IJ.error("ROI Manager", e.getMessage()); + } + return true; + } + + boolean saveMultiple(int[] indexes, String path) { + Macro.setOptions(null); + if (path == null) { + SaveDialog sd = new SaveDialog("Save ROIs...", "RoiSet", ".zip"); + String name = sd.getFileName(); + if (name == null) + return false; + if (!(name.endsWith(".zip") || name.endsWith(".ZIP"))) + name = name + ".zip"; + String dir = sd.getDirectory(); + path = dir + name; + } + try { + if (indexes.length > 0) { + // String nameFile=path.substring(path.lastIndexOf("\\")+1); + String nameFileTMP = "teleMic.zip"; + String fileName = path.substring(path.lastIndexOf("/") + 1, + path.lastIndexOf(".")) + ".tif"; + // String + // pathName=pathFile.substring(path.lastIndexOf("\\")+1,path.lastIndexOf("."))+".tif"; + + // get a temp file + File tempFile = File.createTempFile(nameFileTMP, null); + // delete it, otherwise you cannot rename your existing zip to + // it. + tempFile.delete(); + + File zipFile = new File(path); + boolean renameOk = zipFile.renameTo(tempFile); + if (!renameOk) { + throw new RuntimeException("could not rename the file " + + zipFile.getAbsolutePath() + " to " + + tempFile.getAbsolutePath()); + } + byte[] buf = new byte[1024]; + + ZipInputStream zin = new ZipInputStream(new FileInputStream( + tempFile)); + ZipOutputStream out = new ZipOutputStream(new FileOutputStream( + zipFile)); + + ZipEntry entry = zin.getNextEntry(); + while (entry != null) { + String name = entry.getName(); + if (name.equals(fileName)) { + + // Add ZIP entry to output stream. + out.putNextEntry(new ZipEntry(name)); + // Transfer bytes from the ZIP file to the output file + int len; + while ((len = zin.read(buf)) > 0) { + out.write(buf, 0, len); + } + + } + entry = zin.getNextEntry(); + } + // Close the streams + zin.close(); + + // Compress the files + /* + * for (int i = 0; i < files.length; i++) { InputStream in = new + * FileInputStream(files[i]); // Add ZIP entry to output stream. + * out.putNextEntry(new ZipEntry(files[i].getName())); // + * Transfer bytes from the file to the ZIP file int len; while + * ((len = in.read(buf)) > 0) { out.write(buf, 0, len); } // + * Complete the entry out.closeEntry(); in.close(); } + */ + + // Complete the ZIP file + // out.close(); + // tempFile.delete(); + + // ZipOutputStream zos = new ZipOutputStream(new + // FileOutputStream(path)); + // DataOutputStream out = new DataOutputStream(new + // BufferedOutputStream(zos)); + + DataOutputStream out2 = new DataOutputStream( + new BufferedOutputStream(out)); + RoiEncoder re = new RoiEncoder(out2); + for (int i = 0; i < indexes.length; i++) { + String label = list.getItem(indexes[i]); + Roi roi = (Roi) rois.get(label); + if (!label.endsWith(".roi")) + label += ".roi"; + out.putNextEntry(new ZipEntry(label)); + re.write(roi); + out2.flush(); + } + out2.close(); + + // AQP + // Copy the Image File inside + // zos.putNextEntry(new ZipEntry("taka")); + + tempFile.delete(); + } + } catch (IOException e) { + error("" + e); + return false; + } + if (record()) + Recorder.record("roiManager", "Save", path); + return true; + } + + boolean measure(int mode) { + ImagePlus imp = getImage(); + if (imp == null) + return false; + int[] indexes = list.getSelectedIndexes(); + if (indexes.length == 0) + indexes = getAllIndexes(); + if (indexes.length == 0) + return false; + boolean allSliceOne = true; + for (int i = 0; i < indexes.length; i++) { + String label = list.getItem(indexes[i]); + if (getSliceNumber(label) > 1) + allSliceOne = false; + Roi roi = (Roi) rois.get(label); + } + int measurements = Analyzer.getMeasurements(); + if (imp.getStackSize() > 1) + Analyzer.setMeasurements(measurements | Measurements.SLICE); + int currentSlice = imp.getCurrentSlice(); + for (int i = 0; i < indexes.length; i++) { + if (restore(getImage(), indexes[i], !allSliceOne)) + IJ.run("Measure"); + else + break; + } + imp.setSlice(currentSlice); + Analyzer.setMeasurements(measurements); + if (indexes.length > 1) + IJ.run("Select None"); + if (record()) + Recorder.record("roiManager", "Measure"); + return true; + } + + /* + * void showIndexes(int[] indexes) { for (int i=0; i 1) + measureAll = true; + onePerSlice = true; + } else { + GenericDialog gd = new GenericDialog("Multi Measure"); + if (nSlices > 1) + gd.addCheckbox("Measure All " + nSlices + " Slices", measureAll); + gd.addCheckbox("One Row Per Slice", onePerSlice); + int columns = getColumnCount(imp, measurements) * indexes.length; + String str = nSlices == 1 ? "this option" : "both options"; + gd.setInsets(10, 25, 0); + gd.addMessage("Enabling " + str + " will result\n" + + "in a table with " + columns + " columns."); + gd.showDialog(); + if (gd.wasCanceled()) + return false; + if (nSlices > 1) + measureAll = gd.getNextBoolean(); + onePerSlice = gd.getNextBoolean(); + } + if (!measureAll) + nSlices = 1; + int currentSlice = imp.getCurrentSlice(); + + if (!onePerSlice) { + int measurements2 = nSlices > 1 ? measurements | Measurements.SLICE + : measurements; + ResultsTable rt = new ResultsTable(); + Analyzer analyzer = new Analyzer(imp, measurements2, rt); + for (int slice = 1; slice <= nSlices; slice++) { + if (nSlices > 1) + imp.setSliceWithoutUpdate(slice); + for (int i = 0; i < indexes.length; i++) { + if (restoreWithoutUpdate(indexes[i])) + analyzer.measure(); + else + break; + } + } + rt.show("Results"); + if (nSlices > 1) + imp.setSlice(currentSlice); + return true; + } + + Analyzer aSys = new Analyzer(imp); // System Analyzer + ResultsTable rtSys = Analyzer.getResultsTable(); + ResultsTable rtMulti = new ResultsTable(); + Analyzer aMulti = new Analyzer(imp, measurements, rtMulti); // Private + // Analyzer + + for (int slice = 1; slice <= nSlices; slice++) { + int sliceUse = slice; + if (nSlices == 1) + sliceUse = currentSlice; + imp.setSliceWithoutUpdate(sliceUse); + rtMulti.incrementCounter(); + int roiIndex = 0; + for (int i = 0; i < indexes.length; i++) { + if (restoreWithoutUpdate(indexes[i])) { + roiIndex++; + aSys.measure(); + for (int j = 0; j <= rtSys.getLastColumn(); j++) { + float[] col = rtSys.getColumn(j); + String head = rtSys.getColumnHeading(j); + String suffix = "" + roiIndex; + Roi roi = imp.getRoi(); + if (roi != null) { + String name = roi.getName(); + if (name != null + && name.length() > 0 + && (name.length() < 9 || !Character + .isDigit(name.charAt(0)))) + suffix = "(" + name + ")"; + } + if (head != null && col != null + && !head.equals("Slice")) + rtMulti.addValue(head + suffix, + rtSys.getValue(j, rtSys.getCounter() - 1)); + } + } else + break; + } + // aMulti.displayResults(); + // aMulti.updateHeadings(); + } + rtMulti.show("Results"); + + imp.setSlice(currentSlice); + if (indexes.length > 1) + IJ.run("Select None"); + if (record()) + Recorder.record("roiManager", "Multi Measure"); + return true; + } + + int getColumnCount(ImagePlus imp, int measurements) { + ImageStatistics stats = imp.getStatistics(measurements); + ResultsTable rt = new ResultsTable(); + Analyzer analyzer = new Analyzer(imp, measurements, rt); + analyzer.saveResults(stats, null); + int count = 0; + for (int i = 0; i <= rt.getLastColumn(); i++) { + float[] col = rt.getColumn(i); + String head = rt.getColumnHeading(i); + if (head != null && col != null) + count++; + } + return count; + } + + boolean drawOrFill(int mode) { + int[] indexes = list.getSelectedIndexes(); + if (indexes.length == 0) + indexes = getAllIndexes(); + ImagePlus imp = WindowManager.getCurrentImage(); + imp.killRoi(); + ImageProcessor ip = imp.getProcessor(); + ip.setColor(Toolbar.getForegroundColor()); + ip.snapshot(); + Undo.setup(Undo.FILTER, imp); + Filler filler = mode == LABEL ? new Filler() : null; + int slice = imp.getCurrentSlice(); + for (int i = 0; i < indexes.length; i++) { + String name = list.getItem(indexes[i]); + Roi roi = (Roi) rois.get(name); + int type = roi.getType(); + if (roi == null) + continue; + if (mode == FILL + && (type == Roi.POLYLINE || type == Roi.FREELINE || type == Roi.ANGLE)) + mode = DRAW; + int slice2 = getSliceNumber(name); + if (slice2 >= 1 && slice2 <= imp.getStackSize()) { + imp.setSlice(slice2); + ip = imp.getProcessor(); + ip.setColor(Toolbar.getForegroundColor()); + if (slice2 != slice) + Undo.reset(); + } + switch (mode) { + case DRAW: + roi.drawPixels(ip); + break; + case FILL: + ip.fillPolygon(roi.getPolygon()); + break; + case LABEL: + roi.drawPixels(ip); + filler.drawLabel(imp, ip, i + 1, roi.getBounds()); + break; + } + } + ImageCanvas ic = imp.getCanvas(); + if (ic != null) + ic.setShowAllROIs(false); + imp.updateAndDraw(); + return true; + } + + void setProperties(Color color, int lineWidth, Color fillColor) { + int[] indexes = list.getSelectedIndexes(); + if (indexes.length == 0) + indexes = getAllIndexes(); + int n = indexes.length; + if (n == 0) + return; + Roi rpRoi = null; + String rpName = null; + Font font = null; + double opacity = -1; + if (color == null && lineWidth == 0 && fillColor == null) { + String label = list.getItem(indexes[0]); + rpRoi = (Roi) rois.get(label); + if (n == 1) { + fillColor = rpRoi.getFillColor(); + rpName = rpRoi.getName(); + } + if (rpRoi.getStrokeColor() == null) + rpRoi.setStrokeColor(ImageCanvas.getShowAllColor()); + rpRoi = (Roi) rpRoi.clone(); + if (n > 1) + rpRoi.setName("range: " + (indexes[0] + 1) + "-" + + (indexes[n - 1] + 1)); + rpRoi.setFillColor(fillColor); + RoiProperties rp = new RoiProperties("Properties", rpRoi); + if (!rp.showDialog()) + return; + lineWidth = (int) rpRoi.getStrokeWidth(); + defaultLineWidth = lineWidth; + color = rpRoi.getStrokeColor(); + fillColor = rpRoi.getFillColor(); + defaultColor = color; + if (rpRoi instanceof TextRoi) + font = ((TextRoi) rpRoi).getCurrentFont(); + if (rpRoi instanceof ImageRoi) + opacity = ((ImageRoi) rpRoi).getOpacity(); + } + ImagePlus imp = WindowManager.getCurrentImage(); + if (n == list.getItemCount() && !IJ.isMacro()) { + GenericDialog gd = new GenericDialog("ROI Manager"); + gd.addMessage("Apply changes to all " + n + " selections?"); + gd.showDialog(); + if (gd.wasCanceled()) + return; + } + for (int i = 0; i < n; i++) { + String label = list.getItem(indexes[i]); + Roi roi = (Roi) rois.get(label); + // IJ.log("set "+color+" "+lineWidth+" "+fillColor); + if (color != null) + roi.setStrokeColor(color); + if (lineWidth > 0) + roi.setStrokeWidth(lineWidth); + roi.setFillColor(fillColor); + if (roi != null && (roi instanceof TextRoi)) { + roi.setImage(imp); + if (font != null) + ((TextRoi) roi).setCurrentFont(font); + roi.setImage(null); + } + if (roi != null && (roi instanceof ImageRoi) && opacity != -1) + ((ImageRoi) roi).setOpacity(opacity); + } + if (rpRoi != null && rpName != null && !rpRoi.getName().equals(rpName)) + rename(rpRoi.getName()); + ImageCanvas ic = imp != null ? imp.getCanvas() : null; + Roi roi = imp != null ? imp.getRoi() : null; + boolean showingAll = ic != null && ic.getShowAllROIs(); + if (roi != null && (n == 1 || !showingAll)) { + if (lineWidth != 0) + roi.setStrokeWidth(lineWidth); + if (color != null) + roi.setStrokeColor(color); + if (fillColor != null) + roi.setFillColor(fillColor); + if (roi != null && (roi instanceof TextRoi)) + ((TextRoi) roi).setCurrentFont(font); + if (roi != null && (roi instanceof ImageRoi) && opacity != -1) + ((ImageRoi) roi).setOpacity(opacity); + } + if (lineWidth > 1 && !showingAll && roi == null) { + showAll(SHOW_ALL); + showingAll = true; + } + if (imp != null) + imp.draw(); + if (record()) { + if (fillColor != null) + Recorder.record("roiManager", "Set Fill Color", + Colors.colorToString(fillColor)); + else { + Recorder.record("roiManager", "Set Color", + Colors.colorToString(color != null ? color : Color.red)); + Recorder.record("roiManager", "Set Line Width", lineWidth); + } + } + } + + void flatten() { + ImagePlus imp = WindowManager.getCurrentImage(); + if (imp == null) { + IJ.noImage(); + return; + } + ImageCanvas ic = imp.getCanvas(); + if (!ic.getShowAllROIs() && ic.getDisplayList() == null + && imp.getRoi() == null) + error("Image does not have an overlay or ROI"); + else + IJ.doCommand("Flatten"); // run Image>Flatten in separate thread + } + + public boolean getDrawLabels() { + return labelsCheckbox.getState(); + } + + void combine() { + ImagePlus imp = getImage(); + if (imp == null) + return; + int[] indexes = list.getSelectedIndexes(); + if (indexes.length == 1) { + error("More than one item must be selected, or none"); + return; + } + if (indexes.length == 0) + indexes = getAllIndexes(); + int nPointRois = 0; + for (int i = 0; i < indexes.length; i++) { + Roi roi = (Roi) rois.get(list.getItem(indexes[i])); + if (roi.getType() == Roi.POINT) + nPointRois++; + else + break; + } + if (nPointRois == indexes.length) + combinePoints(imp, indexes); + else + combineRois(imp, indexes); + if (record()) + Recorder.record("roiManager", "Combine"); + } + + void combineRois(ImagePlus imp, int[] indexes) { + ShapeRoi s1 = null, s2 = null; + for (int i = 0; i < indexes.length; i++) { + Roi roi = (Roi) rois.get(list.getItem(indexes[i])); + if (roi.isLine() || roi.getType() == Roi.POINT) + continue; + Calibration cal = imp.getCalibration(); + if (cal.xOrigin != 0.0 || cal.yOrigin != 0.0) { + roi = (Roi) roi.clone(); + Rectangle r = roi.getBounds(); + roi.setLocation(r.x + (int) cal.xOrigin, r.y + + (int) cal.yOrigin); + } + if (s1 == null) { + if (roi instanceof ShapeRoi) + s1 = (ShapeRoi) roi; + else + s1 = new ShapeRoi(roi); + if (s1 == null) + return; + } else { + if (roi instanceof ShapeRoi) + s2 = (ShapeRoi) roi; + else + s2 = new ShapeRoi(roi); + if (s2 == null) + continue; + if (roi.isArea()) + s1.or(s2); + } + } + if (s1 != null) + imp.setRoi(s1); + } + + void combinePoints(ImagePlus imp, int[] indexes) { + int n = indexes.length; + Polygon[] p = new Polygon[n]; + int points = 0; + for (int i = 0; i < n; i++) { + Roi roi = (Roi) rois.get(list.getItem(indexes[i])); + p[i] = roi.getPolygon(); + points += p[i].npoints; + } + if (points == 0) + return; + int[] xpoints = new int[points]; + int[] ypoints = new int[points]; + int index = 0; + for (int i = 0; i < p.length; i++) { + for (int j = 0; j < p[i].npoints; j++) { + xpoints[index] = p[i].xpoints[j]; + ypoints[index] = p[i].ypoints[j]; + index++; + } + } + imp.setRoi(new PointRoi(xpoints, ypoints, xpoints.length)); + } + + void and() { + ImagePlus imp = getImage(); + if (imp == null) + return; + int[] indexes = list.getSelectedIndexes(); + if (indexes.length == 1) { + error("More than one item must be selected, or none"); + return; + } + if (indexes.length == 0) + indexes = getAllIndexes(); + ShapeRoi s1 = null, s2 = null; + for (int i = 0; i < indexes.length; i++) { + Roi roi = (Roi) rois.get(list.getItem(indexes[i])); + if (!roi.isArea()) + continue; + if (s1 == null) { + if (roi instanceof ShapeRoi) + s1 = (ShapeRoi) roi.clone(); + else + s1 = new ShapeRoi(roi); + if (s1 == null) + return; + } else { + if (roi instanceof ShapeRoi) + s2 = (ShapeRoi) roi.clone(); + else + s2 = new ShapeRoi(roi); + if (s2 == null) + continue; + s1.and(s2); + } + } + if (s1 != null) + imp.setRoi(s1); + if (record()) + Recorder.record("roiManager", "AND"); + } + + void addParticles() { + String err = IJ.runMacroFile("ij.jar:AddParticles", null); + if (err != null && err.length() > 0) + error(err); + } + + void sort() { + int n = rois.size(); + if (n == 0) + return; + String[] labels = new String[n]; + int index = 0; + for (Enumeration en = rois.keys(); en.hasMoreElements();) + labels[index++] = (String) en.nextElement(); + list.removeAll(); + StringSorter.sort(labels); + for (int i = 0; i < labels.length; i++) + list.add(labels[i]); + if (record()) + Recorder.record("roiManager", "Sort"); + } + + void specify() { + try { + IJ.run("Specify..."); + } catch (Exception e) { + return; + } + runCommand("add"); + } + + void removeSliceInfo() { + int[] indexes = list.getSelectedIndexes(); + if (indexes.length == 0) + indexes = getAllIndexes(); + for (int i = 0; i < indexes.length; i++) { + int index = indexes[i]; + String name = list.getItem(index); + int n = getSliceNumber(name); + if (n == -1) + continue; + String name2 = name.substring(5, name.length()); + name2 = getUniqueName(name2); + Roi roi = (Roi) rois.get(name); + rois.remove(name); + roi.setName(name2); + rois.put(name2, roi); + list.replaceItem(name2, index); + } + } + + void help() { + String macro = "run('URL...', 'url=" + IJ.URL + + "/docs/menus/analyze.html#manager');"; + new MacroRunner(macro); + } + + void options() { + Color c = ImageCanvas.getShowAllColor(); + GenericDialog gd = new GenericDialog("Options"); + gd.addPanel(makeButtonPanel(gd), GridBagConstraints.CENTER, new Insets( + 5, 0, 0, 0)); + gd.addCheckbox("Associate \"Show All\" ROIs with Slices", + Prefs.showAllSliceOnly); + gd.addCheckbox("Restore ROIs Centered", restoreCentered); + gd.showDialog(); + if (gd.wasCanceled()) { + if (c != ImageCanvas.getShowAllColor()) + ImageCanvas.setShowAllColor(c); + return; + } + Prefs.showAllSliceOnly = gd.getNextBoolean(); + restoreCentered = gd.getNextBoolean(); + ImagePlus imp = WindowManager.getCurrentImage(); + if (imp != null) + imp.draw(); + if (record()) { + Recorder.record("roiManager", "Associate", + Prefs.showAllSliceOnly ? "true" : "false"); + Recorder.record("roiManager", "Centered", restoreCentered ? "true" + : "false"); + } + } + + Panel makeButtonPanel(GenericDialog gd) { + Panel panel = new Panel(); + // buttons.setLayout(new FlowLayout(FlowLayout.CENTER, 5, 0)); + colorButton = new Button("\"Show All\" Color..."); + colorButton.addActionListener(this); + panel.add(colorButton); + return panel; + } + + void setShowAllColor() { + ColorChooser cc = new ColorChooser("\"Show All\" Color", + ImageCanvas.getShowAllColor(), false); + ImageCanvas.setShowAllColor(cc.getColor()); + } + + void split() { + ImagePlus imp = getImage(); + if (imp == null) + return; + Roi roi = imp.getRoi(); + if (roi == null || roi.getType() != Roi.COMPOSITE) { + error("Image with composite selection required"); + return; + } + boolean record = Recorder.record; + Recorder.record = false; + Roi[] rois = ((ShapeRoi) roi).getRois(); + for (int i = 0; i < rois.length; i++) { + imp.setRoi(rois[i]); + addRoi(false); + } + Recorder.record = record; + if (record()) + Recorder.record("roiManager", "Split"); + } + + void showAll(int mode) { + ImagePlus imp = WindowManager.getCurrentImage(); + if (imp == null) { + error("There are no images open."); + return; + } + ImageCanvas ic = imp.getCanvas(); + if (ic == null) + return; + boolean showAll = mode == SHOW_ALL; + if (mode == LABELS) { + showAll = true; + if (record()) + Recorder.record("roiManager", "Show All with labels"); + } else if (mode == NO_LABELS) { + showAll = true; + if (record()) + Recorder.record("roiManager", "Show All without labels"); + } + if (showAll) + imp.killRoi(); + ic.setShowAllROIs(showAll); + if (record()) + Recorder.record("roiManager", showAll ? "Show All" : "Show None"); + imp.draw(); + } + + void updateShowAll() { + ImagePlus imp = WindowManager.getCurrentImage(); + if (imp == null) + return; + ImageCanvas ic = imp.getCanvas(); + if (ic != null && ic.getShowAllROIs()) + imp.draw(); + } + + int[] getAllIndexes() { + int count = list.getItemCount(); + int[] indexes = new int[count]; + for (int i = 0; i < count; i++) + indexes[i] = i; + return indexes; + } + + ImagePlus getImage() { + ImagePlus imp = WindowManager.getCurrentImage(); + if (imp == null) { + error("There are no images open."); + return null; + } else + return imp; + } + + boolean error(String msg) { + new MessageDialog(this, "ROI Manager", msg); + Macro.abort(); + return false; + } + + public void processWindowEvent(WindowEvent e) { + super.processWindowEvent(e); + // if (e.getID() == WindowEvent.WINDOW_CLOSING) { + // instance = null; + // } + if (!IJ.isMacro()) + ignoreInterrupts = false; + } + + /** + * Returns a reference to the ROI Manager or null if it is not open. + */ + public static RoiManager getInstance() { + return (RoiManager) instance; + } + + public static void setInstance(RoiManager gr) { + RoiManager.instance = gr; + } + + /** + * Returns the ROI Hashtable. + * + * @see getCount + * @see getRoisAsArray + */ + public Hashtable getROIs() { + return rois; + } + + /** + * Returns the selection list. + * + * @see getCount + * @see getRoisAsArray + */ + public List getList() { + return list; + } + + /** Returns the ROI count. */ + public int getCount() { + return list.getItemCount(); + } + + /** Returns the ROIs as an array. */ + public Roi[] getRoisAsArray() { + int n = list.getItemCount(); + Roi[] array = new Roi[n]; + for (int i = 0; i < n; i++) { + String label = list.getItem(i); + array[i] = (Roi) rois.get(label); + } + return array; + } + + /** + * Returns the selected ROIs as an array, or all the ROIs if none are + * selected. + */ + public Roi[] getSelectedRoisAsArray() { + int[] indexes = list.getSelectedIndexes(); + if (indexes.length == 0) + indexes = getAllIndexes(); + int n = indexes.length; + Roi[] array = new Roi[n]; + for (int i = 0; i < n; i++) { + String label = list.getItem(indexes[i]); + array[i] = (Roi) rois.get(label); + } + return array; + } + + /** + * Returns the name of the ROI with the specified index. Can be called from + * a macro using + * + *
+	 * call("ij.plugin.frame.RoiManager.getName", index)
+	 * 
+ * + * Returns "null" if the Roi Manager is not open or index is out of range. + */ + public static String getName(String index) { + int i = (int) Tools.parseDouble(index, -1); + RoiManager instance = getInstance(); + if (instance != null && i >= 0 && i < instance.list.getItemCount()) + return instance.list.getItem(i); + else + return "null"; + } + + /** + * Executes the ROI Manager "Add", "Add & Draw", "Update", "Delete", + * "Measure", "Draw", "Show All", Show None", "Fill", "Deselect", "Select + * All", "Combine", "AND", "Split", "Sort" or "Multi Measure" command. + * Returns false if cmd is not one of these strings. + */ + public boolean runCommand(String cmd) { + cmd = cmd.toLowerCase(); + macro = true; + boolean ok = true; + if (cmd.equals("add")) + add(IJ.shiftKeyDown(), IJ.altKeyDown()); + else if (cmd.equals("add & draw")) + addAndDraw(false); + else if (cmd.equals("update")) + update(true); + else if (cmd.equals("update2")) + update(false); + else if (cmd.equals("delete")) + delete(false); + else if (cmd.equals("measure")) + measure(COMMAND); + else if (cmd.equals("draw")) + drawOrFill(DRAW); + else if (cmd.equals("fill")) + drawOrFill(FILL); + else if (cmd.equals("label")) + drawOrFill(LABEL); + else if (cmd.equals("and")) + and(); + else if (cmd.equals("or") || cmd.equals("combine")) + combine(); + else if (cmd.equals("split")) + split(); + else if (cmd.equals("sort")) + sort(); + else if (cmd.startsWith("multi")) + multiMeasure(); + else if (cmd.startsWith("show all")) { + if (WindowManager.getCurrentImage() != null) { + showAll(SHOW_ALL); + showAllCheckbox.setState(true); + } + } else if (cmd.startsWith("show none")) { + if (WindowManager.getCurrentImage() != null) { + showAll(SHOW_NONE); + showAllCheckbox.setState(false); + } + } else if (cmd.equals("show all with labels")) { + labelsCheckbox.setState(true); + showAll(LABELS); + if (Interpreter.isBatchMode()) + IJ.wait(250); + } else if (cmd.equals("show all without labels")) { + labelsCheckbox.setState(false); + showAll(NO_LABELS); + if (Interpreter.isBatchMode()) + IJ.wait(250); + } else if (cmd.equals("deselect") || cmd.indexOf("all") != -1) { + if (IJ.isMacOSX()) + ignoreInterrupts = true; + select(-1); + IJ.wait(50); + } else if (cmd.equals("reset")) { + if (IJ.isMacOSX() && IJ.isMacro()) + ignoreInterrupts = true; + list.removeAll(); + rois.clear(); + } else if (cmd.equals("debug")) { + // IJ.log("Debug: "+debugCount); + // for (int i=0; icmd is not "Open", "Save" or "Rename", or if an + * error occurs. + */ + public boolean runCommand(String cmd, String name) { + cmd = cmd.toLowerCase(); + macro = true; + if (cmd.equals("open")) { + open(name); + macro = false; + return true; + } else if (cmd.equals("save")) { + if (!name.endsWith(".zip") && !name.equals("")) + return error("Name must end with '.zip'"); + if (list.getItemCount() == 0) + return error("The selection list is empty."); + int[] indexes = getAllIndexes(); + boolean ok = false; + if (name.equals("")) + ok = saveMultiple(indexes, null); + else + ok = saveMultiple(indexes, name); + macro = false; + return ok; + } else if (cmd.equals("save_especial")) { + if (!name.endsWith(".zip") && !name.equals("")) + return error("Name must end with '.zip'"); + // if (list.getItemCount()==0) + // return error("The selection list is empty."); + int[] indexes = getAllIndexes(); + boolean ok = false; + if (name.equals("")) + return ok; + else + ok = saveMultiple(indexes, name); + macro = false; + return ok; + } else if (cmd.equals("rename")) { + rename(name); + macro = false; + return true; + } else if (cmd.equals("set color")) { + Color color = Colors.decode(name, Color.cyan); + setProperties(color, 0, null); + macro = false; + return true; + } else if (cmd.equals("set fill color")) { + Color fillColor = Colors.decode(name, Color.cyan); + setProperties(null, 0, fillColor); + macro = false; + return true; + } else if (cmd.equals("set line width")) { + int lineWidth = (int) Tools.parseDouble(name, 0); + if (lineWidth != 0) + setProperties(null, lineWidth, null); + macro = false; + return true; + } else if (cmd.equals("associate")) { + Prefs.showAllSliceOnly = name.equals("true") ? true : false; + macro = false; + return true; + } else if (cmd.equals("centered")) { + restoreCentered = name.equals("true") ? true : false; + macro = false; + return true; + } + return false; + } + + /** + * Adds the current selection to the ROI Manager, using the specified color + * (a 6 digit hex string) and line width. + */ + public boolean runCommand(String cmd, String hexColor, double lineWidth) { + if (hexColor == null && lineWidth == 1.0 && IJ.altKeyDown()) + addRoi(true); + else { + if (hexColor == null) + hexColor = getHex(null); + Color color = Colors.decode(hexColor, Color.cyan); + addRoi(null, false, color, (int) Math.round(lineWidth)); + } + return true; + } + + public boolean runCommand(String cmd, int sliceSourcePos, + int sliceDestinationPos) { + reorder(sliceSourcePos + 1, sliceDestinationPos + 1); + return true; + } + + /** Assigns the ROI at the specified index to the current image. */ + public void select(int index) { + select(null, index); + } + + /** Assigns the ROI at the specified index to 'imp'. */ + public void select(ImagePlus imp, int index) { + int n = list.getItemCount(); + if (index < 0) { + for (int i = 0; i < n; i++) + if (list.isSelected(i)) + list.deselect(i); + if (record()) + Recorder.record("roiManager", "Deselect"); + return; + } + if (index >= n) + return; + boolean mm = list.isMultipleMode(); + if (mm) + list.setMultipleMode(false); + int delay = 1; + long start = System.currentTimeMillis(); + while (true) { + list.select(index); + if (delay > 1) + IJ.wait(delay); + if (list.isIndexSelected(index)) + break; + for (int i = 0; i < n; i++) + if (list.isSelected(i)) + list.deselect(i); + IJ.wait(delay); + delay *= 2; + if (delay > 32) + delay = 32; + if ((System.currentTimeMillis() - start) > 1000L) + error("Failed to select ROI " + index); + } + if (imp == null) + imp = getImage(); + restore(imp, index, true); + if (mm) + list.setMultipleMode(true); + } + + public void select(int index, boolean shiftKeyDown, boolean altKeyDown) { + if (!(shiftKeyDown || altKeyDown)) + select(index); + ImagePlus imp = IJ.getImage(); + if (imp == null) + return; + Roi previousRoi = imp.getRoi(); + if (previousRoi == null) { + select(index); + return; + } + Roi.previousRoi = (Roi) previousRoi.clone(); + String label = list.getItem(index); + Roi roi = (Roi) rois.get(label); + if (roi != null) { + roi.setImage(imp); + roi.update(shiftKeyDown, altKeyDown); + } + } + + public void setEditMode(ImagePlus imp, boolean editMode) { + ImageCanvas ic = imp.getCanvas(); + boolean showAll = false; + if (ic != null) { + showAll = ic.getShowAllROIs() | editMode; + ic.setShowAllROIs(showAll); + imp.draw(); + } + showAllCheckbox.setState(showAll); + labelsCheckbox.setState(editMode); + } + + /* + * void selectAll() { boolean allSelected = true; int count = + * list.getItemCount(); for (int i=0; i 1) + rot = 1; + index += rot; + if (index < 0) + index = 0; + if (index >= list.getItemCount()) + index = list.getItemCount(); + // IJ.log(index+" "+rot); + select(index); + if (IJ.isWindows()) + list.requestFocusInWindow(); + } + } + + private boolean record() { + return Recorder.record && !IJ.isMacro(); + } + + public void mouseReleased(MouseEvent e) { + } + + public void mouseClicked(MouseEvent e) { + } + + public void mouseEntered(MouseEvent e) { + } + + public void mouseExited(MouseEvent e) { + } +} diff --git a/src/edition/StackEditorTM.java b/src/edition/StackEditorTM.java new file mode 100755 index 0000000..c166384 --- /dev/null +++ b/src/edition/StackEditorTM.java @@ -0,0 +1,296 @@ +package edition; + +import ij.CompositeImage; +import ij.IJ; +import ij.ImagePlus; +import ij.ImageStack; +import ij.gui.GenericDialog; +import ij.gui.ImageWindow; +import ij.macro.Interpreter; +import ij.measure.Calibration; +import ij.plugin.ImagesToStack; +import ij.plugin.PlugIn; +import ij.process.ImageProcessor; +import ij.process.LUT; + +import java.awt.Image; + +/** Implements the AddSlice, DeleteSlice and "Stack to Images" commands. */ +public class StackEditorTM implements PlugIn { + ImagePlus imp; + int nSlices, width, height; + static boolean deleteFrames; + + public void run(String arg) { + imp = IJ.getImage(); + nSlices = imp.getStackSize(); + width = imp.getWidth(); + height = imp.getHeight(); + + if (arg.equals("add")) + addSlice(); + else if (arg.equals("delete")) + deleteSlice(); + else if (arg.equals("toimages")) + convertStackToImages(imp); + } + + void addSlice() { + if (imp.isDisplayedHyperStack()) + return; + if (!imp.lock()) + return; + int id = 0; + ImageStack stack = imp.getStack(); + if (stack.getSize() == 1) { + String label = stack.getSliceLabel(1); + if (label != null && label.indexOf("\n") != -1) + stack.setSliceLabel(null, 1); + Object obj = imp.getProperty("Label"); + if (obj != null && (obj instanceof String)) + stack.setSliceLabel((String) obj, 1); + id = imp.getID(); + } + ImageProcessor ip = imp.getProcessor(); + int n = imp.getCurrentSlice(); + if (IJ.altKeyDown()) + n--; // insert in front of current slice + stack.addSlice(null, ip.createProcessor(width, height), n); + imp.setStack(null, stack); + imp.setSlice(n + 1); + imp.unlock(); + if (id != 0) + IJ.selectWindow(id); // prevents macros from failing + } + + void deleteSlice() { + if (nSlices < 2) { + IJ.error("\"Delete Slice\" requires a stack"); + return; + } + if (imp.isDisplayedHyperStack()) { + deleteHyperstackSliceOrFrame(); + return; + } + if (!imp.lock()) + return; + ImageStack stack = imp.getStack(); + int n = imp.getCurrentSlice(); + stack.deleteSlice(n); + if (stack.getSize() == 1) { + String label = stack.getSliceLabel(1); + if (label != null) + imp.setProperty("Label", label); + } + imp.setStack(null, stack); + if (n-- < 1) + n = 1; + imp.setSlice(n); + imp.unlock(); + } + + void deleteHyperstackSliceOrFrame() { + int channels = imp.getNChannels(); + int slices = imp.getNSlices(); + int frames = imp.getNFrames(); + int c1 = imp.getChannel(); + int z1 = imp.getSlice(); + int t1 = imp.getFrame(); + if (frames > 1 && slices == 1) + deleteFrames = true; + else if (frames == 1 && slices > 1) + deleteFrames = false; + else if (slices > 1 && frames > 1) { + GenericDialog gd = new GenericDialog("Delete Slice"); + gd.addCheckbox("Delete time point " + t1, deleteFrames); + gd.showDialog(); + if (gd.wasCanceled()) + return; + deleteFrames = gd.getNextBoolean(); + } else + return; + if (!imp.lock()) + return; + ImageStack stack = imp.getStack(); + if (deleteFrames) { // delete time point + for (int z = slices; z >= 1; z--) { + int index = imp.getStackIndex(channels, z, t1); + for (int i = 0; i < channels; i++) + stack.deleteSlice(index - i); + } + frames--; + } else { // delete slice z1 from all volumes + for (int t = frames; t >= 1; t--) { + int index = imp.getStackIndex(channels, z1, t); + for (int i = 0; i < channels; i++) + stack.deleteSlice(index - i); + } + slices--; + } + imp.setDimensions(channels, slices, frames); + // for (int i=1; i<=stack.getSize(); i++) + // IJ.log(i+" "+stack.getSliceLabel(i)+" "+stack.getProcessor(i).getPixel(0,0)); + imp.unlock(); + } + + public void convertImagesToStack() { + (new ImagesToStack()).run(""); + } + + public void convertStackToImages(ImagePlus imp) { + if (nSlices < 2) { + IJ.error("\"Convert Stack to Images\" requires a stack"); + return; + } + if (!imp.lock()) + return; + ImageStack stack = imp.getStack(); + int size = stack.getSize(); + if (size > 30 && !IJ.isMacro()) { + boolean ok = IJ.showMessageWithCancel("Convert to Images?", + "Are you sure you want to convert this\nstack to " + size + + " separate windows?"); + if (!ok) { + imp.unlock(); + return; + } + } + Calibration cal = imp.getCalibration(); + CompositeImage cimg = imp.isComposite() ? (CompositeImage) imp : null; + if (imp.getNChannels() != imp.getStackSize()) + cimg = null; + for (int i = 1; i <= size; i++) { + String label = stack.getShortSliceLabel(i); + String title = label != null && !label.equals("") ? label + : getTitle(imp, i); + ImageProcessor ip = stack.getProcessor(i); + if (cimg != null) { + LUT lut = cimg.getChannelLut(i); + if (lut != null) { + ip.setColorModel(lut); + ip.setMinAndMax(lut.min, lut.max); + } + } + ImagePlus imp2 = new ImagePlus(title, ip); + imp2.setCalibration(cal); + String info = stack.getSliceLabel(i); + if (info != null && !info.equals(label)) + imp2.setProperty("Info", info); + imp2.show(); + } + imp.changes = false; + ImageWindow win = imp.getWindow(); + if (win != null) + win.close(); + else if (Interpreter.isBatchMode()) + Interpreter.removeBatchModeImage(imp); + imp.unlock(); + } + + public Image[] getImages() { + + /* + * if (nSlices<2) + * {IJ.error("\"Convert Stack to Images\" requires a stack"); return + * null;} + */ + imp = IJ.getImage(); + if (!imp.lock()) + return null; + ImageStack stack = imp.getStack(); + int size = stack.getSize(); + Image[] imagesArray = new Image[size]; + for (int i = 1; i <= size; i++) { + ImageProcessor ip = stack.getProcessor(i); + imagesArray[i - 1] = ip.createImage(); + } + imp.unlock(); + return imagesArray; + } + + void deleteSlice(int indice) { + /* + * if (nSlices<2) {IJ.error("\"Delete Slice\" requires a stack"); + * return;} if (imp.isDisplayedHyperStack()) { + * deleteHyperstackSliceOrFrame(); return; } + */ + // imp = WindowManager.getCurrentImage(); + imp = IJ.getImage(); + if (!imp.lock()) + return; + ImageStack stack = imp.getStack(); + // int n = imp.getCurrentSlice(); + stack.deleteSlice(indice); + if (stack.getSize() == 1) { + String label = stack.getSliceLabel(1); + if (label != null) + imp.setProperty("Label", label); + } + imp.setStack(null, stack); + if (indice-- < 1) + indice = 1; + imp.setSlice(indice); + imp.unlock(); + } + + void addSlice(int indice) { + // if (imp.isDisplayedHyperStack()) return; + // Traemos la imagen de fichero + + imp = IJ.getImage(); + width = imp.getWidth(); + height = imp.getHeight(); + if (!imp.lock()) + return; + int id = 0; + ImageStack stack = imp.getStack(); + if (stack.getSize() == 1) { + String label = stack.getSliceLabel(1); + if (label != null && label.indexOf("\n") != -1) + stack.setSliceLabel(null, 1); + Object obj = imp.getProperty("Label"); + if (obj != null && (obj instanceof String)) + stack.setSliceLabel((String) obj, 1); + id = imp.getID(); + } + ImageProcessor ip = imp.getProcessor(); + // int n = imp.getCurrentSlice(); + int n = indice; + if (IJ.altKeyDown()) + n--; // insert in front of current slice + stack.addSlice(null, ip.createProcessor(width, height), n); + imp.setStack(null, stack); + imp.setSlice(n + 1); + imp.unlock(); + if (id != 0) + IJ.selectWindow(id); // prevents macros from failing + } + + /* + * int size = stack.getSize(); if (size>30 && !IJ.isMacro()) { boolean ok = + * IJ.showMessageWithCancel("Convert to Images?", + * "Are you sure you want to convert this\nstack to " + * +size+" separate windows?"); if (!ok) {imp.unlock(); return;} } + * Calibration cal = imp.getCalibration(); CompositeImage cimg = + * imp.isComposite()?(CompositeImage)imp:null; if + * (imp.getNChannels()!=imp.getStackSize()) cimg = null; for (int i=1; + * i<=size; i++) { String label = stack.getShortSliceLabel(i); String title + * = label!=null&&!label.equals("")?label:getTitle(imp, i); ImageProcessor + * ip = stack.getProcessor(i); if (cimg!=null) { LUT lut = + * cimg.getChannelLut(i); if (lut!=null) { ip.setColorModel(lut); + * ip.setMinAndMax(lut.min, lut.max); } } ImagePlus imp2 = new + * ImagePlus(title, ip); imp2.setCalibration(cal); String info = + * stack.getSliceLabel(i); if (info!=null && !info.equals(label)) + * imp2.setProperty("Info", info); imp2.show(); } imp.changes = false; + * ImageWindow win = imp.getWindow(); if (win!=null) win.close(); else if + * (Interpreter.isBatchMode()) Interpreter.removeBatchModeImage(imp); + * imp.unlock(); } + */ + + String getTitle(ImagePlus imp, int n) { + String digits = "00000000" + n; + return imp.getShortTitle() + "-" + + digits.substring(digits.length() - 4, digits.length()); + } + +} diff --git a/src/gestion/GestorOpen.java b/src/gestion/GestorOpen.java new file mode 100644 index 0000000..77773fe --- /dev/null +++ b/src/gestion/GestorOpen.java @@ -0,0 +1,47 @@ +package gestion; +import ij.ImagePlus; +import ij.WindowManager; +import ij.gui.ImageCanvas; +import ij.gui.ImageWindow; +import ij.io.FileInfo; + +import java.awt.event.KeyListener; +import java.io.File; + +import communication.v4l.FrameCamServer; +import edition.ImageManager; +import edition.RoiManager; + +/** + * This class is used to open new sessions. An ImagePlus object is passed as parameter. + * @author ehas + * + */ +public class GestorOpen { + public void opener(ImagePlus imp) { + FileInfo ofi = null; + if (imp!=null) { + ofi = imp.getOriginalFileInfo(); + } + String name = imp.getTitle(); + String directory = ofi.directory; + String imagePath = directory+name; + String filePath= imagePath.substring(0, imagePath.length()-3)+"zip"; + + + if (new File(filePath).exists()==true){ + RoiManager rm = RoiManager.getInstance(); + if (rm==null) rm = new RoiManager(); + rm.runCommand("reset"); + rm.runCommand("Open", filePath); + } + else{ + RoiManager rm = RoiManager.getInstance(); + if (rm==null) rm = new RoiManager(); + rm.runCommand("reset"); + } + + ImageManager.getInstance().run(""); + + } +} diff --git a/src/gestion/GestorSave.java b/src/gestion/GestorSave.java new file mode 100755 index 0000000..262e638 --- /dev/null +++ b/src/gestion/GestorSave.java @@ -0,0 +1,111 @@ +package gestion; + +import ij.IJ; +import ij.ImagePlus; +import ij.WindowManager; +import ij.io.FileInfo; +import ij.io.FileSaver; +import ij.io.Opener; +import ij.macro.MacroRunner; + +import java.io.FileNotFoundException; +import java.io.PrintStream; + +import edition.RoiManager; + +/** + * This class is used to save the current session. It prompts a dialog frame asking for the place to save + * the file. + * + * @author ehas + * + */ +public class GestorSave { + + public Boolean saveAsTifInZip(ImagePlus imp, boolean open) { + + Boolean savedOk = saveImageAsTifInZip(imp, open); + + if (savedOk != null && savedOk == true) { + FileInfo ofi = null; + if (imp != null) + ofi = imp.getOriginalFileInfo(); + String name = imp.getTitle(); + String directory = ofi.directory; + String path = directory + name; + + RoiManager rm = RoiManager.getInstance(); + if (rm != null) {// rm = new GestorRoi(); + rm.runCommand("Save_Especial", + path.substring(0, path.lastIndexOf(".")) + ".zip"); + } + + } + return savedOk; + + } + + public Boolean saveImageAsTifInZip(ImagePlus imp, boolean open) { + boolean unlockedImage = imp.getStackSize() == 1 && !imp.isLocked(); + if (unlockedImage) + imp.lock(); + Boolean savedOk = null; + + FileInfo ofi = null; + if (imp != null){ + ofi = imp.getOriginalFileInfo(); + } + + savedOk = new FileSaver(imp).saveAsZip(); + + if (unlockedImage) + imp.unlock(); + + boolean needToReopen = (ofi == null || !imp.getTitle() + .substring(0, imp.getTitle().lastIndexOf(".")) + .equals(ofi.fileName.substring(0, + ofi.fileName.lastIndexOf(".")))); + if (savedOk != null + && savedOk != false + && needToReopen) { + // First, current image is closed + new MacroRunner("run(\"ForceClose \")\n"); + // Then, new image is opened + new Opener().open(imp.getOriginalFileInfo().directory + + imp.getTitle().substring(0, imp.getTitle().length() - 3) + + "zip"); + new GestorOpen().opener(imp); + } + + return savedOk; + } + + public PrintStream generateXml(String path, String imagen, String roiZip) { + PrintStream xml; + try { + xml = new PrintStream(path + ".xml"); + + StringBuffer StrXML = new StringBuffer(); + StrXML.append("\n"); + + StrXML.append("\n"); + + StrXML.append("\t"); + StrXML.append(imagen); + StrXML.append("\n"); + + StrXML.append("\t"); + StrXML.append(roiZip); + StrXML.append("\n"); + + StrXML.append(""); + + xml.println(StrXML.toString()); + return xml; + } catch (FileNotFoundException e) { + IJ.error("Error while writting XML File"); + return null; + } + + } +} diff --git a/src/util/ConfigFile.java b/src/util/ConfigFile.java new file mode 100644 index 0000000..0e44fcb --- /dev/null +++ b/src/util/ConfigFile.java @@ -0,0 +1,61 @@ +package util; + +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Properties; + +public class ConfigFile { + + // Relative path to the configuration file + public static String configFile; + + // These key strings must match with the name in the configuration file + public static final String LOG_NAME = "logname"; + public static final String LOG_SYNCHRONIZATION = "logsynchronization"; + public static final String LOG_EMAIL = "logemail"; + public static final String FONT_SIZE = "fontsize"; + public static final String SHOW_SURVEY_FORM = "showsurveyform"; + + /** + * Set the path to the configuration file + * @param configFile + */ + public static void setConfigFile (String configFile){ + ConfigFile.configFile = configFile; + } + + public static String getConfigFile () { + return configFile; + } + + /** + * Return the value associated to the key. If it does not exist, it returns null. + * @param key + * @return + */ + public static String getValue (String key){ + + Properties prop = new Properties(); + InputStream input = null; + String result = null; + + try { + input = new FileInputStream(configFile); + prop.load(input); + result = prop.getProperty(key); + } catch (IOException e){ + e.printStackTrace(); + } finally { + if (input != null){ + try { + input.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + return result; + } + +} diff --git a/src/util/Dictionary.java b/src/util/Dictionary.java new file mode 100755 index 0000000..ec056fe --- /dev/null +++ b/src/util/Dictionary.java @@ -0,0 +1,34 @@ +package util; + +import java.util.HashMap; +import java.util.Map; + +/** + * It provides a static method to get word translations. + * + * @author ehas + * + */ +public class Dictionary { + + private static Map dictionary = new HashMap(){ + { + put("Brightness", "Brillo"); + put("Contrast", "Contraste"); + put("Sharpness", "Nitidez"); + put("Auto Gain", "Ganancia Automática"); + put("Power Line Frequency", "Frecuencia de la corriente"); + put("Saturation", "Saturación"); + put("Hue", "Tono"); + put("Backlight Compensation", "Compensación luz de fondo"); + + } + }; + + public static String translateWord(String word){ + if (dictionary.containsKey(word)) + return dictionary.get(word); + return word; + + }; +} diff --git a/src/util/LogSync.java b/src/util/LogSync.java new file mode 100644 index 0000000..5cb81d8 --- /dev/null +++ b/src/util/LogSync.java @@ -0,0 +1,126 @@ +package util; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; + +import communication.v4l.DefaultStreamGobbler; + +public class LogSync { + + /** Directory to store synchronized log files. */ + private static final String LOG_SYNC_DIR = "log_sync"; + + /** + * This method must be executed BEFORE any log file is created. + */ + public static void updatePendingLogs (){ + + File f = new File(MyLogger.DEFAULT_LOGGERDIR); // log directory + + if(!f.exists()){ + f.mkdir(); + return; + } + + File[] files = f.listFiles(); + for (File file : files) { + String name = file.getName(); + + // If this file exists, it means that the program was incorrectly close. It must be deleted. + if(name.contains(".log.lck")){ + file.delete(); + } else if (name.contains(".log")){ + sendLogFile(file); + + } + } + } + + private static void storeLogFile (File logFile){ + + File sync_dir = new File(MyLogger.DEFAULT_LOGGERDIR + "/" + LOG_SYNC_DIR); + + if (!sync_dir.exists()){ + sync_dir.mkdir(); + } + + logFile.renameTo(new File(sync_dir.getPath() + "/" + logFile.getName())); + logFile.delete(); + } + + public static void sendLogFile (File attachment){ + sendLogFile(ConfigFile.getValue(ConfigFile.LOG_EMAIL), "[TM Log] " + + ConfigFile.getValue(ConfigFile.LOG_NAME), attachment.getName(), attachment); + } + + public static void sendLogFile (String destination, String subject, String body, File attachment) { + + // If mutt/postfix is not installed, send a warning message and return. + if (!isMuttInstalled()){ + //TODO Send a warning message + return; + } + String command = "echo \""; + if (body != null){ + command = command.concat(body); + } + command = command.concat("\" | mutt"); + + if (subject != null){ + command = command.concat(" -s '" + subject + "'"); + } + + if (attachment != null){ + command = command.concat(" -a " + attachment.getPath() + " --"); + } + + //TODO check if the destination address is right + command = command.concat(" " + destination + ""); + + try { + Process sendMail = Runtime.getRuntime().exec(new String[] {"/bin/sh", "-c", command}); + + DefaultStreamGobbler mailstdout = new DefaultStreamGobbler(sendMail.getInputStream(), "OUTPUT", + true, "ffmpeg_out"); + mailstdout.start(); + + DefaultStreamGobbler mailstderr = new DefaultStreamGobbler(sendMail.getErrorStream(), "ERROR", + true, "ffmpeg_err"); + mailstderr.start(); + + sendMail.waitFor(); + + storeLogFile(attachment); + + } catch (IOException | InterruptedException e) { + e.printStackTrace(); + } + + } + + public static boolean isMuttInstalled () { + boolean isInstalled = false; + try { + Process checkMutt = Runtime.getRuntime().exec(new String[] {"mutt","-v"}); + BufferedReader br = new BufferedReader(new InputStreamReader(checkMutt.getInputStream())); + String line = null; + int count = 0; + while (!Thread.interrupted() && ((line = br.readLine()) != null)) { + + // If Mutt is installed, the second line starts with Copyright + if (count == 1 && line.startsWith("Copyright")){ + isInstalled = true; + } + count++; + } + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + return isInstalled; + } + + +} diff --git a/src/util/MyComboBoxRenderer.java b/src/util/MyComboBoxRenderer.java new file mode 100644 index 0000000..436de96 --- /dev/null +++ b/src/util/MyComboBoxRenderer.java @@ -0,0 +1,20 @@ +package util; + +import java.awt.Component; + +import javax.swing.JList; +import javax.swing.plaf.basic.BasicComboBoxRenderer; + +public class MyComboBoxRenderer extends BasicComboBoxRenderer { + + public Component getListCellRendererComponent(JList list, Object value, int index, + boolean isSelected, boolean cellHasFocus) { + + super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); + + setText(value.toString().split(" ")[0]); + + return this; + } + +} diff --git a/src/util/MyLogger.java b/src/util/MyLogger.java new file mode 100644 index 0000000..737f295 --- /dev/null +++ b/src/util/MyLogger.java @@ -0,0 +1,90 @@ +package util; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.sql.Date; +import java.util.HashMap; +import java.util.logging.Logger; + +import communication.v4l.CamServer; + +/** + * This class extends the default java.util.logging.Logger class. The only + * purpose of this class is to avoid the registration of the Logger in the + * LogManager. LogManager adds a shutdownhook that resets all registered + * loggers, so all loggers became disabled at shutdown rutine and it is not + * possible to log anything at that moment. Loggers should be manually reseted. + * + * @author ehas + * + */ +public class MyLogger extends Logger { + + /** Directory to store log files. */ + public static final String DEFAULT_LOGGERDIR = "log"; + /** Name of the default logger file (it can be overwritten by including a line in the config file + * with the line "Name: name_of_the_log" */ + private static final String DEFAULT_LOGGERFILENAME = "AppTm4l"; + + protected MyLogger(String name, String resourceBundleName) { + super(name, resourceBundleName); + } + + /** + * This class directly create a new Logger and doesn't ask the LogManager + * for a Logger, so it avoids the registration of the Logger. + * + * @param name + * @return + */ + public static MyLogger getLogger(String name) { + return new MyLogger(name, null); + } + + public static String getLogRecord(String key) { + return logEnglishRecords.get(key); + } + + private static HashMap logEnglishRecords; + static { + logEnglishRecords = new HashMap(); + logEnglishRecords.put("APPTM4L_START", "AppTm4l starts"); + logEnglishRecords.put("APPTM4L_FINISH", "AppTm4l finishes"); + logEnglishRecords.put("APPLICATION_TIME", "Application time (seconds):"); + logEnglishRecords.put("VIDEO_STREAMING_START", "Video streaming starts"); + logEnglishRecords.put("VIDEO_STREAMING_FINISH", "Video streaming finishes"); + logEnglishRecords.put("INCOMING_CONNECTION", "Incoming connection from"); + logEnglishRecords.put("EXTERNAL_CAPTURE", "An external capture has been taken"); + logEnglishRecords.put("LOCAL_CAPTURE", "A local capture has been taken"); + logEnglishRecords.put("VIDEO_STREAMING_START_CLIENT", "Video streaming connection from"); + logEnglishRecords.put("VIDEO_STREAMING_FINISH_CLIENT", "Video streaming disconnection from"); + logEnglishRecords.put("VIDEO_STREAMING_CONNECTION_TIME", "Connection time (seconds):"); + logEnglishRecords.put("SURVEY_FORM", "Survey answer:"); + } + + public static String createLoggerName (){ + + File log_dir = new File(DEFAULT_LOGGERDIR); + + if(!log_dir.exists()){ + log_dir.mkdir(); + } + + String tempName = ConfigFile.getValue(ConfigFile.LOG_NAME); + String logFileName = DEFAULT_LOGGERDIR + "/"; + long currentTime = System.currentTimeMillis(); + if (tempName != null){ + logFileName = logFileName.concat(tempName + "_" + new Date(currentTime).toString()); + logFileName = logFileName.concat( "_" + currentTime + ".log"); + } + else { + logFileName = logFileName.concat(DEFAULT_LOGGERFILENAME + "_" + new Date(currentTime).toString()); + logFileName = logFileName.concat("_" + currentTime + ".log"); + } + return logFileName; + } + +} diff --git a/src/util/MyLoggerFormatter.java b/src/util/MyLoggerFormatter.java new file mode 100755 index 0000000..e8cb9ae --- /dev/null +++ b/src/util/MyLoggerFormatter.java @@ -0,0 +1,26 @@ +package util; + +import java.sql.Date; +import java.util.logging.Formatter; +import java.util.logging.LogRecord; + + +/** + * This class extends Formatter class and is used to customized the AppTm4l log + * appearance + * + * @author ehas + * + */ +public class MyLoggerFormatter extends Formatter { + + @Override + public String format(LogRecord record) { + Date date = new Date(record.getMillis()); + String date_string = date.toString() + " " + record.getMillis(); + String message = "[" + date_string + "] " + record.getMessage() + + "\n"; + return message; + } + +} diff --git a/src/util/ProgressFrame.java b/src/util/ProgressFrame.java new file mode 100755 index 0000000..831cdfd --- /dev/null +++ b/src/util/ProgressFrame.java @@ -0,0 +1,65 @@ +package util; + +import java.awt.BorderLayout; +import java.awt.Container; +import java.awt.GraphicsEnvironment; + +import javax.swing.BorderFactory; +import javax.swing.JFrame; +import javax.swing.JProgressBar; +import javax.swing.border.Border; + +/** + * This class represents a frame with a progress bar in the middle of the screen. It is + * used to visually represent that a process is under execution. + * + * @author ehas + * + */ +public class ProgressFrame extends JFrame{ + + /** + * Create a frame with a progress bar. + * + * @param title Title of the frame. + * @param message Message to show above the progress bar. + */ + public ProgressFrame (String title, String message){ + super(title); + this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + Container content = this.getContentPane(); + JProgressBar progress = new JProgressBar(); + progress.setIndeterminate(true); + Border border = BorderFactory.createTitledBorder(message); + progress.setBorder(border); + content.add(progress, BorderLayout.NORTH); + GraphicsEnvironment graphics = GraphicsEnvironment + .getLocalGraphicsEnvironment(); + this.setLocation(graphics.getCenterPoint()); + this.setSize(300, 100); + } + + /** + * Create a frame with a progress bar. + * + * @param title Title and message of the progress frame. + */ + public ProgressFrame (String title){ + this(title,title); + } + + /** + * Shows the frame with the progress bar. + */ + public void start(){ + this.setVisible(true); + } + + /** + * Hides the frame with the progress bar. + */ + public void stop(){ + this.setVisible(false); + + } +} diff --git a/src/util/StatusIndicator.java b/src/util/StatusIndicator.java new file mode 100755 index 0000000..d97ea05 --- /dev/null +++ b/src/util/StatusIndicator.java @@ -0,0 +1,64 @@ +package util; + +import java.awt.Color; + +import javax.swing.JButton; +import javax.swing.JLabel; + +/** + * This class extends the JLabel class to create a JLabel with three possible + * status: DOWN (red color), PROGRESS (orange color) and UP (GREEN color). This + * is used to monitor the state of the video server. + * + * @author ehas + * + */ +public class StatusIndicator extends JButton { + + public static final int DOWN = 0; + public static final int PROGRESS = 1; + public static final int UP = 2; + + private int state = DOWN; + + /** + * Create a JLabel with the text provided as argument + * + * @param name + * Text to be displayed in the JLabel + */ + public StatusIndicator(String name) { + super(name); + } + + /** + * Change the state of the StatusIndicator object + * + * @param state + * Values allowed: StatusIndicator.DOWN, StatusIndicator.PROGRESS + * and StatusIndicator.UP + */ + public void setState(int state) { + switch (state) { + case DOWN: + this.setBackground(new Color(255, 120, 120)); + this.setToolTipText("Camara desactivada"); + this.state = DOWN; + break; + case PROGRESS: + this.setBackground(new Color(255, 165, 0)); + this.setToolTipText("Camara en progreso"); + this.state = PROGRESS; + break; + case UP: + this.setBackground(new Color(154, 205, 50)); + this.setToolTipText("Camara activada"); + this.state = UP; + break; + } + } + + public int getState(){ + return state; + } +} \ No newline at end of file diff --git a/src/util/Utilities.java b/src/util/Utilities.java new file mode 100644 index 0000000..baa3b55 --- /dev/null +++ b/src/util/Utilities.java @@ -0,0 +1,138 @@ +package util; + +import ij.WindowManager; + +import java.awt.BorderLayout; +import java.awt.image.BufferedImage; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; + +import javax.imageio.ImageIO; +import javax.swing.JDialog; +import javax.swing.JLabel; +import javax.swing.JProgressBar; + +public class Utilities { + + public static void showProgressBar(Integer maxSize) { + final JDialog dlg = new JDialog(WindowManager.getCurrentWindow(), + "Cuadro de Progreso", true); + JProgressBar dpb = new JProgressBar(0, maxSize); + dlg.add(BorderLayout.CENTER, dpb); + dlg.add(BorderLayout.NORTH, new JLabel("Capturando...")); + dlg.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE); + dlg.setSize(300, 75); + dlg.setLocationRelativeTo(WindowManager.getCurrentWindow()); + + Thread t = new Thread(new Runnable() { + public void run() { + dlg.setVisible(true); + } + }); + t.start(); + for (int i = 0; i <= maxSize; i++) { + // jl.setText("Count : " + i); + dpb.setValue(i); + if (dpb.getValue() == maxSize) { + dlg.setVisible(false); + // System.exit(0); + } + try { + Thread.sleep(25); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + + public static Boolean writeBufferedImageToFile(String path, + BufferedImage bufferedImage) { + try { + File f = new File(path); + return ImageIO.write(bufferedImage, "jpg", f); + } catch (IOException e) { + e.printStackTrace(); + return false; + } + + } + + public static String[] executeLineCommand(String[] command, + String inputData, Boolean readOutputAndErrorResponse) { + try { + String s; + StringBuffer stdErrString = new StringBuffer(); + StringBuffer stdOutputString = new StringBuffer(); + String[] outputErrorResponse = new String[3]; + String commandLine = "Command = "; + for (String commandItem : command) { + commandLine += commandItem; + } + System.out.println(commandLine); + ProcessBuilder builder = new ProcessBuilder(command); + // builder.redirectErrorStream(true); + /* + * Map env=builder.environment(); for + * (Map.Entry entry : env.entrySet()) { String key = + * entry.getKey(); String value = entry.getValue(); + * System.out.println(key + value); } + */ + /* + * env.put("XMIPP_HOME", "/home/aquintana/xmipp"); + * env.put("LD_LIBRARY_PATH", "/home/aquintana/xmipp/lib"); + * env.put("PATH", "$XMIPP_HOME/bin"); + */ + Process process = builder.start(); + // System.out.println(""+process.exitValue()); + try { + outputErrorResponse[2] = String.valueOf(process.exitValue()); + } catch (Exception e) { + outputErrorResponse[2] = null; + } + if (inputData != null) { + BufferedWriter writer = new BufferedWriter( + new OutputStreamWriter(process.getOutputStream())); + writer.write(inputData); + writer.flush(); + writer.close(); + } + BufferedReader stdInput = new BufferedReader(new InputStreamReader( + process.getInputStream())); + BufferedReader stdError = new BufferedReader(new InputStreamReader( + process.getErrorStream())); + // read the output from the command + // System.out.println("Here is the standard output of the command:\n"); + if (readOutputAndErrorResponse) { + while ((s = stdInput.readLine()) != null) { + System.out.println("Input" + s); + stdOutputString.append(s).append('\n'); + } + + // read any errors from the attempted command + // System.out.println("Here is the standard error of the command (if any):\n"); + while ((s = stdError.readLine()) != null) { + System.out.println("Error" + s); + stdErrString.append(s).append('\n'); + } + } + outputErrorResponse[0] = stdOutputString.toString(); + outputErrorResponse[1] = stdErrString.toString(); + + // outputErrorResponse[2]=String.valueOf(process.exitValue()); + + System.out.println("Standard Output = " + outputErrorResponse[0]); + System.out.println("Standard Error = " + outputErrorResponse[1]); + System.out.println("Exit Value = " + outputErrorResponse[2]); + + return outputErrorResponse; + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + return null; + } +}