From: Not Zed Date: Sat, 5 Nov 2022 03:03:26 +0000 (+1030) Subject: Initial import. X-Git-Url: https://code.zedzone.au/cvs?a=commitdiff_plain;h=refs%2Fheads%2Fmaster;p=csvedit Initial import. --- 251b5f763874315e3baf4ea2f0190c54ace5e632 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4bf5d9b --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +bin/ +config.make +/build/ +/dist/ +/.lib/ +/nbproject/private/ diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..94a9ed0 --- /dev/null +++ b/COPYING @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..d343f8c --- /dev/null +++ b/Makefile @@ -0,0 +1,44 @@ + +dist_VERSION=0.0 +dist_NAME=csvedit +dist_EXTRA=README \ + COPYING \ + build.xml \ + nbproject/build-impl.xml \ + nbproject/genfiles.properties \ + nbproject/project.properties \ + nbproject/project.xml + +include config.make + +java_MODULES=notzed.csvedit + +image_deps=bin/$(TARGET)/image + +all: + +include ./java.make + +image: $(image_deps) +image: $(patsubst src/notzed.csvedit/image.$(TARGET)/%,bin/$(TARGET)/image/%,$(wildcard src/notzed.csvedit/image.$(TARGET)/*)) + +ifeq ($(TARGET),linux-amd64) +jlink_MODPATH = $(JAVA_HOME)/jmods +bin/$(TARGET)/image/%: src/notzed.csvedit/image.$(TARGET)/% $(image_deps) + cp -a $< $@ +else +jlink_MODPATH = /usr/local/$(TARGET)/jdk-19.0.1/jmods +bin/$(TARGET)/image/%.bat: src/notzed.csvedit/image.$(TARGET)/%.bat $(image_deps) + sed -E 's/\r?$$/\r/' < $< > $@~ + mv $@~ $@ +endif + +.PHONY: image + +bin/$(TARGET)/image: $(java_jmoddir)/notzed.csvedit.jmod + $(JAVA_HOME)/bin/jlink \ + --module-path $(jlink_MODPATH):bin/$(TARGET)/jmods \ + --add-modules notzed.csvedit \ + --no-man-pages \ + --compress 2 \ + --output bin/$(TARGET)/image diff --git a/README b/README new file mode 100644 index 0000000..adf8b7c --- /dev/null +++ b/README @@ -0,0 +1,106 @@ + + +csvedit - A basic CSV editor. +----------------------------- + +usage +----- + +Click "Open" to open a file, holding the button down will show a list +of recent files. + +Drop one or more files onto the file list (left pane) to open them. +Any files matching those already opened will be replaced. + +Navigate cells with the cursor keys or the mouse. + +Press Enter or double click the mouse button to begin edting a cell. +Press Enter, Tab, or click elsewhere to save the changes. + +Press tab to move to the next cell, if in editing mode it will +continue to be in editing mode. Shift-tab can be used to move to the +previous cell. + +Press shift-enter to append a new row. The first cell on the row will +automatically be in edit mode. + +Cut/Copy/Paste/Clear all work as expected but only across whole rows. + +Click the "Save All" button to save all modified files to their +existing filenames. A backup file will be created of the old +"file.txt" named "file.txt~". + +Click the "Save As" to export the file to a new csv file. The +modified state and filename of the file is not changed. + +Click "Open Set" to open a set of files from a '.set' file (see +below). + +Click "Save Set" to save the list of currently opened files. Only +those files within the same directory (or lower) as the set file are +included in the '.set' file. + +features +-------- + + - Edit tab-separated CSV files. + - Multiple documents can be loaded/saved as one (a 'set'). + - cut/copy/paste of whole rows to other applications. + - Files can be loaded by dropping into the file list. + - Recent file list for files and sets. + +non-features +------------ + + - Undo. + - Column oriented operations. + +known bugs +---------- + + - If a cell is being edited clicking on another file tab will not + commit the edit. + - Shift-Tab for edit mode is not implemented. + + +csv file formats +---------------- + +All files must be in tab-separated format. + +No escaping or processing is performed on data entries. + +All files must contain a header line which defines the valid columns. + +All data rows with more columns than the header line will be +discarded on load. + + +sets +---- + +A set is a set of csv files that can be loaded as a group. Saving a +'set' just saves the list of filenames currently opened. + +The '.set' file lists all files in the group relative to the location +of the .set file. Data files must be in the same directory or a sub +directory of the location of the '.set' file. + +The .set file is encoded as a java properties file. + +license +------- + +Copyright (C) 2022 Michael Zucchi + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + See COPYING for details. diff --git a/build.xml b/build.xml new file mode 100644 index 0000000..5239280 --- /dev/null +++ b/build.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + Builds, tests, and runs the project csvedit. + + + diff --git a/config.make.in b/config.make.in new file mode 100644 index 0000000..9e9b0fc --- /dev/null +++ b/config.make.in @@ -0,0 +1,12 @@ + +TARGET ?= linux-amd64 + +JAVA_HOME ?= /usr/local/jdk + +JAVAMODPATH = +JAVACFLAGS += -source 19 + +JAVA ?= $(JAVA_HOME)/bin/java +JAVAC ?= $(JAVA_HOME)/bin/javac +JAR ?= $(JAVA_HOME)/bin/jar +JMOD ?= $(JAVA_HOME)/bin/jmod diff --git a/java.make b/java.make new file mode 100644 index 0000000..266b0ac --- /dev/null +++ b/java.make @@ -0,0 +1,425 @@ +# +# Copyright (C) 2019,2022 Michael Zucchi +# +# This is the copyright for java.make +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# + +# General purpose modular java makefile that supports native library +# compilation directly. Non-recrusve implementation. +# +# Uses metamake programming with some file conventions to implement +# auto-make-like features. + +# Define modules +# -------------- +# java_MODULES list of java modules to compile. The sources must +# exist in src//classes. Resource files are +# stored in src//classes. Source-code +# generators must exist in src//gen. Native +# libraries must exist in src//jni. + +# native_MODULES list of native-only "modules". + + +# Global variables + +# JAVA_HOME location of jdk. +# JAVAC java compiler to use. Default is 'javac' on the path. +# JAVACFLAGS javac flags applied to all invocations. +# JAR jar command. +# JARFLAGS jar flags +# JMOD jmod command. +# JMODFLAGS jmod flags. +# JAVAFLAGS java flags for run targets + +# Module specific variables + +# _JDEPMOD Lists modules which this one depends on. + +# _JAVACFLAGS Extra module-specific flags for each command. +# _JARFLAGS +# _JMODFLAGS + +# all paths are relative to the root package name + +# _JAVA Java sources. If not set it is found from src//classes/(*.java) +# _RESOURCES .jar resources. If not set it is found from src//classes/(not *.java) +# _JAVA_GENERATED Java generated sources. +# _RESOURCES_GENERATED Java generated sources. + +# Variables for use in fragments + +# gen.make and jni.make can additionally make use of these variables + +# _gendir Location for files used in Java generation process (per project). +# _genjavadir Location where _JAVA_GENERATED .java files will be created (per project). +# _objdir Location for c objects (per target). +# _incdir Location for output includes, .jmod staging. +# _libdir Location for output libraries, .jmod staging. May point to _bindir. +# _bindir Location for output commands, .jmod staging. + +# Define libraries +# ---------------- + +# Each module can define one or more native libraries. + +# These are compiled after the java sources have been compiled as that +# process also generates any native binding headers. + +# _NATIVE_LIBRARIES list of libraries to build. +# library names match System.loadLibrary(). + +# Global variables + +# _LDFLAGS +# _LDLIBS +# _CPPFLAGS +# _CFLAGS +# _CC +# _CXXFLAGS +# _CXX +# SO shared library suffix +# LIB shared library prefix + +# Utility functions +# +# $(call library-path,,) will resolve to the library file name. + +# Per library variables. + +# _SOURCES .c source files for library. Paths are relative to src//native. +# _CXXSOURCES .c source files for library. Paths are relative to src//native. +# _HEADERS header files for install/jmod +# _COMMANDS commands/bin/scripts for install/jmod + +# _LDFLAGS link flags +# _LIBADD extra objects to add to link line +# _LDLIBS link libraries +# _CPPFLAGS c and c++ pre-processor flags. "-Isrc//jni -Ibin/include/" is implicit. +# _CCFLAGS c compiler flags +# _CXXFLAGS c++ compiler flags + +# _DEPENDENCIES A list of other objects on which this library depends before linking. + +# .c and .cc files have dependencies automatically generated + +# Targets +# ------- + +# make gen only generate java sources +# make clean rm -rf bin +# make dist create dist tar in bin/ +# make | make jar make all jars and jmods + +# Outputs +# ------- + +# All intermediate and output files are written to bin/ + +# This layout is enforced by javac +# bin/include// .h files from javac -h +# bin/modules// .class files from javac + +# This layout is convenient for netbeans +# bin/gen//gen/ .c, exe files for generator free use +# bin/gen//classes/ .java files from generator _JAVA_GENERATED + +# Working files +# bin/status/ marker files for makefile + +# bin///lib .so librareies for jmod _LIBRARIES = libname +# bin///obj .o, .d files for library _SOURCES +# bin///include .h files for jmod _HEADERS +# bin///.jmod .jmod module + +# Output files +# bin//lib/ modular jar files and shared libraries for GNU/linux dev +# bin//include/ header files for exported shared libraries +# bin//bin/ shared libraries for microsoft dev +# bin//jmods/ jmod files for 'jlink' use. + +# ###################################################################### + +all_MODULES = $(java_MODULES) $(native_MODULES) + +E:= +S:=$(E) $(E) +SO=$($(TARGET)_SO) +LIB=$($(TARGET)_LIB) + +# Define some useful variables before including fragments +define common_variables= +$1_gendir:=bin/gen/$1/gen +$1_genjavadir:=bin/gen/$1/classes +$1_objdir:=bin/$1/$(TARGET)/obj +$1_incdir:=bin/$1/$(TARGET)/include +$1_libdir:=$$(if $$(filter windows-%,$(TARGET)),bin/$1/$(TARGET)/bin,bin/$1/$(TARGET)/lib) +$1_bindir:=bin/$1/$(TARGET)/bin +endef + +define java_variables= +ifndef $1_JAVA +$1_JAVA := $$(shell cd src/$1/classes && find * -type f -name '*.java') +endif +ifndef $1_RESOURCES +$1_RESOURCES := $$(shell cd src/$1/classes && find * -type f \! -name '*.java') +endif +endef + +java_libdir:=$(if $(filter windows-%,$(TARGET)),bin/$(TARGET)/bin,bin/$(TARGET)/lib) +java_bindir:=bin/$(TARGET)/bin +java_jardir:=bin/$(TARGET)/lib +java_incdir:=bin/$(TARGET)/include +java_jmoddir:=bin/$(TARGET)/jmods + +$(foreach module,$(java_MODULES) $(native_MODULES),$(eval $(call common_variables,$(module)))) +$(foreach module,$(java_MODULES),$(eval $(call java_variables,$(module)))) + +# ###################################################################### + +all: +jar: +gen: + +.PHONY: all clean jar gen $(java_MODULES) dist +clean: + rm -rf bin + +# dist things +dist_TAR = bin/$(dist_NAME)-$(dist_VERSION).tar.gz +dist_FILES = config.make.in java.make Makefile src $(dist_EXTRA) + +# Gen is things that go into the jar (sources and resources) +include $(wildcard $(all_MODULES:%=src/%/gen/gen.make)) +# Native is things that go into the sdk/jmod +include $(wildcard $(all_MODULES:%=src/%/native/native.make)) + +# ###################################################################### + +# create module depencies +# variables: +# _sdk is the target location of an expanded 'sdk' for this module +# it resides in a common location bin// +# _jmod is the target location of a staging area for jmod files +# is resides in a per-module lcoation bin/// +# _java is all the targets that will cause the invocation of javac +# it includes the module source, generated sources, and sentinals for generated sources + +# targets: +# bin/status/.depjava marks all source/generated sources are ready/updated +# bin/status/.depjar all compiled class files and resources are ready/updated +# bin/status/.sdk all files are available in bin/ as if it was an installed image + +define module_vars= +$1_sdk := $(addprefix $(java_bindir)/,$($1_COMMANDS)) $(addprefix $(java_libdir)/,$($1_LIBRARIES)) $($1_NATIVE_LIBRARIES:%=$(java_libdir)/lib%.so) +$1_jmod := $(addprefix $($1_bindir)/,$($1_COMMANDS)) $(addprefix $($1_libdir)/,$($1_LIBRARIES)) $($1_NATIVE_LIBRARIES:%=$($1_libdir)/lib%.so) +$1_java :=$($1_JAVA:%=src/$1/classes/%) $($1_JAVA_GENERATED:%=$($1_genjavadir)/%) +$1_resources:= $($1_RESOURCES:%=src/$1/classes/%) $($1_RESOURCES_GENERATED:%=$($1_genjavadir)/%) +$1_depjava := $($1_API:%=bin/status/$1-%.export) $(patsubst %,bin/status/%.classes, $(filter $($1_JDEPMOD),$(java_MODULES))) + +ifneq ("$$(strip $$($1_java) $$($1_depjava))", "") +bin/status/$1.depjava: $$($1_java) $$($1_depjava) + @install -d $$(@D) + touch $$@ +bin/status/$1.depjar: bin/status/$1.classes $$($1_resources) + @install -d $$(@D) + touch $$@ +bin/status/$1.depmod: bin/status/$1.classes $$($1_resources) $$($1_jmod) + @install -d $$(@D) + touch $$@ +bin/status/$1.sdk: $(java_jardir)/$1.jar +jar: $(java_jardir)/$1.jar +gen: bin/status/$1.depjava +$1 all: $(java_jardir)/$1.jar $(java_jmoddir)/$1.jmod +else +# acutally not sure here? +$1 all: bin/status/$1.sdk +endif + +$1-sdk sdk: bin/status/$1.sdk + +bin/status/$1.sdk: $$($1_sdk) $$($1_jmod) + @install -d $$(@D) + touch $$@ + +endef + +#$(foreach m,$(all_MODULES),$(info $(call module_vars,$m))) +$(foreach m,$(all_MODULES),$(eval $(call module_vars,$m))) + +# ###################################################################### +# notzed.nativez export-api +# ###################################################################### + +define api_targets= +bin/status/$1-$2.export: src/$1/gen/$2.api src/$1/gen/$2.h +bin/status/$1-$2.export: + mkdir -p bin/gen/$1/gen bin/status + $(NATIVEZ_HOME)/bin/export-api \ + -w bin/gen/$1/gen -d bin/gen/$1/classes $($1_APIFLAGS) $($1_$2_APIFLAGS) src/$1/gen/$2.api + touch $$@ + +bin/status/$1-$2.export.d: + @$(NATIVEZ_HOME)/bin/export-api -M -MT "$$(@:.d=) $$@" -MF $$@ \ + -w bin/gen/$1/gen -d bin/gen/$1/classes $($1_APIFLAGS) $($1_$2_APIFLAGS) src/$1/gen/$2.api 2>/dev/null + +$(if $(filter clean dist gen,$(MAKECMDGOALS)),,-include bin/status/$1-$2.export.d) +endef + +$(foreach m,$(all_MODULES),$(foreach a,$($m_API),$(eval $(call api_targets,$m,$a)))) + +# ###################################################################### +# Java +# ###################################################################### + +# Build targets for java modules + +define java_targets= + +# Create (modular) jar +$(java_jardir)/$1.jar: bin/status/$1.depjar + @install -d $$(@D) + $(JAR) cf $$@ \ + $(JARFLAGS) $$($(1)_JARFLAGS) \ + -C bin/modules/$(1) . \ + $(if $($1_RESOURCES),$($1_RESOURCES:%=-C src/$1/classes %)) \ + $(if $($1_RESOURCES_GENERATED),$($1_RESOURCES_GENERATED:%=-C bin/gen/$1/classes %)) + +# Create a jmod +$(java_jmoddir)/$1.jmod: bin/status/$1.depmod + rm -f $$@ + @install -d $$(@D) + $$(JMOD) create \ + $$(JMODFLAGS) $$($(1)_JMODFLAGS) \ + --target-platform $(TARGET) \ + --class-path bin/modules/$(1) \ + $$(if $$(wildcard bin/$(1)/$(TARGET)/include),--header-files bin/$(1)/$(TARGET)/include) \ + $$(if $$(wildcard src/$(1)/legal),--legal-notices src/$(1)/legal) \ + $$(if $$(wildcard bin/$(1)/$(TARGET)/bin),--cmds bin/$(1)/$(TARGET)/bin) \ + $$(if $$(wildcard bin/$(1)/$(TARGET)/lib),--libs bin/$(1)/$(TARGET)/lib) \ + $$@ + +# Create an IDE source zip, paths have to match --module-source-path +$(java_jardir)/$1-sources.zip: bin/status/$1.depjar + @install -d $$(@D) + $(JAR) -c -f $$@ -M \ + $$($1_JAVA:%=-C src/$1/classes %) \ + $$($1_JAVA_GENERATED:%=-C bin/gen/$1/classes %) + +# resources +bin/modules/$1/%: src/$1/classes/% + install -vD $$< $$@ + +# Compile module. +bin/status/$1.classes: bin/status/$1.depjava + @install -d $$(@D) + $(JAVAC) \ + --module-source-path "src/*/classes:bin/gen/*/classes" \ + $(if $(JAVAMODPATH),--module-path $(subst $(S),:,$(JAVAMODPATH))) \ + $(JAVACFLAGS) $($1_JAVACFLAGS) \ + -d bin/modules \ + -m $1 \ + $$($1_JAVA:%=src/$1/classes/%) \ + $$($1_JAVA_GENERATED:%=bin/gen/$1/classes/%) + touch $$@ +endef + +#$(foreach module,$(java_MODULES),$(info $(call java_targets,$(module)))) +$(foreach module,$(java_MODULES),$(eval $(call java_targets,$(module)))) + +# ###################################################################### + +# setup run-* targets +define run_targets= +run-$1/$2: bin/status/$1.sdk $($1_JDEPMOD:%=bin/status/%.sdk) + LD_LIBRARY_PATH=$(FFMPEG_HOME)/lib \ + $(JAVA) \ + $(if $(strip $(JAVAMODPATH) $($1_JAVAMODPATH)),--module-path $(subst $(S),:,$(strip $(JAVAMODPATH) $($1_JAVAMODPATH)))) \ + $(JMAINFLAGS) $($1_JMAINFLAGS) \ + -m $1/$2 \ + $(ARGV) +.PHONY: run-$1/$2 +endef + +#$(foreach module,$(java_MODULES),$(foreach main,$($(module)_JMAIN),$(info $(call run_targets,$(module),$(main))))) +$(foreach module,$(java_MODULES),$(foreach main,$($(module)_JMAIN),$(eval $(call run_targets,$(module),$(main))))) + +# ###################################################################### +# C and c++ native library support +# ###################################################################### + +define native_library= +# Rule for library $$2 in module $$1 +$2_OBJS = $(patsubst %.c, $($1_objdir)/%.o, $($2_SOURCES)) \ + $(patsubst %.cc, $($1_objdir)/%.o, $($2_CXXSOURCES)) +$2_SRCS = $(addprefix src/$1/native/,$($2_SOURCES)) +$2_SO = $($1_libdir)/$(LIB)$2$(SO) + +# Copy anything from staging area for jmods bin/module//* to sdk area bin//* +$(java_libdir)/%: $($(1)_libdir)/% + @install -d $$(@D) + ln -fs $$(abspath $$<) $$@ +$(java_bindir)/%: $($(1)_bindir)/% + @install -d $$(@D) + ln -fs $$(abspath $$<) $$@ +$(java_incdir)/%: $($(1)_incdir)/% + @install -d $$(@D) + ln -fs $$(abspath $$<) $$@ + +$($1_libdir)/$(LIB)$2$(SO): $$($2_OBJS) $($2_LIBADD) $($2_DEPENDENCIES) + @install -d $$(@D) + $($(TARGET)_CC) -o $$@ -shared \ + $($(TARGET)_LDFLAGS) $($2_LDFLAGS) $$($2_OBJS) $($2_LIBADD) $($(TARGET)_LDLIBS) $($2_LDLIBS) + +$($1_objdir)/%.o: src/$1/native/%.c + @install -d $$(@D) + $($(TARGET)_CC) -Isrc/$1/native -Ibin/include/$1 \ + $($(TARGET)_CPPFLAGS) $($2_CPPFLAGS) \ + $($(TARGET)_CFLAGS) $($2_CFLAGS) -c -o $$@ $$< + +$($1_objdir)/%.o: src/$1/native/%.cc + @install -d $$(@D) + $($(TARGET)_CXX) -Isrc/$1/native -Ibin/include/$1 \ + $($(TARGET)_CPPFLAGS) $($2_CPPFLAGS) \ + $($(TARGET)_CXXFLAGS) $($2_CXXFLAGS) -c -o $$@ $$< + +# auto-dependencies for c files +$($1_objdir)/%.d: src/$1/native/%.c + @install -d $$(@D) + @rm -f $$@ + @$($(TARGET)_CC) -MM -MT "$$(@:.d=.o) $$@" -Isrc/$1/jni -Ibin/include/$1 \ + $($(TARGET)_CPPFLAGS) $($2_CPPFLAGS) $$< -o $$@ 2>/dev/null + +# auto-dependencies for c++ files +$($1_objdir)/%.d: src/$1/native/%.cc + @install -d $$(@D) + @rm -f $$@ + @$($(TARGET)_CXX) -MM -MT "$$(@:.d=.o) $$@" -Isrc/$1/jni -Ibin/include/$1 \ + $($(TARGET)_CPPFLAGS) $($2_CPPFLAGS) $$< -o $$@ 2>/dev/null + +$(if $(filter clean dist gen,$(MAKECMDGOALS)),,-include $$($2_OBJS:.o=.d)) +endef + +#$(foreach module,$(all_MODULES),$(foreach library,$($(module)_NATIVE_LIBRARIES),$(info $(call native_library,$(module),$(library))))) +$(foreach module,$(all_MODULES),$(foreach library,$($(module)_NATIVE_LIBRARIES),$(eval $(call native_library,$(module),$(library))))) + +# ###################################################################### + +dist: + @install -d bin + tar cfz bin/$(dist_NAME)-$(dist_VERSION).tar.gz \ + --transform=s,^,$(dist_NAME)-$(dist_VERSION)/, \ + $(dist_FILES) diff --git a/nbproject/build-impl.xml b/nbproject/build-impl.xml new file mode 100644 index 0000000..43da245 --- /dev/null +++ b/nbproject/build-impl.xml @@ -0,0 +1,1845 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +package netbeans; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Task; + +public class CoalesceKeyvalue extends Task { + private String property; + + public void setProperty(String property) { + this.property = property; + } + + private String value; + + public void setValue(String value) { + this.value = value; + } + + private String valueSep; + + public void setValueSep(String valueSep) { + this.valueSep = valueSep; + } + + private String entrySep; + + public void setEntrySep(String entrySep) { + this.entrySep = entrySep; + } + + private String multiSep; + + public void setMultiSep(String multiSep) { + this.multiSep = multiSep; + } + + private String outSep; + + public void setOutSep(String outSep) { + this.outSep = outSep; + } + + @Override + public void execute() throws BuildException { + List<String> result = new ArrayList<>(); + Map<String, List<String>> module2Paths = new HashMap<>(); + + for (String entry : value.split(Pattern.quote(entrySep))) { + String[] keyValue = entry.split(Pattern.quote(valueSep), 2); + if (keyValue.length == 1) { + result.add(keyValue[0]); + } else { + module2Paths.computeIfAbsent(keyValue[0], s -> new ArrayList<>()) + .add(keyValue[1].trim()); + } + } + module2Paths.entrySet() + .stream() + .forEach(e -> result.add(e.getKey() + valueSep + e.getValue().stream().collect(Collectors.joining(multiSep)))); + getProject().setProperty(property, result.stream().collect(Collectors.joining(" " + entrySep))); + } + +} + + + + +package netbeans; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Task; + +public class ModsourceRegexp extends Task { + private String property; + + public void setProperty(String property) { + this.property = property; + } + + private String filePattern; + + public void setFilePattern(String filePattern) { + this.filePattern = filePattern; + } + + private String modsource; + + public void setModsource(String modsource) { + this.modsource = modsource; + } + + private List<String> expandGroup(String grp) { + List<String> exp = new ArrayList<>(); + String item = ""; + int depth = 0; + + for (int i = 0; i < grp.length(); i++) { + char c = grp.charAt(i); + switch (c) { + case '{': + if (depth++ == 0) { + continue; + } + break; + case '}': + if (--depth == 0) { + exp.add(item); + continue; + } + break; + case ',': + if (depth == 1) { + exp.add(item); + item = ""; + continue; + } + default: + break; + } + item = item + c; + } + return exp; + } + + private List<String> pathVariants(String spec) { + return pathVariants(spec, new ArrayList<>()); + } + + private List<String> pathVariants(String spec, List<String> res) { + int start = spec.indexOf('{'); + if (start == -1) { + res.add(spec); + return res; + } + int depth = 1; + int end; + for (end = start + 1; end < spec.length() && depth > 0; end++) { + char c = spec.charAt(end); + switch (c) { + case '{': depth++; break; + case '}': depth--; break; + } + } + String prefix = spec.substring(0, start); + String suffix = spec.substring(end); + expandGroup(spec.substring(start, end)).stream().forEach(item -> { + pathVariants(prefix + item + suffix, res); + }); + return res; + } + + private String toRegexp2(String spec, String filepattern, String separator) { + List<String> prefixes = new ArrayList<>(); + List<String> suffixes = new ArrayList<>(); + pathVariants(spec).forEach(item -> { + suffixes.add(item); + }); + String tail = ""; + String separatorString = separator; + if ("\\".equals(separatorString)) { + separatorString = "\\\\"; + } + if (filepattern != null && !Objects.equals(filepattern, tail)) { + tail = separatorString + filepattern; + } + return "([^" + separatorString +"]+)\\Q" + separator + "\\E(" + suffixes.stream().collect(Collectors.joining("|")) + ")" + tail; + } + + @Override + public void execute() throws BuildException { + getProject().setProperty(property, toRegexp2(modsource, filePattern, getProject().getProperty("file.separator"))); + } + +} + + + + +package netbeans; + +import java.io.File; +import java.util.Arrays; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.types.selectors.BaseExtendSelector; + +public class ModuleInfoSelector extends BaseExtendSelector { + + @Override + public boolean isSelected(File basedir, String filename, File file) throws BuildException { + String extension = Arrays.stream(getParameters()) + .filter(p -> "extension".equals(p.getName())) + .map(p -> p.getValue()) + .findAny() + .get(); + return !new File(file, "module-info." + extension).exists(); + } + +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @{paths} + + + + + + + + + + + + @{paths} + + + + + + + + + + + + + + + + + + + + Must set src.dir + Must set test.src.dir + Must set build.dir + Must set dist.dir + Must set build.modules.dir + Must set dist.javadoc.dir + Must set build.test.modules.dir + Must set build.test.results.dir + Must set build.classes.excludes + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must set javac.includes + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + No tests executed. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must set JVM to use for profiling in profiler.info.jvm + Must set profiler agent JVM arguments in profiler.info.jvmargs.agent + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must select some files in the IDE or set javac.includes + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + No main class specified + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must select one file in the IDE or set run.class + + + + Must select one file in the IDE or set run.class + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must select one file in the IDE or set debug.class + + + + + Must select one file in the IDE or set debug.class + + + + + Must set fix.includes + + + + + + + + + + + This target only works when run from inside the NetBeans IDE. + + + + + + + + + Must select one file in the IDE or set profile.class + This target only works when run from inside the NetBeans IDE. + + + + + + + + + This target only works when run from inside the NetBeans IDE. + + + + + + + + + + + + + This target only works when run from inside the NetBeans IDE. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must select one file in the IDE or set run.class + + + + + + Must select some files in the IDE or set test.includes + + + + + Must select one file in the IDE or set run.class + + + + + Must select one file in the IDE or set applet.url + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must select some files in the IDE or set javac.includes + + + + + + + + + + + + + + + + + + + + + + + + + + Some tests failed; see details above. + + + + + + + + + Must select some files in the IDE or set test.includes + + + + Some tests failed; see details above. + + + + Must select some files in the IDE or set test.class + Must select some method in the IDE or set test.method + + + + Some tests failed; see details above. + + + + + Must select one file in the IDE or set test.class + + + + Must select one file in the IDE or set test.class + Must select some method in the IDE or set test.method + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/nbproject/genfiles.properties b/nbproject/genfiles.properties new file mode 100644 index 0000000..338af96 --- /dev/null +++ b/nbproject/genfiles.properties @@ -0,0 +1,8 @@ +build.xml.data.CRC32=5285ee43 +build.xml.script.CRC32=11a163fa +build.xml.stylesheet.CRC32=32069288@1.19 +# This file is used by a NetBeans-based IDE to track changes in generated files such as build-impl.xml. +# Do not edit this file. You may delete it but then the IDE will never regenerate such files for you. +nbproject/build-impl.xml.data.CRC32=5285ee43 +nbproject/build-impl.xml.script.CRC32=2d2b98b3 +nbproject/build-impl.xml.stylesheet.CRC32=d1ebcf0f@1.19 diff --git a/nbproject/project.properties b/nbproject/project.properties new file mode 100644 index 0000000..9dd8f21 --- /dev/null +++ b/nbproject/project.properties @@ -0,0 +1,97 @@ +annotation.processing.enabled=true +annotation.processing.enabled.in.editor=false +annotation.processing.processors.list= +annotation.processing.run.all.processors=true +annotation.processing.source.output=${build.generated.sources.dir}/ap-source-output +application.title=csvedit +application.vendor=notzed +build.classes.dir=${build.dir}/classes +build.classes.excludes=**/*.java,**/*.form +# This directory is removed when the project is cleaned: +build.dir=build +build.generated.dir=${build.dir}/generated +build.generated.sources.dir=${build.dir}/generated-sources +build.modules.dir=${build.dir}/modules +# Only compile against the classpath explicitly listed here: +build.sysclasspath=ignore +build.test.classes.dir=${build.dir}/test/classes +build.test.modules.dir=${build.dir}/test/modules +build.test.results.dir=${build.dir}/test/results +# Uncomment to specify the preferred debugger connection transport: +#debug.transport=dt_socket +debug.classpath=\ + ${run.classpath} +debug.modulepath=\ + ${run.modulepath} +debug.test.classpath=\ + ${run.test.classpath} +debug.test.modulepath=\ + ${run.test.modulepath} +# Files in build.classes.dir which should be excluded from distribution jar +dist.archive.excludes= +# This directory is removed when the project is cleaned: +dist.dir=dist +dist.javadoc.dir=${dist.dir}/javadoc +dist.jlink.dir=${dist.dir}/jlink +dist.jlink.output=${dist.jlink.dir}/csvedit +endorsed.classpath= +excludes= +includes=** +jar.compress=false +javac.classpath= +# Space-separated list of extra javac options +javac.compilerargs= +javac.deprecation=false +javac.external.vm=false +javac.modulepath= +javac.processormodulepath= +javac.processorpath=\ + ${javac.classpath} +javac.source=19 +javac.target=19 +javac.test.classpath=\ + ${javac.classpath} +javac.test.modulepath=\ + ${javac.modulepath}:\ + ${build.modules.dir} +javac.test.processorpath=\ + ${javac.test.classpath} +javadoc.additionalparam= +javadoc.author=false +javadoc.encoding=${source.encoding} +javadoc.html5=false +javadoc.noindex=false +javadoc.nonavbar=false +javadoc.notree=false +javadoc.private=false +javadoc.splitindex=true +javadoc.use=true +javadoc.version=false +javadoc.windowtitle= +# The jlink additional root modules to resolve +jlink.additionalmodules= +# The jlink additional command line parameters +jlink.additionalparam= +jlink.launcher=true +jlink.launcher.name=csvedit +main.class=au.notzed.csvedit.CSVEdit +platform.active=default_platform +project.license=gpl3-notzed +run.classpath= +# Space-separated list of JVM arguments used when running the project. +# You may also define separate properties like run-sys-prop.name=value instead of -Dname=value. +# To set system properties for unit tests define test-sys-prop.name=value: +run.jvmargs= +run.modulepath=\ + ${javac.modulepath}:\ + ${build.modules.dir} +run.test.classpath=\ + ${javac.test.classpath} +run.test.modulepath=\ + ${javac.test.modulepath}:\ + ${build.test.modules.dir} +source.encoding=UTF-8 +src.dir=src +src.dir.path=classes +test.src.dir=src +test.src.dir.path=tests diff --git a/nbproject/project.xml b/nbproject/project.xml new file mode 100644 index 0000000..987507e --- /dev/null +++ b/nbproject/project.xml @@ -0,0 +1,15 @@ + + + org.netbeans.modules.java.j2semodule + + + csvedit + + + + + + + + + diff --git a/src/notzed.csvedit/classes/au/notzed/csvedit/CE.java b/src/notzed.csvedit/classes/au/notzed/csvedit/CE.java new file mode 100644 index 0000000..f91686e --- /dev/null +++ b/src/notzed.csvedit/classes/au/notzed/csvedit/CE.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2022 Michael Zucchi + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package au.notzed.csvedit; + +import java.util.prefs.Preferences; + +public class CE { + + static Preferences prefs; + + public static Preferences prefs() { + if (prefs == null) + prefs = Preferences.userNodeForPackage(CE.class); + return prefs; + } + + public static String getString(String key, String def) { + return prefs().get(key, def); + } + + public static void setString(String key, String def) { + prefs().put(key, def); + } + + public static int getInt(String key, int def) { + return prefs().getInt(key, def); + } + + public static void setInt(String key, int def) { + prefs().putInt(key, def); + } + +} diff --git a/src/notzed.csvedit/classes/au/notzed/csvedit/CSVEdit.java b/src/notzed.csvedit/classes/au/notzed/csvedit/CSVEdit.java new file mode 100644 index 0000000..fa90a71 --- /dev/null +++ b/src/notzed.csvedit/classes/au/notzed/csvedit/CSVEdit.java @@ -0,0 +1,615 @@ +/* + * Copyright (C) 2022 Michael Zucchi + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package au.notzed.csvedit; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Component; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.HeadlessException; +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.UnsupportedFlavorException; +import java.awt.dnd.DnDConstants; +import java.awt.dnd.DropTargetDragEvent; +import java.awt.dnd.DropTargetDropEvent; +import java.awt.dnd.DropTargetEvent; +import java.awt.dnd.DropTargetListener; +import java.awt.event.ActionEvent; +import java.awt.event.KeyEvent; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.awt.geom.AffineTransform; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.Properties; +import java.util.TooManyListenersException; +import java.util.stream.Collectors; +import javax.swing.Action; +import javax.swing.BorderFactory; +import javax.swing.BoxLayout; +import javax.swing.JButton; +import javax.swing.JComponent; +import javax.swing.JFileChooser; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JMenu; +import javax.swing.JMenuBar; +import javax.swing.JMenuItem; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JPopupMenu; +import javax.swing.JSeparator; +import javax.swing.JTabbedPane; +import javax.swing.KeyStroke; +import javax.swing.TransferHandler; +import static javax.swing.TransferHandler.COPY; +import javax.swing.UIManager; +import javax.swing.filechooser.FileNameExtensionFilter; + +public class CSVEdit extends JFrame { + + JTabbedPane files; + // + RecentList openRecent; + RecentList openSetRecent; + // + Action quitAction; + Action openAction; + Action saveAction; + Action saveAllAction; + Action openSetAction; + Action saveSetAction; + // + Action clearAction; + + public CSVEdit() throws HeadlessException { + super("csvedit"); + setSize(1000, 800); + + openRecent = new RecentList("recent.file"); + openSetRecent = new RecentList("recent.set"); + + JPanel root = new JPanel(new BorderLayout()); + files = new JTabbedPane(JTabbedPane.LEFT); + files.setOpaque(true); + + JPanel buttons = new JPanel(); + buttons.setLayout(new BoxLayout(buttons, BoxLayout.X_AXIS)); + + initActions(); + initTransfer(); + + buttons.add(new JPopupButton(openAction, this::recentOpenPopup)); + buttons.add(new JButton(saveAction)); + JSeparator sep; + buttons.add(sep = new JSeparator(JSeparator.VERTICAL)); + sep.setPreferredSize(new Dimension(6, 8)); + buttons.add(new JButton(saveAllAction)); + buttons.add(sep = new JSeparator(JSeparator.VERTICAL)); + sep.setPreferredSize(new Dimension(6, 8)); + buttons.add(new JPopupButton(openSetAction, this::recentOpenSetPopup)); + buttons.add(new JButton(saveSetAction)); + + JPanel hfill = new JPanel(); + buttons.add(hfill); + hfill.setPreferredSize(new Dimension(Integer.MAX_VALUE, 1)); + + JMenuItem item; + JMenuBar menubar = new JMenuBar(); + + JMenu filemenu = new JMenu("File"); + filemenu.setMnemonic(KeyEvent.VK_F); + menubar.add(filemenu); + + filemenu.add(item = new JMenuItem(quitAction)); + item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Q, ActionEvent.CTRL_MASK)); + + TransferActionListener xferListener = new TransferActionListener(); + + JMenu editmenu = new JMenu("Edit"); + editmenu.setMnemonic(KeyEvent.VK_E); + menubar.add(editmenu); + editmenu.add(item = new JMenuItem(TransferHandler.getCutAction())); + item.setText("Cut"); + item.setMnemonic(KeyEvent.VK_T); + item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_X, ActionEvent.CTRL_MASK)); + item.addActionListener(xferListener); + + editmenu.add(item = new JMenuItem(TransferHandler.getCopyAction())); + item.setText("Copy"); + item.setMnemonic(KeyEvent.VK_C); + item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C, ActionEvent.CTRL_MASK)); + item.addActionListener(xferListener); + + editmenu.add(item = new JMenuItem(TransferHandler.getPasteAction())); + item.setText("Paste"); + item.setMnemonic(KeyEvent.VK_P); + item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_V, ActionEvent.CTRL_MASK)); + item.addActionListener(xferListener); + editmenu.add(new JSeparator()); + + editmenu.add(item = new JMenuItem(TableEditor.getClearAction())); + item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Z, ActionEvent.CTRL_MASK)); + item.addActionListener(xferListener); + + JPanel top = new JPanel(new BorderLayout()); + top.add(menubar, BorderLayout.NORTH); + top.add(buttons, BorderLayout.SOUTH); + + root.add(top, BorderLayout.NORTH); + root.add(files, BorderLayout.CENTER); + + setContentPane(root); + + setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); + addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(WindowEvent e) { + quitAction.actionPerformed(null); + } + }); + } + + private JPopupMenu recentOpenPopup() { + JPopupMenu menu = new JPopupMenu(); + + openRecent.stream().filter(p -> Files.isRegularFile(p)).forEach(p -> { + menu.add(SimpleAction.of(p.getFileName().toString(), e -> openFileCatch(p))); + }); + + return menu; + + } + + static void setLastDirectory(JFileChooser fc, String key) { + String dir = CE.getString(key, null); + if (dir != null) + fc.setCurrentDirectory(new File(dir)); + + } + + static void updateLastDirectory(JFileChooser fc, String key) { + CE.setString(key, fc.getCurrentDirectory().toString()); + } + + private void initActions() { + quitAction = SimpleAction.of("Quit", e -> { + List list = getModified(); + if (!list.isEmpty()) { + final String[] options = {"Save All", "Quit Without Saving", "Cancel"}; + StringBuilder sb = new StringBuilder("

