client

Clone or download

Initial Commit

Modified Files

--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1 @@
+patreon: streampi
A .gitignore
+11 −0
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,11 @@
+target/
+.idea/
+streampi.log
+streampi.log.lck
+gen/
+data/
+
+.settings/
+.project
+.factorypath
+.classpath
A LICENSE
+674 −0
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <https://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 <https://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
+<https://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
+<https://www.gnu.org/licenses/why-not-lgpl.html>.
\ No newline at end of file
--- /dev/null
+++ b/Themes/com.StreamPi.DefaultLight/res/client/style.css
@@ -0,0 +1,12 @@
+/*
+StreamPi Default Light mode
+Fully supports JavaFX CSS
+*/
+
+.dashboard{
+ -fx-background-color: white;
+}
+
+.action_grid_pane
+{
+}
\ No newline at end of file
--- /dev/null
+++ b/Themes/com.StreamPi.DefaultLight/theme.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<configuration>
+ <theme-platform-version>1.0.0</theme-platform-version>
+
+ <info>
+ <short-name>Default Light</short-name>
+ <author>StreamPi Team</author>
+ <version>1.0.0</version>
+ </info>
+
+ <theme>
+ <stylesheets>
+ <stylesheet>res/client/style.css</stylesheet>
+ </stylesheets>
+ </theme>
+</configuration>
\ No newline at end of file
Binary files /dev/null and b/Themes/com.StreamPi.ROG_Cybercity_2077/res/client/Cyberpunk.ttf differ
--- /dev/null
+++ b/Themes/com.StreamPi.ROG_Cybercity_2077/res/client/style.css
@@ -0,0 +1,40 @@
+.dashboard{
+ -fx-background-image: url("ROG_ANIM_BG.gif");
+ -fx-background-repeat:stretch;
+ -fx-background-size: 110% 100%;
+ -fx-background-position: bottom ;
+}
+
+.action_box{
+ -fx-border-color : white;
+ -fx-shape: "M9.00035 7.55536L2.02871 11.4285C1.39378 11.7812 1 12.4505 1 13.1768V21.8232C1 22.5495 1.39378 23.2188 2.02871 23.5715L9.00035 27.4446C9.61973 27.7887 10.3749 27.7794 10.9857 27.4202L17.514 23.58C18.1249 23.2206 18.5 22.5648 18.5 21.8561V13.1439C18.5 12.4352 18.1249 11.7794 17.514 11.42L10.9857 7.57981C10.375 7.22056 9.61973 7.21126 9.00035 7.55536Z";
+}
+
+.action_box_display_text_label
+{
+ -fx-text-fill:white;
+}
+
+.action_box_icon_not_present
+{
+ -fx-background-color: rgba(255,255,255,0.2);
+}
+
+.action_box_onclick
+{
+ -fx-border-color : green;
+}
+
+.action_box_not_onclick
+{
+ -fx-border-color : red;
+}
+
+.ikonli-font-icon {
+ -fx-icon-color : pink;
+}
+
+Button {
+ -fx-background:none;
+ -fx-background-color : rgba(255,255,255,0.2);
+}
--- /dev/null
+++ b/Themes/com.StreamPi.ROG_Cybercity_2077/theme.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<configuration>
+ <theme-platform-version>1.0.0</theme-platform-version>
+
+ <info>
+ <short-name>ROG Cybercity 2077</short-name>
+ <author>Necroxys</author>
+ <version>1.0.0</version>
+ </info>
+
+ <theme>
+ <stylesheets>
+ <stylesheet>res/client/style.css</stylesheet>
+ </stylesheets>
+
+ <fonts>
+ <font>res/client/Cyberpunk.ttf</font>
+ </fonts>
+ </theme>
+</configuration>
--- /dev/null
+++ b/native_reflection_config.json
@@ -0,0 +1,8 @@
+[
+ {
+ "name" : "java.util.logging.FileHandler",
+ "methods" : [
+ { "name" : "<init>", "parameterTypes" : [] }
+ ]
+ }
+]
\ No newline at end of file
A pom.xml
+182 −0
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <groupId>com.StreamPi</groupId>
+ <artifactId>Client</artifactId>
+ <version>1.0.0</version>
+
+ <url>https://stream-pi.com/</url>
+
+ <properties>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ <maven.compiler.source>11</maven.compiler.source>
+ <maven.compiler.target>11</maven.compiler.target>
+
+
+ <client.plugin.version>0.1.35</client.plugin.version>
+
+ <JavaFXVersion>16-ea+5</JavaFXVersion>
+
+ <IkonliVersion>11.5.0</IkonliVersion>
+ <IkonliFA5PackVersion>11.5.0</IkonliFA5PackVersion>
+
+ <ActionAPIVersion>1.0.0</ActionAPIVersion>
+ <UtilVersion>1.0.0</UtilVersion>
+ <ThemeAPIVersion>1.0.0</ThemeAPIVersion>
+
+ <MainClassName>com.StreamPi.Client.Main</MainClassName>
+
+ <JavaFXSDK>/path/to/sdk</JavaFXSDK>
+ </properties>
+
+
+ <dependencies>
+
+ <dependency>
+ <groupId>org.openjfx</groupId>
+ <artifactId>javafx-controls</artifactId>
+ <version>${JavaFXVersion}</version>
+ <!--<systemPath>${JavaFXSDK}/lib/javafx.controls.jar</systemPath>-->
+ </dependency>
+
+ <dependency>
+ <groupId>org.openjfx</groupId>
+ <artifactId>javafx-base</artifactId>
+ <version>${JavaFXVersion}</version>
+ <!--<systemPath>${JavaFXSDK}/lib/javafx.base.jar</systemPath>-->
+ </dependency>
+
+ <dependency>
+ <groupId>org.kordamp.ikonli</groupId>
+ <artifactId>ikonli-fontawesome5-pack</artifactId>
+ <version>${IkonliFA5PackVersion}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.kordamp.ikonli</groupId>
+ <artifactId>ikonli-javafx</artifactId>
+ <version>${IkonliVersion}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>com.StreamPi</groupId>
+ <artifactId>ActionAPI</artifactId>
+ <version>${ActionAPIVersion}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>com.StreamPi</groupId>
+ <artifactId>ThemeAPI</artifactId>
+ <version>${ThemeAPIVersion}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>com.StreamPi</groupId>
+ <artifactId>Util</artifactId>
+ <version>${UtilVersion}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.json</groupId>
+ <artifactId>json</artifactId>
+ <version>20201115</version>
+ </dependency>
+
+ <!-- https://mvnrepository.com/artifact/com.gluonhq.attach/lifecycle -->
+<dependency>
+ <groupId>com.gluonhq.attach</groupId>
+ <artifactId>lifecycle</artifactId>
+ <version>4.0.10</version>
+</dependency>
+
+<dependency>
+ <groupId>com.gluonhq.attach</groupId>
+ <artifactId>util</artifactId>
+ <version>4.0.10</version>
+</dependency>
+ </dependencies>
+
+
+
+ <repositories>
+ <repository>
+ <id>gluon-releases</id>
+ <url>http://nexus.gluonhq.com/nexus/content/repositories/releases/</url>
+ </repository>
+ </repositories>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <version>3.8.1</version>
+ <configuration>
+ <release>11</release>
+ </configuration>
+ </plugin>
+
+ <plugin>
+ <groupId>org.openjfx</groupId>
+ <artifactId>javafx-maven-plugin</artifactId>
+ <version>0.0.5</version>
+ <configuration>
+ <stripDebug>true</stripDebug>
+ <compress>2</compress>
+ <noHeaderFiles>true</noHeaderFiles>
+ <noManPages>true</noManPages>
+ <mainClass>${MainClassName}</mainClass>
+ </configuration>
+ </plugin>
+
+
+ <plugin>
+ <groupId>com.gluonhq</groupId>
+ <artifactId>client-maven-plugin</artifactId>
+ <version>${client.plugin.version}</version>
+ <configuration>
+ <target>android</target>
+ <nativeImageArgs>
+ <list>--initialize-at-build-time=com.sun.org.apache.xml.internal.serializer.ToXMLStream</list>
+ <list>-Dsvm.targetName=android</list>
+ </nativeImageArgs>
+ <bundlesList>
+ <list>com.sun.org.apache.xerces.internal.impl.msg.XMLMessages</list>
+ </bundlesList>
+ <attachList>
+ <list>lifecycle</list>
+ </attachList>
+ <reflectionList>
+ <list>java.util.logging.FileHandler</list>
+ </reflectionList>
+ <mainClass>${MainClassName}</mainClass>
+ </configuration>
+ </plugin>
+
+ <plugin>
+ <artifactId>maven-resources-plugin</artifactId>
+ <version>3.2.0</version>
+ <configuration>
+ <includeEmptyDirs>true</includeEmptyDirs>
+ </configuration>
+ </plugin>
+
+
+ </plugins>
+ </build>
+
+ <pluginRepositories>
+ <pluginRepository>
+ <id>gluon-releases</id>
+ <url>http://nexus.gluonhq.com/nexus/content/repositories/releases/</url>
+ </pluginRepository>
+ <pluginRepository>
+ <id>gluon-snapshots</id>
+ <url>https://nexus.gluonhq.com/nexus/content/repositories/public-snapshots</url>
+ </pluginRepository>
+ </pluginRepositories>
+
+</project>
--- /dev/null
+++ b/src/android/AndroidManifest.xml
@@ -0,0 +1,20 @@
+<?xml version='1.0'?>
+<manifest xmlns:android='http://schemas.android.com/apk/res/android' package='com.StreamPi.Client' android:versionCode='1' android:versionName='1.0'>
+ <supports-screens android:xlargeScreens="true"/>
+ <uses-permission android:name="android.permission.INTERNET"/>
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.WRITE_INTERNAL_STORAGE" />
+
+ <application android:label='StreamPi Client' android:icon="@mipmap/ic_launcher" android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
+ android:requestLegacyExternalStorage="true">
+ <activity android:name='com.gluonhq.helloandroid.MainActivity' android:configChanges="orientation|keyboardHidden">
+ <intent-filter>
+ <category android:name='android.intent.category.LAUNCHER'/>
+ <action android:name='android.intent.action.MAIN'/>
+ </intent-filter>
+ </activity>
+ <activity android:name="com.gluonhq.impl.attach.android.PermissionRequestActivity" />
+ <activity android:name='com.gluonhq.helloandroid.PermissionRequestActivity'/>
+ </application>
+</manifest>
+
Binary files /dev/null and b/src/android/res/mipmap-hdpi/ic_launcher.png differ
Binary files /dev/null and b/src/android/res/mipmap-ldpi/ic_launcher.png differ
Binary files /dev/null and b/src/android/res/mipmap-mdpi/ic_launcher.png differ
Binary files /dev/null and b/src/android/res/mipmap-xhdpi/ic_launcher.png differ
Binary files /dev/null and b/src/android/res/mipmap-xxhdpi/ic_launcher.png differ
Binary files /dev/null and b/src/android/res/mipmap-xxxhdpi/ic_launcher.png differ
--- /dev/null
+++ b/src/main/java/com/StreamPi/Client/Connection/Client.java
@@ -0,0 +1,958 @@
+package com.StreamPi.Client.Connection;
+
+import com.StreamPi.ActionAPI.Action.Action;
+import com.StreamPi.ActionAPI.Action.ActionType;
+import com.StreamPi.ActionAPI.Action.DisplayTextAlignment;
+import com.StreamPi.ActionAPI.Action.Location;
+import com.StreamPi.ActionAPI.ActionProperty.ClientProperties;
+import com.StreamPi.ActionAPI.ActionProperty.Property.Property;
+import com.StreamPi.ActionAPI.ActionProperty.Property.Type;
+import com.StreamPi.Client.IO.Config;
+import com.StreamPi.Client.Info.ClientInfo;
+import com.StreamPi.Client.Profile.ClientProfile;
+import com.StreamPi.Client.Profile.ClientProfiles;
+import com.StreamPi.Client.Window.ExceptionAndAlertHandler;
+import com.StreamPi.Client.Window.Dashboard.ActionGridPane.ActionBox;
+import com.StreamPi.ThemeAPI.Theme;
+import com.StreamPi.Util.Alert.StreamPiAlertType;
+import com.StreamPi.Util.Exception.MinorException;
+import com.StreamPi.Util.Exception.SevereException;
+import com.StreamPi.Util.Exception.StreamPiException;
+import com.StreamPi.Util.Platform.Platform;
+import com.StreamPi.Util.Platform.ReleaseStatus;
+import com.StreamPi.Util.Version.Version;
+import javafx.concurrent.Task;
+import javafx.scene.control.Alert;
+
+import java.io.*;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.util.ArrayList;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.logging.LogManager;
+import java.util.logging.Logger;
+
+public class Client extends Thread{
+
+ private Socket socket;
+
+ //private Logger logger;
+
+ private DataOutputStream dos;
+ private DataInputStream dis;
+
+ private AtomicBoolean stop = new AtomicBoolean(false);
+
+ private ClientListener clientListener;
+ private ExceptionAndAlertHandler exceptionAndAlertHandler;
+
+ private ClientInfo clientInfo;
+
+ private String serverIP;
+ private int serverPort;
+ private Logger logger;
+
+ public Client(String serverIP, int serverPort, ClientListener clientListener, ExceptionAndAlertHandler exceptionAndAlertHandler)
+ {
+ this.serverIP = serverIP;
+ this.serverPort = serverPort;
+
+ this.exceptionAndAlertHandler = exceptionAndAlertHandler;
+ this.clientInfo = ClientInfo.getInstance();
+ this.clientListener = clientListener;
+
+ logger = Logger.getLogger(Client.class.getName());
+
+ new Thread(new Task<Void>() {
+ @Override
+ protected Void call()
+ {
+ try
+ {
+ try {
+ logger.info("Trying to connect to server at "+serverIP+":"+serverPort);
+ socket = new Socket();
+ socket.connect(new InetSocketAddress(serverIP, serverPort), 5000);
+ clientListener.setConnected(true);
+ clientListener.updateSettingsConnectDisconnectButton();
+ logger.info("Connected to "+socket.getRemoteSocketAddress()+" !");
+ } catch (IOException e) {
+ e.printStackTrace();
+
+ clientListener.setConnected(false);
+ clientListener.updateSettingsConnectDisconnectButton();
+ throw new MinorException("Connection Error", "Unable to connect to server. Please check settings and connection and try again.");
+ }
+
+ try
+ {
+ dos = new DataOutputStream(socket.getOutputStream());
+ dis = new DataInputStream(socket.getInputStream());
+ }
+ catch (IOException e)
+ {
+ logger.severe(e.getMessage());
+ e.printStackTrace();
+ throw new MinorException("Unable to set up IO Streams to server. Check connection and try again.");
+ }
+ start();
+ } catch (MinorException e)
+ {
+ exceptionAndAlertHandler.handleMinorException(e);
+ }
+ return null;
+ }
+ }).start();
+ }
+
+ public synchronized void exit()
+ {
+ if(stop.get())
+ return;
+
+ logger.info("Stopping ...");
+
+ try
+ {
+ if(socket!=null)
+ {
+ disconnect();
+ }
+ }
+ catch (SevereException e)
+ {
+ logger.severe(e.getMessage());
+ exceptionAndAlertHandler.handleSevereException(e);
+ e.printStackTrace();
+ }
+ }
+
+
+
+ public void writeToStream(String text) throws SevereException
+ {
+ /*try
+ {
+ logger.debug(text);
+ dos.writeUTF(text);
+ dos.flush();
+ }
+ catch (IOException e)
+ {
+ e.printStackTrace();
+ throw new SevereException("Unable to write to IO Stream!");
+ }*/
+
+ try
+ {
+ byte[] txtBytes = text.getBytes();
+
+ Thread.sleep(50);
+ dos.writeUTF("string:: ::");
+ dos.flush();
+ dos.writeInt(txtBytes.length);
+ dos.flush();
+ write(txtBytes);
+ dos.flush();
+ }
+ catch (IOException | InterruptedException e)
+ {
+ e.printStackTrace();
+ throw new SevereException("Unable to write to IO Stream!");
+ }
+
+ }
+
+ public void write(byte[] array) throws SevereException
+ {
+ try
+ {
+ dos.write(array);
+ }
+ catch (IOException e)
+ {
+ logger.severe(e.getMessage());
+ e.printStackTrace();
+ throw new SevereException("Unable to write to IO Stream!");
+ }
+ }
+
+ @Override
+ public void run() {
+ try
+ {
+ while(!stop.get())
+ {
+ String msg = "";
+
+ try
+ {
+ String raw = dis.readUTF();
+
+ System.out.println("AAAAAAAAAAAAAAAAAA : "+raw);
+
+ int length = dis.readInt();
+
+ String[] precursor = raw.split("::");
+
+ String inputType = precursor[0];
+ String secondArg = precursor[1];
+
+
+ //ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+
+ /*int count;
+ int chunkSize = 512;
+ while (length>0)
+ {
+ if(chunkSize > length)
+ chunkSize = length;
+ else
+ chunkSize = 512;
+
+ byte[] buffer = new byte[chunkSize];
+ count = dis.read(buffer);
+
+ byteArrayOutputStream.write(buffer);
+
+ length-=count;
+ }*/
+
+ /*byte[] buffer = new byte[8192];
+ int read;
+ while((read = dis.read(buffer)) != -1){
+ System.out.println("READ : "+read);
+ byteArrayOutputStream.write(buffer, 0, read);
+ }
+
+
+ byteArrayOutputStream.close();
+
+ byte[] bArr = byteArrayOutputStream.toByteArray();*/
+
+ byte[] bArr = new byte[length];
+
+ dis.readFully(bArr);
+
+ if(inputType.equals("string"))
+ {
+ msg = new String(bArr);
+ }
+ else if(inputType.equals("action_icon"))
+ {
+ System.out.println("asdsdsxxxxxxxxxxxxxxxxxxxxxxxxxxx");
+ String[] secondArgSep = secondArg.split("!!");
+
+ String profileID = secondArgSep[0];
+ String actionID = secondArgSep[1];
+
+ clientListener.getClientProfiles().getProfileFromID(profileID).saveActionIcon(actionID, bArr);
+
+ Action a = clientListener.getClientProfiles().getProfileFromID(profileID).getActionFromID(actionID);
+ clientListener.clearActionBox(a.getLocation().getCol(), a.getLocation().getRow());
+ clientListener.renderAction(profileID, a);
+
+
+ continue;
+ }
+ }
+ catch (IOException e)
+ {
+ logger.severe(e.getMessage());
+ e.printStackTrace();
+
+ clientListener.setConnected(false);
+ clientListener.updateSettingsConnectDisconnectButton();
+
+ if(!stop.get())
+ {
+ //isDisconnect.set(true); //Prevent running disconnect
+ throw new MinorException("Accidentally disconnected from Server! (Failed at readUTF)");
+ }
+
+ exit();
+
+ return;
+ }
+
+
+ logger.info("Received text : '"+msg+"'");
+
+ String[] sep = msg.split("::");
+
+ String command = sep[0];
+
+ switch (command)
+ {
+ case "disconnect" : serverDisconnected(msg);
+ break;
+
+ case "get_client_details" : sendClientDetails();
+ break;
+
+ case "get_profiles" : sendProfileNamesToServer();
+ break;
+
+ case "get_profile_details": sendProfileDetailsToServer(sep[1]);
+ break;
+
+ case "save_action_details": saveActionDetails(sep);
+ break;
+
+ case "delete_action": deleteAction(sep[1], sep[2]);
+ break;
+
+ case "get_themes": sendThemesToServer();
+ break;
+
+ case "save_client_details": saveClientDetails(sep);
+ break;
+
+ case "save_client_profile": saveProfileDetails(sep);
+ break;
+
+ case "delete_profile": deleteProfile(sep[1]);
+ break;
+
+ case "action_failed": actionFailed(sep[1], sep[2]);
+ break;
+
+
+ default: logger.warning("Command '"+command+"' does not match records. Make sure client and server versions are equal.");
+
+ }
+ }
+ }
+ catch (SevereException e)
+ {
+ logger.severe(e.getMessage());
+ e.printStackTrace();
+
+ exceptionAndAlertHandler.handleSevereException(e);
+ }
+ catch (MinorException e)
+ {
+ logger.severe(e.getMessage());
+ e.printStackTrace();
+
+ exceptionAndAlertHandler.handleMinorException(e);
+ }
+ }
+
+
+
+
+ //commands
+
+ /*public void receiveActionIcon(String[] sep) throws MinorException {
+ String profileID = sep[1];
+ String actionID = sep[2];
+ int bytesToRead = Integer.parseInt(sep[3]);
+ int port = Integer.parseInt(sep[4]);
+
+ try
+ {
+ Socket tempSocket = new Socket(socket.getInetAddress(), port);
+ tempSocket.setReceiveBufferSize(bytesToRead);
+
+ tempSocket.setSoTimeout(10000);
+
+ DataInputStream dataInputStream = new DataInputStream(tempSocket.getInputStream());
+
+ byte[] dataIcon = new byte[bytesToRead];
+
+ dataInputStream.read(dataIcon);
+
+ clientProfiles.getProfileFromID(profileID).getActionFromID(actionID).setIcon(dataIcon);
+
+ dataInputStream.close();
+ tempSocket.close();
+ }
+ catch (IOException e)
+ {
+ e.printStackTrace();
+ throw new MinorException("Unable to Receive icon");
+ }
+ }*/
+
+
+ public void sendIcon(String profileID, String actionID, byte[] icon) throws SevereException
+ {
+ try
+ {
+ Thread.sleep(50);
+ dos.writeUTF("action_icon::"+profileID+"!!"+actionID+"!!::"+icon.length);
+ dos.flush();
+ dos.writeInt(icon.length);
+ dos.flush();
+ write(icon);
+ dos.flush();
+ }
+ catch (IOException | InterruptedException e)
+ {
+ e.printStackTrace();
+ throw new SevereException("Unable to write to IO Stream!");
+ }
+ }
+
+
+
+ /*public void sendIcon(String profileID, String actionID, byte[] icon) throws SevereException
+ {
+ try
+ {
+
+ ServerSocket tmpServer = new ServerSocket(0);
+
+ dos.writeUTF("action_icon::"+
+ profileID+"::"+
+ actionID+"::"+
+ icon.length+"::"+
+ tmpServer.getLocalPort()+"::");
+
+
+ Socket socket = tmpServer.accept();
+ socket.setSendBufferSize(icon.length);
+
+ DataOutputStream dataOutputStream = new DataOutputStream(socket.getOutputStream());
+
+ dataOutputStream.write(icon);
+
+ dataOutputStream.close();
+
+ tmpServer.close();
+
+ }
+ catch (IOException e)
+ {
+ e.printStackTrace();
+ throw new SevereException(e.getMessage());
+ }
+ }*/
+
+ public void disconnect() throws SevereException
+ {
+ disconnect("");
+ }
+
+ public void disconnect(String message) throws SevereException
+ {
+ if(stop.get())
+ return;
+
+ stop.set(true);
+
+ logger.info("Sending server disconnect message ...");
+ writeToStream("disconnect::"+message+"::");
+
+ try
+ {
+ if(!socket.isClosed())
+ socket.close();
+
+ clientListener.setConnected(false);
+ clientListener.updateSettingsConnectDisconnectButton();
+ }
+ catch (IOException e)
+ {
+ e.printStackTrace();
+ throw new SevereException("Unable to close socket");
+ }
+ }
+
+ public void sendThemesToServer() throws SevereException
+ {
+ StringBuilder finalQuery = new StringBuilder("themes::");
+
+ for(Theme theme : clientListener.getThemes().getThemeList())
+ {
+ finalQuery.append(theme.getFullName())
+ .append("__")
+ .append(theme.getShortName())
+ .append("__")
+ .append(theme.getAuthor())
+ .append("__")
+ .append(theme.getVersion().getText())
+ .append("__::");
+ }
+
+ writeToStream(finalQuery.toString());
+ }
+
+
+ public void sendActionIcon(String clientProfileID, String actionID) throws SevereException
+ {
+ System.out.println("sending action icon "+clientProfileID+", "+actionID);
+ sendIcon(clientProfileID, actionID, clientListener.getClientProfiles().getProfileFromID(clientProfileID).getActionFromID(actionID).getIconAsByteArray());
+ }
+
+ public void serverDisconnected(String message)
+ {
+ stop.set(true);
+ String txt = "Disconnected!";
+
+ if(!message.equals("disconnect::::"))
+ txt = "Message : "+message.split("::")[1];
+
+ exceptionAndAlertHandler.onAlert("Disconnected from Server", txt, StreamPiAlertType.WARNING);
+
+ if(!socket.isClosed()) {
+ try {
+ socket.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ clientListener.setConnected(false);
+ clientListener.updateSettingsConnectDisconnectButton();
+
+ }
+
+ public void sendClientDetails() throws SevereException
+ {
+ Version clientVersion = clientInfo.getVersion();
+ ReleaseStatus releaseStatus = clientInfo.getReleaseStatus();
+ Version clientCommsStandard = clientInfo.getCommsStandardVersion();
+ Version clientMinThemeStandard = clientInfo.getMinThemeSupportVersion();
+ String clientNickname = Config.getInstance().getClientNickName();
+ double screenWidth = Config.getInstance().getStartupWindowWidth();
+ double screenHeight = Config.getInstance().getStartupWindowHeight();
+ Platform OS = clientInfo.getPlatformType();
+ String defaultProfileID = Config.getInstance().getStartupProfileID();
+
+ writeToStream("client_details::"+
+ clientVersion.getText()+"::"+
+ releaseStatus+"::"+
+ clientCommsStandard.getText()+"::"+
+ clientMinThemeStandard.getText()+"::"+
+ clientNickname+"::"+
+ screenWidth+"::"+
+ screenHeight+"::"+
+ OS+"::" +
+ defaultProfileID+"::"+
+ clientListener.getDefaultThemeFullName()+"::");
+ }
+
+
+ public void sendProfileNamesToServer() throws SevereException
+ {
+ StringBuilder finalQuery = new StringBuilder("profiles::");
+
+ finalQuery.append(clientListener.getClientProfiles().getClientProfiles().size()).append("::");
+
+ for(ClientProfile clientProfile : clientListener.getClientProfiles().getClientProfiles())
+ {
+ finalQuery.append(clientProfile.getID()).append("::");
+ }
+
+ writeToStream(finalQuery.toString());
+ }
+
+ public void sendProfileDetailsToServer(String ID) throws SevereException
+ {
+ StringBuilder finalQuery = new StringBuilder("profile_details::");
+
+ ClientProfile clientProfile = clientListener.getClientProfiles().getProfileFromID(ID);
+
+ finalQuery.append(ID).append("::");
+
+ finalQuery.append(clientProfile.getName())
+ .append("::")
+ .append(clientProfile.getRows())
+ .append("::")
+ .append(clientProfile.getCols())
+ .append("::")
+ .append(clientProfile.getActionSize())
+ .append("::")
+ .append(clientProfile.getActionGap())
+ .append("::");
+
+
+ writeToStream(finalQuery.toString());
+
+
+ for(Action action : clientProfile.getActions())
+ {
+ sendActionDetails(clientProfile.getID(), action);
+ }
+
+ for(Action action : clientProfile.getActions())
+ {
+ if(action.isHasIcon())
+ {
+ System.out.println("23123123123 : "+action.getID() + action.isHasIcon());
+ sendActionIcon(clientProfile.getID(), action.getID());
+ }
+ }
+
+ }
+
+ public void sendActionDetails(String profileID, Action action) throws SevereException {
+
+ if(action == null)
+ {
+ logger.info("NO SUCH ACTION");
+ return;
+ }
+
+ StringBuilder finalQuery = new StringBuilder("action_details::");
+
+ finalQuery.append(profileID)
+ .append("::")
+ .append(action.getID())
+ .append("::")
+ .append(action.getActionType())
+ .append("::");
+
+ if(action.getActionType() == ActionType.NORMAL) {
+ finalQuery.append(action.getVersion().getText());
+ }
+
+ finalQuery.append("::");
+
+ if(action.getActionType() ==ActionType.NORMAL)
+ {
+ finalQuery.append(action.getModuleName());
+ }
+
+
+ finalQuery.append("::");
+
+ //display
+
+ finalQuery.append(action.getBgColourHex())
+ .append("::");
+
+ //icon
+ finalQuery.append(action.isHasIcon())
+ .append("::")
+ .append(action.isShowIcon())
+ .append("::");
+
+ //text
+ finalQuery.append(action.isShowDisplayText())
+ .append("::")
+ .append(action.getDisplayTextFontColourHex())
+ .append("::")
+ .append(action.getDisplayText())
+ .append("::")
+ .append(action.getDisplayTextAlignment())
+ .append("::");
+
+ //location
+
+ if(action.getLocation() == null)
+ finalQuery.append("-1::-1::");
+ else
+ finalQuery.append(action.getLocation().getRow())
+ .append("::")
+ .append(action.getLocation().getCol())
+ .append("::");
+
+ //client properties
+
+ ClientProperties clientProperties = action.getClientProperties();
+
+ finalQuery.append(clientProperties.getSize())
+ .append("::");
+
+ for(Property property : clientProperties.get())
+ {
+ finalQuery.append(property.getName())
+ .append("__")
+ .append(property.getRawValue())
+ .append("__");
+
+ finalQuery.append("!!");
+ }
+
+ finalQuery.append("::")
+ .append(action.getParent())
+ .append("::");
+
+
+ writeToStream(finalQuery.toString());
+
+
+ }
+
+ public void saveActionDetails(String[] sep)
+ {
+ String profileID = sep[1];
+
+ String ID = sep[2];
+ ActionType actionType = ActionType.valueOf(sep[3]);
+
+ //4 - Version
+ //5 - ModuleName
+
+ //display
+ String bgColorHex = sep[6];
+
+ //icon
+ boolean isHasIcon = sep[7].equals("true");
+ boolean isShowIcon = sep[8].equals("true");
+
+ //text
+ boolean isShowDisplayText = sep[9].equals("true");
+ String displayFontColor = sep[10];
+ String displayText = sep[11];
+ DisplayTextAlignment displayTextAlignment = DisplayTextAlignment.valueOf(sep[12]);
+
+ //location
+ String row = sep[13];
+ String col = sep[14];
+
+ Location location = new Location(Integer.parseInt(row), Integer.parseInt(col));
+
+ Action action = new Action(ID, actionType);
+
+ if(actionType == ActionType.NORMAL)
+ {
+ try
+ {
+ action.setVersion(new Version(sep[4]));
+ action.setModuleName(sep[5]);
+ }
+ catch (Exception e)
+ {
+ logger.severe(e.getMessage());
+ e.printStackTrace();
+ }
+ }
+
+
+ action.setBgColourHex(bgColorHex);
+
+ action.setShowIcon(isShowIcon);
+ action.setHasIcon(isHasIcon);
+
+ System.out.println("IS HAS ICON : "+isHasIcon+", IS SHOW ICON :"+isShowIcon);
+
+
+ action.setShowDisplayText(isShowDisplayText);
+ action.setDisplayTextFontColourHex(displayFontColor);
+ action.setDisplayText(displayText);
+ action.setDisplayTextAlignment(displayTextAlignment);
+
+
+ action.setLocation(location);
+
+ //client properties
+
+ int clientPropertiesSize = Integer.parseInt(sep[15]);
+
+ String[] clientPropertiesRaw = sep[16].split("!!");
+
+ ClientProperties clientProperties = new ClientProperties();
+
+ if(actionType == ActionType.FOLDER)
+ clientProperties.setDuplicatePropertyAllowed(true);
+
+ for(int i = 0;i<clientPropertiesSize; i++)
+ {
+ String[] clientPraw = clientPropertiesRaw[i].split("__");
+
+ Property property = new Property(clientPraw[0], Type.STRING);
+
+ if(clientPraw.length > 1)
+ property.setRawValue(clientPraw[1]);
+
+ clientProperties.addProperty(property);
+ }
+
+ action.setClientProperties(clientProperties);
+
+
+ String parent = sep[17];
+ action.setParent(parent);
+
+
+ try
+ {
+ Action old = clientListener.getClientProfiles().getProfileFromID(profileID).getActionFromID(action.getID());
+
+ if(old != null)
+ {
+ if(action.isHasIcon())
+ action.setIcon(clientListener.getClientProfiles().getProfileFromID(profileID).getActionFromID(action.getID()).getIconAsByteArray());
+ }
+
+ clientListener.getClientProfiles().getProfileFromID(profileID).addAction(action);
+
+ System.out.println("XXXXXXXXXXX " +action.isHasIcon());
+
+ clientListener.getClientProfiles().getProfileFromID(profileID).saveActions();
+
+ if(clientListener.getCurrentProfile().getID().equals(profileID))
+ {
+ javafx.application.Platform.runLater(()->{
+ ActionBox box = clientListener.getActionBox(action.getLocation().getCol(), action.getLocation().getRow());
+ box.clear();
+ box.setAction(action);
+ box.baseInit();
+ box.init();
+ });
+ }
+
+ //clientListener.clearActionBox(action.getLocation().getCol(), action.getLocation().getRow());
+ //clientListener.renderAction(profileID, action);
+
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ exceptionAndAlertHandler.handleMinorException(new MinorException(e.getMessage()));
+ }
+ }
+
+ public void deleteAction(String profileID, String actionID)
+ {
+ try
+ {
+
+
+ Action acc = clientListener.getClientProfiles().getProfileFromID(profileID).getActionFromID(actionID);
+
+ if(acc.getActionType() == ActionType.FOLDER)
+ {
+ ArrayList<String> idsToBeRemoved = new ArrayList<>();
+
+ ArrayList<String> folders = new ArrayList<>();
+ String folderToBeDeletedID = clientListener.getClientProfiles().getProfileFromID(profileID).getActionFromID(actionID).getID();
+
+ folders.add(folderToBeDeletedID);
+
+ boolean startOver = true;
+ while(startOver)
+ {
+ startOver = false;
+ for(Action action : clientListener.getClientProfiles().getProfileFromID(profileID).getActions())
+ {
+ if(folders.contains(action.getParent()))
+ {
+ if(!idsToBeRemoved.contains(action.getID()))
+ {
+ idsToBeRemoved.add(action.getID());
+ if(action.getActionType() == ActionType.FOLDER)
+ {
+ folders.add(action.getID());
+ startOver = true;
+ }
+ }
+ }
+ }
+ }
+
+
+ for(String ids : idsToBeRemoved)
+ {
+ clientListener.getClientProfiles().getProfileFromID(profileID).removeAction(ids);
+ }
+
+ }
+ else if (acc.getActionType() == ActionType.COMBINE)
+ {
+ for(Property property : acc.getClientProperties().get())
+ {
+ clientListener.getClientProfiles().getProfileFromID(profileID).removeAction(property.getRawValue());
+ }
+ }
+
+
+ clientListener.getClientProfiles().getProfileFromID(profileID).removeAction(acc.getID());
+
+ clientListener.getClientProfiles().getProfileFromID(profileID).saveActions();
+
+ clientListener.clearActionBox(acc.getLocation().getCol(), acc.getLocation().getRow());
+ clientListener.addBlankActionBox(acc.getLocation().getCol(), acc.getLocation().getRow());
+
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ exceptionAndAlertHandler.handleMinorException(new MinorException(e.getMessage()));
+ }
+ }
+
+ public void saveClientDetails(String[] sep)
+ {
+ try
+ {
+
+ Config.getInstance().setNickName(sep[1]);
+
+ String oldWidth = Config.getInstance().getStartupWindowWidth()+"";
+ String oldHeight = Config.getInstance().getStartupWindowHeight()+"";
+
+
+ Config.getInstance().setStartupWindowSize(
+ Double.parseDouble(sep[2]),
+ Double.parseDouble(sep[3])
+ );
+
+ Config.getInstance().setStartupProfileID(sep[4]);
+
+ String oldThemeFullName = Config.getInstance().getCurrentThemeFullName();
+
+ Config.getInstance().setCurrentThemeFullName(sep[5]);
+
+ if(!oldHeight.equals(sep[3]) || !oldWidth.equals(sep[2]) || !oldThemeFullName.equals(sep[5]))
+ javafx.application.Platform.runLater(()-> clientListener.init());
+
+
+ Config.getInstance().save();
+ javafx.application.Platform.runLater(()->clientListener.loadSettings());
+ }
+ catch (SevereException e)
+ {
+ e.printStackTrace();
+ exceptionAndAlertHandler.handleSevereException(e);
+ }
+ }
+
+ public void saveProfileDetails(String[] sep) throws SevereException, MinorException
+ {
+ ClientProfile clientProfile = clientListener.getClientProfiles().getProfileFromID(sep[1]);
+
+ if(clientProfile == null)
+ {
+ clientProfile = new ClientProfile(new File(Config.getInstance().getProfilesPath().toString()+"/"+sep[1]+".xml"),
+ Config.getInstance().getIconsPath());
+ }
+
+ clientProfile.setName(sep[2]);
+ clientProfile.setRows(Integer.parseInt(sep[3]));
+ clientProfile.setCols(Integer.parseInt(sep[4]));
+ clientProfile.setActionSize(Integer.parseInt(sep[5]));
+ clientProfile.setActionGap(Integer.parseInt(sep[6]));
+
+ try
+ {
+ clientListener.getClientProfiles().addProfile(clientProfile);
+ clientProfile.saveProfileDetails();
+ clientListener.refreshGridIfCurrent(sep[1]);
+ javafx.application.Platform.runLater(()->clientListener.loadSettings());
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ throw new SevereException(e.getMessage());
+ }
+ }
+
+ public void deleteProfile(String ID)
+ {
+ clientListener.getClientProfiles().deleteProfile(clientListener.getClientProfiles().getProfileFromID(ID));
+ }
+
+ public void onActionClicked(String profileID, String actionID) throws SevereException {
+ writeToStream("action_clicked::"+profileID+"::"+actionID+"::");
+ }
+
+ public void actionFailed(String profileID, String actionID)
+ {
+ clientListener.onActionFailed(profileID, actionID);
+ }
+}
--- /dev/null
+++ b/src/main/java/com/StreamPi/Client/Connection/ClientListener.java
@@ -0,0 +1,55 @@
+package com.StreamPi.Client.Connection;
+
+import com.StreamPi.ActionAPI.Action.Action;
+import com.StreamPi.ActionAPI.Action.Location;
+import com.StreamPi.Client.Profile.ClientProfile;
+import com.StreamPi.Client.Profile.ClientProfiles;
+import com.StreamPi.Client.Window.Dashboard.ActionGridPane.ActionBox;
+import com.StreamPi.Client.Window.FirstTimeUse.FirstTimeUse;
+import com.StreamPi.ThemeAPI.Theme;
+import com.StreamPi.ThemeAPI.Themes;
+import com.StreamPi.Util.Exception.MinorException;
+import com.StreamPi.Util.Exception.SevereException;
+import com.StreamPi.Util.Exception.StreamPiException;
+
+import javafx.stage.WindowEvent;
+
+public interface ClientListener {
+ void onActionFailed(String profileID, String actionID);
+ void onNormalActionClicked(String profileID, String actionID);
+
+ ClientProfiles getClientProfiles();
+
+ Themes getThemes();
+ String getDefaultThemeFullName();
+
+ void renderRootDefaultProfile() throws SevereException;
+
+ void setConnected(boolean isConnected);
+ boolean isConnected();
+
+ void renderProfile(ClientProfile clientProfile);
+
+ void clearActionBox(int col, int row);
+ void addBlankActionBox(int col, int row);
+ void renderAction(String currentProfileID, Action action);
+ void refreshGridIfCurrent(String currentProfileID);
+
+ ActionBox getActionBox(int col, int row);
+
+ ClientProfile getCurrentProfile();
+
+ Theme getCurrentTheme();
+
+ void init();
+
+ void disconnect(String message) throws SevereException;
+
+ void setupClientConnection();
+
+ void updateSettingsConnectDisconnectButton();
+
+ void onCloseRequest();
+
+ void loadSettings();
+}
--- /dev/null
+++ b/src/main/java/com/StreamPi/Client/Connection/protos/ClientConnectionProtos.proto
@@ -0,0 +1,49 @@
+syntax = "proto3";
+
+package protos;
+
+option java_package = "com.StreamPi.Client.Connection.protos";
+option java_outer_classname = "ClientConnectionProtos";
+
+message ClientAction
+{
+ string moduleName = 1;
+ string id = 2;
+
+ enum ActionType
+ {
+ FOLDER = 0;
+ COMBINE = 1;
+ NORMAL = 2;
+ }
+
+ ActionType actionType = 3;
+ int32 locationX = 4;
+ int32 locationY = 5;
+ bool hasIcon = 6;
+ string name = 7;
+ string actionName = 8;
+
+ repeated ClientProperty clientProperties = 9;
+}
+
+message ClientProperty
+{
+ string name = 1;
+ string value = 2;
+}
+
+message ClientProfile
+{
+ string name = 1;
+ string id = 2;
+
+ int32 rows = 3;
+ int32 cols = 4;
+
+ int32 actionSize = 5;
+ int32 actionGap = 6;
+
+ repeated ClientAction actions = 7;
+}
+
--- /dev/null
+++ b/src/main/java/com/StreamPi/Client/Connection/protos/ClientGRPC.proto
@@ -0,0 +1,13 @@
+syntax = "proto3";
+
+package protos;
+
+option java_package = "com.StreamPi.Client.Connection.protos";
+option java_outer_classname = "ClientGRPC";
+
+import "com/StreamPi/Client/Connection/protos/ClientConnectionProtos.proto";
+
+service Connection
+{
+
+}
\ No newline at end of file
--- /dev/null
+++ b/src/main/java/com/StreamPi/Client/Controller/Controller.java
@@ -0,0 +1,383 @@
+package com.StreamPi.Client.Controller;
+
+import com.StreamPi.ActionAPI.Action.Action;
+import com.StreamPi.ActionAPI.Action.Location;
+import com.StreamPi.Client.Connection.Client;
+import com.StreamPi.Client.IO.Config;
+import com.StreamPi.Client.Info.ClientInfo;
+import com.StreamPi.Client.Main;
+import com.StreamPi.Client.Profile.ClientProfile;
+import com.StreamPi.Client.Profile.ClientProfiles;
+import com.StreamPi.Client.Window.Base;
+import com.StreamPi.Client.Window.Dashboard.ActionGridPane.ActionBox;
+import com.StreamPi.Client.Window.FirstTimeUse.FirstTimeUse;
+import com.StreamPi.Client.Window.Settings.SettingsBase;
+import com.StreamPi.Util.Alert.StreamPiAlert;
+import com.StreamPi.Util.Alert.StreamPiAlertListener;
+import com.StreamPi.Util.Alert.StreamPiAlertType;
+import com.StreamPi.Util.Exception.MinorException;
+import com.StreamPi.Util.Exception.SevereException;
+import com.StreamPi.Util.Exception.StreamPiException;
+import com.StreamPi.Util.IOHelper.IOHelper;
+import com.gluonhq.attach.lifecycle.LifecycleService;
+import com.gluonhq.attach.util.Services;
+
+import javafx.animation.Interpolator;
+import javafx.animation.KeyFrame;
+import javafx.animation.KeyValue;
+import javafx.animation.Timeline;
+import javafx.application.Platform;
+import javafx.concurrent.Task;
+import javafx.event.Event;
+import javafx.scene.Node;
+import javafx.scene.Scene;
+import javafx.scene.control.Alert;
+import javafx.stage.Stage;
+import javafx.stage.StageStyle;
+import javafx.stage.WindowEvent;
+import javafx.util.Duration;
+
+import java.io.*;
+import java.net.URL;
+import java.nio.channels.Channels;
+import java.nio.channels.FileChannel;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardCopyOption;
+import java.util.Enumeration;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+
+
+public class Controller extends Base
+{
+ private Client client;
+
+ public Controller()
+ {
+ client = null;
+ }
+
+
+ private boolean firstRun = true;
+
+ @Override
+ public void init()
+ {
+ try
+ {
+ initBase();
+
+ if(Config.getInstance().isFirstTimeUse())
+ return;
+
+ setupDashWindow();
+
+
+ setupSettingsWindowsAnimations();
+
+
+
+ getDashboardPane().getSettingsButton().setOnAction(event -> {
+ openSettingsTimeLine.play();
+ });
+
+ getSettingsPane().getCloseButton().setOnAction(event -> {
+ closeSettingsTimeLine.play();
+ });
+
+ setClientProfiles(new ClientProfiles(new File(getConfig().getProfilesPath()), getConfig().getStartupProfileID()));
+
+ if(getClientProfiles().getLoadingErrors().size() > 0)
+ {
+ StringBuilder errors = new StringBuilder("Please rectify the following errors and try again");
+
+ for(MinorException exception : getClientProfiles().getLoadingErrors())
+ {
+ errors.append("\n * ")
+ .append(exception.getMessage());
+ }
+
+ throw new MinorException("Profiles", errors.toString());
+ }
+
+ renderRootDefaultProfile();
+ loadSettings();
+
+ if(firstRun)
+ {
+ setupClientConnection();
+ firstRun = false;
+ }
+
+ }
+ catch (SevereException e)
+ {
+ handleSevereException(e);
+ return;
+ }
+ catch (MinorException e)
+ {
+ handleMinorException(e);
+ return;
+ }
+
+
+ }
+
+
+
+ @Override
+ public void setupClientConnection()
+ {
+ Platform.runLater(()->getSettingsPane().setDisableStatus(true));
+ client = new Client(getConfig().getSavedServerHostNameOrIP(), getConfig().getSavedServerPort(), this, this);
+ }
+
+ @Override
+ public void updateSettingsConnectDisconnectButton() {
+ getSettingsPane().setConnectDisconnectButtonStatus();
+ }
+
+ @Override
+ public void disconnect(String message) throws SevereException {
+ client.disconnect(message);
+ }
+
+
+ public void setupDashWindow()
+ {
+ getStage().setTitle("StreamPi Client");
+ getStage().setOnCloseRequest(e->onCloseRequest());
+ }
+
+
+ @Override
+ public void onCloseRequest()
+ {
+ if(isConnected())
+ client.exit();
+
+
+ getLogger().info("Shut down");
+ closeLogger();
+
+ Services.get(LifecycleService.class).ifPresent(LifecycleService::shutdown);
+ }
+
+ @Override
+ public void loadSettings() {
+ try {
+ getSettingsPane().loadData();
+ } catch (SevereException e) {
+ e.printStackTrace();
+ handleSevereException(e);
+ }
+ }
+
+
+ private Timeline openSettingsTimeLine;
+ private Timeline closeSettingsTimeLine;
+
+
+ private void setupSettingsWindowsAnimations()
+ {
+ Node settingsNode = getSettingsPane();
+ Node dashboardNode = getDashboardPane();
+
+ openSettingsTimeLine = new Timeline();
+ openSettingsTimeLine.setCycleCount(1);
+
+
+ openSettingsTimeLine.getKeyFrames().addAll(
+ new KeyFrame(Duration.millis(0.0D),
+ new KeyValue(settingsNode.opacityProperty(),
+ 0.0D, Interpolator.EASE_IN)),
+ new KeyFrame(Duration.millis(90.0D),
+ new KeyValue(settingsNode.opacityProperty(),
+ 1.0D, Interpolator.LINEAR)),
+
+ new KeyFrame(Duration.millis(0.0D),
+ new KeyValue(dashboardNode.opacityProperty(),
+ 1.0D, Interpolator.LINEAR)),
+ new KeyFrame(Duration.millis(90.0D),
+ new KeyValue(dashboardNode.opacityProperty(),
+ 0.0D, Interpolator.LINEAR))
+ );
+
+ openSettingsTimeLine.setOnFinished(event1 -> settingsNode.toFront());
+
+
+ closeSettingsTimeLine = new Timeline();
+ closeSettingsTimeLine.setCycleCount(1);
+
+ closeSettingsTimeLine.getKeyFrames().addAll(
+ new KeyFrame(Duration.millis(0.0D),
+ new KeyValue(settingsNode.opacityProperty(),
+ 1.0D, Interpolator.LINEAR)),
+ new KeyFrame(Duration.millis(90.0D),
+ new KeyValue(settingsNode.opacityProperty(),
+ 0.0D, Interpolator.LINEAR)),
+ new KeyFrame(Duration.millis(0.0D),
+ new KeyValue(dashboardNode.opacityProperty(),
+ 0.0D, Interpolator.LINEAR)),
+ new KeyFrame(Duration.millis(90.0D),
+ new KeyValue(dashboardNode.opacityProperty(),
+ 1.0D, Interpolator.LINEAR))
+ );
+
+ closeSettingsTimeLine.setOnFinished(event1 -> {
+ dashboardNode.toFront();
+ Platform.runLater(()-> {
+ try {
+ getSettingsPane().loadData();
+ } catch (SevereException e) {
+ e.printStackTrace();
+
+ handleSevereException(e);
+ }
+ });
+ });
+ }
+
+
+
+
+
+ @Override
+ public void handleMinorException(MinorException e)
+ {
+ Platform.runLater(()-> genNewAlert(e.getTitle(), e.getShortMessage(), StreamPiAlertType.WARNING).show());
+ }
+
+ @Override
+ public void handleSevereException(SevereException e)
+ {
+ Platform.runLater(()->
+ {
+ StreamPiAlert alert = genNewAlert(e.getTitle(), e.getShortMessage(), StreamPiAlertType.ERROR);
+
+ alert.setOnClicked(new StreamPiAlertListener()
+ {
+ @Override
+ public void onClick(String txt)
+ {
+ onCloseRequest();
+ Platform.exit();
+ }
+ });
+ alert.show();
+ });
+ }
+
+ @Override
+ public void onAlert(String title, String body, StreamPiAlertType alertType) {
+ Platform.runLater(()-> genNewAlert(title, body, alertType).show());
+ }
+
+ public StreamPiAlert genNewAlert(String title, String message, StreamPiAlertType alertType)
+ {
+ StreamPiAlert alert = new StreamPiAlert(title, message, alertType);
+ return alert;
+ }
+
+
+ private boolean isConnected = false;
+
+ @Override
+ public void onActionFailed(String profileID, String actionID) {
+ Platform.runLater(()-> getDashboardPane().getActionGridPane().actionFailed(profileID, actionID));
+ }
+
+ @Override
+ public void onNormalActionClicked(String profileID, String actionID) {
+ try {
+ client.onActionClicked(profileID, actionID);
+ } catch (SevereException e) {
+ e.printStackTrace();
+ handleSevereException(e);
+ }
+ }
+
+ @Override
+ public void setConnected(boolean isConnected) {
+ this.isConnected = isConnected;
+ }
+
+ @Override
+ public ActionBox getActionBox(int col, int row)
+ {
+ return getDashboardPane().getActionGridPane().getActionBox(col, row);
+ }
+
+ @Override
+ public boolean isConnected()
+ {
+ return isConnected;
+ }
+
+ @Override
+ public void renderProfile(ClientProfile clientProfile) {
+ try {
+ getDashboardPane().renderProfile(clientProfile);
+ } catch (SevereException e) {
+ e.printStackTrace();
+ handleSevereException(e);
+ }
+ }
+
+ @Override
+ public void clearActionBox(int col, int row)
+ {
+ Platform.runLater(()->getDashboardPane().getActionGridPane().clearActionBox(col, row));
+ }
+
+ @Override
+ public void addBlankActionBox(int col, int row)
+ {
+ Platform.runLater(()->getDashboardPane().getActionGridPane().addBlankActionBox(col, row));
+ }
+
+ @Override
+ public void renderAction(String currentProfileID, Action action)
+ {
+ Platform.runLater(()->{
+ try {
+ if(getDashboardPane().getActionGridPane().getCurrentParent().equals(action.getParent()) &&
+ getDashboardPane().getActionGridPane().getClientProfile().getID().equals(currentProfileID))
+ {
+ getDashboardPane().getActionGridPane().renderAction(action);
+ }
+
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ });
+ }
+
+ @Override
+ public void refreshGridIfCurrent(String profileID) {
+ ClientProfile clientProfile = getDashboardPane().getActionGridPane().getClientProfile();
+
+ if(clientProfile.getID().equals(profileID))
+ {
+ Platform.runLater(()->{
+ try {
+ getDashboardPane().renderProfile(getClientProfiles().getProfileFromID(profileID));
+ } catch (SevereException e) {
+ e.printStackTrace();
+ handleSevereException(e);
+ }
+ });
+ }
+ }
+
+ @Override
+ public ClientProfile getCurrentProfile() {
+ return getDashboardPane().getActionGridPane().getClientProfile();
+ }
+
+}
--- /dev/null
+++ b/src/main/java/com/StreamPi/Client/IO/Config.java
@@ -0,0 +1,359 @@
+/*
+Config.java
+
+Contributor(s) : Debayan Sutradhar (@rnayabed)
+
+handler for config.xml
+ */
+
+package com.StreamPi.Client.IO;
+
+import java.io.File;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.transform.Result;
+import javax.xml.transform.Source;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+
+import com.StreamPi.Client.Info.ClientInfo;
+import com.StreamPi.Util.Exception.SevereException;
+import com.StreamPi.Util.Platform.Platform;
+import com.StreamPi.Util.XMLConfigHelper.XMLConfigHelper;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+public class Config {
+ private static Config instance = null;
+
+ private final File configFile;
+
+ private Document document;
+
+ private Config() throws SevereException
+ {
+ try
+ {
+ configFile = new File(ClientInfo.getInstance().getPrePath()+"config.xml");
+ DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
+ DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
+ document = docBuilder.parse(configFile);
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ throw new SevereException("Config", "unable to read config.xml");
+ }
+ }
+
+ public static synchronized Config getInstance() throws SevereException
+ {
+ if(instance == null)
+ instance = new Config();
+
+ return instance;
+ }
+
+ public void save() throws SevereException
+ {
+ try
+ {
+ Transformer transformer = TransformerFactory.newInstance().newTransformer();
+ Result output = new StreamResult(configFile);
+ Source input = new DOMSource(document);
+
+ transformer.transform(input, output);
+ }
+ catch (Exception e)
+ {
+ throw new SevereException("Config", "unable to save config.xml");
+ }
+ }
+
+
+ //Client Element
+ public Element getClientElement()
+ {
+ return (Element) document.getElementsByTagName("client").item(0);
+ }
+
+ //Default Values
+ public String getDefaultClientNickName()
+ {
+ return "StreamPi Client";
+ }
+
+ public String getDefaultStartupProfileID()
+ {
+ return "default";
+ }
+
+ public String getDefaultCurrentThemeFullName()
+ {
+ return "com.StreamPi.DefaultLight";
+ }
+
+ public String getDefaultThemesPath()
+ {
+ return "Themes/";
+ }
+
+ public String getDefaultProfilesPath()
+ {
+ return "Profiles/";
+ }
+
+ public String getDefaultIconsPath()
+ {
+ return "Icons/";
+ }
+
+ public int getDefaultStartupWindowWidth()
+ {
+ return 800;
+ }
+
+ public int getDefaultStartupWindowHeight()
+ {
+ return 400;
+ }
+
+ //Getters
+
+ public String getClientNickName()
+ {
+ return XMLConfigHelper.getStringProperty(getClientElement(), "nickname", getDefaultClientNickName(), false, true, document, configFile);
+ }
+
+ public String getStartupProfileID()
+ {
+ return XMLConfigHelper.getStringProperty(getClientElement(), "startup-profile", getDefaultStartupProfileID(), false, true, document, configFile);
+ }
+
+ public String getCurrentThemeFullName()
+ {
+ return XMLConfigHelper.getStringProperty(getClientElement(), "current-theme-full-name", getDefaultCurrentThemeFullName(), false, true, document, configFile);
+ }
+
+ public String getThemesPath()
+ {
+ if(ClientInfo.getInstance().getPlatformType() == Platform.ANDROID)
+ return ClientInfo.getInstance().getPrePath() + "Themes/";
+
+ return XMLConfigHelper.getStringProperty(getClientElement(), "themes-path", getDefaultThemesPath(), false, true, document, configFile);
+ }
+
+ public String getProfilesPath()
+ {
+
+ if(ClientInfo.getInstance().getPlatformType() == Platform.ANDROID)
+ return ClientInfo.getInstance().getPrePath() + "Profiles/";
+
+ return XMLConfigHelper.getStringProperty(getClientElement(), "profiles-path", getDefaultThemesPath(), false, true, document, configFile);
+ }
+
+ public String getIconsPath()
+ {
+ if(ClientInfo.getInstance().getPlatformType() == Platform.ANDROID)
+ return ClientInfo.getInstance().getPrePath() + "Icons/";
+
+ return XMLConfigHelper.getStringProperty(getClientElement(), "icons-path", getDefaultThemesPath(), false, true, document, configFile);
+ }
+
+ public double getStartupWindowWidth()
+ {
+ return XMLConfigHelper.getDoubleProperty((Element) getClientElement().getElementsByTagName("startup-window-size").item(0), "width", getDefaultStartupWindowWidth(), false, true, document, configFile);
+ }
+
+ public double getStartupWindowHeight()
+ {
+ return XMLConfigHelper.getDoubleProperty((Element) getClientElement().getElementsByTagName("startup-window-size").item(0), "height", getDefaultStartupWindowHeight(), false, true, document, configFile);
+ }
+
+
+
+ //Setters
+
+ public void setNickName(String nickName)
+ {
+ getClientElement().getElementsByTagName("nickname").item(0).setTextContent(nickName);
+ }
+
+ public void setStartupProfileID(String id)
+ {
+ getClientElement().getElementsByTagName("startup-profile").item(0).setTextContent(id);
+ }
+
+ public void setCurrentThemeFullName(String name)
+ {
+ getClientElement().getElementsByTagName("current-theme-full-name").item(0).setTextContent(name);
+ }
+
+ public void setProfilesPath(String profilesPath)
+ {
+ getClientElement().getElementsByTagName("profiles-path").item(0).setTextContent(profilesPath);
+ }
+
+ public void setIconsPath(String iconsPath)
+ {
+ getClientElement().getElementsByTagName("icons-path").item(0).setTextContent(iconsPath);
+ }
+
+ public void setThemesPath(String themesPath)
+ {
+ getClientElement().getElementsByTagName("themes-path").item(0).setTextContent(themesPath);
+ }
+
+ //client > startup-window-size
+ public void setStartupWindowSize(double width, double height)
+ {
+ setStartupWindowWidth(width);
+ setStartupWindowHeight(height);
+ }
+
+ public void setStartupWindowWidth(double width)
+ {
+ ((Element) getClientElement().getElementsByTagName("startup-window-size").item(0))
+ .getElementsByTagName("width").item(0).setTextContent(width+"");
+ }
+
+ public void setStartupWindowHeight(double height)
+ {
+ ((Element) getClientElement().getElementsByTagName("startup-window-size").item(0))
+ .getElementsByTagName("height").item(0).setTextContent(height+"");
+ }
+
+
+
+
+
+
+
+
+
+
+
+ //comms-server
+ public Element getCommsServerElement()
+ {
+ return (Element) document.getElementsByTagName("comms-server").item(0);
+ }
+
+ public String getDefaultSavedServerHostNameOrIP()
+ {
+ return "127.0.0.1";
+ }
+
+ public int getDefaultSavedServerPort()
+ {
+ return -1;
+ }
+
+
+ public String getSavedServerHostNameOrIP()
+ {
+ return XMLConfigHelper.getStringProperty(getCommsServerElement(), "hostname-ip", getDefaultSavedServerHostNameOrIP(), false, true, document, configFile);
+ }
+
+ public int getSavedServerPort()
+ {
+ return XMLConfigHelper.getIntProperty(getCommsServerElement(), "port", getDefaultSavedServerPort(), false, true, document, configFile);
+ }
+
+
+ public void setServerHostNameOrIP(String hostNameOrIP)
+ {
+ getCommsServerElement().getElementsByTagName("hostname-ip").item(0).setTextContent(hostNameOrIP);
+ }
+
+ public void setServerPort(int port)
+ {
+ getCommsServerElement().getElementsByTagName("port").item(0).setTextContent(port+"");
+ }
+
+
+
+
+
+
+
+
+ //others
+ public Element getOthersElement()
+ {
+ return (Element) document.getElementsByTagName("others").item(0);
+ }
+
+ //others-default
+
+ public boolean getDefaultStartOnBoot()
+ {
+ return false;
+ }
+
+ public boolean getDefaultFullscreen()
+ {
+ return true;
+ }
+
+ public boolean getDefaultIsShowCursor()
+ {
+ return true;
+ }
+
+ public boolean getDefaultFirstTimeUse()
+ {
+ return true;
+ }
+
+
+
+ public boolean isShowCursor()
+ {
+ return XMLConfigHelper.getBooleanProperty(getOthersElement(), "show-cursor", getDefaultIsShowCursor(), false, true, document, configFile);
+ }
+
+
+ public boolean isStartOnBoot()
+ {
+ return XMLConfigHelper.getBooleanProperty(getOthersElement(), "start-on-boot", getDefaultStartOnBoot(), false, true, document, configFile);
+ }
+
+
+ public boolean isFullscreen()
+ {
+ return XMLConfigHelper.getBooleanProperty(getOthersElement(), "fullscreen", getDefaultFullscreen(), false, true, document, configFile);
+ }
+
+
+ public boolean isFirstTimeUse()
+ {
+ return XMLConfigHelper.getBooleanProperty(getOthersElement(), "first-time-use", true, false, true, document, configFile);
+ }
+
+
+ public void setStartOnBoot(boolean value)
+ {
+ getOthersElement().getElementsByTagName("start-on-boot").item(0).setTextContent(value+"");
+ }
+
+ public void setShowCursor(boolean value)
+ {
+ getOthersElement().getElementsByTagName("show-cursor").item(0).setTextContent(value+"");
+ }
+
+ public void setFullscreen(boolean value)
+ {
+ getOthersElement().getElementsByTagName("fullscreen").item(0).setTextContent(value+"");
+ }
+
+ public void setFirstTimeUse(boolean value)
+ {
+ getOthersElement().getElementsByTagName("first-time-use").item(0).setTextContent(value+"");
+ }
+
+}
--- /dev/null
+++ b/src/main/java/com/StreamPi/Client/Info/ClientInfo.java
@@ -0,0 +1,147 @@
+/*
+ServerInfo.java
+
+Stores basic information about the server - name, platform type
+
+Contributors: Debayan Sutradhar (@dubbadhar)
+ */
+
+package com.StreamPi.Client.Info;
+
+import java.io.File;
+import java.util.Optional;
+import java.util.function.Function;
+
+import com.StreamPi.Util.Exception.MinorException;
+import com.StreamPi.Util.Platform.Platform;
+import com.StreamPi.Util.Platform.ReleaseStatus;
+import com.StreamPi.Util.Version.Version;
+
+public class ClientInfo {
+ private Version version;
+ private final ReleaseStatus releaseStatus;
+ private Platform platformType = null;
+
+ private String prePath;
+
+ private Version minThemeSupportVersion;
+ private Version minPluginSupportVersion;
+ private Version commsStandardVersion;
+
+ private String runnerFileName;
+
+ private static ClientInfo instance = null;
+
+ private ClientInfo(){
+
+ try {
+ version = new Version("1.0.0");
+ minThemeSupportVersion = new Version("1.0.0");
+ minPluginSupportVersion = new Version("1.0.0");
+ commsStandardVersion = new Version("1.0.0");
+ } catch (MinorException e) {
+ e.printStackTrace();
+ }
+
+ releaseStatus = ReleaseStatus.EA;
+
+ // Hardcoded values for android because os.name returns linux on android
+
+ //platformType = Platform.ANDROID;
+ //prePath = "/sdcard/StreamPiClient/";
+
+ if(platformType == null)
+ {
+ String osName = System.getProperty("os.name").toLowerCase();
+ System.out.println("CCVVBB : '"+osName+"'");
+
+ if(osName.contains("windows"))
+ {
+ prePath = "data/";
+ platformType = Platform.WINDOWS;
+ }
+ else if (osName.contains("linux"))
+ {
+ if(osName.contains("raspberrypi"))
+ {
+ prePath = "data/";
+ platformType = Platform.LINUX_RPI;
+ }
+ else
+ {
+ prePath = "data/";
+ platformType = Platform.LINUX;
+ }
+ }
+ else if(osName.contains("android")) // SPECIFY -Dsvm.targetName=android WHILE BUILDING ANDROID NATIVE IMAGE
+ {
+ prePath = "/sdcard/StreamPiClient/";
+ platformType = Platform.ANDROID;
+ }
+ else if (osName.contains("mac"))
+ {
+ prePath = "data/";
+ platformType = Platform.MAC;
+ }
+ else
+ {
+ prePath = "data/";
+ platformType = Platform.UNKNOWN;
+ }
+ }
+
+
+ }
+
+ public void setRunnerFileName(String runnerFileName)
+ {
+ this.runnerFileName = runnerFileName;
+ }
+
+ public String getRunnerFileName()
+ {
+ return runnerFileName;
+ }
+
+ public static synchronized ClientInfo getInstance(){
+ if(instance == null)
+ {
+ instance = new ClientInfo();
+ }
+
+ return instance;
+ }
+
+ public String getPrePath() {
+ return prePath;
+ }
+
+ public Platform getPlatformType()
+ {
+ return platformType;
+ }
+
+ public Version getVersion() {
+ return version;
+ }
+
+ public ReleaseStatus getReleaseStatus()
+ {
+ return releaseStatus;
+ }
+
+ public Version getMinThemeSupportVersion()
+ {
+ return minThemeSupportVersion;
+ }
+
+ public Version getMinPluginSupportVersion()
+ {
+ return minPluginSupportVersion;
+ }
+
+ public Version getCommsStandardVersion()
+ {
+ return commsStandardVersion;
+ }
+}
--- /dev/null
+++ b/src/main/java/com/StreamPi/Client/Info/License.java
@@ -0,0 +1,19 @@
+package com.StreamPi.Client.Info;
+
+public class License {
+ public static String getLicense()
+ {
+ return "StreamPi - Free & Opensource Modular Cross-Platform Programmable Macropad\n" +
+ "Copyright (C) 2020 StreamPi Team\n" +
+ "\n" +
+ "This program is free software: you can redistribute it and/or modify\n" +
+ "it under the terms of the GNU General Public License as published by\n" +
+ "the Free Software Foundation, either version 3 of the License, or\n" +
+ "(at your option) any later version.\n" +
+ "\n" +
+ "This program is distributed in the hope that it will be useful,\n" +
+ "but WITHOUT ANY WARRANTY; without even the implied warranty of\n" +
+ "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n" +
+ "GNU General Public License for more details.\n";
+ }
+}
--- /dev/null
+++ b/src/main/java/com/StreamPi/Client/Main.java
@@ -0,0 +1,26 @@
+package com.StreamPi.Client;
+
+import com.StreamPi.Client.Controller.Controller;
+import javafx.application.Application;
+import javafx.scene.Scene;
+import javafx.stage.Stage;
+
+public class Main extends Application {
+
+
+ @Override
+ public void start(Stage stage)
+ {
+ Controller d = new Controller(); //Starts new dash instance
+
+ Scene s = new Scene(d); //Starts new scene instance from dash
+ stage.setScene(s); //Init Scene
+ d.init();
+ stage.show();
+ }
+
+
+ public static void main(String[] args) {
+ launch(args);
+ }
+}
--- /dev/null
+++ b/src/main/java/com/StreamPi/Client/Profile/ClientProfile.java
@@ -0,0 +1,682 @@
+package com.StreamPi.Client.Profile;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.file.Files;
+import java.nio.file.NoSuchFileException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.logging.Logger;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.transform.Result;
+import javax.xml.transform.Source;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+
+import com.StreamPi.ActionAPI.Action.Action;
+import com.StreamPi.ActionAPI.Action.ActionType;
+import com.StreamPi.ActionAPI.Action.DisplayTextAlignment;
+import com.StreamPi.ActionAPI.Action.Location;
+import com.StreamPi.ActionAPI.ActionProperty.ClientProperties;
+import com.StreamPi.ActionAPI.ActionProperty.Property.Property;
+import com.StreamPi.ActionAPI.ActionProperty.Property.Type;
+import com.StreamPi.Client.Info.ClientInfo;
+import com.StreamPi.Util.Exception.MinorException;
+import com.StreamPi.Util.Version.Version;
+import com.StreamPi.Util.XMLConfigHelper.XMLConfigHelper;
+
+import org.w3c.dom.Attr;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+public class ClientProfile implements Cloneable{
+ private String name, ID;
+
+ private int rows, cols, actionSize, actionGap;
+
+
+ private HashMap<String, Action> actions;
+ private String iconsPath;
+
+ private File file;
+
+ private Logger logger;
+ private Document document;
+
+ public ClientProfile(File file, String iconsPath) throws MinorException
+ {
+ this.file = file;
+ this.iconsPath = iconsPath;
+
+ actions = new HashMap<>();
+
+ logger = Logger.getLogger(ClientProfile.class.getName());
+
+ if(!file.exists() && !file.isFile())
+ createConfigFile(file);
+
+ try
+ {
+ DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
+ DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
+ document = docBuilder.parse(file);
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ throw new MinorException("Profile", "Unable to read profile config file.");
+ }
+
+
+ setID(file.getName().replace(".xml", ""));
+ load();
+ }
+
+
+ private Element getProfileElement()
+ {
+ return (Element) document.getElementsByTagName("profile").item(0);
+ }
+
+ private Element getActionsElement()
+ {
+ return (Element) document.getElementsByTagName("actions").item(0);
+ }
+
+ public void load() throws MinorException
+ {
+ try
+ {
+ actions.clear();
+
+ logger.info("Loading profile "+getID()+" ...");
+
+ String name = XMLConfigHelper.getStringProperty(getProfileElement(), "name");
+ int rows = XMLConfigHelper.getIntProperty(getProfileElement(), "rows");
+ int cols = XMLConfigHelper.getIntProperty(getProfileElement(), "cols");
+ int actionSize = XMLConfigHelper.getIntProperty(getProfileElement(), "action-size");
+ int actionGap = XMLConfigHelper.getIntProperty(getProfileElement(), "action-gap");
+
+ setName(name);
+ setRows(rows);
+ setCols(cols);
+ setActionSize(actionSize);
+ setActionGap(actionGap);
+
+
+ //Load Actions
+
+ NodeList actionsNodesList = getActionsElement().getChildNodes();
+
+ int actionsSize = actionsNodesList.getLength();
+
+ logger.info("Actions Size : "+actionsSize);
+
+ for(int item = 0; item<actionsSize; item++)
+ {
+ Node eachActionNode = actionsNodesList.item(item);
+
+ if(eachActionNode.getNodeType() != Node.ELEMENT_NODE)
+ continue;
+
+ Element eachActionElement = (Element) eachActionNode;
+
+ if(!eachActionElement.getNodeName().equals("action"))
+ continue;
+
+
+
+ String id = XMLConfigHelper.getStringProperty(eachActionElement, "id");
+ String parent = XMLConfigHelper.getStringProperty(eachActionElement, "parent");
+
+ logger.info("Loading action "+id+" ...");
+
+ ActionType actionType = ActionType.valueOf(XMLConfigHelper.getStringProperty(eachActionElement, "action-type"));
+
+ Action action = new Action(id, actionType);
+ action.setParent(parent);
+
+ ClientProperties properties = new ClientProperties();
+
+ if(actionType == ActionType.FOLDER)
+ properties.setDuplicatePropertyAllowed(true);
+
+
+ if(actionType == ActionType.NORMAL)
+ {
+ action.setVersion(new Version(XMLConfigHelper.getStringProperty(eachActionElement, "version")));
+ action.setModuleName(XMLConfigHelper.getStringProperty(eachActionElement, "module-name"));
+ }
+
+ Node propertiesNode = eachActionElement.getElementsByTagName("properties").item(0);
+
+ NodeList propertiesNodesList = propertiesNode.getChildNodes();
+
+ int propertiesSize = propertiesNodesList.getLength();
+
+ for(int propItem = 0; propItem < propertiesSize; propItem++)
+ {
+ Node eachPropertyNode = propertiesNodesList.item(propItem);
+
+ if(eachPropertyNode.getNodeType() != Node.ELEMENT_NODE)
+ continue;
+
+ Element eachPropertyElement = (Element) eachPropertyNode;
+
+ if(eachPropertyElement.getNodeName() != "property")
+ continue;
+
+
+
+ String propertyName = XMLConfigHelper.getStringProperty(eachPropertyElement, "name");
+ String propertyValue = XMLConfigHelper.getStringProperty(eachPropertyElement, "value");
+
+ logger.info("Property Name : "+propertyName);
+ logger.info("Property Value : "+propertyValue);
+
+ Property p = new Property(propertyName, Type.STRING);
+ p.setRawValue(propertyValue);
+
+ properties.addProperty(p);
+ }
+
+ action.setClientProperties(properties);
+
+
+
+ Element displayElement = (Element) eachActionElement.getElementsByTagName("display").item(0);
+
+ //display
+
+ //location
+
+ try
+ {
+ Element locationElement = (Element) displayElement.getElementsByTagName("location").item(0);
+ int row = XMLConfigHelper.getIntProperty(locationElement, "row");
+ int col = XMLConfigHelper.getIntProperty(locationElement, "col");
+ action.setLocation(new Location(row, col));
+ }
+ catch (Exception e)
+ {
+ logger.info("Action has no location, most probably a combine action child");
+ }
+
+ //background
+
+ Element backgroundElement = (Element) displayElement.getElementsByTagName("background").item(0);
+
+ action.setBgColourHex(XMLConfigHelper.getStringProperty(backgroundElement, "colour-hex"));
+
+ Element iconElement = (Element) backgroundElement.getElementsByTagName("icon").item(0);
+
+ boolean showIcon = XMLConfigHelper.getBooleanProperty(iconElement, "show");
+ boolean hasIcon = XMLConfigHelper.getBooleanProperty(iconElement, "has");
+
+ Element textElement = (Element) displayElement.getElementsByTagName("text").item(0);
+
+ boolean showText = XMLConfigHelper.getBooleanProperty(textElement, "show");
+ String displayTextFontColour = XMLConfigHelper.getStringProperty(textElement, "colour-hex");
+ DisplayTextAlignment displayTextAlignment = DisplayTextAlignment.valueOf(XMLConfigHelper.getStringProperty(textElement, "alignment"));
+
+
+ action.setDisplayTextAlignment(displayTextAlignment);
+ action.setShowIcon(showIcon);
+ action.setHasIcon(hasIcon);
+ action.setShowDisplayText(showText);
+
+ action.setDisplayTextFontColourHex(displayTextFontColour);
+
+
+ String displayText = XMLConfigHelper.getStringProperty(textElement, "display-text");
+
+ action.setDisplayText(displayText);
+
+
+
+ if(hasIcon)
+ {
+ File f = new File(iconsPath+"/"+id);
+
+ try
+ {
+ byte[] iconFileByteArray = Files.readAllBytes(f.toPath());
+ action.setIcon(iconFileByteArray);
+ }
+ catch(NoSuchFileException e)
+ {
+ action.setIcon(null);
+ action.setHasIcon(false);
+ action.setShowIcon(false);
+ saveAction(action);
+ }
+ }
+
+
+
+ addAction(action);
+
+
+ logger.info("... Done!");
+ }
+
+ logger.info("Loaded profile "+getID()+" ("+getName()+") !");
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+
+ throw new MinorException("Profile", "Profile is corrupt.");
+ }
+
+ }
+
+ public void addAction(Action action) throws CloneNotSupportedException {
+ actions.put(action.getID(), (Action) action.clone());
+ }
+
+
+
+
+ private void createConfigFile(File file) throws MinorException
+ {
+ try
+ {
+ file.createNewFile();
+
+ DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
+ DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
+ Document newDocument = dBuilder.newDocument();
+
+
+ Element rootElement = newDocument.createElement("config");
+ newDocument.appendChild(rootElement);
+
+ Element profileElement = newDocument.createElement("profile");
+ rootElement.appendChild(profileElement);
+
+ Element actionsElement = newDocument.createElement("actions");
+ rootElement.appendChild(actionsElement);
+
+ Element nameElement = newDocument.createElement("name");
+ nameElement.setTextContent("Untitled Profile");
+ profileElement.appendChild(nameElement);
+
+ Element rowsElement = newDocument.createElement("rows");
+ rowsElement.setTextContent("3");
+ profileElement.appendChild(rowsElement);
+
+ Element colsElement = newDocument.createElement("cols");
+ colsElement.setTextContent("3");
+ profileElement.appendChild(colsElement);
+
+ Element actionSizeElement = newDocument.createElement("action-size");
+ actionSizeElement.setTextContent("100");
+ profileElement.appendChild(actionSizeElement);
+
+ Element actionGapElement = newDocument.createElement("action-gap");
+ actionGapElement.setTextContent("5");
+ profileElement.appendChild(actionGapElement);
+
+
+
+
+ TransformerFactory transformerFactory = TransformerFactory.newInstance();
+ Transformer transformer = transformerFactory.newTransformer();
+ DOMSource source = new DOMSource(newDocument);
+ StreamResult result = new StreamResult(file);
+ transformer.transform(source, result);
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ throw new MinorException(e.getMessage());
+ }
+ }
+
+
+
+ public void deleteProfile()
+ {
+ file.delete();
+ }
+
+
+ public void saveAction(Action action) throws Exception {
+
+ Element newActionElement = document.createElement("action");
+ getActionsElement().appendChild(newActionElement);
+
+ Element idElement = document.createElement("id");
+ idElement.setTextContent(action.getID());
+ newActionElement.appendChild(idElement);
+
+ Element parentElement = document.createElement("parent");
+ parentElement.setTextContent(action.getParent());
+ newActionElement.appendChild(parentElement);
+
+ Element actionTypeElement = document.createElement("action-type");
+ actionTypeElement.setTextContent(action.getActionType()+"");
+ newActionElement.appendChild(actionTypeElement);
+
+ if(action.getActionType() == ActionType.NORMAL)
+ {
+ Element versionElement = document.createElement("version");
+ versionElement.setTextContent(action.getVersion().getText());
+ newActionElement.appendChild(versionElement);
+
+ System.out.println(action.getModuleName());
+
+ Element moduleNameElement = document.createElement("module-name");
+ moduleNameElement.setTextContent(action.getModuleName());
+ newActionElement.appendChild(moduleNameElement);
+ }
+
+ Element displayElement = document.createElement("display");
+ newActionElement.appendChild(displayElement);
+
+ Element backgroundElement = document.createElement("background");
+ displayElement.appendChild(backgroundElement);
+
+ Element colourHexElement = document.createElement("colour-hex");
+ colourHexElement.setTextContent(action.getBgColourHex());
+ backgroundElement.appendChild(colourHexElement);
+
+ Element iconElement = document.createElement("icon");
+
+ Element iconShowElement = document.createElement("show");
+ iconShowElement.setTextContent(action.isShowIcon()+"");
+ iconElement.appendChild(iconShowElement);
+
+ Element iconHasElement = document.createElement("has");
+ iconHasElement.setTextContent(action.isHasIcon()+"");
+ iconElement.appendChild(iconHasElement);
+
+ backgroundElement.appendChild(iconElement);
+
+ Element textElement = document.createElement("text");
+ displayElement.appendChild(textElement);
+
+ Element textTolourHexElement = document.createElement("colour-hex");
+ textTolourHexElement.setTextContent(action.getDisplayTextFontColourHex());
+ textElement.appendChild(textTolourHexElement);
+
+ Element textShowElement = document.createElement("show");
+ textShowElement.setTextContent(action.isShowDisplayText()+"");
+ textElement.appendChild(textShowElement);
+
+ Element textDisplayTextElement = document.createElement("display-text");
+ textDisplayTextElement.setTextContent(action.getDisplayText());
+ textElement.appendChild(textDisplayTextElement);
+
+ Element textAlignmentElement = document.createElement("alignment");
+ textAlignmentElement.setTextContent(action.getDisplayTextAlignment()+"");
+ textElement.appendChild(textAlignmentElement);
+
+
+ Element locationElement = document.createElement("location");
+ displayElement.appendChild(locationElement);
+
+ Element colElement = document.createElement("col");
+ colElement.setTextContent(action.getLocation().getCol()+"");
+ locationElement.appendChild(colElement);
+
+ Element rowElement = document.createElement("row");
+ rowElement.setTextContent(action.getLocation().getRow()+"");
+ locationElement.appendChild(rowElement);
+
+
+ Element propertiesElement = document.createElement("properties");
+ newActionElement.appendChild(propertiesElement);
+
+ for(String key : action.getClientProperties().getNames())
+ {
+ for(Property eachProperty : action.getClientProperties().getMultipleProperties(key))
+ {
+ Element propertyElement = document.createElement("property");
+ propertiesElement.appendChild(propertyElement);
+
+ Element nameElement = document.createElement("name");
+ nameElement.setTextContent(eachProperty.getName());
+ propertyElement.appendChild(nameElement);
+
+ Element valueElement = document.createElement("value");
+ valueElement.setTextContent(eachProperty.getRawValue());
+ propertyElement.appendChild(valueElement);
+ }
+ }
+
+
+ save();
+ }
+
+ private int getActionIndexInConfig(String actionID)
+ {
+ NodeList actionsList = getActionsElement().getChildNodes();
+
+ int actionsSize = actionsList.getLength();
+
+ int index = 0;
+
+ for(int i = 0;i<actionsSize;i++)
+ {
+ Node eachActionNode = actionsList.item(index);
+
+ if(eachActionNode.getNodeType() != Node.ELEMENT_NODE)
+ continue;
+
+ if(!eachActionNode.getNodeName().equals("action"))
+ continue;
+
+ Element eachActionElement = (Element) eachActionNode;
+
+ Element idElement = (Element) eachActionElement.getElementsByTagName("id").item(0);
+
+
+ if(idElement.getTextContent().equals(actionID))
+ return index;
+
+
+ index++;
+ }
+
+
+ return -1;
+ }
+
+ public void saveActionIcon(String actionID, byte[] array){
+ int index = getActionIndexInConfig(actionID);
+
+ getActionFromID(actionID).setIcon(array);
+
+ File iconFile = new File(iconsPath+"/"+actionID);
+ if(iconFile.exists())
+ {
+ boolean result = iconFile.delete();
+ System.out.println("result : "+result);
+ }
+
+ try
+ {
+ OutputStream outputStream = new FileOutputStream(iconFile);
+ outputStream.write(array);
+ outputStream.flush();
+ outputStream.close();
+
+ Element actionElement = (Element) getActionsElement().getElementsByTagName("action").item(index);
+
+ Element displayElement = (Element) actionElement.getElementsByTagName("display").item(0);
+ Element backgroundElement = (Element) displayElement.getElementsByTagName("background").item(0);
+ Element iconElement = (Element) backgroundElement.getElementsByTagName("icon").item(0);
+
+ Element hasElement = (Element) iconElement.getElementsByTagName("has").item(0);
+ hasElement.setTextContent("true");
+
+ save();
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ private void save() throws Exception
+ {
+ Transformer transformer = TransformerFactory.newInstance().newTransformer();
+ Result output = new StreamResult(file);
+ Source input = new DOMSource(document);
+
+ transformer.transform(input, output);
+ }
+
+ public void saveProfileDetails() throws Exception {
+ XMLConfigHelper.removeChilds(getProfileElement());
+
+ Element nameElement = document.createElement("name");
+ nameElement.setTextContent(getName());
+ getProfileElement().appendChild(nameElement);
+
+ Element rowsElement = document.createElement("rows");
+ rowsElement.setTextContent(getRows()+"");
+ getProfileElement().appendChild(rowsElement);
+
+ Element colsElement = document.createElement("cols");
+ colsElement.setTextContent(getCols()+"");
+ getProfileElement().appendChild(colsElement);
+
+ Element actionSizeElement = document.createElement("action-size");
+ actionSizeElement.setTextContent(getActionSize()+"");
+ getProfileElement().appendChild(actionSizeElement);
+
+ Element actionGapElement = document.createElement("action-gap");
+ actionGapElement.setTextContent(getActionGap()+"");
+ getProfileElement().appendChild(actionGapElement);
+
+ save();
+ }
+
+ public void saveActions() throws Exception
+ {
+ XMLConfigHelper.removeChilds(getActionsElement());
+ save();
+ for(Action action : getActions())
+ {
+ logger.info("ACTION ID :"+action.getID());
+ logger.info("Action ICON : "+action.isHasIcon());
+ saveAction(action);
+ }
+ }
+
+
+
+ public void removeAction(String ID) throws Exception {
+ int index = getActionIndexInConfig(ID);
+
+ if(index>-1)
+ {
+
+ Element actionElement = (Element) getActionsElement().getElementsByTagName("action").item(index);
+
+ Element displayElement = (Element) actionElement.getElementsByTagName("display").item(0);
+ Element backgroundElement = (Element) displayElement.getElementsByTagName("background").item(0);
+ Element iconElement = (Element) backgroundElement.getElementsByTagName("icon").item(0);
+
+ if(XMLConfigHelper.getBooleanProperty(iconElement, "has"))
+ {
+ File file = new File(ClientInfo.getInstance().getPrePath()+iconsPath+"/"+ID);
+
+ System.out.println(file.delete());
+ }
+ actions.remove(ID);
+ }
+
+ }
+
+ public ArrayList<Action> getActions()
+ {
+ ArrayList<Action> p = new ArrayList<>();
+ for(String profile : actions.keySet())
+ p.add(actions.get(profile));
+ return p;
+ }
+
+ public String getID()
+ {
+ return ID;
+ }
+
+ public String getName()
+ {
+ return name;
+ }
+
+ public int getRows()
+ {
+ return rows;
+ }
+
+ public int getCols()
+ {
+ return cols;
+ }
+
+ public int getActionSize()
+ {
+ return actionSize;
+ }
+
+ public Action getActionFromID(String ID)
+ {
+ return actions.getOrDefault(ID, null);
+ }
+
+ public int getActionGap()
+ {
+ return actionGap;
+ }
+
+ public void setRows(int rows)
+ {
+ this.rows = rows;
+ }
+
+ public void setCols(int cols)
+ {
+ this.cols = cols;
+ }
+
+ public void setID(String ID)
+ {
+ this.ID = ID;
+ }
+
+ public void setActionSize(int actionSize)
+ {
+ this.actionSize = actionSize;
+ }
+
+ public void setActionGap(int actionGap)
+ {
+ this.actionGap = actionGap;
+ }
+
+ public void setName(String name)
+ {
+ this.name = name;
+ }
+
+
+ public Object clone() throws CloneNotSupportedException
+ {
+ return super.clone();
+ }
+}
--- /dev/null
+++ b/src/main/java/com/StreamPi/Client/Profile/ClientProfiles.java
@@ -0,0 +1,121 @@
+package com.StreamPi.Client.Profile;
+
+import com.StreamPi.ActionAPI.Action.Action;
+import com.StreamPi.Client.IO.Config;
+import com.StreamPi.Util.Exception.MinorException;
+import com.StreamPi.Util.Exception.SevereException;
+
+import java.io.File;
+import java.lang.reflect.Array;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.logging.Logger;
+
+public class ClientProfiles {
+
+
+ private File profilesFolder;
+ private String defaultProfileID;
+
+ private Logger logger;
+
+ public ClientProfiles(File profilesFolder, String defaultProfileID) throws SevereException
+ {
+ logger = Logger.getLogger(ClientProfiles.class.getName());
+
+ this.defaultProfileID = defaultProfileID;
+ this.profilesFolder = profilesFolder;
+ clientProfiles = new HashMap<>();
+ loadingErrors = new ArrayList<>();
+
+ loadProfiles();
+ }
+
+ public void addProfile(ClientProfile clientProfile) throws CloneNotSupportedException {
+ clientProfiles.put(clientProfile.getID(), (ClientProfile) clientProfile.clone());
+ }
+
+ public void deleteProfile(ClientProfile clientProfile)
+ {
+ clientProfiles.remove(clientProfile.getID());
+
+ clientProfile.deleteProfile();
+ }
+
+ private ArrayList<MinorException> loadingErrors;
+ private HashMap<String, ClientProfile> clientProfiles;
+
+ public void loadProfiles() throws SevereException
+ {
+ logger.info("Loading profiles ...");
+
+
+ String iconsPath = Config.getInstance().getIconsPath();
+
+ clientProfiles.clear();
+ loadingErrors.clear();
+
+ if(!profilesFolder.isDirectory())
+ {
+ throw new SevereException("Profiles","Profile folder doesn't exist! Cant continue.");
+ }
+
+
+ File[] profilesFiles = profilesFolder.listFiles();
+ if(profilesFiles == null)
+ {
+ throw new SevereException("Profiles","profilesFiles returned null. Cant continue!");
+ }
+
+ for(File eachProfileFile : profilesFiles)
+ {
+ try
+ {
+ ClientProfile profile = new ClientProfile(eachProfileFile, iconsPath);
+ try
+ {
+ addProfile(profile);
+ }
+ catch (CloneNotSupportedException e)
+ {
+ e.printStackTrace();
+ throw new SevereException(e.getMessage());
+ }
+ }
+ catch (MinorException e)
+ {
+ if(eachProfileFile.getName().replace(".xml","").equals(defaultProfileID))
+ {
+ throw new SevereException("Profiles", "Default Profile bad. Can't continue");
+ }
+
+ loadingErrors.add(new MinorException(e.getMessage()+" ("+eachProfileFile.getName().replace(".xml", "")));
+
+ e.printStackTrace();
+ }
+ }
+
+ logger.info("Loaded all profiles!");
+ }
+
+ public ArrayList<MinorException> getLoadingErrors()
+ {
+ return loadingErrors;
+ }
+
+ public ArrayList<ClientProfile> getClientProfiles()
+ {
+ ArrayList<ClientProfile> p = new ArrayList<>();
+ for(String profile : clientProfiles.keySet())
+ p.add(clientProfiles.get(profile));
+ return p;
+ }
+
+ public ClientProfile getProfileFromID(String profileID)
+ {
+ return clientProfiles.getOrDefault(profileID, null);
+ }
+
+
+
+}
--- /dev/null
+++ b/src/main/java/com/StreamPi/Client/Window/Base.java
@@ -0,0 +1,392 @@
+package com.StreamPi.Client.Window;
+
+import com.StreamPi.Client.Connection.ClientListener;
+import com.StreamPi.Client.IO.Config;
+import com.StreamPi.Client.Info.ClientInfo;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.logging.Logger;
+
+import com.StreamPi.Client.Main;
+import com.StreamPi.Client.Profile.ClientProfiles;
+import com.StreamPi.Client.Window.Dashboard.DashboardBase;
+import com.StreamPi.Client.Window.FirstTimeUse.FirstTimeUse;
+import com.StreamPi.Client.Window.Settings.SettingsBase;
+import com.StreamPi.ThemeAPI.Theme;
+import com.StreamPi.ThemeAPI.Themes;
+import com.StreamPi.Util.Alert.StreamPiAlert;
+import com.StreamPi.Util.ComboBox.StreamPiComboBox;
+import com.StreamPi.Util.Exception.MinorException;
+import com.StreamPi.Util.Exception.SevereException;
+import com.StreamPi.Util.IOHelper.IOHelper;
+import com.StreamPi.Util.LoggerHelper.StreamPiLogFallbackHandler;
+import com.StreamPi.Util.LoggerHelper.StreamPiLogFileHandler;
+import com.StreamPi.Util.Platform.Platform;
+import com.gluonhq.attach.lifecycle.LifecycleService;
+import com.gluonhq.attach.util.Services;
+
+import javafx.scene.CacheHint;
+import javafx.scene.Cursor;
+import javafx.scene.input.KeyCombination;
+import javafx.scene.layout.StackPane;
+import javafx.scene.text.Font;
+import javafx.stage.Stage;
+
+public abstract class Base extends StackPane implements ExceptionAndAlertHandler, ClientListener {
+
+ private Config config;
+
+ private ClientProfiles clientProfiles;
+
+ private ClientInfo clientInfo;
+
+ private Stage stage;
+
+ public Stage getStage()
+ {
+ return stage;
+ }
+
+ public Logger getLogger()
+ {
+ return logger;
+ }
+
+ private DashboardBase dashboardBase;
+ private SettingsBase settingsBase;
+
+ private FirstTimeUse firstTimeUse;
+
+ public FirstTimeUse getFirstTimeUse() {
+ return firstTimeUse;
+ }
+
+
+ private StackPane alertStackPane;
+
+ @Override
+ public ClientProfiles getClientProfiles() {
+ return clientProfiles;
+ }
+
+ public void setClientProfiles(ClientProfiles clientProfiles) {
+ this.clientProfiles = clientProfiles;
+ }
+
+ private Logger logger = null;
+ private StreamPiLogFileHandler logFileHandler = null;
+ private StreamPiLogFallbackHandler logFallbackHandler = null;
+
+ public void initLogger()
+ {
+ try
+ {
+ if(logger != null || logFileHandler != null)
+ return;
+
+ closeLogger();
+ logger = Logger.getLogger("");
+
+ if(new File(ClientInfo.getInstance().getPrePath()).getAbsoluteFile().getParentFile().canWrite())
+ {
+
+ String path = ClientInfo.getInstance().getPrePath()+"../streampi.log";
+
+ if(ClientInfo.getInstance().getPlatformType() == Platform.ANDROID)
+ path = ClientInfo.getInstance().getPrePath()+"streampi.log";
+
+ logFileHandler = new StreamPiLogFileHandler(path);
+ logger.addHandler(logFileHandler);
+ }
+ else
+ {
+ logFallbackHandler = new StreamPiLogFallbackHandler();
+ logger.addHandler(logFallbackHandler);
+ }
+
+ }
+ catch(Exception e)
+ {
+ e.printStackTrace();
+ //throw new SevereException("Cant get logger started!");
+ }
+ }
+
+ public void closeLogger()
+ {
+ if(logFileHandler != null)
+ logFileHandler.close();
+ else if(logFallbackHandler != null)
+ logFallbackHandler.close();
+ }
+
+ public void initBase() throws SevereException
+ {
+
+ stage = (Stage) getScene().getWindow();
+
+ initLogger();
+
+ clientInfo = ClientInfo.getInstance();
+ dashboardBase = new DashboardBase(this, this);
+ dashboardBase.setCache(true);
+ dashboardBase.setCacheHint(CacheHint.SPEED);
+ dashboardBase.prefWidthProperty().bind(widthProperty());
+ dashboardBase.prefHeightProperty().bind(heightProperty());
+
+ settingsBase = new SettingsBase(this, this);
+ settingsBase.setCache(true);
+ settingsBase.setCacheHint(CacheHint.SPEED);
+
+ alertStackPane = new StackPane();
+ alertStackPane.setVisible(false);
+
+ StreamPiAlert.setParent(alertStackPane);
+ StreamPiComboBox.setParent(alertStackPane);
+
+ firstTimeUse = new FirstTimeUse(this, this);
+
+ getChildren().clear();
+ getChildren().addAll(settingsBase, dashboardBase, alertStackPane);
+
+ requestFocus();
+
+ setStyle(null);
+ clearStylesheets();
+ applyDefaultStylesheet();
+
+ checkPrePathDirectory();
+
+ config = Config.getInstance();
+
+ if(config.isFirstTimeUse())
+ {
+ getChildren().add(firstTimeUse);
+ firstTimeUse.toFront();
+ }
+ else
+ {
+ dashboardBase.toFront();
+ }
+
+ initThemes();
+
+ if(clientInfo.getPlatformType()!= Platform.ANDROID && clientInfo.getPlatformType() != Platform.IOS)
+ {
+ stage.setWidth(config.getStartupWindowWidth());
+ stage.setHeight(config.getStartupWindowHeight());
+ setupFlags();
+ }
+ }
+
+ private void checkPrePathDirectory() throws SevereException
+ {
+ try
+ {
+ File filex = new File(ClientInfo.getInstance().getPrePath());
+
+ if(filex.getAbsoluteFile().getParentFile().canWrite())
+ {
+ if(!filex.exists())
+ {
+ filex.mkdirs();
+ IOHelper.unzip(Main.class.getResourceAsStream("Default.obj"), ClientInfo.getInstance().getPrePath());
+ }
+ }
+ else
+ {
+ throw new SevereException("No storage permission. Give it!");
+ }
+
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ throw new SevereException(e.getMessage());
+ }
+ }
+
+ public void setupFlags()
+ {
+ //Full Screen
+ if(getConfig().isFullscreen())
+ {
+ getStage().setFullScreenExitKeyCombination(KeyCombination.NO_MATCH);
+ getStage().setFullScreen(true);
+ }
+ else
+ {
+ getStage().setFullScreenExitKeyCombination(KeyCombination.keyCombination("Esc"));
+ getStage().setFullScreen(false);
+ }
+
+ //Cursor
+ if(getConfig().isShowCursor())
+ {
+ setCursor(Cursor.DEFAULT);
+ }
+ else
+ {
+ setCursor(Cursor.NONE);
+ }
+ }
+
+
+ public SettingsBase getSettingsPane() {
+ return settingsBase;
+ }
+
+ public DashboardBase getDashboardPane() {
+ return dashboardBase;
+ }
+
+ public void renderRootDefaultProfile() throws SevereException {
+ getDashboardPane().renderProfile(getClientProfiles().getProfileFromID(
+ Config.getInstance().getStartupProfileID()
+ ));
+ }
+
+
+
+ public void clearStylesheets()
+ {
+ getStylesheets().clear();
+ }
+
+ public void initThemes() throws SevereException
+ {
+ registerThemes();
+
+ applyDefaultTheme();
+ }
+
+
+
+ public void applyDefaultStylesheet()
+ {
+ Font.loadFont(Main.class.getResourceAsStream("Roboto.ttf"), 13);
+ getStylesheets().add(Main.class.getResource("style.css").toExternalForm());
+ }
+
+
+
+ public Config getConfig()
+ {
+ return config;
+ }
+
+ public ClientInfo getClientInfo()
+ {
+ return clientInfo;
+ }
+
+ private Theme currentTheme;
+
+ @Override
+ public Theme getCurrentTheme()
+ {
+ return currentTheme;
+ }
+
+
+ public void applyTheme(Theme t)
+ {
+ logger.info("Applying theme '"+t.getFullName()+"' ...");
+
+ if(t.getFonts() != null)
+ {
+ for(String fontFile : t.getFonts())
+ {
+ Font.loadFont(fontFile.replace("%20",""), 13);
+ }
+ }
+ currentTheme = t;
+ getStylesheets().addAll(t.getStylesheets());
+
+ logger.info("... Done!");
+ }
+
+ Themes themes;
+ public void registerThemes() throws SevereException
+ {
+ logger.info("Loading themes ...");
+ themes = new Themes(getConfig().getThemesPath(), getConfig().getCurrentThemeFullName(), clientInfo.getMinThemeSupportVersion());
+
+
+ if(themes.getErrors().size()>0)
+ {
+ StringBuilder themeErrors = new StringBuilder();
+
+ for(MinorException eachException : themes.getErrors())
+ {
+ themeErrors.append("\n * ").append(eachException.getShortMessage());
+ }
+
+ if(themes.getIsBadThemeTheCurrentOne())
+ {
+ themeErrors.append("\n\nReverted to default theme! (").append(getConfig().getDefaultCurrentThemeFullName()).append(")");
+
+ getConfig().setCurrentThemeFullName(getConfig().getDefaultCurrentThemeFullName());
+ getConfig().save();
+ }
+
+ handleMinorException(new MinorException("Theme Loading issues", themeErrors.toString()));
+ }
+
+ logger.info("... Done!");
+ }
+
+ @Override
+ public Themes getThemes() {
+ return themes;
+ }
+
+
+
+ public void applyDefaultTheme()
+ {
+ logger.info("Applying default theme ...");
+
+
+ boolean foundTheme = false;
+ for(Theme t: themes.getThemeList())
+ {
+ if(t.getFullName().equals(config.getCurrentThemeFullName()))
+ {
+ foundTheme = true;
+ applyTheme(t);
+ break;
+ }
+ }
+
+ if(foundTheme)
+ {
+ logger.info("... Done!");
+ }
+ else
+ {
+ logger.info("Theme not found. reverting to light theme ...");
+ try {
+ Config.getInstance().setCurrentThemeFullName("com.StreamPi.DefaultLight");
+ Config.getInstance().save();
+
+ applyDefaultTheme();
+ }
+ catch (SevereException e)
+ {
+ handleSevereException(e);
+ }
+ }
+
+
+ }
+
+ @Override
+ public String getDefaultThemeFullName()
+ {
+ return config.getCurrentThemeFullName();
+ }
+
+
+}
--- /dev/null
+++ b/src/main/java/com/StreamPi/Client/Window/Dashboard/ActionGridPane/ActionBox.java
@@ -0,0 +1,334 @@
+package com.StreamPi.Client.Window.Dashboard.ActionGridPane;
+
+import com.StreamPi.ActionAPI.Action.Action;
+import com.StreamPi.ActionAPI.Action.ActionType;
+import com.StreamPi.ActionAPI.Action.DisplayTextAlignment;
+import com.StreamPi.ActionAPI.Action.Location;
+import com.StreamPi.Client.Main;
+import com.StreamPi.Client.IO.Config;
+import com.StreamPi.Client.Window.ExceptionAndAlertHandler;
+import com.StreamPi.Util.Exception.MinorException;
+import javafx.animation.Interpolator;
+import javafx.animation.KeyFrame;
+import javafx.animation.KeyValue;
+import javafx.animation.Timeline;
+import javafx.geometry.Insets;
+import javafx.geometry.Pos;
+import javafx.scene.CacheHint;
+import javafx.scene.control.Label;
+import javafx.scene.image.Image;
+import javafx.scene.image.ImageView;
+import javafx.scene.input.ClipboardContent;
+import javafx.scene.input.Dragboard;
+import javafx.scene.input.MouseButton;
+import javafx.scene.input.TouchEvent;
+import javafx.scene.input.TransferMode;
+import javafx.scene.layout.Background;
+import javafx.scene.layout.BackgroundFill;
+import javafx.scene.layout.BackgroundImage;
+import javafx.scene.layout.BackgroundPosition;
+import javafx.scene.layout.BackgroundRepeat;
+import javafx.scene.layout.BackgroundSize;
+import javafx.scene.layout.CornerRadii;
+import javafx.scene.layout.StackPane;
+import javafx.scene.paint.Color;
+import javafx.scene.paint.ImagePattern;
+import javafx.scene.paint.Paint;
+import javafx.scene.shape.Rectangle;
+import javafx.scene.text.TextAlignment;
+import javafx.util.Duration;
+import org.kordamp.ikonli.javafx.FontIcon;
+
+import java.io.ByteArrayInputStream;
+import java.io.ObjectInputStream;
+import java.nio.ByteBuffer;
+
+public class ActionBox extends StackPane{
+
+ private Label displayTextLabel;
+
+ private int row;
+ private int col;
+
+ public void setRow(int row) {
+ this.row = row;
+ }
+
+ public void setCol(int col) {
+ this.col = col;
+ }
+
+ public int getRow() {
+ return row;
+ }
+
+ public int getCol() {
+ return col;
+ }
+
+
+
+ public void clear()
+ {
+ setAction(null);
+ setBackground(Background.EMPTY);
+ setStyle(null);
+ getChildren().clear();
+ }
+
+ private FontIcon statusIcon;
+
+ public void baseInit()
+ {
+
+ displayTextLabel = new Label();
+ displayTextLabel.setWrapText(true);
+ displayTextLabel.setTextAlignment(TextAlignment.CENTER);
+ displayTextLabel.getStyleClass().add("action_box_display_text_label");
+
+ displayTextLabel.prefHeightProperty().bind(heightProperty());
+ displayTextLabel.prefWidthProperty().bind(widthProperty());
+
+
+ statusIcon = new FontIcon();
+ statusIcon.setOpacity(0);
+ statusIcon.setCache(true);
+ statusIcon.setCacheHint(CacheHint.SPEED);
+ statusIcon.setIconSize(size - 30);
+
+
+ getChildren().addAll(statusIcon, displayTextLabel);
+
+ setMinSize(size, size);
+ setMaxSize(size, size);
+
+ getStyleClass().add("action_box");
+ getStyleClass().add("action_box_icon_not_present");
+
+ setOnMouseClicked(touchEvent -> actionClicked());
+
+ setOnMousePressed(TouchEvent -> {
+ if(action != null)
+ {
+ getStyleClass().add("action_box_onclick");
+ }
+ });
+ setOnMouseReleased(TouchEvent ->{
+ if(action != null)
+ {
+ getStyleClass().remove("action_box_onclick");
+ }
+ });
+
+ statusIconAnimation = new Timeline(
+ new KeyFrame(
+ Duration.millis(0.0D),
+ new KeyValue(statusIcon.opacityProperty(), 0.0D, Interpolator.EASE_IN)),
+ new KeyFrame(
+ Duration.millis(100.0D),
+ new KeyValue(statusIcon.opacityProperty(), 1.0D, Interpolator.EASE_IN)),
+ new KeyFrame(
+ Duration.millis(600.0D),
+ new KeyValue(statusIcon.opacityProperty(), 1.0D, Interpolator.EASE_OUT)),
+ new KeyFrame(
+ Duration.millis(700.0D),
+ new KeyValue(statusIcon.opacityProperty(), 0.0D, Interpolator.EASE_OUT))
+ );
+
+ statusIconAnimation.setOnFinished(event -> {
+ statusIcon.toBack();
+ });
+
+
+ }
+
+ public void actionClicked()
+ {
+ if(action!=null)
+ {
+ if(action.getActionType() == ActionType.FOLDER)
+ {
+ getActionGridPaneListener().renderFolder(action.getID());
+ }
+ else
+ {
+ if(action.getActionType() == ActionType.COMBINE)
+ {
+ getActionGridPaneListener().combineActionClicked(action.getID());
+ }
+ else if(action.getActionType() == ActionType.NORMAL)
+ {
+ getActionGridPaneListener().normalActionClicked(action.getID());
+ }
+ }
+ }
+ }
+
+ public FontIcon getStatusIcon() {
+ return statusIcon;
+ }
+
+ private Timeline statusIconAnimation;
+
+ public Timeline getStatusIconAnimation() {
+ return statusIconAnimation;
+ }
+
+ public ActionGridPaneListener getActionGridPaneListener() {
+ return actionGridPaneListener;
+ }
+
+ private int size;
+ private ActionGridPaneListener actionGridPaneListener;
+
+ public ActionBox(int size, ActionGridPaneListener actionGridPaneListener)
+ {
+ this.actionGridPaneListener = actionGridPaneListener;
+ this.size = size;
+ baseInit();
+ }
+
+ public static Action deserialize(ByteBuffer buffer) {
+ try {
+ ByteArrayInputStream is = new ByteArrayInputStream(buffer.array());
+ ObjectInputStream ois = new ObjectInputStream(is);
+ Action obj = (Action) ois.readObject();
+ return obj;
+ } catch (Exception e) {
+ e.printStackTrace();
+ throw new RuntimeException(e);
+ }
+ }
+
+ public void setIcon(byte[] iconByteArray)
+ {
+ if(iconByteArray == null)
+ {
+ getStyleClass().remove("action_box_icon_present");
+ getStyleClass().add("action_box_icon_not_present");
+ setBackground(Background.EMPTY);
+ }
+ else
+ {
+ getStyleClass().add("action_box_icon_present");
+ getStyleClass().remove("action_box_icon_not_present");
+
+
+ setBackground(
+ new Background(
+ new BackgroundImage(new Image(
+ new ByteArrayInputStream(iconByteArray), size, size, false, true
+ ), BackgroundRepeat.NO_REPEAT, BackgroundRepeat.NO_REPEAT, BackgroundPosition.CENTER,
+
+ new BackgroundSize(100, 100, true, true, true, false))
+ )
+ );
+ }
+ }
+
+ private Action action;
+
+ public Action getAction() {
+ return action;
+ }
+
+ private ExceptionAndAlertHandler exceptionAndAlertHandler;
+
+ private String parent;
+
+ public String getStreamPiParent() {
+ return parent;
+ }
+
+ public void setStreamPiParent(String parent) {
+ this.parent = parent;
+ }
+
+ public ActionBox(int size, Action action, ExceptionAndAlertHandler exceptionAndAlertHandler, ActionGridPaneListener actionGridPaneListener)
+ {
+ this.actionGridPaneListener = actionGridPaneListener;
+ this.exceptionAndAlertHandler = exceptionAndAlertHandler;
+ this.action = action;
+ this.size = size;
+
+
+
+ baseInit();
+
+ init();
+
+ }
+
+ public void setAction(Action action)
+ {
+ this.action = action;
+ }
+
+ public void init()
+ {
+
+ setDisplayTextFontColour(action.getDisplayTextFontColourHex());
+
+ if(action.isShowDisplayText())
+ setDisplayTextLabel(action.getDisplayText());
+ else
+ setDisplayTextLabel("");
+
+ setDisplayTextAlignment(action.getDisplayTextAlignment());
+ setBackgroundColour(action.getBgColourHex());
+
+ if(action.isHasIcon() && action.isShowIcon())
+ {
+ setIcon(action.getIconAsByteArray());
+ }
+ else
+ {
+ setIcon(null);
+ }
+
+
+ }
+
+ public void animateStatus()
+ {
+ statusIcon.toFront();
+ statusIconAnimation.play();
+ }
+
+ public void setDisplayTextLabel(String text)
+ {
+ displayTextLabel.setText(text);
+ }
+
+ public void setDisplayTextAlignment(DisplayTextAlignment displayTextAlignment)
+ {
+ if(displayTextAlignment == DisplayTextAlignment.CENTER)
+ displayTextLabel.setAlignment(Pos.CENTER);
+ else if (displayTextAlignment == DisplayTextAlignment.BOTTOM)
+ displayTextLabel.setAlignment(Pos.BOTTOM_CENTER);
+ else if (displayTextAlignment == DisplayTextAlignment.TOP)
+ displayTextLabel.setAlignment(Pos.TOP_CENTER);
+ }
+ public void setDisplayTextFontColour(String colour)
+ {
+ System.out.println("'"+colour+"'COLOR");
+ if(!colour.isEmpty())
+ {
+ System.out.println(
+ "putting ..." + Thread.currentThread().getName()
+ );
+
+
+ displayTextLabel.setStyle("-fx-text-fill : "+colour+";");
+ }
+
+ }
+
+
+ public void setBackgroundColour(String colour)
+ {
+ System.out.println("COLOr : "+colour);
+ if(!colour.isEmpty() && action.getIconAsByteArray() == null)
+ setStyle("-fx-background-color : "+colour);
+ }
+}
--- /dev/null
+++ b/src/main/java/com/StreamPi/Client/Window/Dashboard/ActionGridPane/ActionGridPane.java
@@ -0,0 +1,384 @@
+package com.StreamPi.Client.Window.Dashboard.ActionGridPane;
+
+import java.util.logging.Logger;
+
+import com.StreamPi.ActionAPI.Action.Action;
+import com.StreamPi.ActionAPI.Action.Location;
+import com.StreamPi.ActionAPI.OtherActions.FolderAction;
+import com.StreamPi.Client.Connection.ClientListener;
+import com.StreamPi.Client.IO.Config;
+import com.StreamPi.Client.Profile.ClientProfile;
+import com.StreamPi.Client.Window.ExceptionAndAlertHandler;
+import com.StreamPi.Util.Alert.StreamPiAlertType;
+import com.StreamPi.Util.Exception.MinorException;
+import com.StreamPi.Util.Exception.SevereException;
+import javafx.animation.KeyFrame;
+import javafx.animation.KeyValue;
+import javafx.animation.Timeline;
+import javafx.application.Platform;
+import javafx.concurrent.Task;
+import javafx.geometry.Insets;
+import javafx.geometry.Pos;
+import javafx.scene.CacheHint;
+import javafx.scene.Node;
+import javafx.scene.control.ScrollPane;
+import javafx.scene.image.Image;
+import javafx.scene.layout.GridPane;
+import javafx.scene.layout.Priority;
+import javafx.scene.layout.StackPane;
+import javafx.scene.layout.VBox;
+import javafx.scene.paint.Color;
+import javafx.scene.paint.Paint;
+import javafx.util.Duration;
+import org.kordamp.ikonli.javafx.FontIcon;
+
+public class ActionGridPane extends GridPane implements ActionGridPaneListener {
+
+ private ExceptionAndAlertHandler exceptionAndAlertHandler;
+
+ private ClientListener clientListener;
+
+ public ActionGridPane(ExceptionAndAlertHandler exceptionAndAlertHandler, ClientListener clientListener)
+ {
+ this.clientListener = clientListener;
+
+ logger = Logger.getLogger(ActionGridPane.class.getName());
+ this.exceptionAndAlertHandler = exceptionAndAlertHandler;
+ getStyleClass().add("action_grid_pane");
+
+ setPadding(new Insets(5.0));
+
+ setPrefSize(USE_COMPUTED_SIZE, USE_COMPUTED_SIZE);
+
+
+ setAlignment(Pos.CENTER);
+
+ VBox.setVgrow(this, Priority.ALWAYS);
+ }
+
+ private String currentParent;
+
+ public void setCurrentParent(String currentParent) {
+ this.currentParent = currentParent;
+ }
+
+ public ClientProfile getClientProfile() {
+ return clientProfile;
+ }
+
+ private int rows, cols;
+
+ private ClientProfile clientProfile;
+
+ public void setClientProfile(ClientProfile clientProfile)
+ {
+ this.clientProfile = clientProfile;
+
+ setCurrentParent("root");
+ setRows(clientProfile.getRows());
+ setCols(clientProfile.getCols());
+ }
+
+ public void actionFailed(String profileID, String actionID)
+ {
+ if(getClientProfile().getID().equals(profileID))
+ {
+ Action action = getClientProfile().getActionFromID(actionID);
+ if(action != null)
+ {
+ if(currentParent.equals(action.getParent()))
+ {
+ failShow(action);
+ }
+ else
+ {
+ if(action.getLocation().getCol() == -1)
+ {
+ failShow(getClientProfile().getActionFromID(action.getParent()));
+ }
+ }
+ }
+ }
+ }
+
+ public void failShow(Action action)
+ {
+ for(Node node : getChildren())
+ {
+ if(GridPane.getColumnIndex(node) == action.getLocation().getRow() &&
+ GridPane.getRowIndex(node) == action.getLocation().getCol())
+ {
+
+ ActionBox actionBox = (ActionBox) node;
+
+ actionBox.getStatusIcon().setIconLiteral("fas-exclamation-triangle");
+ actionBox.getStatusIcon().setIconColor(Color.RED);
+
+ actionBox.animateStatus();
+
+ break;
+ }
+ }
+ }
+
+
+ public String getCurrentParent() {
+ return currentParent;
+ }
+
+ public StackPane getFolderBackButton() throws SevereException
+ {
+ StackPane stackPane = new StackPane();
+ stackPane.getStyleClass().add("action_box");
+ stackPane.getStyleClass().add("action_box_valid");
+
+ stackPane.setPrefSize(
+ getClientProfile().getActionSize(),
+ getClientProfile().getActionSize()
+ );
+
+ FontIcon fontIcon = new FontIcon("fas-chevron-left");
+
+ fontIcon.setIconSize(getClientProfile().getActionSize() - 30);
+
+ stackPane.setAlignment(Pos.CENTER);
+ stackPane.getChildren().add(fontIcon);
+
+ stackPane.setOnMouseClicked(e->returnToPreviousParent());
+
+ return stackPane;
+ }
+
+ public void renderGrid() throws SevereException {
+ clear();
+
+ setHgap(getClientProfile().getActionGap());
+ setVgap(getClientProfile().getActionGap());
+
+ boolean isFolder = false;
+
+ if(!getCurrentParent().equals("root"))
+ {
+ isFolder = true;
+
+ add(getFolderBackButton(), 0,0);
+ }
+
+ for(int row = 0; row<rows; row++)
+ {
+ for(int col = 0; col<cols; col++)
+ {
+ if(row == 0 && col == 0 && isFolder)
+ continue;
+
+ addBlankActionBox(col, row);
+
+ }
+ }
+ }
+
+ public void renderActions()
+ {
+ StringBuilder errors = new StringBuilder();
+ for(Action eachAction : getClientProfile().getActions())
+ {
+ logger.info("Action ID : "+eachAction.getID()+"\nInvalid : "+eachAction.isInvalid());
+
+ try {
+ renderAction(eachAction);
+ }
+ catch (SevereException e)
+ {
+ exceptionAndAlertHandler.handleSevereException(e);
+ }
+ catch (MinorException e)
+ {
+ errors.append("*").append(e.getShortMessage()).append("\n");
+ }
+ }
+
+ if(!errors.toString().isEmpty())
+ {
+ exceptionAndAlertHandler.handleMinorException(new MinorException("Error while rendering following actions", errors.toString()));
+ }
+ }
+
+ public void clear()
+ {
+ getChildren().clear();
+ }
+
+ private Logger logger;
+
+
+ public void clearActionBox(int col, int row)
+ {
+ for(Node node : getChildren())
+ {
+ if(GridPane.getColumnIndex(node) == row &&
+ GridPane.getRowIndex(node) == col)
+ {
+ getChildren().remove(node);
+ break;
+ }
+ }
+ }
+
+ public ActionBox getActionBox(int col, int row)
+ {
+ for(Node node : getChildren())
+ {
+ if(GridPane.getColumnIndex(node) == row &&
+ GridPane.getRowIndex(node) == col)
+ {
+ return (ActionBox) node;
+ }
+ }
+
+ return null;
+ }
+
+ public void addBlankActionBox(int col, int row)
+ {
+ ActionBox actionBox = new ActionBox(getClientProfile().getActionSize(), this);
+
+ actionBox.setStreamPiParent(currentParent);
+ actionBox.setRow(row);
+ actionBox.setCol(col);
+
+ add(actionBox, row, col);
+ }
+
+ public void renderAction(Action action) throws SevereException, MinorException
+ {
+ if(!action.getParent().equals(currentParent))
+ {
+ logger.info("Skipping action "+action.getID()+", not current parent!");
+ return;
+ }
+
+ if(action.getLocation().getRow()==-1)
+ {
+ logger.info("Action has -1 rowIndex. Probably Combine Action. Skipping ...");
+ return;
+ }
+
+ if(action.getLocation().getRow() > rows || action.getLocation().getCol() > cols)
+ {
+ throw new MinorException("Action "+action.getDisplayText()+" ("+action.getID()+") falls outside bounds.\n" +
+ " Consider increasing rows/cols from client settings and relocating/deleting it.");
+ }
+
+
+ ActionBox actionBox = new ActionBox(getClientProfile().getActionSize(), action, exceptionAndAlertHandler, this);
+
+ Location location = action.getLocation();
+
+ actionBox.setStreamPiParent(currentParent);
+ actionBox.setRow(location.getRow());
+ actionBox.setCol(location.getCol());
+
+ clearActionBox(location.getCol(), location.getRow());
+
+ System.out.println(location.getCol()+","+location.getRow());
+ add(actionBox, location.getRow(), location.getCol());
+
+ }
+
+ public void setRows(int rows)
+ {
+ this.rows = rows;
+ }
+
+ public void setCols(int cols)
+ {
+ this.cols = cols;
+ }
+
+ public int getRows()
+ {
+ return rows;
+ }
+
+ public int getCols()
+ {
+ return cols;
+ }
+
+ private String previousParent;
+
+ public void setPreviousParent(String previousParent) {
+ this.previousParent = previousParent;
+ }
+
+ public String getPreviousParent() {
+ return previousParent;
+ }
+
+ @Override
+ public void renderFolder(String actionID) {
+ setCurrentParent(clientProfile.getActionFromID(actionID).getID());
+ setPreviousParent(clientProfile.getActionFromID(actionID).getParent());
+ try {
+ renderGrid();
+ renderActions();
+ } catch (SevereException e) {
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ public void normalActionClicked(String ID) {
+ if(clientListener.isConnected())
+ clientListener.onNormalActionClicked(getClientProfile().getID(), ID);
+ else
+ exceptionAndAlertHandler.onAlert("Not Connected", "Not Connected to any Server", StreamPiAlertType.ERROR);
+ }
+
+ @Override
+ public void combineActionClicked(String ID) {
+ if(clientListener.isConnected())
+ {
+ new Thread(new Task<Void>() {
+ @Override
+ protected Void call()
+ {
+ Action action = getClientProfile().getActionFromID(ID);
+
+ for(int i = 0;i<action.getClientProperties().get().size();i++)
+ {
+ try {
+ logger.info("Clicking "+i+", '"+action.getClientProperties().getSingleProperty(i+"").getRawValue()+"'");
+ normalActionClicked(action.getClientProperties().getSingleProperty(i+"").getRawValue());
+ } catch (MinorException e) {
+ e.printStackTrace();
+ exceptionAndAlertHandler.handleMinorException(e);
+ }
+ }
+
+ return null;
+ }
+ }).start();
+ }
+ }
+
+ public void returnToPreviousParent()
+ {
+ setCurrentParent(getPreviousParent());
+
+ if(!getPreviousParent().equals("root"))
+ {
+ System.out.println("parent : "+getPreviousParent());
+ setPreviousParent(getClientProfile().getActionFromID(
+ getPreviousParent()
+ ).getParent());
+ }
+
+ try {
+ renderGrid();
+ renderActions();
+ } catch (SevereException e) {
+ e.printStackTrace();
+ }
+ }
+}
--- /dev/null
+++ b/src/main/java/com/StreamPi/Client/Window/Dashboard/ActionGridPane/ActionGridPaneListener.java
@@ -0,0 +1,12 @@
+package com.StreamPi.Client.Window.Dashboard.ActionGridPane;
+
+import com.StreamPi.ActionAPI.Action.Action;
+import com.StreamPi.ActionAPI.OtherActions.FolderAction;
+
+public interface ActionGridPaneListener {
+
+ void renderFolder(String ID);
+
+ void normalActionClicked(String ID);
+ void combineActionClicked(String ID);
+}
--- /dev/null
+++ b/src/main/java/com/StreamPi/Client/Window/Dashboard/DashboardBase.java
@@ -0,0 +1,81 @@
+package com.StreamPi.Client.Window.Dashboard;
+
+import com.StreamPi.Client.Connection.ClientListener;
+import com.StreamPi.Client.IO.Config;
+import com.StreamPi.Client.Profile.ClientProfile;
+import com.StreamPi.Client.Window.ExceptionAndAlertHandler;
+import com.StreamPi.Client.Window.Dashboard.ActionGridPane.ActionGridPane;
+import com.StreamPi.Util.Exception.SevereException;
+import javafx.geometry.Insets;
+import javafx.geometry.Pos;
+import javafx.scene.CacheHint;
+import javafx.scene.control.Button;
+import javafx.scene.control.ScrollPane;
+import javafx.scene.layout.HBox;
+import javafx.scene.layout.VBox;
+import javafx.scene.paint.Paint;
+import org.kordamp.ikonli.javafx.FontIcon;
+
+public class DashboardBase extends VBox {
+ private ExceptionAndAlertHandler exceptionAndAlertHandler;
+
+ private ActionGridPane actionGridPane;
+ private Button settingsButton;
+
+ public DashboardBase(ExceptionAndAlertHandler exceptionAndAlertHandler, ClientListener clientListener)
+ {
+ this.exceptionAndAlertHandler = exceptionAndAlertHandler;
+
+ actionGridPane = new ActionGridPane(exceptionAndAlertHandler, clientListener);
+
+ FontIcon fontIcon = new FontIcon("fas-cog");
+ fontIcon.getStyleClass().addAll("dashboard_settings_button_icon");
+
+ settingsButton = new Button();
+ settingsButton.setGraphic(fontIcon);
+
+ HBox hBox = new HBox(settingsButton);
+ hBox.setPadding(new Insets(0,5,5,0));
+ hBox.setAlignment(Pos.CENTER_RIGHT);
+
+
+ getChildren().addAll(actionGridPane,hBox);
+
+ getStyleClass().add("dashboard");
+
+ setCache(true);
+ setCacheHint(CacheHint.SPEED);
+ }
+
+ public void renderProfile(ClientProfile clientProfile) throws SevereException
+ {
+ renderProfile(clientProfile, "root");
+ }
+
+ public void renderProfile(ClientProfile clientProfile, String currentParent) throws SevereException
+ {
+ actionGridPane.setClientProfile(clientProfile);
+ actionGridPane.setCurrentParent(currentParent);
+
+ actionGridPane.renderGrid();
+ actionGridPane.renderActions();
+ }
+
+ public void addBlankActionBox(int col, int row)
+ {
+ actionGridPane.addBlankActionBox(col, row);
+ }
+
+ public void clearActionBox(int col, int row)
+ {
+ actionGridPane.clearActionBox(col, row);
+ }
+
+ public ActionGridPane getActionGridPane() {
+ return actionGridPane;
+ }
+
+ public Button getSettingsButton() {
+ return settingsButton;
+ }
+}
--- /dev/null
+++ b/src/main/java/com/StreamPi/Client/Window/ExceptionAndAlertHandler.java
@@ -0,0 +1,11 @@
+package com.StreamPi.Client.Window;
+
+import com.StreamPi.Util.Alert.StreamPiAlertType;
+import com.StreamPi.Util.Exception.MinorException;
+import com.StreamPi.Util.Exception.SevereException;
+
+public interface ExceptionAndAlertHandler {
+ void handleMinorException(MinorException e);
+ void handleSevereException(SevereException e);
+ void onAlert(String title, String body, StreamPiAlertType alertType);
+}
--- /dev/null
+++ b/src/main/java/com/StreamPi/Client/Window/FirstTimeUse/FinalConfigPane.java
@@ -0,0 +1,174 @@
+package com.StreamPi.Client.Window.FirstTimeUse;
+
+import com.StreamPi.Client.Connection.Client;
+import com.StreamPi.Client.Connection.ClientListener;
+import com.StreamPi.Client.IO.Config;
+import com.StreamPi.Client.Info.ClientInfo;
+import com.StreamPi.Client.Window.ExceptionAndAlertHandler;
+import com.StreamPi.Util.Alert.StreamPiAlert;
+import com.StreamPi.Util.Alert.StreamPiAlertType;
+import com.StreamPi.Util.Exception.SevereException;
+import com.StreamPi.Util.FormHelper.HBoxInputBox;
+import com.StreamPi.Util.FormHelper.SpaceFiller;
+import com.StreamPi.Util.FormHelper.SpaceFiller.FillerType;
+import com.StreamPi.Util.Platform.Platform;
+
+import javafx.geometry.Pos;
+import javafx.scene.control.Alert;
+import javafx.scene.control.Button;
+import javafx.scene.control.Label;
+import javafx.scene.control.ScrollPane;
+import javafx.scene.control.TextField;
+import javafx.scene.control.Alert.AlertType;
+import javafx.scene.layout.HBox;
+import javafx.scene.layout.Priority;
+import javafx.scene.layout.VBox;
+import javafx.stage.Stage;
+
+public class FinalConfigPane extends VBox
+{
+ private TextField clientNicknameTextField;
+ private TextField serverIPHostNameTextField;
+ private TextField serverPortTextField;
+ private TextField displayWidthTextField;
+ private TextField displayHeightTextField;
+
+ private ExceptionAndAlertHandler exceptionAndAlertHandler;
+ private ClientListener clientListener;
+
+ public FinalConfigPane(ExceptionAndAlertHandler exceptionAndAlertHandler, ClientListener clientListener)
+ {
+ this.exceptionAndAlertHandler = exceptionAndAlertHandler;
+ this.clientListener = clientListener;
+
+ getStyleClass().add("first_time_use_pane_final_config");
+
+ Label label = new Label("Thats it. Now just a little bit and then youre set!");
+ label.setWrapText(true);
+ VBox.setVgrow(label, Priority.ALWAYS);
+ label.getStyleClass().add("first_time_use_pane_final_config_label");
+
+ clientNicknameTextField = new TextField();
+ serverIPHostNameTextField = new TextField();
+ serverPortTextField = new TextField();
+
+ displayWidthTextField = new TextField();
+ displayHeightTextField = new TextField();
+
+ HBoxInputBox clientNickNameInputBox = new HBoxInputBox("Nickname", clientNicknameTextField);
+ HBoxInputBox serverIPHostNameInputBox = new HBoxInputBox("Server IP", serverIPHostNameTextField);
+ HBoxInputBox serverIPPortInputBox = new HBoxInputBox("Server Port", serverPortTextField);
+ HBoxInputBox displayWidthInputBox = new HBoxInputBox("Display Width", displayWidthTextField);
+ HBoxInputBox displayHeightInputBox = new HBoxInputBox("Display Height", displayHeightTextField);
+
+ if(ClientInfo.getInstance().getPlatformType() == Platform.ANDROID)
+ {
+ displayWidthInputBox.setVisible(false);
+ displayHeightInputBox.setVisible(false);
+ }
+
+ Button confirmButton = new Button("Confirm");
+ confirmButton.setOnAction(event -> onConfirmButtonClicked());
+ HBox bBar = new HBox(confirmButton);
+ bBar.setAlignment(Pos.CENTER_RIGHT);
+
+ VBox v = new VBox(clientNickNameInputBox, serverIPHostNameInputBox, serverIPPortInputBox,
+ displayWidthInputBox, displayHeightInputBox);
+ v.setSpacing(10.0);
+
+ ScrollPane scrollPane = new ScrollPane(v);
+ v.prefWidthProperty().bind(scrollPane.widthProperty().subtract(25));
+
+ getChildren().addAll(label, scrollPane,new SpaceFiller(FillerType.VBox), bBar);
+
+ setSpacing(10.0);
+
+ setVisible(false);
+ }
+
+ private void onConfirmButtonClicked()
+ {
+ StringBuilder errors = new StringBuilder();
+
+ if(clientNicknameTextField.getText().isBlank())
+ {
+ errors.append("* Nick name cannot be blank.\n");
+ }
+
+ if(serverIPHostNameTextField.getText().isBlank())
+ {
+ errors.append("* Server IP cannot be empty.\n");
+ }
+
+ int port = -1;
+ try
+ {
+ port = Integer.parseInt(serverPortTextField.getText());
+
+ if(port < 1024)
+ errors.append("* Server IP should be above 1024.\n");
+ }
+ catch (NumberFormatException exception)
+ {
+ errors.append("* Server IP should be a number.\n");
+ }
+
+ double width=-1,height=-1;
+
+ if(ClientInfo.getInstance().getPlatformType() != Platform.ANDROID)
+ {
+ try
+ {
+ width = Double.parseDouble(displayWidthTextField.getText());
+
+ if(width < 0)
+ errors.append("* Display Width should be above 0.\n");
+ }
+ catch (NumberFormatException exception)
+ {
+ errors.append("* Display Width should be a number.\n");
+ }
+
+ try
+ {
+ height = Double.parseDouble(displayHeightTextField.getText());
+
+ if(height < 0)
+ errors.append("* Display Height should be above 0.\n");
+ }
+ catch (NumberFormatException exception)
+ {
+ errors.append("* Display Height should be a number.\n");
+ }
+ }
+
+ if(errors.toString().isEmpty())
+ {
+ try
+ {
+ Config.getInstance().setNickName(clientNicknameTextField.getText());
+ Config.getInstance().setServerHostNameOrIP(serverIPHostNameTextField.getText());
+ Config.getInstance().setServerPort(port);
+ Config.getInstance().setFirstTimeUse(false);
+
+ if(ClientInfo.getInstance().getPlatformType() != Platform.ANDROID)
+ {
+ Config.getInstance().setStartupWindowSize(
+ width, height
+ );
+ }
+ Config.getInstance().save();
+
+ clientListener.init();
+ }
+ catch(SevereException e)
+ {
+ exceptionAndAlertHandler.handleSevereException(e);
+ }
+ }
+ else
+ {
+ new StreamPiAlert("Uh Oh", "Please rectify the following errors and try again:\n"+errors.toString(), StreamPiAlertType.ERROR).show();
+ }
+ }
+}
--- /dev/null
+++ b/src/main/java/com/StreamPi/Client/Window/FirstTimeUse/FirstTimeUse.java
@@ -0,0 +1,146 @@
+package com.StreamPi.Client.Window.FirstTimeUse;
+
+import com.StreamPi.Client.Main;
+import com.StreamPi.Client.Connection.ClientListener;
+import com.StreamPi.Client.Window.ExceptionAndAlertHandler;
+import com.StreamPi.Util.FormHelper.SpaceFiller;
+import com.StreamPi.Util.FormHelper.SpaceFiller.FillerType;
+
+import javafx.event.ActionEvent;
+import javafx.geometry.Insets;
+import javafx.geometry.Pos;
+import javafx.scene.control.Button;
+import javafx.scene.control.Label;
+import javafx.scene.layout.HBox;
+import javafx.scene.layout.Pane;
+import javafx.scene.layout.Priority;
+import javafx.scene.layout.StackPane;
+import javafx.scene.layout.VBox;
+import javafx.scene.text.Font;
+
+public class FirstTimeUse extends VBox{
+
+
+ public FirstTimeUse(ExceptionAndAlertHandler exceptionAndAlertHandler, ClientListener clientListener)
+ {
+ Font.loadFont(Main.class.getResourceAsStream("Roboto.ttf"), 13);
+ getStylesheets().add(Main.class.getResource("style.css").toExternalForm());
+
+ getStyleClass().add("first_time_use_pane");
+
+ setSpacing(10.0);
+ setPadding(new Insets(5));
+
+ headingLabel = new Label();
+ headingLabel.getStyleClass().add("first_time_use_pane_heading_label");
+
+ StackPane stackPane = new StackPane();
+ stackPane.getStyleClass().add("first_time_use_pane_stackpane");
+
+ VBox.setVgrow(stackPane, Priority.ALWAYS);
+
+ welcomePane = new WelcomePane();
+ licensePane = new LicensePane();
+ finalConfigPane = new FinalConfigPane(exceptionAndAlertHandler, clientListener);
+
+ stackPane.getChildren().addAll(
+ welcomePane,
+ licensePane,
+ finalConfigPane
+ );
+
+
+ nextButton = new Button("Next");
+ nextButton.setOnAction(event-> onNextButtonClicked());
+
+ previousButton = new Button("Previous");
+ previousButton.setOnAction(event-> onPreviousButtonClicked());
+
+
+ HBox buttonBar = new HBox(previousButton, new SpaceFiller(FillerType.HBox), nextButton);
+ buttonBar.setSpacing(10.0);
+
+ getChildren().addAll(headingLabel, stackPane, buttonBar);
+
+ setWindow(WindowName.WELCOME);
+ }
+
+ private Label headingLabel;
+ private Button nextButton;
+ private Button previousButton;
+ private WelcomePane welcomePane;
+ private LicensePane licensePane;
+ private FinalConfigPane finalConfigPane;
+
+ private WindowName windowName;
+
+ private void onNextButtonClicked()
+ {
+ if(windowName == WindowName.WELCOME)
+ {
+ setWindow(WindowName.LICENSE);
+ }
+ else if(windowName == WindowName.LICENSE)
+ {
+ setWindow(WindowName.FINAL);
+ }
+ }
+
+ private void onPreviousButtonClicked()
+ {
+ if(windowName == WindowName.FINAL)
+ {
+ setWindow(WindowName.LICENSE);
+ }
+ else if(windowName == WindowName.LICENSE)
+ {
+ setWindow(WindowName.WELCOME);
+ }
+ }
+
+ private void setWindow(WindowName windowName)
+ {
+ if (windowName == WindowName.WELCOME)
+ {
+ this.windowName = WindowName.WELCOME;
+ welcomePane.toFront();
+ welcomePane.setVisible(true);
+ licensePane.setVisible(false);
+ finalConfigPane.setVisible(false);
+
+ headingLabel.setText("Welcome!");
+
+ nextButton.setDisable(false);
+ previousButton.setDisable(true);
+ }
+ else if (windowName == WindowName.LICENSE)
+ {
+ this.windowName = WindowName.LICENSE;
+ licensePane.toFront();
+ welcomePane.setVisible(false);
+ licensePane.setVisible(true);
+ finalConfigPane.setVisible(false);
+
+ headingLabel.setText("License Agreement");
+
+ nextButton.setDisable(false);
+ previousButton.setDisable(false);
+ }
+ else if (windowName == WindowName.FINAL)
+ {
+ this.windowName = WindowName.FINAL;
+ finalConfigPane.toFront();
+ welcomePane.setVisible(false);
+ licensePane.setVisible(false);
+ finalConfigPane.setVisible(true);
+
+ headingLabel.setText("Finishing up ...");
+
+ nextButton.setDisable(true);
+ previousButton.setDisable(false);
+ }
+ }
+
+
+
+}
--- /dev/null
+++ b/src/main/java/com/StreamPi/Client/Window/FirstTimeUse/LicensePane.java
@@ -0,0 +1,28 @@
+package com.StreamPi.Client.Window.FirstTimeUse;
+
+import com.StreamPi.Client.Info.License;
+
+import javafx.scene.control.Label;
+import javafx.scene.control.TextArea;
+import javafx.scene.layout.Priority;
+import javafx.scene.layout.VBox;
+
+public class LicensePane extends VBox {
+ public LicensePane()
+ {
+ getStyleClass().add("first_time_use_pane_license");
+
+ Label label = new Label("By Clicking on 'Next', you agree with the license.");
+ label.prefWidthProperty().bind(widthProperty());
+ label.setWrapText(true);
+
+ TextArea licenseTextArea = new TextArea(License.getLicense());
+ licenseTextArea.setWrapText(false);
+ licenseTextArea.setEditable(false);
+
+ licenseTextArea.prefWidthProperty().bind(widthProperty());
+ VBox.setVgrow(licenseTextArea, Priority.ALWAYS);
+
+ getChildren().addAll(label, licenseTextArea);
+ }
+}
--- /dev/null
+++ b/src/main/java/com/StreamPi/Client/Window/FirstTimeUse/WelcomePane.java
@@ -0,0 +1,17 @@
+package com.StreamPi.Client.Window.FirstTimeUse;
+
+import javafx.scene.control.Label;
+import javafx.scene.layout.VBox;
+
+public class WelcomePane extends VBox{
+ public WelcomePane()
+ {
+ getStyleClass().add("first_time_use_pane_welcome");
+
+ Label label = new Label("Welcome to StreamPi!\nClick on 'Next' to continue with the setup.");
+
+ getChildren().add(label);
+
+ setVisible(false);
+ }
+}
--- /dev/null
+++ b/src/main/java/com/StreamPi/Client/Window/FirstTimeUse/WindowName.java
@@ -0,0 +1,5 @@
+package com.StreamPi.Client.Window.FirstTimeUse;
+
+public enum WindowName {
+ WELCOME, LICENSE, FINAL
+}
--- /dev/null
+++ b/src/main/java/com/StreamPi/Client/Window/Settings/SettingsBase.java
@@ -0,0 +1,503 @@
+package com.StreamPi.Client.Window.Settings;
+
+import java.io.File;
+
+import com.StreamPi.Client.Connection.Client;
+import com.StreamPi.Client.Connection.ClientListener;
+import com.StreamPi.Client.IO.Config;
+import com.StreamPi.Client.Info.ClientInfo;
+import com.StreamPi.Client.Profile.ClientProfile;
+import com.StreamPi.Client.Window.ExceptionAndAlertHandler;
+import com.StreamPi.ThemeAPI.Theme;
+import com.StreamPi.Util.Alert.StreamPiAlert;
+import com.StreamPi.Util.Alert.StreamPiAlertType;
+import com.StreamPi.Util.ComboBox.StreamPiComboBox;
+import com.StreamPi.Util.ComboBox.StreamPiComboBoxFactory;
+import com.StreamPi.Util.ComboBox.StreamPiComboBoxListener;
+import com.StreamPi.Util.Exception.MinorException;
+import com.StreamPi.Util.Exception.SevereException;
+import com.StreamPi.Util.FormHelper.HBoxInputBox;
+import com.StreamPi.Util.FormHelper.SpaceFiller;
+import com.StreamPi.Util.Platform.ReleaseStatus;
+import com.StreamPi.Util.StartAtBoot.SoftwareType;
+import com.StreamPi.Util.StartAtBoot.StartAtBoot;
+import com.gluonhq.attach.util.Services;
+
+import javafx.application.Platform;
+import javafx.collections.FXCollections;
+import javafx.geometry.Insets;
+import javafx.geometry.Pos;
+import javafx.scene.control.*;
+import javafx.scene.layout.HBox;
+import javafx.scene.layout.Priority;
+import javafx.scene.layout.VBox;
+import javafx.util.Callback;
+import org.w3c.dom.Text;
+
+public class SettingsBase extends VBox {
+ private TextField serverPortTextField;
+ private TextField serverHostNameOrIPTextField;
+
+ private StreamPiComboBox<ClientProfile> clientProfileComboBox;
+ private StreamPiComboBox<Theme> themeComboBox;
+
+ private TextField displayWidthTextField;
+ private TextField displayHeightTextField;
+
+ private TextField nickNameTextField;
+
+ private Button closeButton;
+ private Button saveButton;
+ private Button connectDisconnectButton;
+
+ private ToggleButton startOnBootToggleButton;
+
+ private ToggleButton showCursorToggleButton;
+ private ToggleButton fullScreenToggleButton;
+
+ private ClientListener clientListener;
+ private ExceptionAndAlertHandler exceptionAndAlertHandler;
+
+ private TextField themesPathTextField;
+ private TextField iconsPathTextField;
+ private TextField profilesPathTextField;
+
+ public SettingsBase(ExceptionAndAlertHandler exceptionAndAlertHandler, ClientListener clientListener) {
+
+ getStyleClass().add("settings_base");
+ this.clientListener = clientListener;
+ this.exceptionAndAlertHandler = exceptionAndAlertHandler;
+
+ serverPortTextField = new TextField();
+ serverHostNameOrIPTextField = new TextField();
+ nickNameTextField = new TextField();
+
+ clientProfileComboBox = new StreamPiComboBox<>();
+
+ clientProfileComboBox.setStreamPiComboBoxFactory(new StreamPiComboBoxFactory<ClientProfile>()
+ {
+ @Override
+ public String getOptionDisplayText(ClientProfile object)
+ {
+ return object.getName();
+ }
+ });
+
+ clientProfileComboBox.setStreamPiComboBoxListener(new StreamPiComboBoxListener<ClientProfile>(){
+ @Override
+ public void onNewItemSelected(ClientProfile selectedItem)
+ {
+ clientListener.renderProfile(selectedItem);
+ }
+ });
+
+
+ themeComboBox = new StreamPiComboBox<>();
+ themeComboBox.setStreamPiComboBoxFactory(new StreamPiComboBoxFactory<Theme>()
+ {
+ @Override
+ public String getOptionDisplayText(Theme object)
+ {
+ return object.getShortName();
+ }
+ });
+
+ displayHeightTextField = new TextField();
+ displayWidthTextField = new TextField();
+
+ themesPathTextField = new TextField();
+ iconsPathTextField = new TextField();
+ profilesPathTextField = new TextField();
+
+ startOnBootToggleButton = new ToggleButton("Start On Boot");
+ startOnBootToggleButton.managedProperty().bind(startOnBootToggleButton.visibleProperty());
+
+ showCursorToggleButton = new ToggleButton("Show Cursor");
+ showCursorToggleButton.managedProperty().bind(showCursorToggleButton.visibleProperty());
+
+ fullScreenToggleButton = new ToggleButton("Full Screen");
+ fullScreenToggleButton.managedProperty().bind(fullScreenToggleButton.visibleProperty());
+
+ int prefWidth = 200;
+
+ Label licenseLabel = new Label("This software is licensed to GNU GPLv3. Check StreamPi Server > Settings > About to see full License Statement.");
+ licenseLabel.setWrapText(true);
+
+ Label versionLabel = new Label(ClientInfo.getInstance().getReleaseStatus().getUIName() +" "+ClientInfo.getInstance().getVersion().getText());
+
+
+ HBoxInputBox themesPathInputBox = new HBoxInputBox("Themes Path", themesPathTextField, prefWidth);
+ themesPathInputBox.managedProperty().bind(themesPathInputBox.visibleProperty());
+
+
+ HBoxInputBox iconsPathInputBox = new HBoxInputBox("Icons Path", iconsPathTextField, prefWidth);
+ iconsPathInputBox.managedProperty().bind(iconsPathInputBox.visibleProperty());
+
+
+ HBoxInputBox profilesPathInputBox = new HBoxInputBox("Profiles Path", profilesPathTextField, prefWidth);
+ profilesPathInputBox.managedProperty().bind(profilesPathInputBox.visibleProperty());
+
+
+ HBoxInputBox screenHeightInputBox = new HBoxInputBox("Screen Height", displayHeightTextField, prefWidth);
+ screenHeightInputBox.managedProperty().bind(screenHeightInputBox.visibleProperty());
+
+
+ HBoxInputBox screenWidthInputBox = new HBoxInputBox("Screen Width", displayWidthTextField, prefWidth);
+ screenWidthInputBox.managedProperty().bind(screenWidthInputBox.visibleProperty());
+
+ if(ClientInfo.getInstance().getPlatformType() == com.StreamPi.Util.Platform.Platform.ANDROID)
+ {
+ themesPathInputBox.setVisible(false);
+ iconsPathInputBox.setVisible(false);
+ profilesPathInputBox.setVisible(false);
+
+ startOnBootToggleButton.setVisible(false);
+ showCursorToggleButton.setVisible(false);
+ fullScreenToggleButton.setVisible(false);
+
+ screenHeightInputBox.setVisible(false);
+ screenWidthInputBox.setVisible(false);
+ }
+
+
+ VBox vBox = new VBox(
+ new HBoxInputBox("Nick Name", nickNameTextField, prefWidth),
+ new HBoxInputBox("Host Name/IP", serverHostNameOrIPTextField, prefWidth),
+ new HBoxInputBox("Port", serverPortTextField, prefWidth),
+ new HBox(
+ new Label("Current Profile"),
+ new SpaceFiller(SpaceFiller.FillerType.HBox),
+ clientProfileComboBox
+ ),
+ new HBox(
+ new Label("Theme"),
+ new SpaceFiller(SpaceFiller.FillerType.HBox),
+ themeComboBox
+ ),
+ screenHeightInputBox,
+ screenWidthInputBox,
+ themesPathInputBox,
+ iconsPathInputBox,
+ profilesPathInputBox,
+ startOnBootToggleButton,
+ showCursorToggleButton,
+ fullScreenToggleButton,
+ licenseLabel,
+ versionLabel
+ );
+ vBox.getStyleClass().add("settings_base_vbox");
+
+ vBox.setSpacing(5.0);
+ vBox.setPadding(new Insets(5));
+
+ ScrollPane scrollPane = new ScrollPane();
+ scrollPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.AS_NEEDED);
+ VBox.setVgrow(scrollPane, Priority.ALWAYS);
+ scrollPane.getStyleClass().add("settings_base_scrollpane");
+ scrollPane.setContent(vBox);
+
+ vBox.setMinWidth(300);
+
+ vBox.prefWidthProperty().bind(scrollPane.widthProperty().subtract(25));
+
+
+ Label settingsLabel = new Label("Settings");
+ settingsLabel.setPadding(new Insets(5,0,0,5));
+ settingsLabel.getStyleClass().add("settings_heading");
+
+ saveButton = new Button("Save");
+ saveButton.setOnAction(event->onSaveButtonClicked());
+
+ closeButton = new Button("Close");
+
+ connectDisconnectButton = new Button("Connect");
+ connectDisconnectButton.setOnAction(event -> onConnectDisconnectButtonClicked());
+
+ Button exitButton = new Button("Exit");
+ exitButton.setOnAction(event -> onExitButtonClicked());
+
+ HBox buttonBar = new HBox(connectDisconnectButton, saveButton, exitButton, closeButton);
+ buttonBar.setPadding(new Insets(0,5,5,0));
+ buttonBar.setSpacing(5.0);
+ buttonBar.setAlignment(Pos.CENTER_RIGHT);
+
+ setSpacing(5.0);
+
+ getChildren().addAll(
+ settingsLabel,
+ scrollPane,
+ buttonBar
+ );
+ }
+
+ public void onExitButtonClicked()
+ {
+ Platform.exit();
+ clientListener.onCloseRequest();
+ }
+
+ public void setDisableStatus(boolean status)
+ {
+ saveButton.setDisable(status);
+ connectDisconnectButton.setDisable(status);
+ }
+
+ public void onConnectDisconnectButtonClicked()
+ {
+ try
+ {
+ if(clientListener.isConnected())
+ clientListener.disconnect("Client disconnected from settings");
+ else
+ clientListener.setupClientConnection();
+ }
+ catch (SevereException e)
+ {
+ e.printStackTrace();
+ exceptionAndAlertHandler.handleSevereException(e);
+ }
+ }
+
+ public void setConnectDisconnectButtonStatus()
+ {
+ Platform.runLater(()->{
+ setDisableStatus(false);
+
+ System.out.println("q24qwdqwd : "+clientListener.isConnected());
+
+ if(clientListener.isConnected())
+ connectDisconnectButton.setText("Disconnect");
+ else
+ connectDisconnectButton.setText("Connect");
+ });
+
+ }
+
+ public Button getCloseButton() {
+ return closeButton;
+ }
+
+ public void loadData() throws SevereException
+ {
+ nickNameTextField.setText(Config.getInstance().getClientNickName());
+
+ serverHostNameOrIPTextField.setText(Config.getInstance().getSavedServerHostNameOrIP());
+ serverPortTextField.setText(Config.getInstance().getSavedServerPort()+"");
+
+ clientProfileComboBox.setOptions(clientListener.getClientProfiles().getClientProfiles());
+
+ int ind = 0;
+ for(int i = 0;i<clientProfileComboBox.getOptions().size();i++)
+ {
+ if(clientProfileComboBox.getOptions().get(i).getID().equals(clientListener.getCurrentProfile().getID()))
+ {
+ ind = i;
+ break;
+ }
+ }
+
+ clientProfileComboBox.setCurrentSelectedItemIndex(ind);
+
+ themeComboBox.setOptions(clientListener.getThemes().getThemeList());
+
+ int ind2 = 0;
+ for(int i = 0;i<themeComboBox.getOptions().size();i++)
+ {
+ if(themeComboBox.getOptions().get(i).getFullName().equals(clientListener.getCurrentTheme().getFullName()))
+ {
+ ind2 = i;
+ break;
+ }
+ }
+
+ themeComboBox.setCurrentSelectedItemIndex(ind2);
+
+ displayWidthTextField.setText(Config.getInstance().getStartupWindowWidth()+"");
+ displayHeightTextField.setText(Config.getInstance().getStartupWindowHeight()+"");
+
+ themesPathTextField.setText(Config.getInstance().getThemesPath());
+ iconsPathTextField.setText(Config.getInstance().getIconsPath());
+ profilesPathTextField.setText(Config.getInstance().getProfilesPath());
+
+ startOnBootToggleButton.setSelected(Config.getInstance().isStartOnBoot());
+
+ fullScreenToggleButton.setSelected(Config.getInstance().isFullscreen());
+ showCursorToggleButton.setSelected(Config.getInstance().isShowCursor());
+ }
+
+ public void onSaveButtonClicked()
+ {
+ StringBuilder errors = new StringBuilder();
+
+ int port = -1;
+ try
+ {
+ port = Integer.parseInt(serverPortTextField.getText());
+
+ if(port < 1024)
+ errors.append("* Server IP should be above 1024.\n");
+ }
+ catch (NumberFormatException exception)
+ {
+ errors.append("* Server IP should be a number.\n");
+ }
+
+ double width = -1;
+ try
+ {
+ width = Double.parseDouble(displayWidthTextField.getText());
+
+ if(width < 0)
+ errors.append("* Display Width should be above 0.\n");
+ }
+ catch (NumberFormatException exception)
+ {
+ errors.append("* Display Width should be a number.\n");
+ }
+
+ double height = -1;
+ try
+ {
+ height = Double.parseDouble(displayHeightTextField.getText());
+
+ if(height < 0)
+ errors.append("* Display Height should be above 0.\n");
+ }
+ catch (NumberFormatException exception)
+ {
+ errors.append("* Display Height should be a number.\n");
+ }
+
+ if(serverHostNameOrIPTextField.getText().isBlank())
+ {
+ errors.append("* Server IP cannot be empty.\n");
+ }
+
+ if(nickNameTextField.getText().isBlank())
+ {
+ errors.append("* Nick name cannot be blank.\n");
+ }
+
+
+ if(!errors.toString().isEmpty())
+ {
+ exceptionAndAlertHandler.handleMinorException(new MinorException(
+ "You made mistakes",
+ "Please fix the errors and try again :\n"+errors.toString()
+ ));
+ return;
+ }
+
+
+
+ try {
+ boolean toBeReloaded = false;
+
+ boolean breakConnection = false;
+
+ if(!Config.getInstance().getCurrentThemeFullName().equals(themeComboBox.getCurrentSelectedItem().getFullName()))
+ toBeReloaded = true;
+
+ Config.getInstance().setCurrentThemeFullName(themeComboBox.getCurrentSelectedItem().getFullName());
+
+ if(width != Config.getInstance().getStartupWindowWidth() || height != Config.getInstance().getStartupWindowHeight())
+ toBeReloaded = true;
+
+ Config.getInstance().setStartupWindowSize(width, height);
+
+
+ if(!Config.getInstance().getClientNickName().equals(nickNameTextField.getText()))
+ breakConnection = true;
+
+ Config.getInstance().setNickName(nickNameTextField.getText());
+
+ if(port != Config.getInstance().getSavedServerPort() || !serverHostNameOrIPTextField.getText().equals(Config.getInstance().getSavedServerHostNameOrIP()))
+ breakConnection = true;
+
+ Config.getInstance().setServerPort(port);
+ Config.getInstance().setServerHostNameOrIP(serverHostNameOrIPTextField.getText());
+
+ boolean startOnBoot = startOnBootToggleButton.isSelected();
+
+ if(Config.getInstance().isStartOnBoot() != startOnBoot)
+ {
+ if(ClientInfo.getInstance().getRunnerFileName() == null)
+ {
+ new StreamPiAlert("Uh Oh", "No Runner File Name Specified as startup arguments. Cant set run at boot.", StreamPiAlertType.ERROR).show();
+ startOnBoot = false;
+ }
+ else
+ {
+ StartAtBoot startAtBoot = new StartAtBoot(SoftwareType.CLIENT, ClientInfo.getInstance().getPlatformType());
+ if(startOnBoot)
+ {
+ startAtBoot.create(new File(ClientInfo.getInstance().getRunnerFileName()));
+ }
+ else
+ {
+ boolean result = startAtBoot.delete();
+ if(!result)
+ new StreamPiAlert("Uh Oh!", "Unable to delete starter file", StreamPiAlertType.ERROR).show();
+ }
+ }
+ }
+
+ Config.getInstance().setStartOnBoot(startOnBoot);
+
+ if(Config.getInstance().isFullscreen() != fullScreenToggleButton.isSelected() ||
+ Config.getInstance().isShowCursor() != showCursorToggleButton.isSelected())
+ toBeReloaded = true;
+
+
+ Config.getInstance().setFullscreen(fullScreenToggleButton.isSelected());
+ Config.getInstance().setShowCursor(showCursorToggleButton.isSelected());
+
+
+
+ if(!Config.getInstance().getThemesPath().equals(themesPathTextField.getText()))
+ toBeReloaded = true;
+
+ Config.getInstance().setThemesPath(themesPathTextField.getText());
+
+
+ if(!Config.getInstance().getIconsPath().equals(iconsPathTextField.getText()))
+ toBeReloaded = true;
+
+ Config.getInstance().setIconsPath(iconsPathTextField.getText());
+
+ if(!Config.getInstance().getProfilesPath().equals(profilesPathTextField.getText()))
+ toBeReloaded = true;
+
+ Config.getInstance().setProfilesPath(profilesPathTextField.getText());
+
+
+ Config.getInstance().save();
+
+ loadData();
+
+
+ if(breakConnection)
+ {
+ if(clientListener.isConnected())
+ clientListener.disconnect("Client connection settings were changed.");
+ }
+
+ if(toBeReloaded)
+ {
+ clientListener.init();
+ clientListener.renderRootDefaultProfile();
+ }
+ }
+ catch (SevereException e)
+ {
+ e.printStackTrace();
+ exceptionAndAlertHandler.handleSevereException(e);
+ }
+ catch (MinorException e)
+ {
+ e.printStackTrace();
+ exceptionAndAlertHandler.handleMinorException(e);
+ }
+ }
+
+}
--- /dev/null
+++ b/src/main/java/module-info.java
@@ -0,0 +1,20 @@
+module com.StreamPi.Client {
+ requires javafx.base;
+ requires javafx.graphics;
+ requires javafx.controls;
+ requires org.kordamp.iconli.core;
+ requires org.kordamp.ikonli.javafx;
+
+ requires com.gluonhq.attach.lifecycle;
+ requires com.gluonhq.attach.util;
+
+ requires java.xml;
+
+ requires com.StreamPi.Util;
+ requires com.StreamPi.ActionAPI;
+ requires com.StreamPi.ThemeAPI;
+
+ requires org.kordamp.ikonli.fontawesome5;
+
+ exports com.StreamPi.Client;
+}
\ No newline at end of file
Binary files /dev/null and b/src/main/resources/com/StreamPi/Client/Default.obj differ
Binary files /dev/null and b/src/main/resources/com/StreamPi/Client/Roboto.ttf differ
--- /dev/null
+++ b/src/main/resources/com/StreamPi/Client/style.css
@@ -0,0 +1,206 @@
+.root {
+ -fx-font-family : 'Roboto';
+}
+
+
+.action_box
+{
+ -fx-border-width: 1px;
+ -fx-shape: "M100,100 h200 a20,20 0 0 1 20,20 v200 a20,20 0 0 1 -20,20 h-200 a20,20 0 0 1 -20,-20 v-200 a20,20 0 0 1 20,-20 z";
+ -fx-border-color : grey;
+}
+
+.action_box_icon_present
+{
+
+}
+
+.action_box_icon_not_present
+{
+
+}
+
+.action_box_onclick
+{
+
+}
+
+.action_box_not_onclick
+{
+
+}
+
+.action_box_display_text_label
+{
+ -fx-font-size : 16;
+}
+
+.action_box_icon
+{
+ -fx-arc-width:20px;
+ -fx-arc-height:20px;
+}
+
+.settings_heading
+{
+ -fx-font-size: 20;
+}
+
+
+.alert_header
+{
+ -fx-padding: 5;
+}
+
+.alert_pane
+{
+ -fx-background-color : white;
+ -fx-border-color : white;
+ -fx-border-width : 5;
+ -fx-border-radius : 5;
+ -fx-background-radius : 5;
+ -fx-max-height : 300;
+ -fx-max-width : 410;
+ -fx-effect: dropshadow( three-pass-box , rgba(0,0,0,0.6) , 5, 0.0 , 0.0 , 0 );
+}
+
+.alert_header_icon
+{
+ -fx-icon-size : 30;
+}
+
+.alert_error_icon
+{
+ -fx-icon-color: RED;
+}
+
+.alert_information_icon
+{
+ -fx-icon-color: BLUE;
+}
+
+.alert_warning_icon
+{
+ -fx-icon-color: #ffcc00;
+}
+
+.alert_content_pane
+{
+ -fx-background-color: white;
+ -fx-padding: 5;
+}
+
+.alert_pane_parent, .combobox_pane_parent
+{
+ -fx-background-color : rgba(0,0,0,0.5);
+}
+
+.alert_pane_header_text
+{
+ -fx-font-size: 18;
+}
+
+.alert_button_bar
+{
+ -fx-background-color: white;
+ -fx-alignment: CENTER_RIGHT;
+ -fx-spacing: 5;
+ -fx-padding: 5;
+}
+
+.alert_scroll_pane {
+ -fx-background: #FFFFFF;
+ -fx-border-color:#FFFFFF;
+ /*-fx-focus-color: #FFFFFF;
+ -fx-faint-focus-color:#FFFFFF;*/
+}
+
+
+.combo_box
+{
+ -fx-alignment:CENTER_LEFT;
+ -fx-pref-width:200;
+ -fx-padding: 5;
+ -fx-border-width : 1;
+ -fx-border-color : grey;
+ -fx-border-radius : 3;
+}
+
+.combo_box_popup
+{
+ -fx-border-width : 5;
+ -fx-border-radius : 5;
+ -fx-background-radius : 5;
+ -fx-max-height : 300;
+ -fx-max-width : 410;
+ -fx-background: #FFFFFF;
+ -fx-border-color:#FFFFFF;
+ -fx-effect: dropshadow( three-pass-box , rgba(0,0,0,0.6) , 5, 0.0 , 0.0 , 0 );
+}
+
+.combo_box_popup_vbox
+{
+ -fx-background-color: white;
+ -fx-padding: 5;
+ -fx-spacing:5;
+}
+
+.combo_box_drop_down_icon
+{
+ -fx-icon-code: fas-caret-down;
+ -fx-icon-size: 14;
+}
+
+.settings_base
+{
+ -fx-background-color:white;
+}
+
+.first_time_use_pane
+{
+ -fx-padding : 5;
+ -fx-background-color: white;
+}
+
+.first_time_use_pane_heading_label
+{
+ -fx-font-size: 20;
+}
+
+.first_time_use_pane_stackpane
+{
+
+}
+
+.first_time_use_pane_welcome
+{
+
+}
+
+.first_time_use_pane_license
+{
+ -fx-spacing : 10;
+}
+
+.first_time_use_pane_final_config
+{
+
+}
+
+.first_time_use_pane_final_config_label
+{
+
+}
+
+.scroll-pane{
+ -fx-background-color:transparent;
+}
+
+
+.scroll-pane .viewport {
+ -fx-background-color: transparent;
+}
+
+.dashboard{
+ -fx-background-color: white;
+}
\ No newline at end of file