commit e49b5793fc5809cc2d191316d1becb6b4ce1938b Author: Scott Duensing Date: Wed Nov 23 19:39:57 2022 -0600 Initial commit. diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..d2ac973 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +*.tgz filter=lfs diff=lfs merge=lfs -text +*.png filter=lfs diff=lfs merge=lfs -text diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e94df01 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +.idea/ +crapForLater/ +thirdparty/scintilla/ +thirdparty/lexilla/ +ui/generated/ +cmake-build-debug/ +crapForLater/ + +*~ diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..e2d9ece --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,79 @@ +# +# JoeyDev +# Copyright (C) 2018-2023 Scott Duensing +# +# This software is provided 'as-is', without any express or implied +# warranty. In no event will the authors be held liable for any damages +# arising from the use of this software. +# +# Permission is granted to anyone to use this software for any purpose, +# including commercial applications, and to alter it and redistribute it +# freely, subject to the following restrictions: +# +# 1. The origin of this software must not be misrepresented; you must not +# claim that you wrote the original software. If you use this software +# in a product, an acknowledgment in the product documentation would be +# appreciated but is not required. +# 2. Altered source versions must be plainly marked as such, and must not be +# misrepresented as being the original software. +# 3. This notice may not be removed or altered from any source distribution. +# + + +cmake_minimum_required(VERSION 3.22) +project(joeydev C) + +set(CMAKE_C_STANDARD 99) + +set(SOURCE_FILES + src/main.c + src/utils.c + src/joeydev.c + src/vector.c + src/array.c src/draw.c) + +add_executable(${CMAKE_PROJECT_NAME} ${SOURCE_FILES}) + +# Perform pre-build operations. +add_custom_target(GENERATE_UI_HEADERS + COMMAND ${CMAKE_SOURCE_DIR}/tools/prebuild.sh "${CMAKE_SOURCE_DIR}" +) +add_dependencies(${CMAKE_PROJECT_NAME} GENERATE_UI_HEADERS) + + +# Find dependencies. + +# GTK+ 3.0 is a PITA. This will be fixed in CMake 3.25 when FindGTK3 finally shows up. +execute_process(COMMAND pkg-config --cflags gtk+-3.0 + OUTPUT_VARIABLE OUT_TMP_VAR + OUTPUT_STRIP_TRAILING_WHITESPACE +) +string(STRIP ${OUT_TMP_VAR} GTK3_CFLAGS) +execute_process(COMMAND pkg-config --libs gtk+-3.0 + OUTPUT_VARIABLE OUT_TMP_VAR + OUTPUT_STRIP_TRAILING_WHITESPACE +) +string(STRIP ${OUT_TMP_VAR} GTK3_LIBRARIES) + +# Compile Options. +include_directories( + include + ui/generated + thirdparty/scintilla/include + thirdparty/lexilla/include +) + +add_definitions( + -Wall + -O0 + ${GTK3_CFLAGS} +) + +target_link_libraries(${CMAKE_PROJECT_NAME} + -rdynamic + ${CMAKE_SOURCE_DIR}/thirdparty/scintilla/bin/scintilla.a + ${CMAKE_SOURCE_DIR}/thirdparty/lexilla/bin/liblexilla.a + ${GTK3_LIBRARIES} + -lm + -lstdc++ +) diff --git a/agpl-3.0.txt b/agpl-3.0.txt new file mode 100644 index 0000000..be3f7b2 --- /dev/null +++ b/agpl-3.0.txt @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. diff --git a/buildDeps.sh b/buildDeps.sh new file mode 100644 index 0000000..2e661dd --- /dev/null +++ b/buildDeps.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +pushd thirdparty + # Scintilla + [[ -d scintilla ]] && rm -rf scintilla + tar xzf scintilla531.tgz + pushd scintilla/gtk + GTK3=1 make + popd + # Lexilla + [[ -d lexilla ]] && rm -rf lexilla + tar xzf lexilla520.tgz + pushd lexilla/src + make + popd +popd diff --git a/include/array.h b/include/array.h new file mode 100644 index 0000000..cff551f --- /dev/null +++ b/include/array.h @@ -0,0 +1,30 @@ +/* + * JoeyDev + * Copyright (C) 2018-2023 Scott Duensing + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. +*/ + + +#ifndef ARRAY_H +#define ARRAY_H + + +#include "../thirdparty/stb_ds.h" + + +#endif // ARRAY_H diff --git a/include/common.h b/include/common.h new file mode 100644 index 0000000..01a4913 --- /dev/null +++ b/include/common.h @@ -0,0 +1,50 @@ +/* + * JoeyDev + * Copyright (C) 2018-2023 Scott Duensing + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. +*/ + + +#ifndef COMMON_H +#define COMMON_H + + +#include +#include "array.h" + + +// Prevents "unused" warnings on event handlers and provides proper exports on various OSs. +#define EVENT __attribute__((unused)) G_MODULE_EXPORT + +// Allows passing just the name of an embedded resource instead of the name and length. +#define EMBEDDED(name) name, name ## _len + +// Allocation helpers. +#define NEW(t) (t*)calloc(1, sizeof(t)) // Add check for NULL and die here. +#define DEL(v) {if(v!=NULL) {free(v); v=NULL;}} + + +// Must be the first element of all userData structs. +typedef struct WindowDataS { + GtkWidget *window; + gboolean (*closeWindow)(GtkWidget* widget, gpointer data); + gboolean isDirty; +} WindowDataT; + + +#endif // COMMON_H diff --git a/include/draw.h b/include/draw.h new file mode 100644 index 0000000..4fb50c5 --- /dev/null +++ b/include/draw.h @@ -0,0 +1,78 @@ +/* + * JoeyDev + * Copyright (C) 2018-2023 Scott Duensing + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. +*/ + + +#ifndef DRAW_H +#define DRAW_H + + +#define jint16 short +#define jbool unsigned char +#define jbyte unsigned char + +#define jtrue 1 +#define jfalse 0 + + +typedef struct _jlStackS { + struct _jlStackS *previous; + void *data; +} jlStackT; + +typedef struct _jlColorS { + jbyte r; + jbyte g; + jbyte b; +} jlColorT; + +typedef struct _jlContextS { + jbyte *_pixels; + jbyte _jlDrawColor; + jbyte _jlDrawFillColor; + jlStackT *_jlFillStackTop; + jlColorT _jlPalette[16]; +} jlContextT; + + +jlContextT *jlContextNew(jbyte *pixels); +void jlContextDel(jlContextT **context); + +void jlDrawBox(jlContextT *c, jint16 x1, jint16 y1, jint16 x2, jint16 y2); +void jlDrawBoxFilled(jlContextT *c, jint16 x1, jint16 y1, jint16 x2, jint16 y2); +void jlDrawCircle(jlContextT *c, jint16 x, jint16 y, jint16 radius); +void jlDrawClear(jlContextT *c); +jbyte jlDrawColorGet(jlContextT *c); +void jlDrawColorSet(jlContextT *c, jbyte index); +void jlDrawEllipse(jlContextT *c, jint16 x1, jint16 y1, jint16 x2, jint16 y2); +void jlDrawFill(jlContextT *c, jint16 x, jint16 y); +void jlDrawFillTo(jlContextT *c, jint16 x, jint16 y, jbyte color); +void jlDrawLine(jlContextT *c, jint16 x1, jint16 y1, jint16 x2, jint16 y2); +jbyte jlDrawPixelGet(jlContextT *c, jint16 x, jint16 y); +void jlDrawPixelSet(jlContextT *c, jint16 x, jint16 y); +void jlPaletteDefault(jlContextT *c); +void jlPaletteSet(jlContextT *c, jbyte index, jbyte r, jbyte g, jbyte b); +#define jlUtilStackPop(c, stack) _jlUtilStackPop(c, (jlStackT **)&(stack)) // Syntatic Sugar +void *_jlUtilStackPop(jlContextT *c, jlStackT **stack); +#define jlUtilStackPush(c, stack, data) _jlUtilStackPush(c, (jlStackT **)&(stack), data, __LINE__, (char *)__FILE__) // Syntatic Sugar +void _jlUtilStackPush(jlContextT *c, jlStackT **stack, void *data, jint16 line, const char *file); + + +#endif // DRAW_H diff --git a/include/joeydev.h b/include/joeydev.h new file mode 100644 index 0000000..ed2e82f --- /dev/null +++ b/include/joeydev.h @@ -0,0 +1,30 @@ +/* + * JoeyDev + * Copyright (C) 2018-2023 Scott Duensing + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. +*/ + + +#ifndef JOEYDEV_H +#define JOEYDEV_H + + +void winJoeyDevCreate(void); + + +#endif // JOEYDEV_H diff --git a/include/stddclmr.h b/include/stddclmr.h new file mode 100644 index 0000000..8e6b3d9 --- /dev/null +++ b/include/stddclmr.h @@ -0,0 +1,95 @@ +#ifndef STDDCLMR_H +#define STDDCLMR_H + +/* +Action figures sold separately. Add toner. All models over 18 years of age. +All rights reserved. Allow four to six weeks for delivery. An equal +opportunity employer. Any resemblance to actual persons, living or dead, is +unintentional and purely coincidental. Apply only to affected area. Approved +for veterans. As seen on TV. At participating locations only. Avoid contact +with mucous membranes. Avoid contact with skin. Avoid extreme temperatures +and store in a cool dry place. Batteries not included. Be sure each item is +properly endorsed. Beware of dog. Booths for two or more. Breaking seal +constitutes acceptance of agreement. Call toll free number before digging. +Caveat emptor. Check here if tax deductible. Close cover before striking +Colors may fade. Contains a substantial amount of non-tobacco ingredients. +Contents may settle during shipment. Contestants have been briefed on some +questions before the show. Copyright 1995 Joker's Wild. Disclaimer does +not cover hurricane, lightning, tornado, tsunami, volcanic eruption, +earthquake, flood, and other Acts of God, misuse, neglect, unauthorized +repair, damage from improper installation, broken antenna or marred cabinet, +incorrect line voltage, missing or altered serial numbers, sonic boom +vibrations, electromagnetic radiation from nuclear blasts, customer +adjustments that are not covered in the joke list, and incidents owing to +airplane crash, ship sinking, motor vehicle accidents, leaky roof, broken +glass, falling rocks, mud slides, forest fire, flying projectiles, or +dropping the item. Do not bend, fold, mutilate, or spindle. Do not place +near flammable or magnetic source. Do not puncture, incinerate, or store +above 120 degrees Fahrenheit. Do not stamp. Use other side for additional +listings. Do not use while operating a motor vehicle or heavy equipment. Do +not write below this line. Documents are provided "as is" without any +warranties expressed or implied. Don't quote me on anything. Don't quote me +on that. Driver does not carry cash. Drop in any mailbox. Edited for +television. Employees and their families are not eligible. Falling rock. +First pull up, then pull down. Flames redirected to /dev/null. For a +limited time only. For external use only. For off-road use only. For office +use only. For recreational use only. Do not disturb. Freshest if eaten +before date on carton. Hand wash only, tumble dry on low heat. If a rash, +redness, irritation, or swelling develops, discontinue use. If condition +persists, consult your physician. If defects are discovered, do not attempt +to fix them yourself, but return to an authorized service center. If +ingested, do not induce vomiting, if symptoms persist, consult a doctor. +Keep away from open flames and avoid inhaling fumes. Keep away from +sunlight, pets, and small children. Keep cool; process promptly. Limit +one-per-family please. Limited time offer, call now to ensure prompt +delivery. List at least two alternate dates. List each check separately by +bank number. List was current at time of printing. Lost ticket pays maximum +rate. May be too intense for some viewers. Must be 18 to enter. No Canadian +coins. No alcohol, dogs or horses. No anchovies unless otherwise specified. +No animals were harmed in the production of these documents. No money down. +No other warranty expressed or implied. No passes accepted for this +engagement. No postage necessary if mailed in the United States. No +preservatives added. No purchase necessary. No salt, MSG, artificial color +or flavor added. No shoes, no shirt, no service, no kidding. No solicitors. +No substitutions allowed. No transfers issued until the bus comes to a +complete stop. No user-serviceable parts inside. Not affiliated with the +American Red Cross. Not liable for damages due to use or misuse. Not +recommended for children. Not responsible for direct, indirect, incidental +or consequential damages resulting from any defect, error or failure to +perform. Not the Beatles. Objects in mirror may be closer than they appear. +One size fits all. Many suitcases look alike. Other copyright laws for +specific entries apply wherever noted. Other restrictions may apply. Package +sold by weight, not volume. Parental advisory - explicit lyrics. Penalty for +private use. Place stamp here. Please remain seated until the ride has come +to a complete stop. Possible penalties for early withdrawal. Post office will +not deliver without postage. Postage will be paid by addressee. Prerecorded +for this time zone. Price does not include taxes. Processed at location +stamped in code at top of carton. Quantities are limited while supplies last. +Read at your own risk. Record additional transactions on back of previous +stub. Replace with same type. Reproduction strictly prohibited. Restaurant +package, not for resale. Return to sender, no forwarding order on file, +unable to forward. Safety goggles may be required during use. Sanitized for +your protection. Sealed for your protection, do not use if the safety seal is +broken. See label for sequence. Shading within a garment may occur. Sign here +without admitting guilt. Simulated picture. Slightly enlarged to show detail. +Slightly higher west of the Rockies. Slippery when wet. Smoking these may be +hazardous to your health. Some assembly required. Some equipment shown is +optional. Some of the trademarks mentioned in this product appear for +identification purposes only. Subject to FCC approval. Subject to change +without notice. Substantial penalty for early withdrawal. Text may contain +material some readers may find objectionable, parental guidance is advised. +Text used in these documents is made from 100% recycled electrons and magnetic +particles. These documents do not reflect the thoughts or opinions of either +myself, my company, my friends, or my rabbit. This is not an offer to sell +securities. This offer is void where prohibited, taxed, or otherwise +restricted. This product is meant for educational purposes only. Times +approximate. Unix is a registered trademark of AT&T. Use only as directed. Use +only in a well-ventilated are. User assumes full liabilities. Void where +prohibited. We have sent the forms which seem right for you. You must be +present to win. You need not be present to win. Your canceled check is your +receipt. Your mileage may vary. I didn't do it. You can't prove anything. + +This supersedes all previous notices. +*/ + +#endif // STDDCLMR_H diff --git a/include/utils.h b/include/utils.h new file mode 100644 index 0000000..894072c --- /dev/null +++ b/include/utils.h @@ -0,0 +1,41 @@ +/* + * JoeyDev + * Copyright (C) 2018-2023 Scott Duensing + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. +*/ + + +#ifndef UTILS_H +#define UTILS_H + + +#include "common.h" + + +GdkPixbuf *utilGetPixbufFromMemory(char *data, unsigned int len); +WindowDataT *utilGetWindowData(GtkWidget *window); +gboolean utilGetWidgetsFromMemory(char *name[], GtkWidget **widgets[], char *data, unsigned int len, gpointer userData); +GtkWidget *utilGetWindowFromMemory(char *name, char *data, unsigned int len, gpointer userData); +gboolean utilQuestionDialog(GtkWidget *parent, char *title, char *question); +void utilWindowRegister(gpointer windowData); +int utilWindowsCloseAll(void); +int utilWindowsOpen(void); +gboolean utilWindowUnRegister(gpointer windowData); + + +#endif // UTILS_H diff --git a/include/vector.h b/include/vector.h new file mode 100644 index 0000000..9163cd0 --- /dev/null +++ b/include/vector.h @@ -0,0 +1,30 @@ +/* + * JoeyDev + * Copyright (C) 2018-2023 Scott Duensing + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. +*/ + + +#ifndef VECTOR_H +#define VECTOR_H + + +void winVectorCreate(void); + + +#endif // VECTOR_H diff --git a/src/array.c b/src/array.c new file mode 100644 index 0000000..c3a5857 --- /dev/null +++ b/src/array.c @@ -0,0 +1,24 @@ +/* + * JoeyDev + * Copyright (C) 2018-2023 Scott Duensing + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. +*/ + + +#define STB_DS_IMPLEMENTATION +#include "../thirdparty/stb_ds.h" diff --git a/src/draw.c b/src/draw.c new file mode 100644 index 0000000..107efe8 --- /dev/null +++ b/src/draw.c @@ -0,0 +1,451 @@ +/* + * JoeyDev + * Copyright (C) 2018-2023 Scott Duensing + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. +*/ + + +#pragma clang diagnostic push +#pragma ide diagnostic ignored "cppcoreguidelines-narrowing-conversions" + + +#include "common.h" +#include "draw.h" + + +typedef struct { + jint16 StartX; + jint16 EndX; + jint16 Y; + signed char Dir; // 'signed' needs to be specified for ORCA/C + jbool ScanLeft; + jbool ScanRight; + jbool padding; // Aligns structure on x86 +} _jlScanDataT; + + +static void _jlDrawCircleClipped(jlContextT *c, jint16 x0, jint16 y0, jint16 radius); +static void _jlDrawFill(jlContextT *c, jint16 x, jint16 y, jbool how); +static void _jlDrawFillAddLine(jlContextT *c, jint16 startX, jint16 endX, jint16 y, jint16 ignoreStart, jint16 ignoreEnd, char dir, jbool isNextInDir, jbool how); +static _jlScanDataT *_jlDrawFillNewSegment(jlContextT *c, jint16 startX, jint16 endX, jint16 y, signed char dir, jbool scanLeft, jbool scanRight); + + +jlContextT *jlContextNew(jbyte *pixels) { + jlContextT *c = NEW(jlContextT); + + // "pixels" is ARGB32 @ 320x200 + + c->_jlDrawColor = 15; + c->_jlDrawFillColor = 0; + c->_jlFillStackTop = NULL; + c->_pixels = pixels; + + jlPaletteDefault(c); + + return c; +} + +void jlContextDel(jlContextT **context) { + jlContextT *c = (jlContextT *)*context; + DEL(c); +} + + +void jlDrawBox(jlContextT *c, jint16 x1, jint16 y1, jint16 x2, jint16 y2) { + jlDrawLine(c, x1, y1, x2, y1); + jlDrawLine(c, x2, y1, x2, y2); + jlDrawLine(c, x2, y2, x1, y2); + jlDrawLine(c, x1, y2, x1, y1); +} + + +void jlDrawBoxFilled(jlContextT *c, jint16 x1, jint16 y1, jint16 x2, jint16 y2) { + jint16 y; + for (y=y1; y<=y2; y++) { + jlDrawLine(c, x1, y, x2, y); + } +} + + +void _jlDrawCircleClipped(jlContextT *c, jint16 x0, jint16 y0, jint16 radius) { + jint16 x = radius-1; + jint16 y = 0; + jint16 dx = 1; + jint16 dy = 1; + jint16 err = dx - (jint16)(radius << 1); + + while (x >= y) { + if ((x0 + x < 320) && (y0 + y < 200)) jlDrawPixelSet(c, x0 + x, y0 + y); + if ((x0 + y < 320) && (y0 + x < 200)) jlDrawPixelSet(c, x0 + y, y0 + x); + if ((x0 - y < 320) && (y0 + x < 200)) jlDrawPixelSet(c, x0 - y, y0 + x); + if ((x0 - x < 320) && (y0 + y < 200)) jlDrawPixelSet(c, x0 - x, y0 + y); + if ((x0 - x < 320) && (y0 - y < 200)) jlDrawPixelSet(c, x0 - x, y0 - y); + if ((x0 - x < 320) && (y0 - x < 200)) jlDrawPixelSet(c, x0 - y, y0 - x); + if ((x0 + y < 320) && (y0 - x < 200)) jlDrawPixelSet(c, x0 + y, y0 - x); + if ((x0 + x < 320) && (y0 - y < 200)) jlDrawPixelSet(c, x0 + x, y0 - y); + + if (err <= 0) { + y++; + err += dy; + dy += 2; + } + + if (err > 0) { + x--; + dx += 2; + err += dx - (radius << 1); + } + } +} + + +void jlDrawCircle(jlContextT *c, jint16 x0, jint16 y0, jint16 radius) { + jint16 x = radius-1; + jint16 y = 0; + jint16 dx = 1; + jint16 dy = 1; + jint16 err = dx - (jint16)(radius << 1); + + // Is any of this going to be off the screen? + //***TODO*** All our drawing primitives should do this. + if ((x0 + radius > 319) || (x0 - radius < 0) || (y0 + radius > 199) || (y0 - radius < 0)) { + _jlDrawCircleClipped(c, x0, y0, radius); + return; + } + + while (x >= y) { + jlDrawPixelSet(c, x0 + x, y0 + y); + jlDrawPixelSet(c, x0 + y, y0 + x); + jlDrawPixelSet(c, x0 - y, y0 + x); + jlDrawPixelSet(c, x0 - x, y0 + y); + jlDrawPixelSet(c, x0 - x, y0 - y); + jlDrawPixelSet(c, x0 - y, y0 - x); + jlDrawPixelSet(c, x0 + y, y0 - x); + jlDrawPixelSet(c, x0 + x, y0 - y); + + if (err <= 0) { + y++; + err += dy; + dy += 2; + } else { + x--; + dx += 2; + err += dx - (radius << 1); + } + } +} + + +void jlDrawClear(jlContextT *c) { + jlDrawBoxFilled(c, 0, 0, 319, 199); +} + + +void jlDrawColorSet(jlContextT *c, jbyte index) { + c->_jlDrawColor = index; +} + + +// http://members.chello.at/~easyfilter/bresenham.html +void jlDrawEllipse(jlContextT *c, jint16 x0, jint16 y0, jint16 x1, jint16 y1) { + jint16 a = (jint16)abs(x1-x0), b = (jint16)abs(y1-y0), b1 = b&1; /* values of diameter */ + long dx = 4*(1-a)*b*b, dy = 4*(b1+1)*a*a; /* error increment */ + long err = dx+dy+b1*a*a, e2; /* error of 1.step */ + + if (x0 > x1) { x0 = x1; x1 += a; } /* if called with swapped points */ + if (y0 > y1) y0 = y1; /* .. exchange them */ + y0 += (b+1)/2; y1 = y0-b1; /* starting pixel */ + a *= 8*a; b1 = 8*b*b; + + do { + jlDrawPixelSet(c, x1, y0); /* I. Quadrant */ + jlDrawPixelSet(c, x0, y0); /* II. Quadrant */ + jlDrawPixelSet(c, x0, y1); /* III. Quadrant */ + jlDrawPixelSet(c, x1, y1); /* IV. Quadrant */ + e2 = 2*err; + if (e2 <= dy) { y0++; y1--; err += dy += a; } /* y step */ + if (e2 >= dx || 2*err > dy) { x0++; x1--; err += dx += b1; } /* x step */ + } while (x0 <= x1); + + while (y0-y1 < b) { /* too early stop of flat ellipses a=1 */ + jlDrawPixelSet(c, x0-1, y0); /* -> finish tip of ellipse */ + jlDrawPixelSet(c, x1+1, y0++); + jlDrawPixelSet(c, x0-1, y1); + jlDrawPixelSet(c, x1+1, y1--); + } +} + + +_jlScanDataT *_jlDrawFillNewSegment(jlContextT *c, jint16 startX, jint16 endX, jint16 y, signed char dir, jbool scanLeft, jbool scanRight) { + _jlScanDataT *s = NEW(_jlScanDataT); + + (void)c; + + s->StartX = startX; + s->EndX = endX; + s->Y = y; + s->Dir = dir; + s->ScanLeft = scanLeft; + s->ScanRight = scanRight; + + return s; +} + + +void _jlDrawFillAddLine(jlContextT *c, jint16 startX, jint16 endX, jint16 y, jint16 ignoreStart, jint16 ignoreEnd, char dir, jbool isNextInDir, jbool how) { + jint16 regionStart = -1; + jint16 x; + + for (x=startX; x= ignoreEnd) && (jlDrawPixelGet(c, x, y) == c->_jlDrawFillColor)) { + jlDrawPixelSet(c, x, y); + if (regionStart < 0) regionStart = x; + } else if (regionStart >= 0) { + jlUtilStackPush(c, c->_jlFillStackTop, _jlDrawFillNewSegment(c, regionStart, x, y, dir, regionStart == startX, jfalse)); + regionStart = -1; + } + } else { + // Unrolled contents to reduce comparison complexity. + if ((isNextInDir || x < ignoreStart || x >= ignoreEnd) && (jlDrawPixelGet(c, x, y) != c->_jlDrawFillColor)) { + jlDrawPixelSet(c, x, y); + if (regionStart < 0) regionStart = x; + } else if (regionStart >= 0) { + jlUtilStackPush(c, c->_jlFillStackTop, _jlDrawFillNewSegment(c, regionStart, x, y, dir, regionStart == startX, jfalse)); + regionStart = -1; + } + } + if (!isNextInDir && x < ignoreEnd && x >= ignoreStart) x = ignoreEnd-1; + } + if (regionStart >= 0) { + jlUtilStackPush(c, c->_jlFillStackTop, _jlDrawFillNewSegment(c, regionStart, x, y, dir, regionStart == startX, jtrue)); + } +} + + +// Stole this from http://www.adammil.net/blog/v126_A_More_Efficient_Flood_Fill.html +void _jlDrawFill(jlContextT *c, jint16 x, jint16 y, jbool how) { + jint16 height = 200; + jint16 width = 320; + _jlScanDataT *r; + jint16 startX; + jint16 endX; + + // how == true; Fill on top of _jlDrawFillColor + // how == false; Fill on top of any color until we encounter _jlDrawFillColor + + jlDrawPixelSet(c, x, y); + + jlUtilStackPush(c, c->_jlFillStackTop, _jlDrawFillNewSegment(c, x, x+1, y, 0, jtrue, jtrue)); + while ((r = (_jlScanDataT *)jlUtilStackPop(c, c->_jlFillStackTop)) != NULL) { + startX = r->StartX; + endX = r->EndX; + if (r->ScanLeft) { + if (how) { + while (startX > 0 && (jlDrawPixelGet(c, startX-1, r->Y) == c->_jlDrawFillColor)) jlDrawPixelSet(c, --startX, r->Y); + } else { + while (startX > 0 && (jlDrawPixelGet(c, startX-1, r->Y) != c->_jlDrawFillColor)) jlDrawPixelSet(c, --startX, r->Y); + } + } + if (r->ScanRight) { + if (how) { + while(endX < width && (jlDrawPixelGet(c, endX, r->Y) == c->_jlDrawFillColor)) jlDrawPixelSet(c, endX++, r->Y); + } else { + while(endX < width && (jlDrawPixelGet(c, endX, r->Y) != c->_jlDrawFillColor)) jlDrawPixelSet(c, endX++, r->Y); + } + } + r->StartX--; + r->EndX++; + if (r->Y > 0) _jlDrawFillAddLine(c, startX, endX, r->Y-1, r->StartX, r->EndX, -1, r->Dir <= 0, how); + if (r->Y < height-1) _jlDrawFillAddLine(c, startX, endX, r->Y+1, r->StartX, r->EndX, 1, r->Dir >= 0, how); + DEL(r); + } +} + + +void jlDrawFill(jlContextT *c, jint16 x, jint16 y) { + c->_jlDrawFillColor = jlDrawPixelGet(c, x, y); + _jlDrawFill(c, x, y, jtrue); +} + + +void jlDrawFillTo(jlContextT *c, jint16 x, jint16 y, jbyte color) { + c->_jlDrawFillColor = color; + _jlDrawFill(c, x, y, jfalse); +} + + +void jlDrawLine(jlContextT *c, jint16 x1, jint16 y1, jint16 x2, jint16 y2) { + jint16 x; + jint16 y; + jint16 dx; + jint16 dy; + jint16 incX; + jint16 incY; + jint16 balance; + + if (x2 >= x1) { + dx = x2 - x1; + incX = 1; + } else { + dx = x1 - x2; + incX = -1; + } + + if (y2 >= y1) { + dy = y2 - y1; + incY = 1; + } else { + dy = y1 - y2; + incY = -1; + } + + x = x1; + y = y1; + + if (dx >= dy) { + dy <<= 1; + balance = dy - dx; + dx <<= 1; + while (x != x2) { + jlDrawPixelSet(c, x, y); + if (balance >= 0) { + y += incY; + balance -= dx; + } + balance += dy; + x += incX; + } + jlDrawPixelSet(c, x, y); + } else { + dx <<= 1; + balance = dx - dy; + dy <<= 1; + while (y != y2) { + jlDrawPixelSet(c, x, y); + if (balance >= 0) { + x += incX; + balance -= dy; + } + balance += dx; + y += incY; + } + jlDrawPixelSet(c, x, y); + } +} + + +jbyte jlDrawPixelGet(jlContextT *c, jint16 x, jint16 y) { + int offset = (x + y * 320) * 4; + int r = c->_pixels[offset + 1]; + int g = c->_pixels[offset + 2]; + int b = c->_pixels[offset + 3]; + int index = 0; + int i; + + // Find the palette index for this color. + for (i=0; i<16; i++) { + if (r == c->_jlPalette[i].r && g == c->_jlPalette[i].g && b == c->_jlPalette[i].b) { + index = i; + break; + } + } + + return index; +} + + +void jlDrawPixelSet(jlContextT *c, jint16 x, jint16 y) { + int offset = (x + y * 320) * 4; + + c->_pixels[offset] = 255; + c->_pixels[offset + 1] = c->_jlPalette[c->_jlDrawColor].r; + c->_pixels[offset + 2] = c->_jlPalette[c->_jlDrawColor].g; + c->_pixels[offset + 3] = c->_jlPalette[c->_jlDrawColor].b; +} + + +void jlPaletteDefault(jlContextT *c) { + jbyte i; + + // Set palette. + c->_jlPalette[ 0].r = 0; c->_jlPalette[ 0].g = 0; c->_jlPalette[ 0].b = 0; // 000000 Black + c->_jlPalette[ 1].r = 0; c->_jlPalette[ 1].g = 0; c->_jlPalette[ 1].b = 10; // 0000AA Blue + c->_jlPalette[ 2].r = 0; c->_jlPalette[ 2].g = 10; c->_jlPalette[ 2].b = 0; // 00AA00 Green + c->_jlPalette[ 3].r = 0; c->_jlPalette[ 3].g = 10; c->_jlPalette[ 3].b = 10; // 00AAAA Cyan + c->_jlPalette[ 4].r = 10; c->_jlPalette[ 4].g = 0; c->_jlPalette[ 4].b = 0; // AA0000 Red + c->_jlPalette[ 5].r = 10; c->_jlPalette[ 5].g = 0; c->_jlPalette[ 5].b = 10; // AA00AA Magenta + c->_jlPalette[ 6].r = 10; c->_jlPalette[ 6].g = 5; c->_jlPalette[ 6].b = 0; // AA5500 Brown + c->_jlPalette[ 7].r = 10; c->_jlPalette[ 7].g = 10; c->_jlPalette[ 7].b = 10; // AAAAAA Light Gray + c->_jlPalette[ 8].r = 5; c->_jlPalette[ 8].g = 5; c->_jlPalette[ 8].b = 5; // 555555 Dark Gray + c->_jlPalette[ 9].r = 5; c->_jlPalette[ 9].g = 5; c->_jlPalette[ 9].b = 15; // 5555FF Bright Blue + c->_jlPalette[10].r = 5; c->_jlPalette[10].g = 15; c->_jlPalette[10].b = 5; // 55FF55 Bright Green + c->_jlPalette[11].r = 5; c->_jlPalette[11].g = 15; c->_jlPalette[11].b = 15; // 55FFFF Bright Cyan + c->_jlPalette[12].r = 15; c->_jlPalette[12].g = 5; c->_jlPalette[12].b = 5; // FF5555 Bright Red + c->_jlPalette[13].r = 15; c->_jlPalette[13].g = 5; c->_jlPalette[13].b = 15; // FF55FF Bright Magenta + c->_jlPalette[14].r = 15; c->_jlPalette[14].g = 15; c->_jlPalette[14].b = 5; // FFFF55 Bright Yellow + c->_jlPalette[15].r = 15; c->_jlPalette[15].g = 15; c->_jlPalette[15].b = 15; // FFFFFF White + + // Adjust 4 bit color values to 8 bit. + for (i=0; i<16; i++) { + c->_jlPalette[i].r *= 16; + c->_jlPalette[i].g *= 16; + c->_jlPalette[i].b *= 16; + } +} + + +void jlPaletteSet(jlContextT *c, jbyte index, jbyte r, jbyte g, jbyte b) { + c->_jlPalette[index].r = r * 16; + c->_jlPalette[index].g = g * 16; + c->_jlPalette[index].b = b * 16; +} + + +void *_jlUtilStackPop(jlContextT *c, jlStackT **stack) { + void *d = NULL; + jlStackT *s; + + (void)c; + + if (*stack != NULL) { + s = *stack; + d = s->data; + *stack = s->previous; + DEL(s); + } + return d; +} + + +void _jlUtilStackPush(jlContextT *c, jlStackT **stack, void *data, jint16 line, const char *file) { + jlStackT *s = NULL; + + (void)line; + (void)file; + (void)c; + + s = NEW(jlStackT); + s->previous = *stack; + s->data = data; + *stack = s; +} + + +#pragma clang diagnostic pop diff --git a/src/joeydev.c b/src/joeydev.c new file mode 100644 index 0000000..6416936 --- /dev/null +++ b/src/joeydev.c @@ -0,0 +1,108 @@ +/* + * JoeyDev + * Copyright (C) 2018-2023 Scott Duensing + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. +*/ + + +#include + +#include "common.h" +#include "utils.h" + +#include "gladeJoeyDev.h" +#include "pngLogo.h" +#include "vector.h" +#include "joeydev.h" + + +static GtkWidget *_winJoeyDev; + + +EVENT void toolJoeyDevAboutClicked(GtkWidget *object, gpointer userData); +EVENT void toolJoeyDevProjectClicked(GtkWidget *object, gpointer userData); +EVENT void toolJoeyDevQuitClicked(GtkWidget *object, gpointer userData); +EVENT void toolJoeyDevVectorClicked(GtkWidget *object, gpointer userData); +EVENT gboolean winJoeyDevClose(GtkWidget *object, gpointer userData); + + +EVENT void toolJoeyDevAboutClicked(GtkWidget *object, gpointer userData) { + GtkWidget *dialog; + GdkPixbuf *pixbuf; + + (void)object; + (void)userData; + + pixbuf = utilGetPixbufFromMemory(EMBEDDED(___ui_Logo_png)); + + dialog = gtk_about_dialog_new(); + gtk_about_dialog_set_program_name(GTK_ABOUT_DIALOG(dialog), "JoeyDev"); + gtk_about_dialog_set_version(GTK_ABOUT_DIALOG(dialog), "0.1"); + gtk_about_dialog_set_copyright(GTK_ABOUT_DIALOG(dialog),"Copyright (C) 2018-2023 Scott Duensing"); + gtk_about_dialog_set_comments(GTK_ABOUT_DIALOG(dialog), "Development Environment for JoeyLib."); + gtk_about_dialog_set_website(GTK_ABOUT_DIALOG(dialog), "https://JoeyLib.com"); + gtk_about_dialog_set_logo(GTK_ABOUT_DIALOG(dialog), pixbuf); + + g_object_unref(pixbuf); + pixbuf = NULL; + + gtk_dialog_run(GTK_DIALOG(dialog)); + gtk_widget_destroy(dialog); +} + + +EVENT void toolJoeyDevProjectClicked(GtkWidget *object, gpointer userData) { + (void)object; + (void)userData; + + printf("Project Clicked!\n"); +} + + +EVENT void toolJoeyDevQuitClicked(GtkWidget *object, gpointer userData) { + winJoeyDevClose(object, userData); +} + + +EVENT void toolJoeyDevVectorClicked(GtkWidget *object, gpointer userData) { + (void)object; + (void)userData; + + winVectorCreate(); +} + + +EVENT gboolean winJoeyDevClose(GtkWidget *object, gpointer userData) { + (void)object; + (void)userData; + + // Do they want to quit? + if (utilQuestionDialog(_winJoeyDev, "Exit", "Are you sure you wish to exit JoeyDev?") == FALSE) return TRUE; + + // Are all editors closed? + if (utilWindowsCloseAll() > 0) return TRUE; + + // Quit! + gtk_main_quit(); + return FALSE; +} + + +void winJoeyDevCreate(void) { + _winJoeyDev = utilGetWindowFromMemory("_winJoeyDev", EMBEDDED(___ui_JoeyDev_glade), NULL); +} diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..f21ee94 --- /dev/null +++ b/src/main.c @@ -0,0 +1,35 @@ +/* + * JoeyDev + * Copyright (C) 2018-2023 Scott Duensing + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. +*/ + + +#include "common.h" +#include "joeydev.h" +#include "stddclmr.h" + + +int main(int argc, char **argv) { + + gtk_init(&argc, &argv); + winJoeyDevCreate(); + gtk_main(); + + return 0; +} diff --git a/src/utils.c b/src/utils.c new file mode 100644 index 0000000..57f6235 --- /dev/null +++ b/src/utils.c @@ -0,0 +1,149 @@ +/* + * JoeyDev + * Copyright (C) 2018-2023 Scott Duensing + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. +*/ + + +#include "utils.h" + + +typedef struct WindowListS { + GtkWidget *key; + WindowDataT *value; +} WindowListT; + + +static WindowListT *_windowList = NULL; + + +GdkPixbuf *utilGetPixbufFromMemory(char *data, unsigned int len) { + GdkPixbufLoader *loader; + GdkPixbuf *pixbuf; + + loader = gdk_pixbuf_loader_new(); + gdk_pixbuf_loader_write(loader, (const guchar *)data, len, NULL); + pixbuf = gdk_pixbuf_loader_get_pixbuf(loader); + gdk_pixbuf_loader_close(loader, NULL); + + return pixbuf; +} + + +WindowDataT *utilGetWindowData(GtkWidget *window) { + return hmget(_windowList, window); +} + + +GtkWidget *utilGetWindowFromMemory(char *name, char *data, unsigned int len, gpointer userData) { + GtkBuilder *gtkBuilder; + GtkWidget *window; + + gtkBuilder = gtk_builder_new(); + if (gtk_builder_add_from_string(gtkBuilder, data, len, NULL) == 0) { + printf("Failed to build UI '%s'!\n", name); + exit(1); + } + window = GTK_WIDGET(gtk_builder_get_object(gtkBuilder, name)); + gtk_builder_connect_signals(gtkBuilder, userData); + g_object_unref(G_OBJECT(gtkBuilder)); + + gtk_widget_show(window); + + return window; +} + + +gboolean utilGetWidgetsFromMemory(char *name[], GtkWidget **widgets[], char *data, unsigned int len, gpointer userData) { + GtkBuilder *gtkBuilder; + int x; + + gtkBuilder = gtk_builder_new(); + if (gtk_builder_add_from_string(gtkBuilder, data, len, NULL) == 0) { + printf("Failed to build UI '%s'!\n", name[0]); + exit(1); + } + + x = 0; + while (name[x] != NULL) { + *widgets[x] = GTK_WIDGET(gtk_builder_get_object(gtkBuilder, name[x])); + x++; + } + + gtk_builder_connect_signals(gtkBuilder, userData); + g_object_unref(G_OBJECT(gtkBuilder)); + + return TRUE; +} + + +gboolean utilQuestionDialog(GtkWidget *parent, char *title, char *question) { + GtkWidget *dialog; + int response; + + dialog = gtk_message_dialog_new( + GTK_WINDOW(parent), + GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL, + GTK_MESSAGE_QUESTION, + GTK_BUTTONS_OK_CANCEL, + "%s", question); + gtk_window_set_title(GTK_WINDOW(dialog), title); + response = gtk_dialog_run(GTK_DIALOG(dialog)); + gtk_widget_destroy(dialog); + + return (response == GTK_RESPONSE_OK) ? TRUE : FALSE; +} + + +void utilWindowRegister(gpointer windowData) { + WindowDataT *w = (WindowDataT *)windowData; + hmput(_windowList, w->window, windowData); +} + + +int utilWindowsCloseAll(void) { + WindowDataT *w; + while (hmlen(_windowList) > 0) { + w = (WindowDataT *)_windowList[0].value; + if (w->closeWindow(w->window, w) == TRUE) { + // User canceled closing. + break; + } + // Mark it clean and close it ourselves. + w->isDirty = FALSE; + gtk_window_close(GTK_WINDOW(w->window)); + // Unregister it. + utilWindowUnRegister(w); + } + return utilWindowsOpen(); +} + + +int utilWindowsOpen(void) { + return hmlen(_windowList); +} + + +gboolean utilWindowUnRegister(gpointer windowData) { + int result; + + WindowDataT *w = (WindowDataT *)windowData; + result = hmdel(_windowList, w->window); + + return (result == 1) ? TRUE : FALSE; +} diff --git a/src/vector.c b/src/vector.c new file mode 100644 index 0000000..d51901e --- /dev/null +++ b/src/vector.c @@ -0,0 +1,623 @@ +/* + * JoeyDev + * Copyright (C) 2018-2023 Scott Duensing + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. +*/ + + +#pragma clang diagnostic push +#pragma ide diagnostic ignored "cppcoreguidelines-narrowing-conversions" +#pragma ide diagnostic ignored "hicpp-multiway-paths-covered" + + +#include "common.h" +#include +#include + +#define PLAT_GTK 1 +#define GTK 3 +#include "Scintilla.h" +#include "SciLexer.h" +#include "ScintillaWidget.h" +#include "Lexilla.h" + +#include "gladeVector.h" +#include "vector.h" +#include "utils.h" +#include "draw.h" + + +#define SSM(m, w, l) scintilla_send_message(self->sci, m, w, l) +#define MARGIN_SCRIPT_FOLD_INDEX 1 + + +enum PassE { + PASS_DRAW, + PASS_GENERATE +}; +typedef enum PassE PassT; + + +typedef struct VectorDataS { + WindowDataT windowData; + GtkWidget *drawVectorImage; + GtkWidget *boxVectorForEditor; + GtkWidget *editor; + ScintillaObject *sci; + void *pLexer; + int id; + cairo_surface_t *surface; + cairo_t *cr; + unsigned char *surfacePointer; + jlContextT *jlc; +} VectorDataT; + + +typedef struct CommandsS { + char *command; + gboolean (*parserFunction)(PassT pass, char **tokenEnd, VectorDataT *self); +} CommandsT; + + +static int _nextEditorId = 0; + + +EVENT gboolean drawVectorImageDraw(GtkWidget *widget, cairo_t *cr, gpointer userData); +EVENT void editorVectorNotify(GtkWidget *sciWidget, gint ctrlID, struct SCNotification *notifyData, gpointer userData); +EVENT void menuVectorFileClose(GtkWidget *object, gpointer userData); +static gboolean parseBox(PassT pass, char **tokenEnd, VectorDataT *self); +static gboolean parseCircle(PassT pass, char **tokenEnd, VectorDataT *self); +static gboolean parseColor(PassT pass, char **tokenEnd, VectorDataT *self); +static gboolean parseComment(PassT pass, char **tokenEnd, VectorDataT *self); +static gboolean parseEllipse(PassT pass, char **tokenEnd, VectorDataT *self); +static gboolean parseFill(PassT pass, char **tokenEnd, VectorDataT *self); +static gboolean parseLine(PassT pass, char **tokenEnd, VectorDataT *self); +static gboolean parsePlot(PassT pass, char **tokenEnd, VectorDataT *self); +static gboolean parseRectangle(PassT pass, char **tokenEnd, VectorDataT *self); +static gboolean parseReset(PassT pass, char **tokenEnd, VectorDataT *self); +static void parser(PassT pass, gpointer userData); +static gboolean parserGetWord(char *word, char **tokenEnd); +static gboolean parserGetX(char **tokenEnd, int *x); +static gboolean parserGetXY(char **tokenEnd, int *x, int *y); +EVENT gboolean winVectorClose(GtkWidget *object, gpointer userData); +static void winVectorDelete(gpointer userData); + + +EVENT gboolean drawVectorImageDraw(GtkWidget *widget, cairo_t *cr, gpointer userData) { + VectorDataT *self = (VectorDataT *)userData; + cairo_surface_t *target = cairo_get_target(cr); + int width = cairo_image_surface_get_width(target); + int height = cairo_image_surface_get_height(target); + + (void)widget; + (void)userData; + + cairo_surface_mark_dirty(self->surface); + + cairo_save(cr); + cairo_set_source_surface(cr, self->surface, 0, 0); + cairo_rectangle(cr, 0, 0, width, height); + cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); + cairo_fill(cr); + cairo_restore(cr); + + cairo_surface_flush(self->surface); + + printf("Redrew image\n"); + + return FALSE; +} + + +EVENT void editorVectorNotify(GtkWidget *sciWidget, gint ctrlID, struct SCNotification *notifyData, gpointer userData) { + VectorDataT *self = (VectorDataT *)userData; + int lineNumber = (int)SSM(SCI_LINEFROMPOSITION, (uptr_t)notifyData->position , (sptr_t)0); + + (void)sciWidget; + (void)ctrlID; + + //printf("Notification %d\n", notifyData->nmhdr.code); + + switch (notifyData->nmhdr.code) { + case SCN_CHARADDED: + parser(PASS_DRAW, userData); + gtk_widget_queue_draw(self->drawVectorImage); + break; + + case SCN_SAVEPOINTLEFT: + self->windowData.isDirty = TRUE; //***TODO*** I don't think this is very accurate. + break; + + case SCN_SAVEPOINTREACHED: + self->windowData.isDirty = FALSE; + break; + + case SCN_MARGINCLICK: + switch (notifyData->margin) { + case MARGIN_SCRIPT_FOLD_INDEX: + SSM(SCI_TOGGLEFOLD, lineNumber, (sptr_t)0); + break; + } + break; + } +} + + +EVENT void menuVectorFileClose(GtkWidget *object, gpointer userData) { + VectorDataT *self = (VectorDataT *)userData; + + (void)object; + + gtk_window_close(GTK_WINDOW(self->windowData.window)); +} + + +static gboolean parseBox(PassT pass, char **tokenEnd, VectorDataT *self) { + int x1; + int y1; + int x2; + int y2; + + // Box (value),(value) to (value),(value) + if (!parserGetXY(tokenEnd, &x1, &y1)) return FALSE; + if (!parserGetWord("TO", tokenEnd)) return FALSE; + if (!parserGetXY(tokenEnd, &x2, &y2)) return FALSE; + + switch (pass) { + case PASS_DRAW: + jlDrawBox(self->jlc, x1, y1, x2, y2); + break; + + case PASS_GENERATE: + break; + } + + return TRUE; +} + + +static gboolean parseCircle(PassT pass, char **tokenEnd, VectorDataT *self) { + int r; + int x1; + int y1; + + // Circle (value) at (value),(value) + if (!parserGetX(tokenEnd, &r)) return FALSE; + if (!parserGetWord("AT", tokenEnd)) return FALSE; + if (!parserGetXY(tokenEnd, &x1, &y1)) return FALSE; + + switch (pass) { + case PASS_DRAW: + jlDrawCircle(self->jlc, x1, y1, r); + break; + + case PASS_GENERATE: + break; + } + + return TRUE; +} + + +static gboolean parseColor(PassT pass, char **tokenEnd, VectorDataT *self) { + int c; + + // Color (short) + + if (!parserGetX(tokenEnd, &c)) return FALSE; + + switch (pass) { + case PASS_DRAW: + jlDrawColorSet(self->jlc, c); + break; + + case PASS_GENERATE: + break; + } + + return TRUE; +} + + +static gboolean parseComment(PassT pass, char **tokenEnd, VectorDataT *self) { + char *token = (char *)1; // Just can't be NULL. + + // Eat the rest of the line. + while (token != NULL) { + token = strtok_r(NULL, " ", tokenEnd); + } + + return TRUE; +} + + +static gboolean parseEllipse(PassT pass, char **tokenEnd, VectorDataT *self) { + int x1; + int y1; + int x2; + int y2; + + // Ellipse (value),(value) to (value),(value) + if (!parserGetXY(tokenEnd, &x1, &y1)) return FALSE; + if (!parserGetWord("TO", tokenEnd)) return FALSE; + if (!parserGetXY(tokenEnd, &x2, &y2)) return FALSE; + + switch (pass) { + case PASS_DRAW: + jlDrawEllipse(self->jlc, x1, y1, x2, y2); + break; + + case PASS_GENERATE: + break; + } + + return TRUE; +} + + +static gboolean parseFill(PassT pass, char **tokenEnd, VectorDataT *self) { + // Fill (on|to) (value),(value) + + switch (pass) { + case PASS_DRAW: + break; + + case PASS_GENERATE: + break; + } + + return TRUE; +} + + +static gboolean parseLine(PassT pass, char **tokenEnd, VectorDataT *self) { + // Line (value),(value) to (value),(value) [to ...] + if (!parserGetXY(tokenEnd, &x1, &y1)) return FALSE; + if (!parserGetWord("TO", tokenEnd)) return FALSE; + if (!parserGetXY(tokenEnd, &x2, &y2)) return FALSE; + + switch (pass) { + case PASS_DRAW: + break; + + case PASS_GENERATE: + break; + } + + return TRUE; +} + + +static gboolean parsePlot(PassT pass, char **tokenEnd, VectorDataT *self) { + int x1; + int y1; + + // Plot (value),(value) + if (!parserGetXY(tokenEnd, &x1, &y1)) return FALSE; + + switch (pass) { + case PASS_DRAW: + jlDrawPixelSet(self->jlc, x1, y1); + break; + + case PASS_GENERATE: + break; + } + + return TRUE; +} + + +static gboolean parseRectangle(PassT pass, char **tokenEnd, VectorDataT *self) { + int x1; + int y1; + int x2; + int y2; + + // Rectangle (value),(value) to (value),(value) + if (!parserGetXY(tokenEnd, &x1, &y1)) return FALSE; + if (!parserGetWord("TO", tokenEnd)) return FALSE; + if (!parserGetXY(tokenEnd, &x2, &y2)) return FALSE; + + switch (pass) { + case PASS_DRAW: + jlDrawBoxFilled(self->jlc, x1, y1, x2, y2); + break; + + case PASS_GENERATE: + break; + } + + return TRUE; +} + + +static gboolean parseReset(PassT pass, char **tokenEnd, VectorDataT *self) { + // Reset + + // Reset draw context. + jlContextDel(&self->jlc); + self->jlc = jlContextNew(self->surfacePointer); + + switch (pass) { + case PASS_DRAW: + jlDrawColorSet(self->jlc, 0); + jlDrawClear(self->jlc); + jlDrawColorSet(self->jlc, 15); + break; + + case PASS_GENERATE: + break; + } + + return TRUE; +} + + +static void parser(PassT pass, gpointer userData) { + VectorDataT *self = (VectorDataT *)userData; + int length = SSM(SCI_GETLENGTH, 0, 0); + int x; + char *code; + char *line; + char *lineEnd; + gboolean lineOkay; + char *token; + char *tokenEnd; + CommandsT commands[] = { + { "BOX", parseBox }, + { "CIRCLE", parseCircle }, + { "COLOR", parseColor }, + { "//", parseComment }, + { "ELLIPSE", parseEllipse }, + { "FILL", parseFill }, + { "LINE", parseLine }, + { "PLOT", parsePlot }, + { "RECTANGLE", parseRectangle }, + { "RESET", parseReset }, + { NULL, NULL } + }; + + // Allocate space to fetch code from editor. + code = (char *)malloc(length + 1); + if (!code) return; + + // Fetch code. + SSM(SCI_GETTEXT, length, (sptr_t)code); + + // Parse code. + line = strtok_r(code, "\n", &lineEnd); + while (line != NULL) { + // Get the first token on the line. + token = strtok_r(line, " ", &tokenEnd); + // Is this something we care about? + x = 0; + lineOkay = FALSE; + while (commands[x].command) { + if (strcasecmp(commands[x].command, token) == 0) { + if (commands[x].parserFunction(pass, &tokenEnd, self) == TRUE) { + lineOkay = TRUE; + } + break; + } + x++; + } + if (lineOkay == FALSE) { + //***TODO*** Mark lines that fail to parse. + } + // Move to next line. + line = strtok_r(NULL, "\n", &lineEnd); + } + + // Release code. + free(code); + + /* + * (value) is a 16-bit integer. Since we only need a fraction of the + * possible values provided by this, we steal a couple bits for our + * own use. All values are stored without messing with 2's complement. + * + * Type Negative Value + * \ /__________/_ + * \ // \ + * tnvvvvvvvvvvvvvv + * + * So with this scheme we can store values from -16383 to 16383 (yes, + * zero is represented twice). + * + * The Type bit determines if the value stored is a literal value or a + * reference to a variable in the variable table. + * + * (short) is a simplified version used for colors. It is always positive + * and has a range from 0 to 127 with Type being the MSb. This effectively + * limits the number of available variables to 128. + * + */ +} + +static gboolean parserGetWord(char *word, char **tokenEnd) { + char *token; + + // Is this token the "WORD"? + + token = strtok_r(NULL, " ", tokenEnd); + if (token == NULL) return FALSE; + if (strcasecmp(token, word) != 0) return FALSE; + + return TRUE; +} + + +static gboolean parserGetX(char **tokenEnd, int *x) { + char *value; + char *endPtr = NULL; + + // Return single value. + //***TODO*** Variable support. + + value = strtok_r(NULL, " ", tokenEnd); + if (value == NULL) return FALSE; + errno = 0; endPtr = NULL; + *x = (int)strtol(value, &endPtr, 10); + if (errno != 0) return FALSE; + + return TRUE; +} + + +static gboolean parserGetXY(char **tokenEnd, int *x, int *y) { + char *token; + char *value; + char *valueEnd; + char *endPtr; + + // Return values of X,Y pair. + //***TODO*** Variable support. + + token = strtok_r(NULL, " ", tokenEnd); + if (token == NULL) return FALSE; + + value = strtok_r(token, ",", &valueEnd); + if (value == NULL) return FALSE; + errno = 0; endPtr = NULL; + *x = (int)strtol(value, &endPtr, 10); + if (errno != 0) return FALSE; + + value = strtok_r(NULL, ",", &valueEnd); + if (value == NULL) return FALSE; + errno = 0; endPtr = NULL; + *y = (int)strtol(value, &endPtr, 10); + if (errno != 0) return FALSE; + + return TRUE; +} + + +EVENT gboolean winVectorClose(GtkWidget *object, gpointer userData) { + // userData is not reliable due to menuVectorFileClose and util indirectly calling us. + VectorDataT *self = (VectorDataT *)utilGetWindowData(object); + + (void)userData; + + if (self->windowData.isDirty == TRUE) { + if (utilQuestionDialog(self->windowData.window, "Exit", "You have unsaved changes. Exit?")) { + winVectorDelete(self); + return FALSE; + } + return TRUE; + } + + winVectorDelete(self); + return FALSE; +} + + +void winVectorCreate(void) { + VectorDataT *self; + char *widgetNames[] = { "winVector", "boxVectorForEditor", "drawVectorImage", NULL }; + GtkWidget **widgets[] = { NULL, NULL, NULL }; + + // Set up instance data. + self = NEW(VectorDataT); + self->windowData.closeWindow = winVectorClose; + + // Load widgets from XML. + widgets[0] = &self->windowData.window; + widgets[1] = &self->boxVectorForEditor; + widgets[2] = &self->drawVectorImage; + utilGetWidgetsFromMemory(widgetNames, widgets, EMBEDDED(___ui_Vector_glade), self); + + // Create Scintilla editor. + self->editor = scintilla_new(); + self->sci = SCINTILLA(self->editor); + self->id = _nextEditorId++; + gtk_container_add(GTK_CONTAINER(self->boxVectorForEditor), self->editor); + scintilla_set_id(self->sci, self->id); + gtk_widget_set_size_request(self->editor, 500, 300); + + // Configure editor. + SSM(SCI_SETCODEPAGE, SC_CP_UTF8, 0); + SSM(SCI_SETIMEINTERACTION, SC_IME_WINDOWED, 0); + SSM(SCI_STYLESETCHARACTERSET, STYLE_DEFAULT, SC_CHARSET_DEFAULT); + SSM(SCI_STYLESETFONT, STYLE_DEFAULT, (sptr_t)"Monospace"); + SSM(SCI_STYLESETSIZEFRACTIONAL, STYLE_DEFAULT, 11 * SC_FONT_SIZE_MULTIPLIER); + SSM(SCI_STYLESETFORE, STYLE_DEFAULT, 0xFFFFFF); + SSM(SCI_STYLESETBACK, STYLE_DEFAULT, 0); + SSM(SCI_STYLECLEARALL, 0, 0); + SSM(SCI_SETTABWIDTH, 3, 0); + SSM(SCI_SETMARGINWIDTHN, 0, (int)SSM(SCI_TEXTWIDTH, STYLE_LINENUMBER, (sptr_t)"99999")); + SSM(SCI_SETMARGINWIDTHN, 1, 0); + SSM(SCI_SETWRAPMODE, SC_WRAP_WORD, 0); + SSM(SCI_SETWRAPVISUALFLAGS, SC_WRAPVISUALFLAG_END, 0); + SSM(SCI_SETWRAPINDENTMODE, SC_WRAPINDENT_INDENT, 0); + SSM(SCI_SETCARETSTYLE, CARETSTYLE_BLOCK | CARETSTYLE_OVERSTRIKE_BLOCK, 0); + SSM(SCI_SETCARETFORE, 0x00ffff, 0); + SSM(SCI_STYLESETBACK, STYLE_LINENUMBER, 0x222222); + + // Add "indent" (no language) lexer for basic folding support. + self->pLexer = CreateLexer("cpp"); + SSM(SCI_SETILEXER, 0, (sptr_t)self->pLexer); + + // Syntax highlighting. Not really C but it works. + SSM(SCI_SETKEYWORDS, 0, (sptr_t)"at call circle color end fill include line to sub var"); + SSM(SCI_STYLESETFORE, SCE_CAML_KEYWORD, 0x777777); // Not right - trying to find the property that is making keywords blue! + SSM(SCI_STYLESETFORE, SCE_C_PREPROCESSOR, 0x0080ff); + SSM(SCI_STYLESETFORE, SCE_C_COMMENT, 0x00FF00); + SSM(SCI_STYLESETFORE, SCE_C_COMMENTLINE, 0x00FF00); + SSM(SCI_STYLESETFORE, SCE_C_NUMBER, 0xFFFF00); + SSM(SCI_STYLESETFORE, SCE_C_WORD, 0xFF0000); + SSM(SCI_STYLESETFORE, SCE_C_STRING, 0xFF00FF); + SSM(SCI_STYLESETBOLD, SCE_C_OPERATOR, 1); + + // Debug + SSM(SCI_INSERTTEXT, 0, (sptr_t) + "reset\n" + "color 15\n" + "box 0,0 to 319,299\n" + ); + + // Connect editor to our code. + g_signal_connect(G_OBJECT(self->editor), "sci-notify", G_CALLBACK(editorVectorNotify), self); + + // Create our drawing surface and context. + self->surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 320, 200); + self->cr = cairo_create(self->surface); + cairo_surface_flush(self->surface); + self->surfacePointer = cairo_image_surface_get_data(self->surface); + self->jlc = jlContextNew(self->surfacePointer); + + // Register window & show it. + utilWindowRegister(self); + gtk_widget_show_all(self->windowData.window); +} + + +static void winVectorDelete(gpointer userData) { + VectorDataT *self = (VectorDataT *)userData; + + utilWindowUnRegister(userData); + + jlContextDel(&self->jlc); + cairo_destroy(self->cr); + cairo_surface_destroy(self->surface); + + DEL(self); +} + + +#pragma clang diagnostic pop diff --git a/thirdparty/lexilla520.tgz b/thirdparty/lexilla520.tgz new file mode 100644 index 0000000..92afc19 --- /dev/null +++ b/thirdparty/lexilla520.tgz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a45766e0753cfeb6707ee4b0abb7f796de4deebe1b1f46d17128c122a6e107c5 +size 929215 diff --git a/thirdparty/scintilla531.tgz b/thirdparty/scintilla531.tgz new file mode 100644 index 0000000..7ef85ae --- /dev/null +++ b/thirdparty/scintilla531.tgz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0f57b56bc43ef5786e3a194562b7d0903a74f768f0834ed4b20b53b47129b08f +size 1311571 diff --git a/thirdparty/stb_ds.h b/thirdparty/stb_ds.h new file mode 100644 index 0000000..ebd6161 --- /dev/null +++ b/thirdparty/stb_ds.h @@ -0,0 +1,1895 @@ +/* stb_ds.h - v0.67 - public domain data structures - Sean Barrett 2019 + + This is a single-header-file library that provides easy-to-use + dynamic arrays and hash tables for C (also works in C++). + + For a gentle introduction: + http://nothings.org/stb_ds + + To use this library, do this in *one* C or C++ file: + #define STB_DS_IMPLEMENTATION + #include "stb_ds.h" + +TABLE OF CONTENTS + + Table of Contents + Compile-time options + License + Documentation + Notes + Notes - Dynamic arrays + Notes - Hash maps + Credits + +COMPILE-TIME OPTIONS + + #define STBDS_NO_SHORT_NAMES + + This flag needs to be set globally. + + By default stb_ds exposes shorter function names that are not qualified + with the "stbds_" prefix. If these names conflict with the names in your + code, define this flag. + + #define STBDS_SIPHASH_2_4 + + This flag only needs to be set in the file containing #define STB_DS_IMPLEMENTATION. + + By default stb_ds.h hashes using a weaker variant of SipHash and a custom hash for + 4- and 8-byte keys. On 64-bit platforms, you can define the above flag to force + stb_ds.h to use specification-compliant SipHash-2-4 for all keys. Doing so makes + hash table insertion about 20% slower on 4- and 8-byte keys, 5% slower on + 64-byte keys, and 10% slower on 256-byte keys on my test computer. + + #define STBDS_REALLOC(context,ptr,size) better_realloc + #define STBDS_FREE(context,ptr) better_free + + These defines only need to be set in the file containing #define STB_DS_IMPLEMENTATION. + + By default stb_ds uses stdlib realloc() and free() for memory management. You can + substitute your own functions instead by defining these symbols. You must either + define both, or neither. Note that at the moment, 'context' will always be NULL. + @TODO add an array/hash initialization function that takes a memory context pointer. + + #define STBDS_UNIT_TESTS + + Defines a function stbds_unit_tests() that checks the functioning of the data structures. + + Note that on older versions of gcc (e.g. 5.x.x) you may need to build with '-std=c++0x' + (or equivalentally '-std=c++11') when using anonymous structures as seen on the web + page or in STBDS_UNIT_TESTS. + +LICENSE + + Placed in the public domain and also MIT licensed. + See end of file for detailed license information. + +DOCUMENTATION + + Dynamic Arrays + + Non-function interface: + + Declare an empty dynamic array of type T + T* foo = NULL; + + Access the i'th item of a dynamic array 'foo' of type T, T* foo: + foo[i] + + Functions (actually macros) + + arrfree: + void arrfree(T*); + Frees the array. + + arrlen: + ptrdiff_t arrlen(T*); + Returns the number of elements in the array. + + arrlenu: + size_t arrlenu(T*); + Returns the number of elements in the array as an unsigned type. + + arrpop: + T arrpop(T* a) + Removes the final element of the array and returns it. + + arrput: + T arrput(T* a, T b); + Appends the item b to the end of array a. Returns b. + + arrins: + T arrins(T* a, int p, T b); + Inserts the item b into the middle of array a, into a[p], + moving the rest of the array over. Returns b. + + arrinsn: + void arrinsn(T* a, int p, int n); + Inserts n uninitialized items into array a starting at a[p], + moving the rest of the array over. + + arraddnptr: + T* arraddnptr(T* a, int n) + Appends n uninitialized items onto array at the end. + Returns a pointer to the first uninitialized item added. + + arraddnindex: + size_t arraddnindex(T* a, int n) + Appends n uninitialized items onto array at the end. + Returns the index of the first uninitialized item added. + + arrdel: + void arrdel(T* a, int p); + Deletes the element at a[p], moving the rest of the array over. + + arrdeln: + void arrdeln(T* a, int p, int n); + Deletes n elements starting at a[p], moving the rest of the array over. + + arrdelswap: + void arrdelswap(T* a, int p); + Deletes the element at a[p], replacing it with the element from + the end of the array. O(1) performance. + + arrsetlen: + void arrsetlen(T* a, int n); + Changes the length of the array to n. Allocates uninitialized + slots at the end if necessary. + + arrsetcap: + size_t arrsetcap(T* a, int n); + Sets the length of allocated storage to at least n. It will not + change the length of the array. + + arrcap: + size_t arrcap(T* a); + Returns the number of total elements the array can contain without + needing to be reallocated. + + Hash maps & String hash maps + + Given T is a structure type: struct { TK key; TV value; }. Note that some + functions do not require TV value and can have other fields. For string + hash maps, TK must be 'char *'. + + Special interface: + + stbds_rand_seed: + void stbds_rand_seed(size_t seed); + For security against adversarially chosen data, you should seed the + library with a strong random number. Or at least seed it with time(). + + stbds_hash_string: + size_t stbds_hash_string(char *str, size_t seed); + Returns a hash value for a string. + + stbds_hash_bytes: + size_t stbds_hash_bytes(void *p, size_t len, size_t seed); + These functions hash an arbitrary number of bytes. The function + uses a custom hash for 4- and 8-byte data, and a weakened version + of SipHash for everything else. On 64-bit platforms you can get + specification-compliant SipHash-2-4 on all data by defining + STBDS_SIPHASH_2_4, at a significant cost in speed. + + Non-function interface: + + Declare an empty hash map of type T + T* foo = NULL; + + Access the i'th entry in a hash table T* foo: + foo[i] + + Function interface (actually macros): + + hmfree + shfree + void hmfree(T*); + void shfree(T*); + Frees the hashmap and sets the pointer to NULL. + + hmlen + shlen + ptrdiff_t hmlen(T*) + ptrdiff_t shlen(T*) + Returns the number of elements in the hashmap. + + hmlenu + shlenu + size_t hmlenu(T*) + size_t shlenu(T*) + Returns the number of elements in the hashmap. + + hmgeti + shgeti + hmgeti_ts + ptrdiff_t hmgeti(T*, TK key) + ptrdiff_t shgeti(T*, char* key) + ptrdiff_t hmgeti_ts(T*, TK key, ptrdiff_t tempvar) + Returns the index in the hashmap which has the key 'key', or -1 + if the key is not present. + + hmget + hmget_ts + shget + TV hmget(T*, TK key) + TV shget(T*, char* key) + TV hmget_ts(T*, TK key, ptrdiff_t tempvar) + Returns the value corresponding to 'key' in the hashmap. + The structure must have a 'value' field + + hmgets + shgets + T hmgets(T*, TK key) + T shgets(T*, char* key) + Returns the structure corresponding to 'key' in the hashmap. + + hmgetp + shgetp + hmgetp_ts + hmgetp_null + shgetp_null + T* hmgetp(T*, TK key) + T* shgetp(T*, char* key) + T* hmgetp_ts(T*, TK key, ptrdiff_t tempvar) + T* hmgetp_null(T*, TK key) + T* shgetp_null(T*, char *key) + Returns a pointer to the structure corresponding to 'key' in + the hashmap. Functions ending in "_null" return NULL if the key + is not present in the hashmap; the others return a pointer to a + structure holding the default value (but not the searched-for key). + + hmdefault + shdefault + TV hmdefault(T*, TV value) + TV shdefault(T*, TV value) + Sets the default value for the hashmap, the value which will be + returned by hmget/shget if the key is not present. + + hmdefaults + shdefaults + TV hmdefaults(T*, T item) + TV shdefaults(T*, T item) + Sets the default struct for the hashmap, the contents which will be + returned by hmgets/shgets if the key is not present. + + hmput + shput + TV hmput(T*, TK key, TV value) + TV shput(T*, char* key, TV value) + Inserts a pair into the hashmap. If the key is already + present in the hashmap, updates its value. + + hmputs + shputs + T hmputs(T*, T item) + T shputs(T*, T item) + Inserts a struct with T.key into the hashmap. If the struct is already + present in the hashmap, updates it. + + hmdel + shdel + int hmdel(T*, TK key) + int shdel(T*, char* key) + If 'key' is in the hashmap, deletes its entry and returns 1. + Otherwise returns 0. + + Function interface (actually macros) for strings only: + + sh_new_strdup + void sh_new_strdup(T*); + Overwrites the existing pointer with a newly allocated + string hashmap which will automatically allocate and free + each string key using realloc/free + + sh_new_arena + void sh_new_arena(T*); + Overwrites the existing pointer with a newly allocated + string hashmap which will automatically allocate each string + key to a string arena. Every string key ever used by this + hash table remains in the arena until the arena is freed. + Additionally, any key which is deleted and reinserted will + be allocated multiple times in the string arena. + +NOTES + + * These data structures are realloc'd when they grow, and the macro + "functions" write to the provided pointer. This means: (a) the pointer + must be an lvalue, and (b) the pointer to the data structure is not + stable, and you must maintain it the same as you would a realloc'd + pointer. For example, if you pass a pointer to a dynamic array to a + function which updates it, the function must return back the new + pointer to the caller. This is the price of trying to do this in C. + + * The following are the only functions that are thread-safe on a single data + structure, i.e. can be run in multiple threads simultaneously on the same + data structure + hmlen shlen + hmlenu shlenu + hmget_ts shget_ts + hmgeti_ts shgeti_ts + hmgets_ts shgets_ts + + * You iterate over the contents of a dynamic array and a hashmap in exactly + the same way, using arrlen/hmlen/shlen: + + for (i=0; i < arrlen(foo); ++i) + ... foo[i] ... + + * All operations except arrins/arrdel are O(1) amortized, but individual + operations can be slow, so these data structures may not be suitable + for real time use. Dynamic arrays double in capacity as needed, so + elements are copied an average of once. Hash tables double/halve + their size as needed, with appropriate hysteresis to maintain O(1) + performance. + +NOTES - DYNAMIC ARRAY + + * If you know how long a dynamic array is going to be in advance, you can avoid + extra memory allocations by using arrsetlen to allocate it to that length in + advance and use foo[n] while filling it out, or arrsetcap to allocate the memory + for that length and use arrput/arrpush as normal. + + * Unlike some other versions of the dynamic array, this version should + be safe to use with strict-aliasing optimizations. + +NOTES - HASH MAP + + * For compilers other than GCC and clang (e.g. Visual Studio), for hmput/hmget/hmdel + and variants, the key must be an lvalue (so the macro can take the address of it). + Extensions are used that eliminate this requirement if you're using C99 and later + in GCC or clang, or if you're using C++ in GCC. But note that this can make your + code less portable. + + * To test for presence of a key in a hashmap, just do 'hmgeti(foo,key) >= 0'. + + * The iteration order of your data in the hashmap is determined solely by the + order of insertions and deletions. In particular, if you never delete, new + keys are always added at the end of the array. This will be consistent + across all platforms and versions of the library. However, you should not + attempt to serialize the internal hash table, as the hash is not consistent + between different platforms, and may change with future versions of the library. + + * Use sh_new_arena() for string hashmaps that you never delete from. Initialize + with NULL if you're managing the memory for your strings, or your strings are + never freed (at least until the hashmap is freed). Otherwise, use sh_new_strdup(). + @TODO: make an arena variant that garbage collects the strings with a trivial + copy collector into a new arena whenever the table shrinks / rebuilds. Since + current arena recommendation is to only use arena if it never deletes, then + this can just replace current arena implementation. + + * If adversarial input is a serious concern and you're on a 64-bit platform, + enable STBDS_SIPHASH_2_4 (see the 'Compile-time options' section), and pass + a strong random number to stbds_rand_seed. + + * The default value for the hash table is stored in foo[-1], so if you + use code like 'hmget(T,k)->value = 5' you can accidentally overwrite + the value stored by hmdefault if 'k' is not present. + +CREDITS + + Sean Barrett -- library, idea for dynamic array API/implementation + Per Vognsen -- idea for hash table API/implementation + Rafael Sachetto -- arrpop() + github:HeroicKatora -- arraddn() reworking + + Bugfixes: + Andy Durdin + Shane Liesegang + Vinh Truong + Andreas Molzer + github:hashitaku + github:srdjanstipic + Macoy Madson + Andreas Vennstrom + Tobias Mansfield-Williams +*/ + +#ifdef STBDS_UNIT_TESTS +#define _CRT_SECURE_NO_WARNINGS +#endif + +#ifndef INCLUDE_STB_DS_H +#define INCLUDE_STB_DS_H + +#include +#include + +#ifndef STBDS_NO_SHORT_NAMES +#define arrlen stbds_arrlen +#define arrlenu stbds_arrlenu +#define arrput stbds_arrput +#define arrpush stbds_arrput +#define arrpop stbds_arrpop +#define arrfree stbds_arrfree +#define arraddn stbds_arraddn // deprecated, use one of the following instead: +#define arraddnptr stbds_arraddnptr +#define arraddnindex stbds_arraddnindex +#define arrsetlen stbds_arrsetlen +#define arrlast stbds_arrlast +#define arrins stbds_arrins +#define arrinsn stbds_arrinsn +#define arrdel stbds_arrdel +#define arrdeln stbds_arrdeln +#define arrdelswap stbds_arrdelswap +#define arrcap stbds_arrcap +#define arrsetcap stbds_arrsetcap + +#define hmput stbds_hmput +#define hmputs stbds_hmputs +#define hmget stbds_hmget +#define hmget_ts stbds_hmget_ts +#define hmgets stbds_hmgets +#define hmgetp stbds_hmgetp +#define hmgetp_ts stbds_hmgetp_ts +#define hmgetp_null stbds_hmgetp_null +#define hmgeti stbds_hmgeti +#define hmgeti_ts stbds_hmgeti_ts +#define hmdel stbds_hmdel +#define hmlen stbds_hmlen +#define hmlenu stbds_hmlenu +#define hmfree stbds_hmfree +#define hmdefault stbds_hmdefault +#define hmdefaults stbds_hmdefaults + +#define shput stbds_shput +#define shputi stbds_shputi +#define shputs stbds_shputs +#define shget stbds_shget +#define shgeti stbds_shgeti +#define shgets stbds_shgets +#define shgetp stbds_shgetp +#define shgetp_null stbds_shgetp_null +#define shdel stbds_shdel +#define shlen stbds_shlen +#define shlenu stbds_shlenu +#define shfree stbds_shfree +#define shdefault stbds_shdefault +#define shdefaults stbds_shdefaults +#define sh_new_arena stbds_sh_new_arena +#define sh_new_strdup stbds_sh_new_strdup + +#define stralloc stbds_stralloc +#define strreset stbds_strreset +#endif + +#if defined(STBDS_REALLOC) && !defined(STBDS_FREE) || !defined(STBDS_REALLOC) && defined(STBDS_FREE) +#error "You must define both STBDS_REALLOC and STBDS_FREE, or neither." +#endif +#if !defined(STBDS_REALLOC) && !defined(STBDS_FREE) +#include +#define STBDS_REALLOC(c,p,s) realloc(p,s) +#define STBDS_FREE(c,p) free(p) +#endif + +#ifdef _MSC_VER +#define STBDS_NOTUSED(v) (void)(v) +#else +#define STBDS_NOTUSED(v) (void)sizeof(v) +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +// for security against attackers, seed the library with a random number, at least time() but stronger is better +extern void stbds_rand_seed(size_t seed); + +// these are the hash functions used internally if you want to test them or use them for other purposes +extern size_t stbds_hash_bytes(void *p, size_t len, size_t seed); +extern size_t stbds_hash_string(char *str, size_t seed); + +// this is a simple string arena allocator, initialize with e.g. 'stbds_string_arena my_arena={0}'. +typedef struct stbds_string_arena stbds_string_arena; +extern char * stbds_stralloc(stbds_string_arena *a, char *str); +extern void stbds_strreset(stbds_string_arena *a); + +// have to #define STBDS_UNIT_TESTS to call this +extern void stbds_unit_tests(void); + +/////////////// +// +// Everything below here is implementation details +// + +extern void * stbds_arrgrowf(void *a, size_t elemsize, size_t addlen, size_t min_cap); +extern void stbds_arrfreef(void *a); +extern void stbds_hmfree_func(void *p, size_t elemsize); +extern void * stbds_hmget_key(void *a, size_t elemsize, void *key, size_t keysize, int mode); +extern void * stbds_hmget_key_ts(void *a, size_t elemsize, void *key, size_t keysize, ptrdiff_t *temp, int mode); +extern void * stbds_hmput_default(void *a, size_t elemsize); +extern void * stbds_hmput_key(void *a, size_t elemsize, void *key, size_t keysize, int mode); +extern void * stbds_hmdel_key(void *a, size_t elemsize, void *key, size_t keysize, size_t keyoffset, int mode); +extern void * stbds_shmode_func(size_t elemsize, int mode); + +#ifdef __cplusplus +} +#endif + +#if defined(__GNUC__) || defined(__clang__) +#define STBDS_HAS_TYPEOF +#ifdef __cplusplus +//#define STBDS_HAS_LITERAL_ARRAY // this is currently broken for clang +#endif +#endif + +#if !defined(__cplusplus) +#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L +#define STBDS_HAS_LITERAL_ARRAY +#endif +#endif + +// this macro takes the address of the argument, but on gcc/clang can accept rvalues +#if defined(STBDS_HAS_LITERAL_ARRAY) && defined(STBDS_HAS_TYPEOF) + #if __clang__ + #define STBDS_ADDRESSOF(typevar, value) ((__typeof__(typevar)[1]){value}) // literal array decays to pointer to value + #else + #define STBDS_ADDRESSOF(typevar, value) ((typeof(typevar)[1]){value}) // literal array decays to pointer to value + #endif +#else +#define STBDS_ADDRESSOF(typevar, value) &(value) +#endif + +#define STBDS_OFFSETOF(var,field) ((char *) &(var)->field - (char *) (var)) + +#define stbds_header(t) ((stbds_array_header *) (t) - 1) +#define stbds_temp(t) stbds_header(t)->temp +#define stbds_temp_key(t) (*(char **) stbds_header(t)->hash_table) + +#define stbds_arrsetcap(a,n) (stbds_arrgrow(a,0,n)) +#define stbds_arrsetlen(a,n) ((stbds_arrcap(a) < (size_t) (n) ? stbds_arrsetcap((a),(size_t)(n)),0 : 0), (a) ? stbds_header(a)->length = (size_t) (n) : 0) +#define stbds_arrcap(a) ((a) ? stbds_header(a)->capacity : 0) +#define stbds_arrlen(a) ((a) ? (ptrdiff_t) stbds_header(a)->length : 0) +#define stbds_arrlenu(a) ((a) ? stbds_header(a)->length : 0) +#define stbds_arrput(a,v) (stbds_arrmaybegrow(a,1), (a)[stbds_header(a)->length++] = (v)) +#define stbds_arrpush stbds_arrput // synonym +#define stbds_arrpop(a) (stbds_header(a)->length--, (a)[stbds_header(a)->length]) +#define stbds_arraddn(a,n) ((void)(stbds_arraddnindex(a, n))) // deprecated, use one of the following instead: +#define stbds_arraddnptr(a,n) (stbds_arrmaybegrow(a,n), (n) ? (stbds_header(a)->length += (n), &(a)[stbds_header(a)->length-(n)]) : (a)) +#define stbds_arraddnindex(a,n)(stbds_arrmaybegrow(a,n), (n) ? (stbds_header(a)->length += (n), stbds_header(a)->length-(n)) : stbds_arrlen(a)) +#define stbds_arraddnoff stbds_arraddnindex +#define stbds_arrlast(a) ((a)[stbds_header(a)->length-1]) +#define stbds_arrfree(a) ((void) ((a) ? STBDS_FREE(NULL,stbds_header(a)) : (void)0), (a)=NULL) +#define stbds_arrdel(a,i) stbds_arrdeln(a,i,1) +#define stbds_arrdeln(a,i,n) (memmove(&(a)[i], &(a)[(i)+(n)], sizeof *(a) * (stbds_header(a)->length-(n)-(i))), stbds_header(a)->length -= (n)) +#define stbds_arrdelswap(a,i) ((a)[i] = stbds_arrlast(a), stbds_header(a)->length -= 1) +#define stbds_arrinsn(a,i,n) (stbds_arraddn((a),(n)), memmove(&(a)[(i)+(n)], &(a)[i], sizeof *(a) * (stbds_header(a)->length-(n)-(i)))) +#define stbds_arrins(a,i,v) (stbds_arrinsn((a),(i),1), (a)[i]=(v)) + +#define stbds_arrmaybegrow(a,n) ((!(a) || stbds_header(a)->length + (n) > stbds_header(a)->capacity) \ + ? (stbds_arrgrow(a,n,0),0) : 0) + +#define stbds_arrgrow(a,b,c) ((a) = stbds_arrgrowf_wrapper((a), sizeof *(a), (b), (c))) + +#define stbds_hmput(t, k, v) \ + ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void*) STBDS_ADDRESSOF((t)->key, (k)), sizeof (t)->key, 0), \ + (t)[stbds_temp((t)-1)].key = (k), \ + (t)[stbds_temp((t)-1)].value = (v)) + +#define stbds_hmputs(t, s) \ + ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), &(s).key, sizeof (s).key, STBDS_HM_BINARY), \ + (t)[stbds_temp((t)-1)] = (s)) + +#define stbds_hmgeti(t,k) \ + ((t) = stbds_hmget_key_wrapper((t), sizeof *(t), (void*) STBDS_ADDRESSOF((t)->key, (k)), sizeof (t)->key, STBDS_HM_BINARY), \ + stbds_temp((t)-1)) + +#define stbds_hmgeti_ts(t,k,temp) \ + ((t) = stbds_hmget_key_ts_wrapper((t), sizeof *(t), (void*) STBDS_ADDRESSOF((t)->key, (k)), sizeof (t)->key, &(temp), STBDS_HM_BINARY), \ + (temp)) + +#define stbds_hmgetp(t, k) \ + ((void) stbds_hmgeti(t,k), &(t)[stbds_temp((t)-1)]) + +#define stbds_hmgetp_ts(t, k, temp) \ + ((void) stbds_hmgeti_ts(t,k,temp), &(t)[temp]) + +#define stbds_hmdel(t,k) \ + (((t) = stbds_hmdel_key_wrapper((t),sizeof *(t), (void*) STBDS_ADDRESSOF((t)->key, (k)), sizeof (t)->key, STBDS_OFFSETOF((t),key), STBDS_HM_BINARY)),(t)?stbds_temp((t)-1):0) + +#define stbds_hmdefault(t, v) \ + ((t) = stbds_hmput_default_wrapper((t), sizeof *(t)), (t)[-1].value = (v)) + +#define stbds_hmdefaults(t, s) \ + ((t) = stbds_hmput_default_wrapper((t), sizeof *(t)), (t)[-1] = (s)) + +#define stbds_hmfree(p) \ + ((void) ((p) != NULL ? stbds_hmfree_func((p)-1,sizeof*(p)),0 : 0),(p)=NULL) + +#define stbds_hmgets(t, k) (*stbds_hmgetp(t,k)) +#define stbds_hmget(t, k) (stbds_hmgetp(t,k)->value) +#define stbds_hmget_ts(t, k, temp) (stbds_hmgetp_ts(t,k,temp)->value) +#define stbds_hmlen(t) ((t) ? (ptrdiff_t) stbds_header((t)-1)->length-1 : 0) +#define stbds_hmlenu(t) ((t) ? stbds_header((t)-1)->length-1 : 0) +#define stbds_hmgetp_null(t,k) (stbds_hmgeti(t,k) == -1 ? NULL : &(t)[stbds_temp((t)-1)]) + +#define stbds_shput(t, k, v) \ + ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void*) (k), sizeof (t)->key, STBDS_HM_STRING), \ + (t)[stbds_temp((t)-1)].value = (v)) + +#define stbds_shputi(t, k, v) \ + ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void*) (k), sizeof (t)->key, STBDS_HM_STRING), \ + (t)[stbds_temp((t)-1)].value = (v), stbds_temp((t)-1)) + +#define stbds_shputs(t, s) \ + ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void*) (s).key, sizeof (s).key, STBDS_HM_STRING), \ + (t)[stbds_temp((t)-1)] = (s), \ + (t)[stbds_temp((t)-1)].key = stbds_temp_key((t)-1)) // above line overwrites whole structure, so must rewrite key here if it was allocated internally + +#define stbds_pshput(t, p) \ + ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void*) (p)->key, sizeof (p)->key, STBDS_HM_PTR_TO_STRING), \ + (t)[stbds_temp((t)-1)] = (p)) + +#define stbds_shgeti(t,k) \ + ((t) = stbds_hmget_key_wrapper((t), sizeof *(t), (void*) (k), sizeof (t)->key, STBDS_HM_STRING), \ + stbds_temp((t)-1)) + +#define stbds_pshgeti(t,k) \ + ((t) = stbds_hmget_key_wrapper((t), sizeof *(t), (void*) (k), sizeof (*(t))->key, STBDS_HM_PTR_TO_STRING), \ + stbds_temp((t)-1)) + +#define stbds_shgetp(t, k) \ + ((void) stbds_shgeti(t,k), &(t)[stbds_temp((t)-1)]) + +#define stbds_pshget(t, k) \ + ((void) stbds_pshgeti(t,k), (t)[stbds_temp((t)-1)]) + +#define stbds_shdel(t,k) \ + (((t) = stbds_hmdel_key_wrapper((t),sizeof *(t), (void*) (k), sizeof (t)->key, STBDS_OFFSETOF((t),key), STBDS_HM_STRING)),(t)?stbds_temp((t)-1):0) +#define stbds_pshdel(t,k) \ + (((t) = stbds_hmdel_key_wrapper((t),sizeof *(t), (void*) (k), sizeof (*(t))->key, STBDS_OFFSETOF(*(t),key), STBDS_HM_PTR_TO_STRING)),(t)?stbds_temp((t)-1):0) + +#define stbds_sh_new_arena(t) \ + ((t) = stbds_shmode_func_wrapper(t, sizeof *(t), STBDS_SH_ARENA)) +#define stbds_sh_new_strdup(t) \ + ((t) = stbds_shmode_func_wrapper(t, sizeof *(t), STBDS_SH_STRDUP)) + +#define stbds_shdefault(t, v) stbds_hmdefault(t,v) +#define stbds_shdefaults(t, s) stbds_hmdefaults(t,s) + +#define stbds_shfree stbds_hmfree +#define stbds_shlenu stbds_hmlenu + +#define stbds_shgets(t, k) (*stbds_shgetp(t,k)) +#define stbds_shget(t, k) (stbds_shgetp(t,k)->value) +#define stbds_shgetp_null(t,k) (stbds_shgeti(t,k) == -1 ? NULL : &(t)[stbds_temp((t)-1)]) +#define stbds_shlen stbds_hmlen + +typedef struct +{ + size_t length; + size_t capacity; + void * hash_table; + ptrdiff_t temp; +} stbds_array_header; + +typedef struct stbds_string_block +{ + struct stbds_string_block *next; + char storage[8]; +} stbds_string_block; + +struct stbds_string_arena +{ + stbds_string_block *storage; + size_t remaining; + unsigned char block; + unsigned char mode; // this isn't used by the string arena itself +}; + +#define STBDS_HM_BINARY 0 +#define STBDS_HM_STRING 1 + +enum +{ + STBDS_SH_NONE, + STBDS_SH_DEFAULT, + STBDS_SH_STRDUP, + STBDS_SH_ARENA +}; + +#ifdef __cplusplus +// in C we use implicit assignment from these void*-returning functions to T*. +// in C++ these templates make the same code work +template static T * stbds_arrgrowf_wrapper(T *a, size_t elemsize, size_t addlen, size_t min_cap) { + return (T*)stbds_arrgrowf((void *)a, elemsize, addlen, min_cap); +} +template static T * stbds_hmget_key_wrapper(T *a, size_t elemsize, void *key, size_t keysize, int mode) { + return (T*)stbds_hmget_key((void*)a, elemsize, key, keysize, mode); +} +template static T * stbds_hmget_key_ts_wrapper(T *a, size_t elemsize, void *key, size_t keysize, ptrdiff_t *temp, int mode) { + return (T*)stbds_hmget_key_ts((void*)a, elemsize, key, keysize, temp, mode); +} +template static T * stbds_hmput_default_wrapper(T *a, size_t elemsize) { + return (T*)stbds_hmput_default((void *)a, elemsize); +} +template static T * stbds_hmput_key_wrapper(T *a, size_t elemsize, void *key, size_t keysize, int mode) { + return (T*)stbds_hmput_key((void*)a, elemsize, key, keysize, mode); +} +template static T * stbds_hmdel_key_wrapper(T *a, size_t elemsize, void *key, size_t keysize, size_t keyoffset, int mode){ + return (T*)stbds_hmdel_key((void*)a, elemsize, key, keysize, keyoffset, mode); +} +template static T * stbds_shmode_func_wrapper(T *, size_t elemsize, int mode) { + return (T*)stbds_shmode_func(elemsize, mode); +} +#else +#define stbds_arrgrowf_wrapper stbds_arrgrowf +#define stbds_hmget_key_wrapper stbds_hmget_key +#define stbds_hmget_key_ts_wrapper stbds_hmget_key_ts +#define stbds_hmput_default_wrapper stbds_hmput_default +#define stbds_hmput_key_wrapper stbds_hmput_key +#define stbds_hmdel_key_wrapper stbds_hmdel_key +#define stbds_shmode_func_wrapper(t,e,m) stbds_shmode_func(e,m) +#endif + +#endif // INCLUDE_STB_DS_H + + +////////////////////////////////////////////////////////////////////////////// +// +// IMPLEMENTATION +// + +#ifdef STB_DS_IMPLEMENTATION +#include +#include + +#ifndef STBDS_ASSERT +#define STBDS_ASSERT_WAS_UNDEFINED +#define STBDS_ASSERT(x) ((void) 0) +#endif + +#ifdef STBDS_STATISTICS +#define STBDS_STATS(x) x +size_t stbds_array_grow; +size_t stbds_hash_grow; +size_t stbds_hash_shrink; +size_t stbds_hash_rebuild; +size_t stbds_hash_probes; +size_t stbds_hash_alloc; +size_t stbds_rehash_probes; +size_t stbds_rehash_items; +#else +#define STBDS_STATS(x) +#endif + +// +// stbds_arr implementation +// + +//int *prev_allocs[65536]; +//int num_prev; + +void *stbds_arrgrowf(void *a, size_t elemsize, size_t addlen, size_t min_cap) +{ + stbds_array_header temp={0}; // force debugging + void *b; + size_t min_len = stbds_arrlen(a) + addlen; + (void) sizeof(temp); + + // compute the minimum capacity needed + if (min_len > min_cap) + min_cap = min_len; + + if (min_cap <= stbds_arrcap(a)) + return a; + + // increase needed capacity to guarantee O(1) amortized + if (min_cap < 2 * stbds_arrcap(a)) + min_cap = 2 * stbds_arrcap(a); + else if (min_cap < 4) + min_cap = 4; + + //if (num_prev < 65536) if (a) prev_allocs[num_prev++] = (int *) ((char *) a+1); + //if (num_prev == 2201) + // num_prev = num_prev; + b = STBDS_REALLOC(NULL, (a) ? stbds_header(a) : 0, elemsize * min_cap + sizeof(stbds_array_header)); + //if (num_prev < 65536) prev_allocs[num_prev++] = (int *) (char *) b; + b = (char *) b + sizeof(stbds_array_header); + if (a == NULL) { + stbds_header(b)->length = 0; + stbds_header(b)->hash_table = 0; + stbds_header(b)->temp = 0; + } else { + STBDS_STATS(++stbds_array_grow); + } + stbds_header(b)->capacity = min_cap; + + return b; +} + +void stbds_arrfreef(void *a) +{ + STBDS_FREE(NULL, stbds_header(a)); +} + +// +// stbds_hm hash table implementation +// + +#ifdef STBDS_INTERNAL_SMALL_BUCKET +#define STBDS_BUCKET_LENGTH 4 +#else +#define STBDS_BUCKET_LENGTH 8 +#endif + +#define STBDS_BUCKET_SHIFT (STBDS_BUCKET_LENGTH == 8 ? 3 : 2) +#define STBDS_BUCKET_MASK (STBDS_BUCKET_LENGTH-1) +#define STBDS_CACHE_LINE_SIZE 64 + +#define STBDS_ALIGN_FWD(n,a) (((n) + (a) - 1) & ~((a)-1)) + +typedef struct +{ + size_t hash [STBDS_BUCKET_LENGTH]; + ptrdiff_t index[STBDS_BUCKET_LENGTH]; +} stbds_hash_bucket; // in 32-bit, this is one 64-byte cache line; in 64-bit, each array is one 64-byte cache line + +typedef struct +{ + char * temp_key; // this MUST be the first field of the hash table + size_t slot_count; + size_t used_count; + size_t used_count_threshold; + size_t used_count_shrink_threshold; + size_t tombstone_count; + size_t tombstone_count_threshold; + size_t seed; + size_t slot_count_log2; + stbds_string_arena string; + stbds_hash_bucket *storage; // not a separate allocation, just 64-byte aligned storage after this struct +} stbds_hash_index; + +#define STBDS_INDEX_EMPTY -1 +#define STBDS_INDEX_DELETED -2 +#define STBDS_INDEX_IN_USE(x) ((x) >= 0) + +#define STBDS_HASH_EMPTY 0 +#define STBDS_HASH_DELETED 1 + +static size_t stbds_hash_seed=0x31415926; + +void stbds_rand_seed(size_t seed) +{ + stbds_hash_seed = seed; +} + +#define stbds_load_32_or_64(var, temp, v32, v64_hi, v64_lo) \ + temp = v64_lo ^ v32, temp <<= 16, temp <<= 16, temp >>= 16, temp >>= 16, /* discard if 32-bit */ \ + var = v64_hi, var <<= 16, var <<= 16, /* discard if 32-bit */ \ + var ^= temp ^ v32 + +#define STBDS_SIZE_T_BITS ((sizeof (size_t)) * 8) + +static size_t stbds_probe_position(size_t hash, size_t slot_count, size_t slot_log2) +{ + size_t pos; + STBDS_NOTUSED(slot_log2); + pos = hash & (slot_count-1); + #ifdef STBDS_INTERNAL_BUCKET_START + pos &= ~STBDS_BUCKET_MASK; + #endif + return pos; +} + +static size_t stbds_log2(size_t slot_count) +{ + size_t n=0; + while (slot_count > 1) { + slot_count >>= 1; + ++n; + } + return n; +} + +static stbds_hash_index *stbds_make_hash_index(size_t slot_count, stbds_hash_index *ot) +{ + stbds_hash_index *t; + t = (stbds_hash_index *) STBDS_REALLOC(NULL,0,(slot_count >> STBDS_BUCKET_SHIFT) * sizeof(stbds_hash_bucket) + sizeof(stbds_hash_index) + STBDS_CACHE_LINE_SIZE-1); + t->storage = (stbds_hash_bucket *) STBDS_ALIGN_FWD((size_t) (t+1), STBDS_CACHE_LINE_SIZE); + t->slot_count = slot_count; + t->slot_count_log2 = stbds_log2(slot_count); + t->tombstone_count = 0; + t->used_count = 0; + + #if 0 // A1 + t->used_count_threshold = slot_count*12/16; // if 12/16th of table is occupied, grow + t->tombstone_count_threshold = slot_count* 2/16; // if tombstones are 2/16th of table, rebuild + t->used_count_shrink_threshold = slot_count* 4/16; // if table is only 4/16th full, shrink + #elif 1 // A2 + //t->used_count_threshold = slot_count*12/16; // if 12/16th of table is occupied, grow + //t->tombstone_count_threshold = slot_count* 3/16; // if tombstones are 3/16th of table, rebuild + //t->used_count_shrink_threshold = slot_count* 4/16; // if table is only 4/16th full, shrink + + // compute without overflowing + t->used_count_threshold = slot_count - (slot_count>>2); + t->tombstone_count_threshold = (slot_count>>3) + (slot_count>>4); + t->used_count_shrink_threshold = slot_count >> 2; + + #elif 0 // B1 + t->used_count_threshold = slot_count*13/16; // if 13/16th of table is occupied, grow + t->tombstone_count_threshold = slot_count* 2/16; // if tombstones are 2/16th of table, rebuild + t->used_count_shrink_threshold = slot_count* 5/16; // if table is only 5/16th full, shrink + #else // C1 + t->used_count_threshold = slot_count*14/16; // if 14/16th of table is occupied, grow + t->tombstone_count_threshold = slot_count* 2/16; // if tombstones are 2/16th of table, rebuild + t->used_count_shrink_threshold = slot_count* 6/16; // if table is only 6/16th full, shrink + #endif + // Following statistics were measured on a Core i7-6700 @ 4.00Ghz, compiled with clang 7.0.1 -O2 + // Note that the larger tables have high variance as they were run fewer times + // A1 A2 B1 C1 + // 0.10ms : 0.10ms : 0.10ms : 0.11ms : 2,000 inserts creating 2K table + // 0.96ms : 0.95ms : 0.97ms : 1.04ms : 20,000 inserts creating 20K table + // 14.48ms : 14.46ms : 10.63ms : 11.00ms : 200,000 inserts creating 200K table + // 195.74ms : 196.35ms : 203.69ms : 214.92ms : 2,000,000 inserts creating 2M table + // 2193.88ms : 2209.22ms : 2285.54ms : 2437.17ms : 20,000,000 inserts creating 20M table + // 65.27ms : 53.77ms : 65.33ms : 65.47ms : 500,000 inserts & deletes in 2K table + // 72.78ms : 62.45ms : 71.95ms : 72.85ms : 500,000 inserts & deletes in 20K table + // 89.47ms : 77.72ms : 96.49ms : 96.75ms : 500,000 inserts & deletes in 200K table + // 97.58ms : 98.14ms : 97.18ms : 97.53ms : 500,000 inserts & deletes in 2M table + // 118.61ms : 119.62ms : 120.16ms : 118.86ms : 500,000 inserts & deletes in 20M table + // 192.11ms : 194.39ms : 196.38ms : 195.73ms : 500,000 inserts & deletes in 200M table + + if (slot_count <= STBDS_BUCKET_LENGTH) + t->used_count_shrink_threshold = 0; + // to avoid infinite loop, we need to guarantee that at least one slot is empty and will terminate probes + STBDS_ASSERT(t->used_count_threshold + t->tombstone_count_threshold < t->slot_count); + STBDS_STATS(++stbds_hash_alloc); + if (ot) { + t->string = ot->string; + // reuse old seed so we can reuse old hashes so below "copy out old data" doesn't do any hashing + t->seed = ot->seed; + } else { + size_t a,b,temp; + memset(&t->string, 0, sizeof(t->string)); + t->seed = stbds_hash_seed; + // LCG + // in 32-bit, a = 2147001325 b = 715136305 + // in 64-bit, a = 2862933555777941757 b = 3037000493 + stbds_load_32_or_64(a,temp, 2147001325, 0x27bb2ee6, 0x87b0b0fd); + stbds_load_32_or_64(b,temp, 715136305, 0, 0xb504f32d); + stbds_hash_seed = stbds_hash_seed * a + b; + } + + { + size_t i,j; + for (i=0; i < slot_count >> STBDS_BUCKET_SHIFT; ++i) { + stbds_hash_bucket *b = &t->storage[i]; + for (j=0; j < STBDS_BUCKET_LENGTH; ++j) + b->hash[j] = STBDS_HASH_EMPTY; + for (j=0; j < STBDS_BUCKET_LENGTH; ++j) + b->index[j] = STBDS_INDEX_EMPTY; + } + } + + // copy out the old data, if any + if (ot) { + size_t i,j; + t->used_count = ot->used_count; + for (i=0; i < ot->slot_count >> STBDS_BUCKET_SHIFT; ++i) { + stbds_hash_bucket *ob = &ot->storage[i]; + for (j=0; j < STBDS_BUCKET_LENGTH; ++j) { + if (STBDS_INDEX_IN_USE(ob->index[j])) { + size_t hash = ob->hash[j]; + size_t pos = stbds_probe_position(hash, t->slot_count, t->slot_count_log2); + size_t step = STBDS_BUCKET_LENGTH; + STBDS_STATS(++stbds_rehash_items); + for (;;) { + size_t limit,z; + stbds_hash_bucket *bucket; + bucket = &t->storage[pos >> STBDS_BUCKET_SHIFT]; + STBDS_STATS(++stbds_rehash_probes); + + for (z=pos & STBDS_BUCKET_MASK; z < STBDS_BUCKET_LENGTH; ++z) { + if (bucket->hash[z] == 0) { + bucket->hash[z] = hash; + bucket->index[z] = ob->index[j]; + goto done; + } + } + + limit = pos & STBDS_BUCKET_MASK; + for (z = 0; z < limit; ++z) { + if (bucket->hash[z] == 0) { + bucket->hash[z] = hash; + bucket->index[z] = ob->index[j]; + goto done; + } + } + + pos += step; // quadratic probing + step += STBDS_BUCKET_LENGTH; + pos &= (t->slot_count-1); + } + } + done: + ; + } + } + } + + return t; +} + +#define STBDS_ROTATE_LEFT(val, n) (((val) << (n)) | ((val) >> (STBDS_SIZE_T_BITS - (n)))) +#define STBDS_ROTATE_RIGHT(val, n) (((val) >> (n)) | ((val) << (STBDS_SIZE_T_BITS - (n)))) + +size_t stbds_hash_string(char *str, size_t seed) +{ + size_t hash = seed; + while (*str) + hash = STBDS_ROTATE_LEFT(hash, 9) + (unsigned char) *str++; + + // Thomas Wang 64-to-32 bit mix function, hopefully also works in 32 bits + hash ^= seed; + hash = (~hash) + (hash << 18); + hash ^= hash ^ STBDS_ROTATE_RIGHT(hash,31); + hash = hash * 21; + hash ^= hash ^ STBDS_ROTATE_RIGHT(hash,11); + hash += (hash << 6); + hash ^= STBDS_ROTATE_RIGHT(hash,22); + return hash+seed; +} + +#ifdef STBDS_SIPHASH_2_4 +#define STBDS_SIPHASH_C_ROUNDS 2 +#define STBDS_SIPHASH_D_ROUNDS 4 +typedef int STBDS_SIPHASH_2_4_can_only_be_used_in_64_bit_builds[sizeof(size_t) == 8 ? 1 : -1]; +#endif + +#ifndef STBDS_SIPHASH_C_ROUNDS +#define STBDS_SIPHASH_C_ROUNDS 1 +#endif +#ifndef STBDS_SIPHASH_D_ROUNDS +#define STBDS_SIPHASH_D_ROUNDS 1 +#endif + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable:4127) // conditional expression is constant, for do..while(0) and sizeof()== +#endif + +static size_t stbds_siphash_bytes(void *p, size_t len, size_t seed) +{ + unsigned char *d = (unsigned char *) p; + size_t i,j; + size_t v0,v1,v2,v3, data; + + // hash that works on 32- or 64-bit registers without knowing which we have + // (computes different results on 32-bit and 64-bit platform) + // derived from siphash, but on 32-bit platforms very different as it uses 4 32-bit state not 4 64-bit + v0 = ((((size_t) 0x736f6d65 << 16) << 16) + 0x70736575) ^ seed; + v1 = ((((size_t) 0x646f7261 << 16) << 16) + 0x6e646f6d) ^ ~seed; + v2 = ((((size_t) 0x6c796765 << 16) << 16) + 0x6e657261) ^ seed; + v3 = ((((size_t) 0x74656462 << 16) << 16) + 0x79746573) ^ ~seed; + + #ifdef STBDS_TEST_SIPHASH_2_4 + // hardcoded with key material in the siphash test vectors + v0 ^= 0x0706050403020100ull ^ seed; + v1 ^= 0x0f0e0d0c0b0a0908ull ^ ~seed; + v2 ^= 0x0706050403020100ull ^ seed; + v3 ^= 0x0f0e0d0c0b0a0908ull ^ ~seed; + #endif + + #define STBDS_SIPROUND() \ + do { \ + v0 += v1; v1 = STBDS_ROTATE_LEFT(v1, 13); v1 ^= v0; v0 = STBDS_ROTATE_LEFT(v0,STBDS_SIZE_T_BITS/2); \ + v2 += v3; v3 = STBDS_ROTATE_LEFT(v3, 16); v3 ^= v2; \ + v2 += v1; v1 = STBDS_ROTATE_LEFT(v1, 17); v1 ^= v2; v2 = STBDS_ROTATE_LEFT(v2,STBDS_SIZE_T_BITS/2); \ + v0 += v3; v3 = STBDS_ROTATE_LEFT(v3, 21); v3 ^= v0; \ + } while (0) + + for (i=0; i+sizeof(size_t) <= len; i += sizeof(size_t), d += sizeof(size_t)) { + data = d[0] | (d[1] << 8) | (d[2] << 16) | (d[3] << 24); + data |= (size_t) (d[4] | (d[5] << 8) | (d[6] << 16) | (d[7] << 24)) << 16 << 16; // discarded if size_t == 4 + + v3 ^= data; + for (j=0; j < STBDS_SIPHASH_C_ROUNDS; ++j) + STBDS_SIPROUND(); + v0 ^= data; + } + data = len << (STBDS_SIZE_T_BITS-8); + switch (len - i) { + case 7: data |= ((size_t) d[6] << 24) << 24; // fall through + case 6: data |= ((size_t) d[5] << 20) << 20; // fall through + case 5: data |= ((size_t) d[4] << 16) << 16; // fall through + case 4: data |= (d[3] << 24); // fall through + case 3: data |= (d[2] << 16); // fall through + case 2: data |= (d[1] << 8); // fall through + case 1: data |= d[0]; // fall through + case 0: break; + } + v3 ^= data; + for (j=0; j < STBDS_SIPHASH_C_ROUNDS; ++j) + STBDS_SIPROUND(); + v0 ^= data; + v2 ^= 0xff; + for (j=0; j < STBDS_SIPHASH_D_ROUNDS; ++j) + STBDS_SIPROUND(); + +#ifdef STBDS_SIPHASH_2_4 + return v0^v1^v2^v3; +#else + return v1^v2^v3; // slightly stronger since v0^v3 in above cancels out final round operation? I tweeted at the authors of SipHash about this but they didn't reply +#endif +} + +size_t stbds_hash_bytes(void *p, size_t len, size_t seed) +{ +#ifdef STBDS_SIPHASH_2_4 + return stbds_siphash_bytes(p,len,seed); +#else + unsigned char *d = (unsigned char *) p; + + if (len == 4) { + unsigned int hash = d[0] | (d[1] << 8) | (d[2] << 16) | (d[3] << 24); + #if 0 + // HASH32-A Bob Jenkin's hash function w/o large constants + hash ^= seed; + hash -= (hash<<6); + hash ^= (hash>>17); + hash -= (hash<<9); + hash ^= seed; + hash ^= (hash<<4); + hash -= (hash<<3); + hash ^= (hash<<10); + hash ^= (hash>>15); + #elif 1 + // HASH32-BB Bob Jenkin's presumably-accidental version of Thomas Wang hash with rotates turned into shifts. + // Note that converting these back to rotates makes it run a lot slower, presumably due to collisions, so I'm + // not really sure what's going on. + hash ^= seed; + hash = (hash ^ 61) ^ (hash >> 16); + hash = hash + (hash << 3); + hash = hash ^ (hash >> 4); + hash = hash * 0x27d4eb2d; + hash ^= seed; + hash = hash ^ (hash >> 15); + #else // HASH32-C - Murmur3 + hash ^= seed; + hash *= 0xcc9e2d51; + hash = (hash << 17) | (hash >> 15); + hash *= 0x1b873593; + hash ^= seed; + hash = (hash << 19) | (hash >> 13); + hash = hash*5 + 0xe6546b64; + hash ^= hash >> 16; + hash *= 0x85ebca6b; + hash ^= seed; + hash ^= hash >> 13; + hash *= 0xc2b2ae35; + hash ^= hash >> 16; + #endif + // Following statistics were measured on a Core i7-6700 @ 4.00Ghz, compiled with clang 7.0.1 -O2 + // Note that the larger tables have high variance as they were run fewer times + // HASH32-A // HASH32-BB // HASH32-C + // 0.10ms // 0.10ms // 0.10ms : 2,000 inserts creating 2K table + // 0.96ms // 0.95ms // 0.99ms : 20,000 inserts creating 20K table + // 14.69ms // 14.43ms // 14.97ms : 200,000 inserts creating 200K table + // 199.99ms // 195.36ms // 202.05ms : 2,000,000 inserts creating 2M table + // 2234.84ms // 2187.74ms // 2240.38ms : 20,000,000 inserts creating 20M table + // 55.68ms // 53.72ms // 57.31ms : 500,000 inserts & deletes in 2K table + // 63.43ms // 61.99ms // 65.73ms : 500,000 inserts & deletes in 20K table + // 80.04ms // 77.96ms // 81.83ms : 500,000 inserts & deletes in 200K table + // 100.42ms // 97.40ms // 102.39ms : 500,000 inserts & deletes in 2M table + // 119.71ms // 120.59ms // 121.63ms : 500,000 inserts & deletes in 20M table + // 185.28ms // 195.15ms // 187.74ms : 500,000 inserts & deletes in 200M table + // 15.58ms // 14.79ms // 15.52ms : 200,000 inserts creating 200K table with varying key spacing + + return (((size_t) hash << 16 << 16) | hash) ^ seed; + } else if (len == 8 && sizeof(size_t) == 8) { + size_t hash = d[0] | (d[1] << 8) | (d[2] << 16) | (d[3] << 24); + hash |= (size_t) (d[4] | (d[5] << 8) | (d[6] << 16) | (d[7] << 24)) << 16 << 16; // avoid warning if size_t == 4 + hash ^= seed; + hash = (~hash) + (hash << 21); + hash ^= STBDS_ROTATE_RIGHT(hash,24); + hash *= 265; + hash ^= STBDS_ROTATE_RIGHT(hash,14); + hash ^= seed; + hash *= 21; + hash ^= STBDS_ROTATE_RIGHT(hash,28); + hash += (hash << 31); + hash = (~hash) + (hash << 18); + return hash; + } else { + return stbds_siphash_bytes(p,len,seed); + } +#endif +} +#ifdef _MSC_VER +#pragma warning(pop) +#endif + + +static int stbds_is_key_equal(void *a, size_t elemsize, void *key, size_t keysize, size_t keyoffset, int mode, size_t i) +{ + if (mode >= STBDS_HM_STRING) + return 0==strcmp((char *) key, * (char **) ((char *) a + elemsize*i + keyoffset)); + else + return 0==memcmp(key, (char *) a + elemsize*i + keyoffset, keysize); +} + +#define STBDS_HASH_TO_ARR(x,elemsize) ((char*) (x) - (elemsize)) +#define STBDS_ARR_TO_HASH(x,elemsize) ((char*) (x) + (elemsize)) + +#define stbds_hash_table(a) ((stbds_hash_index *) stbds_header(a)->hash_table) + +void stbds_hmfree_func(void *a, size_t elemsize) +{ + if (a == NULL) return; + if (stbds_hash_table(a) != NULL) { + if (stbds_hash_table(a)->string.mode == STBDS_SH_STRDUP) { + size_t i; + // skip 0th element, which is default + for (i=1; i < stbds_header(a)->length; ++i) + STBDS_FREE(NULL, *(char**) ((char *) a + elemsize*i)); + } + stbds_strreset(&stbds_hash_table(a)->string); + } + STBDS_FREE(NULL, stbds_header(a)->hash_table); + STBDS_FREE(NULL, stbds_header(a)); +} + +static ptrdiff_t stbds_hm_find_slot(void *a, size_t elemsize, void *key, size_t keysize, size_t keyoffset, int mode) +{ + void *raw_a = STBDS_HASH_TO_ARR(a,elemsize); + stbds_hash_index *table = stbds_hash_table(raw_a); + size_t hash = mode >= STBDS_HM_STRING ? stbds_hash_string((char*)key,table->seed) : stbds_hash_bytes(key, keysize,table->seed); + size_t step = STBDS_BUCKET_LENGTH; + size_t limit,i; + size_t pos; + stbds_hash_bucket *bucket; + + if (hash < 2) hash += 2; // stored hash values are forbidden from being 0, so we can detect empty slots + + pos = stbds_probe_position(hash, table->slot_count, table->slot_count_log2); + + for (;;) { + STBDS_STATS(++stbds_hash_probes); + bucket = &table->storage[pos >> STBDS_BUCKET_SHIFT]; + + // start searching from pos to end of bucket, this should help performance on small hash tables that fit in cache + for (i=pos & STBDS_BUCKET_MASK; i < STBDS_BUCKET_LENGTH; ++i) { + if (bucket->hash[i] == hash) { + if (stbds_is_key_equal(a, elemsize, key, keysize, keyoffset, mode, bucket->index[i])) { + return (pos & ~STBDS_BUCKET_MASK)+i; + } + } else if (bucket->hash[i] == STBDS_HASH_EMPTY) { + return -1; + } + } + + // search from beginning of bucket to pos + limit = pos & STBDS_BUCKET_MASK; + for (i = 0; i < limit; ++i) { + if (bucket->hash[i] == hash) { + if (stbds_is_key_equal(a, elemsize, key, keysize, keyoffset, mode, bucket->index[i])) { + return (pos & ~STBDS_BUCKET_MASK)+i; + } + } else if (bucket->hash[i] == STBDS_HASH_EMPTY) { + return -1; + } + } + + // quadratic probing + pos += step; + step += STBDS_BUCKET_LENGTH; + pos &= (table->slot_count-1); + } + /* NOTREACHED */ +} + +void * stbds_hmget_key_ts(void *a, size_t elemsize, void *key, size_t keysize, ptrdiff_t *temp, int mode) +{ + size_t keyoffset = 0; + if (a == NULL) { + // make it non-empty so we can return a temp + a = stbds_arrgrowf(0, elemsize, 0, 1); + stbds_header(a)->length += 1; + memset(a, 0, elemsize); + *temp = STBDS_INDEX_EMPTY; + // adjust a to point after the default element + return STBDS_ARR_TO_HASH(a,elemsize); + } else { + stbds_hash_index *table; + void *raw_a = STBDS_HASH_TO_ARR(a,elemsize); + // adjust a to point to the default element + table = (stbds_hash_index *) stbds_header(raw_a)->hash_table; + if (table == 0) { + *temp = -1; + } else { + ptrdiff_t slot = stbds_hm_find_slot(a, elemsize, key, keysize, keyoffset, mode); + if (slot < 0) { + *temp = STBDS_INDEX_EMPTY; + } else { + stbds_hash_bucket *b = &table->storage[slot >> STBDS_BUCKET_SHIFT]; + *temp = b->index[slot & STBDS_BUCKET_MASK]; + } + } + return a; + } +} + +void * stbds_hmget_key(void *a, size_t elemsize, void *key, size_t keysize, int mode) +{ + ptrdiff_t temp; + void *p = stbds_hmget_key_ts(a, elemsize, key, keysize, &temp, mode); + stbds_temp(STBDS_HASH_TO_ARR(p,elemsize)) = temp; + return p; +} + +void * stbds_hmput_default(void *a, size_t elemsize) +{ + // three cases: + // a is NULL <- allocate + // a has a hash table but no entries, because of shmode <- grow + // a has entries <- do nothing + if (a == NULL || stbds_header(STBDS_HASH_TO_ARR(a,elemsize))->length == 0) { + a = stbds_arrgrowf(a ? STBDS_HASH_TO_ARR(a,elemsize) : NULL, elemsize, 0, 1); + stbds_header(a)->length += 1; + memset(a, 0, elemsize); + a=STBDS_ARR_TO_HASH(a,elemsize); + } + return a; +} + +static char *stbds_strdup(char *str); + +void *stbds_hmput_key(void *a, size_t elemsize, void *key, size_t keysize, int mode) +{ + size_t keyoffset=0; + void *raw_a; + stbds_hash_index *table; + + if (a == NULL) { + a = stbds_arrgrowf(0, elemsize, 0, 1); + memset(a, 0, elemsize); + stbds_header(a)->length += 1; + // adjust a to point AFTER the default element + a = STBDS_ARR_TO_HASH(a,elemsize); + } + + // adjust a to point to the default element + raw_a = a; + a = STBDS_HASH_TO_ARR(a,elemsize); + + table = (stbds_hash_index *) stbds_header(a)->hash_table; + + if (table == NULL || table->used_count >= table->used_count_threshold) { + stbds_hash_index *nt; + size_t slot_count; + + slot_count = (table == NULL) ? STBDS_BUCKET_LENGTH : table->slot_count*2; + nt = stbds_make_hash_index(slot_count, table); + if (table) + STBDS_FREE(NULL, table); + else + nt->string.mode = mode >= STBDS_HM_STRING ? STBDS_SH_DEFAULT : 0; + stbds_header(a)->hash_table = table = nt; + STBDS_STATS(++stbds_hash_grow); + } + + // we iterate hash table explicitly because we want to track if we saw a tombstone + { + size_t hash = mode >= STBDS_HM_STRING ? stbds_hash_string((char*)key,table->seed) : stbds_hash_bytes(key, keysize,table->seed); + size_t step = STBDS_BUCKET_LENGTH; + size_t pos; + ptrdiff_t tombstone = -1; + stbds_hash_bucket *bucket; + + // stored hash values are forbidden from being 0, so we can detect empty slots to early out quickly + if (hash < 2) hash += 2; + + pos = stbds_probe_position(hash, table->slot_count, table->slot_count_log2); + + for (;;) { + size_t limit, i; + STBDS_STATS(++stbds_hash_probes); + bucket = &table->storage[pos >> STBDS_BUCKET_SHIFT]; + + // start searching from pos to end of bucket + for (i=pos & STBDS_BUCKET_MASK; i < STBDS_BUCKET_LENGTH; ++i) { + if (bucket->hash[i] == hash) { + if (stbds_is_key_equal(raw_a, elemsize, key, keysize, keyoffset, mode, bucket->index[i])) { + stbds_temp(a) = bucket->index[i]; + if (mode >= STBDS_HM_STRING) + stbds_temp_key(a) = * (char **) ((char *) raw_a + elemsize*bucket->index[i] + keyoffset); + return STBDS_ARR_TO_HASH(a,elemsize); + } + } else if (bucket->hash[i] == 0) { + pos = (pos & ~STBDS_BUCKET_MASK) + i; + goto found_empty_slot; + } else if (tombstone < 0) { + if (bucket->index[i] == STBDS_INDEX_DELETED) + tombstone = (ptrdiff_t) ((pos & ~STBDS_BUCKET_MASK) + i); + } + } + + // search from beginning of bucket to pos + limit = pos & STBDS_BUCKET_MASK; + for (i = 0; i < limit; ++i) { + if (bucket->hash[i] == hash) { + if (stbds_is_key_equal(raw_a, elemsize, key, keysize, keyoffset, mode, bucket->index[i])) { + stbds_temp(a) = bucket->index[i]; + return STBDS_ARR_TO_HASH(a,elemsize); + } + } else if (bucket->hash[i] == 0) { + pos = (pos & ~STBDS_BUCKET_MASK) + i; + goto found_empty_slot; + } else if (tombstone < 0) { + if (bucket->index[i] == STBDS_INDEX_DELETED) + tombstone = (ptrdiff_t) ((pos & ~STBDS_BUCKET_MASK) + i); + } + } + + // quadratic probing + pos += step; + step += STBDS_BUCKET_LENGTH; + pos &= (table->slot_count-1); + } + found_empty_slot: + if (tombstone >= 0) { + pos = tombstone; + --table->tombstone_count; + } + ++table->used_count; + + { + ptrdiff_t i = (ptrdiff_t) stbds_arrlen(a); + // we want to do stbds_arraddn(1), but we can't use the macros since we don't have something of the right type + if ((size_t) i+1 > stbds_arrcap(a)) + *(void **) &a = stbds_arrgrowf(a, elemsize, 1, 0); + raw_a = STBDS_ARR_TO_HASH(a,elemsize); + + STBDS_ASSERT((size_t) i+1 <= stbds_arrcap(a)); + stbds_header(a)->length = i+1; + bucket = &table->storage[pos >> STBDS_BUCKET_SHIFT]; + bucket->hash[pos & STBDS_BUCKET_MASK] = hash; + bucket->index[pos & STBDS_BUCKET_MASK] = i-1; + stbds_temp(a) = i-1; + + switch (table->string.mode) { + case STBDS_SH_STRDUP: stbds_temp_key(a) = *(char **) ((char *) a + elemsize*i) = stbds_strdup((char*) key); break; + case STBDS_SH_ARENA: stbds_temp_key(a) = *(char **) ((char *) a + elemsize*i) = stbds_stralloc(&table->string, (char*)key); break; + case STBDS_SH_DEFAULT: stbds_temp_key(a) = *(char **) ((char *) a + elemsize*i) = (char *) key; break; + default: memcpy((char *) a + elemsize*i, key, keysize); break; + } + } + return STBDS_ARR_TO_HASH(a,elemsize); + } +} + +void * stbds_shmode_func(size_t elemsize, int mode) +{ + void *a = stbds_arrgrowf(0, elemsize, 0, 1); + stbds_hash_index *h; + memset(a, 0, elemsize); + stbds_header(a)->length = 1; + stbds_header(a)->hash_table = h = (stbds_hash_index *) stbds_make_hash_index(STBDS_BUCKET_LENGTH, NULL); + h->string.mode = (unsigned char) mode; + return STBDS_ARR_TO_HASH(a,elemsize); +} + +void * stbds_hmdel_key(void *a, size_t elemsize, void *key, size_t keysize, size_t keyoffset, int mode) +{ + if (a == NULL) { + return 0; + } else { + stbds_hash_index *table; + void *raw_a = STBDS_HASH_TO_ARR(a,elemsize); + table = (stbds_hash_index *) stbds_header(raw_a)->hash_table; + stbds_temp(raw_a) = 0; + if (table == 0) { + return a; + } else { + ptrdiff_t slot; + slot = stbds_hm_find_slot(a, elemsize, key, keysize, keyoffset, mode); + if (slot < 0) + return a; + else { + stbds_hash_bucket *b = &table->storage[slot >> STBDS_BUCKET_SHIFT]; + int i = slot & STBDS_BUCKET_MASK; + ptrdiff_t old_index = b->index[i]; + ptrdiff_t final_index = (ptrdiff_t) stbds_arrlen(raw_a)-1-1; // minus one for the raw_a vs a, and minus one for 'last' + STBDS_ASSERT(slot < (ptrdiff_t) table->slot_count); + --table->used_count; + ++table->tombstone_count; + stbds_temp(raw_a) = 1; + STBDS_ASSERT(table->used_count >= 0); + //STBDS_ASSERT(table->tombstone_count < table->slot_count/4); + b->hash[i] = STBDS_HASH_DELETED; + b->index[i] = STBDS_INDEX_DELETED; + + if (mode == STBDS_HM_STRING && table->string.mode == STBDS_SH_STRDUP) + STBDS_FREE(NULL, *(char**) ((char *) a+elemsize*old_index)); + + // if indices are the same, memcpy is a no-op, but back-pointer-fixup will fail, so skip + if (old_index != final_index) { + // swap delete + memmove((char*) a + elemsize*old_index, (char*) a + elemsize*final_index, elemsize); + + // now find the slot for the last element + if (mode == STBDS_HM_STRING) + slot = stbds_hm_find_slot(a, elemsize, *(char**) ((char *) a+elemsize*old_index + keyoffset), keysize, keyoffset, mode); + else + slot = stbds_hm_find_slot(a, elemsize, (char* ) a+elemsize*old_index + keyoffset, keysize, keyoffset, mode); + STBDS_ASSERT(slot >= 0); + b = &table->storage[slot >> STBDS_BUCKET_SHIFT]; + i = slot & STBDS_BUCKET_MASK; + STBDS_ASSERT(b->index[i] == final_index); + b->index[i] = old_index; + } + stbds_header(raw_a)->length -= 1; + + if (table->used_count < table->used_count_shrink_threshold && table->slot_count > STBDS_BUCKET_LENGTH) { + stbds_header(raw_a)->hash_table = stbds_make_hash_index(table->slot_count>>1, table); + STBDS_FREE(NULL, table); + STBDS_STATS(++stbds_hash_shrink); + } else if (table->tombstone_count > table->tombstone_count_threshold) { + stbds_header(raw_a)->hash_table = stbds_make_hash_index(table->slot_count , table); + STBDS_FREE(NULL, table); + STBDS_STATS(++stbds_hash_rebuild); + } + + return a; + } + } + } + /* NOTREACHED */ +} + +static char *stbds_strdup(char *str) +{ + // to keep replaceable allocator simple, we don't want to use strdup. + // rolling our own also avoids problem of strdup vs _strdup + size_t len = strlen(str)+1; + char *p = (char*) STBDS_REALLOC(NULL, 0, len); + memmove(p, str, len); + return p; +} + +#ifndef STBDS_STRING_ARENA_BLOCKSIZE_MIN +#define STBDS_STRING_ARENA_BLOCKSIZE_MIN 512u +#endif +#ifndef STBDS_STRING_ARENA_BLOCKSIZE_MAX +#define STBDS_STRING_ARENA_BLOCKSIZE_MAX (1u<<20) +#endif + +char *stbds_stralloc(stbds_string_arena *a, char *str) +{ + char *p; + size_t len = strlen(str)+1; + if (len > a->remaining) { + // compute the next blocksize + size_t blocksize = a->block; + + // size is 512, 512, 1024, 1024, 2048, 2048, 4096, 4096, etc., so that + // there are log(SIZE) allocations to free when we destroy the table + blocksize = (size_t) (STBDS_STRING_ARENA_BLOCKSIZE_MIN) << (blocksize>>1); + + // if size is under 1M, advance to next blocktype + if (blocksize < (size_t)(STBDS_STRING_ARENA_BLOCKSIZE_MAX)) + ++a->block; + + if (len > blocksize) { + // if string is larger than blocksize, then just allocate the full size. + // note that we still advance string_block so block size will continue + // increasing, so e.g. if somebody only calls this with 1000-long strings, + // eventually the arena will start doubling and handling those as well + stbds_string_block *sb = (stbds_string_block *) STBDS_REALLOC(NULL, 0, sizeof(*sb)-8 + len); + memmove(sb->storage, str, len); + if (a->storage) { + // insert it after the first element, so that we don't waste the space there + sb->next = a->storage->next; + a->storage->next = sb; + } else { + sb->next = 0; + a->storage = sb; + a->remaining = 0; // this is redundant, but good for clarity + } + return sb->storage; + } else { + stbds_string_block *sb = (stbds_string_block *) STBDS_REALLOC(NULL, 0, sizeof(*sb)-8 + blocksize); + sb->next = a->storage; + a->storage = sb; + a->remaining = blocksize; + } + } + + STBDS_ASSERT(len <= a->remaining); + p = a->storage->storage + a->remaining - len; + a->remaining -= len; + memmove(p, str, len); + return p; +} + +void stbds_strreset(stbds_string_arena *a) +{ + stbds_string_block *x,*y; + x = a->storage; + while (x) { + y = x->next; + STBDS_FREE(NULL, x); + x = y; + } + memset(a, 0, sizeof(*a)); +} + +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// UNIT TESTS +// + +#ifdef STBDS_UNIT_TESTS +#include +#ifdef STBDS_ASSERT_WAS_UNDEFINED +#undef STBDS_ASSERT +#endif +#ifndef STBDS_ASSERT +#define STBDS_ASSERT assert +#include +#endif + +typedef struct { int key,b,c,d; } stbds_struct; +typedef struct { int key[2],b,c,d; } stbds_struct2; + +static char buffer[256]; +char *strkey(int n) +{ +#if defined(_WIN32) && defined(__STDC_WANT_SECURE_LIB__) + sprintf_s(buffer, sizeof(buffer), "test_%d", n); +#else + sprintf(buffer, "test_%d", n); +#endif + return buffer; +} + +void stbds_unit_tests(void) +{ +#if defined(_MSC_VER) && _MSC_VER <= 1200 && defined(__cplusplus) + // VC6 C++ doesn't like the template<> trick on unnamed structures, so do nothing! + STBDS_ASSERT(0); +#else + const int testsize = 100000; + const int testsize2 = testsize/20; + int *arr=NULL; + struct { int key; int value; } *intmap = NULL; + struct { char *key; int value; } *strmap = NULL, s; + struct { stbds_struct key; int value; } *map = NULL; + stbds_struct *map2 = NULL; + stbds_struct2 *map3 = NULL; + stbds_string_arena sa = { 0 }; + int key3[2] = { 1,2 }; + ptrdiff_t temp; + + int i,j; + + STBDS_ASSERT(arrlen(arr)==0); + for (i=0; i < 20000; i += 50) { + for (j=0; j < i; ++j) + arrpush(arr,j); + arrfree(arr); + } + + for (i=0; i < 4; ++i) { + arrpush(arr,1); arrpush(arr,2); arrpush(arr,3); arrpush(arr,4); + arrdel(arr,i); + arrfree(arr); + arrpush(arr,1); arrpush(arr,2); arrpush(arr,3); arrpush(arr,4); + arrdelswap(arr,i); + arrfree(arr); + } + + for (i=0; i < 5; ++i) { + arrpush(arr,1); arrpush(arr,2); arrpush(arr,3); arrpush(arr,4); + stbds_arrins(arr,i,5); + STBDS_ASSERT(arr[i] == 5); + if (i < 4) + STBDS_ASSERT(arr[4] == 4); + arrfree(arr); + } + + i = 1; + STBDS_ASSERT(hmgeti(intmap,i) == -1); + hmdefault(intmap, -2); + STBDS_ASSERT(hmgeti(intmap, i) == -1); + STBDS_ASSERT(hmget (intmap, i) == -2); + for (i=0; i < testsize; i+=2) + hmput(intmap, i, i*5); + for (i=0; i < testsize; i+=1) { + if (i & 1) STBDS_ASSERT(hmget(intmap, i) == -2 ); + else STBDS_ASSERT(hmget(intmap, i) == i*5); + if (i & 1) STBDS_ASSERT(hmget_ts(intmap, i, temp) == -2 ); + else STBDS_ASSERT(hmget_ts(intmap, i, temp) == i*5); + } + for (i=0; i < testsize; i+=2) + hmput(intmap, i, i*3); + for (i=0; i < testsize; i+=1) + if (i & 1) STBDS_ASSERT(hmget(intmap, i) == -2 ); + else STBDS_ASSERT(hmget(intmap, i) == i*3); + for (i=2; i < testsize; i+=4) + hmdel(intmap, i); // delete half the entries + for (i=0; i < testsize; i+=1) + if (i & 3) STBDS_ASSERT(hmget(intmap, i) == -2 ); + else STBDS_ASSERT(hmget(intmap, i) == i*3); + for (i=0; i < testsize; i+=1) + hmdel(intmap, i); // delete the rest of the entries + for (i=0; i < testsize; i+=1) + STBDS_ASSERT(hmget(intmap, i) == -2 ); + hmfree(intmap); + for (i=0; i < testsize; i+=2) + hmput(intmap, i, i*3); + hmfree(intmap); + + #if defined(__clang__) || defined(__GNUC__) + #ifndef __cplusplus + intmap = NULL; + hmput(intmap, 15, 7); + hmput(intmap, 11, 3); + hmput(intmap, 9, 5); + STBDS_ASSERT(hmget(intmap, 9) == 5); + STBDS_ASSERT(hmget(intmap, 11) == 3); + STBDS_ASSERT(hmget(intmap, 15) == 7); + #endif + #endif + + for (i=0; i < testsize; ++i) + stralloc(&sa, strkey(i)); + strreset(&sa); + + { + s.key = "a", s.value = 1; + shputs(strmap, s); + STBDS_ASSERT(*strmap[0].key == 'a'); + STBDS_ASSERT(strmap[0].key == s.key); + STBDS_ASSERT(strmap[0].value == s.value); + shfree(strmap); + } + + { + s.key = "a", s.value = 1; + sh_new_strdup(strmap); + shputs(strmap, s); + STBDS_ASSERT(*strmap[0].key == 'a'); + STBDS_ASSERT(strmap[0].key != s.key); + STBDS_ASSERT(strmap[0].value == s.value); + shfree(strmap); + } + + { + s.key = "a", s.value = 1; + sh_new_arena(strmap); + shputs(strmap, s); + STBDS_ASSERT(*strmap[0].key == 'a'); + STBDS_ASSERT(strmap[0].key != s.key); + STBDS_ASSERT(strmap[0].value == s.value); + shfree(strmap); + } + + for (j=0; j < 2; ++j) { + STBDS_ASSERT(shgeti(strmap,"foo") == -1); + if (j == 0) + sh_new_strdup(strmap); + else + sh_new_arena(strmap); + STBDS_ASSERT(shgeti(strmap,"foo") == -1); + shdefault(strmap, -2); + STBDS_ASSERT(shgeti(strmap,"foo") == -1); + for (i=0; i < testsize; i+=2) + shput(strmap, strkey(i), i*3); + for (i=0; i < testsize; i+=1) + if (i & 1) STBDS_ASSERT(shget(strmap, strkey(i)) == -2 ); + else STBDS_ASSERT(shget(strmap, strkey(i)) == i*3); + for (i=2; i < testsize; i+=4) + shdel(strmap, strkey(i)); // delete half the entries + for (i=0; i < testsize; i+=1) + if (i & 3) STBDS_ASSERT(shget(strmap, strkey(i)) == -2 ); + else STBDS_ASSERT(shget(strmap, strkey(i)) == i*3); + for (i=0; i < testsize; i+=1) + shdel(strmap, strkey(i)); // delete the rest of the entries + for (i=0; i < testsize; i+=1) + STBDS_ASSERT(shget(strmap, strkey(i)) == -2 ); + shfree(strmap); + } + + { + struct { char *key; char value; } *hash = NULL; + char name[4] = "jen"; + shput(hash, "bob" , 'h'); + shput(hash, "sally" , 'e'); + shput(hash, "fred" , 'l'); + shput(hash, "jen" , 'x'); + shput(hash, "doug" , 'o'); + + shput(hash, name , 'l'); + shfree(hash); + } + + for (i=0; i < testsize; i += 2) { + stbds_struct s = { i,i*2,i*3,i*4 }; + hmput(map, s, i*5); + } + + for (i=0; i < testsize; i += 1) { + stbds_struct s = { i,i*2,i*3 ,i*4 }; + stbds_struct t = { i,i*2,i*3+1,i*4 }; + if (i & 1) STBDS_ASSERT(hmget(map, s) == 0); + else STBDS_ASSERT(hmget(map, s) == i*5); + if (i & 1) STBDS_ASSERT(hmget_ts(map, s, temp) == 0); + else STBDS_ASSERT(hmget_ts(map, s, temp) == i*5); + //STBDS_ASSERT(hmget(map, t.key) == 0); + } + + for (i=0; i < testsize; i += 2) { + stbds_struct s = { i,i*2,i*3,i*4 }; + hmputs(map2, s); + } + hmfree(map); + + for (i=0; i < testsize; i += 1) { + stbds_struct s = { i,i*2,i*3,i*4 }; + stbds_struct t = { i,i*2,i*3+1,i*4 }; + if (i & 1) STBDS_ASSERT(hmgets(map2, s.key).d == 0); + else STBDS_ASSERT(hmgets(map2, s.key).d == i*4); + //STBDS_ASSERT(hmgetp(map2, t.key) == 0); + } + hmfree(map2); + + for (i=0; i < testsize; i += 2) { + stbds_struct2 s = { { i,i*2 }, i*3,i*4, i*5 }; + hmputs(map3, s); + } + for (i=0; i < testsize; i += 1) { + stbds_struct2 s = { { i,i*2}, i*3, i*4, i*5 }; + stbds_struct2 t = { { i,i*2}, i*3+1, i*4, i*5 }; + if (i & 1) STBDS_ASSERT(hmgets(map3, s.key).d == 0); + else STBDS_ASSERT(hmgets(map3, s.key).d == i*5); + //STBDS_ASSERT(hmgetp(map3, t.key) == 0); + } +#endif +} +#endif + + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2019 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/tools/prebuild.sh b/tools/prebuild.sh new file mode 100755 index 0000000..0532b37 --- /dev/null +++ b/tools/prebuild.sh @@ -0,0 +1,49 @@ +#!/bin/bash + +# +# JoeyDev +# Copyright (C) 2018-2023 Scott Duensing +# +# This software is provided 'as-is', without any express or implied +# warranty. In no event will the authors be held liable for any damages +# arising from the use of this software. +# +# Permission is granted to anyone to use this software for any purpose, +# including commercial applications, and to alter it and redistribute it +# freely, subject to the following restrictions: +# +# 1. The origin of this software must not be misrepresented; you must not +# claim that you wrote the original software. If you use this software +# in a product, an acknowledgment in the product documentation would be +# appreciated but is not required. +# 2. Altered source versions must be plainly marked as such, and must not be +# misrepresented as being the original software. +# 3. This notice may not be removed or altered from any source distribution. +# + + +ROOT=$1 + +pushd "${ROOT}/tools" || exit &> /dev/null + echo Generating UI Headers... + rm -f ../ui/generated/*.h &> /dev/null || true + # We do this from the tools folder so the variable names get "__ui_" prepended to them. + for E in glade png; do + for F in ../ui/*."${E}"; do + + I="${F}" # Input File. + R="${E}" # What extension to remove. + + GUARD="${E^^}"_$(basename -s ."${R}" "${I}")_H + GUARD=${GUARD^^} + HEADER=../ui/generated/"${E}"$(basename -s ."${R}" "${I}").h + echo " ${HEADER}" + echo "#ifndef ${GUARD}" > "${HEADER}" + # shellcheck disable=SC2129 + echo "#define ${GUARD}" >> "${HEADER}" + xxd -i "${I}" - >> "${HEADER}" + echo "#endif // ${GUARD}" >> "${HEADER}" + sed -i 's/unsigned char/char/' "${HEADER}" + done + done +popd || true &> /dev/null diff --git a/ui/JoeyDev.glade b/ui/JoeyDev.glade new file mode 100644 index 0000000..6e39c5d --- /dev/null +++ b/ui/JoeyDev.glade @@ -0,0 +1,80 @@ + + + + + + False + JoeyDev + False + + + + + True + False + both + + + True + False + About + About + True + dialog-information + + + + False + True + + + + + True + False + False + Project + Project + True + applications-engineering + + + + False + True + + + + + True + False + Vector + Vector + True + image-x-generic + + + + False + True + + + + + True + False + Quit + Quit + True + application-exit + + + + False + True + + + + + + diff --git a/ui/Logo.png b/ui/Logo.png new file mode 100644 index 0000000..9872eb8 --- /dev/null +++ b/ui/Logo.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:edc657d867830628b3bbf740715c3fe7e685ff026139f514314e3827d5c63721 +size 54603 diff --git a/ui/Vector.glade b/ui/Vector.glade new file mode 100644 index 0000000..1e71b91 --- /dev/null +++ b/ui/Vector.glade @@ -0,0 +1,367 @@ + + + + + + 100 + 100 + 1 + 10 + + + 100 + 50 + 1 + 10 + + + + *.png + *.jpg + *.bmp + *.jpeg + + + + False + Vector + 640 + 480 + + + + True + False + vertical + top + + + True + False + + + True + False + _File + True + + + True + False + + + True + False + _New + True + + + + + True + False + _Open + True + + + + + True + False + _Save + True + + + + + True + False + Save _As... + True + + + + + True + False + + + + + True + False + _Close + True + + + + + + + + + + True + False + _Edit + True + + + True + False + + + True + False + Cut + True + + + + + True + False + Copy + True + + + + + True + False + Paste + True + + + + + True + False + Delete + True + + + + + + + + + True + False + _View + True + + + True + False + + + True + False + Zoom 1x (Normal) + True + + + + + True + False + Zoom 2x + True + + + + + + + + + True + False + _Help + True + + + True + False + + + True + False + Vector Editor... + True + + + + + + + + + False + True + 0 + + + + + True + False + top + + + 320 + True + False + start + start + vertical + top + + + 320 + 200 + True + False + start + start + + + + False + False + 0 + + + + + + True + False + + + True + False + end + Image Opacity: + right + True + + + 0 + 0 + + + + + True + False + end + Trace Opacity: + right + True + + + 0 + 1 + + + + + True + True + True + adjustmentVectorMainImage + 0 + 0 + right + + + 1 + 0 + + + + + True + True + True + adjustmentVectorTraceImage + 0 + 0 + right + + + 1 + 1 + + + + + False + True + 1 + + + + + True + False + + + True + False + 10 + Trace Image: + True + + + False + True + 0 + + + + + True + False + False + imageFilter + Open Trace Image + + + False + True + 1 + + + + + False + True + 2 + + + + + False + True + 0 + + + + + + + + True + True + 1 + + + + + + +