Modified Files

    "); + for (TableEditor te: list) + sb.append("
  1. ").append(te.data.path()); + sb.append("
"); + int res = JOptionPane.showOptionDialog(this, sb, "Clear Warning", + JOptionPane.YES_NO_CANCEL_OPTION, + JOptionPane.WARNING_MESSAGE, + null, + options, + options[2]); + + if (res == JOptionPane.CANCEL_OPTION) + return; + + if (res == JOptionPane.YES_OPTION) { + saveAllAction.actionPerformed(e); + // ignore errors? + } + } + dispose(); + }); + saveAction = SimpleAction.of("Save As", (e) -> { + TableEditor te = (TableEditor)files.getSelectedComponent(); + if (te != null) { + JFileChooser fc = new JFileChooser(); + + fc.setSelectedFile(te.data.path().toFile()); + fc.setFileFilter(new FileNameExtensionFilter("Character Separated Files", "txt", "csv", "tsv")); + int returnVal = fc.showSaveDialog(CSVEdit.this); + + if (returnVal == JFileChooser.APPROVE_OPTION) { + Path path = fc.getSelectedFile().toPath(); + + updateLastDirectory(fc, "open.drawer"); + + try { + te.saveAs(path); + openRecent.addRecent(path); + } catch (IOException ex) { + JOptionPane.showMessageDialog(this, ex, "Problem saving", JOptionPane.ERROR_MESSAGE); + ex.printStackTrace(); + } + } + } + }); + + openSetAction = SimpleAction.of("Open Set", (e) -> { + JFileChooser fc = new JFileChooser(); + + fc.setFileFilter(new FileNameExtensionFilter("Set File", "set", "properties")); + setLastDirectory(fc, "open.drawer"); + + int returnVal = fc.showOpenDialog(CSVEdit.this); + if (returnVal == JFileChooser.APPROVE_OPTION) { + try { + File file = fc.getSelectedFile(); + + updateLastDirectory(fc, "open.drawer"); + + openSet(file); + openSetRecent.addRecent(file.toPath()); + } catch (IOException ex) { + ex.printStackTrace(); + JOptionPane.showMessageDialog(CSVEdit.this, ex, "Open Set Failed", JOptionPane.ERROR_MESSAGE); + } + } + }); + saveSetAction = SimpleAction.of("Save Set", (e) -> { + JFileChooser fc = new JFileChooser(); + + fc.setFileFilter(new FileNameExtensionFilter("Set File", "set")); + setLastDirectory(fc, "open.drawer"); + + int returnVal = fc.showSaveDialog(CSVEdit.this); + if (returnVal == JFileChooser.APPROVE_OPTION) { + File file = fc.getSelectedFile(); + + if (!file.getName().endsWith(".set")) { + file = new File(file.getParentFile(), file.getName() + ".set"); + } + + updateLastDirectory(fc, "open.drawer"); + try { + saveSet(file); + openSetRecent.addRecent(file.toPath()); + } catch (IOException ex) { + ex.printStackTrace(); + JOptionPane.showMessageDialog(CSVEdit.this, ex, "Save Set Failed", JOptionPane.ERROR_MESSAGE); + } + } + }); + + openAction = SimpleAction.of("Open", (e) -> { + JFileChooser fc = new JFileChooser(); + + fc.setMultiSelectionEnabled(true); + fc.setFileFilter(new FileNameExtensionFilter("Character Separated Files", "txt", "csv", "tsv")); + setLastDirectory(fc, "open.drawer"); + + int returnVal = fc.showOpenDialog(CSVEdit.this); + if (returnVal == JFileChooser.APPROVE_OPTION) { + + updateLastDirectory(fc, "open.drawer"); + + List failed = new ArrayList<>(); + for (File file: fc.getSelectedFiles()) { + Path path = file.toPath(); + try { + openFile(path); + openRecent.addRecent(path); + } catch (IOException ex) { + ex.printStackTrace(); + failed.add(path); + } + } + showFileError("Files could not be opened", "Files could not be opened", failed); + } + }); + + saveAllAction = SimpleAction.of("Save All", (e) -> { + List failed = new ArrayList<>(); + for (int i = 0; i < files.getTabCount(); i++) { + TableEditor te = (TableEditor)files.getComponentAt(i); + try { + if (te.modified) + te.save(); + } catch (IOException ex) { + ex.printStackTrace(); + failed.add(te.data.path()); + } + } + saveAllAction.setEnabled(!failed.isEmpty()); + showFileError("Files could not be saved", "Files could not be saved", failed); + }); + saveAllAction.setEnabled(false); + } + + private void showFileError(String title, String message, List failed) { + if (!failed.isEmpty()) { + StringBuilder sb = new StringBuilder("

