From 242e7b03d24b6df25772e87bef1d73d6bce8d1e1 Mon Sep 17 00:00:00 2001 From: Florent Berthaut <florent@hitmuri.net> Date: Thu, 15 Dec 2016 14:04:13 +0100 Subject: [PATCH] Transfered files from private repo --- COPYING | 674 +++++++ data/Info.plist | 27 + data/controllar.icns | Bin 0 -> 40371 bytes data/controllar.xml | 8 + data/controllar128.png | Bin 0 -> 9998 bytes data/controllar16.png | Bin 0 -> 884 bytes data/controllar256.png | Bin 0 -> 20518 bytes data/controllar32.png | Bin 0 -> 2097 bytes data/controllar48.png | Bin 0 -> 3478 bytes data/controllar512.png | Bin 0 -> 41500 bytes data/vjpirate.desktop | 12 + example.car | 7 + src/Controlar.cpp | 1664 +++++++++++++++++ src/Controlar.hpp | 372 ++++ src/CutWindow.hpp | 86 + src/CutWindowsManager.hpp | 58 + src/FlipGroup.cpp | 96 + src/FlipGroup.hpp | 49 + src/FlipMenu.cpp | 377 ++++ src/FlipMenu.hpp | 99 + src/GroupWidget.cpp | 203 ++ src/GroupWidget.hpp | 74 + src/MainPanel.cpp | 138 ++ src/MainPanel.hpp | 85 + src/ZonePanel.cpp | 282 +++ src/ZonePanel.hpp | 124 ++ src/ZoneWidget.cpp | 1077 +++++++++++ src/ZoneWidget.hpp | 195 ++ src/mac/MacWindow.cpp | 172 ++ src/mac/MacWindow.hpp | 62 + src/mac/MacWindowsManager.cpp | 89 + src/mac/MacWindowsManager.hpp | 42 + src/osc/ip/IpEndpointName.cpp | 81 + src/osc/ip/IpEndpointName.h | 74 + src/osc/ip/NetworkingUtils.h | 49 + src/osc/ip/PacketListener.h | 43 + src/osc/ip/TimerListener.h | 40 + src/osc/ip/UdpSocket.h | 158 ++ src/osc/ip/posix/NetworkingUtils.cpp | 57 + src/osc/ip/posix/UdpSocket.cpp | 546 ++++++ src/osc/osc/MessageMappingOscPacketListener.h | 73 + src/osc/osc/OscException.h | 54 + src/osc/osc/OscHostEndianness.h | 70 + src/osc/osc/OscOutboundPacketStream.cpp | 639 +++++++ src/osc/osc/OscOutboundPacketStream.h | 142 ++ src/osc/osc/OscPacketListener.h | 72 + src/osc/osc/OscPrintReceivedElements.cpp | 241 +++ src/osc/osc/OscPrintReceivedElements.h | 49 + src/osc/osc/OscReceivedElements.cpp | 722 +++++++ src/osc/osc/OscReceivedElements.h | 486 +++++ src/osc/osc/OscTypes.cpp | 40 + src/osc/osc/OscTypes.h | 178 ++ src/win/WinWindow.cpp | 122 ++ src/win/WinWindow.hpp | 53 + src/win/WinWindowsManager.cpp | 75 + src/win/WinWindowsManager.hpp | 42 + src/x11/XWindow.cpp | 154 ++ src/x11/XWindow.hpp | 62 + src/x11/XWindowsManager.cpp | 93 + src/x11/XWindowsManager.hpp | 49 + waf | Bin 0 -> 87674 bytes wscript | 93 + 62 files changed, 10629 insertions(+) create mode 100644 COPYING create mode 100644 data/Info.plist create mode 100644 data/controllar.icns create mode 100755 data/controllar.xml create mode 100644 data/controllar128.png create mode 100644 data/controllar16.png create mode 100644 data/controllar256.png create mode 100644 data/controllar32.png create mode 100644 data/controllar48.png create mode 100644 data/controllar512.png create mode 100755 data/vjpirate.desktop create mode 100644 example.car create mode 100755 src/Controlar.cpp create mode 100755 src/Controlar.hpp create mode 100644 src/CutWindow.hpp create mode 100644 src/CutWindowsManager.hpp create mode 100644 src/FlipGroup.cpp create mode 100644 src/FlipGroup.hpp create mode 100644 src/FlipMenu.cpp create mode 100644 src/FlipMenu.hpp create mode 100644 src/GroupWidget.cpp create mode 100644 src/GroupWidget.hpp create mode 100644 src/MainPanel.cpp create mode 100644 src/MainPanel.hpp create mode 100644 src/ZonePanel.cpp create mode 100644 src/ZonePanel.hpp create mode 100644 src/ZoneWidget.cpp create mode 100644 src/ZoneWidget.hpp create mode 100644 src/mac/MacWindow.cpp create mode 100644 src/mac/MacWindow.hpp create mode 100644 src/mac/MacWindowsManager.cpp create mode 100644 src/mac/MacWindowsManager.hpp create mode 100755 src/osc/ip/IpEndpointName.cpp create mode 100755 src/osc/ip/IpEndpointName.h create mode 100755 src/osc/ip/NetworkingUtils.h create mode 100755 src/osc/ip/PacketListener.h create mode 100755 src/osc/ip/TimerListener.h create mode 100755 src/osc/ip/UdpSocket.h create mode 100755 src/osc/ip/posix/NetworkingUtils.cpp create mode 100755 src/osc/ip/posix/UdpSocket.cpp create mode 100755 src/osc/osc/MessageMappingOscPacketListener.h create mode 100755 src/osc/osc/OscException.h create mode 100755 src/osc/osc/OscHostEndianness.h create mode 100755 src/osc/osc/OscOutboundPacketStream.cpp create mode 100755 src/osc/osc/OscOutboundPacketStream.h create mode 100755 src/osc/osc/OscPacketListener.h create mode 100755 src/osc/osc/OscPrintReceivedElements.cpp create mode 100755 src/osc/osc/OscPrintReceivedElements.h create mode 100755 src/osc/osc/OscReceivedElements.cpp create mode 100755 src/osc/osc/OscReceivedElements.h create mode 100755 src/osc/osc/OscTypes.cpp create mode 100755 src/osc/osc/OscTypes.h create mode 100644 src/win/WinWindow.cpp create mode 100644 src/win/WinWindow.hpp create mode 100644 src/win/WinWindowsManager.cpp create mode 100644 src/win/WinWindowsManager.hpp create mode 100644 src/x11/XWindow.cpp create mode 100644 src/x11/XWindow.hpp create mode 100644 src/x11/XWindowsManager.cpp create mode 100644 src/x11/XWindowsManager.hpp create mode 100755 waf create mode 100755 wscript diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..94a9ed0 --- /dev/null +++ b/COPYING @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> + 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. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + 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 <http://www.gnu.org/licenses/>. + +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: + + <program> Copyright (C) <year> <name of author> + 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 +<http://www.gnu.org/licenses/>. + + 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 +<http://www.gnu.org/philosophy/why-not-lgpl.html>. diff --git a/data/Info.plist b/data/Info.plist new file mode 100644 index 0000000..5331787 --- /dev/null +++ b/data/Info.plist @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>CFBundleDevelopmentRegion</key> + <string>English</string> + <key>CFBundleExecutable</key> + <string>controllar</string> + <key>CFBundleIconFile</key> + <string>controllar.icns</string> + <key>CFBundleIdentifier</key> + <string>net.hitmuri.www</string> + <key>CFBundleInfoDictionaryVersion</key> + <string>6.0</string> + <key>CFBundleName</key> + <string>ControllAR</string> + <key>CFBundlePackageType</key> + <string>APPL</string> + <key>CFBundleShortVersionString</key> + <string>0.0.1</string> + <key>LSMinimumSystemVersion</key> + <string>10.4</string> + <key>CFBundleVersion</key> + <string>1</string> +</dict> +</plist> + diff --git a/data/controllar.icns b/data/controllar.icns new file mode 100644 index 0000000000000000000000000000000000000000..a75da2a2869f74f7ca265973366d9e165cf6df81 GIT binary patch literal 40371 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 a|7>%EuLuq`G!^Eb@@8j?Kgtw4Ui$z!_a0FI literal 0 HcmV?d00001 diff --git a/data/controllar.xml b/data/controllar.xml new file mode 100755 index 0000000..0991c65 --- /dev/null +++ b/data/controllar.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="UTF-8"?> +<mime-info xmlns="http://www.freedesktop.org/standards/shared-mime-info"> + <mime-type type="application/x-vjpirate-file"> + <comment>VjPirate file</comment> + <glob pattern="*.vjp"/> + <generic-icon name="vjp"/> + </mime-type> +</mime-info> diff --git a/data/controllar128.png b/data/controllar128.png new file mode 100644 index 0000000000000000000000000000000000000000..5006ce7874f071fb4f9c6a7fc39539a2040b01a3 GIT binary patch literal 9998 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 Gp$P!E96>Gs literal 0 HcmV?d00001 diff --git a/data/controllar16.png b/data/controllar16.png new file mode 100644 index 0000000000000000000000000000000000000000..a8c0ddfcdf01122b8ba178b115486c8f718bcc53 GIT binary patch literal 884 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7Sc;uILpV4%IBGajIv5xj zI14-?iy0W$nn9S6b<?Ly3=9mCC9V-A!TD(=<%vb94CUqJdYO6I#mR{Use1WE>9gP2 zNHH)lee`s346*1fopjb;IaTKP{?BKOYnNRvvzn`Wp)0lg_=!9P?Uhnr8#+5VJLP)3 z4pbPvI6GVAAEQdu$Aq5gy^3->DlSUs$CX`lNxeQ@Bjsk^u}v%y36+{tjK1V9JHN+f zp2D@M?|**(U9*3`_)Fi5RlLtn+}Kqfcwejk(zlNtBCcA3jdC-lyV=(!Ub0)e@c;E0 zv420W+PCpf-i!8C$CplCToo}{qIk1r;?D=MTVokCc|0F(Uw?nH|D2??Yjao^K5~^^ z$1q`*^={jnt8M#d%w^;#&wTe=-~QXu86mGX{AN%%G(l!+xW2ml`S3ljHcGb6{19{C zz}kGf4=qjXr=?d_W-2^0mEt@l;U6t7e=O*vS!!1Mi7y-f3vn<!nU=Rl<yKt%<^H#^ z6`wvmJ8UmD-%-SK<Cc3#RhPUsZ7`o7Uc|*IH?vXb-K8V{e%-EaKbZD8!T$QK)nA^@ zm)&`JU5Kw5yPKNm)4L`2`LC|odO~|G&+n}&rElN<y&WH0{{G(VI}tJ7uKU)XFl_y9 z_xH-Co3od2ni_oX|B}1^+KxxX>#oSQr*12cUL_EpG3(pKH*ePH*H(P~9CUW#tGL6$ z=WeAb7Tn!oH^F|xx|B1r_v#-%SigI6_UhHQRzCiyzx|_2eSLL7K|#CWHzP&I3&Jgv zp6_0F%IB}j_n3`S-g+PNU-G1BOYx6mC$?tKTBMOau~i@<GV)^1?QMq)YUXlqFfwFC z<uf&KFj-FQ)9me%_^~_WQts_zU%r^MwYBY<7QQwt_5Szxr7j%0s@rw;U+!D2rfT@? zrN}0^%wJ4Pzx*uP+4J>lX-|KDvtgTj{XSNP71v)g82D74s&K!m<mvjX=Ul4N6Knp8 zr0VZee|vjNGVJ(wnTbK+Qw&4Hy4MOV7P0C-x2vhCJ(_aq?a!Y-x##*V|6R#(@WzcB z3lk+4f2w4-y{$QE<BpujYu)?aAD2D8aFdSm?6XM~6+1E|j-?kD7vG;d`$0%>aIn_% wKf(<8oQiymf7WH*I_tFX!`m}we7^8|>v@>EJ`xaPU|?YIboFyt=akR{0G3Ft4gdfE literal 0 HcmV?d00001 diff --git a/data/controllar256.png b/data/controllar256.png new file mode 100644 index 0000000000000000000000000000000000000000..c4c8373e05e7e56d58d8cea0c1738283792b7f27 GIT binary patch literal 20518 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 XTAt~MUe?LLz`)??>gTe~DWM4f{%QLx literal 0 HcmV?d00001 diff --git a/data/controllar32.png b/data/controllar32.png new file mode 100644 index 0000000000000000000000000000000000000000..cda0544d56cdabf242d4fd643f7ad4493018da79 GIT binary patch literal 2097 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 bpYe(01j_)8Ro@vH7#KWV{an^LB{Ts5WbEx< literal 0 HcmV?d00001 diff --git a/data/controllar48.png b/data/controllar48.png new file mode 100644 index 0000000000000000000000000000000000000000..ac1febf8d339a5e5a83f9fb1ef10fdf0faef3b84 GIT binary patch literal 3478 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 YggdEhIhT?c7#J8lUHx3vIVCg!0O1CgQ2+n{ literal 0 HcmV?d00001 diff --git a/data/controllar512.png b/data/controllar512.png new file mode 100644 index 0000000000000000000000000000000000000000..773cc2ec98f7f9d74359886586d4dfd4f5372d00 GIT binary patch literal 41500 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 literal 0 HcmV?d00001 diff --git a/data/vjpirate.desktop b/data/vjpirate.desktop new file mode 100755 index 0000000..65995b5 --- /dev/null +++ b/data/vjpirate.desktop @@ -0,0 +1,12 @@ +[Desktop Entry] +Name=ControllAR +Comment=Remixing visual feedback +Icon=controllar +Type=Application +Exec=controllar %f +FilePattern=controllar; +Terminal=false +StartupNotify=false +Categories=Audio;AudioVideo;Application; +GenericName=ControllAR +MimeType=application/x-controllar-file diff --git a/example.car b/example.car new file mode 100644 index 0000000..07bb594 --- /dev/null +++ b/example.car @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ControllAR> + <Scene/> + <Zone win_name="pipeline.tex - QtikZ" win_x="70" win_y="67" win_w="87" win_h="83" zone_x="159" zone_y="94" zone_w="491" zone_h="434" alpha="255" transform="1" shape="1" effect="0" midi_type="0" midi_channel="32" midi_control="32752" color="0" visible="-1" update="-1" content="0"/> + <Zone win_name="xfce4-panel" win_x="0" win_y="0" win_w="241" win_h="36" zone_x="11" zone_y="54" zone_w="241" zone_h="194" alpha="255" transform="0" shape="0" effect="0" midi_type="0" midi_channel="16777216" midi_control="21848" color="0" visible="-1" update="-1" content="0"/> + <Midi device="" set_channel="0" set_type="0" set_control="0"/> +</ControllAR> diff --git a/src/Controlar.cpp b/src/Controlar.cpp new file mode 100755 index 0000000..c3aae16 --- /dev/null +++ b/src/Controlar.cpp @@ -0,0 +1,1664 @@ +/*************************************************************************** + * Controlar.cpp + * Part of ControllAR + * 2016- Florent Berthaut + * hitmuri.net + ****************************************************************************/ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "Controlar.hpp" + +#include <iostream> +#include <sstream> +#include <stdio.h> +#include <inttypes.h> +#include <unistd.h> +#include <portmidi.h> +#include <porttime.h> +#include <FL/fl_ask.H> +#include <FL/Fl.H> +#include "osc/osc/OscReceivedElements.h" +#include "osc/osc/OscOutboundPacketStream.h" +#include "MainPanel.hpp" +#include "ZonePanel.hpp" + +#ifdef OSX + #include "mac/MacWindowsManager.hpp" +#else + #ifdef WINDOWS + #include "win/WinWindowsManager.hpp" + #else + #include "x11/XWindowsManager.hpp" + #endif +#endif + +using namespace std; + +void idleFunc(void* data) { + Controlar::getInstance()->update(); + usleep(10000); +} + +void* oscThreadFunction(void* pvParam) { + Controlar::getInstance()->m_socket->Run(); + return 0; +} + +#ifdef GL +Controlar::Controlar(): Fl_Gl_Window(800, 600, "ControllAR") { +#else +Controlar::Controlar(): Fl_Double_Window(800, 600, "ControllAR") { +#endif + Fl::set_color(FL_BACKGROUND_COLOR, 200, 200, 200); + Fl::set_color(FL_BACKGROUND2_COLOR, 150, 150, 150); + Fl::set_color(FL_SELECTION_COLOR, 250, 250, 250); + box(FL_FLAT_BOX); + end(); + + m_drawing=false; + m_flipped=false; + m_fullscreen=false; + m_duplicating=false; + m_midiLearning=false; + m_askedScene=-1; + m_displayScale=1; + + m_alphaLevels[0] = 65; + m_alphaLevels[1] = 125; + m_alphaLevels[2] = 190; + m_alphaLevels[3] = 255; + for(int i=0;i<10;++i) { + m_menuValues[i]=i; + } + + //add a first set + //m_currentSet=0; + //m_zonesSets.push_back(map<unsigned int, ZoneWidget*>()); + m_scenes[0]="scene"; + m_currentScene=0; + + //create menus + m_zoneMenu = new FlipMenu(); + m_zoneMenu->hide(); + m_zoneMenu->end(); + add(m_zoneMenu); + m_mainMenu = new FlipMenu(); + m_mainMenu->end(); + m_mainMenu->hide(); + add(m_mainMenu); + m_mainMenu->addWithSub("File/Open", statOpen, this); + m_mainMenu->addWithSub("File/Save", statSave, this); + m_mainMenu->addWithSub("File/Save As", statSaveAs, this); + m_mainMenu->addWithSub("View/FullScreen", statFullscreen, this); + m_mainMenu->addWithSub("View/Mirror", statFlip, this); + m_mainMenu->addWithSub("Scenes/Next", statSceneNext, this); + m_mainMenu->addWithSub("Scenes/Prev", statScenePrev, this); + m_mainMenu->addWithSub("Scenes/Delete", statSceneDel, this); + m_mainMenu->addWithSub("Scenes/Learn Input", statSceneLearn, this); + m_mainMenu->addSub("Midi Input"); + m_mainMenu->add("Quit", statQuit, this); + + m_mainPanel = MainPanel::getInstance(); + //add(m_mainPanel); + m_mainPanel->hide(); + m_zonePanel= ZonePanel::getInstance(); + //add(m_zonePanel); + m_zonePanel->hide(); + + //initialize main loop function + Fl::add_idle(idleFunc, NULL); + + //create windows manager and get display scale if needed + #ifdef OSX + m_winsMan = new MacWindowsManager(); + #else + #ifdef WINDOWS + m_winsMan = new WinWindowsManager(); + #else + m_winsMan = new XWindowsManager(); + #endif + #endif + + updateTitle(); + + //initialise midi + m_midiStream=NULL; + Pm_Initialize(); + detectMidiDevices(); + Pt_Start(1, NULL, NULL); + + //initialize osc + try { + m_socket = new UdpListeningReceiveSocket( + IpEndpointName(IpEndpointName::ANY_ADDRESS, 8327), + this); + pthread_mutex_init(&m_zonesMutex, NULL); + pthread_create(&m_oscThread, NULL, oscThreadFunction , this); + } + catch(const exception& e) { + DEBUG("Error in the OscManager "<<e.what()); + } +} + +void Controlar::refreshWindowList() { + //recreate windows list + m_winsMan->updateWindowsList(); + + //clean all zones and reassign windows + map<unsigned int, ZoneWidget*>::iterator itZo = m_zones.begin(); + for(; itZo!=m_zones.end(); ++itZo) { + itZo->second->refreshCutWin(); + } + + //rebuild zone menu + m_zoneMenu->clear(); + vector<CutWindow*>::iterator itWin = m_winsMan->editWindowList().begin(); + for(; itWin!=m_winsMan->editWindowList().end(); ++itWin) { + m_zoneMenu->addWithSub((string("Select/")+(*itWin)->getName()).c_str(), + statZoneSelect, (*itWin)); + } + m_zonePanel->setWindowList(m_winsMan->editWindowList()); + + + m_zoneMenu->addWithSub("Alpha/Opaque", statZoneAlpha, &m_alphaLevels[3]); + m_zoneMenu->addWithSub("Alpha/Mostly Opaque", + statZoneAlpha, &m_alphaLevels[2]); + m_zoneMenu->addWithSub("Alpha/Half Transparent", + statZoneAlpha, &m_alphaLevels[1]); + m_zoneMenu->addWithSub("Alpha/Mostly Transparent", + statZoneAlpha, &m_alphaLevels[0]); + m_zoneMenu->addWithSub("Transform/No Rotation", + statZoneTrans, &m_menuValues[ZoneWidget::NONE]); + m_zoneMenu->addWithSub("Transform/Rotate 90", + statZoneTrans, &m_menuValues[ZoneWidget::ROTATE_90]); + m_zoneMenu->addWithSub("Transform/Rotate 180", + statZoneTrans, &m_menuValues[ZoneWidget::ROTATE_180]); + m_zoneMenu->addWithSub("Transform/Rotate 270", + statZoneTrans, &m_menuValues[ZoneWidget::ROTATE_270]); + m_zoneMenu->addWithSub("Shape/Box", + statZoneShape, &m_menuValues[ZoneWidget::BOX]); + m_zoneMenu->addWithSub("Shape/Circle", + statZoneShape, &m_menuValues[ZoneWidget::CIRCLE]); + m_zoneMenu->addWithSub("Shape/Frame", + statZoneShape, &m_menuValues[ZoneWidget::FRAME]); + m_zoneMenu->addWithSub("Color/Normal", + statZoneCol, &m_menuValues[ZoneWidget::NORMAL]); + m_zoneMenu->addWithSub("Color/Inverse", + statZoneCol, &m_menuValues[ZoneWidget::INVERT]); + m_zoneMenu->addWithSub("Visible/All Scenes", + statZoneVisible, + &m_menuValues[ZoneWidget::VISIBLE_ALL]); + m_zoneMenu->addWithSub("Visible/Current Scene Only", + statZoneVisible, + &m_menuValues[ZoneWidget::VISIBLE_CURRENT]); + m_zoneMenu->addWithSub("Update/All Scenes", + statZoneUpdate, + &m_menuValues[ZoneWidget::UPDATE_ALL]); + m_zoneMenu->addWithSub("Update/Current Scene Only", + statZoneUpdate, + &m_menuValues[ZoneWidget::UPDATE_CURRENT]); + m_zoneMenu->addWithSub("Content/Global", + statZoneContent, + &m_menuValues[ZoneWidget::CONTENT_GLOBAL]); + m_zoneMenu->addWithSub("Content/Local", + statZoneContent, + &m_menuValues[ZoneWidget::CONTENT_LOCAL]); + m_zoneMenu->addWithSub("MIDI Input/Learn", + statZoneMidiLearn, this); + m_zoneMenu->addWithSub("MIDI Input/No effect", + statZoneMidiEffect, &m_menuValues[ZoneWidget::NO_EFFECT]); + m_zoneMenu->addWithSub("MIDI Input/Show", + statZoneMidiEffect, &m_menuValues[ZoneWidget::SHOW]); + m_zoneMenu->addWithSub("MIDI Input/Hide", + statZoneMidiEffect, &m_menuValues[ZoneWidget::HIDE]); + + m_zoneMenu->add("Clear", statZoneClear, this); + m_zoneMenu->add("Delete", statZoneDelete, this); + + m_mainMenu->hide(); + m_zoneMenu->hide(); +} + +void Controlar::update() { + Controlar::getInstance()->damage(FL_DAMAGE_USER1); + parseMidi(); +} + + +void Controlar::parseMidi() { + PmEvent buffer[10]; + vector<PortMidiStream*>::iterator itSt = m_midiStreamsVec.begin(); + for(; itSt!=m_midiStreamsVec.end();++itSt) { + int nb = Pm_Read((*itSt), buffer, 10); + for(int i=0;i<nb;++i) { + long status = Pm_MessageStatus(buffer[i].message); + int msgType = status >> 4; + int msgChannel = (status & 15) + 1; + int control = Pm_MessageData1(buffer[i].message); + int value = Pm_MessageData2(buffer[i].message); + if(m_midiLearning) { + m_midiType = msgType; + m_midiChannel = msgChannel; + m_midiControl = control; + m_midiLearning=false; + } + if(msgType==m_midiType + && msgChannel==m_midiChannel + && control==m_midiControl) { + setScene(value); + } + map<unsigned int, ZoneWidget*>::iterator itZo = m_zones.begin(); + for(; itZo!=m_zones.end(); ++itZo) { + itZo->second->processMidi(msgType, msgChannel, + control, value); + } + } + } +} + +void Controlar::openMidiDevice(const std::string& devName) { + map<string, int>::iterator itDev=m_midiDevMap.find(devName); + if(itDev!=m_midiDevMap.end()) { + if(m_midiStreamsMap.find(devName)!=m_midiStreamsMap.end()) { + Pm_Close(m_midiStreamsMap[devName]); + } + m_midiStreamsMap[devName]=NULL; + Pm_OpenInput(&m_midiStreamsMap[devName], + itDev->second, NULL, 100, NULL, NULL); + } + refreshMidiStreams(); +} + +void Controlar::closeMidiDevice(const std::string& devName) { + map<string, PortMidiStream*>::iterator itSt=m_midiStreamsMap.find(devName); + if(itSt!=m_midiStreamsMap.end()) { + Pm_Close(itSt->second); + m_midiStreamsMap.erase(devName); + } + refreshMidiStreams(); +} + +void Controlar::refreshMidiStreams() { + m_midiStreamsVec.clear(); + map<string, PortMidiStream*>::iterator itSt=m_midiStreamsMap.begin(); + for(; itSt!=m_midiStreamsMap.end(); ++itSt) { + m_midiStreamsVec.push_back(itSt->second); + } +} + +void Controlar::detectMidiDevices() { + + Pm_Terminate(); + Pm_Initialize(); + int nbDevices = Pm_CountDevices(); +/* + FlipMenu* midiMenu = m_mainMenu->getSub("Midi Input"); + midiMenu->clear(); + m_midiDevices.clear(); + m_midiDeviceNames.clear(); + m_midiStreamsMap.clear(); +*/ + vector<bool> devOps; + vector<string> devNames; + for(int i=0; i<nbDevices; ++i) { + const PmDeviceInfo* info = Pm_GetDeviceInfo(i); + if(info->input>0) { + string devName = string(info->name); +/* + m_midiDevices.push_back(i); + m_midiDeviceNames.push_back(devName); + midiMenu->add(devName, statMidiDev, &m_midiDevices.back()); +*/ + m_midiDevMap[devName]=i; + devNames.push_back(devName); + if(m_midiStreamsMap.find(devName)!=m_midiStreamsMap.end()) { + openMidiDevice(devName); + devOps.push_back(true); + } + else { + devOps.push_back(false); + } + } + } + m_mainPanel->setMidiDevices(devNames, devOps); +/* + if(m_currentMidiDeviceName.compare("")!=0) { + setMidiDeviceFromName(m_currentMidiDeviceName); + } +*/ +} + +ZoneWidget* Controlar::startDuplicating(ZoneWidget* dupWid) { + m_duplicatedZone = new ZoneWidget(); + m_duplicatedZone->copy(dupWid); + addZone(m_duplicatedZone); + m_duplicatedZone->forceDraggingZone(); + return m_duplicatedZone; +} + +int Controlar::handle(int event) { + int res=0; + if(!m_drawing) { + res=Fl_Group::handle(event); + } + if(res>0) { + redraw(); + } + else { + switch(event) { + case FL_KEYDOWN: { + if(Fl::event_command()) { + switch(Fl::event_key()) { + case 'o': cbOpen(); break; + case 's': cbSave(); break; + case 'f': cbFullscreen(); break; + case 'm': cbFlip(); break; + case 'q': cbQuit(); break; + default:break; + } + res=1; + } + }break; + case FL_PUSH: + if(m_duplicating) { + m_duplicating=false; + } + switch(Fl::event_button()) { + case FL_LEFT_MOUSE: { + m_drawStartX=Fl::event_x(); + m_drawStartY=Fl::event_y(); + m_drawCurX=Fl::event_x(); + m_drawCurY=Fl::event_y(); + m_drawing=true; + m_drawingGroup=false; + if(Fl::event_shift()) { + m_drawingGroup=true; + } + m_mainMenu->hide(); + m_zoneMenu->hide(); + m_mainPanel->hide(); + m_zonePanel->hide(); + redraw(); + }break; + case FL_RIGHT_MOUSE: { + detectMidiDevices(); + insert(*m_mainMenu, children()); + int menPosX = (Fl::event_x()>w()-m_mainPanel->w())? + Fl::event_x()-m_mainPanel->w():Fl::event_x(); + int menPosY = (Fl::event_y()>h()-m_mainPanel->h())? + Fl::event_y()-m_mainPanel->h():Fl::event_y(); + //m_mainMenu->position(menPosX, menPosY); + //m_mainMenu->show(); + //m_zoneMenu->hide(); + m_mainPanel->position(menPosX, menPosY); + insert(*m_mainPanel,children()); + m_mainPanel->show(); + }break; + default:break; + } + res=1; + break; + case FL_DRAG: + m_drawCurX=Fl::event_x(); + m_drawCurY=Fl::event_y(); + break; + case FL_RELEASE: + switch(Fl::event_button()) { + case FL_LEFT_MOUSE: { + int minPixels=20; + int drawStopX = Fl::event_x(); + int drawStopY = Fl::event_y(); + if(m_drawing) { + if(abs(m_drawStartX-drawStopX)>minPixels && + abs(m_drawStartY-drawStopY)>minPixels) { + if(m_drawingGroup) { + addGroup(new GroupWidget(m_drawStartX, + m_drawStartY, + abs(m_drawStartX-drawStopX), + abs(m_drawStartY-drawStopY))); + } + else { + addZone(new ZoneWidget( + m_drawStartX-ZoneWidget::OUTER, + m_drawStartY-ZoneWidget::OUTER, + abs(m_drawStartX-drawStopX) + +ZoneWidget::OUTER*2, + abs(m_drawStartY-drawStopY) + +ZoneWidget::OUTER*2)); + } + } + } + m_drawing=false; + res=1; + }break; + default:break; + } + break; + default:break; + } + } + redraw(); + return res; +} + +void Controlar::ProcessMessage(const osc::ReceivedMessage& mess, + const IpEndpointName& remoteEndpoint) { + try { + string name = string(mess.AddressPattern()); + if(name.find("/controllar/")!=string::npos) { + if(name.find("/scene")!=string::npos && mess.ArgumentCount()>0) { + pthread_mutex_lock(&m_zonesMutex); + m_askedScene=mess.ArgumentsBegin()->AsInt32(); + pthread_mutex_unlock(&m_zonesMutex); + } + } + } + catch(const exception& e) { + cout<<"Exception when receiving osc message "<<e.what()<<endl; + } +} + + +void Controlar::cbZone(Fl_Widget* wid) { + refreshWindowList(); + + m_clickedZone = static_cast<ZoneWidget*>(wid); + int menPosX = (Fl::event_x()>w()-m_zonePanel->w())? + Fl::event_x()-m_zonePanel->w():Fl::event_x(); + int menPosY = (Fl::event_y()>h()-m_zonePanel->h())? + Fl::event_y()-m_zonePanel->h():Fl::event_y(); + //m_zoneMenu->position(menPosX, menPosY); + //insert(*m_zoneMenu, children()); + //m_zoneMenu->show(); + m_zonePanel->setZone(m_clickedZone); + m_zonePanel->position(menPosX, menPosY); + m_zonePanel->refresh(); + insert(*m_zonePanel,children()); + m_zonePanel->show(); +} + +void Controlar::cbSceneNext() { + if(m_scenes.find(m_currentScene+1)==m_scenes.end()) { + m_scenes[m_currentScene+1]="scene"; + } + setScene(m_currentScene+1); +} + +void Controlar::cbScenePrev() { + setScene(m_currentScene-1); +} + +/* +void Controlar::cbSceneDuplicate() { + //save ref to current set + int cur=m_currentSet; + + //create new set and make it current + m_zonesSets.push_back(map<unsigned int, ZoneWidget*>()); + setSet(m_zonesSets.size()-1); + + //copy zones + map<unsigned int, ZoneWidget*>::iterator itZo = m_zonesSets[cur].begin(); + for(; itZo!=m_zonesSets[cur].end(); ++itZo) { + ZoneWidget* newZo = new ZoneWidget(); + newZo->copy(itZo->second); + addZone(newZo); + } + setSet(m_currentSet); +} +*/ + +void Controlar::cbSceneDelete() { +/* + if(m_zonesSets.size()>1) { + if(fl_choice("Are you sure you want to delete the current set ?", + "No", "Yes", "")) { + map<unsigned int, ZoneWidget*>::iterator itZo + = m_zonesSets[m_currentSet].begin(); + for(; itZo!=m_zonesSets[m_currentSet].end(); ++itZo) { + Fl::delete_widget(itZo->second); + } + m_zonesSets.erase(m_zonesSets.begin()+m_currentSet); + setSet(0); + } + } +*/ +} + +void Controlar::setScene(const int& scene) { + if(m_scenes.find(scene)!=m_scenes.end()) { + //store images for zones that only update on current scene + map<unsigned int, ZoneWidget*>::iterator itZo = m_zones.begin(); + for(; itZo!=m_zones.end(); ++itZo) { + itZo->second->setScene(scene); + } + //change scene + m_currentScene=scene; + m_mainPanel->setCurrentScene(scene); + m_mainPanel->refresh(); + updateTitle(); + } + m_mainMenu->hide(); +} + +void Controlar::updateTitle() { + ostringstream oss; + oss<<m_currentScene; + m_title = "ControllAR - "+m_fileName+" - Scene "+oss.str(); + label(m_title.c_str()); +} + +void Controlar::cbSceneLearn() { + m_midiLearning=true; + m_mainMenu->hide(); +} + +void Controlar::cbFlip() { + m_flipped=!m_flipped; + resize(x(),y(),w(),h()); + m_mainMenu->hide(); + map<unsigned int, ZoneWidget*>::iterator itZo=m_zones.begin(); + for(; itZo!=m_zones.end(); ++itZo) { + itZo->second->recomputePixels(); + } +} + +void Controlar::cbFullscreen() { + m_fullscreen=!m_fullscreen; + if(m_fullscreen) { + fullscreen(); + } + else { + fullscreen_off(); + } + m_mainMenu->hide(); + fl_cursor(FL_CURSOR_NONE); +} + +#ifdef GL +void Controlar::initGL() { + #ifdef OSX + glewExperimental = GL_TRUE; + #endif + glewInit(); + glGenVertexArrays(1, &m_vertexArrayID); + glBindVertexArray(m_vertexArrayID); + glGenBuffers(1, &m_vertexBuffer); + glBindBuffer(GL_ARRAY_BUFFER, m_vertexBuffer); + m_vertexBufferData[0]=0;m_vertexBufferData[1]=0;m_vertexBufferData[2]=0; + m_vertexBufferData[3]=1;m_vertexBufferData[4]=0;m_vertexBufferData[5]=0; + m_vertexBufferData[6]=1;m_vertexBufferData[7]=1;m_vertexBufferData[8]=0; + m_vertexBufferData[9]=0;m_vertexBufferData[10]=0;m_vertexBufferData[11]=0; + m_vertexBufferData[12]=1;m_vertexBufferData[13]=1;m_vertexBufferData[14]=0; + m_vertexBufferData[15]=0;m_vertexBufferData[16]=1;m_vertexBufferData[17]=0; + glBufferData(GL_ARRAY_BUFFER, sizeof(m_vertexBufferData), + m_vertexBufferData, GL_STATIC_DRAW); + + GLint compile_ok = GL_FALSE, link_ok = GL_FALSE; + + GLuint vertexShader, fragmentShader; + + //ZONE PROGRAM + { + vertexShader = glCreateShader(GL_VERTEX_SHADER); + const char *vs_source = + "#version 110\n" + "attribute vec3 coord;\n" + "uniform vec2 winSize;\n" + "uniform vec2 zonePos;\n" + "uniform vec2 zoneSize;\n" + "uniform vec2 cutPos;\n" + "uniform vec2 cutSize;\n" + "uniform float mirrored;\n" + "uniform float outer;\n" + "varying vec2 pixRatio;\n" + "varying vec4 pixToSides;\n" //T,B,L,R + "void main(void) {\n" + " vec2 zonePixRatio;\n" + " if(mirrored>0.0) {\n" + " zonePixRatio = vec2(coord.x, coord.y);\n" + " pixRatio = vec2(\n" + " (coord.x*zoneSize.x-outer)/cutSize.x,\n" + " (coord.y*zoneSize.y-outer)/cutSize.y);\n" + " }\n" + " else {\n" + " zonePixRatio = vec2(coord.x, 1.0-coord.y);\n" + " pixRatio = vec2(\n" + " (coord.x*zoneSize.x-outer)/cutSize.x,\n" + " ((1.0-coord.y)*zoneSize.y-outer)/cutSize.y);\n" + " }\n" + " vec2 cutPixPos = pixRatio*cutSize;\n" + " vec2 pos = ((cutPixPos+cutPos)/winSize)*2.0-1.0;\n" + " if(mirrored>0.0) {\n" + " gl_Position = vec4(pos.x, pos.y, 0.0, 1.0);\n" + " }\n" + " else {\n" + " gl_Position = vec4(pos.x, -pos.y, 0.0, 1.0);\n" + " }\n" + " vec2 zonePixPos = zonePixRatio*zoneSize;\n" + " pixToSides = vec4(zonePixPos.y,\n" + " zoneSize.y-zonePixPos.y,\n" + " zonePixPos.x,\n" + " zoneSize.x-zonePixPos.x);\n" + "}\n"; + glShaderSource(vertexShader, 1, &vs_source, NULL); + glCompileShader(vertexShader); + glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &compile_ok); + if (!compile_ok) { + cout << "Error in vertex shader" << endl; + GLint maxLength = 0; + glGetShaderiv(vertexShader, GL_INFO_LOG_LENGTH, &maxLength); + std::vector<GLchar> errorLog(maxLength); + glGetShaderInfoLog(vertexShader, maxLength, &maxLength, &errorLog[0]); + vector<GLchar>::iterator itC=errorLog.begin(); + for(; itC!=errorLog.end(); ++itC) { + cout<<*itC; + } + cout<<endl; + } + + fragmentShader = glCreateShader(GL_FRAGMENT_SHADER); + const char *fs_source = + "#version 110\n" + "varying vec2 pixRatio;\n" + "varying vec4 pixToSides;\n" //T,B,L,R + "uniform sampler2D windowTexture;\n" + "uniform sampler2D coordsTexture;\n" + "uniform vec2 winSize;\n" + "uniform vec2 zonePos;\n" + "uniform vec2 zoneSize;\n" + "uniform vec2 cutPos;\n" + "uniform vec2 cutSize;\n" + "uniform float mirrored;\n" + "uniform int state;\n"//NORMAL,MOVE_CUT,RESIZE_CUT,MOVE_ZONE,RESIZE_ZONE + "uniform float alpha;\n" + "uniform float filled;\n" + "uniform float invert;\n" + "uniform float outer;\n" + "uniform float inner;\n" + "void main(void) { \n" + " float thick=1.0;\n" + " if(filled>0.0) {\n" + " if(texture2D(coordsTexture, pixRatio).r>=0.0 \n" + " && pixRatio.x>0.0 && pixRatio.x<1.0 \n" + " && pixRatio.y>0.0 && pixRatio.y<1.0) {\n" + " vec4 winCol = texture2D(windowTexture, \n" + " texture2D(coordsTexture, pixRatio).rg);\n" +#ifdef WINDOWS + " winCol = texture2D(windowTexture, pixRatio);\n" +#else + " winCol = vec4(winCol.b,winCol.g,winCol.r,winCol.a);\n" +#endif + " if(invert>0.0) {\n" + " gl_FragColor=vec4(1.0-winCol.r, 1.0-winCol.g, \n" + " 1.0-winCol.b, alpha);\n" + " }\n" + " else {\n" + " gl_FragColor=vec4(winCol.rgb, alpha);\n" + " }\n" + " }\n" + " else {\n" + " gl_FragColor=vec4(0.0, 0.0, 0.0, 0.0);\n" + " }\n" + " if(state>0) {\n" + //outer border + " if(pixToSides.x<thick || pixToSides.y<thick \n" + " || pixToSides.z<thick || pixToSides.w<thick) { \n" + " if(state==3) {\n" + " gl_FragColor=vec4(1.0,0.0,0.0,1.0);\n" + " }\n" + " else {\n" + " gl_FragColor=vec4(1.0,1.0,1.0,1.0);\n" + " }\n" + " }\n" + //inner border + " if(((abs(pixToSides.x-inner)<thick \n" + " || abs(pixToSides.y-inner)<thick)\n" + " && (pixToSides.z>inner && pixToSides.w>inner))\n" + " || (abs(pixToSides.z-inner)<thick \n" + " || abs(pixToSides.w-inner)<thick)\n" + " && (pixToSides.x>inner && pixToSides.y>inner)) {\n" + " if(state==1) {\n" + " gl_FragColor=vec4(1.0,0.0,0.0,1.0);\n" + " }\n" + " else {\n" + " gl_FragColor=vec4(1.0,1.0,1.0,1.0);\n" + " }\n" + " }\n" + //outer corner + " if(abs(sqrt(pow(pixToSides.w, 2.0)\n" + " +pow(pixToSides.y, 2.0))-inner)<thick) {\n" + " if(state==4) {\n" + " gl_FragColor=vec4(1.0,0.0,0.0,1.0);\n" + " }\n" + " else {\n" + " gl_FragColor=vec4(1.0,1.0,1.0,1.0);\n" + " }\n" + " }\n" + //inner corner + " if(( abs(sqrt(pow(pixToSides.w, 2.0)\n" + " +pow(pixToSides.y, 2.0))-inner*3.0)<thick)\n" + " && pixToSides.x>inner && pixToSides.y>inner \n" + " && pixToSides.z>inner && pixToSides.w>inner ){\n" + " if(state==2) {\n" + " gl_FragColor=vec4(1.0,0.0,0.0,1.0);\n" + " }\n" + " else {\n" + " gl_FragColor=vec4(1.0,1.0,1.0,1.0);\n" + " }\n" + " }\n" + " }\n" + " }\n" + " else {\n" + " if(pixToSides.x<thick || pixToSides.y<thick \n" + " || pixToSides.z<thick || pixToSides.w<thick) { \n" + " if(state>0) {\n" + " gl_FragColor=vec4(1.0,0.0,0.0,1.0);\n" + " }\n" + " else {\n" + " gl_FragColor=vec4(1.0,1.0,1.0,1.0);\n" + " }\n" + " }\n" + " else {\n" + " gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0);\n" + " }\n" + " }\n" + "}\n"; + glShaderSource(fragmentShader, 1, &fs_source, NULL); + glCompileShader(fragmentShader); + glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &compile_ok); + if (!compile_ok) { + cout << "Error in fragment shader" << endl; + GLint maxLength = 0; + glGetShaderiv(fragmentShader, GL_INFO_LOG_LENGTH, &maxLength); + std::vector<GLchar> errorLog(maxLength); + glGetShaderInfoLog(fragmentShader, maxLength, &maxLength, &errorLog[0]); + vector<GLchar>::iterator itC=errorLog.begin(); + for(; itC!=errorLog.end(); ++itC) { + cout<<*itC; + } + cout<<endl; + } + m_zoneProgram = glCreateProgram(); + glAttachShader(m_zoneProgram, vertexShader); + glAttachShader(m_zoneProgram, fragmentShader); + glLinkProgram(m_zoneProgram); + glGetProgramiv(m_zoneProgram, GL_LINK_STATUS, &link_ok); + if (!link_ok) { + cout << "Error in glLinkProgram" << endl; + } + m_zoneWinSizeUniform = glGetUniformLocation(m_zoneProgram, "winSize"); + m_zoneSizeUniform = glGetUniformLocation(m_zoneProgram, "zoneSize"); + m_zonePosUniform = glGetUniformLocation(m_zoneProgram, "zonePos"); + m_cutSizeUniform = glGetUniformLocation(m_zoneProgram, "cutSize"); + m_cutPosUniform = glGetUniformLocation(m_zoneProgram, "cutPos"); + m_zoneMirrorUniform = glGetUniformLocation(m_zoneProgram, "mirrored"); + m_zoneStateUniform = glGetUniformLocation(m_zoneProgram, "state"); + m_zoneAlphaUniform = glGetUniformLocation(m_zoneProgram, "alpha"); + m_zoneInvertUniform = glGetUniformLocation(m_zoneProgram, "invert"); + m_zoneFilledUniform = glGetUniformLocation(m_zoneProgram, "filled"); + m_zoneWindowUniform = glGetUniformLocation(m_zoneProgram, "windowTexture"); + m_zoneCoordsUniform = glGetUniformLocation(m_zoneProgram, "coordsTexture"); + m_zoneInnerUniform = glGetUniformLocation(m_zoneProgram, "inner"); + m_zoneOuterUniform = glGetUniformLocation(m_zoneProgram, "outer"); + + glActiveTexture(GL_TEXTURE0); + glGenTextures(1, &m_firstTextureID); + glBindTexture(GL_TEXTURE_2D, m_firstTextureID); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glActiveTexture(GL_TEXTURE1); + glGenTextures(1, &m_secondTextureID); + glBindTexture(GL_TEXTURE_2D, m_secondTextureID); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + } + + //GROUP PROGRAM + { + vertexShader = glCreateShader(GL_VERTEX_SHADER); + const char *vs_source = + "#version 110\n" + "attribute vec3 coord;\n" + "uniform vec2 winSize;\n" + "uniform vec2 groupPos;\n" + "uniform vec2 groupSize;\n" + "uniform float mirrored;\n" + "varying vec2 pixPos;\n" + "void main(void) {\n" + " if(mirrored>0.0) {\n" + " pixPos = vec2(coord.x, coord.y);\n" + " }\n" + " else {\n" + " pixPos = vec2(coord.x, 1.0-coord.y);\n" + " }\n" + " vec2 pos = ((pixPos*groupSize+groupPos)/winSize)*2.0-1.0;\n" + " if(mirrored>0.0) {\n" + " gl_Position = vec4(pos.x, pos.y, 0.0, 1.0);\n" + " }\n" + " else {\n" + " gl_Position = vec4(pos.x, -pos.y, 0.0, 1.0);\n" + " }\n" + "}\n"; + glShaderSource(vertexShader, 1, &vs_source, NULL); + glCompileShader(vertexShader); + glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &compile_ok); + if (!compile_ok) { + cout << "Error in vertex shader" << endl; + GLint maxLength = 0; + glGetShaderiv(vertexShader, GL_INFO_LOG_LENGTH, &maxLength); + std::vector<GLchar> errorLog(maxLength); + glGetShaderInfoLog(vertexShader, maxLength, &maxLength, &errorLog[0]); + vector<GLchar>::iterator itC=errorLog.begin(); + for(; itC!=errorLog.end(); ++itC) { + cout<<*itC; + } + cout<<endl; + } + + fragmentShader = glCreateShader(GL_FRAGMENT_SHADER); + const char *fs_source = + "#version 110\n" + "varying vec2 pixPos;\n" + "uniform vec2 winSize;\n" + "uniform float mirrored;\n" + "uniform float editing;\n" + "uniform float highlight;\n" + "void main(void) { \n" + " if(editing>0.0) {\n" + " gl_FragColor = vec4(0.0,1.0,0.0,1.0);\n" + " }\n" + " else {\n" + " if(pixPos.x<0.01 || pixPos.x>0.99 \n" + " || pixPos.y<0.01 || pixPos.y>0.99) { \n" + " gl_FragColor=vec4(0.0,0.5+highlight*0.5,0.0,1.0);\n" + " }\n" + " else {\n" + " gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0);\n" + " }\n" + " }\n" + "}\n"; + glShaderSource(fragmentShader, 1, &fs_source, NULL); + glCompileShader(fragmentShader); + glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &compile_ok); + if (!compile_ok) { + cout << "Error in fragment shader" << endl; + GLint maxLength = 0; + glGetShaderiv(fragmentShader, GL_INFO_LOG_LENGTH, &maxLength); + std::vector<GLchar> errorLog(maxLength); + glGetShaderInfoLog(fragmentShader, maxLength, &maxLength, &errorLog[0]); + vector<GLchar>::iterator itC=errorLog.begin(); + for(; itC!=errorLog.end(); ++itC) { + cout<<*itC; + } + cout<<endl; + } + m_groupProgram = glCreateProgram(); + glAttachShader(m_groupProgram, vertexShader); + glAttachShader(m_groupProgram, fragmentShader); + glLinkProgram(m_groupProgram); + glGetProgramiv(m_groupProgram, GL_LINK_STATUS, &link_ok); + if (!link_ok) { + cout << "Error in glLinkProgram" << endl; + } + m_groupWinSizeUniform = glGetUniformLocation(m_groupProgram, "winSize"); + m_groupSizeUniform = glGetUniformLocation(m_groupProgram, "groupSize"); + m_groupPosUniform = glGetUniformLocation(m_groupProgram, "groupPos"); + m_groupMirrorUniform = glGetUniformLocation(m_groupProgram, "mirrored"); + m_groupEditingUniform = glGetUniformLocation(m_groupProgram, "editing"); + m_groupHighlightUniform = glGetUniformLocation(m_groupProgram, "highlight"); + } + + //CURSOR PROGRAM + { + vertexShader = glCreateShader(GL_VERTEX_SHADER); + const char *vs_source = + "#version 110\n" + "attribute vec3 coord;\n" + "uniform vec2 winSize;\n" + "uniform vec2 cursPos;\n" + "uniform float mirrored;\n" + "varying vec2 pixPos;\n" + "void main(void) {\n" + " if(mirrored>0.0) {\n" + " pixPos = vec2(coord.x, coord.y);\n" + " }\n" + " else {\n" + " pixPos = vec2(coord.x, 1.0-coord.y);\n" + " }\n" + " vec2 pos = ((pixPos*vec2(10,10)+cursPos)/winSize)*2.0-1.0;\n" + " if(mirrored>0.0) {\n" + " gl_Position = vec4(pos.x, pos.y, 0.0, 1.0);\n" + " }\n" + " else {\n" + " gl_Position = vec4(pos.x, -pos.y, 0.0, 1.0);\n" + " }\n" + "}\n"; + glShaderSource(vertexShader, 1, &vs_source, NULL); + glCompileShader(vertexShader); + glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &compile_ok); + if (!compile_ok) { + cout << "Error in cursor vertex shader" << endl; + } + + fragmentShader = glCreateShader(GL_FRAGMENT_SHADER); + const char *fs_source = + "#version 110\n" + "uniform vec2 winSize;\n" + "uniform vec2 cursPos;\n" + "uniform float mirrored;\n" + "varying vec2 pixPos;\n" + "void main(void) {\n" + " if(pixPos.x<0.1 || pixPos.x>0.9 \n" + " || pixPos.y<0.1 || pixPos.y>0.9) { \n" + " gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);\n" + " }\n" + " else {\n" + " gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0);\n" + " }\n" + "}\n"; + glShaderSource(fragmentShader, 1, &fs_source, NULL); + glCompileShader(fragmentShader); + glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &compile_ok); + if (!compile_ok) { + cout << "Error in cursor fragment shader" << endl; + } + m_cursorProgram = glCreateProgram(); + glAttachShader(m_cursorProgram, vertexShader); + glAttachShader(m_cursorProgram, fragmentShader); + glLinkProgram(m_cursorProgram); + glGetProgramiv(m_cursorProgram, GL_LINK_STATUS, &link_ok); + if (!link_ok) { + cout << "Error in cursor glLinkProgram" << endl; + } + m_cursorWinSizeUniform = glGetUniformLocation(m_cursorProgram, "winSize"); + m_cursorPosUniform = glGetUniformLocation(m_cursorProgram, "cursPos"); + m_cursorMirrorUniform = glGetUniformLocation(m_cursorProgram, "mirrored"); + } + + + //DRAW PROGRAM + { + vertexShader = glCreateShader(GL_VERTEX_SHADER); + const char *vs_source = + "#version 110\n" + "attribute vec3 coord;\n" + "uniform vec2 winSize;\n" + "uniform vec2 drawPos;\n" + "uniform vec2 drawSize;\n" + "uniform float mirrored;\n" + "void main(void) {\n" + " vec2 pixPos;\n" + " if(mirrored>0.0) {\n" + " pixPos = vec2(coord.x, coord.y);\n" + " }\n" + " else {\n" + " pixPos = vec2(coord.x, 1.0-coord.y);\n" + " }\n" + " vec2 pos = ((pixPos*drawSize+drawPos)/winSize)*2.0-1.0;\n" + " if(mirrored>0.0) {\n" + " gl_Position = vec4(pos.x, pos.y, 0.0, 1.0);\n" + " }\n" + " else {\n" + " gl_Position = vec4(pos.x, -pos.y, 0.0, 1.0);\n" + " }\n" + "}\n"; + glShaderSource(vertexShader, 1, &vs_source, NULL); + glCompileShader(vertexShader); + glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &compile_ok); + if (!compile_ok) { + cout << "Error in draw vertex shader" << endl; + } + + fragmentShader = glCreateShader(GL_FRAGMENT_SHADER); + const char *fs_source = + "#version 110\n" + "uniform vec2 winSize;\n" + "uniform vec2 cursPos;\n" + "uniform float mirrored;\n" + "uniform float drawingGroup;\n" + "void main(void) {\n" + " gl_FragColor = vec4(1.0-drawingGroup, drawingGroup, 0.0, 1.0);\n" + "}\n"; + glShaderSource(fragmentShader, 1, &fs_source, NULL); + glCompileShader(fragmentShader); + glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &compile_ok); + if (!compile_ok) { + cout << "Error in draw fragment shader" << endl; + } + m_drawingProgram = glCreateProgram(); + glAttachShader(m_drawingProgram, vertexShader); + glAttachShader(m_drawingProgram, fragmentShader); + glLinkProgram(m_drawingProgram); + glGetProgramiv(m_drawingProgram, GL_LINK_STATUS, &link_ok); + if (!link_ok) { + cout << "Error in cursor glLinkProgram" << endl; + } + m_drawWinSizeUniform = glGetUniformLocation(m_drawingProgram, "winSize"); + m_drawPosUniform = glGetUniformLocation(m_drawingProgram, "drawPos"); + m_drawSizeUniform = glGetUniformLocation(m_drawingProgram, "drawSize"); + m_drawMirrorUniform = glGetUniformLocation(m_drawingProgram, "mirrored"); + m_drawGroupUniform = glGetUniformLocation(m_drawingProgram, "drawingGroup"); + } + + //MENU PROGRAM + { + vertexShader = glCreateShader(GL_VERTEX_SHADER); + const char *vs_source = + "#version 110\n" + "attribute vec3 coord;\n" + "uniform vec2 winSize;\n" + "uniform vec2 menuPos;\n" + "uniform vec2 menuSize;\n" + "uniform float mirrored;\n" + "varying vec2 pixPos;\n" + "void main(void) {\n" + " if(mirrored>0.0) {\n" + " pixPos = vec2(coord.x, coord.y);\n" + " }\n" + " else {\n" + " pixPos = vec2(coord.x, 1.0-coord.y);\n" + " }\n" + " vec2 pos = ((pixPos*menuSize+menuPos)/winSize)*2.0-1.0;\n" + " if(mirrored>0.0) {\n" + " gl_Position = vec4(pos.x, pos.y, 0.0, 1.0);\n" + " }\n" + " else {\n" + " gl_Position = vec4(pos.x, -pos.y, 0.0, 1.0);\n" + " }\n" + "}\n"; + glShaderSource(vertexShader, 1, &vs_source, NULL); + glCompileShader(vertexShader); + glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &compile_ok); + if (!compile_ok) { + cout << "Error in menu vertex shader" << endl; + } + + fragmentShader = glCreateShader(GL_FRAGMENT_SHADER); + const char *fs_source = + "#version 110\n" + "varying vec2 pixPos;\n" + "uniform float mirrored;\n" + "uniform sampler2D menuTexture;\n" + "void main(void) {\n" + " gl_FragColor = texture2D(menuTexture, pixPos);\n" + "}\n"; + glShaderSource(fragmentShader, 1, &fs_source, NULL); + glCompileShader(fragmentShader); + glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &compile_ok); + if (!compile_ok) { + cout << "Error in menu fragment shader" << endl; + } + m_menuProgram = glCreateProgram(); + glAttachShader(m_menuProgram, vertexShader); + glAttachShader(m_menuProgram, fragmentShader); + glLinkProgram(m_menuProgram); + glGetProgramiv(m_menuProgram, GL_LINK_STATUS, &link_ok); + if (!link_ok) { + cout << "Error in cursor glLinkProgram" << endl; + } + m_menuWinSizeUniform = glGetUniformLocation(m_menuProgram, "winSize"); + m_menuPosUniform = glGetUniformLocation(m_menuProgram, "menuPos"); + m_menuSizeUniform = glGetUniformLocation(m_menuProgram, "menuSize"); + m_menuMirrorUniform = glGetUniformLocation(m_menuProgram, "mirrored"); + m_menuTextureUniform = glGetUniformLocation(m_menuProgram, "menuTexture"); + } + glActiveTexture(GL_TEXTURE0); + glGenTextures(1, &m_firstTextureID); + glBindTexture(GL_TEXTURE_2D, m_firstTextureID); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); +} +#endif + +void Controlar::draw() { + + + +/* + pthread_mutex_lock(&m_zonesMutex); + if(m_askedScene>-1) { + setScene(m_askedScene); + m_askedScene=-1; + } + pthread_mutex_unlock(&m_zonesMutex); +*/ +#ifdef GL + if(!valid()) { + initGL(); + } + + glViewport(0, 0, w(), h()); + glClearColor(0, 0, 0, 1); + glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); + glEnable (GL_BLEND); + glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + glEnableVertexAttribArray(0); + glBindBuffer(GL_ARRAY_BUFFER, m_vertexBuffer); + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, (void*)0); + + //reset each window + vector<CutWindow*>::iterator itWin = m_winsMan->editWindowList().begin(); + for(; itWin!=m_winsMan->editWindowList().end(); ++itWin) { + (*itWin)->releaseImage(); + } + + //draw groups + glUseProgram(m_groupProgram); + glUniform1f(m_groupMirrorUniform, m_flipped); + glUniform2f(m_groupWinSizeUniform, w(), h()); + map<unsigned int, GroupWidget*>::iterator itGr = m_groups.begin(); + for(; itGr!=m_groups.end(); ++itGr) { + itGr->second->draw(); + } + + //draw zones + m_currentWindowName=""; + glUseProgram(m_zoneProgram); + glUniform1f(m_zoneMirrorUniform, m_flipped); + glUniform2f(m_zoneWinSizeUniform, w(), h()); + glUniform1i(m_zoneWindowUniform, 0); + glUniform1i(m_zoneCoordsUniform, 1); + glUniform1f(m_zoneInnerUniform, ZoneWidget::INNER); + glUniform1f(m_zoneOuterUniform, ZoneWidget::OUTER); + map<unsigned int, ZoneWidget*>::iterator itZo = m_zones.begin(); + for(; itZo!=m_zones.end(); ++itZo) { + itZo->second->drawZone(m_currentScene); + } + + //draw menus + glUseProgram(m_menuProgram); + glUniform1f(m_menuMirrorUniform, m_flipped); + glUniform2f(m_menuWinSizeUniform, w(), h()); + glUniform1i(m_menuTextureUniform, 0); + if(m_mainMenu->visible()) { + m_mainMenu->draw(); + } + else if(m_zoneMenu->visible()) { + m_zoneMenu->draw(); + } + if(m_mainPanel->visible()) { + m_mainPanel->draw(); + } + if(m_zonePanel->visible()) { + m_zonePanel->draw(); + } + //draw drawing + if(m_drawing) { + glUseProgram(m_drawingProgram); + glUniform1f(m_drawMirrorUniform, m_flipped); + glUniform2f(m_drawWinSizeUniform, w(), h()); + glUniform2f(m_drawSizeUniform, m_drawCurX-m_drawStartX, + m_drawCurY-m_drawStartY); + glUniform2f(m_drawPosUniform, m_drawStartX, + m_drawStartY); + glUniform1f(m_drawGroupUniform, m_drawingGroup); + glDrawArrays(GL_TRIANGLES, 0, 6); + } + +#ifndef LINUX + //draw cursor + glUseProgram(m_cursorProgram); + glUniform1f(m_cursorMirrorUniform, m_flipped); + glUniform2f(m_cursorWinSizeUniform, w(), h()); + glUniform2f(m_cursorPosUniform, Fl::event_x_root()-x(), + Fl::event_y_root()-y()); + glDrawArrays(GL_TRIANGLES, 3, 3); +#endif + + glDisableVertexAttribArray(0); + +#else + //redraw whole background only when needed + if(damage()&FL_DAMAGE_ALL) { + fl_draw_box(box(), 0, 0, w(), h(), FL_BLACK); + map<unsigned int, ZoneWidget*>::iterator itZo + = m_zonesSets[m_currentSet].begin(); + for(; itZo!=m_zonesSets[m_currentSet].end(); ++itZo) { + itZo->second->setRateReached(); + } + } + else { //redraw only dynamic zones background and mask prev curs + fl_rect(m_cursPosX-5, + m_cursPosY-5, + 10, 10, FL_BLACK); + map<unsigned int, ZoneWidget*>::iterator itZo + = m_zonesSets[m_currentSet].begin(); + for(; itZo!=m_zonesSets[m_currentSet].end(); ++itZo) { + if(itZo->second->getRate()==ZoneWidget::DYNAMIC) { + ZoneWidget* wid = itZo->second; + fl_rectf(wid->x(), + m_flipped?h()-wid->y()-wid->h():wid->y(), + wid->w(), + wid->h(), + FL_BLACK); + } + } + } + + //reset windows + vector<CutWindow*>::iterator itWin = m_winsMan->editWindowList().begin(); + for(; itWin!=m_winsMan->editWindowList().end(); ++itWin) { + (*itWin)->releaseImage(); + } + + //draw groups + map<unsigned int, GroupWidget*>::iterator itGr = m_groups.begin(); + for(; itGr!=m_groups.end(); ++itGr) { + itGr->second->draw(); + } + + + //draw zones + map<unsigned int, ZoneWidget*>::iterator itZo + = m_zones.begin(); + for(; itZo!=m_zones.end(); ++itZo) { + itZo->second->draw(); + } + + //draw menus + if(m_mainMenu->visible()) { + m_mainMenu->draw(); + } + else if(m_zoneMenu->visible()) { + m_zoneMenu->draw(); + } + if(m_mainBar->visible()) { + m_mainBar->draw(); + } + if(m_zoneBar->visible()) { + m_zoneBar->draw(); + } + //draw zone creation rectangles + if(m_drawing) { + fl_rectf(m_drawStartX, + m_flipped?h()-m_drawStartY-(m_drawCurY-m_drawStartY):m_drawStartY, + m_drawCurX-m_drawStartX, + m_drawCurY-m_drawStartY, + m_drawingGroup?FL_GREEN:FL_RED); + } + + //draw cursor replacement + m_cursPosX = Fl::event_x_root()-x(); + m_cursPosY=m_flipped?h()-(Fl::event_y_root()-y()):Fl::event_y_root()-y(); + fl_rectf(m_cursPosX-5, + m_cursPosY-5, + 10, 10, FL_BLACK); + fl_rect(m_cursPosX-5, + m_cursPosY-5, + 10, 10, FL_WHITE); +#endif + +} + +void Controlar::resize(int x, int y, int w, int h) { + #ifdef GL + Fl_Gl_Window::resize(x,y,w,h); + #else + Fl_Double_Window::resize(x,y,w,h); + #endif +} + +CutWindow* Controlar::getWindow(const string& name) { + return m_winsMan->getWindow(name); +} + +CutWindow* Controlar::getWindow(const int& win) { + return m_winsMan->editWindowList()[win]; +} + +void Controlar::cbOpen() { + Fl_Native_File_Chooser fnfc; + fnfc.title("Open ControlAR File"); + fnfc.type(Fl_Native_File_Chooser::BROWSE_FILE); + if(fnfc.show()==0) { + load(fnfc.filename()); + } + m_mainMenu->hide(); +} + + +void Controlar::cbSaveAs() { + Fl_Native_File_Chooser fnfc; + fnfc.title("Save ControlAR File"); + fnfc.type(Fl_Native_File_Chooser::BROWSE_SAVE_FILE); + if(fnfc.show()==0) { + save(fnfc.filename()); + } + m_mainMenu->hide(); +} + +void Controlar::cbSave() { + if(m_fileName.compare("")!=0) { + save(m_fileName); + } + else { + cbSaveAs(); + } + m_mainMenu->hide(); +} + +void Controlar::load(const std::string& fileName) { + + DEBUG("Opening "<<fileName); + bool open=true; + xmlDocPtr doc = xmlReadFile(fileName.c_str(), NULL, 0); + xmlNodePtr rootNode = xmlDocGetRootElement(doc); + if(doc==NULL || rootNode==NULL) { + DEBUG("Could not open file "<<fileName); + open=false; + } + else if(xmlStrcmp(rootNode->name, (const xmlChar*)"ControllAR")) { + xmlFreeDoc(doc); + DEBUG("Could not open file "<<fileName); + open=false; + } + if(open) { //if the file is valid + m_fileName=fileName; + + //delete all widgets + map<unsigned int, ZoneWidget*>::iterator itZo = m_zones.begin(); + for(; itZo!=m_zones.end(); ++itZo) { + Fl::delete_widget(itZo->second); + } + m_zones.clear(); + m_scenes.clear(); + map<unsigned int, GroupWidget*>::iterator itGr = m_groups.begin(); + for(; itGr!=m_groups.end(); ++itGr) { + Fl::delete_widget(itGr->second); + } + m_groups.clear(); + + m_winsMan->updateWindowsList(); + + //load/create all zone sets and retrieve midi parameters + m_currentScene=0; + xmlNodePtr sNode; + for(sNode=rootNode->children; sNode; sNode=sNode->next) { + if(sNode->type == XML_ELEMENT_NODE) { + if(!xmlStrcmp(sNode->name, (const xmlChar *)"Group")) { + GroupWidget* wid = new GroupWidget(); + addGroup(wid); + wid->load(sNode); + } + else if(!xmlStrcmp(sNode->name, (const xmlChar *)"Scene")) { + string sceneStr; + char* value = NULL; + value = (char*)xmlGetProp(sNode, (xmlChar*)"name"); + if(value!=NULL) { + sceneStr=string(value); + } + m_scenes[m_currentScene]=sceneStr; + m_currentScene++; + } + else if (!xmlStrcmp(sNode->name, (const xmlChar *)"Zone")) { + ZoneWidget* wid = new ZoneWidget(); + addZone(wid); + wid->load(sNode); + } + else if(!xmlStrcmp(sNode->name, (const xmlChar *)"Midi")) { + char* value = NULL; + value = (char*)xmlGetProp(sNode,(xmlChar*)"device"); + if(value!=NULL) { + setMidiDeviceFromName(string(value)); + } + value = (char*)xmlGetProp(sNode,(xmlChar*)"set_channel"); + if(value!=NULL) { + m_midiChannel=atoi(value); + } + value = (char*)xmlGetProp(sNode,(xmlChar*)"set_type"); + if(value!=NULL) { + m_midiType=atoi(value); + } + value = (char*)xmlGetProp(sNode,(xmlChar*)"set_control"); + if(value!=NULL) { + m_midiControl=atoi(value); + } + } + } + } + + //set current scene to 0 + m_currentScene=0; + setScene(0); + DEBUG("Opened file "<<fileName); + } + updateTitle(); + m_mainPanel->hide(); +} + +void Controlar::save(const std::string& fileName) { + DEBUG("Saving to "<<fileName); + xmlDocPtr doc = xmlNewDoc(BAD_CAST "1.0"); + xmlNodePtr rootNode = xmlNewNode(NULL, BAD_CAST "ControllAR"); + + //save groups + map<unsigned int, GroupWidget*>::iterator itGr = m_groups.begin(); + for(; itGr!=m_groups.end(); ++itGr) { + itGr->second->save(rootNode); + } + + //save scenes + map<int, string>::iterator itSc = m_scenes.begin(); + for(; itSc!=m_scenes.end(); ++itSc) { + xmlNewChild(rootNode, NULL, BAD_CAST "Scene", NULL); + } + + //save zone + map<unsigned int, ZoneWidget*>::iterator itZo = m_zones.begin(); + for(; itZo!=m_zones.end(); ++itZo) { + itZo->second->save(rootNode); + } + + //save midi + xmlNodePtr setNode = xmlNewChild(rootNode, NULL, + BAD_CAST "Midi", NULL); + xmlNewProp(setNode, BAD_CAST "device", + BAD_CAST m_currentMidiDeviceName.c_str()); + ostringstream oss1, oss2, oss3; + oss1<<m_midiChannel; + xmlNewProp(setNode, BAD_CAST "set_channel", + BAD_CAST oss1.str().c_str()); + oss2<<m_midiType; + xmlNewProp(setNode, BAD_CAST "set_type", + BAD_CAST oss2.str().c_str()); + oss3<<m_midiControl; + xmlNewProp(setNode, BAD_CAST "set_control", + BAD_CAST oss3.str().c_str()); + + + xmlDocSetRootElement(doc, rootNode); + xmlSaveFormatFileEnc(fileName.c_str(), doc, "UTF-8", 1); + xmlFreeDoc(doc); + xmlCleanupParser(); + m_fileName=fileName; + updateTitle(); + m_mainPanel->hide(); +} + +void Controlar::addZone(ZoneWidget* zone) { + unsigned int newID=0; + while(m_zones.find(newID)!=m_zones.end()) { + newID++; + } + zone->setID(newID); + m_zones[newID] = zone; + zone->callback(statZone, this); + add(zone); + m_zoneMenu->hide(); +} + +void Controlar::addGroup(GroupWidget* group) { + unsigned int newID=0; + while(m_groups.find(newID)!=m_groups.end()) { + newID++; + } + group->setID(newID); + m_groups[newID] = group; + add(group); +} + +void Controlar::moveAllZonesInsideGroupBy(GroupWidget* group, int dx, int dy) { + map<unsigned int, ZoneWidget*>::iterator itZo = m_zones.begin(); + for(; itZo!=m_zones.end(); ++itZo) { + if(itZo->second->x()>group->x() + && itZo->second->x()<group->x()+group->w() + && itZo->second->y()>group->y() + && itZo->second->y()<group->y()+group->h()) { + itZo->second->position(itZo->second->x()+dx, + itZo->second->y()+dy); + } + } +} + +void Controlar::cbZoneSelect(CutWindow* win) { + m_clickedZone->setCutWin(win); + m_zoneMenu->hide(); +} + +void Controlar::cbZoneClear() { + m_clickedZone->setCutWin(NULL); + m_zoneMenu->hide(); +} + +void Controlar::cbZoneDelete() { + m_zones.erase(m_clickedZone->getID()); + Fl::delete_widget(m_clickedZone); + m_zoneMenu->hide(); + m_zonePanel->hide(); +} + +void Controlar::cbZoneMove() { + +} + +void Controlar::cbZoneAlpha(const int& alpha) { + m_clickedZone->setAlpha(alpha); + m_zoneMenu->hide(); +} + +void Controlar::cbZoneTrans(const ZoneWidget::ZONE_TRANSFO& trans) { + m_clickedZone->setTransformation(trans); + m_zoneMenu->hide(); +} + +void Controlar::cbZoneShape(const ZoneWidget::ZONE_SHAPE& shape) { + m_clickedZone->setShape(shape); + m_zoneMenu->hide(); +} + +void Controlar::cbZoneMidiLearn() { + m_clickedZone->learnMidi(); + m_zoneMenu->hide(); +} + +void Controlar::cbZoneMidiEffect(const ZoneWidget::ZONE_INPUT_EFFECT& effect) { + m_clickedZone->setInputEffect(effect); + m_zoneMenu->hide(); +} + +void Controlar::cbZoneCol(const ZoneWidget::ZONE_COLOR& col) { + m_clickedZone->setColor(col); + m_zoneMenu->hide(); +} + +void Controlar::cbZoneVisible(const ZoneWidget::ZONE_VISIBLE& vis) { + if(vis==ZoneWidget::VISIBLE_CURRENT) { + m_clickedZone->setVisible(m_currentScene); + } + else if(vis==ZoneWidget::VISIBLE_ALL) { + m_clickedZone->setVisible(); + } + m_zoneMenu->hide(); +} + +void Controlar::cbZoneUpdate(const ZoneWidget::ZONE_UPDATE& up) { + if(up==ZoneWidget::UPDATE_CURRENT) { + m_clickedZone->setUpdate(m_currentScene); + } + else if(up==ZoneWidget::UPDATE_ALL) { + m_clickedZone->setUpdate(); + } + m_zoneMenu->hide(); +} + +void Controlar::cbZoneContent(const ZoneWidget::ZONE_CONTENT& cont) { + m_clickedZone->setContent(cont); + m_zoneMenu->hide(); +} + +void Controlar::init() { + this->show(); +} + +Controlar::~Controlar() {} + +Controlar* Controlar::getInstance() { + static Controlar instance; + return &instance; +} + +void Controlar::cbQuit() { + remove(m_zonePanel); + remove(m_mainPanel); + exit(0); +} + +void Controlar::cbMidiDev(const int& dev) { + if(m_midiStream!=NULL) { + Pm_Close(m_midiStream); + } + Pm_OpenInput(&m_midiStream, dev, NULL, 100, NULL, NULL); + const PmDeviceInfo* info = Pm_GetDeviceInfo(dev); + m_currentMidiDeviceName = string(info->name); + m_mainMenu->hide(); + m_zoneMenu->hide(); +} + +void Controlar::setMidiDeviceFromName(const string& name) { + for(unsigned int d=0; d<m_midiDeviceNames.size(); ++d) { + if(m_midiDeviceNames[d].compare(name)==0) { + cbMidiDev(m_midiDevices[d]); + } + } +} + +void DetectionWindow::draw() { + #ifdef OSX + CGRect rect = CGRectMake(0,0,100,100); + CGRect deviceRect = CGContextConvertRectToDeviceSpace(fl_gc, rect); + Controlar::getInstance()->setDisplayScale(deviceRect.size.width + / rect.size.width); + #endif + Fl::delete_widget(this); +} + + +int main(int argc, char* argv[]) { + #ifndef GL + Fl::visual(FL_DOUBLE|FL_RGB); + #endif + + //first grab the display scale + DetectionWindow* detWin = new DetectionWindow(); + detWin->show(); + + //then init controllar + Controlar::getInstance()->init(); + fl_cursor(FL_CURSOR_NONE); + return Fl::run(); +} + + diff --git a/src/Controlar.hpp b/src/Controlar.hpp new file mode 100755 index 0000000..13ed121 --- /dev/null +++ b/src/Controlar.hpp @@ -0,0 +1,372 @@ +/*************************************************************************** + * Controlar.hpp + * Part of ControllAR + * 2016- Florent Berthaut + * hitmuri.net + ****************************************************************************/ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + + +#ifndef Controlar_h +#define Controlar_h + +#include <string> +#include <map> +#include <vector> +#include <sys/time.h> + +#ifdef GL +#include <FL/Fl_Gl_Window.H> +#else +#include <FL/Fl_Double_Window.H> +#endif + +#include <FL/Fl_Tile.H> +#include <FL/Fl_Pack.H> +#include <FL/Fl_Scroll.H> +#include <FL/Fl_Hold_Browser.H> +#include <FL/Fl_Menu_Button.H> +#include <FL/Fl_Toggle_Button.H> +#include <FL/Fl_Native_File_Chooser.H> +#include <FL/Fl.H> +#include <FL/fl_draw.H> +#include <FL/Fl_Overlay_Window.H> + +#include <portmidi.h> + +#include "osc/ip/IpEndpointName.h" +#include "osc/ip/UdpSocket.h" +#include "osc/osc/OscPacketListener.h" + +#include "FlipMenu.hpp" +#include "ZoneWidget.hpp" +#include "GroupWidget.hpp" +#include "CutWindowsManager.hpp" +class MainPanel; +class ZonePanel; + +#define IP_MTU_SIZE 2048 + +#ifdef GL +class Controlar: public Fl_Gl_Window, public osc::OscPacketListener { +#else +class Controlar: public Fl_Double_Window, public osc::OscPacketListener { +#endif + public: + static Controlar* getInstance(); + ~Controlar(); + void init(); + void load(const std::string& fileName); + void save(const std::string& fileName); + + void update(); + int handle(int event); + void draw(); + void resize(int x, int y, int w, int h); + CutWindow* getWindow(const std::string& name); + inline int getNbWindows() {return m_winsMan->getNbWindows();} + CutWindow* getWindow(const int&); + void addZone(ZoneWidget*); + void addGroup(GroupWidget*); + void moveAllZonesInsideGroupBy(GroupWidget*, int dx, int dy); + void refreshWindowList(); + + //ZONE CALLBACKS + static void statZone(Fl_Widget* w, void* f){ + Controlar *tmpf = static_cast<Controlar *>(f); + tmpf->cbZone(w); + } + void cbZone(Fl_Widget*); + + static void statZoneSelect(Fl_Widget* w, void* f){ + Controlar::getInstance()->cbZoneSelect(static_cast<CutWindow*>(f)); + } + void cbZoneSelect(CutWindow*); + static void statZoneClear(Fl_Widget* w, void* f){ + Controlar::getInstance()->cbZoneClear(); + } + void cbZoneClear(); + static void statZoneDelete(Fl_Widget* w, void* f){ + Controlar::getInstance()->cbZoneDelete(); + } + void cbZoneDelete(); + static void statZoneMove(Fl_Widget* w, void* f){ + Controlar::getInstance()->cbZoneMove(); + } + void cbZoneMove(); + static void statZoneAlpha(Fl_Widget* w, void* f){ + Controlar::getInstance()->cbZoneAlpha(*((int*)f)); + } + void cbZoneAlpha(const int& alpha); + static void statZoneShape(Fl_Widget* w, void* f){ + Controlar::getInstance() + ->cbZoneShape(*((ZoneWidget::ZONE_SHAPE*)f)); + } + void cbZoneShape(const ZoneWidget::ZONE_SHAPE& shape); + static void statZoneTrans(Fl_Widget* w, void* f){ + Controlar::getInstance() + ->cbZoneTrans(*((ZoneWidget::ZONE_TRANSFO*)f)); + } + void cbZoneTrans(const ZoneWidget::ZONE_TRANSFO& trans); + static void statZoneMidiLearn(Fl_Widget* w, void* f){ + Controlar::getInstance()->cbZoneMidiLearn(); + } + void cbZoneMidiLearn(); + static void statZoneMidiEffect(Fl_Widget* w, void* f){ + Controlar::getInstance() + ->cbZoneMidiEffect(*((ZoneWidget::ZONE_INPUT_EFFECT*)f)); + } + void cbZoneMidiEffect(const ZoneWidget::ZONE_INPUT_EFFECT& trans); + static void statZoneCol(Fl_Widget* w, void* f){ + Controlar::getInstance()->cbZoneCol(*((ZoneWidget::ZONE_COLOR*)f)); + } + void cbZoneCol(const ZoneWidget::ZONE_COLOR& col); + static void statZoneVisible(Fl_Widget* w, void* f){ + Controlar::getInstance() + ->cbZoneVisible(*((ZoneWidget::ZONE_VISIBLE*)f)); + } + void cbZoneVisible(const ZoneWidget::ZONE_VISIBLE& vis); + static void statZoneUpdate(Fl_Widget* w, void* f){ + Controlar::getInstance() + ->cbZoneUpdate(*((ZoneWidget::ZONE_UPDATE*)f)); + } + void cbZoneUpdate(const ZoneWidget::ZONE_UPDATE& up); + static void statZoneContent(Fl_Widget* w, void* f){ + Controlar::getInstance() + ->cbZoneContent(*((ZoneWidget::ZONE_CONTENT*)f)); + } + void cbZoneContent(const ZoneWidget::ZONE_CONTENT& up); + + //MAIN CALLBACKS + static void statOpen(Fl_Widget* w,void* f){ + Controlar *tmpf = static_cast<Controlar *>(f); + tmpf->cbOpen(); + } + void cbOpen(); + + static void statSave(Fl_Widget* w, void* f){ + Controlar::getInstance()->cbSave(); + } + void cbSave(); + static void statSaveAs(Fl_Widget* w, void* f){ + Controlar::getInstance()->cbSaveAs(); + } + void cbSaveAs(); + + static void statSceneNext(Fl_Widget* w, void* f){ + Controlar::getInstance()->cbSceneNext(); + } + void cbSceneNext(); + static void statScenePrev(Fl_Widget* w, void* f){ + Controlar::getInstance()->cbScenePrev(); + } + void cbScenePrev(); +/* + static void statSetDup(Fl_Widget* w,void* f){ + Controlar::getInstance()->cbSetDuplicate(); + } + void cbSetDuplicate(); +*/ + static void statSceneDel(Fl_Widget* w,void* f){ + Controlar::getInstance()->cbSceneDelete(); + } + void cbSceneDelete(); + static void statSceneLearn(Fl_Widget* w,void* f){ + Controlar::getInstance()->cbSceneLearn(); + } + void cbSceneLearn(); + void setScene(const int&); + + static void statFlip(Fl_Widget* w,void* f){ + Controlar::getInstance()->cbFlip(); + } + void cbFlip(); + inline bool isFlipped(){return m_flipped;} + + static void statFullscreen(Fl_Widget* w,void* f){ + Controlar::getInstance()->cbFullscreen(); + } + void cbFullscreen(); + static void statMidiDev(Fl_Widget* w,void* f){ + Controlar::getInstance()->cbMidiDev(*(int*)(f)); + } + void cbMidiDev(const int&); + void setMidiDeviceFromName(const std::string&); + + static void statQuit(Fl_Widget* w,void* f){ + Controlar *tmpf = static_cast<Controlar *>(f); + tmpf->cbQuit(); + } + void cbQuit(); + static void statZoneMenuWin(Fl_Widget* w, void* f){ + static_cast<ZoneWidget*>(w)->setCutWin(static_cast<CutWindow*>(f)); + } + + void parseMidi(); + void openMidiDevice(const std::string& devName); + void closeMidiDevice(const std::string& devName); + void refreshMidiStreams(); + void detectMidiDevices(); + void updateTitle(); + + ZoneWidget* startDuplicating(ZoneWidget* dupWid); + + virtual void ProcessMessage(const osc::ReceivedMessage& m, + const IpEndpointName& remoteEndpoint); + + inline const int& getDisplayScale(){return m_displayScale;} + inline void setDisplayScale(const int& s){m_displayScale=s;} + +#ifdef GL + void initGL(); + inline GLint getZoneSizeUniform(){return m_zoneSizeUniform;} + inline GLint getZonePosUniform(){return m_zonePosUniform;} + inline GLint getCutSizeUniform(){return m_cutSizeUniform;} + inline GLint getCutPosUniform(){return m_cutPosUniform;} + inline GLint getZoneStateUniform(){return m_zoneStateUniform;} + inline GLint getZoneWindowUniform(){return m_zoneWindowUniform;} + inline GLint getZoneCoordsUniform(){return m_zoneCoordsUniform;} + inline GLint getZoneFilledUniform(){return m_zoneFilledUniform;} + inline GLint getZoneAlphaUniform(){return m_zoneAlphaUniform;} + inline GLint getZoneInvertUniform(){return m_zoneInvertUniform;} + inline GLint getGroupSizeUniform(){return m_groupSizeUniform;} + inline GLint getGroupPosUniform(){return m_groupPosUniform;} + inline GLint getGroupEditingUniform(){return m_groupEditingUniform;} + inline GLint getGroupHighlightUniform(){return m_groupHighlightUniform;} + inline GLint getMenuSizeUniform(){return m_menuSizeUniform;} + inline GLint getMenuPosUniform(){return m_menuPosUniform;} + inline GLuint getFirstTextureID(){return m_firstTextureID;} + inline GLuint getSecondTextureID(){return m_secondTextureID;} + inline const std::string& getCurrentWindowName() { + return m_currentWindowName; + } + inline void setCurrentWindowName(const std::string& n) { + m_currentWindowName=n; + } +#endif + + protected: + friend void* oscThreadFunction(void*); + + protected: + UdpListeningReceiveSocket* m_socket; + + private: + Controlar(); + std::string m_title; + std::string m_fileName; + int m_drawStartX, m_drawStartY; + int m_drawCurX, m_drawCurY; + bool m_drawing, m_drawingGroup; + FlipMenu* m_zoneMenu; + FlipMenu* m_mainMenu; + MainPanel* m_mainPanel; + ZonePanel* m_zonePanel; + CutWindowsManager* m_winsMan; + //std::vector<std::map<unsigned int, ZoneWidget*> > m_zonesSets; + std::map<int, std::string> m_scenes; + std::map<unsigned int, ZoneWidget*> m_zones; + std::map<unsigned int, GroupWidget*> m_groups; + int m_alphaLevels[4]; + int m_menuValues[10]; + //unsigned int m_currentSet; + int m_currentScene; + ZoneWidget* m_clickedZone; + bool m_flipped; + bool m_fullscreen; + bool m_duplicating; + ZoneWidget* m_duplicatedZone; + PortMidiStream* m_midiStream; + std::map<std::string, int> m_midiDevMap; + std::map<std::string, PortMidiStream*> m_midiStreamsMap; + std::vector<PortMidiStream*> m_midiStreamsVec; + bool m_midiLearning; + int m_midiType; + int m_midiChannel; + int m_midiControl; + std::vector<int> m_midiDevices; + std::vector<std::string> m_midiDeviceNames; + std::string m_currentMidiDeviceName; + pthread_t m_oscThread; + pthread_mutex_t m_zonesMutex; + int m_askedScene; + int m_displayScale; + +#ifdef GL + GLuint m_vertexArrayID; + GLuint m_vertexBuffer; + GLfloat m_vertexBufferData[18]; + + GLuint m_zoneProgram; + GLint m_zoneWinSizeUniform; + GLint m_zoneSizeUniform; + GLint m_zonePosUniform; + GLint m_cutSizeUniform; + GLint m_cutPosUniform; + GLint m_zoneMirrorUniform; + GLint m_zoneStateUniform; + GLint m_zoneFilledUniform; + GLint m_zoneAlphaUniform; + GLint m_zoneInvertUniform; + GLint m_zoneWindowUniform; + GLint m_zoneCoordsUniform; + GLint m_zoneOuterUniform; + GLint m_zoneInnerUniform; + + GLuint m_cursorProgram; + GLint m_cursorWinSizeUniform; + GLint m_cursorPosUniform; + GLint m_cursorMirrorUniform; + + GLuint m_drawingProgram; + GLint m_drawWinSizeUniform; + GLint m_drawPosUniform; + GLint m_drawSizeUniform; + GLint m_drawMirrorUniform; + GLint m_drawGroupUniform; + + GLuint m_menuProgram; + GLint m_menuWinSizeUniform; + GLint m_menuPosUniform; + GLint m_menuSizeUniform; + GLint m_menuMirrorUniform; + GLint m_menuTextureUniform; + GLuint m_firstTextureID; + GLuint m_secondTextureID; + std::string m_currentWindowName; + + GLuint m_groupProgram; + GLint m_groupWinSizeUniform; + GLint m_groupPosUniform; + GLint m_groupSizeUniform; + GLint m_groupMirrorUniform; + GLint m_groupEditingUniform; + GLint m_groupHighlightUniform; +#else + int m_cursPosX; + int m_cursPosY; +#endif +}; + +class DetectionWindow: public Fl_Double_Window { + public: + DetectionWindow():Fl_Double_Window(10, 10, ""){} + void draw(); +}; + +#endif + diff --git a/src/CutWindow.hpp b/src/CutWindow.hpp new file mode 100644 index 0000000..3fdffd3 --- /dev/null +++ b/src/CutWindow.hpp @@ -0,0 +1,86 @@ +/*************************************************************************** + * CutWindow.hpp + * Part of Controlar + * 2016- Florent Berthaut + * hitmuri.net + ****************************************************************************/ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + + +#ifndef CutWindow_h +#define CutWindow_h + +#include <vector> +#include <map> +#include <string> + +#include <FL/Fl.H> +#include <FL/Fl_RGB_Image.H> + +class CutWindowsManager; +#include "ZoneWidget.hpp" + +class CutWindow { + public : + CutWindow(CutWindowsManager* man):m_winsMan(man){} + virtual ~CutWindow(){} + + void setName(const std::string& name){m_name=name;} + const std::string& getName(){return m_name;} + virtual void getPixels(const int& x, const int& y, + const int& sx, const int& sy, + const std::vector<int>& srcIndices, + const std::vector<std::vector<int> >& srcCoords, + const std::vector<int>& destIndices, + const uchar& alpha, + const ZoneWidget::ZONE_COLOR& color, + uchar* destImg)=0; + + virtual int computeNbPixPerRow(const int& srcW, const int& srcH)=0; + inline const int& getWidth(){return m_width;} + inline const int& getHeight(){return m_height;} + inline const int& getOffsetX(){return m_offsetX;} + inline const int& getOffsetY(){return m_offsetY;} + inline const int& getPixPerRow(){return m_pixPerRow;} + inline bool isBGR(){return m_isBGR;} + inline bool needsOffset(){return m_needsOffset;} + inline void setWinID(const int& id){m_winID=id;} + inline const int& getWinID(){return m_winID;} + + virtual uchar* grabImage()=0; + virtual void releaseImage()=0; + + virtual void storeImage(const int& id)=0; + virtual uchar* retrieveImage(const int& id)=0; + + protected: + int m_winID; + std::string m_name; + int m_posX, m_posY, m_width, m_height; + int m_offsetX, m_offsetY, m_pixPerRow; + bool m_isBGR; + bool m_needsOffset; + CutWindowsManager* m_winsMan; + bool m_grabbed; + uchar* m_imgData; + uchar* m_defaultImgData; + + std::map<int, uchar*> m_storedImgData; +}; + +#endif + diff --git a/src/CutWindowsManager.hpp b/src/CutWindowsManager.hpp new file mode 100644 index 0000000..6d44f66 --- /dev/null +++ b/src/CutWindowsManager.hpp @@ -0,0 +1,58 @@ +/*************************************************************************** + * CutWindowsManager.hpp + * Part of Over + * 2013 Florent Berthaut + * hitmuri.net + ****************************************************************************/ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + + +#ifndef CutWindowsManager_h +#define CutWindowsManager_h + +#include <vector> +#include <string> +#include <iostream> + +#include "CutWindow.hpp" + +class CutWindowsManager { + public : + CutWindowsManager(){} + virtual ~CutWindowsManager(){} + + virtual void updateWindowsList()=0; + std::vector<CutWindow*>& editWindowList(){return m_windowList;} + CutWindow* getWindow(const std::string& name) { + CutWindow* res=NULL; + std::vector<CutWindow*>::iterator itWin=m_windowList.begin(); + for(; itWin!=m_windowList.end(); ++itWin) { + if((*itWin)->getName().compare(name)==0 && name.compare("")!=0){ + res=(*itWin); + } + } + return res; + } + int getNbWindows(){return m_windowList.size();} + + protected: + std::vector<CutWindow*> m_windowList; + std::map<unsigned int, CutWindow*> m_windowMap; +}; + +#endif + diff --git a/src/FlipGroup.cpp b/src/FlipGroup.cpp new file mode 100644 index 0000000..5a06987 --- /dev/null +++ b/src/FlipGroup.cpp @@ -0,0 +1,96 @@ +/*************************************************************************** + * FlipGroup.cpp + * Part of + * 2016- Florent Berthaut + * hitmuri.net + ****************************************************************************/ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "FlipGroup.hpp" +#include <FL/Fl_Button.H> +#include <FL/fl_draw.H> +#include <algorithm> +#include <iostream> +#include "Controlar.hpp" + +using namespace std; + +FlipGroup::FlipGroup(int x, int y, int w, int h): Fl_Group(x, y, w, h, "") { + end(); + box(FL_FLAT_BOX); + m_wasChanged=true; +} + +void FlipGroup::draw() { + Controlar* cont = Controlar::getInstance(); + if(m_wasChanged) { + Fl_Image_Surface* surf = new Fl_Image_Surface(w(), h()); + surf->set_current(); + fl_draw_box(box(), 0, 0, + w(), h(), color()); + for(int c=0; c<children(); ++c) { + surf->draw(child(c), + child(c)->x()-x(), + child(c)->y()-y()); + } + m_flipImg = surf->image(); + delete surf; + Fl_Display_Device::display_device()->set_current(); + m_wasChanged=false; + } +#ifdef GL + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, cont->getFirstTextureID()); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, + w(), h(), + 0, GL_RGB, GL_UNSIGNED_BYTE, + m_flipImg->array); + glUniform2f(cont->getMenuPosUniform(), x(), y()); + glUniform2f(cont->getMenuSizeUniform(), w(), h()); + glDrawArrays(GL_TRIANGLES, 0, 6); +#else + fl_draw_image(statDrawFlipImg, this, + x(), cont->isFlipped()?cont->h()-y()-h():y(), + w(), h(), m_flipImg->d()); +#endif +} + +int FlipGroup::handle(int event) { + int res=Fl_Group::handle(event); + if(res) { + refresh(); + } + return 1; +} + +void FlipGroup::resize(int x, int y, int w, int h) { + Fl_Group::resize(x,y,w,h); + m_wasChanged=true; +} + +void FlipGroup::refresh() { + m_wasChanged=true; + redraw(); +} + +void FlipGroup::addWid(Fl_Widget* wid) { + m_wasChanged=true; + add(wid); + wid->box(FL_BORDER_BOX); + wid->visible_focus(0); +} + diff --git a/src/FlipGroup.hpp b/src/FlipGroup.hpp new file mode 100644 index 0000000..312e34c --- /dev/null +++ b/src/FlipGroup.hpp @@ -0,0 +1,49 @@ +/*************************************************************************** + * FlipGroup.hpp + * Part of ControllAR + * 2016- Florent Berthaut + * hitmuri.net + ****************************************************************************/ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + + +#ifndef FlipGroup_h +#define FlipGroup_h + +#include <string> +#include <map> +#include <vector> + +#include <FL/Fl_Pack.H> +#include <FL/Fl_Button.H> +#include <FL/Fl_Image_Surface.H> + +class FlipGroup: public Fl_Group { + public : + FlipGroup(int x, int y, int w, int h); + void draw(); + int handle(int event); + void addWid(Fl_Widget*); + void resize(int x, int y, int w, int h); + void refresh(); + + protected: + Fl_RGB_Image* m_flipImg; + bool m_wasChanged; +}; + +#endif diff --git a/src/FlipMenu.cpp b/src/FlipMenu.cpp new file mode 100644 index 0000000..422dd92 --- /dev/null +++ b/src/FlipMenu.cpp @@ -0,0 +1,377 @@ +/*************************************************************************** + * FlipMenu.cpp + * Part of + * 2016- Florent Berthaut + * hitmuri.net + ****************************************************************************/ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "FlipMenu.hpp" +#include <FL/Fl_Button.H> +#include <FL/fl_draw.H> +#include <algorithm> +#include <iostream> +#include "Controlar.hpp" + +using namespace std; + +FlipMenu::FlipMenu():Fl_Pack(0, 0, 100, 100, "") { + type(Fl_Pack::VERTICAL); + end(); +} + +void FlipMenu::addWithSub(const std::string& itemName,Fl_Callback* cb,void* p) { + //parse submenu, create flip menu if needed + size_t pos = itemName.find_first_of('/'); + if(pos==string::npos) { + add(itemName, cb, p); + } + else { + string subName = itemName.substr(0, pos); + addSub(subName); + string subItemName = itemName.substr(pos+1); + m_subMenuMap[subName]->add(subItemName, cb, p); + } +} + +void FlipMenu::add(const std::string& itemName, Fl_Callback* cb, void* p) { + FlipItem* but = new FlipItem(this); + but->copy_label(itemName.c_str()); + but->callback(cb, p); + Fl_Pack::add(but); + resize(0, 0, + max(w(), + int(float(itemName.length()*FL_NORMAL_SIZE)/4.0)*4), + children()*20); +} + +void FlipMenu::addSub(const string& subName) { + if(m_subMenuMap.find(subName)==m_subMenuMap.end()) { + m_subMenuMap[subName] = new FlipMenu(); + FlipItemMenu* men = new FlipItemMenu(this, m_subMenuMap[subName]); + men->copy_label(subName.c_str()); + Fl_Pack::add(men); + } +} + +FlipMenu* FlipMenu::getSub(const string& subName) { + FlipMenu* res=NULL; + if(m_subMenuMap.find(subName)!=m_subMenuMap.end()) { + res=m_subMenuMap[subName]; + } + return res; +} + +void FlipMenu::clear() { + map<string, FlipMenu*>::iterator itSub = m_subMenuMap.begin(); + for(; itSub!=m_subMenuMap.end(); ++itSub) { + itSub->second->clear(); + Controlar::getInstance()->remove(itSub->second); + Fl::delete_widget(itSub->second); + } + m_subMenuMap.clear(); + Fl_Pack::clear(); + resize(x(), y(), 100, 100); +} + +void FlipMenu::hide() { + Fl_Pack::hide(); + hideSubMenus(); +} + +void FlipMenu::draw() { + Fl_Pack::draw(); +} + +void FlipMenu::hideSubMenus() { + map<string, FlipMenu*>::iterator itSub = m_subMenuMap.begin(); + for(; itSub!=m_subMenuMap.end(); ++itSub) { + itSub->second->hide(); + } +} + + +//--------------------------------ITEM MENU +FlipItemMenu::FlipItemMenu(FlipMenu* men, FlipMenu* subMen): + Fl_Button(0,0,20,20,""), + m_menu(men), + m_subMenu(subMen) { + align(FL_ALIGN_LEFT|FL_ALIGN_INSIDE); + box(FL_THIN_DOWN_BOX); + visible_focus(false); + m_wasResized=true; + m_highlight=false; +} + +int FlipItemMenu::handle(int event) { + switch(event) { + case FL_ENTER: { + m_menu->hideSubMenus(); + color(FL_SELECTION_COLOR); + m_subMenu->position(x()+w(), y()); + Controlar* cont = Controlar::getInstance(); + cont->insert(*m_subMenu, cont->children()); + m_subMenu->show(); + m_highlight=true; + return 1; + }break; + case FL_LEAVE: { + color(FL_BACKGROUND_COLOR); + m_highlight=false; + return 1; + }break; + default:break; + } + return 0; +} + +void FlipItemMenu::resize(int x, int y, int w, int h) { + Fl_Button::resize(x,y,w,h); + m_wasResized=true; +} + +void FlipItemMenu::draw() { + Controlar* cont = Controlar::getInstance(); + if(m_wasResized) { + Fl_Image_Surface* surf = new Fl_Image_Surface(w(), h()); + surf->set_current(); + fl_draw_box(box(), 0, 0, + w(), h(), color()); + fl_color(FL_BLACK); + fl_draw(label(), + 0, 0, + w(), h(), + align(), NULL, 0); + m_flipImg = surf->image(); + delete surf; + + Fl_Display_Device::display_device()->set_current(); + surf = new Fl_Image_Surface(w(), h()); + surf->set_current(); + fl_draw_box(box(), 0, 0, + w(), h(), FL_SELECTION_COLOR); + fl_color(FL_BLACK); + fl_draw(label(), + 0, 0, + w(), h(), + align(), NULL, 0); + m_flipImgH = surf->image(); + delete surf; + + Fl_Display_Device::display_device()->set_current(); + m_wasResized=false; + } + +#ifdef GL + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, cont->getFirstTextureID()); + if(m_highlight) { + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, + w(), h(), + 0, GL_RGB, GL_UNSIGNED_BYTE, + m_flipImgH->array); + } + else { + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, + w(), h(), + 0, GL_RGB, GL_UNSIGNED_BYTE, + m_flipImg->array); + } + glUniform2f(cont->getMenuPosUniform(), x(), y()); + glUniform2f(cont->getMenuSizeUniform(), w(), h()); + glDrawArrays(GL_TRIANGLES, 0, 6); +#else + if(m_highlight) { + fl_draw_image(statDrawFlipImg, this, + x(), cont->isFlipped()?cont->h()-y()-h():y(), + w(), h(), m_flipImgH->d()); + } + else { + fl_draw_image(statDrawFlipImg, this, + x(), cont->isFlipped()?cont->h()-y()-h():y(), + w(), h(), m_flipImg->d()); + } +#endif + if(m_subMenu->visible()) { + m_subMenu->draw(); + } +} + +void FlipItemMenu::drawFlipImg(int x, int y, int w, uchar* buf) { + if(Controlar::getInstance()->isFlipped()) { + if(m_highlight) { + memcpy(buf, + &m_flipImgH->array[((m_flipImgH->h()-y)*m_flipImgH->w()+x) + *m_flipImgH->d()], + w*m_flipImgH->d()); + } + else { + memcpy(buf, + &m_flipImg->array[((m_flipImg->h()-y)*m_flipImg->w()+x) + *m_flipImg->d()], + w*m_flipImg->d()); + } + } + else { + if(m_highlight) { + memcpy(buf, + &m_flipImgH->array[(y*m_flipImgH->w()+x)*m_flipImgH->d()], + w*m_flipImgH->d()); + } + else { + memcpy(buf, + &m_flipImg->array[(y*m_flipImg->w()+x)*m_flipImg->d()], + w*m_flipImg->d()); + } + } +} + +//-------------------------------ITEM +FlipItem::FlipItem(FlipMenu* men):Fl_Button(0,0,20,20,""), + m_menu(men) { + align(FL_ALIGN_LEFT|FL_ALIGN_INSIDE); + box(FL_THIN_UP_BOX); + visible_focus(false); + m_wasResized=true; + m_highlight=false; +} + +int FlipItem::handle(int event) { + switch(event) { + case FL_ENTER: { + m_menu->hideSubMenus(); + color(FL_SELECTION_COLOR); + m_highlight=true; + return 1; + }break; + case FL_LEAVE: { + color(FL_BACKGROUND_COLOR); + m_highlight=false; + return 1; + }break; + case FL_PUSH: { + if(Fl::event_button()==FL_LEFT_MOUSE) { + do_callback(); + color(FL_BACKGROUND_COLOR); + m_menu->hide(); + return 1; + } + }break; + default:break; + } + return 0; +} + +void FlipItem::resize(int x, int y, int w, int h) { + Fl_Button::resize(x,y,w,h); + m_wasResized=true; +} + +void FlipItem::draw() { + + Controlar* cont = Controlar::getInstance(); + if(m_wasResized) { + Fl_Image_Surface* surf = new Fl_Image_Surface(w(), h()); + surf->set_current(); + fl_draw_box(box(), 0, 0, + w(), h(), color()); + fl_color(FL_BLACK); + fl_draw(label(), + 0, 0, + w(), h(), + align(), NULL, 0); + m_flipImg = surf->image(); + delete surf; + + Fl_Display_Device::display_device()->set_current(); + surf = new Fl_Image_Surface(w(), h()); + surf->set_current(); + fl_draw_box(box(), 0, 0, + w(), h(), FL_SELECTION_COLOR); + fl_color(FL_BLACK); + fl_draw(label(), + 0, 0, + w(), h(), + align(), NULL, 0); + m_flipImgH = surf->image(); + + delete surf; + Fl_Display_Device::display_device()->set_current(); + m_wasResized=false; + } + +#ifdef GL + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, cont->getFirstTextureID()); + if(m_highlight) { + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, + w(), h(), + 0, GL_RGB, GL_UNSIGNED_BYTE, + m_flipImgH->array); + } + else { + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, + w(), h(), + 0, GL_RGB, GL_UNSIGNED_BYTE, + m_flipImg->array); + } + glUniform2f(cont->getMenuPosUniform(), x(), y()); + glUniform2f(cont->getMenuSizeUniform(), w(), h()); + glDrawArrays(GL_TRIANGLES, 0, 6); +#else + if(m_highlight) { + fl_draw_image(statDrawFlipImg, this, + x(), cont->isFlipped()?cont->h()-y()-h():y(), + w(), h(), m_flipImgH->d()); + } + else { + fl_draw_image(statDrawFlipImg, this, + x(), cont->isFlipped()?cont->h()-y()-h():y(), + w(), h(), m_flipImg->d()); + } +#endif +} + +void FlipItem::drawFlipImg(int x, int y, int w, uchar* buf) { + if(Controlar::getInstance()->isFlipped()) { + if(m_highlight) { + memcpy(buf, + &m_flipImgH->array[((m_flipImgH->h()-y)*m_flipImgH->w()+x) + *m_flipImgH->d()], + w*m_flipImgH->d()); + } + else { + memcpy(buf, + &m_flipImg->array[((m_flipImg->h()-y)*m_flipImg->w()+x) + *m_flipImg->d()], + w*m_flipImg->d()); + } + } + else { + if(m_highlight) { + memcpy(buf, + &m_flipImgH->array[(y*m_flipImgH->w()+x)*m_flipImgH->d()], + w*m_flipImgH->d()); + } + else { + memcpy(buf, + &m_flipImg->array[(y*m_flipImg->w()+x)*m_flipImg->d()], + w*m_flipImg->d()); + } + } +} + diff --git a/src/FlipMenu.hpp b/src/FlipMenu.hpp new file mode 100644 index 0000000..bcbaa87 --- /dev/null +++ b/src/FlipMenu.hpp @@ -0,0 +1,99 @@ +/*************************************************************************** + * FlipMenu.hpp + * Part of + * 2016- Florent Berthaut + * hitmuri.net + ****************************************************************************/ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + + +#ifndef FlipMenu_h +#define FlipMenu_h + +#include <string> +#include <map> +#include <vector> + +#include <FL/Fl_Pack.H> +#include <FL/Fl_Button.H> +#include <FL/Fl_Image_Surface.H> +#include <map> +#include <string> + +class FlipItemMenu; +class FlipItem; + +class FlipMenu: public Fl_Pack { + public : + FlipMenu(); + void draw(); + void addWithSub(const std::string& itemName, Fl_Callback*, void* p); + void add(const std::string& itemName, Fl_Callback*, void* p); + void addSub(const std::string& subName); + FlipMenu* getSub(const std::string& subName); + void hide(); + void clear(); + void hideSubMenus(); + + private: + std::map<std::string, FlipMenu*> m_subMenuMap; + +}; + +class FlipItemMenu : public Fl_Button { + public : + FlipItemMenu(FlipMenu* menu, FlipMenu* subMenu); + int handle(int event); + void draw(); + void resize(int x, int y, int w, int h); + static void statDrawFlipImg(void *data,int x,int y, int w, uchar *buf) { + FlipItemMenu *item = static_cast<FlipItemMenu*>(data); + item->drawFlipImg(x, y, w, buf); + } + void drawFlipImg(int x, int y, int w, uchar* buf); + + private: + FlipMenu* m_menu; + FlipMenu* m_subMenu; + Fl_RGB_Image* m_flipImg; + Fl_RGB_Image* m_flipImgH; + bool m_wasResized; + bool m_highlight; +}; + +class FlipItem : public Fl_Button { + public : + FlipItem(FlipMenu*); + int handle(int event); + void draw(); + void resize(int x, int y, int w, int h); + static void statDrawFlipImg(void *data,int x,int y, int w, uchar *buf) { + FlipItem *item = static_cast<FlipItem*>(data); + item->drawFlipImg(x, y, w, buf); + } + void drawFlipImg(int x, int y, int w, uchar* buf); + + private: + FlipMenu* m_menu; + Fl_RGB_Image* m_flipImg; + Fl_RGB_Image* m_flipImgH; + bool m_wasResized; + bool m_highlight; +}; + +#endif + diff --git a/src/GroupWidget.cpp b/src/GroupWidget.cpp new file mode 100644 index 0000000..6c439ff --- /dev/null +++ b/src/GroupWidget.cpp @@ -0,0 +1,203 @@ +/*************************************************************************** + * GroupWidget.cpp + * Part of + * 2016- Florent Berthaut + * hitmuri.net + ****************************************************************************/ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + + +#include "GroupWidget.hpp" +#include <iostream> +#include <sstream> +#include <cmath> + +#include "Controlar.hpp" + +using namespace std; + +GroupWidget::GroupWidget(int x, int y, int w, int h):Fl_Widget(x,y,w,h,"") { + box(FL_FLAT_BOX); + m_dragging=false; + m_highlight=false; + m_editing=false; + m_scaleX=1; + m_scaleY=1; +} + +void GroupWidget::copy(GroupWidget* original) { + resize(original->x(), original->y(), original->w(), original->h()); +} + +GroupWidget::~GroupWidget() {} + +void GroupWidget::draw() { + Controlar* cont = Controlar::getInstance(); + + //draw +#ifdef GL + glUniform1f(cont->getGroupHighlightUniform(), m_highlight); + glUniform1f(cont->getGroupEditingUniform(), m_editing); + glUniform2f(cont->getGroupSizeUniform(), w(), h()); + glUniform2f(cont->getGroupPosUniform(), x(), y()); + glDrawArrays(GL_TRIANGLES, 0, 6); +#else + //process drawing + fl_rect(x(), cont->isFlipped()?cont->h()-y()-h():y(),w(),h(),FL_DARK_GREEN); + if(m_highlight) { + fl_rect(x(), cont->isFlipped()?cont->h()-y()-h():y(),w(),h(),FL_GREEN); + } + +#endif +} + +int GroupWidget::handle(int event) { + int res=0; + switch(event) { + case FL_PUSH:{ + switch(Fl::event_button()) { + case FL_RIGHT_MOUSE: { + do_callback(); + res=1; + }break; + case FL_LEFT_MOUSE: { + if(Fl::event_shift()) { + startDraggingGroup(); + m_editing=true; + m_dragging=true; + res=1; + } + }break; + default:break; + } + }break; + case FL_DRAG: { + if(m_dragging) { + if(Fl::event_button()==FL_LEFT_MOUSE) { + if(Fl::event_command()) { + size(m_dragW+Fl::event_x(), m_dragH+Fl::event_y()); + } + else { + Controlar* cont = Controlar::getInstance(); + cont->moveAllZonesInsideGroupBy(this, + m_dragX+Fl::event_x()-x(), + m_dragY+Fl::event_y()-y()); + position(m_dragX+Fl::event_x(), + m_dragY+Fl::event_y()); + } + res=1; + } + } + }break; + case FL_SHORTCUT: { + if(m_highlight && Fl::event_key()==FL_Shift_L) { + m_editing=true; + res=1; + } + }break; + case FL_KEYUP: { + if(m_highlight && Fl::event_key()==FL_Shift_L) { + m_editing=false; + res=1; + } + }break; + case FL_RELEASE: { + if(m_dragging) { + m_editing=false; + m_dragging=false; + res=1; + } + }break; + case FL_MOVE:{ + m_highlight=true; + if(Fl::event_shift()) { + m_editing=true; + res=1; + } + }break; + case FL_ENTER: { + m_highlight=true; + res=1; + }break; + case FL_LEAVE: { + m_highlight=false; + res=1; + }break; + default:break; + } + if(res==1) { + parent()->redraw(); + } + return res; +} + +void GroupWidget::startDraggingGroup() { + m_dragX=x()-Fl::event_x(); + m_dragY=y()-Fl::event_y(); + m_dragW=w()-Fl::event_x(); + m_dragH=h()-Fl::event_y(); +} + +void GroupWidget::resize(int nx, int ny, int nw, int nh) { + Fl_Widget::resize(nx, ny, nw, nh); +} + +void GroupWidget::load(xmlNodePtr groupNode) { + char* value = NULL; + + int x=0,y=0,w=20,h=20; + value = (char*)xmlGetProp(groupNode,(xmlChar*)"group_x"); + if(value!=NULL) { + x = atoi(value); + } + value = (char*)xmlGetProp(groupNode,(xmlChar*)"group_y"); + if(value!=NULL) { + y = atoi(value); + } + value = (char*)xmlGetProp(groupNode,(xmlChar*)"group_w"); + if(value!=NULL) { + w = atoi(value); + } + value = (char*)xmlGetProp(groupNode,(xmlChar*)"group_h"); + if(value!=NULL) { + h = atoi(value); + } + resize(x,y,w,h); +} + +void GroupWidget::save(xmlNodePtr parentNode) { + xmlNodePtr newNode = xmlNewChild(parentNode, NULL, + BAD_CAST "Group", NULL); + ostringstream oss1, oss2, oss3, oss4, oss5, + oss6, oss7, oss8, oss9, oss10, + oss11, oss12, oss13, oss14, oss15, + oss16, oss17; + oss5<<x(); + xmlNewProp(newNode, BAD_CAST "group_x", + BAD_CAST oss5.str().c_str()); + oss6<<y(); + xmlNewProp(newNode, BAD_CAST "group_y", + BAD_CAST oss6.str().c_str()); + oss7<<w(); + xmlNewProp(newNode, BAD_CAST "group_w", + BAD_CAST oss7.str().c_str()); + oss8<<h(); + xmlNewProp(newNode, BAD_CAST "group_h", + BAD_CAST oss8.str().c_str()); +} + + diff --git a/src/GroupWidget.hpp b/src/GroupWidget.hpp new file mode 100644 index 0000000..dffc234 --- /dev/null +++ b/src/GroupWidget.hpp @@ -0,0 +1,74 @@ +/*************************************************************************** + * GroupWidget.hpp + * Part of + * 2016- Florent Berthaut + * hitmuri.net + ****************************************************************************/ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef GroupWidget_h +#define GroupWidget_h + +#include <FL/Fl.H> +#include <FL/Fl_Widget.H> +#include <FL/fl_draw.H> +#include <GL/glew.h> +#include <FL/gl.h> + +#include <libxml/parser.h> +#include <libxml/tree.h> + +#include <sys/time.h> +#include <string> +#include <vector> + +class GroupWidget : public Fl_Widget { + + public: + GroupWidget(int x=0, int y=0, int w=10, int h=10); + ~GroupWidget(); + void copy(GroupWidget*); + void draw(); + int handle(int event); + void save(xmlNodePtr parentNode); + void load(xmlNodePtr); + void resize(int x, int y, int w, int h); + inline void setID(const unsigned int& id){m_id=id;} + inline const unsigned int& getID(){return m_id;} + void startDraggingGroup(); + void forceDraggingGroup(); + + private: + unsigned int m_id; + bool m_dragging; + bool m_highlight; + bool m_editing; + float m_dragX, m_dragY; + float m_dragW, m_dragH; + float m_dragStartX, m_dragStartY; + float m_scaleX, m_scaleY; + +#ifdef GL + GLint m_sizeUniform; + GLint m_posUniform; + GLint m_editingUniform; +#endif +}; + + +#endif + diff --git a/src/MainPanel.cpp b/src/MainPanel.cpp new file mode 100644 index 0000000..053775c --- /dev/null +++ b/src/MainPanel.cpp @@ -0,0 +1,138 @@ +/*************************************************************************** + * MainPanel.cpp + * Part of ControllAR + * 2016- Florent Berthaut + * hitmuri.net + ****************************************************************************/ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "MainPanel.hpp" +#include <FL/Fl_Button.H> +#include <FL/fl_draw.H> +#include <algorithm> +#include <iostream> +#include "Controlar.hpp" + +using namespace std; + +MainPanel::MainPanel(): FlipGroup(0, 0, 240, 160) { + int labW=60; + int butW=40; + int smButW=20; + int butH=20; + int sep=5; + Fl_Pack* fileP = new Fl_Pack(sep, sep, (butW+sep)*3, butH, "File"); + fileP->type(Fl_Pack::HORIZONTAL); + fileP->align(FL_ALIGN_LEFT | FL_ALIGN_INSIDE); + fileP->spacing(sep); + fileP->box(FL_NO_BOX); + add(fileP); + fileP->add(new Fl_Group(0,0,labW,0,"")); + Fl_Button* opBut = new Fl_Button(0, 0, butW, butH, "open"); + opBut->callback(statOpen,this); + fileP->add(opBut); + Fl_Button* saBut = new Fl_Button(0, 0, butW, butH, "save"); + saBut->callback(statSave,this); + fileP->add(saBut); + Fl_Button* quBut = new Fl_Button(0, 0, butW, butH, "quit"); + quBut->callback(statQuit,this); + fileP->add(quBut); + + Fl_Pack* disP = new Fl_Pack(sep, sep+(butH+sep), + (butW+sep)*3, butH, "Display"); + disP->type(Fl_Pack::HORIZONTAL); + disP->align(FL_ALIGN_LEFT | FL_ALIGN_INSIDE); + disP->spacing(sep); + disP->box(FL_NO_BOX); + add(disP); + disP->add(new Fl_Group(0,0,labW,0,"")); + Fl_Button* fsBut = new Fl_Button(0, 0, butW*2, butH, "fullscreen"); + fsBut->callback(statFullscreen,this); + disP->add(fsBut); + Fl_Button* mirBut = new Fl_Button(0, 0, butW*2, butH, "mirror"); + mirBut->callback(statMirror,this); + disP->add(mirBut); + + int brW=160; + int brH=70; + Fl_Group* inps = new Fl_Group(sep,sep+(butH+sep)*2,brW+labW, brH, "Inputs"); + inps->box(FL_NO_BOX); + inps->align(FL_ALIGN_TOP|FL_ALIGN_LEFT|FL_ALIGN_INSIDE); + add(inps); + m_midiBr = new Fl_Multi_Browser(sep*2+labW, sep+(butH+sep)*2, brW, brH, ""); + m_midiBr->align(FL_ALIGN_TOP); + m_midiBr->has_scrollbar(Fl_Browser_::VERTICAL_ALWAYS); + m_midiBr->callback(statMidiBr, this); + inps->add(m_midiBr); + + Fl_Pack* scnP = new Fl_Pack(sep, sep+(butH+sep)*2+brH+sep, + brW, butH, "Scenes"); + scnP->align(FL_ALIGN_TOP|FL_ALIGN_LEFT|FL_ALIGN_INSIDE); + scnP->type(Fl_Pack::HORIZONTAL); + scnP->spacing(sep); + scnP->box(FL_NO_BOX); + add(scnP); + scnP->add(new Fl_Group(0, 0, labW, 0, "")); + Fl_Button* prevSc = new Fl_Button(0, 0, smButW, butH, "<"); + prevSc->callback(statScenePrev, this); + scnP->add(prevSc); + m_sceneOut = new Fl_Value_Output(0, 0, butW, butH, ""); + m_sceneOut->value(0); + scnP->add(m_sceneOut); + Fl_Button* nextSc = new Fl_Button(0, 0, smButW, butH, ">"); + nextSc->callback(statSceneNext, this); + scnP->add(nextSc); + Fl_Button* delSc = new Fl_Button(0, 0, smButW, butH, "x"); + delSc->callback(statSceneDel, this); + scnP->add(delSc); + Fl_Button* learnSc = new Fl_Button(0, 0, butW, butH, "learn"); + learnSc->callback(statSceneLearn, this); + scnP->add(learnSc); +} + +MainPanel* MainPanel::getInstance() { + static MainPanel instance; + return &instance; +} + + +void MainPanel::cbMidiBr() { + for(int i=1; i<=m_midiBr->size(); ++i) { + if(m_midiBr->selected(i)) { + Controlar::getInstance()->openMidiDevice(m_midiBr->text(i)); + } + else { + Controlar::getInstance()->closeMidiDevice(m_midiBr->text(i)); + } + } +} + +void MainPanel::setMidiDevices(const vector<string>& devs, + const vector<bool>& opened) { + m_midiBr->clear(); + vector<string>::const_iterator itDev = devs.begin(); + vector<bool>::const_iterator itOp = opened.begin(); + int el=1; + for(; itDev!=devs.end(); ++itDev, ++itOp, ++el) { + m_midiBr->add((*itDev).c_str(), NULL); + if((*itOp)) { + m_midiBr->select(el); + } + } +} + + diff --git a/src/MainPanel.hpp b/src/MainPanel.hpp new file mode 100644 index 0000000..a2bd11e --- /dev/null +++ b/src/MainPanel.hpp @@ -0,0 +1,85 @@ +/*************************************************************************** + * MainPanel.hpp + * Part of ControllAR + * 2016- Florent Berthaut + * hitmuri.net + ****************************************************************************/ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + + +#ifndef MainPanel_h +#define MainPanel_h + +#include <FL/Fl_Multi_Browser.H> +#include <FL/Fl_Value_Output.H> + +#include "Controlar.hpp" +#include "FlipGroup.hpp" + +class MainPanel: public FlipGroup { + public : + static MainPanel* getInstance(); + ~MainPanel(){}; + static void statOpen(Fl_Widget* w,void* f){ + Controlar::getInstance()->cbOpen(); + } + static void statSave(Fl_Widget* w, void* f){ + Controlar::getInstance()->cbSave(); + } + static void statQuit(Fl_Widget* w,void* f){ + Controlar::getInstance()->cbQuit(); + } + + static void statMidiBr(Fl_Widget* w, void* f){ + MainPanel::getInstance()->cbMidiBr(); + } + void cbMidiBr(); + static void statSceneNext(Fl_Widget* w, void* f){ + Controlar::getInstance()->cbSceneNext(); + MainPanel::getInstance()->refresh(); + } + static void statScenePrev(Fl_Widget* w, void* f){ + Controlar::getInstance()->cbScenePrev(); + MainPanel::getInstance()->refresh(); + } + static void statSceneDel(Fl_Widget* w,void* f){ + Controlar::getInstance()->cbSceneDelete(); + MainPanel::getInstance()->refresh(); + } + static void statSceneLearn(Fl_Widget* w,void* f){ + Controlar::getInstance()->cbSceneLearn(); + MainPanel::getInstance()->refresh(); + } + static void statFullscreen(Fl_Widget* w,void* f){ + Controlar::getInstance()->cbFullscreen(); + MainPanel::getInstance()->refresh(); + } + static void statMirror(Fl_Widget* w,void* f){ + Controlar::getInstance()->cbFlip(); + MainPanel::getInstance()->refresh(); + } + void setMidiDevices(const std::vector<std::string>& midiDeviceNames, + const std::vector<bool>& midiDeviceOp); + inline void setCurrentScene(const int& sc){m_sceneOut->value(sc);} + + private: + MainPanel(); + Fl_Multi_Browser* m_midiBr; + Fl_Value_Output* m_sceneOut; +}; + +#endif diff --git a/src/ZonePanel.cpp b/src/ZonePanel.cpp new file mode 100644 index 0000000..f34e349 --- /dev/null +++ b/src/ZonePanel.cpp @@ -0,0 +1,282 @@ +/*************************************************************************** + * ZonePanel.cpp + * Part of + * 2016- Florent Berthaut + * hitmuri.net + ****************************************************************************/ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "ZonePanel.hpp" +#include <FL/Fl_Button.H> +#include <FL/fl_draw.H> +#include <algorithm> +#include <iostream> +#include "Controlar.hpp" +#include "ZoneWidget.hpp" + +using namespace std; + +ZonePanel::ZonePanel(): FlipGroup(0, 0, 420, 200) { + int sep=5; + int butH=20; + int offY=sep; + int brW=200; + int brH=160; + int labW=60; + m_winsBr = new Fl_Hold_Browser(sep, offY, brW, brH, "window:"); + m_winsBr->align(FL_ALIGN_TOP); + m_winsBr->has_scrollbar(Fl_Browser_::VERTICAL_ALWAYS); + m_winsBr->callback(statWinBr, this); + addWid(m_winsBr); + + //transformations + Fl_Pack* radButs=new Fl_Pack(2*sep+brW, sep,(butH+sep)*4+50, + butH,"Transfo"); + add(radButs); + radButs->align(FL_ALIGN_INSIDE|FL_ALIGN_LEFT); + radButs->type(Fl_Pack::HORIZONTAL); + radButs->spacing(sep); + radButs->box(FL_NO_BOX); + radButs->add(new Fl_Group(0, 0, labW, butH, "")); + for(int i=0; i<ZoneWidget::NB_TRANSFO; ++i) { + m_transfos[i]=ZoneWidget::ZONE_TRANSFO(i); + m_transfoButs[i]= new Fl_Radio_Button(0, 0, butH, butH, ""); + m_transfoButs[i]->callback(statZoneTransfo, &m_transfos[i]); + radButs->add(m_transfoButs[i]); + } + m_transfoButs[0]->label("@8->"); + m_transfoButs[1]->label("@6->"); + m_transfoButs[2]->label("@2->"); + m_transfoButs[3]->label("@4->"); + + //shapes + radButs=new Fl_Pack(2*sep+brW, sep*2+butH, 0, butH, "Shape"); + add(radButs); + radButs->align(FL_ALIGN_INSIDE|FL_ALIGN_LEFT); + radButs->type(Fl_Pack::HORIZONTAL); + radButs->spacing(sep); + radButs->box(FL_NO_BOX); + radButs->add(new Fl_Group(0, 0, labW, butH, "")); + for(int i=0; i<ZoneWidget::NB_SHAPES; ++i) { + m_shapes[i]=ZoneWidget::ZONE_SHAPE(i); + m_shapeButs[i] = new Fl_Radio_Button(0, 0, butH, butH, ""); + m_shapeButs[i]->callback(statZoneShape, &m_shapes[i]); + radButs->add(m_shapeButs[i]); + } + m_shapeButs[0]->label("@-1square"); + m_shapeButs[1]->label("@-1circle"); + m_shapeButs[2]->label("@-1||"); + + //alpha + radButs=new Fl_Pack(2*sep+brW, sep+(sep+butH)*2, + (butH+sep)*3+60, butH, "Alpha"); + add(radButs); + radButs->align(FL_ALIGN_INSIDE|FL_ALIGN_LEFT); + radButs->type(Fl_Pack::HORIZONTAL); + radButs->spacing(sep); + radButs->box(FL_NO_BOX); + radButs->add(new Fl_Group(0, 0, labW, butH, "")); + m_alphaSlider=new Fl_Slider(0, 0, (butH+sep)*3, butH, ""); + m_alphaSlider->callback(statZoneAlpha, this); + m_alphaSlider->type(FL_HORIZONTAL); + m_alphaSlider->bounds(0, 255); + radButs->add(m_alphaSlider); + + //colors + radButs=new Fl_Pack(2*sep+brW, sep+(sep+butH)*3, + (butH+sep)*2+50, butH, "Color"); + add(radButs); + radButs->align(FL_ALIGN_INSIDE|FL_ALIGN_LEFT); + radButs->type(Fl_Pack::HORIZONTAL); + radButs->spacing(sep); + radButs->box(FL_NO_BOX); + radButs->add(new Fl_Group(0, 0, labW, butH, "")); + for(int i=0; i<ZoneWidget::NB_COLORS; ++i) { + m_colors[i]=ZoneWidget::ZONE_COLOR(i); + m_colorButs[i] = new Fl_Radio_Button(0,0, + butH, butH, ""); + m_colorButs[i]->callback(statZoneColor, &m_colors[i]); + radButs->add(m_colorButs[i]); + } + m_colorButs[0]->label("N"); + m_colorButs[1]->label("I"); + + //activity + radButs=new Fl_Pack(2*sep+brW, sep+(sep+butH)*4, + (butH+sep)*3+50, butH, "Activity"); + add(radButs); + radButs->align(FL_ALIGN_INSIDE|FL_ALIGN_LEFT); + radButs->type(Fl_Pack::HORIZONTAL); + radButs->spacing(sep); + radButs->box(FL_NO_BOX); + radButs->add(new Fl_Group(0, 0, 60, butH, "")); + for(int i=0; i<ZoneWidget::NB_EFFECTS; ++i) { + m_inputs[i]=ZoneWidget::ZONE_INPUT_EFFECT(i); + m_inputButs[i] = new Fl_Radio_Button(0, 0, butH, butH, ""); + m_inputButs[i]->callback(statZoneInput, &m_inputs[i]); + radButs->add(m_inputButs[i]); + } + m_inputButs[0]->label("-"); + m_inputButs[1]->label("S"); + m_inputButs[2]->label("H"); + Fl_Button* learnBut = new Fl_Button(0, 0, 50, butH, "Learn"); + radButs->add(learnBut); + learnBut->callback(statZoneLearn,this); + + //content + radButs=new Fl_Pack(2*sep+brW, sep+(sep+butH)*6, + (butH+sep)*3+50, butH, "Content"); + add(radButs); + radButs->align(FL_ALIGN_INSIDE|FL_ALIGN_LEFT); + radButs->type(Fl_Pack::HORIZONTAL); + radButs->spacing(sep); + radButs->box(FL_NO_BOX); + radButs->add(new Fl_Group(0, 0, 60, butH, "")); + for(int i=0; i<ZoneWidget::NB_CONTENTS; ++i) { + m_contents[i]=ZoneWidget::ZONE_CONTENT(i); + m_contentButs[i] = new Fl_Radio_Button(0, 0, butH*3, butH, ""); + m_contentButs[i]->callback(statZoneContent, &m_contents[i]); + radButs->add(m_contentButs[i]); + } + m_contentButs[0]->label("global"); + m_contentButs[1]->label("local"); + + //update + radButs=new Fl_Pack(2*sep+brW, sep+(sep+butH)*5, + (butH+sep)*3+50, butH, "Update"); + add(radButs); + radButs->align(FL_ALIGN_INSIDE|FL_ALIGN_LEFT); + radButs->type(Fl_Pack::HORIZONTAL); + radButs->spacing(sep); + radButs->box(FL_NO_BOX); + radButs->add(new Fl_Group(0, 0, 60, butH, "")); + for(int i=0; i<ZoneWidget::NB_UPDATES; ++i) { + m_updates[i]=ZoneWidget::ZONE_UPDATE(i); + m_updateButs[i] = new Fl_Radio_Button(0, 0, butH*3, butH, ""); + m_updateButs[i]->callback(statZoneUpdate, &m_updates[i]); + radButs->add(m_updateButs[i]); + } + m_updateButs[0]->label("all"); + m_updateButs[1]->label("current"); + + //delete + Fl_Button* delBut = new Fl_Button(sep, h()-sep-butH, 60, butH, "Delete"); + delBut->callback(statZoneDelete,this); + add(delBut); +} + +void ZonePanel::setWindowList(vector<CutWindow*>& wins) { + m_winsBr->clear(); + m_winsBr->add("none", NULL); + vector<CutWindow*>::iterator itWin = wins.begin(); + for(; itWin!=wins.end(); ++itWin) { + m_winsBr->add(((*itWin)->getName()).c_str(), (*itWin)); + } + m_winsBr->value(1); +} + +void ZonePanel::setZone(ZoneWidget* z) { + m_curZone=z; + + //retrieve values + if(m_curZone) { + CutWindow* cutW = m_curZone->getCutWin(); + if(cutW) { + string cutWN = cutW->getName(); + for(int i=1; i<=m_winsBr->size(); ++i) { + if(cutWN.compare(m_winsBr->text(i))==0) { + m_winsBr->value(i); + } + } + } + else { + m_winsBr->value(1); + } + + for(int i=0; i<int(ZoneWidget::NB_TRANSFO); ++i) { + m_transfoButs[i]->value(0); + } + m_transfoButs[int(m_curZone->getTransformation())]->value(1); + for(int i=0; i<int(ZoneWidget::NB_SHAPES); ++i) { + m_shapeButs[i]->value(0); + } + m_shapeButs[int(m_curZone->getShape())]->value(1); + for(int i=0; i<int(ZoneWidget::NB_COLORS); ++i) { + m_colorButs[i]->value(0); + } + m_colorButs[int(m_curZone->getColor())]->value(1); + for(int i=0; i<int(ZoneWidget::NB_EFFECTS); ++i) { + m_inputButs[i]->value(0); + } + m_inputButs[int(m_curZone->getEffect())]->value(1); + for(int i=0; i<int(ZoneWidget::NB_UPDATES); ++i) { + m_updateButs[i]->value(0); + } + m_updateButs[int(m_curZone->getZoneUpdate())]->value(1); + for(int i=0; i<int(ZoneWidget::NB_CONTENTS); ++i) { + m_contentButs[i]->value(0); + } + m_contentButs[int(m_curZone->getContent())]->value(1); + m_alphaSlider->value(m_curZone->getAlpha()); + } +} + +void ZonePanel::cbWinBr(Fl_Widget*) { + m_curZone->setCutWin((CutWindow*)(m_winsBr->data(m_winsBr->value()))); + refresh(); +} + +void ZonePanel::cbZoneTransfo(const ZoneWidget::ZONE_TRANSFO& trans) { + m_curZone->setTransformation(trans); + refresh(); +} + +void ZonePanel::cbZoneShape(const ZoneWidget::ZONE_SHAPE& shape) { + m_curZone->setShape(shape); + refresh(); +} + +void ZonePanel::cbZoneAlpha() { + m_curZone->setAlpha(m_alphaSlider->value()); + refresh(); +} + +void ZonePanel::cbZoneColor(const ZoneWidget::ZONE_COLOR& color) { + m_curZone->setColor(color); + refresh(); +} + +void ZonePanel::cbZoneInput(const ZoneWidget::ZONE_INPUT_EFFECT& inp) { + m_curZone->setInputEffect(inp); + refresh(); +} + +void ZonePanel::cbZoneUpdate(const ZoneWidget::ZONE_UPDATE& up) { + Controlar::getInstance()->cbZoneUpdate(up); + refresh(); +} + +void ZonePanel::cbZoneContent(const ZoneWidget::ZONE_CONTENT& cont) { + m_curZone->setContent(cont); + refresh(); +} + +ZonePanel* ZonePanel::getInstance() { + static ZonePanel instance; + return &instance; +} + diff --git a/src/ZonePanel.hpp b/src/ZonePanel.hpp new file mode 100644 index 0000000..fb95307 --- /dev/null +++ b/src/ZonePanel.hpp @@ -0,0 +1,124 @@ +/*************************************************************************** + * ZonePanel.hpp + * Part of ControllAR + * 2016- Florent Berthaut + * hitmuri.net + ****************************************************************************/ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + + +#ifndef ZonePanel_h +#define ZonePanel_h + +#include <string> +#include <map> +#include <vector> + +#include <FL/Fl_Choice.H> +#include <FL/Fl_Hold_Browser.H> +#include <FL/Fl_Radio_Round_Button.H> +#include <FL/Fl_Radio_Button.H> +#include <FL/Fl_Slider.H> +#include "Controlar.hpp" +#include "FlipGroup.hpp" +#include "CutWindow.hpp" + +class ZoneWidget; + +class ZonePanel: public FlipGroup { + public : + static ZonePanel* getInstance(); + ~ZonePanel(){}; + void setWindowList(std::vector<CutWindow*>& wins); + + static void statWinBr(Fl_Widget* w, void* f){ + ZonePanel *tmpf = static_cast<ZonePanel *>(f); + tmpf->cbWinBr(w); + } + void cbWinBr(Fl_Widget*); + + static void statZoneTransfo(Fl_Widget* w, void* f){ + ZonePanel::getInstance() + ->cbZoneTransfo(*(ZoneWidget::ZONE_TRANSFO*)(f)); + } + void cbZoneTransfo(const ZoneWidget::ZONE_TRANSFO&); + + static void statZoneShape(Fl_Widget* w, void* f){ + ZonePanel::getInstance() + ->cbZoneShape(*(ZoneWidget::ZONE_SHAPE*)(f)); + } + void cbZoneShape(const ZoneWidget::ZONE_SHAPE&); + + static void statZoneAlpha(Fl_Widget* w, void* f){ + ZonePanel::getInstance()->cbZoneAlpha(); + } + void cbZoneAlpha(); + + static void statZoneColor(Fl_Widget* w, void* f){ + ZonePanel::getInstance() + ->cbZoneColor(*(ZoneWidget::ZONE_COLOR*)(f)); + } + void cbZoneColor(const ZoneWidget::ZONE_COLOR&); + + static void statZoneInput(Fl_Widget* w, void* f){ + ZonePanel::getInstance() + ->cbZoneInput(*(ZoneWidget::ZONE_INPUT_EFFECT*)(f)); + } + void cbZoneInput(const ZoneWidget::ZONE_INPUT_EFFECT&); + + static void statZoneContent(Fl_Widget* w, void* f){ + ZonePanel::getInstance() + ->cbZoneContent(*(ZoneWidget::ZONE_CONTENT*)(f)); + } + void cbZoneContent(const ZoneWidget::ZONE_CONTENT&); + + static void statZoneUpdate(Fl_Widget* w, void* f){ + ZonePanel::getInstance() + ->cbZoneUpdate(*(ZoneWidget::ZONE_UPDATE*)(f)); + } + void cbZoneUpdate(const ZoneWidget::ZONE_UPDATE&); + + static void statZoneLearn(Fl_Widget* w, void* f){ + Controlar::getInstance()->cbZoneMidiLearn(); + } + + static void statZoneDelete(Fl_Widget* w, void* f){ + Controlar::getInstance()->cbZoneDelete(); + } + + void setZone(ZoneWidget*); + + private: + ZonePanel(); + Fl_Hold_Browser* m_winsBr; + ZoneWidget* m_curZone; + ZoneWidget::ZONE_TRANSFO m_transfos[ZoneWidget::NB_TRANSFO]; + Fl_Radio_Button* m_transfoButs[ZoneWidget::NB_TRANSFO]; + ZoneWidget::ZONE_SHAPE m_shapes[ZoneWidget::NB_SHAPES]; + Fl_Radio_Button* m_shapeButs[ZoneWidget::NB_SHAPES]; + ZoneWidget::ZONE_COLOR m_colors[ZoneWidget::NB_COLORS]; + Fl_Radio_Button* m_colorButs[ZoneWidget::NB_COLORS]; + ZoneWidget::ZONE_INPUT_EFFECT m_inputs[ZoneWidget::NB_EFFECTS]; + Fl_Radio_Button* m_inputButs[ZoneWidget::NB_EFFECTS]; + ZoneWidget::ZONE_CONTENT m_contents[ZoneWidget::NB_CONTENTS]; + Fl_Radio_Button* m_contentButs[ZoneWidget::NB_CONTENTS]; + ZoneWidget::ZONE_UPDATE m_updates[ZoneWidget::NB_UPDATES]; + Fl_Radio_Button* m_updateButs[ZoneWidget::NB_UPDATES]; + Fl_Slider* m_alphaSlider; +}; + +#endif diff --git a/src/ZoneWidget.cpp b/src/ZoneWidget.cpp new file mode 100644 index 0000000..a6c542e --- /dev/null +++ b/src/ZoneWidget.cpp @@ -0,0 +1,1077 @@ +/*************************************************************************** + * ZoneWidget.cpp + * Part of + * 2016- Florent Berthaut + * hitmuri.net + ****************************************************************************/ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + + +#include "ZoneWidget.hpp" +#include <iostream> +#include <sstream> +#include <cmath> + +#include "Controlar.hpp" + +using namespace std; + +const int ZoneWidget::GLOBAL_PROPS; + +ZoneWidget::ZoneWidget(int x, int y, int w, int h): Fl_Widget(x,y,w,h,"") { + box(FL_FLAT_BOX); + m_state=NORMAL_STATE; + m_imageData=NULL; + m_image=NULL; + m_coordImage=NULL; + recreateImage(); + m_dragging=false; + m_forcedDragging=false; + m_editing=false; + m_highlight=false; + m_scaleX=1; + m_scaleY=1; + m_midiLearning=false; + m_effectDelay.tv_sec=0; + m_effectDelay.tv_usec=250000; + m_hiddenByEffect=false; + m_effectDelayOver=true; + m_rateReached=false; + m_initialized=false; + m_visibleInScene=-1; + m_updateInScene=-1; + m_content=CONTENT_GLOBAL; + + m_curCutProps=ZoneWidget::GLOBAL_PROPS; + m_cutProps[m_curCutProps]=CutProps(); + m_currentScene=0; +} + +void ZoneWidget::copy(ZoneWidget* original) { + resize(original->x(), original->y(), original->w(), original->h()); + m_cutProps=original->m_cutProps; + m_curCutProps=original->m_curCutProps; + m_visibleInScene=original->m_visibleInScene; + m_updateInScene=original->m_updateInScene; + m_content = original->m_content; + recomputePixels(); +} + +ZoneWidget::~ZoneWidget() {} + +void ZoneWidget::setScene(const int& scene) { + if(m_updateInScene==m_currentScene && m_cutProps[m_curCutProps].m_ovWin) { + m_cutProps[m_curCutProps].m_ovWin->storeImage(m_currentScene); + } + if(m_content==CONTENT_LOCAL) { + m_curCutProps=scene; + } + else { + m_curCutProps=ZoneWidget::GLOBAL_PROPS; + } + m_currentScene=scene; + recomputePixels(); +} + +void ZoneWidget::setContent(const ZONE_CONTENT& cont) { + m_content=cont; + //set current scene to define current cut props + setScene(m_currentScene); + //copy props + if(m_content==CONTENT_LOCAL) { + //copy from global to local + m_cutProps[m_curCutProps]=m_cutProps[ZoneWidget::GLOBAL_PROPS]; + } + else { + //copy from local to global + m_cutProps[ZoneWidget::GLOBAL_PROPS]=m_cutProps[m_curCutProps]; + } +} + +void ZoneWidget::refreshCutWin() { + setCutWin(Controlar::getInstance() + ->getWindow(m_cutProps[m_curCutProps].m_ovWinName)); +} + +void ZoneWidget::setCutWin(CutWindow* ow) { + CutProps& props = m_cutProps[m_curCutProps]; + props.m_ovWin=ow; + if(props.m_ovWin) { //if the window exists + if(props.m_ovWinName.compare(props.m_ovWin->getName())==0) { //same name + //preserve coordinates if possible + props.m_winW = max(1, min(props.m_winW, + props.m_ovWin->getWidth())); + props.m_winH = max(1, min(props.m_winH, + props.m_ovWin->getHeight())); + props.m_winX = max(0, min(props.m_winX, + props.m_ovWin->getWidth()-props.m_winW)); + props.m_winY = max(0, min(props.m_winY, + props.m_ovWin->getHeight()-props.m_winH)); + m_nbPixPerRow=props.m_winW; + } + else { //different window + //reset position and size of the cut + props.m_ovWinName=props.m_ovWin->getName(); + props.m_winX=0; + props.m_winY=0; + props.m_winW = max(1, min(w(), props.m_ovWin->getWidth())); + props.m_winH = max(1, min(h(), props.m_ovWin->getHeight())); + m_nbPixPerRow=props.m_winW; + } + recomputePixels(); + } +} + +CutWindow* ZoneWidget::getCutWin() { + return m_cutProps[m_curCutProps].m_ovWin; +} + +void ZoneWidget::setInputEffect(const ZONE_INPUT_EFFECT& effect) { + ZONE_INPUT_EFFECT& meffect = m_cutProps[m_curCutProps].m_effect; + meffect=effect; + m_hiddenByEffect=(meffect==SHOW)?true:false; +} + +void ZoneWidget::setVisible(const int& vis) { + m_visibleInScene=vis; +} + +void ZoneWidget::setUpdate(const int& up) { + m_updateInScene=up; +} + +void ZoneWidget::processMidi(const int& midiType, + const int& midiChannel, + const int& midiControl, + const int& midiValue) { + if(m_midiLearning) { + m_midiType=midiType; + m_midiChannel=midiChannel; + m_midiControl=midiControl; + m_midiLearning=false; + } + ZONE_INPUT_EFFECT& meffect = m_cutProps[m_curCutProps].m_effect; + if(meffect!=NO_EFFECT) { + if(midiType==m_midiType + && midiChannel==m_midiChannel + && midiControl==m_midiControl) { + m_effectDelayOver=false; + gettimeofday(&m_effectStartTime, NULL); + switch(meffect) { + case SHOW:{ + m_hiddenByEffect=false; + }break; + case HIDE:{ + m_hiddenByEffect=true; + }break; + default:break; + } + } + } +} + +void ZoneWidget::drawZone(const int& currentScene) { + int nw=w()-OUTER*2; + int nh=h()-OUTER*2; + + CutProps& props = m_cutProps[m_curCutProps]; + + Controlar* cont = Controlar::getInstance(); + //process effect + if(props.m_effect!=NO_EFFECT && !m_effectDelayOver) { + timeval curTime, diffTime; + gettimeofday(&curTime, NULL); + timersub(&curTime, &m_effectStartTime, &diffTime); + if(timercmp(&diffTime, &m_effectDelay, >)) { + m_effectDelayOver=true; + switch(props.m_effect) { + case SHOW:{ + m_hiddenByEffect=true; + }break; + case HIDE:{ + m_hiddenByEffect=false; + }break; + default:break; + } + } + } + + //draw +#ifdef GL + if(m_visibleInScene<0 || m_visibleInScene==currentScene) { + glUniform1f(cont->getZoneFilledUniform(), 0.0); + glUniform1i(cont->getZoneStateUniform(), int(m_state)); + glUniform2f(cont->getZoneSizeUniform(), w(), h()); + glUniform2f(cont->getZonePosUniform(), x(), y()); + glUniform2f(cont->getCutSizeUniform(), nw, nh); + glUniform2f(cont->getCutPosUniform(), x()+OUTER, y()+OUTER); + glUniform1f(cont->getZoneAlphaUniform(), + m_hiddenByEffect?0.0:float(props.m_alpha)/255.0); + glUniform1f(cont->getZoneInvertUniform(), props.m_color==INVERT); + if(props.m_ovWin) { + glUniform1f(cont->getZoneFilledUniform(), 1.0); + //if using stored image from other scene + if(m_updateInScene>=0 && m_updateInScene!=currentScene) { + uchar* storedData=props.m_ovWin->retrieveImage(m_updateInScene); + if(storedData) { + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, cont->getFirstTextureID()); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, + m_nbPixPerRow, props.m_ovWin->getHeight(), + 0, GL_RGBA, GL_UNSIGNED_BYTE, + storedData); + cont->setCurrentWindowName(""); + } + } + else if(cont->getCurrentWindowName() + .compare(props.m_ovWin->getName())!=0) { + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, cont->getFirstTextureID()); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, + m_nbPixPerRow, props.m_ovWin->getHeight(), + 0, GL_RGBA, GL_UNSIGNED_BYTE, + props.m_ovWin->grabImage()); + cont->setCurrentWindowName(props.m_ovWin->getName()); + } + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, cont->getSecondTextureID()); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RG16F, + nw, nh, + 0, GL_RG, GL_FLOAT, + m_coordImage); + } + glDrawArrays(GL_TRIANGLES, 0, 6); + } + +#else + + //process drawing + if(props.m_ovWin) { + if(m_visibleInScene<0 || m_visibleInScene==currentScene) { + if(!m_hiddenByEffect && m_rateReached) { + m_image->uncache(); + m_ovWin->getPixels(props.m_winX, props.m_winY, + props.m_winW, props.m_winH, + m_srcIndices, m_srcCoords, m_destIndices, + props.m_alpha, props.m_color, m_imageData); + m_image->draw(x(), cont->isFlipped()?cont->h()-y()-h():y(), + w(), h()); + } + switch(m_rate) { + case STATIC:m_rateReached=false;break; + default:m_rateReached=true;break; + } + } + } + else { + fl_rect(x(), cont->isFlipped()?cont->h()-y()-h():y(), + w(), h(), FL_WHITE); + } + if(m_editing) { + fl_rectf(x(), cont->isFlipped()?cont->h()-y()-h():y(), + w(), h(), FL_RED); + } + if(m_highlight) { + fl_rect(x(), cont->isFlipped()?cont->h()-y()-h():y(), + w(), h(), FL_RED); + } + +#endif +} + +void ZoneWidget::recomputePixels() { + CutProps& props = m_cutProps[m_curCutProps]; + + //if window attached, recompute correspondance between pixel coordinates + if(props.m_ovWin) { + + //get new pixperrow in window + m_nbPixPerRow = props.m_ovWin->computeNbPixPerRow(props.m_winW, + props.m_winH); + + //clear existing coordinates + m_srcIndices.clear(); + m_srcCoords.clear(); + m_destIndices.clear(); + vector<float> coord(2,0); + vector<float> start(2,0); + vector<bool> restart(2,false); + vector<float> stepY(2,0); + vector<float> stepX(2,0); + int nw=w()-OUTER*2; + int nh=h()-OUTER*2; + + //compute the pixels offset and steps depending on transformation + switch(props.m_transfo) { + case ROTATE_90 : { + start[0]=props.m_ovWin->needsOffset()?props.m_winX:0; + start[1]=props.m_ovWin->needsOffset()?props.m_winY+props.m_winH + :props.m_winH; + stepY[0]=float(props.m_winW)/float(nh); + restart[1]=true; + stepX[1]=-float(props.m_winH)/float(nw); + }break; + case ROTATE_180 : { + start[0]=props.m_ovWin->needsOffset()?props.m_winX+props.m_winW + :props.m_winW; + start[1]=props.m_ovWin->needsOffset()?props.m_winY+props.m_winH + :props.m_winH; + stepY[1]=-float(props.m_winH)/float(nh); + restart[0]=true; + stepX[0]=-float(props.m_winW)/float(nw); + }break; + case ROTATE_270 : { + start[0]=props.m_ovWin->needsOffset()?props.m_winX+props.m_winW + :props.m_winW; + start[1]=props.m_ovWin->needsOffset()?props.m_winY:0; + stepY[0]=-float(props.m_winW)/float(nh); + restart[1]=true; + stepX[1]=float(props.m_winH)/float(nw); + }break; + default : { + start[0]=props.m_ovWin->needsOffset()?props.m_winX:0; + start[1]=props.m_ovWin->needsOffset()?props.m_winY:0; + stepY[1]=float(props.m_winH)/float(nh); + restart[0]=true; + stepX[0]=float(props.m_winW)/float(nw); + }break; + } + + vector<int> intCoord(2,0); + coord[0]=start[0]; + coord[1]=start[1]; + + Controlar* cont = Controlar::getInstance(); + + //compute coordinates according to shape + switch(props.m_shape) { + case CIRCLE : { //circle shape + int radius = min(nw,nh); + for(int py = 0; py < nh; py++) { + for(int px = 0; px < nw; px++) { + float distX = px-nw/2; + float distY = py-nh/2; + float dist = sqrt(distX*distX + distY*distY); + if(dist>radius/4 && dist<radius/2) { //inside the circle + distX/=float(radius); + distY/=float(radius); + float angleY = (atan2(distY,distX)-M_PI/2.0) + / (2.0*M_PI)*float(nh); + if(angleY<0) { + angleY=nh+angleY; + } + float radiusX = + (dist-radius/4)/(radius/4)*float(nw); + intCoord[0] = + start[0]+stepY[0]*angleY+stepX[0]*radiusX; + intCoord[1] = + start[1]+stepY[1]*angleY+stepX[1]*radiusX; + } + else { + intCoord[0]=-1; intCoord[1]=-1; + } + m_srcCoords.push_back(intCoord); + int srcInd = (m_nbPixPerRow*intCoord[1] + +intCoord[0])*4; + if(props.m_ovWin->isBGR()) { + m_srcIndices.push_back(srcInd+2); + m_srcIndices.push_back(srcInd+1); + m_srcIndices.push_back(srcInd); + } + else { + m_srcIndices.push_back(srcInd); + m_srcIndices.push_back(srcInd+1); + m_srcIndices.push_back(srcInd+2); + } + m_srcIndices.push_back(srcInd+3); + for(int c=0; c<4; ++c) { + if(cont->isFlipped()) { + m_destIndices.push_back((nw*(nh-1-py)+px)*4+c); + } + else { + m_destIndices.push_back((nw*py+px)*4+c); + } + } + m_coordImage[(nw*py+px)*2]=float(intCoord[0]) + / float(props.m_ovWin->getWidth()); + m_coordImage[(nw*py+px)*2+1]=float(intCoord[1]) + / float(props.m_ovWin->getHeight()); + } + } + }break; + case FRAME : { //frame shape + int border=20; + for(int py = 0; py < nh; py++) { + coord[0]=restart[0]?start[0]:coord[0]; + coord[1]=restart[1]?start[1]:coord[1]; + for(int px = 0; px < nw; px++) { + if(px>border && px<nw-border + && py>border && py<nh-border) { + intCoord[0]=-1; intCoord[1]=-1; + } + else { + float nbSrcPix=0; + if(py<border) { + nbSrcPix = float(py)/float(border)*float(nh/2); + intCoord[0]=coord[0]; + intCoord[1]=start[1] + +nbSrcPix*stepX[1] + +nbSrcPix*stepY[1]; + } + else if(py>nh-border) { + nbSrcPix= float(nh) + -(float(nh-py) + /float(border) + *float(nh/2)); + intCoord[0]=coord[0]; + intCoord[1]=start[1] + +nbSrcPix*stepX[1] + +nbSrcPix*stepY[1]; + } + else if(px<border) { + nbSrcPix = float(px)/float(border)*float(nw/2); + intCoord[0]=start[0] + +nbSrcPix*stepX[0] + +nbSrcPix*stepY[0]; + intCoord[1]=coord[1]; + } + else { + nbSrcPix= float(nw) + -(float(nw-px) + /float(border) + *float(nw/2)); + intCoord[0]=start[0] + +nbSrcPix*stepX[0] + +nbSrcPix*stepY[0]; + intCoord[1]=coord[1]; + } + } + m_srcCoords.push_back(intCoord); + int srcInd = (m_nbPixPerRow*intCoord[1] + +intCoord[0])*4; + if(props.m_ovWin->isBGR()) { + m_srcIndices.push_back(srcInd+2); + m_srcIndices.push_back(srcInd+1); + m_srcIndices.push_back(srcInd); + } + else { + m_srcIndices.push_back(srcInd); + m_srcIndices.push_back(srcInd+1); + m_srcIndices.push_back(srcInd+2); + } + m_srcIndices.push_back(srcInd+3); + for(int c=0; c<4; ++c) { + if(cont->isFlipped()) { + m_destIndices.push_back((nw*(nh-1-py)+px)*4+c); + } + else { + m_destIndices.push_back((nw*py+px)*4+c); + } + } + coord[0]+=stepX[0]; + coord[1]+=stepX[1]; + m_coordImage[(nw*py+px)*2]=float(intCoord[0]) + / float(props.m_ovWin->getWidth()); + m_coordImage[(nw*py+px)*2+1]=float(intCoord[1]) + / float(props.m_ovWin->getHeight()); + } + coord[0]+=stepY[0]; + coord[1]+=stepY[1]; + } + }break; + default: { //box shape + for(int py = 0; py < nh; py++) { + coord[0]=restart[0]?start[0]:coord[0]; + coord[1]=restart[1]?start[1]:coord[1]; + for(int px = 0; px < nw; px++) { + intCoord[0]=coord[0]; intCoord[1]=coord[1]; + m_srcCoords.push_back(intCoord); + int srcInd = (m_nbPixPerRow*intCoord[1] + +intCoord[0])*4; + if(props.m_ovWin->isBGR()) { + m_srcIndices.push_back(srcInd+2); + m_srcIndices.push_back(srcInd+1); + m_srcIndices.push_back(srcInd); + } + else { + m_srcIndices.push_back(srcInd); + m_srcIndices.push_back(srcInd+1); + m_srcIndices.push_back(srcInd+2); + } + m_srcIndices.push_back(srcInd+3); + for(int c=0; c<4; ++c) { + if(cont->isFlipped()) { + m_destIndices.push_back((nw*(nh-1-py)+px)*4+c); + } + else { + m_destIndices.push_back((nw*py+px)*4+c); + } + } + coord[0]+=stepX[0]; + coord[1]+=stepX[1]; + m_coordImage[(nw*py+px)*2]=float(intCoord[0]) + / float(props.m_ovWin->getWidth()); + m_coordImage[(nw*py+px)*2+1]=float(intCoord[1]) + / float(props.m_ovWin->getHeight()); + } + coord[0]+=stepY[0]; + coord[1]+=stepY[1]; + } + }break; + } + m_scaleX=(stepY[0]!=0)?abs(stepY[0]):abs(stepX[0]); + m_scaleY=(stepY[1]!=0)?abs(stepY[1]):abs(stepX[1]); + } +} + +void ZoneWidget::recreateImage() { + int nw=w()-OUTER*2; + int nh=h()-OUTER*2; + if(m_imageData) { + delete [] m_imageData; + } + m_imageData = new uchar[nw*nh*4]; + if(m_image) { + delete m_image; + } + m_image = new Fl_RGB_Image(m_imageData, nw, nh, 4); + + if(m_coordImage) { + delete m_coordImage; + } + m_coordImage = new float[nw*nh*2]; +} + +int ZoneWidget::handle(int event) { + CutProps& props = m_cutProps[m_curCutProps]; + int res=0; + switch(event) { + case FL_MOUSEWHEEL : { + Fl_Widget* wid = Fl::belowmouse(); + if(parent()!=NULL) { + parent()->insert(*wid,0); + } + res=1; + }break; + case FL_PUSH:{ + if(Fl::event_inside(this)) { + m_forcedDragging=false; + switch(Fl::event_button()) { + case FL_RIGHT_MOUSE: { + do_callback(); + res=1; + }break; + case FL_LEFT_MOUSE: { + //if(Fl::event_shift()) { //move/resize zone + if(m_state==MOVE_ZONE || m_state==RESIZE_ZONE) { + startDraggingZone(); + m_editing=true; + } + //else if(props.m_ovWin) { //move/resize window + else if(props.m_ovWin) { //move/resize window + m_editing=true; + switch(props.m_transfo) { + case ROTATE_90: { + m_dragStartX=props.m_winY; + m_dragStartY=props.m_winX; + m_dragX=Fl::event_x(); + m_dragY=Fl::event_y(); + m_dragW=props.m_winW+Fl::event_y(); + m_dragH=props.m_winH-Fl::event_x(); + }break; + case ROTATE_180: { + m_dragStartX=props.m_winX; + m_dragStartY=props.m_winY; + m_dragX=Fl::event_x(); + m_dragY=Fl::event_y(); + m_dragW=props.m_winW-Fl::event_x(); + m_dragH=props.m_winH-Fl::event_y(); + }break; + case ROTATE_270: { + m_dragStartX=props.m_winY; + m_dragStartY=props.m_winX; + m_dragX=Fl::event_x(); + m_dragY=Fl::event_y(); + m_dragW=props.m_winW-Fl::event_y(); + m_dragH=props.m_winH+Fl::event_x(); + }break; + default: { + m_dragStartX=props.m_winX; + m_dragStartY=props.m_winY; + m_dragX=Fl::event_x(); + m_dragY=Fl::event_y(); + m_dragW=props.m_winW+Fl::event_x(); + m_dragH=props.m_winH+Fl::event_y(); + }break; + } + } + m_dragging=true; + res=1; + }break; + default:break; + } + } + }break; + case FL_DRAG: { + if(m_dragging) { + if(Fl::event_button()==FL_LEFT_MOUSE) { +/* + if(Fl::event_shift()) { //move/resize zone + m_editing=true; + if(Fl::event_command()) { + size(m_dragW+Fl::event_x(), m_dragH+Fl::event_y()); + } + else { + position(m_dragX+Fl::event_x(), + m_dragY+Fl::event_y()); + } + } +*/ + if(m_state==MOVE_ZONE) { + m_editing=true; + position(m_dragX+Fl::event_x(), + m_dragY+Fl::event_y()); + } + else if(m_state==RESIZE_ZONE) { + m_editing=true; + size(max(float(OUTER*2), m_dragW+Fl::event_x()), + max(float(OUTER*2), m_dragH+Fl::event_y())); + } + else if(props.m_ovWin) { //move/resize window + int transDW, transDH, transDX, transDY; + switch(props.m_transfo) { + case ROTATE_90: { + transDW=m_dragW-Fl::event_y(); + transDH=m_dragH+Fl::event_x(); + transDX=m_dragStartY + - m_scaleX*(Fl::event_y()-m_dragY); + transDY=m_dragStartX + + m_scaleY*(Fl::event_x()-m_dragX); + }break; + case ROTATE_180: { + transDW=m_dragW+Fl::event_x(); + transDH=m_dragH+Fl::event_y(); + transDX=m_dragStartX + + m_scaleX*(Fl::event_x()-m_dragX); + transDY=m_dragStartY + + m_scaleY*(Fl::event_y()-m_dragY); + }break; + case ROTATE_270: { + transDW=m_dragW+Fl::event_y(); + transDH=m_dragH-Fl::event_x(); + transDX=m_dragStartY + + m_scaleX*(Fl::event_y()-m_dragY); + transDY=m_dragStartX + - m_scaleY*(Fl::event_x()-m_dragX); + }break; + default: { + transDW=m_dragW-Fl::event_x(); + transDH=m_dragH-Fl::event_y(); + transDX=m_dragStartX + - m_scaleX*(Fl::event_x()-m_dragX); + transDY=m_dragStartY + - m_scaleY*(Fl::event_y()-m_dragY); + }break; + } + //if(Fl::event_command()) { + if(m_state==RESIZE_CUT) { + props.m_winW = max(1, min(transDW, + props.m_ovWin->getWidth())); + props.m_winH = max(1, min(transDH, + props.m_ovWin->getHeight())); + props.m_winX = max(0, min(props.m_winX, + props.m_ovWin->getWidth() + - props.m_winW)); + props.m_winY = max(0, min(props.m_winY, + props.m_ovWin->getHeight() + - props.m_winH)); + } + else { + props.m_winX = max(0, min(transDX, + props.m_ovWin->getWidth() + - props.m_winW)); + props.m_winY = max(0, min(transDY, + props.m_ovWin->getHeight() + - props.m_winH)); + } + recomputePixels(); + } + res=1; + } + } + }break; + case FL_RELEASE: { + m_editing=false; + res=1; + }break; + case FL_SHORTCUT: { + if(m_highlight && Fl::event_key()==FL_Shift_L) { + m_editing=true; + return 1; + } + else { + switch(Fl::event_key()) { + case 'd': { + m_forcedDragging=false; + Controlar::getInstance()->startDuplicating(this); + res=1; + }break; + case 'a': { + props.m_alpha=(props.m_alpha<255)?props.m_alpha+10:255; + res=1; + }break; + case 'q': { + props.m_alpha=(props.m_alpha>0)?props.m_alpha-10:0; + res=1; + }break; + case 's' : { + props.m_shape = ZONE_SHAPE((props.m_shape+1)%NB_SHAPES); + recomputePixels(); + res=1; + }break; + case 't' : { + props.m_transfo + = ZONE_TRANSFO((props.m_transfo+1)%NB_TRANSFO); + recomputePixels(); + res=1; + }break; + case 'c' : { + props.m_color= ZONE_COLOR((props.m_color+1)%NB_COLORS); + res=1; + }break; + case 'e' : { + setInputEffect(ZONE_INPUT_EFFECT((props.m_effect+1) + %NB_EFFECTS)); + res=1; + }break; + case FL_Left : + case FL_Right : + case FL_Down : + case FL_Up : { + int transDX=props.m_winX; + int transDY=props.m_winY; + int transDW=props.m_ovWin->getWidth(); + int transDH=props.m_ovWin->getHeight(); + if((Fl::event_key()==FL_Left && props.m_transfo==NONE) + || (Fl::event_key()==FL_Right + && props.m_transfo==ROTATE_180) + || (Fl::event_key()==FL_Down + && props.m_transfo==ROTATE_270) + || (Fl::event_key()==FL_Up + && props.m_transfo==ROTATE_90) ) { + transDX-=10*m_scaleX; + transDW-=10*m_scaleX; + } + else if((Fl::event_key()==FL_Right + && props.m_transfo==NONE) + || (Fl::event_key()==FL_Left + && props.m_transfo==ROTATE_180) + || (Fl::event_key()==FL_Up + && props.m_transfo==ROTATE_270) + || (Fl::event_key()==FL_Down + && props.m_transfo==ROTATE_90) ) { + transDX+=10*m_scaleX; + transDW+=10*m_scaleX; + } + else if((Fl::event_key()==FL_Down + && props.m_transfo==NONE) + || (Fl::event_key()==FL_Up + && props.m_transfo==ROTATE_180) + || (Fl::event_key()==FL_Right + && props.m_transfo==ROTATE_270) + || (Fl::event_key()==FL_Left + && props.m_transfo==ROTATE_90) ) { + transDY+=10*m_scaleY; + transDH+=10*m_scaleY; + } + else if((Fl::event_key()==FL_Up + && props.m_transfo==NONE) + || (Fl::event_key()==FL_Down + && props.m_transfo==ROTATE_180) + || (Fl::event_key()==FL_Left + && props.m_transfo==ROTATE_270) + || (Fl::event_key()==FL_Right + && props.m_transfo==ROTATE_90) ) { + transDY-=10*m_scaleY; + transDH-=10*m_scaleY; + } + if(Fl::event_command()) { + props.m_winW = max(1, min(transDW, + props.m_ovWin->getWidth())); + props.m_winH = max(1, min(transDH, + props.m_ovWin->getHeight())); + props.m_winX = max(0, min(props.m_winX, + props.m_ovWin->getWidth() + - props.m_winW)); + props.m_winY = max(0, min(props.m_winY, + props.m_ovWin->getHeight() + - props.m_winH)); + } + else { + props.m_winX = max(0, min(transDX, + props.m_ovWin->getWidth() + - props.m_winW)); + props.m_winY = max(0, min(transDY, + props.m_ovWin->getHeight() + - props.m_winH)); + } + recomputePixels(); + res=1; + }break; + } + } + }break; + case FL_KEYUP: { + if(m_highlight && Fl::event_key()==FL_Shift_L) { + m_editing=false; + res=1; + } + }break; + case FL_MOVE:{ + if(!m_editing) { + int inPosX = Fl::event_x()-x(); + int inPosY = Fl::event_y()-y(); + if(inPosX>OUTER && inPosY>OUTER + && inPosX<w()-OUTER && inPosY<h()-OUTER) { + if(sqrt(pow(inPosX-w(),2)+pow(inPosY-h(),2))<OUTER*3) { + m_state=RESIZE_CUT; + } + else { + m_state=MOVE_CUT; + } + } + else { + if(sqrt(pow(inPosX-w(),2)+pow(inPosY-h(),2))<OUTER) { + m_state=RESIZE_ZONE; + } + else { + m_state=MOVE_ZONE; + } + } + } + if(m_forcedDragging) { + position(m_dragX+Fl::event_x(), + m_dragY+Fl::event_y()); + } + res=1; + }break; + case FL_ENTER: { + m_highlight=true; + m_editing=false; + res=1; + }break; + case FL_LEAVE: { + m_state=NORMAL_STATE; + m_highlight=false; + m_editing=false; + res=1; + }break; + default:break; + } + if(res==1) { + parent()->redraw(); + } + return res; +} + +void ZoneWidget::forceDraggingZone() { + startDraggingZone(); + m_forcedDragging=true; +} + +void ZoneWidget::startDraggingZone() { + m_dragX=x()-Fl::event_x(); + m_dragY=y()-Fl::event_y(); + m_dragW=w()-Fl::event_x(); + m_dragH=h()-Fl::event_y(); +} + +void ZoneWidget::resize(int nx, int ny, int nw, int nh) { + bool recImg=false; + if(nw!=w() || nh!=h()) { + recImg=true; + } + Fl_Widget::resize(nx, ny, nw, nh); + if(recImg) { + recreateImage(); + recomputePixels(); + } +} + +void ZoneWidget::load(xmlNodePtr zoneNode) { + CutProps& props = m_cutProps[m_curCutProps]; + + char* value = NULL; + + value = (char*)xmlGetProp(zoneNode,(xmlChar*)"win_name"); + if(value!=NULL) { + props.m_ovWinName = string(value); + props.m_ovWin = Controlar::getInstance()->getWindow(props.m_ovWinName); + } + + value = (char*)xmlGetProp(zoneNode,(xmlChar*)"win_x"); + if(value!=NULL) { + props.m_winX = atoi(value); + } + value = (char*)xmlGetProp(zoneNode,(xmlChar*)"win_y"); + if(value!=NULL) { + props.m_winY = atoi(value); + } + value = (char*)xmlGetProp(zoneNode,(xmlChar*)"win_w"); + if(value!=NULL) { + props.m_winW = atoi(value); + } + value = (char*)xmlGetProp(zoneNode,(xmlChar*)"win_h"); + if(value!=NULL) { + props.m_winH = atoi(value); + } + int x=0,y=0,w=20,h=20; + value = (char*)xmlGetProp(zoneNode,(xmlChar*)"zone_x"); + if(value!=NULL) { + x = atoi(value); + } + value = (char*)xmlGetProp(zoneNode,(xmlChar*)"zone_y"); + if(value!=NULL) { + y = atoi(value); + } + value = (char*)xmlGetProp(zoneNode,(xmlChar*)"zone_w"); + if(value!=NULL) { + w = atoi(value); + } + value = (char*)xmlGetProp(zoneNode,(xmlChar*)"zone_h"); + if(value!=NULL) { + h = atoi(value); + } + value = (char*)xmlGetProp(zoneNode,(xmlChar*)"alpha"); + if(value!=NULL) { + props.m_alpha = atoi(value); + } + value = (char*)xmlGetProp(zoneNode,(xmlChar*)"transform"); + if(value!=NULL) { + props.m_transfo = ZONE_TRANSFO(atoi(value)); + } + value = (char*)xmlGetProp(zoneNode,(xmlChar*)"shape"); + if(value!=NULL) { + props.m_shape = ZONE_SHAPE(atoi(value)); + } + value = (char*)xmlGetProp(zoneNode,(xmlChar*)"effect"); + if(value!=NULL) { + setInputEffect(ZONE_INPUT_EFFECT(atoi(value))); + } + value = (char*)xmlGetProp(zoneNode,(xmlChar*)"color"); + if(value!=NULL) { + setColor(ZONE_COLOR(atoi(value))); + } + value = (char*)xmlGetProp(zoneNode,(xmlChar*)"midi_type"); + if(value!=NULL) { + m_midiType=atoi(value); + } + value = (char*)xmlGetProp(zoneNode,(xmlChar*)"midi_channel"); + if(value!=NULL) { + m_midiChannel=atoi(value); + } + value = (char*)xmlGetProp(zoneNode,(xmlChar*)"midi_control"); + if(value!=NULL) { + m_midiControl=atoi(value); + } + value = (char*)xmlGetProp(zoneNode,(xmlChar*)"visible"); + if(value!=NULL) { + m_visibleInScene=atoi(value); + } + value = (char*)xmlGetProp(zoneNode,(xmlChar*)"update"); + if(value!=NULL) { + m_updateInScene=atoi(value); + } + value = (char*)xmlGetProp(zoneNode,(xmlChar*)"content"); + if(value!=NULL) { + m_content=ZONE_CONTENT(atoi(value)); + } + resize(x,y,w,h); +} + +void ZoneWidget::save(xmlNodePtr parentNode) { + CutProps& props = m_cutProps[m_curCutProps]; + xmlNodePtr newNode = xmlNewChild(parentNode, NULL, + BAD_CAST "Zone", NULL); + if(props.m_ovWin) { + xmlNewProp(newNode, BAD_CAST "win_name", + BAD_CAST props.m_ovWinName.c_str()); + } + ostringstream oss1, oss2, oss3, oss4, oss5, + oss6, oss7, oss8, oss9, oss10, + oss11, oss12, oss13, oss14, oss15, + oss16, oss17, oss18, oss19; + oss1<<props.m_winX; + xmlNewProp(newNode, BAD_CAST "win_x", + BAD_CAST oss1.str().c_str()); + oss2<<props.m_winY; + xmlNewProp(newNode, BAD_CAST "win_y", + BAD_CAST oss2.str().c_str()); + oss3<<props.m_winW; + xmlNewProp(newNode, BAD_CAST "win_w", + BAD_CAST oss3.str().c_str()); + oss4<<props.m_winH; + xmlNewProp(newNode, BAD_CAST "win_h", + BAD_CAST oss4.str().c_str()); + oss5<<x(); + xmlNewProp(newNode, BAD_CAST "zone_x", + BAD_CAST oss5.str().c_str()); + oss6<<y(); + xmlNewProp(newNode, BAD_CAST "zone_y", + BAD_CAST oss6.str().c_str()); + oss7<<w(); + xmlNewProp(newNode, BAD_CAST "zone_w", + BAD_CAST oss7.str().c_str()); + oss8<<h(); + xmlNewProp(newNode, BAD_CAST "zone_h", + BAD_CAST oss8.str().c_str()); + oss9<<props.m_alpha; + xmlNewProp(newNode, BAD_CAST "alpha", + BAD_CAST oss9.str().c_str()); + oss10<<props.m_transfo; + xmlNewProp(newNode, BAD_CAST "transform", + BAD_CAST oss10.str().c_str()); + oss11<<props.m_shape; + xmlNewProp(newNode, BAD_CAST "shape", + BAD_CAST oss11.str().c_str()); + oss12<<props.m_effect; + xmlNewProp(newNode, BAD_CAST "effect", + BAD_CAST oss12.str().c_str()); + oss13<<m_midiType; + xmlNewProp(newNode, BAD_CAST "midi_type", + BAD_CAST oss13.str().c_str()); + oss14<<m_midiChannel; + xmlNewProp(newNode, BAD_CAST "midi_channel", + BAD_CAST oss14.str().c_str()); + oss15<<m_midiControl; + xmlNewProp(newNode, BAD_CAST "midi_control", + BAD_CAST oss15.str().c_str()); + oss16<<props.m_color; + xmlNewProp(newNode, BAD_CAST "color", + BAD_CAST oss16.str().c_str()); + oss17<<m_visibleInScene; + xmlNewProp(newNode, BAD_CAST "visible", + BAD_CAST oss17.str().c_str()); + oss18<<m_updateInScene; + xmlNewProp(newNode, BAD_CAST "update", + BAD_CAST oss18.str().c_str()); + oss19<<m_content; + xmlNewProp(newNode, BAD_CAST "content", + BAD_CAST oss19.str().c_str()); +} + + diff --git a/src/ZoneWidget.hpp b/src/ZoneWidget.hpp new file mode 100644 index 0000000..ebbbe36 --- /dev/null +++ b/src/ZoneWidget.hpp @@ -0,0 +1,195 @@ +/*************************************************************************** + * ZoneWidget.hpp + * Part of + * 2016- Florent Berthaut + * hitmuri.net + ****************************************************************************/ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef ZoneWidget_h +#define ZoneWidget_h + +#include <FL/Fl.H> +#include <FL/Fl_Widget.H> +#include <FL/fl_draw.H> +#include <GL/glew.h> +#include <FL/gl.h> + +#include <libxml/parser.h> +#include <libxml/tree.h> + +#include <sys/time.h> +#include <string> +#include <vector> +#include <map> + +class CutWindow; + +class ZoneWidget : public Fl_Widget { + + public: + enum ZONE_TRANSFO{NONE, ROTATE_90, ROTATE_180, ROTATE_270, NB_TRANSFO}; + enum ZONE_SHAPE{BOX, CIRCLE, FRAME, NB_SHAPES}; + enum ZONE_INPUT_EFFECT{NO_EFFECT, SHOW, HIDE, NB_EFFECTS}; + enum ZONE_COLOR{NORMAL, INVERT, NB_COLORS}; + enum ZONE_VISIBLE{VISIBLE_ALL, VISIBLE_CURRENT}; + enum ZONE_UPDATE{UPDATE_ALL, UPDATE_CURRENT, NB_UPDATES}; + enum ZONE_CONTENT{CONTENT_GLOBAL, CONTENT_LOCAL, NB_CONTENTS}; + enum ZONE_STATE{NORMAL_STATE,MOVE_CUT,RESIZE_CUT,MOVE_ZONE,RESIZE_ZONE}; + + static const int INNER=20; + static const int OUTER=20; + + struct CutProps { + CutProps(): m_ovWin(NULL), m_ovWinName(""), m_winX(0), m_winY(0), + m_winW(0), m_winH(0), m_alpha(255), m_transfo(NONE), + m_color(NORMAL), m_shape(BOX), m_effect(NO_EFFECT){} + CutWindow* m_ovWin; + std::string m_ovWinName; + int m_winX, m_winY; + int m_winW, m_winH; + int m_alpha; + ZONE_TRANSFO m_transfo; + ZONE_COLOR m_color; + ZONE_SHAPE m_shape; + ZONE_INPUT_EFFECT m_effect; + }; + + public: + ZoneWidget(int x=0, int y=0, int w=10, int h=10); + ~ZoneWidget(); + void copy(ZoneWidget*); + inline void draw(){drawZone(-1);} + void drawZone(const int&); + int handle(int event); + void setCutWin(CutWindow* ow); + CutWindow* getCutWin(); + void save(xmlNodePtr parentNode); + void load(xmlNodePtr); + void resize(int x, int y, int w, int h); + inline void setID(const unsigned int& id){m_id=id;} + inline const unsigned int& getID(){return m_id;} + inline void setAlpha(const int& alpha) { + m_cutProps[m_curCutProps].m_alpha=alpha; + recomputePixels(); + } + inline const int& getAlpha(){return m_cutProps[m_curCutProps].m_alpha;} + inline void setTransformation(const ZONE_TRANSFO& transfo) { + m_cutProps[m_curCutProps].m_transfo=transfo; + recomputePixels(); + } + inline const ZONE_TRANSFO& getTransformation() { + return m_cutProps[m_curCutProps].m_transfo; + } + inline void setShape(const ZONE_SHAPE& shape) { + m_cutProps[m_curCutProps].m_shape=shape; + recomputePixels(); + } + inline const ZONE_SHAPE& getShape() { + return m_cutProps[m_curCutProps].m_shape; + } + void setScene(const int& scene); + void setInputEffect(const ZONE_INPUT_EFFECT& effect); + inline const ZONE_INPUT_EFFECT& getEffect() { + return m_cutProps[m_curCutProps].m_effect; + } + void setColor(const ZONE_COLOR& color) { + m_cutProps[m_curCutProps].m_color=color; + } + inline const ZONE_COLOR& getColor() { + return m_cutProps[m_curCutProps].m_color; + } + void setVisible(const int& vis=-1); + void setUpdate(const int& up=-1); + inline int getUpdate(){return m_updateInScene;} + inline ZONE_UPDATE getZoneUpdate() { + return ZONE_UPDATE(m_updateInScene>=0); + } + void setContent(const ZONE_CONTENT& cont); + inline const ZONE_CONTENT& getContent() { + return m_content; + } + inline void learnMidi(){m_midiLearning=true;} + void refreshCutWin(); + void startDraggingZone(); + void forceDraggingZone(); + inline void setRateReached(){m_rateReached=true;} + + void processMidi(const int& midiType, + const int& midiChannel, + const int& midiControl, + const int& midiValue); + + void recomputePixels(); + + private: + void recreateImage(); + + private: + unsigned int m_id; +// CutWindow* m_ovWin; +// std::string m_ovWinName; + Fl_RGB_Image* m_image; + uchar* m_imageData; +/* + int m_winX, m_winY; + int m_winW, m_winH; +*/ + std::map<int, CutProps> m_cutProps; + static const int GLOBAL_PROPS=-1; + int m_curCutProps; + int m_currentScene; + + ZONE_STATE m_state; + bool m_editing; + bool m_highlight; + bool m_dragging; + bool m_forcedDragging; + float m_dragX, m_dragY; + float m_dragW, m_dragH; + float m_dragStartX, m_dragStartY; + float m_scaleX, m_scaleY; + int m_nbPixPerRow; + std::vector<int> m_srcIndices; + std::vector<std::vector<int> > m_srcCoords; + std::vector<int> m_destIndices; +/* + int m_alpha; + ZONE_TRANSFO m_transfo; + ZONE_SHAPE m_shape; +*/ + bool m_rateReached; +// ZONE_COLOR m_color; + ZONE_CONTENT m_content; + bool m_midiLearning; + int m_midiControl; + int m_midiChannel; + int m_midiType; +// ZONE_INPUT_EFFECT m_effect; + timeval m_effectDelay; + timeval m_effectStartTime; + bool m_hiddenByEffect; + bool m_effectDelayOver; + bool m_initialized; + float* m_coordImage; + int m_visibleInScene; + int m_updateInScene; +}; + + +#endif + diff --git a/src/mac/MacWindow.cpp b/src/mac/MacWindow.cpp new file mode 100644 index 0000000..5c30444 --- /dev/null +++ b/src/mac/MacWindow.cpp @@ -0,0 +1,172 @@ +/*************************************************************************** + * MacWindow.cpp + * Part of + * 2016- Florent Berthaut + * hitmuri.net + ****************************************************************************/ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "MacWindow.hpp" +#include <iostream> + +#include "../Controlar.hpp" + +using namespace std; + +MacWindow::MacWindow(CutWindowsManager* man, CGWindowID id): CutWindow(man) { + CGRect rect = CGRectMake(0, 0, 100, 100); + m_idCArray[0] = id; + m_idArray = CFArrayCreate(NULL, (const void **) m_idCArray, 1, NULL); + CFArrayRef descArray = CGWindowListCreateDescriptionFromArray(m_idArray); + if(CFArrayGetCount(descArray)>0) { + CFDictionaryRef description = + (CFDictionaryRef) CFArrayGetValueAtIndex(descArray, 0); + if (CFDictionaryContainsKey(description, kCGWindowBounds)) { + CFDictionaryRef bounds = + (CFDictionaryRef) CFDictionaryGetValue(description, + kCGWindowBounds); + if (bounds) { + CGRectMakeWithDictionaryRepresentation(bounds, &rect); + } + } + } + CFRelease(descArray); + + m_posX = rect.origin.x; + m_posY = rect.origin.y; + m_width = rect.size.width; + m_height = rect.size.height; + m_offsetX = m_posX; + m_offsetY = m_posY; + m_pixPerRow = m_width; + m_isBGR=true; + m_needsOffset=true; + m_pixPerRow=m_width; + m_grabbed=false; + m_defaultImgData = new uchar[m_width*m_height*3]; +} + +MacWindow::~MacWindow() { + CFRelease(m_idArray); +} + +int MacWindow::computeNbPixPerRow(const int& srcW, const int& srcH) { + CGImageRef img = CGWindowListCreateImageFromArray(CGRectNull, + m_idArray, + kCGWindowImageBoundsIgnoreFraming); + + int nb = CGImageGetBytesPerRow(img)/4; + m_width=CGImageGetWidth(img)*Controlar::getInstance()->getDisplayScale(); + m_height=CGImageGetHeight(img)*Controlar::getInstance()->getDisplayScale(); + CFRelease(img); + return nb; +} + +void MacWindow::getPixels(const int& srcX, const int& srcY, + const int& srcW, const int& srcH, + const vector<int>& srcIndices, + const vector<vector<int> >& srcCoords, + const vector<int>& destIndices, + const uchar& alpha, + const ZoneWidget::ZONE_COLOR& color, + uchar* destImg) { + //CGRect rect = CGRectMake(m_posX+srcX, m_posY+srcY, srcW, srcH); + //CGImageRef img = CGWindowListCreateImageFromArray(rect, + CGImageRef img = CGWindowListCreateImageFromArray(CGRectNull, + m_idArray, + kCGWindowImageBoundsIgnoreFraming); + if(img!=NULL) { + CFDataRef data = CGDataProviderCopyData(CGImageGetDataProvider(img)); + uchar* buf = (uchar*)CFDataGetBytePtr(data); + vector<int>::const_iterator itId = srcIndices.begin(); + vector<vector<int> >::const_iterator itCo = srcCoords.begin(); + vector<int>::const_iterator itDe = destIndices.begin(); + for(; itCo!=srcCoords.end(); ++itCo) { + if((*itCo)[0]>=0) { + //copy rgb + for(int c=0; c<3; ++c) { + destImg[(*itDe)] = (color==ZoneWidget::NORMAL) + ?buf[(*itId)] + :255-buf[(*itId)]; + ++itId; + ++itDe; + } + //copy alpha + destImg[(*itDe)] = alpha; + ++itId; + ++itDe; + } + else { //black + for(int c=0; c<4; ++c) { + destImg[(*itDe)] = 0; + ++itId; + ++itDe; + } + } + } + //release CG stuff + CFRelease(data); + CFRelease(img); + } + else { + cout<<"Error getting image of window "<<m_name<<endl; + } +} + +void MacWindow::releaseImage() { + if(m_grabbed) { + CFRelease(m_data); + CFRelease(m_img); + m_grabbed=false; + } +} + +uchar* MacWindow::grabImage() { + if(!m_grabbed) { + m_img = CGWindowListCreateImageFromArray(CGRectNull, + m_idArray, + kCGWindowImageBoundsIgnoreFraming); + if(m_img) { + m_data = CGDataProviderCopyData(CGImageGetDataProvider(m_img)); + m_imgData = (uchar*)CFDataGetBytePtr(m_data); + m_grabbed=true; + } + else { + m_imgData=m_defaultImgData; + } + } + return m_imgData; +} + +void MacWindow::storeImage(const int& id) { + int size = m_pixPerRow*m_height*4; + if(m_storedImgData.find(id)!=m_storedImgData.end()) { + delete [] m_storedImgData[id]; + } + m_storedImgData[id] = new uchar[size]; + memcpy(m_storedImgData[id], m_imgData, size); +} + +uchar* MacWindow::retrieveImage(const int& id) { + map<int, uchar*>::iterator itSt = m_storedImgData.find(id); + if(itSt!=m_storedImgData.end()) { + return itSt->second; + } + return NULL; +} + + diff --git a/src/mac/MacWindow.hpp b/src/mac/MacWindow.hpp new file mode 100644 index 0000000..9ace8e9 --- /dev/null +++ b/src/mac/MacWindow.hpp @@ -0,0 +1,62 @@ +/*************************************************************************** + * MacWindow.hpp + * Part of + * 2016- Florent Berthaut + * hitmuri.net + ****************************************************************************/ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + + +#ifndef MacWindow_h +#define MacWindow_h + +#include <vector> +#include <string> + +#include "../CutWindow.hpp" +#import <ApplicationServices/ApplicationServices.h> +//#import <Cocoa/Cocoa.h> +//#import <QuartzCore/QuartzCore.h> +//#import <AppKit/AppKit.h> + +class MacWindow: public CutWindow { + public : + MacWindow(CutWindowsManager* man, CGWindowID winID); + virtual ~MacWindow(); + virtual void getPixels(const int& x, const int& y, + const int& sx, const int& sy, + const std::vector<int>& srcIndices, + const std::vector<std::vector<int> >& srcCoords, + const std::vector<int>& destIndices, + const uchar& alpha, + const ZoneWidget::ZONE_COLOR& color, + uchar* destImg); + int computeNbPixPerRow(const int& srcW, const int& srcH); + virtual uchar* grabImage(); + virtual void releaseImage(); + virtual void storeImage(const int& id); + virtual uchar* retrieveImage(const int& id); + + protected: + CGWindowID m_idCArray[1]; + CFArrayRef m_idArray; + CGImageRef m_img; + CFDataRef m_data; +}; + +#endif + diff --git a/src/mac/MacWindowsManager.cpp b/src/mac/MacWindowsManager.cpp new file mode 100644 index 0000000..43d09d0 --- /dev/null +++ b/src/mac/MacWindowsManager.cpp @@ -0,0 +1,89 @@ +/*************************************************************************** + * MacWindowsManager.cpp + * Part of + * 2016- Florent Berthaut + * hitmuri.net + ****************************************************************************/ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* + Relies on code from: + https://github.com/JoelSjogren/window-copy/blob/master/windowutils.cpp +*/ + +#include "MacWindowsManager.hpp" +#include <iostream> +#include <stdlib.h> + +#include "../Controlar.hpp" + +using namespace std; + +MacWindowsManager::MacWindowsManager(): CutWindowsManager() {} + +MacWindowsManager::~MacWindowsManager() {} + +void MacWindowsManager::updateWindowsList() { + map<unsigned int, CutWindow*> eraseWindows = m_windowMap; + m_windowList.clear(); + CFArrayRef windowArray = + CGWindowListCreate(kCGWindowListExcludeDesktopElements + |kCGWindowListOptionOnScreenOnly, kCGNullWindowID); + CFArrayRef descArray = CGWindowListCreateDescriptionFromArray(windowArray); + if(descArray!=NULL) { + for(int i=0; i<CFArrayGetCount(descArray); ++i) { + CFDictionaryRef win = + (CFDictionaryRef)CFArrayGetValueAtIndex(descArray,i); + CFStringRef name = + (CFStringRef) CFDictionaryGetValue(win, kCGWindowName); + if(name!=NULL) { + if(CFStringGetLength(name)>0) { + char tempStr[128]; + CFStringGetCString(name,tempStr,128,kCFStringEncodingUTF8); + string nameStr(tempStr); + unsigned int winID=(uintptr_t) + CFArrayGetValueAtIndex(windowArray,i); + if(m_windowMap.find(winID)==m_windowMap.end()) { + MacWindow* newWin + = new MacWindow(this, + (CGWindowID)(uintptr_t) + CFArrayGetValueAtIndex(windowArray,i)); + newWin->setName(nameStr); + newWin->setWinID(winID); + m_windowMap[winID]=newWin; + } + m_windowList.push_back(m_windowMap[winID]); + eraseWindows.erase(winID); + } + } + CFRelease(win); + } + + //remove/delete windows not present anymore + map<unsigned int, CutWindow*>::iterator itWin = eraseWindows.begin(); + for(; itWin!=eraseWindows.end(); ++itWin) { + m_windowMap.erase(itWin->first); + delete itWin->second; + } + } + else { + cout<<"Error: No windows found"<<endl; + } + +} + + diff --git a/src/mac/MacWindowsManager.hpp b/src/mac/MacWindowsManager.hpp new file mode 100644 index 0000000..f3d5590 --- /dev/null +++ b/src/mac/MacWindowsManager.hpp @@ -0,0 +1,42 @@ +/*************************************************************************** + * MacWindowsManager.hpp + * Part of Over + * 2013 Florent Berthaut + * hitmuri.net + ****************************************************************************/ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + + +#ifndef MacWindowsManager_h +#define MacWindowsManager_h + +#include <vector> +#include <string> + +#include "../CutWindowsManager.hpp" +#include "MacWindow.hpp" + +class MacWindowsManager : public CutWindowsManager { + public : + MacWindowsManager(); + virtual ~MacWindowsManager(); + virtual void updateWindowsList(); + protected: +}; + +#endif + diff --git a/src/osc/ip/IpEndpointName.cpp b/src/osc/ip/IpEndpointName.cpp new file mode 100755 index 0000000..33fdd98 --- /dev/null +++ b/src/osc/ip/IpEndpointName.cpp @@ -0,0 +1,81 @@ +/* + oscpack -- Open Sound Control packet manipulation library + http://www.audiomulch.com/~rossb/oscpack + + Copyright (c) 2004-2005 Ross Bencina <rossb@audiomulch.com> + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + Any person wishing to distribute modifications to the Software is + requested to send the modifications to the original developer so that + they can be incorporated into the canonical version. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +#include "IpEndpointName.h" + +#include <stdio.h> + +#include "NetworkingUtils.h" + + +unsigned long IpEndpointName::GetHostByName( const char *s ) +{ + return ::GetHostByName(s); +} + + +void IpEndpointName::AddressAsString( char *s ) const +{ + if( address == ANY_ADDRESS ){ + sprintf( s, "<any>" ); + }else{ + sprintf( s, "%d.%d.%d.%d", + (int)((address >> 24) & 0xFF), + (int)((address >> 16) & 0xFF), + (int)((address >> 8) & 0xFF), + (int)(address & 0xFF) ); + } +} + + +void IpEndpointName::AddressAndPortAsString( char *s ) const +{ + if( port == ANY_PORT ){ + if( address == ANY_ADDRESS ){ + sprintf( s, "<any>:<any>" ); + }else{ + sprintf( s, "%d.%d.%d.%d:<any>", + (int)((address >> 24) & 0xFF), + (int)((address >> 16) & 0xFF), + (int)((address >> 8) & 0xFF), + (int)(address & 0xFF) ); + } + }else{ + if( address == ANY_ADDRESS ){ + sprintf( s, "<any>:%d", port ); + }else{ + sprintf( s, "%d.%d.%d.%d:%d", + (int)((address >> 24) & 0xFF), + (int)((address >> 16) & 0xFF), + (int)((address >> 8) & 0xFF), + (int)(address & 0xFF), + (int)port ); + } + } +} diff --git a/src/osc/ip/IpEndpointName.h b/src/osc/ip/IpEndpointName.h new file mode 100755 index 0000000..c7b078e --- /dev/null +++ b/src/osc/ip/IpEndpointName.h @@ -0,0 +1,74 @@ +/* + oscpack -- Open Sound Control packet manipulation library + http://www.audiomulch.com/~rossb/oscpack + + Copyright (c) 2004-2005 Ross Bencina <rossb@audiomulch.com> + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + Any person wishing to distribute modifications to the Software is + requested to send the modifications to the original developer so that + they can be incorporated into the canonical version. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +#ifndef INCLUDED_IPENDPOINTNAME_H +#define INCLUDED_IPENDPOINTNAME_H + + +class IpEndpointName{ + static unsigned long GetHostByName( const char *s ); +public: + static const unsigned long ANY_ADDRESS = 0xFFFFFFFF; + static const int ANY_PORT = -1; + + IpEndpointName() + : address( ANY_ADDRESS ), port( ANY_PORT ) {} + IpEndpointName( int port_ ) + : address( ANY_ADDRESS ), port( port_ ) {} + IpEndpointName( unsigned long ipAddress_, int port_ ) + : address( ipAddress_ ), port( port_ ) {} + IpEndpointName( const char *addressName, int port_=ANY_PORT ) + : address( GetHostByName( addressName ) ) + , port( port_ ) {} + IpEndpointName( int addressA, int addressB, int addressC, int addressD, int port_=ANY_PORT ) + : address( ( (addressA << 24) | (addressB << 16) | (addressC << 8) | addressD ) ) + , port( port_ ) {} + + // address and port are maintained in host byte order here + unsigned long address; + int port; + + enum { ADDRESS_STRING_LENGTH=17 }; + void AddressAsString( char *s ) const; + + enum { ADDRESS_AND_PORT_STRING_LENGTH=23}; + void AddressAndPortAsString( char *s ) const; +}; + +inline bool operator==( const IpEndpointName& lhs, const IpEndpointName& rhs ) +{ + return (lhs.address == rhs.address && lhs.port == rhs.port ); +} + +inline bool operator!=( const IpEndpointName& lhs, const IpEndpointName& rhs ) +{ + return !(lhs == rhs); +} + +#endif /* INCLUDED_IPENDPOINTNAME_H */ diff --git a/src/osc/ip/NetworkingUtils.h b/src/osc/ip/NetworkingUtils.h new file mode 100755 index 0000000..0d6901c --- /dev/null +++ b/src/osc/ip/NetworkingUtils.h @@ -0,0 +1,49 @@ +/* + oscpack -- Open Sound Control packet manipulation library + http://www.audiomulch.com/~rossb/oscpack + + Copyright (c) 2004-2005 Ross Bencina <rossb@audiomulch.com> + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + Any person wishing to distribute modifications to the Software is + requested to send the modifications to the original developer so that + they can be incorporated into the canonical version. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +#ifndef INCLUDED_NETWORKINGUTILS_H +#define INCLUDED_NETWORKINGUTILS_H + + +// in general NetworkInitializer is only used internally, but if you're +// application creates multiple sockets from different threads at runtime you +// should instantiate one of these in main just to make sure the networking +// layer is initialized. +class NetworkInitializer{ +public: + NetworkInitializer(); + ~NetworkInitializer(); +}; + + +// return ip address of host name in host byte order +unsigned long GetHostByName( const char *name ); + + +#endif /* INCLUDED_NETWORKINGUTILS_H */ diff --git a/src/osc/ip/PacketListener.h b/src/osc/ip/PacketListener.h new file mode 100755 index 0000000..6647209 --- /dev/null +++ b/src/osc/ip/PacketListener.h @@ -0,0 +1,43 @@ +/* + oscpack -- Open Sound Control packet manipulation library + http://www.audiomulch.com/~rossb/oscpack + + Copyright (c) 2004-2005 Ross Bencina <rossb@audiomulch.com> + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + Any person wishing to distribute modifications to the Software is + requested to send the modifications to the original developer so that + they can be incorporated into the canonical version. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +#ifndef INCLUDED_PACKETLISTENER_H +#define INCLUDED_PACKETLISTENER_H + + +class IpEndpointName; + +class PacketListener{ +public: + virtual ~PacketListener() {} + virtual void ProcessPacket( const char *data, int size, + const IpEndpointName& remoteEndpoint ) = 0; +}; + +#endif /* INCLUDED_PACKETLISTENER_H */ diff --git a/src/osc/ip/TimerListener.h b/src/osc/ip/TimerListener.h new file mode 100755 index 0000000..82b1181 --- /dev/null +++ b/src/osc/ip/TimerListener.h @@ -0,0 +1,40 @@ +/* + oscpack -- Open Sound Control packet manipulation library + http://www.audiomulch.com/~rossb/oscpack + + Copyright (c) 2004-2005 Ross Bencina <rossb@audiomulch.com> + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + Any person wishing to distribute modifications to the Software is + requested to send the modifications to the original developer so that + they can be incorporated into the canonical version. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +#ifndef INCLUDED_TIMERLISTENER_H +#define INCLUDED_TIMERLISTENER_H + + +class TimerListener{ +public: + virtual ~TimerListener() {} + virtual void TimerExpired() = 0; +}; + +#endif /* INCLUDED_TIMERLISTENER_H */ diff --git a/src/osc/ip/UdpSocket.h b/src/osc/ip/UdpSocket.h new file mode 100755 index 0000000..6d9c26d --- /dev/null +++ b/src/osc/ip/UdpSocket.h @@ -0,0 +1,158 @@ +/* + oscpack -- Open Sound Control packet manipulation library + http://www.audiomulch.com/~rossb/oscpack + + Copyright (c) 2004-2005 Ross Bencina <rossb@audiomulch.com> + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + Any person wishing to distribute modifications to the Software is + requested to send the modifications to the original developer so that + they can be incorporated into the canonical version. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +#ifndef INCLUDED_UDPSOCKET_H +#define INCLUDED_UDPSOCKET_H + +#ifndef INCLUDED_NETWORKINGUTILITIES_H +#include "NetworkingUtils.h" +#endif /* INCLUDED_NETWORKINGUTILITIES_H */ + +#ifndef INCLUDED_IPENDPOINTNAME_H +#include "IpEndpointName.h" +#endif /* INCLUDED_IPENDPOINTNAME_H */ + + +class PacketListener; +class TimerListener; + +class UdpSocket; + +class SocketReceiveMultiplexer{ + class Implementation; + Implementation *impl_; + + friend class UdpSocket; + +public: + SocketReceiveMultiplexer(); + ~SocketReceiveMultiplexer(); + + // only call the attach/detach methods _before_ calling Run + + // only one listener per socket, each socket at most once + void AttachSocketListener( UdpSocket *socket, PacketListener *listener ); + void DetachSocketListener( UdpSocket *socket, PacketListener *listener ); + + void AttachPeriodicTimerListener( int periodMilliseconds, TimerListener *listener ); + void AttachPeriodicTimerListener( + int initialDelayMilliseconds, int periodMilliseconds, TimerListener *listener ); + void DetachPeriodicTimerListener( TimerListener *listener ); + + void Run(); // loop and block processing messages indefinitely + void RunUntilSigInt(); + void Break(); // call this from a listener to exit once the listener returns + void AsynchronousBreak(); // call this from another thread or signal handler to exit the Run() state +}; + + +class UdpSocket{ + class Implementation; + Implementation *impl_; + + friend class SocketReceiveMultiplexer::Implementation; + +public: + + // ctor throws std::runtime_error if there's a problem + // initializing the socket. + UdpSocket(); + virtual ~UdpSocket(); + + // the socket is created in an unbound, unconnected state + // such a socket can only be used to send to an arbitrary + // address using SendTo(). To use Send() you need to first + // connect to a remote endpoint using Connect(). To use + // ReceiveFrom you need to first bind to a local endpoint + // using Bind(). + + // retrieve the local endpoint name when sending to 'to' + IpEndpointName LocalEndpointFor( const IpEndpointName& remoteEndpoint ) const; + + // Connect to a remote endpoint which is used as the target + // for calls to Send() + void Connect( const IpEndpointName& remoteEndpoint ); + void Send( const char *data, int size ); + void SendTo( const IpEndpointName& remoteEndpoint, const char *data, int size ); + + + // Bind a local endpoint to receive incoming data. Endpoint + // can be 'any' for the system to choose an endpoint + void Bind( const IpEndpointName& localEndpoint ); + bool IsBound() const; + + int ReceiveFrom( IpEndpointName& remoteEndpoint, char *data, int size ); +}; + + +// convenience classes for transmitting and receiving +// they just call Connect and/or Bind in the ctor. +// note that you can still use a receive socket +// for transmitting etc + +class UdpTransmitSocket : public UdpSocket{ +public: + UdpTransmitSocket( const IpEndpointName& remoteEndpoint ) + { Connect( remoteEndpoint ); } +}; + + +class UdpReceiveSocket : public UdpSocket{ +public: + UdpReceiveSocket( const IpEndpointName& localEndpoint ) + { Bind( localEndpoint ); } +}; + + +// UdpListeningReceiveSocket provides a simple way to bind one listener +// to a single socket without having to manually set up a SocketReceiveMultiplexer + +class UdpListeningReceiveSocket : public UdpSocket{ + SocketReceiveMultiplexer mux_; + PacketListener *listener_; +public: + UdpListeningReceiveSocket( const IpEndpointName& localEndpoint, PacketListener *listener ) + : listener_( listener ) + { + Bind( localEndpoint ); + mux_.AttachSocketListener( this, listener_ ); + } + + ~UdpListeningReceiveSocket() + { mux_.DetachSocketListener( this, listener_ ); } + + // see SocketReceiveMultiplexer above for the behaviour of these methods... + void Run() { mux_.Run(); } + void RunUntilSigInt() { mux_.RunUntilSigInt(); } + void Break() { mux_.Break(); } + void AsynchronousBreak() { mux_.AsynchronousBreak(); } +}; + + +#endif /* INCLUDED_UDPSOCKET_H */ diff --git a/src/osc/ip/posix/NetworkingUtils.cpp b/src/osc/ip/posix/NetworkingUtils.cpp new file mode 100755 index 0000000..d3e595c --- /dev/null +++ b/src/osc/ip/posix/NetworkingUtils.cpp @@ -0,0 +1,57 @@ +/* + oscpack -- Open Sound Control packet manipulation library + http://www.audiomulch.com/~rossb/oscpack + + Copyright (c) 2004-2005 Ross Bencina <rossb@audiomulch.com> + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + Any person wishing to distribute modifications to the Software is + requested to send the modifications to the original developer so that + they can be incorporated into the canonical version. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +#include "../NetworkingUtils.h" + +#include <netdb.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <string.h> +#include <stdio.h> + + + +NetworkInitializer::NetworkInitializer() {} + +NetworkInitializer::~NetworkInitializer() {} + + +unsigned long GetHostByName( const char *name ) +{ + unsigned long result = 0; + + struct hostent *h = gethostbyname( name ); + if( h ){ + struct in_addr a; + memcpy( &a, h->h_addr_list[0], h->h_length ); + result = ntohl(a.s_addr); + } + + return result; +} diff --git a/src/osc/ip/posix/UdpSocket.cpp b/src/osc/ip/posix/UdpSocket.cpp new file mode 100755 index 0000000..01565ca --- /dev/null +++ b/src/osc/ip/posix/UdpSocket.cpp @@ -0,0 +1,546 @@ +/* + oscpack -- Open Sound Control packet manipulation library + http://www.audiomulch.com/~rossb/oscpack + + Copyright (c) 2004-2005 Ross Bencina <rossb@audiomulch.com> + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + Any person wishing to distribute modifications to the Software is + requested to send the modifications to the original developer so that + they can be incorporated into the canonical version. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +#include "..//UdpSocket.h" + +#include <vector> +#include <algorithm> +#include <stdexcept> +#include <assert.h> +#include <signal.h> +#include <math.h> +#include <errno.h> +#include <string.h> // for memset + +#include <pthread.h> +#include <unistd.h> +#include <stdlib.h> +#include <stdio.h> +#include <netdb.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/time.h> +#include <netinet/in.h> // for sockaddr_in + +#include "../PacketListener.h" +#include "../TimerListener.h" + + +#if defined(__APPLE__) && !defined(_SOCKLEN_T) +// pre system 10.3 didn have socklen_t +typedef ssize_t socklen_t; +#endif + + +static void SockaddrFromIpEndpointName( struct sockaddr_in& sockAddr, const IpEndpointName& endpoint ) +{ + memset( (char *)&sockAddr, 0, sizeof(sockAddr ) ); + sockAddr.sin_family = AF_INET; + + sockAddr.sin_addr.s_addr = + (endpoint.address == IpEndpointName::ANY_ADDRESS) + ? INADDR_ANY + : htonl( endpoint.address ); + + sockAddr.sin_port = + (endpoint.port == IpEndpointName::ANY_PORT) + ? 0 + : htons( endpoint.port ); +} + + +static IpEndpointName IpEndpointNameFromSockaddr( const struct sockaddr_in& sockAddr ) +{ + return IpEndpointName( + (sockAddr.sin_addr.s_addr == INADDR_ANY) + ? IpEndpointName::ANY_ADDRESS + : ntohl( sockAddr.sin_addr.s_addr ), + (sockAddr.sin_port == 0) + ? IpEndpointName::ANY_PORT + : ntohs( sockAddr.sin_port ) + ); +} + + +class UdpSocket::Implementation{ + bool isBound_; + bool isConnected_; + + int socket_; + struct sockaddr_in connectedAddr_; + struct sockaddr_in sendToAddr_; + +public: + + Implementation() + : isBound_( false ) + , isConnected_( false ) + , socket_( -1 ) + { + if( (socket_ = socket( AF_INET, SOCK_DGRAM, 0 )) == -1 ){ + throw std::runtime_error("unable to create udp socket\n"); + } + + memset( &sendToAddr_, 0, sizeof(sendToAddr_) ); + sendToAddr_.sin_family = AF_INET; + } + + ~Implementation() + { + if (socket_ != -1) close(socket_); + } + + IpEndpointName LocalEndpointFor( const IpEndpointName& remoteEndpoint ) const + { + assert( isBound_ ); + + // first connect the socket to the remote server + + struct sockaddr_in connectSockAddr; + SockaddrFromIpEndpointName( connectSockAddr, remoteEndpoint ); + + if (connect(socket_, (struct sockaddr *)&connectSockAddr, sizeof(connectSockAddr)) < 0) { + throw std::runtime_error("unable to connect udp socket\n"); + } + + // get the address + + struct sockaddr_in sockAddr; + memset( (char *)&sockAddr, 0, sizeof(sockAddr ) ); + socklen_t length = sizeof(sockAddr); + if (getsockname(socket_, (struct sockaddr *)&sockAddr, &length) < 0) { + throw std::runtime_error("unable to getsockname\n"); + } + + if( isConnected_ ){ + // reconnect to the connected address + + if (connect(socket_, (struct sockaddr *)&connectedAddr_, sizeof(connectedAddr_)) < 0) { + throw std::runtime_error("unable to connect udp socket\n"); + } + + }else{ + // unconnect from the remote address + + struct sockaddr_in unconnectSockAddr; + memset( (char *)&unconnectSockAddr, 0, sizeof(unconnectSockAddr ) ); + unconnectSockAddr.sin_family = AF_UNSPEC; + // address fields are zero + int connectResult = connect(socket_, (struct sockaddr *)&unconnectSockAddr, sizeof(unconnectSockAddr)); + if ( connectResult < 0 && errno != EAFNOSUPPORT ) { + throw std::runtime_error("unable to un-connect udp socket\n"); + } + } + + return IpEndpointNameFromSockaddr( sockAddr ); + } + + void Connect( const IpEndpointName& remoteEndpoint ) + { + SockaddrFromIpEndpointName( connectedAddr_, remoteEndpoint ); + + if (connect(socket_, (struct sockaddr *)&connectedAddr_, sizeof(connectedAddr_)) < 0) { + throw std::runtime_error("unable to connect udp socket\n"); + } + + isConnected_ = true; + } + + void Send( const char *data, int size ) + { + assert( isConnected_ ); + + send( socket_, data, size, 0 ); + } + + void SendTo( const IpEndpointName& remoteEndpoint, const char *data, int size ) + { + sendToAddr_.sin_addr.s_addr = htonl( remoteEndpoint.address ); + sendToAddr_.sin_port = htons( remoteEndpoint.port ); + + sendto( socket_, data, size, 0, (sockaddr*)&sendToAddr_, sizeof(sendToAddr_) ); + } + + void Bind( const IpEndpointName& localEndpoint ) + { + struct sockaddr_in bindSockAddr; + SockaddrFromIpEndpointName( bindSockAddr, localEndpoint ); + + if (bind(socket_, (struct sockaddr *)&bindSockAddr, sizeof(bindSockAddr)) < 0) { + throw std::runtime_error("unable to bind udp socket\n"); + } + + isBound_ = true; + } + + bool IsBound() const { return isBound_; } + + int ReceiveFrom( IpEndpointName& remoteEndpoint, char *data, int size ) + { + assert( isBound_ ); + + struct sockaddr_in fromAddr; + socklen_t fromAddrLen = sizeof(fromAddr); + + int result = recvfrom(socket_, data, size, 0, + (struct sockaddr *) &fromAddr, (socklen_t*)&fromAddrLen); + if( result < 0 ) + return 0; + + remoteEndpoint.address = ntohl(fromAddr.sin_addr.s_addr); + remoteEndpoint.port = ntohs(fromAddr.sin_port); + + return result; + } + + int Socket() { return socket_; } +}; + +UdpSocket::UdpSocket() +{ + impl_ = new Implementation(); +} + +UdpSocket::~UdpSocket() +{ + delete impl_; +} + +IpEndpointName UdpSocket::LocalEndpointFor( const IpEndpointName& remoteEndpoint ) const +{ + return impl_->LocalEndpointFor( remoteEndpoint ); +} + +void UdpSocket::Connect( const IpEndpointName& remoteEndpoint ) +{ + impl_->Connect( remoteEndpoint ); +} + +void UdpSocket::Send( const char *data, int size ) +{ + impl_->Send( data, size ); +} + +void UdpSocket::SendTo( const IpEndpointName& remoteEndpoint, const char *data, int size ) +{ + impl_->SendTo( remoteEndpoint, data, size ); +} + +void UdpSocket::Bind( const IpEndpointName& localEndpoint ) +{ + impl_->Bind( localEndpoint ); +} + +bool UdpSocket::IsBound() const +{ + return impl_->IsBound(); +} + +int UdpSocket::ReceiveFrom( IpEndpointName& remoteEndpoint, char *data, int size ) +{ + return impl_->ReceiveFrom( remoteEndpoint, data, size ); +} + + +struct AttachedTimerListener{ + AttachedTimerListener( int id, int p, TimerListener *tl ) + : initialDelayMs( id ) + , periodMs( p ) + , listener( tl ) {} + int initialDelayMs; + int periodMs; + TimerListener *listener; +}; + + +static bool CompareScheduledTimerCalls( + const std::pair< double, AttachedTimerListener > & lhs, const std::pair< double, AttachedTimerListener > & rhs ) +{ + return lhs.first < rhs.first; +} + + +SocketReceiveMultiplexer *multiplexerInstanceToAbortWithSigInt_ = 0; + +extern "C" /*static*/ void InterruptSignalHandler( int ); +/*static*/ void InterruptSignalHandler( int ) +{ + multiplexerInstanceToAbortWithSigInt_->AsynchronousBreak(); + signal( SIGINT, SIG_DFL ); +} + + +class SocketReceiveMultiplexer::Implementation{ + std::vector< std::pair< PacketListener*, UdpSocket* > > socketListeners_; + std::vector< AttachedTimerListener > timerListeners_; + + volatile bool break_; + int breakPipe_[2]; // [0] is the reader descriptor and [1] the writer + + double GetCurrentTimeMs() const + { + struct timeval t; + + gettimeofday( &t, 0 ); + + return ((double)t.tv_sec*1000.) + ((double)t.tv_usec / 1000.); + } + +public: + Implementation() + { + if( pipe(breakPipe_) != 0 ) + throw std::runtime_error( "creation of asynchronous break pipes failed\n" ); + } + + ~Implementation() + { + close( breakPipe_[0] ); + close( breakPipe_[1] ); + } + + void AttachSocketListener( UdpSocket *socket, PacketListener *listener ) + { + assert( std::find( socketListeners_.begin(), socketListeners_.end(), std::make_pair(listener, socket) ) == socketListeners_.end() ); + // we don't check that the same socket has been added multiple times, even though this is an error + socketListeners_.push_back( std::make_pair( listener, socket ) ); + } + + void DetachSocketListener( UdpSocket *socket, PacketListener *listener ) + { + std::vector< std::pair< PacketListener*, UdpSocket* > >::iterator i = + std::find( socketListeners_.begin(), socketListeners_.end(), std::make_pair(listener, socket) ); + assert( i != socketListeners_.end() ); + + socketListeners_.erase( i ); + } + + void AttachPeriodicTimerListener( int periodMilliseconds, TimerListener *listener ) + { + timerListeners_.push_back( AttachedTimerListener( periodMilliseconds, periodMilliseconds, listener ) ); + } + + void AttachPeriodicTimerListener( int initialDelayMilliseconds, int periodMilliseconds, TimerListener *listener ) + { + timerListeners_.push_back( AttachedTimerListener( initialDelayMilliseconds, periodMilliseconds, listener ) ); + } + + void DetachPeriodicTimerListener( TimerListener *listener ) + { + std::vector< AttachedTimerListener >::iterator i = timerListeners_.begin(); + while( i != timerListeners_.end() ){ + if( i->listener == listener ) + break; + ++i; + } + + assert( i != timerListeners_.end() ); + + timerListeners_.erase( i ); + } + + void Run() + { + break_ = false; + + // configure the master fd_set for select() + + fd_set masterfds, tempfds; + FD_ZERO( &masterfds ); + FD_ZERO( &tempfds ); + + // in addition to listening to the inbound sockets we + // also listen to the asynchronous break pipe, so that AsynchronousBreak() + // can break us out of select() from another thread. + FD_SET( breakPipe_[0], &masterfds ); + int fdmax = breakPipe_[0]; + + for( std::vector< std::pair< PacketListener*, UdpSocket* > >::iterator i = socketListeners_.begin(); + i != socketListeners_.end(); ++i ){ + + if( fdmax < i->second->impl_->Socket() ) + fdmax = i->second->impl_->Socket(); + FD_SET( i->second->impl_->Socket(), &masterfds ); + } + + + // configure the timer queue + double currentTimeMs = GetCurrentTimeMs(); + + // expiry time ms, listener + std::vector< std::pair< double, AttachedTimerListener > > timerQueue_; + for( std::vector< AttachedTimerListener >::iterator i = timerListeners_.begin(); + i != timerListeners_.end(); ++i ) + timerQueue_.push_back( std::make_pair( currentTimeMs + i->initialDelayMs, *i ) ); + std::sort( timerQueue_.begin(), timerQueue_.end(), CompareScheduledTimerCalls ); + + const int MAX_BUFFER_SIZE = 4098; + char *data = new char[ MAX_BUFFER_SIZE ]; + IpEndpointName remoteEndpoint; + + struct timeval timeout; + + while( !break_ ){ + tempfds = masterfds; + + struct timeval *timeoutPtr = 0; + if( !timerQueue_.empty() ){ + double timeoutMs = timerQueue_.front().first - GetCurrentTimeMs(); + if( timeoutMs < 0 ) + timeoutMs = 0; + + // 1000000 microseconds in a second + timeout.tv_sec = (long)(timeoutMs * .001); + timeout.tv_usec = (long)((timeoutMs - (timeout.tv_sec * 1000)) * 1000); + timeoutPtr = &timeout; + } + + if( select( fdmax + 1, &tempfds, 0, 0, timeoutPtr ) < 0 && errno != EINTR ){ + throw std::runtime_error("select failed\n"); + } + + if ( FD_ISSET( breakPipe_[0], &tempfds ) ){ + // clear pending data from the asynchronous break pipe + char c; + read( breakPipe_[0], &c, 1 ); + } + + if( break_ ) + break; + + for( std::vector< std::pair< PacketListener*, UdpSocket* > >::iterator i = socketListeners_.begin(); + i != socketListeners_.end(); ++i ){ + + if( FD_ISSET( i->second->impl_->Socket(), &tempfds ) ){ + + int size = i->second->ReceiveFrom( remoteEndpoint, data, MAX_BUFFER_SIZE ); + if( size > 0 ){ + i->first->ProcessPacket( data, size, remoteEndpoint ); + if( break_ ) + break; + } + } + } + + // execute any expired timers + currentTimeMs = GetCurrentTimeMs(); + bool resort = false; + for( std::vector< std::pair< double, AttachedTimerListener > >::iterator i = timerQueue_.begin(); + i != timerQueue_.end() && i->first <= currentTimeMs; ++i ){ + + i->second.listener->TimerExpired(); + if( break_ ) + break; + + i->first += i->second.periodMs; + resort = true; + } + if( resort ) + std::sort( timerQueue_.begin(), timerQueue_.end(), CompareScheduledTimerCalls ); + } + + delete [] data; + } + + void Break() + { + break_ = true; + } + + void AsynchronousBreak() + { + break_ = true; + + // Send a termination message to the asynchronous break pipe, so select() will return + write( breakPipe_[1], "!", 1 ); + } +}; + + + +SocketReceiveMultiplexer::SocketReceiveMultiplexer() +{ + impl_ = new Implementation(); +} + +SocketReceiveMultiplexer::~SocketReceiveMultiplexer() +{ + delete impl_; +} + +void SocketReceiveMultiplexer::AttachSocketListener( UdpSocket *socket, PacketListener *listener ) +{ + impl_->AttachSocketListener( socket, listener ); +} + +void SocketReceiveMultiplexer::DetachSocketListener( UdpSocket *socket, PacketListener *listener ) +{ + impl_->DetachSocketListener( socket, listener ); +} + +void SocketReceiveMultiplexer::AttachPeriodicTimerListener( int periodMilliseconds, TimerListener *listener ) +{ + impl_->AttachPeriodicTimerListener( periodMilliseconds, listener ); +} + +void SocketReceiveMultiplexer::AttachPeriodicTimerListener( int initialDelayMilliseconds, int periodMilliseconds, TimerListener *listener ) +{ + impl_->AttachPeriodicTimerListener( initialDelayMilliseconds, periodMilliseconds, listener ); +} + +void SocketReceiveMultiplexer::DetachPeriodicTimerListener( TimerListener *listener ) +{ + impl_->DetachPeriodicTimerListener( listener ); +} + +void SocketReceiveMultiplexer::Run() +{ + impl_->Run(); +} + +void SocketReceiveMultiplexer::RunUntilSigInt() +{ + assert( multiplexerInstanceToAbortWithSigInt_ == 0 ); /* at present we support only one multiplexer instance running until sig int */ + multiplexerInstanceToAbortWithSigInt_ = this; + signal( SIGINT, InterruptSignalHandler ); + impl_->Run(); + signal( SIGINT, SIG_DFL ); + multiplexerInstanceToAbortWithSigInt_ = 0; +} + +void SocketReceiveMultiplexer::Break() +{ + impl_->Break(); +} + +void SocketReceiveMultiplexer::AsynchronousBreak() +{ + impl_->AsynchronousBreak(); +} + diff --git a/src/osc/osc/MessageMappingOscPacketListener.h b/src/osc/osc/MessageMappingOscPacketListener.h new file mode 100755 index 0000000..017bf05 --- /dev/null +++ b/src/osc/osc/MessageMappingOscPacketListener.h @@ -0,0 +1,73 @@ +/* + oscpack -- Open Sound Control packet manipulation library + http://www.audiomulch.com/~rossb/oscpack + + Copyright (c) 2004-2005 Ross Bencina <rossb@audiomulch.com> + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + Any person wishing to distribute modifications to the Software is + requested to send the modifications to the original developer so that + they can be incorporated into the canonical version. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +#ifndef INCLUDED_MESSAGEMAPPINGOSCPACKETLISTENER_H +#define INCLUDED_MESSAGEMAPPINGOSCPACKETLISTENER_H + +#include <string.h> +#include <map> + +#include "OscPacketListener.h" + + + +namespace osc{ + +template< class T > +class MessageMappingOscPacketListener : public OscPacketListener{ +public: + typedef void (T::*function_type)(const osc::ReceivedMessage&, const IpEndpointName&); + +protected: + void RegisterMessageFunction( const char *addressPattern, function_type f ) + { + functions_.insert( std::make_pair( addressPattern, f ) ); + } + + virtual void ProcessMessage( const osc::ReceivedMessage& m, + const IpEndpointName& remoteEndpoint ) + { + typename function_map_type::iterator i = functions_.find( m.AddressPattern() ); + if( i != functions_.end() ) + (dynamic_cast<T*>(this)->*(i->second))( m, remoteEndpoint ); + } + +private: + struct cstr_compare{ + bool operator()( const char *lhs, const char *rhs ) const + { return strcmp( lhs, rhs ) < 0; } + }; + + typedef std::map<const char*, function_type, cstr_compare> function_map_type; + function_map_type functions_; +}; + +} // namespace osc + +#endif /* INCLUDED_MESSAGEMAPPINGOSCPACKETLISTENER_H */ \ No newline at end of file diff --git a/src/osc/osc/OscException.h b/src/osc/osc/OscException.h new file mode 100755 index 0000000..cd8d567 --- /dev/null +++ b/src/osc/osc/OscException.h @@ -0,0 +1,54 @@ +/* + oscpack -- Open Sound Control packet manipulation library + http://www.audiomulch.com/~rossb/oscpack + + Copyright (c) 2004-2005 Ross Bencina <rossb@audiomulch.com> + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + Any person wishing to distribute modifications to the Software is + requested to send the modifications to the original developer so that + they can be incorporated into the canonical version. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +#ifndef INCLUDED_OSC_EXCEPTION_H +#define INCLUDED_OSC_EXCEPTION_H + +#include <exception> + +namespace osc{ + +class Exception : public std::exception { + const char *what_; + +public: + Exception() throw() {} + Exception( const Exception& src ) throw() + : what_( src.what_ ) {} + Exception( const char *w ) throw() + : what_( w ) {} + Exception& operator=( const Exception& src ) throw() + { what_ = src.what_; return *this; } + virtual ~Exception() throw() {} + virtual const char* what() const throw() { return what_; } +}; + +} // namespace osc + +#endif /* INCLUDED_OSC_EXCEPTION_H */ diff --git a/src/osc/osc/OscHostEndianness.h b/src/osc/osc/OscHostEndianness.h new file mode 100755 index 0000000..b542ef5 --- /dev/null +++ b/src/osc/osc/OscHostEndianness.h @@ -0,0 +1,70 @@ +/* + oscpack -- Open Sound Control packet manipulation library + http://www.audiomulch.com/~rossb/oscpack + + Copyright (c) 2004-2005 Ross Bencina <rossb@audiomulch.com> + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + Any person wishing to distribute modifications to the Software is + requested to send the modifications to the original developer so that + they can be incorporated into the canonical version. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +#ifndef OSC_HOSTENDIANNESS_H +#define OSC_HOSTENDIANNESS_H + +/* + Make sure either OSC_HOST_LITTLE_ENDIAN or OSC_HOST_BIG_ENDIAN is defined + + If you know a way to enhance the detection below for Linux and/or MacOSX + please let me know! I've tried a few things which don't work. +*/ +#define OSC_HOST_LITTLE_ENDIAN 1 + +#if defined(OSC_HOST_LITTLE_ENDIAN) || defined(OSC_HOST_BIG_ENDIAN) + +// you can define one of the above symbols from the command line +// then you don't have to edit this file. + +#elif defined(__WIN32__) || defined(WIN32) + +// assume that __WIN32__ is only defined on little endian systems + +#define OSC_HOST_LITTLE_ENDIAN 1 +#undef OSC_HOST_BIG_ENDIAN + +#elif defined(__APPLE__) + +#if defined(__LITTLE_ENDIAN__) +#define OSC_HOST_LITTLE_ENDIAN 1 +#undef OSC_HOST_BIG_ENDIAN +#else +#define OSC_HOST_BIG_ENDIAN 1 +#undef OSC_HOST_LITTLE_ENDIAN +#endif + +#else + +#error please edit OSCHostEndianness.h to configure endianness + +#endif + +#endif /* OSC_HOSTENDIANNESS_H */ + diff --git a/src/osc/osc/OscOutboundPacketStream.cpp b/src/osc/osc/OscOutboundPacketStream.cpp new file mode 100755 index 0000000..75b1800 --- /dev/null +++ b/src/osc/osc/OscOutboundPacketStream.cpp @@ -0,0 +1,639 @@ +/* + oscpack -- Open Sound Control packet manipulation library + http://www.audiomulch.com/~rossb/oscpack + + Copyright (c) 2004-2005 Ross Bencina <rossb@audiomulch.com> + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + Any person wishing to distribute modifications to the Software is + requested to send the modifications to the original developer so that + they can be incorporated into the canonical version. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +#include "OscOutboundPacketStream.h" + +#include <string.h> +#include <stdlib.h> +#include <assert.h> + +#if defined(__WIN32__) || defined(WIN32) +#include <malloc.h> // for alloca +#endif + +#include "OscHostEndianness.h" + + +namespace osc{ + +static void FromInt32( char *p, int32 x ) +{ +#ifdef OSC_HOST_LITTLE_ENDIAN + union{ + osc::int32 i; + char c[4]; + } u; + + u.i = x; + + p[3] = u.c[0]; + p[2] = u.c[1]; + p[1] = u.c[2]; + p[0] = u.c[3]; +#else + *reinterpret_cast<int32*>(p) = x; +#endif +} + + +static void FromUInt32( char *p, uint32 x ) +{ +#ifdef OSC_HOST_LITTLE_ENDIAN + union{ + osc::uint32 i; + char c[4]; + } u; + + u.i = x; + + p[3] = u.c[0]; + p[2] = u.c[1]; + p[1] = u.c[2]; + p[0] = u.c[3]; +#else + *reinterpret_cast<uint32*>(p) = x; +#endif +} + + +static void FromInt64( char *p, int64 x ) +{ +#ifdef OSC_HOST_LITTLE_ENDIAN + union{ + osc::int64 i; + char c[8]; + } u; + + u.i = x; + + p[7] = u.c[0]; + p[6] = u.c[1]; + p[5] = u.c[2]; + p[4] = u.c[3]; + p[3] = u.c[4]; + p[2] = u.c[5]; + p[1] = u.c[6]; + p[0] = u.c[7]; +#else + *reinterpret_cast<int64*>(p) = x; +#endif +} + + +static void FromUInt64( char *p, uint64 x ) +{ +#ifdef OSC_HOST_LITTLE_ENDIAN + union{ + osc::uint64 i; + char c[8]; + } u; + + u.i = x; + + p[7] = u.c[0]; + p[6] = u.c[1]; + p[5] = u.c[2]; + p[4] = u.c[3]; + p[3] = u.c[4]; + p[2] = u.c[5]; + p[1] = u.c[6]; + p[0] = u.c[7]; +#else + *reinterpret_cast<uint64*>(p) = x; +#endif +} + + +static inline long RoundUp4( long x ) +{ + return ((x-1) & (~0x03L)) + 4; +} + + +OutboundPacketStream::OutboundPacketStream( char *buffer, unsigned long capacity ) + : data_( buffer ) + , end_( data_ + capacity ) + , typeTagsCurrent_( end_ ) + , messageCursor_( data_ ) + , argumentCurrent_( data_ ) + , elementSizePtr_( 0 ) + , messageIsInProgress_( false ) +{ + +} + + +OutboundPacketStream::~OutboundPacketStream() +{ + +} + + +char *OutboundPacketStream::BeginElement( char *beginPtr ) +{ + if( elementSizePtr_ == 0 ){ + + elementSizePtr_ = reinterpret_cast<uint32*>(data_); + + return beginPtr; + + }else{ + // store an offset to the old element size ptr in the element size slot + // we store an offset rather than the actual pointer to be 64 bit clean. + *reinterpret_cast<uint32*>(beginPtr) = + (uint32)(reinterpret_cast<char*>(elementSizePtr_) - data_); + + elementSizePtr_ = reinterpret_cast<uint32*>(beginPtr); + + return beginPtr + 4; + } +} + + +void OutboundPacketStream::EndElement( char *endPtr ) +{ + assert( elementSizePtr_ != 0 ); + + if( elementSizePtr_ == reinterpret_cast<uint32*>(data_) ){ + + elementSizePtr_ = 0; + + }else{ + // while building an element, an offset to the containing element's + // size slot is stored in the elements size slot (or a ptr to data_ + // if there is no containing element). We retrieve that here + uint32 *previousElementSizePtr = + (uint32*)(data_ + *reinterpret_cast<uint32*>(elementSizePtr_)); + + // then we store the element size in the slot, note that the element + // size does not include the size slot, hence the - 4 below. + uint32 elementSize = + (endPtr - reinterpret_cast<char*>(elementSizePtr_)) - 4; + FromUInt32( reinterpret_cast<char*>(elementSizePtr_), elementSize ); + + // finally, we reset the element size ptr to the containing element + elementSizePtr_ = previousElementSizePtr; + } +} + + +bool OutboundPacketStream::ElementSizeSlotRequired() const +{ + return (elementSizePtr_ != 0); +} + + +void OutboundPacketStream::CheckForAvailableBundleSpace() +{ + unsigned long required = Size() + ((ElementSizeSlotRequired())?4:0) + 16; + + if( required > Capacity() ) + throw OutOfBufferMemoryException(); +} + + +void OutboundPacketStream::CheckForAvailableMessageSpace( const char *addressPattern ) +{ + // plus 4 for at least four bytes of type tag + unsigned long required = Size() + ((ElementSizeSlotRequired())?4:0) + + RoundUp4(strlen(addressPattern) + 1) + 4; + + if( required > Capacity() ) + throw OutOfBufferMemoryException(); +} + + +void OutboundPacketStream::CheckForAvailableArgumentSpace( long argumentLength ) +{ + // plus three for extra type tag, comma and null terminator + unsigned long required = (argumentCurrent_ - data_) + argumentLength + + RoundUp4( (end_ - typeTagsCurrent_) + 3 ); + + if( required > Capacity() ) + throw OutOfBufferMemoryException(); +} + + +void OutboundPacketStream::Clear() +{ + typeTagsCurrent_ = end_; + messageCursor_ = data_; + argumentCurrent_ = data_; + elementSizePtr_ = 0; + messageIsInProgress_ = false; +} + + +unsigned int OutboundPacketStream::Capacity() const +{ + return end_ - data_; +} + + +unsigned int OutboundPacketStream::Size() const +{ + unsigned int result = argumentCurrent_ - data_; + if( IsMessageInProgress() ){ + // account for the length of the type tag string. the total type tag + // includes an initial comma, plus at least one terminating \0 + result += RoundUp4( (end_ - typeTagsCurrent_) + 2 ); + } + + return result; +} + + +const char *OutboundPacketStream::Data() const +{ + return data_; +} + + +bool OutboundPacketStream::IsReady() const +{ + return (!IsMessageInProgress() && !IsBundleInProgress()); +} + + +bool OutboundPacketStream::IsMessageInProgress() const +{ + return messageIsInProgress_; +} + + +bool OutboundPacketStream::IsBundleInProgress() const +{ + return (elementSizePtr_ != 0); +} + + +OutboundPacketStream& OutboundPacketStream::operator<<( const BundleInitiator& rhs ) +{ + if( IsMessageInProgress() ) + throw MessageInProgressException(); + + CheckForAvailableBundleSpace(); + + messageCursor_ = BeginElement( messageCursor_ ); + + memcpy( messageCursor_, "#bundle\0", 8 ); + FromUInt64( messageCursor_ + 8, rhs.timeTag ); + + messageCursor_ += 16; + argumentCurrent_ = messageCursor_; + + return *this; +} + + +OutboundPacketStream& OutboundPacketStream::operator<<( const BundleTerminator& rhs ) +{ + (void) rhs; + + if( !IsBundleInProgress() ) + throw BundleNotInProgressException(); + if( IsMessageInProgress() ) + throw MessageInProgressException(); + + EndElement( messageCursor_ ); + + return *this; +} + + +OutboundPacketStream& OutboundPacketStream::operator<<( const BeginMessage& rhs ) +{ + if( IsMessageInProgress() ) + throw MessageInProgressException(); + + CheckForAvailableMessageSpace( rhs.addressPattern ); + + messageCursor_ = BeginElement( messageCursor_ ); + + strcpy( messageCursor_, rhs.addressPattern ); + unsigned long rhsLength = strlen(rhs.addressPattern); + messageCursor_ += rhsLength + 1; + + // zero pad to 4-byte boundary + unsigned long i = rhsLength + 1; + while( i & 0x3 ){ + *messageCursor_++ = '\0'; + ++i; + } + + argumentCurrent_ = messageCursor_; + typeTagsCurrent_ = end_; + + messageIsInProgress_ = true; + + return *this; +} + + +OutboundPacketStream& OutboundPacketStream::operator<<( const MessageTerminator& rhs ) +{ + (void) rhs; + + if( !IsMessageInProgress() ) + throw MessageNotInProgressException(); + + int typeTagsCount = end_ - typeTagsCurrent_; + + if( typeTagsCount ){ + + char *tempTypeTags = (char*)alloca(typeTagsCount); + memcpy( tempTypeTags, typeTagsCurrent_, typeTagsCount ); + + // slot size includes comma and null terminator + int typeTagSlotSize = RoundUp4( typeTagsCount + 2 ); + + uint32 argumentsSize = argumentCurrent_ - messageCursor_; + + memmove( messageCursor_ + typeTagSlotSize, messageCursor_, argumentsSize ); + + messageCursor_[0] = ','; + // copy type tags in reverse (really forward) order + for( int i=0; i < typeTagsCount; ++i ) + messageCursor_[i+1] = tempTypeTags[ (typeTagsCount-1) - i ]; + + char *p = messageCursor_ + 1 + typeTagsCount; + for( int i=0; i < (typeTagSlotSize - (typeTagsCount + 1)); ++i ) + *p++ = '\0'; + + typeTagsCurrent_ = end_; + + // advance messageCursor_ for next message + messageCursor_ += typeTagSlotSize + argumentsSize; + + }else{ + // send an empty type tags string + memcpy( messageCursor_, ",\0\0\0", 4 ); + + // advance messageCursor_ for next message + messageCursor_ += 4; + } + + argumentCurrent_ = messageCursor_; + + EndElement( messageCursor_ ); + + messageIsInProgress_ = false; + + return *this; +} + + +OutboundPacketStream& OutboundPacketStream::operator<<( bool rhs ) +{ + CheckForAvailableArgumentSpace(0); + + *(--typeTagsCurrent_) = (char)((rhs) ? TRUE_TYPE_TAG : FALSE_TYPE_TAG); + + return *this; +} + + +OutboundPacketStream& OutboundPacketStream::operator<<( const NilType& rhs ) +{ + (void) rhs; + CheckForAvailableArgumentSpace(0); + + *(--typeTagsCurrent_) = NIL_TYPE_TAG; + + return *this; +} + + +OutboundPacketStream& OutboundPacketStream::operator<<( const InfinitumType& rhs ) +{ + (void) rhs; + CheckForAvailableArgumentSpace(0); + + *(--typeTagsCurrent_) = INFINITUM_TYPE_TAG; + + return *this; +} + + +OutboundPacketStream& OutboundPacketStream::operator<<( int32 rhs ) +{ + CheckForAvailableArgumentSpace(4); + + *(--typeTagsCurrent_) = INT32_TYPE_TAG; + FromInt32( argumentCurrent_, rhs ); + argumentCurrent_ += 4; + + return *this; +} + + +OutboundPacketStream& OutboundPacketStream::operator<<( float rhs ) +{ + CheckForAvailableArgumentSpace(4); + + *(--typeTagsCurrent_) = FLOAT_TYPE_TAG; + +#ifdef OSC_HOST_LITTLE_ENDIAN + union{ + float f; + char c[4]; + } u; + + u.f = rhs; + + argumentCurrent_[3] = u.c[0]; + argumentCurrent_[2] = u.c[1]; + argumentCurrent_[1] = u.c[2]; + argumentCurrent_[0] = u.c[3]; +#else + *reinterpret_cast<float*>(argumentCurrent_) = rhs; +#endif + + argumentCurrent_ += 4; + + return *this; +} + + +OutboundPacketStream& OutboundPacketStream::operator<<( char rhs ) +{ + CheckForAvailableArgumentSpace(4); + + *(--typeTagsCurrent_) = CHAR_TYPE_TAG; + FromInt32( argumentCurrent_, rhs ); + argumentCurrent_ += 4; + + return *this; +} + + +OutboundPacketStream& OutboundPacketStream::operator<<( const RgbaColor& rhs ) +{ + CheckForAvailableArgumentSpace(4); + + *(--typeTagsCurrent_) = RGBA_COLOR_TYPE_TAG; + FromUInt32( argumentCurrent_, rhs ); + argumentCurrent_ += 4; + + return *this; +} + + +OutboundPacketStream& OutboundPacketStream::operator<<( const MidiMessage& rhs ) +{ + CheckForAvailableArgumentSpace(4); + + *(--typeTagsCurrent_) = MIDI_MESSAGE_TYPE_TAG; + FromUInt32( argumentCurrent_, rhs ); + argumentCurrent_ += 4; + + return *this; +} + + +OutboundPacketStream& OutboundPacketStream::operator<<( int64 rhs ) +{ + CheckForAvailableArgumentSpace(8); + + *(--typeTagsCurrent_) = INT64_TYPE_TAG; + FromInt64( argumentCurrent_, rhs ); + argumentCurrent_ += 8; + + return *this; +} + + +OutboundPacketStream& OutboundPacketStream::operator<<( const TimeTag& rhs ) +{ + CheckForAvailableArgumentSpace(8); + + *(--typeTagsCurrent_) = TIME_TAG_TYPE_TAG; + FromUInt64( argumentCurrent_, rhs ); + argumentCurrent_ += 8; + + return *this; +} + + +OutboundPacketStream& OutboundPacketStream::operator<<( double rhs ) +{ + CheckForAvailableArgumentSpace(8); + + *(--typeTagsCurrent_) = DOUBLE_TYPE_TAG; + +#ifdef OSC_HOST_LITTLE_ENDIAN + union{ + double f; + char c[8]; + } u; + + u.f = rhs; + + argumentCurrent_[7] = u.c[0]; + argumentCurrent_[6] = u.c[1]; + argumentCurrent_[5] = u.c[2]; + argumentCurrent_[4] = u.c[3]; + argumentCurrent_[3] = u.c[4]; + argumentCurrent_[2] = u.c[5]; + argumentCurrent_[1] = u.c[6]; + argumentCurrent_[0] = u.c[7]; +#else + *reinterpret_cast<double*>(argumentCurrent_) = rhs; +#endif + + argumentCurrent_ += 8; + + return *this; +} + + +OutboundPacketStream& OutboundPacketStream::operator<<( const char *rhs ) +{ + CheckForAvailableArgumentSpace( RoundUp4(strlen(rhs) + 1) ); + + *(--typeTagsCurrent_) = STRING_TYPE_TAG; + strcpy( argumentCurrent_, rhs ); + unsigned long rhsLength = strlen(rhs); + argumentCurrent_ += rhsLength + 1; + + // zero pad to 4-byte boundary + unsigned long i = rhsLength + 1; + while( i & 0x3 ){ + *argumentCurrent_++ = '\0'; + ++i; + } + + return *this; +} + + +OutboundPacketStream& OutboundPacketStream::operator<<( const Symbol& rhs ) +{ + CheckForAvailableArgumentSpace( RoundUp4(strlen(rhs) + 1) ); + + *(--typeTagsCurrent_) = SYMBOL_TYPE_TAG; + strcpy( argumentCurrent_, rhs ); + unsigned long rhsLength = strlen(rhs); + argumentCurrent_ += rhsLength + 1; + + // zero pad to 4-byte boundary + unsigned long i = rhsLength + 1; + while( i & 0x3 ){ + *argumentCurrent_++ = '\0'; + ++i; + } + + return *this; +} + + +OutboundPacketStream& OutboundPacketStream::operator<<( const Blob& rhs ) +{ + CheckForAvailableArgumentSpace( 4 + RoundUp4(rhs.size) ); + + *(--typeTagsCurrent_) = BLOB_TYPE_TAG; + FromUInt32( argumentCurrent_, rhs.size ); + argumentCurrent_ += 4; + + memcpy( argumentCurrent_, rhs.data, rhs.size ); + argumentCurrent_ += rhs.size; + + // zero pad to 4-byte boundary + unsigned long i = rhs.size; + while( i & 0x3 ){ + *argumentCurrent_++ = '\0'; + ++i; + } + + return *this; +} + +} // namespace osc + + diff --git a/src/osc/osc/OscOutboundPacketStream.h b/src/osc/osc/OscOutboundPacketStream.h new file mode 100755 index 0000000..317e4b2 --- /dev/null +++ b/src/osc/osc/OscOutboundPacketStream.h @@ -0,0 +1,142 @@ +/* + oscpack -- Open Sound Control packet manipulation library + http://www.audiomulch.com/~rossb/oscpack + + Copyright (c) 2004-2005 Ross Bencina <rossb@audiomulch.com> + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + Any person wishing to distribute modifications to the Software is + requested to send the modifications to the original developer so that + they can be incorporated into the canonical version. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +#ifndef INCLUDED_OSCOUTBOUNDPACKET_H +#define INCLUDED_OSCOUTBOUNDPACKET_H + +#include "OscTypes.h" +#include "OscException.h" + + +namespace osc{ + +class OutOfBufferMemoryException : public Exception{ +public: + OutOfBufferMemoryException( const char *w="out of buffer memory" ) + : Exception( w ) {} +}; + +class BundleNotInProgressException : public Exception{ +public: + BundleNotInProgressException( + const char *w="call to EndBundle when bundle is not in progress" ) + : Exception( w ) {} +}; + +class MessageInProgressException : public Exception{ +public: + MessageInProgressException( + const char *w="opening or closing bundle or message while message is in progress" ) + : Exception( w ) {} +}; + +class MessageNotInProgressException : public Exception{ +public: + MessageNotInProgressException( + const char *w="call to EndMessage when message is not in progress" ) + : Exception( w ) {} +}; + + +class OutboundPacketStream{ +public: + OutboundPacketStream( char *buffer, unsigned long capacity ); + ~OutboundPacketStream(); + + void Clear(); + + unsigned int Capacity() const; + + // invariant: size() is valid even while building a message. + unsigned int Size() const; + + const char *Data() const; + + // indicates that all messages have been closed with a matching EndMessage + // and all bundles have been closed with a matching EndBundle + bool IsReady() const; + + bool IsMessageInProgress() const; + bool IsBundleInProgress() const; + + OutboundPacketStream& operator<<( const BundleInitiator& rhs ); + OutboundPacketStream& operator<<( const BundleTerminator& rhs ); + + OutboundPacketStream& operator<<( const BeginMessage& rhs ); + OutboundPacketStream& operator<<( const MessageTerminator& rhs ); + + OutboundPacketStream& operator<<( bool rhs ); + OutboundPacketStream& operator<<( const NilType& rhs ); + OutboundPacketStream& operator<<( const InfinitumType& rhs ); + OutboundPacketStream& operator<<( int32 rhs ); + +#ifndef x86_64 + OutboundPacketStream& operator<<( int rhs ) + { *this << (int32)rhs; return *this; } +#endif + + OutboundPacketStream& operator<<( float rhs ); + OutboundPacketStream& operator<<( char rhs ); + OutboundPacketStream& operator<<( const RgbaColor& rhs ); + OutboundPacketStream& operator<<( const MidiMessage& rhs ); + OutboundPacketStream& operator<<( int64 rhs ); + OutboundPacketStream& operator<<( const TimeTag& rhs ); + OutboundPacketStream& operator<<( double rhs ); + OutboundPacketStream& operator<<( const char* rhs ); + OutboundPacketStream& operator<<( const Symbol& rhs ); + OutboundPacketStream& operator<<( const Blob& rhs ); + +private: + + char *BeginElement( char *beginPtr ); + void EndElement( char *endPtr ); + + bool ElementSizeSlotRequired() const; + void CheckForAvailableBundleSpace(); + void CheckForAvailableMessageSpace( const char *addressPattern ); + void CheckForAvailableArgumentSpace( long argumentLength ); + + char *data_; + char *end_; + + char *typeTagsCurrent_; // stored in reverse order + char *messageCursor_; + char *argumentCurrent_; + + // elementSizePtr_ has two special values: 0 indicates that a bundle + // isn't open, and elementSizePtr_==data_ indicates that a bundle is + // open but that it doesn't have a size slot (ie the outermost bundle) + uint32 *elementSizePtr_; + + bool messageIsInProgress_; +}; + +} // namespace osc + +#endif /* INCLUDED_OSC_OUTBOUND_PACKET_H */ diff --git a/src/osc/osc/OscPacketListener.h b/src/osc/osc/OscPacketListener.h new file mode 100755 index 0000000..bc322b7 --- /dev/null +++ b/src/osc/osc/OscPacketListener.h @@ -0,0 +1,72 @@ +/* + oscpack -- Open Sound Control packet manipulation library + http://www.audiomulch.com/~rossb/oscpack + + Copyright (c) 2004-2005 Ross Bencina <rossb@audiomulch.com> + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + Any person wishing to distribute modifications to the Software is + requested to send the modifications to the original developer so that + they can be incorporated into the canonical version. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +#ifndef INCLUDED_OSCPACKETLISTENER_H +#define INCLUDED_OSCPACKETLISTENER_H + +#include "OscReceivedElements.h" +#include "../ip/PacketListener.h" + + +namespace osc{ + +class OscPacketListener : public PacketListener{ +protected: + virtual void ProcessBundle( const osc::ReceivedBundle& b, + const IpEndpointName& remoteEndpoint ) + { + // ignore bundle time tag for now + + for( ReceivedBundle::const_iterator i = b.ElementsBegin(); + i != b.ElementsEnd(); ++i ){ + if( i->IsBundle() ) + ProcessBundle( ReceivedBundle(*i), remoteEndpoint ); + else + ProcessMessage( ReceivedMessage(*i), remoteEndpoint ); + } + } + + virtual void ProcessMessage( const osc::ReceivedMessage& m, + const IpEndpointName& remoteEndpoint ) = 0; + +public: + virtual void ProcessPacket( const char *data, int size, + const IpEndpointName& remoteEndpoint ) + { + osc::ReceivedPacket p( data, size ); + if( p.IsBundle() ) + ProcessBundle( ReceivedBundle(p), remoteEndpoint ); + else + ProcessMessage( ReceivedMessage(p), remoteEndpoint ); + } +}; + +} // namespace osc + +#endif /* INCLUDED_OSCPACKETLISTENER_H */ diff --git a/src/osc/osc/OscPrintReceivedElements.cpp b/src/osc/osc/OscPrintReceivedElements.cpp new file mode 100755 index 0000000..430cf92 --- /dev/null +++ b/src/osc/osc/OscPrintReceivedElements.cpp @@ -0,0 +1,241 @@ +/* + oscpack -- Open Sound Control packet manipulation library + http://www.audiomulch.com/~rossb/oscpack + + Copyright (c) 2004-2005 Ross Bencina <rossb@audiomulch.com> + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + Any person wishing to distribute modifications to the Software is + requested to send the modifications to the original developer so that + they can be incorporated into the canonical version. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +#include "OscPrintReceivedElements.h" + +#include <iostream> +#include <iomanip> +#include <ctime> +#include <string.h> + + +namespace osc{ + + +std::ostream& operator<<( std::ostream & os, + const ReceivedMessageArgument& arg ) +{ + switch( arg.TypeTag() ){ + case TRUE_TYPE_TAG: + os << "bool:true"; + break; + + case FALSE_TYPE_TAG: + os << "bool:false"; + break; + + case NIL_TYPE_TAG: + os << "(Nil)"; + break; + + case INFINITUM_TYPE_TAG: + os << "(Infinitum)"; + break; + + case INT32_TYPE_TAG: + os << "int32:" << arg.AsInt32Unchecked(); + break; + + case FLOAT_TYPE_TAG: + os << "float32:" << arg.AsFloatUnchecked(); + break; + + case CHAR_TYPE_TAG: + { + char s[2] = {0}; + s[0] = arg.AsCharUnchecked(); + os << "char:'" << s << "'"; + } + break; + + case RGBA_COLOR_TYPE_TAG: + { + uint32 color = arg.AsRgbaColorUnchecked(); + + os << "RGBA:0x" + << std::hex << std::setfill('0') + << std::setw(2) << (int)((color>>24) & 0xFF) + << std::setw(2) << (int)((color>>16) & 0xFF) + << std::setw(2) << (int)((color>>8) & 0xFF) + << std::setw(2) << (int)(color & 0xFF) + << std::setfill(' '); + os.unsetf(std::ios::basefield); + } + break; + + case MIDI_MESSAGE_TYPE_TAG: + { + uint32 m = arg.AsMidiMessageUnchecked(); + os << "midi (port, status, data1, data2):<<" + << std::hex << std::setfill('0') + << "0x" << std::setw(2) << (int)((m>>24) & 0xFF) + << " 0x" << std::setw(2) << (int)((m>>16) & 0xFF) + << " 0x" << std::setw(2) << (int)((m>>8) & 0xFF) + << " 0x" << std::setw(2) << (int)(m & 0xFF) + << std::setfill(' ') << ">>"; + os.unsetf(std::ios::basefield); + } + break; + + case INT64_TYPE_TAG: + os << "int64:" << arg.AsInt64Unchecked(); + break; + + case TIME_TAG_TYPE_TAG: + { + os << "OSC-timetag:" << arg.AsTimeTagUnchecked(); + + std::time_t t = + (unsigned long)( arg.AsTimeTagUnchecked() >> 32 ); + + // strip trailing newline from string returned by ctime + const char *timeString = std::ctime( &t ); + size_t len = strlen( timeString ); + char *s = new char[ len + 1 ]; + strcpy( s, timeString ); + if( len ) + s[ len - 1 ] = '\0'; + + os << " " << s; + } + break; + + case DOUBLE_TYPE_TAG: + os << "double:" << arg.AsDoubleUnchecked(); + break; + + case STRING_TYPE_TAG: + os << "OSC-string:`" << arg.AsStringUnchecked() << "'"; + break; + + case SYMBOL_TYPE_TAG: + os << "OSC-string (symbol):`" << arg.AsSymbolUnchecked() << "'"; + break; + + case BLOB_TYPE_TAG: + { + unsigned long size; + const void *data; + arg.AsBlobUnchecked( data, size ); + os << "OSC-blob:<<" << std::hex << std::setfill('0'); + unsigned char *p = (unsigned char*)data; + for( unsigned long i = 0; i < size; ++i ){ + os << "0x" << std::setw(2) << int(p[i]); + if( i != size-1 ) + os << ' '; + } + os.unsetf(std::ios::basefield); + os << ">>" << std::setfill(' '); + } + break; + + default: + os << "unknown"; + } + + return os; +} + + +std::ostream& operator<<( std::ostream & os, const ReceivedMessage& m ) +{ + + os << "[" << m.AddressPattern(); + bool first = true; + + for( ReceivedMessage::const_iterator i = m.ArgumentsBegin(); + i != m.ArgumentsEnd(); ++i ){ + if( first ){ + os << " "; + first = false; + }else{ + os << ", "; + } + + os << *i; + } + + os << "]"; + + return os; +} + + +std::ostream& operator<<( std::ostream & os, const ReceivedBundle& b ) +{ + static int indent = 0; + + for( int j=0; j < indent; ++j ) + os << " "; + os << "{ ( "; + if( b.TimeTag() == 1 ) + os << "immediate"; + else + os << b.TimeTag(); + os << " )\n"; + + ++indent; + + for( ReceivedBundle::const_iterator i = b.ElementsBegin(); + i != b.ElementsEnd(); ++i ){ + if( i->IsBundle() ){ + ReceivedBundle b(*i); + os << b << "\n"; + }else{ + ReceivedMessage m(*i); + for( int j=0; j < indent; ++j ) + os << " "; + os << m << "\n"; + } + } + + --indent; + + for( int j=0; j < indent; ++j ) + os << " "; + os << "}"; + + return os; +} + + +std::ostream& operator<<( std::ostream & os, const ReceivedPacket& p ) +{ + if( p.IsBundle() ){ + ReceivedBundle b(p); + os << b << "\n"; + }else{ + ReceivedMessage m(p); + os << m << "\n"; + } + + return os; +} + +} // namespace osc diff --git a/src/osc/osc/OscPrintReceivedElements.h b/src/osc/osc/OscPrintReceivedElements.h new file mode 100755 index 0000000..c42cfa5 --- /dev/null +++ b/src/osc/osc/OscPrintReceivedElements.h @@ -0,0 +1,49 @@ +/* + oscpack -- Open Sound Control packet manipulation library + http://www.audiomulch.com/~rossb/oscpack + + Copyright (c) 2004-2005 Ross Bencina <rossb@audiomulch.com> + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + Any person wishing to distribute modifications to the Software is + requested to send the modifications to the original developer so that + they can be incorporated into the canonical version. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +#ifndef INCLUDED_OSCPRINTRECEIVEDELEMENTS_H +#define INCLUDED_OSCPRINTRECEIVEDELEMENTS_H + +#include <iosfwd> + +#ifndef INCLUDED_OSCRECEIVEDELEMENTS_H +#include "OscReceivedElements.h" +#endif /* INCLUDED_OSCRECEIVEDELEMENTS_H */ + + +namespace osc{ + +std::ostream& operator<<( std::ostream & os, const ReceivedPacket& p ); +std::ostream& operator<<( std::ostream & os, const ReceivedMessageArgument& arg ); +std::ostream& operator<<( std::ostream & os, const ReceivedMessage& m ); +std::ostream& operator<<( std::ostream & os, const ReceivedBundle& b ); + +} // namespace osc + +#endif /* INCLUDED_OSCPRINTRECEIVEDELEMENTS_H */ diff --git a/src/osc/osc/OscReceivedElements.cpp b/src/osc/osc/OscReceivedElements.cpp new file mode 100755 index 0000000..326fd62 --- /dev/null +++ b/src/osc/osc/OscReceivedElements.cpp @@ -0,0 +1,722 @@ +/* + oscpack -- Open Sound Control packet manipulation library + http://www.audiomulch.com/~rossb/oscpack + + Copyright (c) 2004-2005 Ross Bencina <rossb@audiomulch.com> + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + Any person wishing to distribute modifications to the Software is + requested to send the modifications to the original developer so that + they can be incorporated into the canonical version. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +#include "OscReceivedElements.h" + +#include <cassert> + +#include "OscHostEndianness.h" + + +namespace osc{ + + +// return the first 4 byte boundary after the end of a str4 +// be careful about calling this version if you don't know whether +// the string is terminated correctly. +static inline const char* FindStr4End( const char *p ) +{ + if( p[0] == '\0' ) // special case for SuperCollider integer address pattern + return p + 4; + + p += 3; + + while( *p ) + p += 4; + + return p + 1; +} + + +// return the first 4 byte boundary after the end of a str4 +// returns 0 if p == end or if the string is unterminated +static inline const char* FindStr4End( const char *p, const char *end ) +{ + if( p >= end ) + return 0; + + if( p[0] == '\0' ) // special case for SuperCollider integer address pattern + return p + 4; + + p += 3; + end -= 1; + + while( p < end && *p ) + p += 4; + + if( *p ) + return 0; + else + return p + 1; +} + + +static inline unsigned long RoundUp4( unsigned long x ) +{ + unsigned long remainder = x & 0x3UL; + if( remainder ) + return x + (4 - remainder); + else + return x; +} + + +static inline int32 ToInt32( const char *p ) +{ +#ifdef OSC_HOST_LITTLE_ENDIAN + union{ + osc::int32 i; + char c[4]; + } u; + + u.c[0] = p[3]; + u.c[1] = p[2]; + u.c[2] = p[1]; + u.c[3] = p[0]; + + return u.i; +#else + return *(int32*)p; +#endif +} + + +static inline uint32 ToUInt32( const char *p ) +{ +#ifdef OSC_HOST_LITTLE_ENDIAN + union{ + osc::uint32 i; + char c[4]; + } u; + + u.c[0] = p[3]; + u.c[1] = p[2]; + u.c[2] = p[1]; + u.c[3] = p[0]; + + return u.i; +#else + return *(uint32*)p; +#endif +} + + +int64 ToInt64( const char *p ) +{ +#ifdef OSC_HOST_LITTLE_ENDIAN + union{ + osc::int64 i; + char c[4]; + } u; + + u.c[0] = p[7]; + u.c[1] = p[6]; + u.c[2] = p[5]; + u.c[3] = p[4]; + u.c[4] = p[3]; + u.c[5] = p[2]; + u.c[6] = p[1]; + u.c[7] = p[0]; + + return u.i; +#else + return *(int64*)p; +#endif +} + + +uint64 ToUInt64( const char *p ) +{ +#ifdef OSC_HOST_LITTLE_ENDIAN + union{ + osc::uint64 i; + char c[4]; + } u; + + u.c[0] = p[7]; + u.c[1] = p[6]; + u.c[2] = p[5]; + u.c[3] = p[4]; + u.c[4] = p[3]; + u.c[5] = p[2]; + u.c[6] = p[1]; + u.c[7] = p[0]; + + return u.i; +#else + return *(uint64*)p; +#endif +} + +//------------------------------------------------------------------------------ + +bool ReceivedPacket::IsBundle() const +{ + return (Size() > 0 && Contents()[0] == '#'); +} + +//------------------------------------------------------------------------------ + +bool ReceivedBundleElement::IsBundle() const +{ + return (Size() > 0 && Contents()[0] == '#'); +} + + +int32 ReceivedBundleElement::Size() const +{ + return ToUInt32( size_ ); +} + +//------------------------------------------------------------------------------ + +bool ReceivedMessageArgument::AsBool() const +{ + if( !typeTag_ ) + throw MissingArgumentException(); + else if( *typeTag_ == TRUE_TYPE_TAG ) + return true; + else if( *typeTag_ == FALSE_TYPE_TAG ) + return false; + else + throw WrongArgumentTypeException(); +} + + +bool ReceivedMessageArgument::AsBoolUnchecked() const +{ + if( !typeTag_ ) + throw MissingArgumentException(); + else if( *typeTag_ == TRUE_TYPE_TAG ) + return true; + else + return false; +} + + +int32 ReceivedMessageArgument::AsInt32() const +{ + if( !typeTag_ ) + throw MissingArgumentException(); + else if( *typeTag_ == INT32_TYPE_TAG ) + return AsInt32Unchecked(); + else + throw WrongArgumentTypeException(); +} + + +int32 ReceivedMessageArgument::AsInt32Unchecked() const +{ +#ifdef OSC_HOST_LITTLE_ENDIAN + union{ + osc::int32 i; + char c[4]; + } u; + + u.c[0] = argument_[3]; + u.c[1] = argument_[2]; + u.c[2] = argument_[1]; + u.c[3] = argument_[0]; + + return u.i; +#else + return *(int32*)argument_; +#endif +} + + +float ReceivedMessageArgument::AsFloat() const +{ + if( !typeTag_ ) + throw MissingArgumentException(); + else if( *typeTag_ == FLOAT_TYPE_TAG ) + return AsFloatUnchecked(); + else + throw WrongArgumentTypeException(); +} + + +float ReceivedMessageArgument::AsFloatUnchecked() const +{ +#ifdef OSC_HOST_LITTLE_ENDIAN + union{ + float f; + char c[4]; + } u; + + u.c[0] = argument_[3]; + u.c[1] = argument_[2]; + u.c[2] = argument_[1]; + u.c[3] = argument_[0]; + + return u.f; +#else + return *(float*)argument_; +#endif +} + + +char ReceivedMessageArgument::AsChar() const +{ + if( !typeTag_ ) + throw MissingArgumentException(); + else if( *typeTag_ == CHAR_TYPE_TAG ) + return AsCharUnchecked(); + else + throw WrongArgumentTypeException(); +} + + +char ReceivedMessageArgument::AsCharUnchecked() const +{ + return (char)ToInt32( argument_ ); +} + + +uint32 ReceivedMessageArgument::AsRgbaColor() const +{ + if( !typeTag_ ) + throw MissingArgumentException(); + else if( *typeTag_ == RGBA_COLOR_TYPE_TAG ) + return AsRgbaColorUnchecked(); + else + throw WrongArgumentTypeException(); +} + + +uint32 ReceivedMessageArgument::AsRgbaColorUnchecked() const +{ + return ToUInt32( argument_ ); +} + + +uint32 ReceivedMessageArgument::AsMidiMessage() const +{ + if( !typeTag_ ) + throw MissingArgumentException(); + else if( *typeTag_ == MIDI_MESSAGE_TYPE_TAG ) + return AsMidiMessageUnchecked(); + else + throw WrongArgumentTypeException(); +} + + +uint32 ReceivedMessageArgument::AsMidiMessageUnchecked() const +{ + return ToUInt32( argument_ ); +} + + +int64 ReceivedMessageArgument::AsInt64() const +{ + if( !typeTag_ ) + throw MissingArgumentException(); + else if( *typeTag_ == INT64_TYPE_TAG ) + return AsInt64Unchecked(); + else + throw WrongArgumentTypeException(); +} + + +int64 ReceivedMessageArgument::AsInt64Unchecked() const +{ + return ToInt64( argument_ ); +} + + +uint64 ReceivedMessageArgument::AsTimeTag() const +{ + if( !typeTag_ ) + throw MissingArgumentException(); + else if( *typeTag_ == TIME_TAG_TYPE_TAG ) + return AsTimeTagUnchecked(); + else + throw WrongArgumentTypeException(); +} + + +uint64 ReceivedMessageArgument::AsTimeTagUnchecked() const +{ + return ToUInt64( argument_ ); +} + + +double ReceivedMessageArgument::AsDouble() const +{ + if( !typeTag_ ) + throw MissingArgumentException(); + else if( *typeTag_ == DOUBLE_TYPE_TAG ) + return AsDoubleUnchecked(); + else + throw WrongArgumentTypeException(); +} + + +double ReceivedMessageArgument::AsDoubleUnchecked() const +{ +#ifdef OSC_HOST_LITTLE_ENDIAN + union{ + double d; + char c[8]; + } u; + + u.c[0] = argument_[7]; + u.c[1] = argument_[6]; + u.c[2] = argument_[5]; + u.c[3] = argument_[4]; + u.c[4] = argument_[3]; + u.c[5] = argument_[2]; + u.c[6] = argument_[1]; + u.c[7] = argument_[0]; + + return u.d; +#else + return *(double*)argument_; +#endif +} + + +const char* ReceivedMessageArgument::AsString() const +{ + if( !typeTag_ ) + throw MissingArgumentException(); + else if( *typeTag_ == STRING_TYPE_TAG ) + return argument_; + else + throw WrongArgumentTypeException(); +} + + +const char* ReceivedMessageArgument::AsSymbol() const +{ + if( !typeTag_ ) + throw MissingArgumentException(); + else if( *typeTag_ == SYMBOL_TYPE_TAG ) + return argument_; + else + throw WrongArgumentTypeException(); +} + + +void ReceivedMessageArgument::AsBlob( const void*& data, unsigned long& size ) const +{ + if( !typeTag_ ) + throw MissingArgumentException(); + else if( *typeTag_ == BLOB_TYPE_TAG ) + AsBlobUnchecked( data, size ); + else + throw WrongArgumentTypeException(); +} + + +void ReceivedMessageArgument::AsBlobUnchecked( const void*& data, unsigned long& size ) const +{ + size = ToUInt32( argument_ ); + data = (void*)(argument_+4); +} + +//------------------------------------------------------------------------------ + +void ReceivedMessageArgumentIterator::Advance() +{ + if( !value_.typeTag_ ) + return; + + switch( *value_.typeTag_++ ){ + case '\0': + // don't advance past end + --value_.typeTag_; + break; + + case TRUE_TYPE_TAG: + case FALSE_TYPE_TAG: + case NIL_TYPE_TAG: + case INFINITUM_TYPE_TAG: + + // zero length + break; + + case INT32_TYPE_TAG: + case FLOAT_TYPE_TAG: + case CHAR_TYPE_TAG: + case RGBA_COLOR_TYPE_TAG: + case MIDI_MESSAGE_TYPE_TAG: + + value_.argument_ += 4; + break; + + case INT64_TYPE_TAG: + case TIME_TAG_TYPE_TAG: + case DOUBLE_TYPE_TAG: + + value_.argument_ += 8; + break; + + case STRING_TYPE_TAG: + case SYMBOL_TYPE_TAG: + + // we use the unsafe function FindStr4End(char*) here because all of + // the arguments have already been validated in + // ReceivedMessage::Init() below. + + value_.argument_ = FindStr4End( value_.argument_ ); + break; + + case BLOB_TYPE_TAG: + { + uint32 blobSize = ToUInt32( value_.argument_ ); + value_.argument_ = value_.argument_ + 4 + RoundUp4( blobSize ); + } + break; + + default: // unknown type tag + // don't advance + --value_.typeTag_; + break; + + + // not handled: + // [ Indicates the beginning of an array. The tags following are for + // data in the Array until a close brace tag is reached. + // ] Indicates the end of an array. + } +} + +//------------------------------------------------------------------------------ + +ReceivedMessage::ReceivedMessage( const ReceivedPacket& packet ) + : addressPattern_( packet.Contents() ) +{ + Init( packet.Contents(), packet.Size() ); +} + + +ReceivedMessage::ReceivedMessage( const ReceivedBundleElement& bundleElement ) + : addressPattern_( bundleElement.Contents() ) +{ + Init( bundleElement.Contents(), bundleElement.Size() ); +} + + +bool ReceivedMessage::AddressPatternIsUInt32() const +{ + return (addressPattern_[0] == '\0'); +} + + +uint32 ReceivedMessage::AddressPatternAsUInt32() const +{ + return ToUInt32( addressPattern_ ); +} + + +void ReceivedMessage::Init( const char *message, unsigned long size ) +{ + if( size == 0 ) + throw MalformedMessageException( "zero length messages not permitted" ); + + if( (size & 0x03L) != 0 ) + throw MalformedMessageException( "message size must be multiple of four" ); + + const char *end = message + size; + + typeTagsBegin_ = FindStr4End( addressPattern_, end ); + if( typeTagsBegin_ == 0 ){ + // address pattern was not terminated before end + throw MalformedMessageException( "unterminated address pattern" ); + } + + if( typeTagsBegin_ == end ){ + // message consists of only the address pattern - no arguments or type tags. + typeTagsBegin_ = 0; + typeTagsEnd_ = 0; + arguments_ = 0; + + }else{ + if( *typeTagsBegin_ != ',' ) + throw MalformedMessageException( "type tags not present" ); + + if( *(typeTagsBegin_ + 1) == '\0' ){ + // zero length type tags + typeTagsBegin_ = 0; + typeTagsEnd_ = 0; + arguments_ = 0; + + }else{ + // check that all arguments are present and well formed + + arguments_ = FindStr4End( typeTagsBegin_, end ); + if( arguments_ == 0 ){ + throw MalformedMessageException( "type tags were not terminated before end of message" ); + } + + ++typeTagsBegin_; // advance past initial ',' + + const char *typeTag = typeTagsBegin_; + const char *argument = arguments_; + + do{ + switch( *typeTag ){ + case TRUE_TYPE_TAG: + case FALSE_TYPE_TAG: + case NIL_TYPE_TAG: + case INFINITUM_TYPE_TAG: + + // zero length + break; + + case INT32_TYPE_TAG: + case FLOAT_TYPE_TAG: + case CHAR_TYPE_TAG: + case RGBA_COLOR_TYPE_TAG: + case MIDI_MESSAGE_TYPE_TAG: + + if( argument == end ) + throw MalformedMessageException( "arguments exceed message size" ); + argument += 4; + if( argument > end ) + throw MalformedMessageException( "arguments exceed message size" ); + break; + + case INT64_TYPE_TAG: + case TIME_TAG_TYPE_TAG: + case DOUBLE_TYPE_TAG: + + if( argument == end ) + throw MalformedMessageException( "arguments exceed message size" ); + argument += 8; + if( argument > end ) + throw MalformedMessageException( "arguments exceed message size" ); + break; + + case STRING_TYPE_TAG: + case SYMBOL_TYPE_TAG: + + if( argument == end ) + throw MalformedMessageException( "arguments exceed message size" ); + argument = FindStr4End( argument, end ); + if( argument == 0 ) + throw MalformedMessageException( "unterminated string argument" ); + break; + + case BLOB_TYPE_TAG: + { + if( argument + 4 > end ) + MalformedMessageException( "arguments exceed message size" ); + + uint32 blobSize = ToUInt32( argument ); + argument = argument + 4 + RoundUp4( blobSize ); + if( argument > end ) + MalformedMessageException( "arguments exceed message size" ); + } + break; + + default: + throw MalformedMessageException( "unknown type tag" ); + + // not handled: + // [ Indicates the beginning of an array. The tags following are for + // data in the Array until a close brace tag is reached. + // ] Indicates the end of an array. + } + + }while( *++typeTag != '\0' ); + typeTagsEnd_ = typeTag; + } + } +} + +//------------------------------------------------------------------------------ + +ReceivedBundle::ReceivedBundle( const ReceivedPacket& packet ) + : elementCount_( 0 ) +{ + Init( packet.Contents(), packet.Size() ); +} + + +ReceivedBundle::ReceivedBundle( const ReceivedBundleElement& bundleElement ) + : elementCount_( 0 ) +{ + Init( bundleElement.Contents(), bundleElement.Size() ); +} + + +void ReceivedBundle::Init( const char *bundle, unsigned long size ) +{ + if( size < 16 ) + throw MalformedBundleException( "packet too short for bundle" ); + + if( (size & 0x03L) != 0 ) + throw MalformedBundleException( "bundle size must be multiple of four" ); + + if( bundle[0] != '#' + || bundle[1] != 'b' + || bundle[2] != 'u' + || bundle[3] != 'n' + || bundle[4] != 'd' + || bundle[5] != 'l' + || bundle[6] != 'e' + || bundle[7] != '\0' ) + throw MalformedBundleException( "bad bundle address pattern" ); + + end_ = bundle + size; + + timeTag_ = bundle + 8; + + const char *p = timeTag_ + 8; + + while( p < end_ ){ + if( p + 4 > end_ ) + throw MalformedBundleException( "packet too short for elementSize" ); + + uint32 elementSize = ToUInt32( p ); + if( (elementSize & 0x03L) != 0 ) + throw MalformedBundleException( "bundle element size must be multiple of four" ); + + p += 4 + elementSize; + if( p > end_ ) + throw MalformedBundleException( "packet too short for bundle element" ); + + ++elementCount_; + } + + if( p != end_ ) + throw MalformedBundleException( "bundle contents " ); +} + + +uint64 ReceivedBundle::TimeTag() const +{ + return ToUInt64( timeTag_ ); +} + + +} // namespace osc + diff --git a/src/osc/osc/OscReceivedElements.h b/src/osc/osc/OscReceivedElements.h new file mode 100755 index 0000000..f438757 --- /dev/null +++ b/src/osc/osc/OscReceivedElements.h @@ -0,0 +1,486 @@ +/* + oscpack -- Open Sound Control packet manipulation library + http://www.audiomulch.com/~rossb/oscpack + + Copyright (c) 2004-2005 Ross Bencina <rossb@audiomulch.com> + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + Any person wishing to distribute modifications to the Software is + requested to send the modifications to the original developer so that + they can be incorporated into the canonical version. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +#ifndef INCLUDED_OSCRECEIVEDELEMENTS_H +#define INCLUDED_OSCRECEIVEDELEMENTS_H + +#include "OscTypes.h" +#include "OscException.h" + + +namespace osc{ + + +class MalformedMessageException : public Exception{ +public: + MalformedMessageException( const char *w="malformed message" ) + : Exception( w ) {} +}; + +class MalformedBundleException : public Exception{ +public: + MalformedBundleException( const char *w="malformed bundle" ) + : Exception( w ) {} +}; + +class WrongArgumentTypeException : public Exception{ +public: + WrongArgumentTypeException( const char *w="wrong argument type" ) + : Exception( w ) {} +}; + +class MissingArgumentException : public Exception{ +public: + MissingArgumentException( const char *w="missing argument" ) + : Exception( w ) {} +}; + +class ExcessArgumentException : public Exception{ +public: + ExcessArgumentException( const char *w="too many arguments" ) + : Exception( w ) {} +}; + + +class ReceivedPacket{ +public: + ReceivedPacket( const char *contents, int32 size ) + : contents_( contents ) + , size_( size ) {} + + bool IsMessage() const { return !IsBundle(); } + bool IsBundle() const; + + int32 Size() const { return size_; } + const char *Contents() const { return contents_; } + +private: + const char *contents_; + int32 size_; +}; + + +class ReceivedBundleElement{ +public: + ReceivedBundleElement( const char *size ) + : size_( size ) {} + + friend class ReceivedBundleElementIterator; + + bool IsMessage() const { return !IsBundle(); } + bool IsBundle() const; + + int32 Size() const; + const char *Contents() const { return size_ + 4; } + +private: + const char *size_; +}; + + +class ReceivedBundleElementIterator{ +public: + ReceivedBundleElementIterator( const char *sizePtr ) + : value_( sizePtr ) {} + + ReceivedBundleElementIterator operator++() + { + Advance(); + return *this; + } + + ReceivedBundleElementIterator operator++(int) + { + ReceivedBundleElementIterator old( *this ); + Advance(); + return old; + } + + const ReceivedBundleElement& operator*() const { return value_; } + + const ReceivedBundleElement* operator->() const { return &value_; } + + friend bool operator==(const ReceivedBundleElementIterator& lhs, + const ReceivedBundleElementIterator& rhs ); + +private: + ReceivedBundleElement value_; + + void Advance() { value_.size_ = value_.Contents() + value_.Size(); } + + bool IsEqualTo( const ReceivedBundleElementIterator& rhs ) const + { + return value_.size_ == rhs.value_.size_; + } +}; + +inline bool operator==(const ReceivedBundleElementIterator& lhs, + const ReceivedBundleElementIterator& rhs ) +{ + return lhs.IsEqualTo( rhs ); +} + +inline bool operator!=(const ReceivedBundleElementIterator& lhs, + const ReceivedBundleElementIterator& rhs ) +{ + return !( lhs == rhs ); +} + + +class ReceivedMessageArgument{ +public: + ReceivedMessageArgument( const char *typeTag, const char *argument ) + : typeTag_( typeTag ) + , argument_( argument ) {} + + friend class ReceivedMessageArgumentIterator; + + const char TypeTag() const { return *typeTag_; } + + // the unchecked methods below don't check whether the argument actually + // is of the specified type. they should only be used if you've already + // checked the type tag or the associated IsType() method. + + bool IsBool() const + { return *typeTag_ == TRUE_TYPE_TAG || *typeTag_ == FALSE_TYPE_TAG; } + bool AsBool() const; + bool AsBoolUnchecked() const; + + bool IsNil() const { return *typeTag_ == NIL_TYPE_TAG; } + bool IsInfinitum() const { return *typeTag_ == INFINITUM_TYPE_TAG; } + + bool IsInt32() const { return *typeTag_ == INT32_TYPE_TAG; } + int32 AsInt32() const; + int32 AsInt32Unchecked() const; + + bool IsFloat() const { return *typeTag_ == FLOAT_TYPE_TAG; } + float AsFloat() const; + float AsFloatUnchecked() const; + + bool IsChar() const { return *typeTag_ == CHAR_TYPE_TAG; } + char AsChar() const; + char AsCharUnchecked() const; + + bool IsRgbaColor() const { return *typeTag_ == RGBA_COLOR_TYPE_TAG; } + uint32 AsRgbaColor() const; + uint32 AsRgbaColorUnchecked() const; + + bool IsMidiMessage() const { return *typeTag_ == MIDI_MESSAGE_TYPE_TAG; } + uint32 AsMidiMessage() const; + uint32 AsMidiMessageUnchecked() const; + + bool IsInt64() const { return *typeTag_ == INT64_TYPE_TAG; } + int64 AsInt64() const; + int64 AsInt64Unchecked() const; + + bool IsTimeTag() const { return *typeTag_ == TIME_TAG_TYPE_TAG; } + uint64 AsTimeTag() const; + uint64 AsTimeTagUnchecked() const; + + bool IsDouble() const { return *typeTag_ == DOUBLE_TYPE_TAG; } + double AsDouble() const; + double AsDoubleUnchecked() const; + + bool IsString() const { return *typeTag_ == STRING_TYPE_TAG; } + const char* AsString() const; + const char* AsStringUnchecked() const { return argument_; } + + bool IsSymbol() const { return *typeTag_ == SYMBOL_TYPE_TAG; } + const char* AsSymbol() const; + const char* AsSymbolUnchecked() const { return argument_; } + + bool IsBlob() const { return *typeTag_ == BLOB_TYPE_TAG; } + void AsBlob( const void*& data, unsigned long& size ) const; + void AsBlobUnchecked( const void*& data, unsigned long& size ) const; + +private: + const char *typeTag_; + const char *argument_; +}; + + +class ReceivedMessageArgumentIterator{ +public: + ReceivedMessageArgumentIterator( const char *typeTags, const char *arguments ) + : value_( typeTags, arguments ) {} + + ReceivedMessageArgumentIterator operator++() + { + Advance(); + return *this; + } + + ReceivedMessageArgumentIterator operator++(int) + { + ReceivedMessageArgumentIterator old( *this ); + Advance(); + return old; + } + + const ReceivedMessageArgument& operator*() const { return value_; } + + const ReceivedMessageArgument* operator->() const { return &value_; } + + friend bool operator==(const ReceivedMessageArgumentIterator& lhs, + const ReceivedMessageArgumentIterator& rhs ); + +private: + ReceivedMessageArgument value_; + + void Advance(); + + bool IsEqualTo( const ReceivedMessageArgumentIterator& rhs ) const + { + return value_.typeTag_ == rhs.value_.typeTag_; + } +}; + +inline bool operator==(const ReceivedMessageArgumentIterator& lhs, + const ReceivedMessageArgumentIterator& rhs ) +{ + return lhs.IsEqualTo( rhs ); +} + +inline bool operator!=(const ReceivedMessageArgumentIterator& lhs, + const ReceivedMessageArgumentIterator& rhs ) +{ + return !( lhs == rhs ); +} + + +class ReceivedMessageArgumentStream{ + friend class ReceivedMessage; + ReceivedMessageArgumentStream( const ReceivedMessageArgumentIterator& begin, + const ReceivedMessageArgumentIterator& end ) + : p_( begin ) + , end_( end ) {} + + ReceivedMessageArgumentIterator p_, end_; + +public: + + // end of stream + bool Eos() const { return p_ == end_; } + + ReceivedMessageArgumentStream& operator>>( bool& rhs ) + { + if( Eos() ) + throw MissingArgumentException(); + + rhs = (*p_++).AsBool(); + return *this; + } + + // not sure if it would be useful to stream Nil and Infinitum + // for now it's not possible + + ReceivedMessageArgumentStream& operator>>( int32& rhs ) + { + if( Eos() ) + throw MissingArgumentException(); + + rhs = (*p_++).AsInt32(); + return *this; + } + + ReceivedMessageArgumentStream& operator>>( float& rhs ) + { + if( Eos() ) + throw MissingArgumentException(); + + rhs = (*p_++).AsFloat(); + return *this; + } + + ReceivedMessageArgumentStream& operator>>( char& rhs ) + { + if( Eos() ) + throw MissingArgumentException(); + + rhs = (*p_++).AsChar(); + return *this; + } + + ReceivedMessageArgumentStream& operator>>( RgbaColor& rhs ) + { + if( Eos() ) + throw MissingArgumentException(); + + rhs.value = (*p_++).AsRgbaColor(); + return *this; + } + + ReceivedMessageArgumentStream& operator>>( MidiMessage& rhs ) + { + if( Eos() ) + throw MissingArgumentException(); + + rhs.value = (*p_++).AsMidiMessage(); + return *this; + } + + ReceivedMessageArgumentStream& operator>>( int64& rhs ) + { + if( Eos() ) + throw MissingArgumentException(); + + rhs = (*p_++).AsInt64(); + return *this; + } + + ReceivedMessageArgumentStream& operator>>( TimeTag& rhs ) + { + if( Eos() ) + throw MissingArgumentException(); + + rhs.value = (*p_++).AsTimeTag(); + return *this; + } + + ReceivedMessageArgumentStream& operator>>( double& rhs ) + { + if( Eos() ) + throw MissingArgumentException(); + + rhs = (*p_++).AsDouble(); + return *this; + } + + ReceivedMessageArgumentStream& operator>>( Blob& rhs ) + { + if( Eos() ) + throw MissingArgumentException(); + + (*p_++).AsBlob( rhs.data, rhs.size ); + return *this; + } + + ReceivedMessageArgumentStream& operator>>( const char*& rhs ) + { + if( Eos() ) + throw MissingArgumentException(); + + rhs = (*p_++).AsString(); + return *this; + } + + ReceivedMessageArgumentStream& operator>>( Symbol& rhs ) + { + if( Eos() ) + throw MissingArgumentException(); + + rhs.value = (*p_++).AsSymbol(); + return *this; + } + + ReceivedMessageArgumentStream& operator>>( MessageTerminator& rhs ) + { + if( !Eos() ) + throw ExcessArgumentException(); + + return *this; + } +}; + + +class ReceivedMessage{ + void Init( const char *bundle, unsigned long size ); +public: + explicit ReceivedMessage( const ReceivedPacket& packet ); + explicit ReceivedMessage( const ReceivedBundleElement& bundleElement ); + + const char *AddressPattern() const { return addressPattern_; } + + // Support for non-standad SuperCollider integer address patterns: + bool AddressPatternIsUInt32() const; + uint32 AddressPatternAsUInt32() const; + + unsigned long ArgumentCount() const { return static_cast<unsigned long>(typeTagsEnd_ - typeTagsBegin_); } + + const char *TypeTags() const { return typeTagsBegin_; } + + + typedef ReceivedMessageArgumentIterator const_iterator; + + ReceivedMessageArgumentIterator ArgumentsBegin() const + { + return ReceivedMessageArgumentIterator( typeTagsBegin_, arguments_ ); + } + + ReceivedMessageArgumentIterator ArgumentsEnd() const + { + return ReceivedMessageArgumentIterator( typeTagsEnd_, 0 ); + } + + ReceivedMessageArgumentStream ArgumentStream() const + { + return ReceivedMessageArgumentStream( ArgumentsBegin(), ArgumentsEnd() ); + } + +private: + const char *addressPattern_; + const char *typeTagsBegin_; + const char *typeTagsEnd_; + const char *arguments_; +}; + + +class ReceivedBundle{ + void Init( const char *message, unsigned long size ); +public: + explicit ReceivedBundle( const ReceivedPacket& packet ); + explicit ReceivedBundle( const ReceivedBundleElement& bundleElement ); + + uint64 TimeTag() const; + + unsigned long ElementCount() const { return elementCount_; } + + typedef ReceivedBundleElementIterator const_iterator; + + ReceivedBundleElementIterator ElementsBegin() const + { + return ReceivedBundleElementIterator( timeTag_ + 8 ); + } + + ReceivedBundleElementIterator ElementsEnd() const + { + return ReceivedBundleElementIterator( end_ ); + } + +private: + const char *timeTag_; + const char *end_; + unsigned long elementCount_; +}; + + +} // namespace osc + + +#endif /* INCLUDED_OSCRECEIVEDELEMENTS_H */ diff --git a/src/osc/osc/OscTypes.cpp b/src/osc/osc/OscTypes.cpp new file mode 100755 index 0000000..889ab43 --- /dev/null +++ b/src/osc/osc/OscTypes.cpp @@ -0,0 +1,40 @@ +/* + oscpack -- Open Sound Control packet manipulation library + http://www.audiomulch.com/~rossb/oscpack + + Copyright (c) 2004-2005 Ross Bencina <rossb@audiomulch.com> + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + Any person wishing to distribute modifications to the Software is + requested to send the modifications to the original developer so that + they can be incorporated into the canonical version. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +#include "OscTypes.h" + +namespace osc{ + +BundleInitiator BeginBundleImmediate(1); +BundleTerminator EndBundle; +MessageTerminator EndMessage; +NilType Nil; +InfinitumType Infinitum; + +} // namespace osc diff --git a/src/osc/osc/OscTypes.h b/src/osc/osc/OscTypes.h new file mode 100755 index 0000000..81549b5 --- /dev/null +++ b/src/osc/osc/OscTypes.h @@ -0,0 +1,178 @@ +/* + oscpack -- Open Sound Control packet manipulation library + http://www.audiomulch.com/~rossb/oscpack + + Copyright (c) 2004-2005 Ross Bencina <rossb@audiomulch.com> + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + Any person wishing to distribute modifications to the Software is + requested to send the modifications to the original developer so that + they can be incorporated into the canonical version. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +#ifndef INCLUDED_OSCTYPES_H +#define INCLUDED_OSCTYPES_H + + +namespace osc{ + +// basic types + +#if defined(__BORLANDC__) || defined(_MSC_VER) + +typedef __int64 int64; +typedef unsigned __int64 uint64; + +#else + +typedef long long int64; +typedef unsigned long long uint64; + +#endif + + + +#ifdef x86_64 + +typedef signed int int32; +typedef unsigned int uint32; + +#else + +typedef signed long int32; +typedef unsigned long uint32; + +#endif + + + +enum TypeTagValues { + TRUE_TYPE_TAG = 'T', + FALSE_TYPE_TAG = 'F', + NIL_TYPE_TAG = 'N', + INFINITUM_TYPE_TAG = 'I', + INT32_TYPE_TAG = 'i', + FLOAT_TYPE_TAG = 'f', + CHAR_TYPE_TAG = 'c', + RGBA_COLOR_TYPE_TAG = 'r', + MIDI_MESSAGE_TYPE_TAG = 'm', + INT64_TYPE_TAG = 'h', + TIME_TAG_TYPE_TAG = 't', + DOUBLE_TYPE_TAG = 'd', + STRING_TYPE_TAG = 's', + SYMBOL_TYPE_TAG = 'S', + BLOB_TYPE_TAG = 'b' +}; + + + +// i/o manipulators used for streaming interfaces + +struct BundleInitiator{ + explicit BundleInitiator( uint64 timeTag_ ) : timeTag( timeTag_ ) {} + uint64 timeTag; +}; + +extern BundleInitiator BeginBundleImmediate; + +inline BundleInitiator BeginBundle( uint64 timeTag=1 ) +{ + return BundleInitiator(timeTag); +} + + +struct BundleTerminator{ +}; + +extern BundleTerminator EndBundle; + +struct BeginMessage{ + explicit BeginMessage( const char *addressPattern_ ) : addressPattern( addressPattern_ ) {} + const char *addressPattern; +}; + +struct MessageTerminator{ +}; + +extern MessageTerminator EndMessage; + + +// osc specific types. they are defined as structs so they can be used +// as separately identifiable types with the streaming operators. + +struct NilType{ +}; + +extern NilType Nil; + + +struct InfinitumType{ +}; + +extern InfinitumType Infinitum; + +struct RgbaColor{ + RgbaColor() {} + explicit RgbaColor( uint32 value_ ) : value( value_ ) {} + uint32 value; + + operator uint32() const { return value; } +}; + + +struct MidiMessage{ + MidiMessage() {} + explicit MidiMessage( uint32 value_ ) : value( value_ ) {} + uint32 value; + + operator uint32() const { return value; } +}; + + +struct TimeTag{ + TimeTag() {} + explicit TimeTag( uint64 value_ ) : value( value_ ) {} + uint64 value; + + operator uint64() const { return value; } +}; + + +struct Symbol{ + Symbol() {} + explicit Symbol( const char* value_ ) : value( value_ ) {} + const char* value; + + operator const char *() const { return value; } +}; + + +struct Blob{ + Blob() {} + explicit Blob( const void* data_, unsigned long size_ ) + : data( data_ ), size( size_ ) {} + const void* data; + unsigned long size; +}; + +} // namespace osc + + +#endif /* INCLUDED_OSCTYPES_H */ diff --git a/src/win/WinWindow.cpp b/src/win/WinWindow.cpp new file mode 100644 index 0000000..5620406 --- /dev/null +++ b/src/win/WinWindow.cpp @@ -0,0 +1,122 @@ +/*************************************************************************** + * MacWindow.cpp + * Part of + * 2016- Florent Berthaut + * hitmuri.net + ****************************************************************************/ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "MacWindow.hpp" +#include <iostream> + +using namespace std; + +MacWindow::MacWindow(OvWindowsManager* man, CGWindowID id): OvWindow(man) { + CGRect rect = CGRectMake(0, 0, 100, 100); + m_idCArray[0] = id; + m_idArray = CFArrayCreate(NULL, (const void **) m_idCArray, 1, NULL); + CFArrayRef descArray = CGWindowListCreateDescriptionFromArray(m_idArray); + if(CFArrayGetCount(descArray)>0) { + CFDictionaryRef description = + (CFDictionaryRef) CFArrayGetValueAtIndex(descArray, 0); + if (CFDictionaryContainsKey(description, kCGWindowBounds)) { + CFDictionaryRef bounds = + (CFDictionaryRef) CFDictionaryGetValue(description, + kCGWindowBounds); + if (bounds) { + CGRectMakeWithDictionaryRepresentation(bounds, &rect); + } + } + } + CFRelease(descArray); + + m_posX = rect.origin.x; + m_posY = rect.origin.y; + m_width = rect.size.width; + m_height = rect.size.height; + m_offsetX = m_posX; + m_offsetY = m_posY; + m_pixPerRow = m_width; + m_isBGR=true; + m_needsOffset=false; + m_pixPerRow=m_width; +} + +MacWindow::~MacWindow() { + CFRelease(m_idArray); +} + +int MacWindow::computeNbPixPerRow(const int& srcW, const int& srcH) { + CGRect rect = CGRectMake(m_posX, m_posY, srcW, srcH); + CGImageRef img = CGWindowListCreateImageFromArray(rect, + m_idArray, + kCGWindowImageBoundsIgnoreFraming); + int nb = CGImageGetBytesPerRow(img)/4; + CFRelease(img); + return nb; +} + +void MacWindow::getPixels(const int& srcX, const int& srcY, + const int& srcW, const int& srcH, + const vector<int>& srcIndices, + const vector<vector<int> >& srcCoords, + const vector<int>& destIndices, + const uchar& alpha, + const ZoneWidget::ZONE_COLOR& color, + uchar* destImg) { + CGRect rect = CGRectMake(m_posX+srcX, m_posY+srcY, srcW, srcH); + CGImageRef img = CGWindowListCreateImageFromArray(rect, + m_idArray, + kCGWindowImageBoundsIgnoreFraming); + if(img!=NULL) { + CFDataRef data = CGDataProviderCopyData(CGImageGetDataProvider(img)); + uchar* buf = (uchar*)CFDataGetBytePtr(data); + vector<int>::const_iterator itId = srcIndices.begin(); + vector<vector<int> >::const_iterator itCo = srcCoords.begin(); + vector<int>::const_iterator itDe = destIndices.begin(); + for(; itCo!=srcCoords.end(); ++itCo) { + if((*itCo)[0]>=0) { + //copy rgb + for(int c=0; c<3; ++c) { + destImg[(*itDe)] = (color==ZoneWidget::NORMAL) + ?buf[(*itId)] + :255-buf[(*itId)]; + ++itId; + ++itDe; + } + //copy alpha + destImg[(*itDe)] = alpha; + ++itId; + ++itDe; + } + else { //black + for(int c=0; c<4; ++c) { + destImg[(*itDe)] = 0; + ++itId; + ++itDe; + } + } + } + //release CG stuff + CFRelease(data); + CFRelease(img); + } + else { + cout<<"Error getting image of window "<<m_name<<endl; + } +} + diff --git a/src/win/WinWindow.hpp b/src/win/WinWindow.hpp new file mode 100644 index 0000000..2c2cfb0 --- /dev/null +++ b/src/win/WinWindow.hpp @@ -0,0 +1,53 @@ +/*************************************************************************** + * MacWindow.hpp + * Part of + * 2016- Florent Berthaut + * hitmuri.net + ****************************************************************************/ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + + +#ifndef MacWindow_h +#define MacWindow_h + +#include <vector> +#include <string> + +#include "../OvWindow.hpp" +#import <ApplicationServices/ApplicationServices.h> + +class MacWindow: public OvWindow { + public : + MacWindow(OvWindowsManager* man, CGWindowID winID); + virtual ~MacWindow(); + virtual void getPixels(const int& x, const int& y, + const int& sx, const int& sy, + const std::vector<int>& srcIndices, + const std::vector<std::vector<int> >& srcCoords, + const std::vector<int>& destIndices, + const uchar& alpha, + const ZoneWidget::ZONE_COLOR& color, + uchar* destImg); + int computeNbPixPerRow(const int& srcW, const int& srcH); + + protected: + CGWindowID m_idCArray[1]; + CFArrayRef m_idArray; +}; + +#endif + diff --git a/src/win/WinWindowsManager.cpp b/src/win/WinWindowsManager.cpp new file mode 100644 index 0000000..5a4e6a6 --- /dev/null +++ b/src/win/WinWindowsManager.cpp @@ -0,0 +1,75 @@ +/*************************************************************************** + * MacWindowsManager.cpp + * Part of + * 2016- Florent Berthaut + * hitmuri.net + ****************************************************************************/ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* + Relies on code from: + https://github.com/JoelSjogren/window-copy/blob/master/windowutils.cpp +*/ + +#include "MacWindowsManager.hpp" +#include <iostream> +#include <stdlib.h> + +using namespace std; + +MacWindowsManager::MacWindowsManager(): OvWindowsManager() { + +} + +MacWindowsManager::~MacWindowsManager() { + +} + +void MacWindowsManager::updateWindowsList() { + m_windowList.clear(); + CFArrayRef windowArray = + CGWindowListCreate(kCGWindowListExcludeDesktopElements + |kCGWindowListOptionOnScreenOnly, kCGNullWindowID); + CFArrayRef descArray = CGWindowListCreateDescriptionFromArray(windowArray); + if(descArray!=NULL) { + for(int i=0; i<CFArrayGetCount(descArray); ++i) { + CFDictionaryRef win = + (CFDictionaryRef)CFArrayGetValueAtIndex(descArray,i); + CFStringRef name = + (CFStringRef) CFDictionaryGetValue(win, kCGWindowName); + if(name!=NULL) { + if(CFStringGetLength(name)>0) { + char tempStr[128]; + CFStringGetCString(name,tempStr,128,kCFStringEncodingUTF8); + string nameStr(tempStr); + MacWindow* newWin = + new MacWindow(this, + (CGWindowID)(uintptr_t) + CFArrayGetValueAtIndex(windowArray,i)); + newWin->setName(nameStr); + m_windowList.push_back(newWin); + } + } + CFRelease(win); + } + } + else { + cout<<"Error: No windows found"<<endl; + } +} + + diff --git a/src/win/WinWindowsManager.hpp b/src/win/WinWindowsManager.hpp new file mode 100644 index 0000000..fd5de79 --- /dev/null +++ b/src/win/WinWindowsManager.hpp @@ -0,0 +1,42 @@ +/*************************************************************************** + * MacWindowsManager.hpp + * Part of Over + * 2013 Florent Berthaut + * hitmuri.net + ****************************************************************************/ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + + +#ifndef MacWindowsManager_h +#define MacWindowsManager_h + +#include <vector> +#include <string> + +#include "../OvWindowsManager.hpp" +#include "MacWindow.hpp" + +class MacWindowsManager : public OvWindowsManager { + public : + MacWindowsManager(); + virtual ~MacWindowsManager(); + virtual void updateWindowsList(); + protected: +}; + +#endif + diff --git a/src/x11/XWindow.cpp b/src/x11/XWindow.cpp new file mode 100644 index 0000000..d1eb557 --- /dev/null +++ b/src/x11/XWindow.cpp @@ -0,0 +1,154 @@ +/*************************************************************************** + * XWindow.cpp + * Part of + * 2016- Florent Berthaut + * hitmuri.net + ****************************************************************************/ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "XWindow.hpp" +#include <iostream> + +using namespace std; + +XWindow::XWindow(CutWindowsManager* man, + Display* disp, + const Window& win): CutWindow(man), + m_disp(disp), + m_win(win) { + Window root; + unsigned int bw, d, w, h; + XGetGeometry(m_disp, m_win, &root, &m_posX, &m_posY, + &w, &h, &bw, &d); + m_width=w; + m_height=h; + m_offsetX=0; + m_offsetY=0; + m_pixPerRow=m_width; + m_isBGR=false; + m_needsOffset=true; + m_grabbed=false; + m_defaultImgData = new uchar[m_width*m_height*3]; +} + +int XWindow::computeNbPixPerRow(const int& srcW, const int& srcH) { + XWindowAttributes attrs; + XGetWindowAttributes(m_disp, m_win, &attrs); + m_width=attrs.width; + m_height=attrs.height; + m_pixPerRow=m_width; + return m_width; +} + +void XWindow::getPixels(const int& srcX, const int& srcY, + const int& srcW, const int& srcH, + const vector<int>& srcIndices, + const vector<vector<int> >& srcCoords, + const vector<int>& destIndices, + const uchar& alpha, + const ZoneWidget::ZONE_COLOR& color, + uchar* destImg) { + XImage* ximg = XGetImage(m_disp, m_win, + srcX, srcY, + srcW, srcH, + AllPlanes, XYPixmap); + if(ximg!=NULL) { + unsigned long red_mask = ximg->red_mask; + unsigned long green_mask = ximg->green_mask; + unsigned long blue_mask = ximg->blue_mask; + + vector<int>::const_iterator itId = srcIndices.begin(); + vector<vector<int> >::const_iterator itCo = srcCoords.begin(); + vector<int>::const_iterator itDe = destIndices.begin(); + for(; itCo!=srcCoords.end(); ++itCo) { + if((*itCo)[0]>=0) {//get and copy pixel + unsigned long pixel = XGetPixel(ximg, (*itCo)[0], (*itCo)[1]); + destImg[(*itDe)] = (color==ZoneWidget::NORMAL) + ?(pixel & red_mask) >> 16 + :255-((pixel & red_mask) >> 16); + ++itDe; + destImg[(*itDe)] = (color==ZoneWidget::NORMAL) + ?(pixel & green_mask) >> 8 + :255-((pixel & green_mask) >> 8); + ++itDe; + destImg[(*itDe)] = (color==ZoneWidget::NORMAL) + ?(pixel & blue_mask) + :255-(pixel & blue_mask); + ++itDe; + destImg[(*itDe)] = alpha; + ++itDe; + } + else { //black + destImg[(*itDe)] = 0; + ++itDe; + destImg[(*itDe)] = 0; + ++itDe; + destImg[(*itDe)] = 0; + ++itDe; + destImg[(*itDe)] = 0; + ++itDe; + } + } + XDestroyImage(ximg); + } +} + +void XWindow::releaseImage() { + if(m_grabbed) { + if(m_ximg) { + XDestroyImage(m_ximg); + } + m_grabbed=false; + } +} + +uchar* XWindow::grabImage() { + if(!m_grabbed) { + m_ximg = XGetImage(m_disp, m_win, + 0, 0, + m_width, m_height, + AllPlanes, ZPixmap); + if(m_ximg) { + m_imgData=(uchar*)m_ximg->data; + } + else { + m_imgData=m_defaultImgData; + } + m_grabbed=true; + } + return m_imgData; +} + +void XWindow::storeImage(const int& id) { + int size = m_pixPerRow*m_height*4; + if(m_storedImgData.find(id)!=m_storedImgData.end()) { + delete [] m_storedImgData[id]; + } + m_storedImgData[id] = new uchar[size]; + memcpy(m_storedImgData[id], m_imgData, size); +} + +uchar* XWindow::retrieveImage(const int& id) { + map<int, uchar*>::iterator itSt = m_storedImgData.find(id); + if(itSt!=m_storedImgData.end()) { + return itSt->second; + } + return m_imgData; +} + + + diff --git a/src/x11/XWindow.hpp b/src/x11/XWindow.hpp new file mode 100644 index 0000000..0d36a61 --- /dev/null +++ b/src/x11/XWindow.hpp @@ -0,0 +1,62 @@ +/*************************************************************************** + * CutWindow.hpp + * Part of Over + * 2013 Florent Berthaut + * hitmuri.net + ****************************************************************************/ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + + +#ifndef XWindow_h +#define XWindow_h + +#include <vector> +#include <string> + +#include "../CutWindow.hpp" + +#include <X11/Xlib.h> +#include <X11/Xatom.h> +#include <X11/Xutil.h> + +class XWindow: public CutWindow { + public : + XWindow(CutWindowsManager* man, Display* disp, const Window& win); + virtual ~XWindow(){} + virtual void getPixels(const int& x, const int& y, + const int& sx, const int& sy, + const std::vector<int>& srcIndices, + const std::vector<std::vector<int> >& srcCoords, + const std::vector<int>& destIndices, + const uchar& alpha, + const ZoneWidget::ZONE_COLOR& color, + uchar* destImg); + int computeNbPixPerRow(const int& srcW, const int& srcH); + virtual uchar* grabImage(); + virtual void releaseImage(); + + virtual void storeImage(const int& id); + virtual uchar* retrieveImage(const int& id); + + protected: + Display* m_disp; + Window m_win; + XImage* m_ximg; +}; + +#endif + diff --git a/src/x11/XWindowsManager.cpp b/src/x11/XWindowsManager.cpp new file mode 100644 index 0000000..cab2c04 --- /dev/null +++ b/src/x11/XWindowsManager.cpp @@ -0,0 +1,93 @@ +/*************************************************************************** + * XWindowsManager.cpp + * Part of + * 2016- Florent Berthaut + * hitmuri.net + ****************************************************************************/ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "XWindowsManager.hpp" +#include <iostream> +#include <stdlib.h> + +using namespace std; + +XWindowsManager::XWindowsManager(): CutWindowsManager() { + m_disp = XOpenDisplay(NULL); + if (!m_disp) { + cout<<"No display found"<<endl; + } +} + +XWindowsManager::~XWindowsManager() { + XCloseDisplay(m_disp); +} + +void XWindowsManager::updateWindowsList() { + map<string, CutWindow*> prevWins; + vector<CutWindow*>::iterator itWin=m_windowList.begin(); + for(; itWin!=m_windowList.end(); ++itWin) { + prevWins[(*itWin)->getName()]=(*itWin); + } + m_windowList.clear(); + unsigned long len; + Window *list = getWinList(m_disp, &len); + for(int i=0;i<(int)len;i++) { + char* name = getWinName(m_disp, list[i]); + CutWindow* newWin=NULL; + if(prevWins.find(string(name))!=prevWins.end()) { + newWin = prevWins[string(name)]; + } + else { + newWin = new XWindow(this, m_disp, list[i]); + } + newWin->setName(string(name)); + m_windowList.push_back(newWin); + free(name); + } + XFree(list); +} + +char* XWindowsManager::getWinName(Display* disp, Window win) { + Atom prop = XInternAtom(disp,"WM_NAME",False), type; + int form; + unsigned long remain, len; + unsigned char *list; + + if (XGetWindowProperty(disp,win,prop,0,1024,False,XA_STRING, + &type,&form,&len,&remain,&list) != Success) { + cout<<"Error getting window name"<<endl; + return NULL; + } + return (char*)list; +} + +Window* XWindowsManager::getWinList(Display* disp, unsigned long* len) { + Atom prop = XInternAtom(disp,"_NET_CLIENT_LIST",False), type; + int form; + unsigned long remain; + unsigned char *list; + if(XGetWindowProperty(disp,XDefaultRootWindow(disp), + prop,0,1024,False,XA_WINDOW, + &type,&form,len,&remain,&list) != Success) { + cout<<"Error getting windows list"<<endl; + return 0; + } + return (Window*)list; +} + + diff --git a/src/x11/XWindowsManager.hpp b/src/x11/XWindowsManager.hpp new file mode 100644 index 0000000..a5d99e8 --- /dev/null +++ b/src/x11/XWindowsManager.hpp @@ -0,0 +1,49 @@ +/*************************************************************************** + * XWindowsManager.hpp + * Part of Over + * 2013 Florent Berthaut + * hitmuri.net + ****************************************************************************/ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + + +#ifndef XWindowsManager_h +#define XWindowsManager_h + +#include <vector> +#include <string> +#include <X11/Xlib.h> +#include <X11/Xatom.h> + +#include "../CutWindowsManager.hpp" +#include "XWindow.hpp" + +class XWindowsManager : public CutWindowsManager { + public : + XWindowsManager(); + virtual ~XWindowsManager(); + virtual void updateWindowsList(); + + Window* getWinList(Display* disp, unsigned long* len); + char* getWinName(Display* disp, Window); + + protected: + Display* m_disp; +}; + +#endif + diff --git a/waf b/waf new file mode 100755 index 0000000000000000000000000000000000000000..e1e34d431afba28d52add07a6094226a1a74374b GIT binary patch literal 87674 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 X|Is@W{X|qm`WE{za4Fl^+HwH^ejc=> literal 0 HcmV?d00001 diff --git a/wscript b/wscript new file mode 100755 index 0000000..698afce --- /dev/null +++ b/wscript @@ -0,0 +1,93 @@ +#!/usr/bin/env python + +import sys, os + +def options(opt): + opt.load('compiler_cxx') + opt.load('compiler_c') + +def configure(conf): + conf.load('compiler_cxx') + conf.load('compiler_c') + + #platform specific + if sys.platform == 'darwin': + conf.env.INCLUDES_OS = ['/opt/local/include', + '/opt/local/include/libxml2'] + conf.env.LIBPATH_OS = ['/opt/local/lib'] + conf.env.LIB_OS = ['m', 'xml2', 'fltk', 'fltk_gl', 'portmidi','GLEW'] + conf.env.FRAMEWORK_OS = ['Cocoa','OpenGL', 'AGL', 'Carbon', + 'Accelerate', 'IOKit','System', 'AppKit', + 'CoreFoundation'] + conf.env.DEFINES_OS = ['OSX=1'] + conf.env.DEFINES_GL = ['GL=1'] + elif sys.platform == 'win32' or sys.platform == 'cygwin': + conf.env.INCLUDES_OS = ['os/win/include/', 'C:\MinGW\include'] + conf.env.LIBPATH_OS = [os.path.join(os.getcwd(), 'os/win/lib/')] + conf.env.LIB_OS = ['m', 'ws2_32', 'xml2', 'GLU', 'GL', + 'fltk', 'fltk_gl','boost_system'] + conf.env.DEFINES_OS = ['WINDOWS=1'] + else : + conf.env.INCLUDES_OS = ['/usr/include', '/usr/local/include', + '/usr/include/libxml2'] + conf.env.LIB_OS = ['X11', 'Xxf86vm', 'm','portmidi','porttime', + 'xml2', 'pthread','GL','GLEW', + 'fltk', 'fltk_gl', 'boost_system'] + conf.env.LIBPATH_OS = ['/usr/local/lib/'] + conf.env.DEFINES_OS = ['LINUX=1'] + conf.env.DEFINES_GL = ['GL=1'] + + + #release specific + conf.env.CXXFLAGS = ['-O3', '-Wall'] + conf.env.DEFINES = ['DEBUG(x)=//x'] + + #debug specific + conf.setenv('debug', env=conf.env.derive()) + conf.env.CXXFLAGS = ['-g', '-Wall'] + conf.env.DEFINES = ['DEBUG(x)=std::cout<< x <<std::endl;'] + +def build(bld): + installPath = '/usr/local/bin' + macApp = False + + if sys.platform == 'darwin': + installPath = '/opt/bin/' + macApp = 'True' + osCode = 'src/mac/*.cpp' + elif sys.platform == 'win32' or sys.platform == 'cygwin': + osCode = 'src/win/*.cpp' + else : + osCode = 'src/x11/*.cpp' + + bld.objects( + source = bld.path.ant_glob('src/osc/ip/posix/*.cpp') + +bld.path.ant_glob('src/osc/ip/*.cpp') + +bld.path.ant_glob('src/osc/osc/*.cpp'), + use = ['OS'], + target = 'oscpack') + + bld.objects( + source = bld.path.ant_glob(osCode), + use = ['OS','GL'], + target = 'winsman') + + bld.program( + source = bld.path.ant_glob('src/*.cpp'), + use = ['OS', 'GL', 'winsman', 'oscpack'], + target = 'controllar'+bld.variant, + vnum = '0.0.1', + install_path = installPath, + mac_app = macApp, + mac_plist = 'data/Info.plist', + mac_resources = 'data/controllar.icns', + ) + +def install(bld): + bld.install('${PREFIX}/bin', 'controllar') + +from waflib.Build import BuildContext, CleanContext +class debug(BuildContext): + cmd = 'debug' + variant = 'debug' + -- GitLab