"); + sb.append(message).append("

    "); + failed.stream() + .map(f -> f.getFileName().toString()) + .peek(n -> sb.append("
  • ")) + .forEach(sb::append); + sb.append("
"); + JOptionPane.showMessageDialog(CSVEdit.this, sb, title, JOptionPane.ERROR_MESSAGE); + } + } + + private void initTransfer() { + files.setTransferHandler(new TransferHandler() { + + @Override + public boolean canImport(TransferHandler.TransferSupport info) { + if (info.isDrop()) { + if (!info.isDataFlavorSupported(DataFlavor.javaFileListFlavor) + || info.getUserDropAction() != DnDConstants.ACTION_COPY) + return false; + + if ((COPY & info.getSourceDropActions()) == COPY) { + info.setDropAction(COPY); + return true; + } + } else { + System.out.println("not drop xfer"); + + } + return false; + } + + @Override + public int getSourceActions(JComponent c) { + return NONE; + } + + @Override + public boolean importData(TransferHandler.TransferSupport info) { + try { + // TODO: handle whole directories, or some sort of meta-file for multiple tables + List list = (List)info.getTransferable().getTransferData(DataFlavor.javaFileListFlavor); + Component component = info.getComponent(); + System.out.println("drop to " + component.getClass()); + for (File file: list) { + Path path = file.toPath(); + + openFile(path); + openRecent.addRecent(path); + + } + return true; + } catch (UnsupportedFlavorException ex) { + ex.printStackTrace(); + } catch (IOException ex) { + ex.printStackTrace(); + } + return false; + } + + }); + try { + files.getDropTarget().addDropTargetListener(new DropTargetListener() { + Color replace = new Color(0xffff8888); + Color drop = new Color(0xff8888ff); + + @Override + public void dragEnter(DropTargetDragEvent dtde) { + if (dtde.getDropAction() == DnDConstants.ACTION_COPY) { + try { + List list = (List)dtde.getTransferable().getTransferData(DataFlavor.javaFileListFlavor); + HashSet paths = list.stream().map(File::toPath).map(Path::toAbsolutePath).collect(Collectors.toCollection(HashSet::new)); + + for (int i = 0; i < files.getTabCount(); i++) { + TableEditor te = (TableEditor)files.getComponentAt(i); + + if (paths.contains(te.data.path().toAbsolutePath())) { + System.out.println(i); + //files.setIconAt(i, UIManager.getDefaults().getIcon("OptionPane.warningIcon")); + JLabel warn = new JLabel("" + files.getTitleAt(i) + ""); + warn.setOpaque(true); + warn.setBackground(replace); + files.setTabComponentAt(i, warn); + System.out.println(files.getBackgroundAt(i)); + System.out.println("!have " + te.data.path()); + } + } + + } catch (UnsupportedFlavorException ex) { + ex.printStackTrace(); + } catch (IOException ex) { + ex.printStackTrace(); + } + + files.setBorder(BorderFactory.createLineBorder(Color.blue, 1)); + files.setBackground(drop); + } + } + + @Override + public void dragOver(DropTargetDragEvent dtde) { + } + + @Override + public void dropActionChanged(DropTargetDragEvent dtde) { + if (dtde.getDropAction() == DnDConstants.ACTION_COPY) + files.setBorder(BorderFactory.createLineBorder(Color.blue, 1)); + else + files.setBorder(null); + } + + void clear() { + files.setBorder(null); + files.setBackground(null); + for (int i = 0; i < files.getTabCount(); i++) { + //files.setBackgroundAt(i, null); + files.setTabComponentAt(i, null); + } + } + + @Override + public void dragExit(DropTargetEvent dte) { + clear(); + } + + @Override + public void drop(DropTargetDropEvent dtde) { + clear(); + } + }); + } catch (TooManyListenersException ex) { + ex.printStackTrace(); + } + } + + private JPopupMenu recentOpenSetPopup() { + JPopupMenu menu = new JPopupMenu(); + + openSetRecent.stream().filter(p -> Files.isRegularFile(p)).forEach(p -> { + menu.add(SimpleAction.of(p.getFileName().toString(), e -> openSetCatch(p.toFile()))); + }); + + return menu; + } + + List getModified() { + ArrayList list = new ArrayList<>(); + + for (int i = 0; i < files.getTabCount(); i++) { + TableEditor te = (TableEditor)files.getComponentAt(i); + if (te.modified) + list.add(te); + } + return list; + } + + Optional findEditor(Path file) { + int index = findEditorIndex(file); + return index >= 0 ? Optional.of((TableEditor)files.getComponentAt(index)) : Optional.empty(); + } + + int findEditorIndex(Path file) { + Path path = file.toAbsolutePath(); + for (int i = 0; i < files.getTabCount(); i++) { + TableEditor te = (TableEditor)files.getComponentAt(i); + + if (path.equals(te.data.path().toAbsolutePath())) + return i; + } + return -1; + } + + private void openSetCatch(File file) { + try { + openSet(file); + } catch (IOException ex) { + ex.printStackTrace(); + } + } + + private void openSet(File file) throws IOException { + Properties props = new Properties(); + try ( FileReader fr = new FileReader(file, Charset.forName("UTF-8"))) { + props.load(fr); + } + int nfiles = Integer.parseInt(props.getProperty("file.count", "0")); + for (int i = 0; i < nfiles; i++) { + String name = props.getProperty("file." + i + ".name"); + if (name != null) { + openFile(new File(file.getParentFile(), name)); + } + } + } + + private void saveSet(File file) throws IOException { + File tmp = new File(file.getParentFile(), file.getName() + "~"); + Path dir = file.getParentFile().toPath().toAbsolutePath(); + + Properties props = new Properties(); + int nfiles = 0; + for (int i = 0; i < files.getTabCount(); i++) { + TableEditor te = (TableEditor)files.getComponentAt(i); + Path path = te.data.path().toAbsolutePath(); + + if (path.startsWith(dir)) { + props.setProperty("file." + i + ".name", dir.relativize(path).toString()); + nfiles++; + } + } + props.setProperty("file.count", String.valueOf(nfiles)); + + System.out.print("Saving: "); + System.out.println(file); + + if (Files.exists(file.toPath())) + Files.copy(file.toPath(), tmp.toPath(), StandardCopyOption.REPLACE_EXISTING); + try ( FileWriter fw = new FileWriter(file, Charset.forName("UTF-8"))) { + props.store(fw, null); + } + } + + void openFile(File file) throws IOException { + openFile(file.toPath()); + } + + void openFileCatch(Path path) { + try { + openFile(path); + } catch (IOException ex) { + ex.printStackTrace(); + } + } + + void openFile(Path path) throws IOException { + Optional match = findEditor(path); + if (match.isPresent()) { + match.get().load(path); + } else { + TableEditor te = new TableEditor(); + te.load(path); + files.add(te.data.path().getFileName().toString(), te); + files.setSelectedComponent(te); + + te.addModifiedListener(tt -> { + int index = findEditorIndex(tt.data.path()); + //files.setTitleAt(index, tt.data.path().getFileName().toString() + (tt.modified ? "*" : "")); + //files.setIconAt(index, UIManager.getDefaults().getIcon("OptionPane.warningIcon")); + saveAllAction.setEnabled(saveAllAction.isEnabled() || tt.modified); + }); + + } + } + + public static void setGlobalFontScale(double scale) { + Enumeration keys = UIManager.getDefaults().keys(); + AffineTransform at = AffineTransform.getScaleInstance(scale, scale); + while (keys.hasMoreElements()) { + Object key = keys.nextElement(); + Object value = UIManager.get(key); + if (value instanceof Font font) { + UIManager.put(key, font.deriveFont(at)); + } + } + } + + public static void main(String[] args) { + + //setGlobalFontScale(1.5); + new CSVEdit().setVisible(true); + } +} diff --git a/src/notzed.csvedit/classes/au/notzed/csvedit/CSVIO.java b/src/notzed.csvedit/classes/au/notzed/csvedit/CSVIO.java new file mode 100644 index 0000000..87146f4 --- /dev/null +++ b/src/notzed.csvedit/classes/au/notzed/csvedit/CSVIO.java @@ -0,0 +1,228 @@ +/* + * Copyright (C) 2022 Michael Zucchi + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package au.notzed.csvedit; + +import java.io.IOException; +import java.io.Reader; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.nio.file.StandardOpenOption; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.Vector; + +public class CSVIO { + public static record Data( + Path path, + Vector headers, + Vector> rows, + boolean truncated) { + } + + public static Data loadData(Path file) throws IOException { + Vector[] all = Files.lines(file, Charset.forName("UTF-8")) + .map(s -> new Vector<>(List.of(s.split("\t")))) + .toArray(Vector[]::new); + Vector header = all[0]; + Vector> data = new Vector<>(List.of(all).subList(1, all.length)); + + return new Data(file, header, data, isTruncated(header.size(), data)); + } + + static boolean isTruncated(int ncol, Vector> rows) { + boolean ok = true; + for (int i = 0; ok && i < rows.size(); i++) + ok = rows.get(i).size() <= ncol; + + return !ok; + } + + static String formatRow(int ncol, String sep, Vector vec) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < ncol; i++) { + String v; + if (i < vec.size()) { + v = vec.get(i); + if (v == null) + v = ""; + } else { + v = ""; + } + if (i > 0) + sb.append(sep); + sb.append(v); + } + + return sb.toString(); + } + + static String EMPTY = ""; + + public static void saveData(Path file, Data data) throws IOException { + Iterable lines = () -> { + return new Iterator() { + int line = 0; + + @Override + public boolean hasNext() { + return line <= data.rows.size(); + } + + @Override + public String next() { + if (line++ == 0) + return formatRow(data.headers.size(), "\t", data.headers); + return formatRow(data.headers.size(), "\t", data.rows.get(line - 2)); + } + }; + }; + + Path tmp = file.resolveSibling(file.getFileName().toString() + "~"); + + if (Files.exists(file)) + Files.copy(file, tmp, StandardCopyOption.REPLACE_EXISTING); + Files.write(file, lines, Charset.forName("UTF-8"), StandardOpenOption.CREATE, StandardOpenOption.WRITE); + } + + public enum LineEnding { + NL, + CR, + CRNL, + EOF + } + + public record line(String[] data, boolean truncated, LineEnding eol) { + } + + static class CSVReader implements Iterable, AutoCloseable { + + final Reader reader; + final StringBuilder val = new StringBuilder(); + final line header; + final int ncol; + int lastc = -1; + + public CSVReader(Reader reader) throws IOException { + this.reader = reader; + this.header = readRow(Integer.MAX_VALUE); + this.ncol = header.data().length; + } + + public CSVReader(Reader reader, int ncol) throws IOException { + this.reader = reader; + this.header = null; + this.ncol = ncol; + } + + private line readRow(int ncol) throws IOException { + String[] row = new String[ncol == Integer.MAX_VALUE ? 4 : ncol]; + int col = 0; + + while (true) { + int c = lastc == -1 ? reader.read() : lastc; + + lastc = -1; + + //System.out.printf("c = %d %c\n", c, (char)(Character.isValidCodePoint(c) ? c : '?')); + if (c == -1 && col == 0 && val.length() == 0) + return null; + + if (c == '\t' || c == '\n' || c == '\r' || c == -1) { + if (col >= row.length && ncol == Integer.MAX_VALUE) { + System.out.println("grow row " + row.length * 2 + " col " + col); + row = Arrays.copyOf(row, row.length * 2); + } + if (col < row.length) + row[col++] = val.toString(); + else + System.out.printf("losing: '%s'\n", val); + val.setLength(0); + + if (c != '\t') { + LineEnding ending; + + row = row.length == ncol || row.length == col ? row : Arrays.copyOf(row, col); + switch (c) { + case '\r': + c = reader.read(); + if (c == '\n') { + ending = LineEnding.CRNL; + } else { + lastc = c; + ending = LineEnding.CR; + } + break; + case '\n': + ending = LineEnding.NL; + break; + default: + ending = LineEnding.EOF; + break; + } + + return new line(row, ncol != Integer.MAX_VALUE && ncol != col, ending); + } + } else { + val.appendCodePoint(c); + } + } + } + + public line getHeader() { + return header; + } + + @Override + public Iterator iterator() { + return new Iterator() { + line next; + + @Override + public boolean hasNext() { + try { + next = readRow(ncol); + } catch (IOException ex) { + ex.printStackTrace(); + } + return next != null; + } + + @Override + public line next() { + return next; + } + }; + } + + @Override + public Spliterator spliterator() { + return Spliterators.spliteratorUnknownSize(iterator(), Spliterator.NONNULL | Spliterator.ORDERED); + } + + @Override + public void close() throws IOException { + reader.close(); + } + + } + +} diff --git a/src/notzed.csvedit/classes/au/notzed/csvedit/JPopupButton.java b/src/notzed.csvedit/classes/au/notzed/csvedit/JPopupButton.java new file mode 100644 index 0000000..53c5692 --- /dev/null +++ b/src/notzed.csvedit/classes/au/notzed/csvedit/JPopupButton.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2022 Michael Zucchi + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package au.notzed.csvedit; + +import java.awt.event.ActionEvent; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.util.function.Supplier; +import javax.swing.Action; +import javax.swing.JButton; +import javax.swing.JPopupMenu; +import javax.swing.JSeparator; +import javax.swing.Timer; +import javax.swing.UIManager; + +/** + * A button that works like a menu, with an optional popup delay that allows a base action. + */ +public class JPopupButton extends JButton { + Timer timer; + JPopupMenu menu; + + public JPopupButton(Action a, Supplier populate) { + this(a, 300, populate); + } + + public JPopupButton(Action a, int timeout, Supplier populate) { + super(a); + + setIcon(UIManager.getDefaults().getIcon("Table.descendingSortIcon")); + + model.addActionListener((ActionEvent e) -> { + if (timer != null) { + timer.stop(); + timer = null; + } + }); + + addMouseListener(new MouseListener() { + + @Override + public void mouseClicked(MouseEvent e) { + timer.stop(); + timer = null; + } + + @Override + public void mousePressed(MouseEvent e) { + timer = new Timer(300, to -> { + timer = null; + model.setArmed(false); + + menu = populate.get(); + + menu.insert(getAction(), 0); + menu.insert(new JSeparator(), 1); + + menu.show(JPopupButton.this, 0, 0);//getHeight()); + }); + timer.setRepeats(false); + timer.start(); + } + + @Override + public void mouseReleased(MouseEvent e) { + if (timer != null) { + timer.stop(); + timer = null; + } + if (menu != null) { + menu.setVisible(false); + } + } + + @Override + public void mouseEntered(MouseEvent e) { + } + + @Override + public void mouseExited(MouseEvent e) { + } + }); + } + + @Override + protected void processMouseEvent(MouseEvent e) { + super.processMouseEvent(e); + } + +} diff --git a/src/notzed.csvedit/classes/au/notzed/csvedit/RecentList.java b/src/notzed.csvedit/classes/au/notzed/csvedit/RecentList.java new file mode 100644 index 0000000..7277b31 --- /dev/null +++ b/src/notzed.csvedit/classes/au/notzed/csvedit/RecentList.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2022 Michael Zucchi + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package au.notzed.csvedit; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.prefs.BackingStoreException; + +public class RecentList extends ArrayList { + String key; + int historyLimit = 16; + + public RecentList(String key) { + this.key = key; + read(); + } + + private String entryKey(int index) { + return "recent." + key + "." + index; + } + + private void read() { + clear(); + + byte[] ba = CE.prefs().getByteArray(key, null); + if (ba != null) { + try ( ObjectInputStream is = new ObjectInputStream(new ByteArrayInputStream(ba))) { + int len = is.readInt(); + for (int i = 0; i < len; i++) { + String val = is.readUTF(); + Path path = Path.of(val); + if (Files.isRegularFile(path)) { + add(path); + } + } + } catch (IOException ex) { + ex.printStackTrace(); + } + } + } + + private void write() { + try ( ByteArrayOutputStream bos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(bos)) { + oos.writeInt(size()); + for (Path p: this) { + oos.writeUTF(p.toString()); + } + oos.flush(); + CE.prefs().putByteArray(key, bos.toByteArray()); + CE.prefs().sync(); + } catch (IOException ex) { + ex.printStackTrace(); + } catch (BackingStoreException ex) { + ex.printStackTrace(); + javax.swing.plaf.basic.BasicTableUI x; + } + } + + public void addRecent(Path path) { + path = path.toAbsolutePath(); + remove(path); + add(0, path); + if (size() > historyLimit) + remove(size() - 1); + write(); + } + + /* + private void readx() { + + int len = CE.getInt("recent." + key, 0); + for (int i = 0; i < len; i++) { + String val = CE.getString(entryKey(i), null); + if (val != null) { + Path path = Path.of(val); + if (Files.isRegularFile(path)) { + add(path); + } + } + } + } + + public void writex() { + int len = CE.getInt("recent." + key, 0); + + for (int i = 0; i < size(); i++) + System.setProperty(entryKey(i), get(i).toAbsolutePath().toString()); + for (int i = size(); i < len; i++) + System.clearProperty(entryKey(i)); + + CE.setInt("recent." + key, len); + System.setProperty("recent." + key, String.valueOf(size())); + } + + */ +} diff --git a/src/notzed.csvedit/classes/au/notzed/csvedit/SimpleAction.java b/src/notzed.csvedit/classes/au/notzed/csvedit/SimpleAction.java new file mode 100644 index 0000000..6acacb7 --- /dev/null +++ b/src/notzed.csvedit/classes/au/notzed/csvedit/SimpleAction.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2022 Michael Zucchi + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package au.notzed.csvedit; + +import java.awt.event.ActionEvent; +import javax.swing.AbstractAction; +import javax.swing.Icon; + +/** + * Action which calls a lambdaerable interface. + *

+ */ +public class SimpleAction extends AbstractAction { + ActionFunc func; + + public SimpleAction(ActionFunc func) { + this.func = func; + } + + public SimpleAction(String name, ActionFunc func) { + super(name); + this.func = func; + } + + public SimpleAction(String name, Icon icon, ActionFunc func) { + super(name, icon); + this.func = func; + } + + public interface ActionFunc { + void action(ActionEvent e); + } + + public static SimpleAction of(ActionFunc func) { + return new SimpleAction(func); + } + + public static SimpleAction of(String name, ActionFunc func) { + return new SimpleAction(name, func); + } + + @Override + public void actionPerformed(ActionEvent e) { + func.action(e); + } +} diff --git a/src/notzed.csvedit/classes/au/notzed/csvedit/TableEditor.java b/src/notzed.csvedit/classes/au/notzed/csvedit/TableEditor.java new file mode 100644 index 0000000..fc75d0b --- /dev/null +++ b/src/notzed.csvedit/classes/au/notzed/csvedit/TableEditor.java @@ -0,0 +1,357 @@ +/* + * Copyright (C) 2022 Michael Zucchi + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package au.notzed.csvedit; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Component; +import java.awt.Dimension; +import java.awt.event.ActionEvent; +import java.awt.event.KeyEvent; +import java.io.IOException; +import java.nio.file.Path; +import java.util.EventListener; +import java.util.Vector; +import javax.swing.Action; +import javax.swing.ActionMap; +import javax.swing.BorderFactory; +import javax.swing.JComponent; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTable; +import javax.swing.KeyStroke; +import javax.swing.ListSelectionModel; +import javax.swing.border.Border; +import javax.swing.event.EventListenerList; +import javax.swing.event.TableModelEvent; +import javax.swing.table.DefaultTableCellRenderer; +import javax.swing.table.DefaultTableModel; +import javax.swing.table.TableCellEditor; +import javax.swing.table.TableCellRenderer; +import javax.swing.table.TableColumn; + +public class TableEditor extends JPanel { + CSVIO.Data data; + JTable table; + DefaultTableModel tm; + boolean modified; + EventListenerList listeners; + + public TableEditor() { + super(new BorderLayout()); + + table = new JTable(); + //table.setAutoCreateColumnsFromModel(false); + table.setAutoCreateRowSorter(false); + table.setGridColor(new Color(0xcccccc)); + table.setColumnSelectionAllowed(false); + table.setSurrendersFocusOnKeystroke(true); + table.setDefaultRenderer(Object.class, new CellRenderer()); + table.setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION); + + JScrollPane tableScroll = new JScrollPane(table); + tableScroll.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS); + tableScroll.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS); + + add(tableScroll, BorderLayout.CENTER); + + ActionMap map = table.getActionMap(); + map.put(clearAction.getValue(Action.NAME), clearAction); + + initKeys(); + + table.setTransferHandler(new TableTransferHandler()); + + table.putClientProperty("JTable.autoStartsEdit", Boolean.FALSE); + } + + static public Action getClearAction() { + return clearAction; + } + + static boolean clearOk(JTable table, int rows[]) { + final String[] options = {"Clear", "Cancel"}; + + // Hack: by default pretty much every key engages editing, stop it first. + // Not sure why accelerator isn't swallowing it? + TableCellEditor cellEditor = table.getCellEditor(); + if (cellEditor != null) { + System.out.println("Editor running"); + cellEditor.cancelCellEditing(); + } + + return Utils.isBlankRows(table.getModel(), rows) + || JOptionPane.showOptionDialog(table, "Rows contain data.", "Clear Warning", + JOptionPane.YES_NO_OPTION, + JOptionPane.WARNING_MESSAGE, + null, //do not use a custom Icon + options, //the titles of buttons + options[1]) == JOptionPane.YES_OPTION; + } + + static final Action clearAction = SimpleAction.of("clear", (e) -> { + + if (e.getSource() instanceof JTable table) { + int[] rows = table.getSelectedRows(); + DefaultTableModel tm = (DefaultTableModel)table.getModel(); + + if (clearOk(table, rows)) + Utils.blankRows(tm, rows); + } + }); + + private void startEditing(int row, int col) { + table.getColumnModel().getSelectionModel().setSelectionInterval(col, col); + table.getSelectionModel().setSelectionInterval(row, row); + table.scrollRectToVisible(table.getCellRect(row, col, true)); + + if (table.editCellAt(row, col)) + table.getEditorComponent().requestFocusInWindow(); + } + + private void initKeys() { + // If editing, advance to next cell to edit. + // If on last cell, add a new row. + // If not editing on last cell, do nothing. + overrideKey(table, KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 0), (jtable, row, col, e, a) -> { + int rows = table.getRowCount(); + int cols = table.getColumnCount(); + + Component editor = table.getEditorComponent(); + if (editor != null) { + + if (col < cols - 1) { + col++; + } else { + col = 0; + row++; + } + + if (row >= rows) + tm.addRow(new Object[]{}); + + //a.actionPerformed(e); + startEditing(row, col); + + return true; + } else { + return row == rows - 1 && col == cols - 1; + } + }); + + // Enter enters edit mode + overrideKey(table, KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), (jtable, row, col, e, a) -> { + startEditing(row, col); + return true; + }); + // S-enter appends new row and begins editing + overrideKey(table, KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, KeyEvent.SHIFT_DOWN_MASK), (jtable, row, col, e, a) -> { + row += 1; + tm.insertRow(row, new Object[]{}); + + startEditing(row, 0); + + return true; + }); + // C-S-enter prepends new row and begins editing + // this doesn't seem to work + overrideKey(table, KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, KeyEvent.SHIFT_DOWN_MASK | KeyEvent.CTRL_DOWN_MASK), (jtable, row, col, e, a) -> { + row = Math.max(0, row - 1); + tm.insertRow(row, new Object[]{}); + startEditing(row, 0); + return true; + }); + + } + + public interface TableModifiedListener extends EventListener { + public void tableModified(TableEditor editor); + } + + public void addModifiedListener(TableModifiedListener l) { + if (listeners == null) + listeners = new EventListenerList(); + listeners.add(TableModifiedListener.class, l); + } + + public void removeListener(TableModifiedListener l) { + if (listeners != null) { + listeners.remove(TableModifiedListener.class, l); + if (listeners.getListenerCount() == 0) + listeners = null; + } + } + + public void fireModified() { + if (listeners != null) { + for (TableModifiedListener l: listeners.getListeners(TableModifiedListener.class)) { + l.tableModified(this); + } + } + } + + public void load(Path file) throws IOException { + System.out.print("Loading: "); + System.out.println(file); + + data = CSVIO.loadData(file); + + tm = new DefaultTableModel(data.rows(), data.headers()); + + tm.addTableModelListener((TableModelEvent e) -> { + boolean old = modified; + modified = true; + if (!old) + fireModified(); + }); + + table.setModel(tm); + adjustColumns(table); + + modified = false; + fireModified(); + } + + /** + * This saves as but doesn't change the name? + * + * @param path + * @throws IOException + */ + public void saveAs(Path path) throws IOException { + cleanData(); + CSVIO.saveData(path, data); + } + + public void save() throws IOException { + if (modified) { + System.out.print("Saving: "); + System.out.println(data.path()); + saveAs(data.path()); + modified = false; + fireModified(); + } + } + + static String EMPTY = ""; + + // remove empty rows, make sure they match the column count + // FIXME:L move to utils + public void cleanData() { + int ncols = tm.getColumnCount(); + int nrows = tm.getRowCount(); + + for (int i = 0; i < nrows; i++) { + int nempty = 0; + Vector row = tm.getDataVector().get(i); + + row.setSize(ncols); + for (int j = 0; j < ncols; j++) { + if (row.get(j) == null) { + tm.setValueAt(EMPTY, i, j); + } + if (row.get(j).isBlank()) + nempty++; + } + if (nempty == ncols) { + System.out.println("empty row " + i); + tm.removeRow(i); + nrows -= 1; + i -= 1; + } + } + } + + public interface KeyCellFunction { + boolean action(JTable table, int row, int col, ActionEvent e, Action defaultAction); + } + + public static void overrideKey(JComponent comp, KeyStroke keyStroke, KeyCellFunction action) { + Object actionKey = comp.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).get(keyStroke); + if (actionKey != null) { + Action defaultAction = comp.getActionMap().get(actionKey); + comp.getActionMap().put(actionKey, SimpleAction.of(e -> { + JTable table = (JTable)e.getSource(); + int row = table.getSelectionModel().getLeadSelectionIndex(); + int col = table.getColumnModel().getSelectionModel().getLeadSelectionIndex(); + + if (!action.action(table, row, col, e, defaultAction)) + defaultAction.actionPerformed(e); + })); + } else { + comp.getActionMap().put(actionKey, SimpleAction.of(e -> { + JTable table = (JTable)e.getSource(); + int row = table.getSelectionModel().getLeadSelectionIndex(); + int col = table.getColumnModel().getSelectionModel().getLeadSelectionIndex(); + + action.action(table, row, col, e, null); + })); + } + } + + static class CellRenderer extends DefaultTableCellRenderer { + + Border border = BorderFactory.createLineBorder(Color.red, 1); + + @Override + public Component getTableCellRendererComponent( + JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { + super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); + + if (isSelected) + setBackground(table.getSelectionBackground()); + else { + setBackground(table.getBackground()); + } + if (hasFocus) { + setBorder(border); + } + + return this; + } + } + + void adjustColumns(JTable table) { + int rowHeight = 6; + Dimension spacing = table.getIntercellSpacing(); + for (int column = 0; column < table.getColumnCount(); column++) { + TableColumn tableColumn = table.getColumnModel().getColumn(column); + int preferredWidth = tableColumn.getMinWidth(); + int maxWidth = tableColumn.getMaxWidth(); + + for (int row = 0; row < table.getRowCount(); row++) { + TableCellRenderer cellRenderer = table.getCellRenderer(row, column); + Component c = table.prepareRenderer(cellRenderer, row, column); + Dimension size = c.getPreferredSize(); + int width = size.width + spacing.width; + preferredWidth = Math.max(preferredWidth, width); + + rowHeight = Math.max(rowHeight, size.height); + + if (preferredWidth >= maxWidth) { + preferredWidth = maxWidth; + break; + } + } + + tableColumn.setPreferredWidth(preferredWidth + 16); + } + table.setRowHeight(rowHeight + 3); + } + +} diff --git a/src/notzed.csvedit/classes/au/notzed/csvedit/TableTransferHandler.java b/src/notzed.csvedit/classes/au/notzed/csvedit/TableTransferHandler.java new file mode 100644 index 0000000..a51a447 --- /dev/null +++ b/src/notzed.csvedit/classes/au/notzed/csvedit/TableTransferHandler.java @@ -0,0 +1,201 @@ +/* + * Copyright (C) 2022 Michael Zucchi + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package au.notzed.csvedit; + +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.StringSelection; +import java.awt.datatransfer.Transferable; +import java.awt.datatransfer.UnsupportedFlavorException; +import java.io.IOException; +import java.util.Arrays; +import java.util.Vector; +import javax.swing.JComponent; +import javax.swing.JOptionPane; +import javax.swing.JTable; +import javax.swing.TransferHandler; +import static javax.swing.TransferHandler.COPY; +import static javax.swing.TransferHandler.COPY_OR_MOVE; +import static javax.swing.TransferHandler.MOVE; +import static javax.swing.TransferHandler.NONE; +import javax.swing.table.DefaultTableModel; +import javax.swing.table.TableModel; + +/** + * Transfer handler for JTable that supports all of CUT, COPY, and PASTE. + *

+ * Must be using DefaultTableModel. + * Only supports single-range selection. + * + * @note Sorting and filtering support is not implemented, requires + * use of: table.getRowSorter().convertRowIndexToModel(xx) + * + */ +public class TableTransferHandler extends TransferHandler { + @Override + public boolean canImport(TransferHandler.TransferSupport info) { + return !info.isDrop() + && info.isDataFlavorSupported(DataFlavor.stringFlavor); + } + + @Override + public int getSourceActions(JComponent c) { + return COPY_OR_MOVE; + } + + private boolean pasteOk(JTable table, String[][] data, int dst) { + TableModel tm = table.getModel(); + boolean isblank = true; + boolean istrunc = false; + + for (int i = 0; i < data.length; i++) { + String[] row = data[i]; + + isblank &= Utils.isBlankRow(tm, dst + i); + istrunc |= row.length > tm.getColumnCount(); + } + + if (!isblank || istrunc) { + StringBuilder sb = new StringBuilder("

Pasting here will lose some data

"); + + if (!isblank) + sb.append("

  Overwriting some existing cells.

"); + if (istrunc) + sb.append("

  Truncating some data columns.

"); + + sb.append("

Proceed with paste?

"); + + final String[] options = {"Paste", "Cancel"}; + + return JOptionPane.showOptionDialog(table, sb, "Paste Warning", + JOptionPane.YES_NO_OPTION, + JOptionPane.WARNING_MESSAGE, + null, //do not use a custom Icon + options, //the titles of buttons + options[1]) == JOptionPane.YES_OPTION; + } + + return true; + } + + /** + * Handle PASTE. + * + * @param info + * @return + */ + @Override + public boolean importData(TransferHandler.TransferSupport info) { + JTable table = (JTable)info.getComponent(); + DefaultTableModel tm = (DefaultTableModel)table.getModel(); + + try { + Transferable t = info.getTransferable(); + if (t.isDataFlavorSupported(DataFlavor.stringFlavor)) { + String data = (String)t.getTransferData(DataFlavor.stringFlavor); + + // Emulate a spreadsheet paste: + // - if the paste is smaller than the selection cycle through the paste rows + // - if the paste is larger than the selection keep going + String[][] rows = Utils.rowscols(data); + int[] sel = table.getSelectedRows(); + int ins = sel.length == 0 ? tm.getRowCount() : sel[0]; + + if (pasteOk(table, rows, ins)) { + int ncols = tm.getColumnCount(); + int nrows = Math.max(rows.length, sel.length); + + for (int k = 0; k < nrows; k++) { + int i = k % rows.length; + String[] row = rows[i]; + int len = Math.min(row.length, ncols); + + if (ins >= tm.getRowCount()) + tm.addRow(new Vector<>(ncols)); + + for (int j = 0; j < len; j++) + tm.setValueAt(row[j], ins, j); + ins++; + } + return true; + } else { + System.out.println("paste cancelled by user"); + } + } + } catch (UnsupportedFlavorException ex) { + ex.printStackTrace(); + } catch (IOException ex) { + ex.printStackTrace(); + } + + return false; + } + + /** + * Handle COPY and MOVE. + * + * @param c + * @return + */ + @Override + protected Transferable createTransferable(JComponent c) { + JTable table = (JTable)c; + int rows[] = table.getSelectedRows(); + int cols = table.getColumnCount(); + + if (cols == 0 || rows.length == 0) + return null; + StringBuilder text = new StringBuilder(); + for (int i = 0; i < rows.length; i++) { + int ii = rows[i]; + text.append(table.getValueAt(ii, 0)); + for (int j = 1; j < cols; j++) { + text.append("\t"); + text.append(table.getValueAt(ii, j)); + } + text.append("\n"); + } + return new StringSelection(text.toString()); + } + + /** + * Called after createTransferable, deletes any CUT data. + * + * @param source + * @param data + * @param action + */ + @Override + protected void exportDone(JComponent source, Transferable data, int action) { + JTable table = (JTable)source; + DefaultTableModel tm = (DefaultTableModel)table.getModel(); + + switch (action) { + case MOVE: + // Ensure we remove from end so that row numbers stay valid + int[] rows = table.getSelectedRows(); + Arrays.sort(rows); + for (int i = rows.length - 1; i >= 0; i--) + tm.removeRow(rows[i]); + break; + case COPY: + break; + case NONE: + break; + } + } + +} diff --git a/src/notzed.csvedit/classes/au/notzed/csvedit/TransferActionListener.java b/src/notzed.csvedit/classes/au/notzed/csvedit/TransferActionListener.java new file mode 100644 index 0000000..e8f76b4 --- /dev/null +++ b/src/notzed.csvedit/classes/au/notzed/csvedit/TransferActionListener.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2022 Michael Zucchi + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package au.notzed.csvedit; + +import java.awt.KeyboardFocusManager; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import javax.swing.Action; +import javax.swing.JComponent; + +public class TransferActionListener implements ActionListener, PropertyChangeListener { + private JComponent focusOwner = null; + + public TransferActionListener() { + KeyboardFocusManager manager = KeyboardFocusManager.getCurrentKeyboardFocusManager(); + manager.addPropertyChangeListener("permanentFocusOwner", this); + } + + public void propertyChange(PropertyChangeEvent e) { + Object o = e.getNewValue(); + if (o instanceof JComponent jComponent) { + focusOwner = jComponent; + } else { + focusOwner = null; + } + } + + public void actionPerformed(ActionEvent e) { + if (focusOwner == null) + return; + + String action = (String)e.getActionCommand(); + Action a = focusOwner.getActionMap().get(action); + System.out.println("action transfer: " + action + " to focus " + focusOwner + " actionmap=" + a); + if (a != null) { + a.actionPerformed(new ActionEvent(focusOwner, ActionEvent.ACTION_PERFORMED, null)); + } + } +} diff --git a/src/notzed.csvedit/classes/au/notzed/csvedit/Utils.java b/src/notzed.csvedit/classes/au/notzed/csvedit/Utils.java new file mode 100644 index 0000000..2f4a4b2 --- /dev/null +++ b/src/notzed.csvedit/classes/au/notzed/csvedit/Utils.java @@ -0,0 +1,250 @@ +/* + * Copyright (C) 2022 Michael Zucchi + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package au.notzed.csvedit; + +import au.notzed.csvedit.CSVIO.CSVReader; +import au.notzed.csvedit.CSVIO.line; +import java.awt.FlowLayout; +import java.io.IOException; +import java.io.StringReader; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.function.IntFunction; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; +import javax.swing.Icon; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.UIManager; +import javax.swing.table.TableModel; + +public class Utils { + + public static Stream rows(String data) { + return StreamSupport.stream(Spliterators.spliteratorUnknownSize(rowsOf(data).iterator(), Spliterator.ORDERED | Spliterator.NONNULL), false); + } + + public static String[] cols(String row, int ncol) { + IntFunction newArray = ncol == Integer.MAX_VALUE ? String[]::new : (n) -> new String[ncol]; + return StreamSupport.stream(Spliterators.spliteratorUnknownSize(fieldsOf(row).iterator(), Spliterator.ORDERED | Spliterator.NONNULL), false) + .limit(ncol) + .toArray(newArray); + // null entries? + } + + public static Stream rowcols(String data, int ncol, boolean noblank) { + return rowcols(rows(data), ncol, noblank); + } + + public static Stream rowcols(Stream is, int ncol, boolean noblank) { + Stream stream = is.map(row -> cols(row, ncol)); + if (noblank) + stream = stream.filter(row -> !isBlankRow(row)); + return stream; + } + + /** + * Read all rows and cols from string, no truncation/etc. + * + * @param data + * @return + */ + public static String[][] rowscols(String data) { + return rowcols(data, Integer.MAX_VALUE, false).toArray(String[][]::new); + } + + public static Iterable rowsOf(String data) { + return () -> { + return new Iterator() { + int pos; + + @Override + public boolean hasNext() { + return pos < data.length(); + } + + @Override + public String next() { + int last = pos; + int eol = pos = data.length(); + + for (int i = last; i < data.length(); i++) { + int c = data.charAt(i); + if (c == '\n') { + pos = i + 1; + eol = i; + break; + } else if (c == '\r') { + if (i < data.length() - 1 && data.charAt(i + 1) == '\n') + pos = i + 2; + else + pos = i + 1; + eol = i; + break; + } + } + + return data.substring(last, eol); + } + }; + }; + } + + public static Iterable fieldsOf(String data) { + return () -> { + return new Iterator() { + int pos; + + @Override + public boolean hasNext() { + return pos < data.length(); + } + + @Override + public String next() { + int last = pos; + int eof = data.indexOf('\t', pos); + + pos = eof != -1 ? eof + 1 : data.length(); + eof = eof != -1 ? eof : data.length(); + + return data.substring(last, eof); + } + }; + }; + } + + public static boolean isBlankRow(String[] row) { + boolean blank = true; + for (int i = 0; blank && i < row.length; i++) { + blank &= row[i] == null || row[i].isBlank(); + } + return blank; + } + + public static boolean isBlankRows(TableModel tm, int[] rows) { + boolean blank = true; + + for (int row: rows) + blank &= isBlankRow(tm, row); + + return blank; + } + + public static boolean isBlankRow(TableModel tm, int row) { + boolean blank = true; + if (row < tm.getRowCount()) { + for (int i = 0; blank && i < tm.getColumnCount(); i++) { + String val = (String)tm.getValueAt(row, i); + blank &= val == null || val.isBlank(); + } + } + return blank; + } + + static String EMPTY = ""; + + public static void blankRows(TableModel tm, int[] rows) { + for (int row: rows) + blankRow(tm, row); + } + + public static void blankRow(TableModel tm, int row) { + if (row < tm.getRowCount()) { + for (int i = 0; i < tm.getColumnCount(); i++) { + tm.setValueAt(EMPTY, row, i); + } + } // add rows? + } + + public static void main(String[] args) { + String data + = "section year name variety region small large bottle\n" + + "red 2021 Umami Ronchi `Podre' Montepulcanio Abruzzo, ITA 10 16 44!!\n" + + "red 2019 Vinetti De Fiorini `Superiore' Chianti Tuscanny, ITA 11 17.5 50!! black 2019 Vinetti De Fiorini `Superiore' Chianti Tuscanny, ITA 11 17.5 50!!\n" + + "\n" + + "red 2021 Bec Hardy Undercover `Pertaringa' Shiraz McLaren Vale, SA 10 16 44!!\n" + + "red 2019 Vinetti De Fiorini `Superiore' Chianti Tuscanny, ITA 11 17.5 50!!\n" + + "red 2019 Vinetti De Fiorini `Superiore' Chianti Tuscanny, ITA 11 17.5 50!!\r" + + "red 2019 Vinetti De Fiorini `Superiore' Chianti Tuscanny, ITA 11 17.5 50!!\r\n" + + " x y\n" + + "red 2021 Main \\& Cherry Tempranillo McLaren Vale/Langhorne Creek 11 17.5 50!!"; + + for (String row: rowsOf(data)) { + System.out.print("'>"); + System.out.print(row); + System.out.println("<'"); + } + + for (String row: rowsOf(data)) { + for (String val: fieldsOf(row)) { + System.out.printf("%-40s ", val); + } + System.out.println(); + } + + System.out.println("\n\nfilter blanks"); + rowcols(data, Integer.MAX_VALUE, true).forEachOrdered(row -> System.out.println(String.join("|", row))); + System.out.println("\ndon't filter blanks"); + rowcols(data, Integer.MAX_VALUE, false).forEachOrdered(row -> System.out.println(String.join("|", row))); + + if (false) { + System.out.println("\n\nlast thing\n"); + try ( CSVReader reader = new CSVReader(new StringReader(data))) { + System.out.print("Header: >"); + System.out.print(String.join("|", reader.getHeader().data())); + System.out.println("<"); + for (line line: reader) { + System.out.print(String.join("|", line.data())); + if (line.truncated()) { + System.out.print(" +"); + } + System.out.print(" "); + System.out.println(line.eol()); + } + } catch (IOException ex) { + ex.printStackTrace(); + } + } + + if (false) { + JFrame win = new JFrame(); + JPanel panel = new JPanel(); + panel.setLayout(new FlowLayout()); + + Enumeration keys = UIManager.getDefaults().keys(); + while (keys.hasMoreElements()) { + Object key = keys.nextElement(); + Object value = UIManager.get(key); + if (value instanceof Icon icon) { + if (value.getClass().getName().equals("sun.swing.ImageIconUIResource")) { + System.out.printf("key=`%s' type=`%s' value=`%s'\n", key, value.getClass(), value); + panel.add(new JLabel(key.toString(), icon, JLabel.RIGHT)); + } + } + } + win.setContentPane(panel); + win.setSize(800, 1000); + win.setVisible(true); + } + + } + +} diff --git a/src/notzed.csvedit/classes/module-info.java b/src/notzed.csvedit/classes/module-info.java new file mode 100644 index 0000000..7762366 --- /dev/null +++ b/src/notzed.csvedit/classes/module-info.java @@ -0,0 +1,5 @@ + +module notzed.csvedit { + requires java.desktop; + requires java.prefs; +} diff --git a/src/notzed.csvedit/image.linux-amd64/csvedit b/src/notzed.csvedit/image.linux-amd64/csvedit new file mode 100755 index 0000000..b3c8049 --- /dev/null +++ b/src/notzed.csvedit/image.linux-amd64/csvedit @@ -0,0 +1,4 @@ +#!/bin/bash + +export JAVA_HOME=$(dirname $(readlink -f $0)) +exec ${JAVA_HOME}/bin/java -m notzed.csvedit/au.notzed.csvedit.CSVEdit diff --git a/src/notzed.csvedit/image.windows-amd64/csvedit.bat b/src/notzed.csvedit/image.windows-amd64/csvedit.bat new file mode 100644 index 0000000..7d08e0f --- /dev/null +++ b/src/notzed.csvedit/image.windows-amd64/csvedit.bat @@ -0,0 +1,5 @@ +@echo off +set JAVA_HOME=%~dp0 +rem "%JAVA_HOME%\bin\java" -m notzed.csvedit/au.notzed.csvedit.CSVEdit +rem stops cmd window staying visible +start /min "csvedit" "%JAVA_HOME%\bin\java" -m notzed.csvedit/au.notzed.csvedit.CSVEdit