lhae (empty) → 0.0.2
raw patch · 64 files changed
+6444/−0 lines, 64 filesdep +arraydep +basedep +containersbuild-type:Customsetup-changed
Dependencies added: array, base, containers, directory, filepath, hgettext, mtl, parsec, process, setlocale, utf8-string, wx, wxcore
Files
- COPYING +674/−0
- Setup.hs +8/−0
- i18n/de.po +473/−0
- i18n/en.po +493/−0
- i18n/messages.pot +469/−0
- lhae.cabal +101/−0
- src/CellCoordinate.hs +11/−0
- src/Config.hs +94/−0
- src/Constants.hs +23/−0
- src/Controller.hs +50/−0
- src/Controller/Canonical.hs +66/−0
- src/Controller/Cell.hs +32/−0
- src/Controller/Dialog.hs +135/−0
- src/Controller/FormulaInput.hs +15/−0
- src/Controller/Grid.hs +163/−0
- src/Controller/Menu/File/Export/Diagram/BarChart.hs +99/−0
- src/Controller/Menu/File/Export/Diagram/BarChartParameters.hs +69/−0
- src/Controller/Menu/File/Export/Diagram/BoxPlot.hs +99/−0
- src/Controller/Menu/File/Export/Diagram/BoxPlotParameters.hs +33/−0
- src/Controller/Menu/File/Export/Diagram/Common.hs +75/−0
- src/Controller/Menu/File/Export/Image.hs +247/−0
- src/Controller/Menu/File/Export/ImageParameters.hs +83/−0
- src/Controller/Menu/File/Import/Csv.hs +100/−0
- src/Controller/Menu/File/Import/CsvParameters.hs +55/−0
- src/Controller/Menu/File/NewClose.hs +34/−0
- src/Controller/Menu/File/Open.hs +59/−0
- src/Controller/Menu/File/Save.hs +52/−0
- src/Controller/Menu/Statistics/Descriptive.hs +114/−0
- src/Controller/Menu/Statistics/FrequencyDistribution.hs +36/−0
- src/Controller/Menu/Statistics/Pivot.hs +146/−0
- src/Controller/Menu/Table/Add.hs +82/−0
- src/Controller/Menu/Table/Copy.hs +22/−0
- src/Controller/Menu/Table/Delete.hs +28/−0
- src/Controller/Menu/Table/Reevaluate.hs +18/−0
- src/Controller/Menu/Table/Transpose.hs +41/−0
- src/Controller/SaveConfig.hs +12/−0
- src/I18n.hs +18/−0
- src/Main.hs +14/−0
- src/Model.hs +48/−0
- src/Model/Cell.hs +46/−0
- src/Model/CellContent.hs +99/−0
- src/Model/CellExpression/Evaluator.hs +50/−0
- src/Model/CellExpression/Evaluator/Common.hs +43/−0
- src/Model/CellExpression/Evaluator/Math.hs +47/−0
- src/Model/CellExpression/Parser.hs +169/−0
- src/Model/Grid.hs +248/−0
- src/Model/GridHeader.hs +46/−0
- src/Run.hs +159/−0
- src/Util.hs +46/−0
- src/Util/Color.hs +32/−0
- src/Util/DynArray.hs +172/−0
- src/Util/FileType.hs +64/−0
- src/Util/Gnuplot.hs +12/−0
- src/Util/Sexp.hs +83/−0
- src/Util/Sexpable.hs +86/−0
- src/View.hs +33/−0
- src/View/Component/Grid.hs +124/−0
- src/View/Component/Notebook.hs +65/−0
- src/View/Component/TextWindow.hs +24/−0
- src/View/Dialog/Complex.hs +248/−0
- src/View/Dialog/Simple.hs +23/−0
- src/View/FormulaInput.hs +16/−0
- src/View/GridPage.hs +96/−0
- src/View/Modes.hs +22/−0
+ COPYING view
@@ -0,0 +1,674 @@+ GNU GENERAL PUBLIC LICENSE+ Version 3, 29 June 2007++ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>+ Everyone is permitted to copy and distribute verbatim copies+ of this license document, but changing it is not allowed.++ Preamble++ The GNU General Public License is a free, copyleft license for+software and other kinds of works.++ The licenses for most software and other practical works are designed+to take away your freedom to share and change the works. By contrast,+the GNU General Public License is intended to guarantee your freedom to+share and change all versions of a program--to make sure it remains free+software for all its users. We, the Free Software Foundation, use the+GNU General Public License for most of our software; it applies also to+any other work released this way by its authors. You can apply it to+your programs, too.++ When we speak of free software, we are referring to freedom, not+price. Our General Public Licenses are designed to make sure that you+have the freedom to distribute copies of free software (and charge for+them if you wish), that you receive source code or can get it if you+want it, that you can change the software or use pieces of it in new+free programs, and that you know you can do these things.++ To protect your rights, we need to prevent others from denying you+these rights or asking you to surrender the rights. Therefore, you have+certain responsibilities if you distribute copies of the software, or if+you modify it: responsibilities to respect the freedom of others.++ For example, if you distribute copies of such a program, whether+gratis or for a fee, you must pass on to the recipients the same+freedoms that you received. You must make sure that they, too, receive+or can get the source code. And you must show them these terms so they+know their rights.++ Developers that use the GNU GPL protect your rights with two steps:+(1) assert copyright on the software, and (2) offer you this License+giving you legal permission to copy, distribute and/or modify it.++ For the developers' and authors' protection, the GPL clearly explains+that there is no warranty for this free software. For both users' and+authors' sake, the GPL requires that modified versions be marked as+changed, so that their problems will not be attributed erroneously to+authors of previous versions.++ Some devices are designed to deny users access to install or run+modified versions of the software inside them, although the manufacturer+can do so. This is fundamentally incompatible with the aim of+protecting users' freedom to change the software. The systematic+pattern of such abuse occurs in the area of products for individuals to+use, which is precisely where it is most unacceptable. Therefore, we+have designed this version of the GPL to prohibit the practice for those+products. If such problems arise substantially in other domains, we+stand ready to extend this provision to those domains in future versions+of the GPL, as needed to protect the freedom of users.++ Finally, every program is threatened constantly by software patents.+States should not allow patents to restrict development and use of+software on general-purpose computers, but in those that do, we wish to+avoid the special danger that patents applied to a free program could+make it effectively proprietary. To prevent this, the GPL assures that+patents cannot be used to render the program non-free.++ The precise terms and conditions for copying, distribution and+modification follow.++ TERMS AND CONDITIONS++ 0. Definitions.++ "This License" refers to version 3 of the GNU General Public License.++ "Copyright" also means copyright-like laws that apply to other kinds of+works, such as semiconductor masks.++ "The Program" refers to any copyrightable work licensed under this+License. Each licensee is addressed as "you". "Licensees" and+"recipients" may be individuals or organizations.++ To "modify" a work means to copy from or adapt all or part of the work+in a fashion requiring copyright permission, other than the making of an+exact copy. The resulting work is called a "modified version" of the+earlier work or a work "based on" the earlier work.++ A "covered work" means either the unmodified Program or a work based+on the Program.++ To "propagate" a work means to do anything with it that, without+permission, would make you directly or secondarily liable for+infringement under applicable copyright law, except executing it on a+computer or modifying a private copy. Propagation includes copying,+distribution (with or without modification), making available to the+public, and in some countries other activities as well.++ To "convey" a work means any kind of propagation that enables other+parties to make or receive copies. Mere interaction with a user through+a computer network, with no transfer of a copy, is not conveying.++ An interactive user interface displays "Appropriate Legal Notices"+to the extent that it includes a convenient and prominently visible+feature that (1) displays an appropriate copyright notice, and (2)+tells the user that there is no warranty for the work (except to the+extent that warranties are provided), that licensees may convey the+work under this License, and how to view a copy of this License. If+the interface presents a list of user commands or options, such as a+menu, a prominent item in the list meets this criterion.++ 1. Source Code.++ The "source code" for a work means the preferred form of the work+for making modifications to it. "Object code" means any non-source+form of a work.++ A "Standard Interface" means an interface that either is an official+standard defined by a recognized standards body, or, in the case of+interfaces specified for a particular programming language, one that+is widely used among developers working in that language.++ The "System Libraries" of an executable work include anything, other+than the work as a whole, that (a) is included in the normal form of+packaging a Major Component, but which is not part of that Major+Component, and (b) serves only to enable use of the work with that+Major Component, or to implement a Standard Interface for which an+implementation is available to the public in source code form. A+"Major Component", in this context, means a major essential component+(kernel, window system, and so on) of the specific operating system+(if any) on which the executable work runs, or a compiler used to+produce the work, or an object code interpreter used to run it.++ The "Corresponding Source" for a work in object code form means all+the source code needed to generate, install, and (for an executable+work) run the object code and to modify the work, including scripts to+control those activities. However, it does not include the work's+System Libraries, or general-purpose tools or generally available free+programs which are used unmodified in performing those activities but+which are not part of the work. For example, Corresponding Source+includes interface definition files associated with source files for+the work, and the source code for shared libraries and dynamically+linked subprograms that the work is specifically designed to require,+such as by intimate data communication or control flow between those+subprograms and other parts of the work.++ The Corresponding Source need not include anything that users+can regenerate automatically from other parts of the Corresponding+Source.++ The Corresponding Source for a work in source code form is that+same work.++ 2. Basic Permissions.++ All rights granted under this License are granted for the term of+copyright on the Program, and are irrevocable provided the stated+conditions are met. This License explicitly affirms your unlimited+permission to run the unmodified Program. The output from running a+covered work is covered by this License only if the output, given its+content, constitutes a covered work. This License acknowledges your+rights of fair use or other equivalent, as provided by copyright law.++ You may make, run and propagate covered works that you do not+convey, without conditions so long as your license otherwise remains+in force. You may convey covered works to others for the sole purpose+of having them make modifications exclusively for you, or provide you+with facilities for running those works, provided that you comply with+the terms of this License in conveying all material for which you do+not control copyright. Those thus making or running the covered works+for you must do so exclusively on your behalf, under your direction+and control, on terms that prohibit them from making any copies of+your copyrighted material outside their relationship with you.++ Conveying under any other circumstances is permitted solely under+the conditions stated below. Sublicensing is not allowed; section 10+makes it unnecessary.++ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.++ No covered work shall be deemed part of an effective technological+measure under any applicable law fulfilling obligations under article+11 of the WIPO copyright treaty adopted on 20 December 1996, or+similar laws prohibiting or restricting circumvention of such+measures.++ When you convey a covered work, you waive any legal power to forbid+circumvention of technological measures to the extent such circumvention+is effected by exercising rights under this License with respect to+the covered work, and you disclaim any intention to limit operation or+modification of the work as a means of enforcing, against the work's+users, your or third parties' legal rights to forbid circumvention of+technological measures.++ 4. Conveying Verbatim Copies.++ You may convey verbatim copies of the Program's source code as you+receive it, in any medium, provided that you conspicuously and+appropriately publish on each copy an appropriate copyright notice;+keep intact all notices stating that this License and any+non-permissive terms added in accord with section 7 apply to the code;+keep intact all notices of the absence of any warranty; and give all+recipients a copy of this License along with the Program.++ You may charge any price or no price for each copy that you convey,+and you may offer support or warranty protection for a fee.++ 5. Conveying Modified Source Versions.++ You may convey a work based on the Program, or the modifications to+produce it from the Program, in the form of source code under the+terms of section 4, provided that you also meet all of these conditions:++ a) The work must carry prominent notices stating that you modified+ it, and giving a relevant date.++ b) The work must carry prominent notices stating that it is+ released under this License and any conditions added under section+ 7. This requirement modifies the requirement in section 4 to+ "keep intact all notices".++ c) You must license the entire work, as a whole, under this+ License to anyone who comes into possession of a copy. This+ License will therefore apply, along with any applicable section 7+ additional terms, to the whole of the work, and all its parts,+ regardless of how they are packaged. This License gives no+ permission to license the work in any other way, but it does not+ invalidate such permission if you have separately received it.++ d) If the work has interactive user interfaces, each must display+ Appropriate Legal Notices; however, if the Program has interactive+ interfaces that do not display Appropriate Legal Notices, your+ work need not make them do so.++ A compilation of a covered work with other separate and independent+works, which are not by their nature extensions of the covered work,+and which are not combined with it such as to form a larger program,+in or on a volume of a storage or distribution medium, is called an+"aggregate" if the compilation and its resulting copyright are not+used to limit the access or legal rights of the compilation's users+beyond what the individual works permit. Inclusion of a covered work+in an aggregate does not cause this License to apply to the other+parts of the aggregate.++ 6. Conveying Non-Source Forms.++ You may convey a covered work in object code form under the terms+of sections 4 and 5, provided that you also convey the+machine-readable Corresponding Source under the terms of this License,+in one of these ways:++ a) Convey the object code in, or embodied in, a physical product+ (including a physical distribution medium), accompanied by the+ Corresponding Source fixed on a durable physical medium+ customarily used for software interchange.++ b) Convey the object code in, or embodied in, a physical product+ (including a physical distribution medium), accompanied by a+ written offer, valid for at least three years and valid for as+ long as you offer spare parts or customer support for that product+ model, to give anyone who possesses the object code either (1) a+ copy of the Corresponding Source for all the software in the+ product that is covered by this License, on a durable physical+ medium customarily used for software interchange, for a price no+ more than your reasonable cost of physically performing this+ conveying of source, or (2) access to copy the+ Corresponding Source from a network server at no charge.++ c) Convey individual copies of the object code with a copy of the+ written offer to provide the Corresponding Source. This+ alternative is allowed only occasionally and noncommercially, and+ only if you received the object code with such an offer, in accord+ with subsection 6b.++ d) Convey the object code by offering access from a designated+ place (gratis or for a charge), and offer equivalent access to the+ Corresponding Source in the same way through the same place at no+ further charge. You need not require recipients to copy the+ Corresponding Source along with the object code. If the place to+ copy the object code is a network server, the Corresponding Source+ may be on a different server (operated by you or a third party)+ that supports equivalent copying facilities, provided you maintain+ clear directions next to the object code saying where to find the+ Corresponding Source. Regardless of what server hosts the+ Corresponding Source, you remain obligated to ensure that it is+ available for as long as needed to satisfy these requirements.++ e) Convey the object code using peer-to-peer transmission, provided+ you inform other peers where the object code and Corresponding+ Source of the work are being offered to the general public at no+ charge under subsection 6d.++ A separable portion of the object code, whose source code is excluded+from the Corresponding Source as a System Library, need not be+included in conveying the object code work.++ A "User Product" is either (1) a "consumer product", which means any+tangible personal property which is normally used for personal, family,+or household purposes, or (2) anything designed or sold for incorporation+into a dwelling. In determining whether a product is a consumer product,+doubtful cases shall be resolved in favor of coverage. For a particular+product received by a particular user, "normally used" refers to a+typical or common use of that class of product, regardless of the status+of the particular user or of the way in which the particular user+actually uses, or expects or is expected to use, the product. A product+is a consumer product regardless of whether the product has substantial+commercial, industrial or non-consumer uses, unless such uses represent+the only significant mode of use of the product.++ "Installation Information" for a User Product means any methods,+procedures, authorization keys, or other information required to install+and execute modified versions of a covered work in that User Product from+a modified version of its Corresponding Source. The information must+suffice to ensure that the continued functioning of the modified object+code is in no case prevented or interfered with solely because+modification has been made.++ If you convey an object code work under this section in, or with, or+specifically for use in, a User Product, and the conveying occurs as+part of a transaction in which the right of possession and use of the+User Product is transferred to the recipient in perpetuity or for a+fixed term (regardless of how the transaction is characterized), the+Corresponding Source conveyed under this section must be accompanied+by the Installation Information. But this requirement does not apply+if neither you nor any third party retains the ability to install+modified object code on the User Product (for example, the work has+been installed in ROM).++ The requirement to provide Installation Information does not include a+requirement to continue to provide support service, warranty, or updates+for a work that has been modified or installed by the recipient, or for+the User Product in which it has been modified or installed. Access to a+network may be denied when the modification itself materially and+adversely affects the operation of the network or violates the rules and+protocols for communication across the network.++ Corresponding Source conveyed, and Installation Information provided,+in accord with this section must be in a format that is publicly+documented (and with an implementation available to the public in+source code form), and must require no special password or key for+unpacking, reading or copying.++ 7. Additional Terms.++ "Additional permissions" are terms that supplement the terms of this+License by making exceptions from one or more of its conditions.+Additional permissions that are applicable to the entire Program shall+be treated as though they were included in this License, to the extent+that they are valid under applicable law. If additional permissions+apply only to part of the Program, that part may be used separately+under those permissions, but the entire Program remains governed by+this License without regard to the additional permissions.++ When you convey a copy of a covered work, you may at your option+remove any additional permissions from that copy, or from any part of+it. (Additional permissions may be written to require their own+removal in certain cases when you modify the work.) You may place+additional permissions on material, added by you to a covered work,+for which you have or can give appropriate copyright permission.++ Notwithstanding any other provision of this License, for material you+add to a covered work, you may (if authorized by the copyright holders of+that material) supplement the terms of this License with terms:++ a) Disclaiming warranty or limiting liability differently from the+ terms of sections 15 and 16 of this License; or++ b) Requiring preservation of specified reasonable legal notices or+ author attributions in that material or in the Appropriate Legal+ Notices displayed by works containing it; or++ c) Prohibiting misrepresentation of the origin of that material, or+ requiring that modified versions of such material be marked in+ reasonable ways as different from the original version; or++ d) Limiting the use for publicity purposes of names of licensors or+ authors of the material; or++ e) Declining to grant rights under trademark law for use of some+ trade names, trademarks, or service marks; or++ f) Requiring indemnification of licensors and authors of that+ material by anyone who conveys the material (or modified versions of+ it) with contractual assumptions of liability to the recipient, for+ any liability that these contractual assumptions directly impose on+ those licensors and authors.++ All other non-permissive additional terms are considered "further+restrictions" within the meaning of section 10. If the Program as you+received it, or any part of it, contains a notice stating that it is+governed by this License along with a term that is a further+restriction, you may remove that term. If a license document contains+a further restriction but permits relicensing or conveying under this+License, you may add to a covered work material governed by the terms+of that license document, provided that the further restriction does+not survive such relicensing or conveying.++ If you add terms to a covered work in accord with this section, you+must place, in the relevant source files, a statement of the+additional terms that apply to those files, or a notice indicating+where to find the applicable terms.++ Additional terms, permissive or non-permissive, may be stated in the+form of a separately written license, or stated as exceptions;+the above requirements apply either way.++ 8. Termination.++ You may not propagate or modify a covered work except as expressly+provided under this License. Any attempt otherwise to propagate or+modify it is void, and will automatically terminate your rights under+this License (including any patent licenses granted under the third+paragraph of section 11).++ However, if you cease all violation of this License, then your+license from a particular copyright holder is reinstated (a)+provisionally, unless and until the copyright holder explicitly and+finally terminates your license, and (b) permanently, if the copyright+holder fails to notify you of the violation by some reasonable means+prior to 60 days after the cessation.++ Moreover, your license from a particular copyright holder is+reinstated permanently if the copyright holder notifies you of the+violation by some reasonable means, this is the first time you have+received notice of violation of this License (for any work) from that+copyright holder, and you cure the violation prior to 30 days after+your receipt of the notice.++ Termination of your rights under this section does not terminate the+licenses of parties who have received copies or rights from you under+this License. If your rights have been terminated and not permanently+reinstated, you do not qualify to receive new licenses for the same+material under section 10.++ 9. Acceptance Not Required for Having Copies.++ You are not required to accept this License in order to receive or+run a copy of the Program. Ancillary propagation of a covered work+occurring solely as a consequence of using peer-to-peer transmission+to receive a copy likewise does not require acceptance. However,+nothing other than this License grants you permission to propagate or+modify any covered work. These actions infringe copyright if you do+not accept this License. Therefore, by modifying or propagating a+covered work, you indicate your acceptance of this License to do so.++ 10. Automatic Licensing of Downstream Recipients.++ Each time you convey a covered work, the recipient automatically+receives a license from the original licensors, to run, modify and+propagate that work, subject to this License. You are not responsible+for enforcing compliance by third parties with this License.++ An "entity transaction" is a transaction transferring control of an+organization, or substantially all assets of one, or subdividing an+organization, or merging organizations. If propagation of a covered+work results from an entity transaction, each party to that+transaction who receives a copy of the work also receives whatever+licenses to the work the party's predecessor in interest had or could+give under the previous paragraph, plus a right to possession of the+Corresponding Source of the work from the predecessor in interest, if+the predecessor has it or can get it with reasonable efforts.++ You may not impose any further restrictions on the exercise of the+rights granted or affirmed under this License. For example, you may+not impose a license fee, royalty, or other charge for exercise of+rights granted under this License, and you may not initiate litigation+(including a cross-claim or counterclaim in a lawsuit) alleging that+any patent claim is infringed by making, using, selling, offering for+sale, or importing the Program or any portion of it.++ 11. Patents.++ A "contributor" is a copyright holder who authorizes use under this+License of the Program or a work on which the Program is based. The+work thus licensed is called the contributor's "contributor version".++ A contributor's "essential patent claims" are all patent claims+owned or controlled by the contributor, whether already acquired or+hereafter acquired, that would be infringed by some manner, permitted+by this License, of making, using, or selling its contributor version,+but do not include claims that would be infringed only as a+consequence of further modification of the contributor version. For+purposes of this definition, "control" includes the right to grant+patent sublicenses in a manner consistent with the requirements of+this License.++ Each contributor grants you a non-exclusive, worldwide, royalty-free+patent license under the contributor's essential patent claims, to+make, use, sell, offer for sale, import and otherwise run, modify and+propagate the contents of its contributor version.++ In the following three paragraphs, a "patent license" is any express+agreement or commitment, however denominated, not to enforce a patent+(such as an express permission to practice a patent or covenant not to+sue for patent infringement). To "grant" such a patent license to a+party means to make such an agreement or commitment not to enforce a+patent against the party.++ If you convey a covered work, knowingly relying on a patent license,+and the Corresponding Source of the work is not available for anyone+to copy, free of charge and under the terms of this License, through a+publicly available network server or other readily accessible means,+then you must either (1) cause the Corresponding Source to be so+available, or (2) arrange to deprive yourself of the benefit of the+patent license for this particular work, or (3) arrange, in a manner+consistent with the requirements of this License, to extend the patent+license to downstream recipients. "Knowingly relying" means you have+actual knowledge that, but for the patent license, your conveying the+covered work in a country, or your recipient's use of the covered work+in a country, would infringe one or more identifiable patents in that+country that you have reason to believe are valid.++ If, pursuant to or in connection with a single transaction or+arrangement, you convey, or propagate by procuring conveyance of, a+covered work, and grant a patent license to some of the parties+receiving the covered work authorizing them to use, propagate, modify+or convey a specific copy of the covered work, then the patent license+you grant is automatically extended to all recipients of the covered+work and works based on it.++ A patent license is "discriminatory" if it does not include within+the scope of its coverage, prohibits the exercise of, or is+conditioned on the non-exercise of one or more of the rights that are+specifically granted under this License. You may not convey a covered+work if you are a party to an arrangement with a third party that is+in the business of distributing software, under which you make payment+to the third party based on the extent of your activity of conveying+the work, and under which the third party grants, to any of the+parties who would receive the covered work from you, a discriminatory+patent license (a) in connection with copies of the covered work+conveyed by you (or copies made from those copies), or (b) primarily+for and in connection with specific products or compilations that+contain the covered work, unless you entered into that arrangement,+or that patent license was granted, prior to 28 March 2007.++ Nothing in this License shall be construed as excluding or limiting+any implied license or other defenses to infringement that may+otherwise be available to you under applicable patent law.++ 12. No Surrender of Others' Freedom.++ If conditions are imposed on you (whether by court order, agreement or+otherwise) that contradict the conditions of this License, they do not+excuse you from the conditions of this License. If you cannot convey a+covered work so as to satisfy simultaneously your obligations under this+License and any other pertinent obligations, then as a consequence you may+not convey it at all. For example, if you agree to terms that obligate you+to collect a royalty for further conveying from those to whom you convey+the Program, the only way you could satisfy both those terms and this+License would be to refrain entirely from conveying the Program.++ 13. Use with the GNU Affero General Public License.++ Notwithstanding any other provision of this License, you have+permission to link or combine any covered work with a work licensed+under version 3 of the GNU Affero General Public License into a single+combined work, and to convey the resulting work. The terms of this+License will continue to apply to the part which is the covered work,+but the special requirements of the GNU Affero General Public License,+section 13, concerning interaction through a network will apply to the+combination as such.++ 14. Revised Versions of this License.++ The Free Software Foundation may publish revised and/or new versions of+the GNU General Public License from time to time. Such new versions will+be similar in spirit to the present version, but may differ in detail to+address new problems or concerns.++ Each version is given a distinguishing version number. If the+Program specifies that a certain numbered version of the GNU General+Public License "or any later version" applies to it, you have the+option of following the terms and conditions either of that numbered+version or of any later version published by the Free Software+Foundation. If the Program does not specify a version number of the+GNU General Public License, you may choose any version ever published+by the Free Software Foundation.++ If the Program specifies that a proxy can decide which future+versions of the GNU General Public License can be used, that proxy's+public statement of acceptance of a version permanently authorizes you+to choose that version for the Program.++ Later license versions may give you additional or different+permissions. However, no additional obligations are imposed on any+author or copyright holder as a result of your choosing to follow a+later version.++ 15. Disclaimer of Warranty.++ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.++ 16. Limitation of Liability.++ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF+SUCH DAMAGES.++ 17. Interpretation of Sections 15 and 16.++ If the disclaimer of warranty and limitation of liability provided+above cannot be given local legal effect according to their terms,+reviewing courts shall apply local law that most closely approximates+an absolute waiver of all civil liability in connection with the+Program, unless a warranty or assumption of liability accompanies a+copy of the Program in return for a fee.++ END OF TERMS AND CONDITIONS++ How to Apply These Terms to Your New Programs++ If you develop a new program, and you want it to be of the greatest+possible use to the public, the best way to achieve this is to make it+free software which everyone can redistribute and change under these terms.++ To do so, attach the following notices to the program. It is safest+to attach them to the start of each source file to most effectively+state the exclusion of warranty; and each file should have at least+the "copyright" line and a pointer to where the full notice is found.++ <one line to give the program's name and a brief idea of what it does.>+ Copyright (C) <year> <name of author>++ This program is free software: you can redistribute it and/or modify+ it under the terms of the GNU General Public License as published by+ the Free Software Foundation, either version 3 of the License, or+ (at your option) any later version.++ This program is distributed in the hope that it will be useful,+ but WITHOUT ANY WARRANTY; without even the implied warranty of+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+ GNU General Public License for more details.++ You should have received a copy of the GNU General Public License+ along with this program. If not, see <http://www.gnu.org/licenses/>.++Also add information on how to contact you by electronic and paper mail.++ If the program does terminal interaction, make it output a short+notice like this when it starts in an interactive mode:++ <program> Copyright (C) <year> <name of author>+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.+ This is free software, and you are welcome to redistribute it+ under certain conditions; type `show c' for details.++The hypothetical commands `show w' and `show c' should show the appropriate+parts of the General Public License. Of course, your program's commands+might be different; for a GUI interface, you would use an "about box".++ You should also get your employer (if you work as a programmer) or school,+if any, to sign a "copyright disclaimer" for the program, if necessary.+For more information on this, and how to apply and follow the GNU GPL, see+<http://www.gnu.org/licenses/>.++ The GNU General Public License does not permit incorporating your program+into proprietary programs. If your program is a subroutine library, you+may consider it more useful to permit linking proprietary applications with+the library. If this is what you want to do, use the GNU Lesser General+Public License instead of this License. But first, please read+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
+ Setup.hs view
@@ -0,0 +1,8 @@+{-import Distribution.Simple++main = defaultMain+-}++import Distribution.Simple.I18N.GetText (gettextDefaultMain)++main = gettextDefaultMain
+ i18n/de.po view
@@ -0,0 +1,473 @@+# Translation file+msgid ""+msgstr ""+"Project-Id-Version: PACKAGE VERSION\n"+"Report-Msgid-Bugs-To: \n"+"POT-Creation-Date: 2009-01-13 06:05-0800\n"+"PO-Revision-Date: 2010-07-30 09:16+0200\n"+"Last-Translator: Alexander Bau <abau@europa.domain.org>\n"+"Language-Team: German\n"+"Language: de\n"+"MIME-Version: 1.0\n"+"Content-Type: text/plain; charset=UTF-8\n"+"Content-Transfer-Encoding: 8bit\n"+"Plural-Forms: nplurals=2; plural=(n != 1);\n"++#: src/Run.hs:0+msgid "File"+msgstr "Datei"++#: src/Run.hs:0+msgid "New"+msgstr "Neu"++#: src/Run.hs:0 src/Controller/Menu/File/Open.hs:0+msgid "Open"+msgstr "Öffnen"++#: src/Run.hs:0 src/Controller/Menu/File/Import/Csv.hs:0+msgid "Import"+msgstr "Importieren"++#: src/Run.hs:0+msgid "From CSV file"+msgstr "aus CSV-Datei"++#: src/Run.hs:0 src/Controller/Menu/File/NewClose.hs:0+msgid "Save as"+msgstr "Speichern unter"++#: src/Run.hs:0 src/Controller/Menu/File/Save.hs:0+msgid "Save"+msgstr "Speichern"++#: src/Run.hs:0 src/Controller/Menu/File/Export/Image.hs:0+#: src/Controller/Menu/File/Export/Diagram/BarChart.hs:0+#: src/Controller/Menu/File/Export/Diagram/BoxPlot.hs:0+msgid "Export"+msgstr "Exportieren"++#: src/Run.hs:0+msgid "As image"+msgstr "als Bild"++#: src/Run.hs:0+msgid "As bar chart"+msgstr "als Balkendiagramm"++#: src/Run.hs:0+msgid "As box plot"+msgstr "als Boxplot"++#: src/Run.hs:0+msgid "Close"+msgstr "Schließen"++#: src/Run.hs:0+msgid "Close all"+msgstr "Alle schließen"++#: src/Run.hs:0+msgid "Quit"+msgstr "Beenden"++#: src/Run.hs:0 src/Controller/Dialog.hs:0+msgid "Table"+msgstr "Tabelle"++#: src/Run.hs:0+msgid "Add"+msgstr "Hinzufügen"++#: src/Run.hs:0 src/Controller/Dialog.hs:0+#: src/Controller/Menu/File/Export/Diagram/BarChart.hs:0+#: src/Controller/Menu/File/Export/Diagram/BoxPlot.hs:0+#: src/Controller/Menu/Statistics/Descriptive.hs:0+msgid "Columns"+msgstr "Spalten"++#: src/Run.hs:0 src/Controller/Dialog.hs:0+msgid "Rows"+msgstr "Zeilen"++#: src/Run.hs:0 src/Controller/Menu/Table/Copy.hs:0+msgid "Copy"+msgstr "Kopieren"++#: src/Run.hs:0 src/Controller/Menu/Table/Delete.hs:0+msgid "Delete"+msgstr "Löschen"++#: src/Run.hs:0 src/Controller/Menu/Table/Transpose.hs:0+msgid "Transpose"+msgstr "Transponieren"++#: src/Run.hs:0+msgid "Reevaluate all cells"+msgstr "Alle Zellen auswerten"++#: src/Run.hs:0+msgid "Statistics"+msgstr "Statistik"++#: src/Run.hs:0 src/Controller/Menu/Statistics/FrequencyDistribution.hs:0+msgid "Frequency Distribution"+msgstr "Häufigkeitsverteilung"++#: src/Run.hs:0+msgid "Descriptive"+msgstr "Deskriptive"++#: src/Run.hs:0 src/Controller/Menu/Statistics/Pivot.hs:0+msgid "Pivot table"+msgstr "Pivot-Tabelle"++#: src/Util/FileType.hs:0+msgid "Unknown file type"+msgstr "Unbekannter Dateityp"++#: src/View/Dialog/Simple.hs:0 src/Model/CellContent.hs:0+msgid "Error"+msgstr "Fehler"++#: src/View/Dialog/Complex.hs:0+msgid "&Ok"+msgstr "&Ok"++#: src/View/Dialog/Complex.hs:0+msgid "&Cancel"+msgstr "&Abbrechen"++#: src/Model/CellContent.hs:0+msgid "Parse error"+msgstr "Analyse-Fehler"++#: src/Model/CellContent.hs:0+msgid "Unknown reference"+msgstr "Unbekannte Referenz"++#: src/Model/CellContent.hs:0+msgid "Reference loop"+msgstr "Selbst-Referenz"++#: src/Model/CellContent.hs:0+msgid "Compile error"+msgstr "Übersetzer-Fehler"++#: src/Model/CellContent.hs:0+msgid "Type error in"+msgstr "Typ-Fehler in"++#: src/Model/CellContent.hs:0+msgid "In"+msgstr "In"++#: src/Model/CellContent.hs:0+msgid "Unknown identifier"+msgstr "Unbekannter Bezeichner"++#: src/Model/CellContent.hs:0+msgid "Empty"+msgstr "Leer"++#: src/Model/CellExpression/Parser.hs:0+msgid "formula"+msgstr "Formel"++#: src/Model/CellExpression/Parser.hs:0+msgid "argument"+msgstr "Argument"++#: src/Model/CellExpression/Parser.hs:0+msgid "function call"+msgstr "Funktionsaufruf"++#: src/Model/CellExpression/Parser.hs:0+msgid "number"+msgstr "Nummer"++#: src/Model/CellExpression/Parser.hs:0+msgid "string"+msgstr "Zeichenkette"++#: src/Model/CellExpression/Parser.hs:0+msgid "reference"+msgstr "Referenz"++#: src/Model/CellExpression/Parser.hs:0+msgid "cell reference"+msgstr "Zellen-Referenz"++#: src/Model/CellExpression/Parser.hs:0+msgid "cell range reference"+msgstr "Zellen-Bereichs-Referenz"++#: src/Model/CellExpression/Parser.hs:0+msgid "subexpression"+msgstr "Subausdruck"++#: src/Model/CellExpression/Parser.hs:0+msgid "end of input"+msgstr "Ende der Eingabe"++#: src/Model/CellExpression/Parser.hs:0+msgid "unexpected"+msgstr "unerwartet"++#: src/Model/CellExpression/Parser.hs:0+msgid "expecting"+msgstr "erwarte"++#: src/Controller/Dialog.hs:0+msgid "Any file"+msgstr "Alle Dateien"++#: src/Controller/Dialog.hs:0+msgid "Column"+msgstr "Spalte"++#: src/Controller/Dialog.hs:0+msgid "to"+msgstr "nach"++#: src/Controller/Dialog.hs:0+msgid "Preview"+msgstr "Vorschau"++#: src/Controller/Dialog.hs:0+msgid "Column(s)"+msgstr "Spalte(n)"++#: src/Controller/Dialog.hs:0+msgid "contains non-numeric data"+msgstr "beinhalten nicht-numerische Daten"++#: src/Controller/Dialog.hs:0+msgid "No columns selected"+msgstr "Keine Spalten ausgewählt"++#: src/Controller/Menu/File/NewClose.hs:0+msgid "Save?"+msgstr "Speichern?"++#: src/Controller/Menu/File/Open.hs:0 src/Controller/Menu/File/Save.hs:0+msgid "Tables"+msgstr "Tabellen"++#: src/Controller/Menu/File/Open.hs:0+msgid "Invalid file"+msgstr "Ungültige Datei"++#: src/Controller/Menu/File/Open.hs:0+msgid "Invalid s-expression"+msgstr "Ungültiger S-Ausdruck"++#: src/Controller/Menu/File/Import/Csv.hs:0+msgid "Column delimiter"+msgstr "Spalten-Trennzeichen"++#: src/Controller/Menu/File/Import/Csv.hs:0+msgid "Row delimiter"+msgstr "Zeilen-Trennzeichen"++#: src/Controller/Menu/File/Import/Csv.hs:0+msgid "First column contains labels"+msgstr "Erste Spalte enthält Spalten-Beschriftungen"++#: src/Controller/Menu/File/Import/Csv.hs:0+msgid "First row contains labels"+msgstr "Erste Zeile enthält Zeilen-Beschriftungen"++#: src/Controller/Menu/File/Import/Csv.hs:0+msgid "Strip whitespace"+msgstr "Entferne Leerzeichen"++#: src/Controller/Menu/File/Import/Csv.hs:0+msgid "Try parsing numbers"+msgstr "Versuche Zahlen zu interpretieren"++#: src/Controller/Menu/File/Import/Csv.hs:0+msgid "&Import"+msgstr "&Importieren"++#: src/Controller/Menu/File/Export/Image.hs:0+msgid "Font size (pt)"+msgstr "Font-Größe (pt)"++#: src/Controller/Menu/File/Export/Image.hs:0+msgid "Font color"+msgstr "Font-Farbe"++#: src/Controller/Menu/File/Export/Image.hs:0+msgid "Background color"+msgstr "Hintergrundfarbe"++#: src/Controller/Menu/File/Export/Image.hs:0+msgid "Padding (px)"+msgstr "Innenabstand (px)"++#: src/Controller/Menu/File/Export/Image.hs:0+msgid "Line width (horizontal) (px)"+msgstr "Linienbreite (horizontal) (px)"++#: src/Controller/Menu/File/Export/Image.hs:0+msgid "Line width (vertical) (px)"+msgstr "Linienbreite (vertikal) (px)"++#: src/Controller/Menu/File/Export/Image.hs:0+msgid "File type"+msgstr "Dateityp"++#: src/Controller/Menu/File/Export/Image.hs:0+#: src/Controller/Menu/File/Export/Diagram/BarChart.hs:0+#: src/Controller/Menu/File/Export/Diagram/BoxPlot.hs:0+msgid "&Export"+msgstr "&Exportieren"++#: src/Controller/Menu/File/Export/Image.hs:0+#: src/Controller/Menu/File/Export/Diagram/BarChart.hs:0+#: src/Controller/Menu/File/Export/Diagram/BoxPlot.hs:0+msgid "&Preview"+msgstr "&Vorschau"++#: src/Controller/Menu/File/Export/ImageParameters.hs:0+msgid "By extension"+msgstr "s. Dateierweiterung"++#: src/Controller/Menu/File/Export/Diagram/Common.hs:0+msgid "Can not plot diagram"+msgstr "Diagramm kann nicht erstellt werden"++#: src/Controller/Menu/File/Export/Diagram/BarChart.hs:0+msgid "Bar width"+msgstr "Balkenbreite"++#: src/Controller/Menu/File/Export/Diagram/BarChart.hs:0+#: src/Controller/Menu/File/Export/Diagram/BoxPlot.hs:0+msgid "Gaps"+msgstr "Lücken"++#: src/Controller/Menu/File/Export/Diagram/BarChart.hs:0+msgid "Legend"+msgstr "Legende"++#: src/Controller/Menu/File/Export/Diagram/BoxPlot.hs:0+msgid "Box width"+msgstr "Boxbreite"++#: src/Controller/Menu/Table/Add.hs:0+msgid "Add columns"+msgstr "Spalten hinzufügen"++#: src/Controller/Menu/Table/Add.hs:0+msgid "Add columns by"+msgstr "Spalten hinzufügen durch"++#: src/Controller/Menu/Table/Add.hs:0+msgid "Add rows"+msgstr "Zeilen hinzufügen"++#: src/Controller/Menu/Table/Add.hs:0+msgid "Add rows by"+msgstr "Zeilen hinzufügen durch"++#: src/Controller/Menu/Table/Add.hs:0+msgid "Number \"n\""+msgstr "Anzahl \"n\""++#: src/Controller/Menu/Table/Add.hs:0+msgid "Labels \"label1[,label2,...]\""+msgstr "Beschriftungen \"beschriftung1[,beschriftung2,...]\""++#: src/Controller/Menu/Table/Add.hs:0+msgid "&Add"+msgstr "&Hinzufügen"++#: src/Controller/Menu/Statistics/Descriptive.hs:0+msgid "Descriptive Statistics"+msgstr "Deskriptive Statistik"++#: src/Controller/Menu/Statistics/Descriptive.hs:0+#: src/Controller/Menu/Statistics/Pivot.hs:0+msgid "Mean"+msgstr "Mittelwert"++#: src/Controller/Menu/Statistics/Descriptive.hs:0+#: src/Controller/Menu/Statistics/Pivot.hs:0+msgid "Median"+msgstr "Median"++#: src/Controller/Menu/Statistics/Descriptive.hs:0+#: src/Controller/Menu/Statistics/Pivot.hs:0+msgid "Standard Deviation"+msgstr "Standardabweichung"++#: src/Controller/Menu/Statistics/Descriptive.hs:0+#: src/Controller/Menu/Statistics/Pivot.hs:0+msgid "Maximum"+msgstr "Maximum"++#: src/Controller/Menu/Statistics/Descriptive.hs:0+#: src/Controller/Menu/Statistics/Pivot.hs:0+msgid "Minimum"+msgstr "Minimum"++#: src/Controller/Menu/Statistics/FrequencyDistribution.hs:0+msgid "Frequency"+msgstr "Häufigkeit"++#: src/Controller/Menu/Statistics/FrequencyDistribution.hs:0+msgid "Percentage"+msgstr "Prozent"++#: src/Controller/Menu/Statistics/Pivot.hs:0+msgid "Sum"+msgstr "Summe"++#: src/Controller/Menu/Statistics/Pivot.hs:0+msgid "Count"+msgstr "Anzahl"++#: src/Controller/Menu/Statistics/Pivot.hs:0+msgid "Abscissa column"+msgstr "Abszissen-Spalte"++#: src/Controller/Menu/Statistics/Pivot.hs:0+msgid "Ordinate column"+msgstr "Ordinaten-Spalte"++#: src/Controller/Menu/Statistics/Pivot.hs:0+msgid "Data column"+msgstr "Daten-Spalte"++#: src/Controller/Menu/Statistics/Pivot.hs:0+msgid "Aggregator"+msgstr "Aggregator"++#: src/Controller/Menu/Statistics/Pivot.hs:0+msgid "Make totals"+msgstr "Gesamtheiten berechnen"++#: src/Controller/Menu/Statistics/Pivot.hs:0+msgid "No abscissa column selected"+msgstr "Keine Abszissen-Spalte ausgewählt"++#: src/Controller/Menu/Statistics/Pivot.hs:0+msgid "No ordinate column selected"+msgstr "Keine Ordinaten-Spalte ausgewählt"++#: src/Controller/Menu/Statistics/Pivot.hs:0+msgid "No data column selected"+msgstr "Keine Daten-Spalte ausgewählt"++#: src/Controller/Menu/Statistics/Pivot.hs:0+msgid ""+"Can not apply aggregator to selected data column (e.g. because of non-"+"numeric data)."+msgstr ""+"Kann gewählten Aggregator nicht auf Daten-Spalte anwenden (z.B. aufgrund "+"nicht-numerischer Daten)"++#: src/Controller/Menu/Statistics/Pivot.hs:0+msgid "Total"+msgstr "Total"
+ i18n/en.po view
@@ -0,0 +1,493 @@+# Translation file+msgid ""+msgstr ""+"Project-Id-Version: PACKAGE VERSION\n"+"Report-Msgid-Bugs-To: \n"+"POT-Creation-Date: 2009-01-13 06:05-0800\n"+"PO-Revision-Date: 2010-07-30 09:16+0200\n"+"Last-Translator: Alexander Bau <abau@europa.domain.org>\n"+"Language-Team: English\n"+"Language: en\n"+"MIME-Version: 1.0\n"+"Content-Type: text/plain; charset=UTF-8\n"+"Content-Transfer-Encoding: 8bit\n"+"Plural-Forms: nplurals=2; plural=(n != 1);\n"++#: src/Run.hs:0+msgid "File"+msgstr "File"++#: src/Run.hs:0+msgid "New"+msgstr "New"++#: src/Run.hs:0 src/Controller/Menu/File/Open.hs:0+msgid "Open"+msgstr "Open"++#: src/Run.hs:0 src/Controller/Menu/File/Import/Csv.hs:0+#, fuzzy+msgid "Import"+msgstr "Export"++#: src/Run.hs:0+msgid "From CSV file"+msgstr ""++#: src/Run.hs:0 src/Controller/Menu/File/NewClose.hs:0+msgid "Save as"+msgstr "Save as"++#: src/Run.hs:0 src/Controller/Menu/File/Save.hs:0+msgid "Save"+msgstr "Save"++#: src/Run.hs:0 src/Controller/Menu/File/Export/Image.hs:0+#: src/Controller/Menu/File/Export/Diagram/BarChart.hs:0+#: src/Controller/Menu/File/Export/Diagram/BoxPlot.hs:0+msgid "Export"+msgstr "Export"++#: src/Run.hs:0+#, fuzzy+msgid "As image"+msgstr "As image"++#: src/Run.hs:0+msgid "As bar chart"+msgstr ""++#: src/Run.hs:0+msgid "As box plot"+msgstr ""++#: src/Run.hs:0+msgid "Close"+msgstr "Close"++#: src/Run.hs:0+msgid "Close all"+msgstr "Close all"++#: src/Run.hs:0+msgid "Quit"+msgstr "Quit"++#: src/Run.hs:0 src/Controller/Dialog.hs:0+msgid "Table"+msgstr "Table"++#: src/Run.hs:0+msgid "Add"+msgstr ""++#: src/Run.hs:0 src/Controller/Dialog.hs:0+#: src/Controller/Menu/File/Export/Diagram/BarChart.hs:0+#: src/Controller/Menu/File/Export/Diagram/BoxPlot.hs:0+#: src/Controller/Menu/Statistics/Descriptive.hs:0+#, fuzzy+msgid "Columns"+msgstr "Column"++#: src/Run.hs:0 src/Controller/Dialog.hs:0+msgid "Rows"+msgstr ""++#: src/Run.hs:0 src/Controller/Menu/Table/Copy.hs:0+msgid "Copy"+msgstr "Copy"++#: src/Run.hs:0 src/Controller/Menu/Table/Delete.hs:0+msgid "Delete"+msgstr "Delete"++#: src/Run.hs:0 src/Controller/Menu/Table/Transpose.hs:0+msgid "Transpose"+msgstr ""++#: src/Run.hs:0+msgid "Reevaluate all cells"+msgstr "Reevaluate all cells"++#: src/Run.hs:0+msgid "Statistics"+msgstr "Statistics"++#: src/Run.hs:0 src/Controller/Menu/Statistics/FrequencyDistribution.hs:0+msgid "Frequency Distribution"+msgstr "Frequency Distribution"++#: src/Run.hs:0+msgid "Descriptive"+msgstr ""++#: src/Run.hs:0 src/Controller/Menu/Statistics/Pivot.hs:0+msgid "Pivot table"+msgstr ""++#: src/Util/FileType.hs:0+#, fuzzy+msgid "Unknown file type"+msgstr "File"++#: src/View/Dialog/Simple.hs:0 src/Model/CellContent.hs:0+msgid "Error"+msgstr ""++#: src/View/Dialog/Complex.hs:0+msgid "&Ok"+msgstr ""++#: src/View/Dialog/Complex.hs:0+msgid "&Cancel"+msgstr ""++#: src/Model/CellContent.hs:0+msgid "Parse error"+msgstr ""++#: src/Model/CellContent.hs:0+#, fuzzy+msgid "Unknown reference"+msgstr "File"++#: src/Model/CellContent.hs:0+msgid "Reference loop"+msgstr ""++#: src/Model/CellContent.hs:0+msgid "Compile error"+msgstr ""++#: src/Model/CellContent.hs:0+msgid "Type error in"+msgstr ""++#: src/Model/CellContent.hs:0+msgid "In"+msgstr ""++#: src/Model/CellContent.hs:0+#, fuzzy+msgid "Unknown identifier"+msgstr "File"++#: src/Model/CellContent.hs:0+msgid "Empty"+msgstr ""++#: src/Model/CellExpression/Parser.hs:0+msgid "formula"+msgstr ""++#: src/Model/CellExpression/Parser.hs:0+msgid "argument"+msgstr ""++#: src/Model/CellExpression/Parser.hs:0+msgid "function call"+msgstr ""++#: src/Model/CellExpression/Parser.hs:0+msgid "number"+msgstr ""++#: src/Model/CellExpression/Parser.hs:0+msgid "string"+msgstr ""++#: src/Model/CellExpression/Parser.hs:0+msgid "reference"+msgstr ""++#: src/Model/CellExpression/Parser.hs:0+msgid "cell reference"+msgstr ""++#: src/Model/CellExpression/Parser.hs:0+msgid "cell range reference"+msgstr ""++#: src/Model/CellExpression/Parser.hs:0+msgid "subexpression"+msgstr ""++#: src/Model/CellExpression/Parser.hs:0+msgid "end of input"+msgstr ""++#: src/Model/CellExpression/Parser.hs:0+msgid "unexpected"+msgstr ""++#: src/Model/CellExpression/Parser.hs:0+msgid "expecting"+msgstr ""++#: src/Controller/Dialog.hs:0+msgid "Any file"+msgstr ""++#: src/Controller/Dialog.hs:0+#, fuzzy+msgid "Column"+msgstr "Column"++#: src/Controller/Dialog.hs:0+msgid "to"+msgstr ""++#: src/Controller/Dialog.hs:0+msgid "Preview"+msgstr ""++#: src/Controller/Dialog.hs:0+#, fuzzy+msgid "Column(s)"+msgstr "Column"++#: src/Controller/Dialog.hs:0+msgid "contains non-numeric data"+msgstr ""++#: src/Controller/Dialog.hs:0+msgid "No columns selected"+msgstr ""++#: src/Controller/Menu/File/NewClose.hs:0+#, fuzzy+msgid "Save?"+msgstr "Save"++#: src/Controller/Menu/File/Open.hs:0 src/Controller/Menu/File/Save.hs:0+#, fuzzy+msgid "Tables"+msgstr "Table"++#: src/Controller/Menu/File/Open.hs:0+msgid "Invalid file"+msgstr ""++#: src/Controller/Menu/File/Open.hs:0+msgid "Invalid s-expression"+msgstr ""++#: src/Controller/Menu/File/Import/Csv.hs:0+msgid "Column delimiter"+msgstr ""++#: src/Controller/Menu/File/Import/Csv.hs:0+msgid "Row delimiter"+msgstr ""++#: src/Controller/Menu/File/Import/Csv.hs:0+msgid "First column contains labels"+msgstr ""++#: src/Controller/Menu/File/Import/Csv.hs:0+msgid "First row contains labels"+msgstr ""++#: src/Controller/Menu/File/Import/Csv.hs:0+msgid "Strip whitespace"+msgstr ""++#: src/Controller/Menu/File/Import/Csv.hs:0+msgid "Try parsing numbers"+msgstr ""++#: src/Controller/Menu/File/Import/Csv.hs:0+#, fuzzy+msgid "&Import"+msgstr "Export"++#: src/Controller/Menu/File/Export/Image.hs:0+msgid "Font size (pt)"+msgstr ""++#: src/Controller/Menu/File/Export/Image.hs:0+msgid "Font color"+msgstr ""++#: src/Controller/Menu/File/Export/Image.hs:0+msgid "Background color"+msgstr ""++#: src/Controller/Menu/File/Export/Image.hs:0+msgid "Padding (px)"+msgstr ""++#: src/Controller/Menu/File/Export/Image.hs:0+msgid "Line width (horizontal) (px)"+msgstr ""++#: src/Controller/Menu/File/Export/Image.hs:0+msgid "Line width (vertical) (px)"+msgstr ""++#: src/Controller/Menu/File/Export/Image.hs:0+#, fuzzy+msgid "File type"+msgstr "File"++#: src/Controller/Menu/File/Export/Image.hs:0+#: src/Controller/Menu/File/Export/Diagram/BarChart.hs:0+#: src/Controller/Menu/File/Export/Diagram/BoxPlot.hs:0+#, fuzzy+msgid "&Export"+msgstr "Export"++#: src/Controller/Menu/File/Export/Image.hs:0+#: src/Controller/Menu/File/Export/Diagram/BarChart.hs:0+#: src/Controller/Menu/File/Export/Diagram/BoxPlot.hs:0+msgid "&Preview"+msgstr ""++#: src/Controller/Menu/File/Export/ImageParameters.hs:0+msgid "By extension"+msgstr ""++#: src/Controller/Menu/File/Export/Diagram/Common.hs:0+msgid "Can not plot diagram"+msgstr ""++#: src/Controller/Menu/File/Export/Diagram/BarChart.hs:0+msgid "Bar width"+msgstr ""++#: src/Controller/Menu/File/Export/Diagram/BarChart.hs:0+#: src/Controller/Menu/File/Export/Diagram/BoxPlot.hs:0+msgid "Gaps"+msgstr ""++#: src/Controller/Menu/File/Export/Diagram/BarChart.hs:0+msgid "Legend"+msgstr ""++#: src/Controller/Menu/File/Export/Diagram/BoxPlot.hs:0+msgid "Box width"+msgstr ""++#: src/Controller/Menu/Table/Add.hs:0+#, fuzzy+msgid "Add columns"+msgstr "Column"++#: src/Controller/Menu/Table/Add.hs:0+msgid "Add columns by"+msgstr ""++#: src/Controller/Menu/Table/Add.hs:0+msgid "Add rows"+msgstr ""++#: src/Controller/Menu/Table/Add.hs:0+msgid "Add rows by"+msgstr ""++#: src/Controller/Menu/Table/Add.hs:0+msgid "Number \"n\""+msgstr ""++#: src/Controller/Menu/Table/Add.hs:0+msgid "Labels \"label1[,label2,...]\""+msgstr ""++#: src/Controller/Menu/Table/Add.hs:0+msgid "&Add"+msgstr ""++#: src/Controller/Menu/Statistics/Descriptive.hs:0+#, fuzzy+msgid "Descriptive Statistics"+msgstr "Statistics"++#: src/Controller/Menu/Statistics/Descriptive.hs:0+#: src/Controller/Menu/Statistics/Pivot.hs:0+msgid "Mean"+msgstr ""++#: src/Controller/Menu/Statistics/Descriptive.hs:0+#: src/Controller/Menu/Statistics/Pivot.hs:0+msgid "Median"+msgstr ""++#: src/Controller/Menu/Statistics/Descriptive.hs:0+#: src/Controller/Menu/Statistics/Pivot.hs:0+msgid "Standard Deviation"+msgstr ""++#: src/Controller/Menu/Statistics/Descriptive.hs:0+#: src/Controller/Menu/Statistics/Pivot.hs:0+msgid "Maximum"+msgstr ""++#: src/Controller/Menu/Statistics/Descriptive.hs:0+#: src/Controller/Menu/Statistics/Pivot.hs:0+msgid "Minimum"+msgstr ""++#: src/Controller/Menu/Statistics/FrequencyDistribution.hs:0+msgid "Frequency"+msgstr ""++#: src/Controller/Menu/Statistics/FrequencyDistribution.hs:0+msgid "Percentage"+msgstr ""++#: src/Controller/Menu/Statistics/Pivot.hs:0+msgid "Sum"+msgstr ""++#: src/Controller/Menu/Statistics/Pivot.hs:0+msgid "Count"+msgstr ""++#: src/Controller/Menu/Statistics/Pivot.hs:0+msgid "Abscissa column"+msgstr ""++#: src/Controller/Menu/Statistics/Pivot.hs:0+msgid "Ordinate column"+msgstr ""++#: src/Controller/Menu/Statistics/Pivot.hs:0+msgid "Data column"+msgstr ""++#: src/Controller/Menu/Statistics/Pivot.hs:0+msgid "Aggregator"+msgstr ""++#: src/Controller/Menu/Statistics/Pivot.hs:0+msgid "Make totals"+msgstr ""++#: src/Controller/Menu/Statistics/Pivot.hs:0+msgid "No abscissa column selected"+msgstr ""++#: src/Controller/Menu/Statistics/Pivot.hs:0+msgid "No ordinate column selected"+msgstr ""++#: src/Controller/Menu/Statistics/Pivot.hs:0+msgid "No data column selected"+msgstr ""++#: src/Controller/Menu/Statistics/Pivot.hs:0+msgid ""+"Can not apply aggregator to selected data column (e.g. because of non-"+"numeric data)."+msgstr ""++#: src/Controller/Menu/Statistics/Pivot.hs:0+msgid "Total"+msgstr ""++#, fuzzy+#~ msgid "Save in"+#~ msgstr "Save"++#~ msgid "As diagram"+#~ msgstr "As diagram"
+ i18n/messages.pot view
@@ -0,0 +1,469 @@+# Translation file+msgid ""+msgstr ""+"Project-Id-Version: PACKAGE VERSION\n"+"Report-Msgid-Bugs-To: \n"+"POT-Creation-Date: 2009-01-13 06:05-0800\n"+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"+"Language-Team: LANGUAGE <LL@li.org>\n"+"MIME-Version: 1.0\n"+"Content-Type: text/plain; charset=UTF-8\n"+"Content-Transfer-Encoding: 8bit\n"++#: src/Run.hs:0+msgid "File"+msgstr ""++#: src/Run.hs:0+msgid "New"+msgstr ""++#: src/Run.hs:0 src/Controller/Menu/File/Open.hs:0+msgid "Open"+msgstr ""++#: src/Run.hs:0 src/Controller/Menu/File/Import/Csv.hs:0+msgid "Import"+msgstr ""++#: src/Run.hs:0+msgid "From CSV file"+msgstr ""++#: src/Run.hs:0 src/Controller/Menu/File/NewClose.hs:0+msgid "Save as"+msgstr ""++#: src/Run.hs:0 src/Controller/Menu/File/Save.hs:0+msgid "Save"+msgstr ""++#: src/Run.hs:0 src/Controller/Menu/File/Export/Image.hs:0+#: src/Controller/Menu/File/Export/Diagram/BarChart.hs:0+#: src/Controller/Menu/File/Export/Diagram/BoxPlot.hs:0+msgid "Export"+msgstr ""++#: src/Run.hs:0+msgid "As image"+msgstr ""++#: src/Run.hs:0+msgid "As bar chart"+msgstr ""++#: src/Run.hs:0+msgid "As box plot"+msgstr ""++#: src/Run.hs:0+msgid "Close"+msgstr ""++#: src/Run.hs:0+msgid "Close all"+msgstr ""++#: src/Run.hs:0+msgid "Quit"+msgstr ""++#: src/Run.hs:0 src/Controller/Dialog.hs:0+msgid "Table"+msgstr ""++#: src/Run.hs:0+msgid "Add"+msgstr ""++#: src/Run.hs:0 src/Controller/Dialog.hs:0+#: src/Controller/Menu/File/Export/Diagram/BarChart.hs:0+#: src/Controller/Menu/File/Export/Diagram/BoxPlot.hs:0+#: src/Controller/Menu/Statistics/Descriptive.hs:0+msgid "Columns"+msgstr ""++#: src/Run.hs:0 src/Controller/Dialog.hs:0+msgid "Rows"+msgstr ""++#: src/Run.hs:0 src/Controller/Menu/Table/Copy.hs:0+msgid "Copy"+msgstr ""++#: src/Run.hs:0 src/Controller/Menu/Table/Delete.hs:0+msgid "Delete"+msgstr ""++#: src/Run.hs:0 src/Controller/Menu/Table/Transpose.hs:0+msgid "Transpose"+msgstr ""++#: src/Run.hs:0+msgid "Reevaluate all cells"+msgstr ""++#: src/Run.hs:0+msgid "Statistics"+msgstr ""++#: src/Run.hs:0 src/Controller/Menu/Statistics/FrequencyDistribution.hs:0+msgid "Frequency Distribution"+msgstr ""++#: src/Run.hs:0+msgid "Descriptive"+msgstr ""++#: src/Run.hs:0 src/Controller/Menu/Statistics/Pivot.hs:0+msgid "Pivot table"+msgstr ""++#: src/Util/FileType.hs:0+msgid "Unknown file type"+msgstr ""++#: src/View/Dialog/Simple.hs:0 src/Model/CellContent.hs:0+msgid "Error"+msgstr ""++#: src/View/Dialog/Complex.hs:0+msgid "&Ok"+msgstr ""++#: src/View/Dialog/Complex.hs:0+msgid "&Cancel"+msgstr ""++#: src/Model/CellContent.hs:0+msgid "Parse error"+msgstr ""++#: src/Model/CellContent.hs:0+msgid "Unknown reference"+msgstr ""++#: src/Model/CellContent.hs:0+msgid "Reference loop"+msgstr ""++#: src/Model/CellContent.hs:0+msgid "Compile error"+msgstr ""++#: src/Model/CellContent.hs:0+msgid "Type error in"+msgstr ""++#: src/Model/CellContent.hs:0+msgid "In"+msgstr ""++#: src/Model/CellContent.hs:0+msgid "Unknown identifier"+msgstr ""++#: src/Model/CellContent.hs:0+msgid "Empty"+msgstr ""++#: src/Model/CellExpression/Parser.hs:0+msgid "formula"+msgstr ""++#: src/Model/CellExpression/Parser.hs:0+msgid "argument"+msgstr ""++#: src/Model/CellExpression/Parser.hs:0+msgid "function call"+msgstr ""++#: src/Model/CellExpression/Parser.hs:0+msgid "number"+msgstr ""++#: src/Model/CellExpression/Parser.hs:0+msgid "string"+msgstr ""++#: src/Model/CellExpression/Parser.hs:0+msgid "reference"+msgstr ""++#: src/Model/CellExpression/Parser.hs:0+msgid "cell reference"+msgstr ""++#: src/Model/CellExpression/Parser.hs:0+msgid "cell range reference"+msgstr ""++#: src/Model/CellExpression/Parser.hs:0+msgid "subexpression"+msgstr ""++#: src/Model/CellExpression/Parser.hs:0+msgid "end of input"+msgstr ""++#: src/Model/CellExpression/Parser.hs:0+msgid "unexpected"+msgstr ""++#: src/Model/CellExpression/Parser.hs:0+msgid "expecting"+msgstr ""++#: src/Controller/Dialog.hs:0+msgid "Any file"+msgstr ""++#: src/Controller/Dialog.hs:0+msgid "Column"+msgstr ""++#: src/Controller/Dialog.hs:0+msgid "to"+msgstr ""++#: src/Controller/Dialog.hs:0+msgid "Preview"+msgstr ""++#: src/Controller/Dialog.hs:0+msgid "Column(s)"+msgstr ""++#: src/Controller/Dialog.hs:0+msgid "contains non-numeric data"+msgstr ""++#: src/Controller/Dialog.hs:0+msgid "No columns selected"+msgstr ""++#: src/Controller/Menu/File/NewClose.hs:0+msgid "Save?"+msgstr ""++#: src/Controller/Menu/File/Open.hs:0 src/Controller/Menu/File/Save.hs:0+msgid "Tables"+msgstr ""++#: src/Controller/Menu/File/Open.hs:0+msgid "Invalid file"+msgstr ""++#: src/Controller/Menu/File/Open.hs:0+msgid "Invalid s-expression"+msgstr ""++#: src/Controller/Menu/File/Import/Csv.hs:0+msgid "Column delimiter"+msgstr ""++#: src/Controller/Menu/File/Import/Csv.hs:0+msgid "Row delimiter"+msgstr ""++#: src/Controller/Menu/File/Import/Csv.hs:0+msgid "First column contains labels"+msgstr ""++#: src/Controller/Menu/File/Import/Csv.hs:0+msgid "First row contains labels"+msgstr ""++#: src/Controller/Menu/File/Import/Csv.hs:0+msgid "Strip whitespace"+msgstr ""++#: src/Controller/Menu/File/Import/Csv.hs:0+msgid "Try parsing numbers"+msgstr ""++#: src/Controller/Menu/File/Import/Csv.hs:0+msgid "&Import"+msgstr ""++#: src/Controller/Menu/File/Export/Image.hs:0+msgid "Font size (pt)"+msgstr ""++#: src/Controller/Menu/File/Export/Image.hs:0+msgid "Font color"+msgstr ""++#: src/Controller/Menu/File/Export/Image.hs:0+msgid "Background color"+msgstr ""++#: src/Controller/Menu/File/Export/Image.hs:0+msgid "Padding (px)"+msgstr ""++#: src/Controller/Menu/File/Export/Image.hs:0+msgid "Line width (horizontal) (px)"+msgstr ""++#: src/Controller/Menu/File/Export/Image.hs:0+msgid "Line width (vertical) (px)"+msgstr ""++#: src/Controller/Menu/File/Export/Image.hs:0+msgid "File type"+msgstr ""++#: src/Controller/Menu/File/Export/Image.hs:0+#: src/Controller/Menu/File/Export/Diagram/BarChart.hs:0+#: src/Controller/Menu/File/Export/Diagram/BoxPlot.hs:0+msgid "&Export"+msgstr ""++#: src/Controller/Menu/File/Export/Image.hs:0+#: src/Controller/Menu/File/Export/Diagram/BarChart.hs:0+#: src/Controller/Menu/File/Export/Diagram/BoxPlot.hs:0+msgid "&Preview"+msgstr ""++#: src/Controller/Menu/File/Export/ImageParameters.hs:0+msgid "By extension"+msgstr ""++#: src/Controller/Menu/File/Export/Diagram/Common.hs:0+msgid "Can not plot diagram"+msgstr ""++#: src/Controller/Menu/File/Export/Diagram/BarChart.hs:0+msgid "Bar width"+msgstr ""++#: src/Controller/Menu/File/Export/Diagram/BarChart.hs:0+#: src/Controller/Menu/File/Export/Diagram/BoxPlot.hs:0+msgid "Gaps"+msgstr ""++#: src/Controller/Menu/File/Export/Diagram/BarChart.hs:0+msgid "Legend"+msgstr ""++#: src/Controller/Menu/File/Export/Diagram/BoxPlot.hs:0+msgid "Box width"+msgstr ""++#: src/Controller/Menu/Table/Add.hs:0+msgid "Add columns"+msgstr ""++#: src/Controller/Menu/Table/Add.hs:0+msgid "Add columns by"+msgstr ""++#: src/Controller/Menu/Table/Add.hs:0+msgid "Add rows"+msgstr ""++#: src/Controller/Menu/Table/Add.hs:0+msgid "Add rows by"+msgstr ""++#: src/Controller/Menu/Table/Add.hs:0+msgid "Number \"n\""+msgstr ""++#: src/Controller/Menu/Table/Add.hs:0+msgid "Labels \"label1[,label2,...]\""+msgstr ""++#: src/Controller/Menu/Table/Add.hs:0+msgid "&Add"+msgstr ""++#: src/Controller/Menu/Statistics/Descriptive.hs:0+msgid "Descriptive Statistics"+msgstr ""++#: src/Controller/Menu/Statistics/Descriptive.hs:0+#: src/Controller/Menu/Statistics/Pivot.hs:0+msgid "Mean"+msgstr ""++#: src/Controller/Menu/Statistics/Descriptive.hs:0+#: src/Controller/Menu/Statistics/Pivot.hs:0+msgid "Median"+msgstr ""++#: src/Controller/Menu/Statistics/Descriptive.hs:0+#: src/Controller/Menu/Statistics/Pivot.hs:0+msgid "Standard Deviation"+msgstr ""++#: src/Controller/Menu/Statistics/Descriptive.hs:0+#: src/Controller/Menu/Statistics/Pivot.hs:0+msgid "Maximum"+msgstr ""++#: src/Controller/Menu/Statistics/Descriptive.hs:0+#: src/Controller/Menu/Statistics/Pivot.hs:0+msgid "Minimum"+msgstr ""++#: src/Controller/Menu/Statistics/FrequencyDistribution.hs:0+msgid "Frequency"+msgstr ""++#: src/Controller/Menu/Statistics/FrequencyDistribution.hs:0+msgid "Percentage"+msgstr ""++#: src/Controller/Menu/Statistics/Pivot.hs:0+msgid "Sum"+msgstr ""++#: src/Controller/Menu/Statistics/Pivot.hs:0+msgid "Count"+msgstr ""++#: src/Controller/Menu/Statistics/Pivot.hs:0+msgid "Abscissa column"+msgstr ""++#: src/Controller/Menu/Statistics/Pivot.hs:0+msgid "Ordinate column"+msgstr ""++#: src/Controller/Menu/Statistics/Pivot.hs:0+msgid "Data column"+msgstr ""++#: src/Controller/Menu/Statistics/Pivot.hs:0+msgid "Aggregator"+msgstr ""++#: src/Controller/Menu/Statistics/Pivot.hs:0+msgid "Make totals"+msgstr ""++#: src/Controller/Menu/Statistics/Pivot.hs:0+msgid "No abscissa column selected"+msgstr ""++#: src/Controller/Menu/Statistics/Pivot.hs:0+msgid "No ordinate column selected"+msgstr ""++#: src/Controller/Menu/Statistics/Pivot.hs:0+msgid "No data column selected"+msgstr ""++#: src/Controller/Menu/Statistics/Pivot.hs:0+msgid ""+"Can not apply aggregator to selected data column (e.g. because of non-"+"numeric data)."+msgstr ""++#: src/Controller/Menu/Statistics/Pivot.hs:0+msgid "Total"+msgstr ""
+ lhae.cabal view
@@ -0,0 +1,101 @@+Name: lhae+Version: 0.0.2+Cabal-Version: >= 1.6+License: GPL+License-file: COPYING+Author: Alexander Bau+Maintainer: Alexander Bau+Homepage: http://www.imn.htwk-leipzig.de/~abau/lhae+Synopsis: Simple spreadsheet program+Build-Type: Custom+Category: Data+Description: lhae is a spreadsheet program. It features a simple formula language and some basic statistical methods.++Extra-Source-Files: i18n/*.po i18n/*.pot++x-gettext-domain-name: lhae+x-gettext-po-files: i18n/*.po +x-gettext-domain-def: __MESSAGE_CATALOG_DOMAIN__+x-gettext-msg-cat-def: __MESSAGE_CATALOG_DIR__++Executable lhae+ Main-Is: Main.hs+ Hs-Source-Dirs: src+ Ghc-options: -W -fno-warn-incomplete-patterns++ Extensions: CPP++ Build-Depends: base >= 4.2 && < 5.0+ , mtl >= 1.1 && < 2.0+ , wx >= 0.12 && < 1.0+ , wxcore >= 0.12 && < 1.0+ , parsec >= 2.1 && < 3.0+ , containers >= 0.3 && < 1.0+ , array >= 0.3 && < 1.0+ , filepath >= 1.1 && < 2.0+ , directory >= 1.0 && < 2.0+ , setlocale >= 0.0 && < 1.0+ , hgettext >= 0.1 && < 1.0+ , utf8-string >= 0.3 && < 1.0+ , process >= 1.0 && < 2.0+++ Other-modules: + Util.Gnuplot+ Util.DynArray+ CellCoordinate+ Model.GridHeader+ Model.CellContent+ Model.CellExpression.Evaluator.Common+ Model.CellExpression.Evaluator.Math+ Model.CellExpression.Evaluator+ Model.Cell+ Model.CellExpression.Parser+ View.Component.TextWindow+ Util.Sexp+ Util.Sexpable+ Controller.Menu.File.Export.Diagram.BarChartParameters+ Controller.Menu.File.Export.Diagram.BoxPlotParameters+ Controller.Menu.File.Import.CsvParameters+ Util.Color+ Util+ View.Component.Grid+ View.Component.Notebook+ View+ View.Modes+ Model.Grid+ Model+ View.FormulaInput+ View.GridPage+ I18n+ Util.FileType+ Controller.Menu.File.Export.ImageParameters+ View.Dialog.Complex+ View.Dialog.Simple+ Constants+ Config+ Controller+ Controller.Cell+ Controller.Menu.Table.Add+ Controller.Grid+ Controller.FormulaInput+ Controller.Menu.Table.Reevaluate+ Controller.Canonical+ Controller.SaveConfig+ Controller.Dialog+ Controller.Menu.File.Open+ Controller.Menu.File.Save+ Controller.Menu.File.NewClose+ Controller.Menu.File.Import.Csv+ Controller.Menu.File.Export.Image+ Controller.Menu.Table.Copy+ Controller.Menu.Table.Delete+ Controller.Menu.Table.Transpose+ Controller.Menu.Statistics.FrequencyDistribution+ Controller.Menu.Statistics.Descriptive+ Controller.Menu.Statistics.Pivot+ Controller.Menu.File.Export.Diagram.Common+ Controller.Menu.File.Export.Diagram.BarChart+ Controller.Menu.File.Export.Diagram.BoxPlot+ Run+ Main
+ src/CellCoordinate.hs view
@@ -0,0 +1,11 @@+module CellCoordinate (CellCoord,coordRange)+ where++type CellCoord = (Int,Int)++coordRange :: (CellCoord,CellCoord) -> [CellCoord]+coordRange ((fromRow,fromCol),(toRow,toCol)) = + do row <- [fromRow..toRow]+ col <- [fromCol..toCol]+ return (row,col)+
+ src/Config.hs view
@@ -0,0 +1,94 @@+module Config (Config,load,save,initI18n+ ,getLastHandledPathForDialogs,setLastHandledPath+ ,csvImportParameters + ,imageExportParameters,barChartExportParameters+ ,boxPlotExportParameters+ )+ where++import System.Directory (createDirectoryIfMissing,doesFileExist)+import System.FilePath (takeDirectory)+import Data.Maybe (fromMaybe)+import qualified I18n+import Util.Sexp (Sexp(..),parseFile,prettyPrint)+import Util.Sexpable (Sexpable(..),PSE,unexpected,recordToSexp,recordFromSexp)+import qualified Controller.Menu.File.Export.ImageParameters as EImage+import qualified Controller.Menu.File.Export.Diagram.BarChartParameters as EBarChart+import qualified Controller.Menu.File.Export.Diagram.BoxPlotParameters as EBoxPlot+import qualified Controller.Menu.File.Import.CsvParameters as ICsv++data Config = Config { lastHandledPath :: Maybe FilePath+ , locale :: String+ , csvImportParameters :: ICsv.Parameters+ , imageExportParameters :: EImage.Parameters+ , barChartExportParameters :: EBarChart.Parameters+ , boxPlotExportParameters :: EBoxPlot.Parameters+ }++defaultConfig :: Config+defaultConfig = Config Nothing "" ICsv.defaultParams + EImage.defaultParams EBarChart.defaultParams+ EBoxPlot.defaultParams++instance Sexpable Config where+ toSexp = recordToSexp+ [ ("lastHandledPath", toSexp . lastHandledPath)+ , ("locale", toSexp . locale)+ , ("csvImportParameters", toSexp . csvImportParameters)+ , ("imageExportParameters", toSexp . imageExportParameters)+ , ("barChartExportParameters", toSexp . barChartExportParameters)+ , ("boxPlotExportParameters", toSexp . boxPlotExportParameters)+ ]+ fromSexp = recordFromSexp setOption defaultConfig++setOption :: Config -> Sexp -> PSE Config+setOption config sexp =+ case sexp of+ List [Atom "lastHandledPath",path] -> do+ option <- fromSexp path+ return $ config {lastHandledPath = option}+ List [Atom "locale",locale] -> do+ option <- fromSexp locale+ return $ config {locale = option}+ List [Atom "csvImportParameters",params] -> do+ option <- fromSexp params+ return $ config {csvImportParameters = option}+ List [Atom "imageExportParameters",params] -> do+ option <- fromSexp params+ return $ config {imageExportParameters = option}+ List [Atom "barChartExportParameters",params] -> do+ option <- fromSexp params+ return $ config {barChartExportParameters = option}+ List [Atom "boxPlotExportParameters",params] -> do+ option <- fromSexp params+ return $ config {boxPlotExportParameters = option}+ _ -> unexpected sexp++load :: FilePath -> IO (Either String Config)+load filePath = do+ fileExist <- doesFileExist filePath+ if fileExist+ then do + result <- parseFile filePath+ let invalidConfig msg = return $ Left $ "Invalid config file: " ++ msg+ case result of+ Left error -> invalidConfig error+ Right sexp -> case fromSexp sexp of+ Left error -> invalidConfig $ show error+ Right config -> return $ Right config+ else return $ Right defaultConfig++save :: FilePath -> Config -> IO ()+save filePath config = do+ createDirectoryIfMissing False $ takeDirectory filePath+ writeFile filePath $ prettyPrint $ toSexp config++initI18n :: Config -> IO ()+initI18n = I18n.initI18n . locale++getLastHandledPathForDialogs :: Config -> FilePath+getLastHandledPathForDialogs = (fromMaybe "") . lastHandledPath++setLastHandledPath :: FilePath -> Config -> Config+setLastHandledPath filePath config = + config {lastHandledPath = Just filePath}
+ src/Constants.hs view
@@ -0,0 +1,23 @@+module Constants (programName,programVersion,configFilePath+ ,tmpFilePath+ )+ where++import Control.Applicative ((<$>))+import System.Directory (getAppUserDataDirectory,getTemporaryDirectory)+import System.FilePath (combine,(</>))++programName :: String+programName = "lhae"++programVersion :: String+programVersion = "0.0"++configFilePath :: IO FilePath+configFilePath = do + appDir <- getAppUserDataDirectory programName+ return $ appDir </> "config"++tmpFilePath :: String -> IO FilePath+tmpFilePath fileName = + combine `flip` fileName <$> getTemporaryDirectory
+ src/Controller.hs view
@@ -0,0 +1,50 @@+module Controller (CtrlData,Controller,new,onView,onModel,onGridView+ ,onGridModel,setOnConfig,getFromConfig+ ,currentConfig)+ where++import Control.Monad.Reader+import Data.IORef+import Config (Config)+import View (View)+import qualified View as V+import qualified View.Component.Grid as GridV+import Model (Model)+import qualified Model as M+import qualified Model.Grid as GridM++data CtrlData = CtrlData { config :: IORef Config+ , model :: Model+ , view :: View+ }++type Controller a = ReaderT CtrlData IO a++new :: Config -> Model -> View -> IO CtrlData+new config model view = do+ configRef <- newIORef config+ return $ CtrlData configRef model view++currentConfig :: Controller Config+currentConfig = asks config >>= liftIO . readIORef++onView :: (View -> IO a) -> Controller a+onView f = asks view >>= liftIO . f++onModel :: (Model -> IO a) -> Controller a+onModel f = asks model >>= liftIO . f++onGridView :: (GridV.Grid -> IO a) -> Controller a+onGridView f = asks view >>= liftIO . flip V.onGrid f++onGridModel :: (GridM.Grid -> IO a) -> Controller a+onGridModel f = asks model >>= liftIO . flip M.onGrid f++setOnConfig :: (Config -> Config) -> Controller ()+setOnConfig setter = do+ config <- asks config + liftIO $ modifyIORef config setter++getFromConfig :: (Config -> a) -> Controller a+getFromConfig getter = + asks config >>= liftIO . readIORef >>= return . getter
+ src/Controller/Canonical.hs view
@@ -0,0 +1,66 @@+module Controller.Canonical (Row,Column,RowData,ColumnData+ ,getRow,getColumn+ ,addRow,addColumn,addRow',addColumn'+ ,writeRowData,writeColumnData)+ where++import Control.Monad (forM)+import Controller (Controller,onGridView) +import Controller.Cell (getInput) +import Controller.Grid (writeCells)+import qualified Controller.Menu.Table.Add as Add+import View.Component.Grid (numDataRows,numDataColumns,getColumnLabel+ ,getRowLabel)+import CellCoordinate (coordRange)++type ColumnData = [String]+type Column = (String,ColumnData)+type RowData = [String]+type Row = (String,RowData)++getRow :: Int -> Controller Row+getRow row = do+ label <- onGridView $ getRowLabel row+ cells <- do cols <- onGridView numDataColumns+ forM [0..cols-1] $ \c -> getInput (row,c)+ return (label,cells)++getColumn :: Int -> Controller Column+getColumn col = do+ label <- onGridView $ getColumnLabel col+ cells <- do rows <- onGridView numDataRows+ forM [0..rows-1] $ \r -> getInput (r,col)+ return (label,cells)++addColumn :: (Maybe String,ColumnData) -> Controller ()+addColumn (label,cells) = do+ index <- onGridView numDataColumns+ Add.addColumn label+ writeColumnData index cells++addColumn' :: Column -> Controller ()+addColumn' (label,cells) = addColumn (Just label,cells)++addRow :: (Maybe String,RowData) -> Controller ()+addRow (label,cells) = do+ index <- onGridView numDataRows+ Add.addRow label+ writeRowData index cells++addRow' :: Row -> Controller ()+addRow' (label,cells) = addRow (Just label,cells)++writeColumnData :: Int -> ColumnData -> Controller ()+writeColumnData index columnData = do+ rowsInView <- onGridView numDataRows+ let range = let numRows = max rowsInView $ length columnData+ in coordRange ((0,index),(numRows-1,index))+ writeCells $ zip range columnData ++writeRowData :: Int -> RowData -> Controller ()+writeRowData index rowData = do+ colsInView <- onGridView numDataColumns+ let range = let numCols = max colsInView $ length rowData+ in coordRange ((index,0),(index,numCols-1))+ writeCells $ zip range rowData+
+ src/Controller/Cell.hs view
@@ -0,0 +1,32 @@+module Controller.Cell (getInput,updateInView+ ,showContent,showSelectedContent)+ where++import Control.Applicative ((<$>))+import Control.Monad (forM_)+import Controller (Controller,onGridModel,onGridView,onView)+import View.Component.Grid (setValue,getCursorPos)+import View.FormulaInput (logString)+import Model.Grid (getCell)+import Model.Cell (showValue,input,value)+import Model.CellContent (CellValue(Error))+import CellCoordinate (CellCoord)++getInput :: CellCoord -> Controller String+getInput coord = input <$> (onGridModel $ getCell coord)++updateInView :: [CellCoord] -> Controller ()+updateInView coords =+ forM_ coords $ \c -> do cell <- onGridModel $ getCell c+ onGridView $ setValue c $ showValue cell+ +showContent :: CellCoord -> Controller ()+showContent coords = do+ cell <- onGridModel $ getCell coords+ let info = case value cell of+ Error e -> show e+ _ -> input cell+ onView $ logString info++showSelectedContent :: Controller ()+showSelectedContent = onGridView getCursorPos >>= showContent
+ src/Controller/Dialog.hs view
@@ -0,0 +1,135 @@+module Controller.Dialog (openFileDialog,saveFileDialog+ ,chooseColumnDialog+ ,chooseColumnsDialog+ ,chooseRowsDialog+ ,chooseColumnsAndTableDialog+-- ,pushColorButton+ ,previewImage,warning+ ,nonNumericDataWarningText,nonNumericDataWarning+ ,noColumnsSelectedText,noColumnsSelected)+ where++import Control.Applicative ((<$>))+import Graphics.UI.WX (Prop ((:=)),on)+import qualified Graphics.UI.WX as WX+import qualified Graphics.UI.WXCore as WXC+import System.FilePath (takeDirectory)+import Controller (Controller,onView,onGridView,setOnConfig,getFromConfig)+import View (View,frame)+import View.Component.Grid (getColumnLabels,getRowLabels)+import View.GridPage (captions)+import View.Dialog.Complex (Layout (..),Widget (..),Modifier (..)+ ,showSimpleDialog,okButton,cancelButton)+import qualified View.Dialog.Simple as Simple+import Config (getLastHandledPathForDialogs,setLastHandledPath)+import Util (justWhen)+import I18n (__)++openFileDialog :: String -> [(String,[String])] -> Controller (Maybe FilePath)+openFileDialog caption filters = do+ lastPath <- getFromConfig getLastHandledPathForDialogs+ result <- onView $ \view -> + WX.fileOpenDialog (frame view) True True caption+ ((__ "Any file",["*"]) : filters) lastPath ""+ justWhen result $ setOnConfig . setLastHandledPath . takeDirectory+ return result++saveFileDialog :: String -> [(String,[String])] -> Controller (Maybe FilePath)+saveFileDialog caption filters = do+ lastPath <- getFromConfig getLastHandledPathForDialogs+ result <- onView $ \view -> + WX.fileSaveDialog (frame view) True True caption+ ((__ "Any file",["*"]) : filters) lastPath ""+ justWhen result $ setOnConfig . setLastHandledPath . takeDirectory+ return result++chooseOneFromListDialog :: String -> String -> [String] -> Controller (Maybe Int)+chooseOneFromListDialog dialogCaption listCaption list =+ let listBox = SingleListBox list id $ \_ -> id+ dialog = Modifier Margin $ Column+ [ Modifier (Boxed listCaption) $ Modifier HFill $ Widget listBox+ , Modifier Center $ Row [ okButton (), cancelButton]]+ in+ maybe Nothing id + <$> (onView $ showSimpleDialog dialogCaption dialog Nothing)++chooseSomeFromListDialog :: String -> String -> [String] -> Controller [Int]+chooseSomeFromListDialog dialogCaption listCaption list =+ let listBox = MultiListBox list id $ \_ -> id+ dialog = Modifier Margin $ Column+ [ Modifier (Boxed listCaption) $ Modifier HFill $ Widget listBox+ , Modifier Center $ Row [ okButton (), cancelButton]]+ in+ maybe [] id <$> (onView $ showSimpleDialog dialogCaption dialog [])++chooseColumnDialog :: String -> Controller (Maybe Int)+chooseColumnDialog caption = + onGridView getColumnLabels + >>= chooseOneFromListDialog caption (__ "Column")++chooseColumnsDialog :: String -> Controller [Int]+chooseColumnsDialog caption = + onGridView getColumnLabels + >>= chooseSomeFromListDialog caption (__ "Columns")++chooseRowsDialog :: String -> Controller [Int]+chooseRowsDialog caption = + onGridView getRowLabels + >>= chooseSomeFromListDialog caption (__ "Rows")++chooseSomethingAndTableDialog :: String -> String -> [String] + -> Controller (Maybe ([Int],Maybe Int))+chooseSomethingAndTableDialog dialogCaption someCaption something = do+ tableCaptions <- onView captions+ let someList = MultiListBox something fst+ $ \(_,table) some -> (some,table)+ tableList = SingleListBox tableCaptions snd+ $ \(some,_) table -> (some,table)+ dialog = Modifier Margin $ Column+ [ Modifier Center $ Row+ [ Modifier (Boxed someCaption) $ Modifier HFill $ Widget someList+ , Label $ __ "to"+ , Modifier (Boxed $ __ "Table") $ Widget tableList+ ]+ , Modifier Center $ Row + [ okButton (), cancelButton]]+ onView $ showSimpleDialog dialogCaption dialog ([],Nothing)++chooseColumnsAndTableDialog :: String+ -> Controller (Maybe ([Int],Maybe Int))+chooseColumnsAndTableDialog caption =+ onGridView getColumnLabels + >>= chooseSomethingAndTableDialog caption (__ "Columns")++previewImage :: WXC.Bitmap () -> Controller ()+previewImage bitmap = + let drawBitmap dc _ = WX.drawBitmap dc bitmap WX.pointZero False []+ makeWindow dialog = do+ size <- do width <- WXC.bitmapGetWidth bitmap+ height <- WXC.bitmapGetHeight bitmap+ return (width,height)++ WX.scrolledWindow dialog [ WX.virtualSize := uncurry WX.sz size+ , WX.clientSize := uncurry WX.sz size+ , on WX.paint := drawBitmap]+ dialog = Modifier Margin $ Column+ [ Widget $ ScrolledWindow makeWindow+ , Modifier Center $ okButton ()]+ in+ onView (showSimpleDialog (__ "Preview") dialog ()) >> return ()++warning :: String -> Controller ()+warning = onView . Simple.warning++nonNumericDataWarningText :: [String] -> String+nonNumericDataWarningText labels = + unwords [__ "Column(s)",show labels,__ "contains non-numeric data"]++nonNumericDataWarning :: [String] -> Controller ()+nonNumericDataWarning = warning . nonNumericDataWarningText++noColumnsSelectedText :: String+noColumnsSelectedText = __ "No columns selected"++noColumnsSelected :: Controller ()+noColumnsSelected = warning noColumnsSelectedText
+ src/Controller/FormulaInput.hs view
@@ -0,0 +1,15 @@+module Controller.FormulaInput (eventHandler)+ where++import Controller (Controller,onView,onGridView)+import Controller.Cell (showSelectedContent)+import Controller.Grid (writeCell)+import View.FormulaInput (formula)+import View.Component.Grid (getCursorPos)++eventHandler :: Controller ()+eventHandler = do+ input <- onView formula+ coords <- onGridView $ getCursorPos+ writeCell coords input+ showSelectedContent
+ src/Controller/Grid.hs view
@@ -0,0 +1,163 @@+module Controller.Grid (new,changePageEventHandler+ ,setFilePath,deleteCurrent,changePageTo+ ,writeCell,writeCells+ ,updateRowLabel,updateColumnLabel+ ,updateRowLabels,updateColumnLabels+ ,markAsSaved)+ where++import Control.Monad.Reader+import qualified Graphics.UI.WXCore as WXC+import Controller (Controller,onView,onModel,onGridModel,onGridView)+import Controller.Cell (updateInView,showContent,showSelectedContent)+import Controller.Menu.Table.Add (addRow,addColumn)+import View (onGrid,info)+import qualified View.GridPage as GridPage+import View.Component.Grid (getRowLabel,getColumnLabel,numDataRows+ ,numDataColumns)+import qualified View.Component.Grid as GridV+import View.Dialog.Simple (input)+import View.Modes (formulaMode)+import qualified Model as M+import qualified Model.Grid as GridM+import qualified Model.Cell as Cell+import System.FilePath (takeFileName)+import Util (justWhen)+import CellCoordinate (CellCoord)+import View.FormulaInput (logString)++new :: Maybe FilePath -> Controller ()+new filePath = + let addModel id = + do viewInfo <- onView $ return . info+ onModel $ M.addGrid id viewInfo+ justWhen filePath $ onGridModel . GridM.setFilePath++ addView grid =+ do onView $ GridPage.add (fmap takeFileName filePath) grid+ ctrl <- ask+ onView $ \view -> + onGrid view $ \grid ->+ do WXC.gridOnGridEvent grid $ \event -> + runReaderT (eventHandler event) ctrl+ WXC.windowOnKeyDown grid + $ GridPage.keyEventHandler view+ in do+ (gridViewId,gridView) <- onView GridPage.new+ addModel gridViewId+ addView gridView++setFilePath :: FilePath -> Controller ()+setFilePath filePath = do+ onView $ GridPage.setCurrentCaption $ takeFileName filePath+ onGridModel $ GridM.setFilePath filePath++markAsUnsaved :: Controller ()+markAsUnsaved = do+ caption <- onView GridPage.getCurrentCaption+ when (head caption /= '*') + $ onView $ GridPage.setCurrentCaption $ '*':caption++markAsSaved :: Controller ()+markAsSaved = do+ x:xs <- onView GridPage.getCurrentCaption+ when (x == '*') $ onView $ GridPage.setCurrentCaption xs++deleteCurrent :: Controller ()+deleteCurrent = do+ onModel M.deleteCurrent+ onView GridPage.deleteCurrent+ next <- onView GridPage.currentSelectionId+ case next of+ Just n -> do onModel $ M.setCurrent n+ showSelectedContent + Nothing -> onView $ logString ""++eventHandler :: WXC.EventGrid -> Controller ()+eventHandler event = + case event of + WXC.GridLabelMouse row col (WXC.MouseLeftDown {}) -> + do extendWhenClicked (row,col)+ liftIO WXC.propagateEvent+ WXC.GridLabelMouse row col (WXC.MouseLeftDClick {}) -> + do value <- if col == -1+ then onGridView $ getRowLabel row+ else onGridView $ getColumnLabel col+ value' <- onView $ input "Label" value+ justWhen value' $ if col == -1+ then updateRowLabel row+ else updateColumnLabel col++ WXC.GridCellMouse row col (WXC.MouseLeftDown {}) -> + do extendWhenClicked (row,col)+ liftIO WXC.propagateEvent+ WXC.GridEditorShown row col veto -> + do extendWhenClicked (row,col) + cell <- onGridModel $ GridM.getCell (row,col)+ onView (formulaMode (row,col) $ Cell.input cell)+ liftIO veto+ WXC.GridCellSelect row col _ -> + do showContent (row,col)+ liftIO WXC.propagateEvent+ _ -> liftIO WXC.propagateEvent++changePageEventHandler :: Controller ()+changePageEventHandler = do+ Just n <- onView $ GridPage.currentSelectionNum+ Just id <- onView $ GridPage.currentSelectionId+ onView $ GridPage.setCurrent n+ onModel $ M.setCurrent id + showSelectedContent++changePageTo :: Int -> Controller ()+changePageTo = onView . GridPage.fireSelectionEvent++extendWhenClicked :: CellCoord -> Controller ()+extendWhenClicked (row,column) = do+ rows <- onGridView GridV.numDataRows+ cols <- onGridView GridV.numDataColumns+ when (row == rows) $ addRow Nothing+ when (column == cols) $ addColumn Nothing++updateRowLabel,updateColumnLabel :: Int -> String -> Controller ()+updateRowLabel n to = do+ changed <- onGridModel $ GridM.updateRowLabel n to+ onGridView $ GridV.setRowLabel n to+ updateInView changed+ showSelectedContent+ markAsUnsaved+updateColumnLabel n to = do+ changed <- onGridModel $ GridM.updateColumnLabel n to+ onGridView $ GridV.setColumnLabel n to+ updateInView changed+ showSelectedContent+ markAsUnsaved++updateRowLabels,updateColumnLabels :: [String] -> Controller ()+updateRowLabels labels = do+ n <- onGridView numDataRows+ forM_ (zip [0..n-1] labels) $ uncurry updateRowLabel+updateColumnLabels labels = do+ n <- onGridView numDataColumns+ forM_ (zip [0..n-1] labels) $ uncurry updateColumnLabel++writeCell :: CellCoord -> String -> Controller ()+writeCell coord input = writeCells [(coord,input)]++writeCells :: [(CellCoord,String)] -> Controller ()+writeCells [] = return ()+writeCells cells = + let (maxRow,maxCol) = maximum $ map fst cells+ in do+ rows <- onGridView numDataRows+ cols <- onGridView numDataColumns++ forM_ [rows..maxRow] $ \_ -> addRow Nothing+ forM_ [cols..maxCol] $ \_ -> addColumn Nothing++ forM_ cells $ \(coord,input) -> do+ changed <- onGridModel $ GridM.setCell coord input+ updateInView changed++ markAsUnsaved+
+ src/Controller/Menu/File/Export/Diagram/BarChart.hs view
@@ -0,0 +1,99 @@+module Controller.Menu.File.Export.Diagram.BarChart (eventHandler)+ where++import Control.Applicative ((<$>))+import Control.Monad (when)+import Text.Printf (printf)+import Data.List (transpose)+import Controller (Controller,getFromConfig,onView+ ,onGridView,setOnConfig)+import Controller.Menu.File.Export.Diagram.BarChartParameters + (Parameters(..),gnuplotLegend,legendFromInt,intFromLegend,legendList)+import qualified Controller.Menu.File.Export.Diagram.Common as Common+import Controller.Dialog (nonNumericDataWarningText+ ,noColumnsSelectedText,saveFileDialog)+import Config (barChartExportParameters)+import View.Component.Grid (getRowLabels,getColumnLabels)+import View.Dialog.Complex (Layout (..),Widget (..),Modifier (..)+ ,showDialog,cancelButton)+import Util.FileType (justPngWildcard)+import I18n (__)++data Action = Preview | Export++eventHandler :: Controller ()+eventHandler = getFromConfig barChartExportParameters + >>= \params -> eventHandlerWith $ params {columns = []}++eventHandlerWith :: Parameters -> Controller ()+eventHandlerWith initParams = do+ result <- do d <- dialog <$> onGridView getColumnLabels+ onView $ showDialog (__ "Export") d initParams+ case result of+ Nothing -> return ()+ Just (params,Export) -> + do saveFile <- saveFileDialog (__ "Export") justPngWildcard+ setOnConfig $ \c -> c {barChartExportParameters = params}+ case saveFile of+ Nothing -> eventHandlerWith params+ Just imageFilePath -> + do success <- Common.plot (gpFileContent params) + (dataFileContent params) imageFilePath+ when (not success) $ eventHandlerWith params++ Just (params,Preview) ->+ do setOnConfig $ \c -> c {barChartExportParameters = params}+ _ <- Common.showPreview (gpFileContent params) + (dataFileContent params)+ eventHandlerWith params++dialog :: [String] -> Layout Parameters Action+dialog columnLabels =+ let setCols p cols = p {columns = cols}+ setBarWidth p w = p {barWidth = w}+ setGaps p g = p {gaps = g}+ setLegend p i = p {legend = legendFromInt i}+ in+ Modifier Margin $ Column + [ Row [ Grid [[ Label $ __ "Bar width"+ , Widget $ NumberEntry barWidth setBarWidth]+ ,[ Label $ __ "Gaps"+ , Widget $ Spinner 0 maxBound gaps setGaps]+ ,[ Label $ __ "Legend"+ , Widget $ Choice legendList + (intFromLegend . legend) setLegend]+ ]+ , Modifier Center $ Modifier (Boxed $ __ "Columns") + $ Widget $ MultiListBox columnLabels columns setCols+ ]+ , Modifier Center $ Row + [ Widget $ DefaultButton (__ "&Export") Export+ , Widget $ Button (__ "&Preview") Preview+ , cancelButton]]++dataFileContent :: Parameters -> Controller (Either String String)+dataFileContent (Parameters {columns = []}) = return $ Left noColumnsSelectedText+dataFileContent (Parameters {columns = cols}) = do+ numericColumns <- Common.getNumericColumns cols+ case numericColumns of+ Left nonNumeric -> return $ Left $ nonNumericDataWarningText nonNumeric+ Right columns -> + do rowLabels <- onGridView getRowLabels+ let firstLine = "\"\"" : rowLabels+ columnToLine (label,values) = label:(map show values)+ dataLines = map columnToLine columns+ lines = transpose $ firstLine : dataLines+ return $ Right $ unlines $ map unwords lines++gpFileContent :: Parameters -> FilePath -> String+gpFileContent params dataFilePath = + printf (unlines+ [ "set key %s top"+ , "set style data histogram"+ , "set style histogram clustered gap %d"+ , "set boxwidth %f"+ , "set style fill solid"+ , "plot for [COL=2:%d] \"%s\" using COL:xtic(1) title columnhead"+ ])+ (gnuplotLegend $ legend params) (gaps params) (barWidth params)+ (1 + (length $ columns params)) dataFilePath
+ src/Controller/Menu/File/Export/Diagram/BarChartParameters.hs view
@@ -0,0 +1,69 @@+module Controller.Menu.File.Export.Diagram.BarChartParameters+ (Parameters(..),defaultParams,LegendPosition(..)+ ,legendList,legendFromInt,intFromLegend,gnuplotLegend)+ where++import Prelude hiding (Left,Right)+import Util.Sexp (Sexp(..))+import Util.Sexpable (Sexpable(..),PSE,unexpected,recordToSexp+ ,recordFromSexp)++data LegendPosition = Left | Center | Right deriving (Show,Read)+type Legend = Maybe LegendPosition++data Parameters = Parameters { columns :: [Int]+ , barWidth :: Double+ , gaps :: Int+ , legend :: Legend+ }++defaultParams :: Parameters+defaultParams = Parameters [] 1.0 1 (Just Right)++legendList :: [String] +legendList = ["None","Left","Center","Right"]++legendFromInt :: Int -> Legend+legendFromInt n = case n of 0 -> Nothing+ 1 -> Just Left+ 2 -> Just Center+ 3 -> Just Right++intFromLegend :: Legend -> Int+intFromLegend legend = case legend of Nothing -> 0+ Just Left -> 1+ Just Center -> 2+ Just Right -> 3++gnuplotLegend :: Legend -> String+gnuplotLegend legend = case legend of Nothing -> "off"+ Just Left -> "left"+ Just Center -> "center"+ Just Right -> "right"++instance Sexpable LegendPosition where+ toSexp = toSexp . show+ fromSexp (Atom a) = return $ read a+ fromSexp sexp = unexpected sexp++instance Sexpable Parameters where+ toSexp = recordToSexp+ [ ("barWidth", toSexp . barWidth)+ , ("gaps", toSexp . gaps)+ , ("legend", toSexp . legend)+ ]+ fromSexp = recordFromSexp setParam defaultParams++setParam :: Parameters -> Sexp -> PSE Parameters+setParam p sexp =+ case sexp of+ List [Atom "barWidth",w] -> do+ parameter <- fromSexp w+ return $ p {barWidth = parameter}+ List [Atom "gaps",g] -> do+ parameter <- fromSexp g+ return $ p {gaps = parameter}+ List [Atom "legend",legend] -> do+ parameter <- fromSexp legend+ return $ p {legend = parameter}+ _ -> unexpected sexp
+ src/Controller/Menu/File/Export/Diagram/BoxPlot.hs view
@@ -0,0 +1,99 @@+module Controller.Menu.File.Export.Diagram.BoxPlot (eventHandler)+ where++import Control.Applicative ((<$>))+import Control.Monad (when)+import Text.Printf (printf)+import Data.List (sort)+import Controller (Controller,getFromConfig,onView+ ,onGridView,setOnConfig)+import Controller.Menu.File.Export.Diagram.BoxPlotParameters (Parameters(..))+import qualified Controller.Menu.File.Export.Diagram.Common as Common+import Controller.Dialog (nonNumericDataWarningText+ ,noColumnsSelectedText,saveFileDialog)+import Config (boxPlotExportParameters)+import View.Component.Grid (getColumnLabels)+import View.Dialog.Complex (Layout (..),Widget (..),Modifier (..)+ ,showDialog,cancelButton)+import Util.FileType (justPngWildcard)+import qualified Model.CellExpression.Evaluator.Math as Math+import I18n (__)++data Action = Preview | Export++eventHandler :: Controller ()+eventHandler = getFromConfig boxPlotExportParameters + >>= \params -> eventHandlerWith $ params {columns = []}++eventHandlerWith :: Parameters -> Controller ()+eventHandlerWith initParams = do+ result <- do d <- dialog <$> onGridView getColumnLabels+ onView $ showDialog (__ "Export") d initParams+ case result of+ Nothing -> return ()+ Just (params,Export) -> + do saveFile <- saveFileDialog (__ "Export") justPngWildcard+ setOnConfig $ \c -> c {boxPlotExportParameters = params}+ case saveFile of+ Nothing -> eventHandlerWith params+ Just imageFilePath -> + do success <- Common.plot (gpFileContent params) + (dataFileContent params) imageFilePath+ when (not success) $ eventHandlerWith params++ Just (params,Preview) ->+ do setOnConfig $ \c -> c {boxPlotExportParameters = params}+ _ <- Common.showPreview (gpFileContent params) + (dataFileContent params)+ eventHandlerWith params++dialog :: [String] -> Layout Parameters Action+dialog columnLabels =+ let setCols p cols = p {columns = cols}+ setBoxWidth p w = p {boxWidth = w}+ setGaps p g = p {gaps = g}+ in+ Modifier Margin $ Column + [ Row [ Grid [[ Label $ __ "Box width"+ , Widget $ NumberEntry boxWidth setBoxWidth]+ ,[ Label $ __ "Gaps"+ , Widget $ Spinner 1 maxBound gaps setGaps]+ ]+ , Modifier Center $ Modifier (Boxed $ __ "Columns") + $ Widget $ MultiListBox columnLabels columns setCols+ ]+ , Modifier Center $ Row + [ Widget $ DefaultButton (__ "&Export") Export+ , Widget $ Button (__ "&Preview") Preview+ , cancelButton]]++dataFileContent :: Parameters -> Controller (Either String String)+dataFileContent (Parameters {columns = []}) = return $ Left noColumnsSelectedText+dataFileContent (Parameters {columns = cols,gaps = gaps}) = do+ numericColumns <- Common.getNumericColumns cols+ case numericColumns of+ Left nonNumeric -> return $ Left $ nonNumericDataWarningText nonNumeric+ Right columns -> + let toLine (i,(label,values)) =+ let sorted = sort values+ max = maximum sorted+ min = head sorted+ firstQuartile = Math.firstQuartileSorted sorted+ thirdQuartile = Math.thirdQuartileSorted sorted+ median = Math.medianSorted sorted+ values' = [firstQuartile,min,max,thirdQuartile,median]+ in unwords $ [show label,show i] ++ (map show values')+ in+ return $ Right $ unlines $ map toLine $ zip [0,gaps..] columns++gpFileContent :: Parameters -> FilePath -> String+gpFileContent params dataFilePath = + printf (unlines+ [ "set key off"+ , "set xrange [%d:%d]"+ , "set boxwidth %f"+ , "plot '%s' using 2:3:4:5:6:xtic(1) with candlesticks whiskerbars,"+ ++ "'' using 2:7:7:7:7 with candlesticks"+ ])+ (-(gaps params)) (length (columns params) + (gaps params) - 1)+ (boxWidth params) dataFilePath
+ src/Controller/Menu/File/Export/Diagram/BoxPlotParameters.hs view
@@ -0,0 +1,33 @@+module Controller.Menu.File.Export.Diagram.BoxPlotParameters+ (Parameters(..),defaultParams)+ where++import Util.Sexp (Sexp(..))+import Util.Sexpable (Sexpable(..),PSE,unexpected,recordToSexp+ ,recordFromSexp)++data Parameters = Parameters { columns :: [Int]+ , boxWidth :: Double+ , gaps :: Int+ }++defaultParams :: Parameters+defaultParams = Parameters [] 0.5 1++instance Sexpable Parameters where+ toSexp = recordToSexp+ [ ("boxWidth", toSexp . boxWidth)+ , ("gaps", toSexp . gaps)+ ]+ fromSexp = recordFromSexp setParam defaultParams++setParam :: Parameters -> Sexp -> PSE Parameters+setParam p sexp =+ case sexp of+ List [Atom "boxWidth",w] -> do+ parameter <- fromSexp w+ return $ p {boxWidth = parameter}+ List [Atom "gaps",g] -> do+ parameter <- fromSexp g+ return $ p {gaps = parameter}+ _ -> unexpected sexp
+ src/Controller/Menu/File/Export/Diagram/Common.hs view
@@ -0,0 +1,75 @@+module Controller.Menu.File.Export.Diagram.Common + (MakeGnuPlotFile,showPreview,plot,getNumericColumns)+ where++import Control.Monad (forM,when)+import Control.Monad.Trans (liftIO)+import Data.Either (partitionEithers)+import System.Exit (ExitCode(ExitSuccess,ExitFailure))+import qualified Graphics.UI.WXCore as WXC+import Controller (Controller,onGridModel,onGridView)+import Controller.Dialog (previewImage,warning)+import Constants (tmpFilePath,programName)+import qualified Util.Gnuplot as GP+import Model.CellExpression.Evaluator.Common (getNumList)+import Model.Grid (getColumnValues)+import View.Component.Grid (getColumnLabel)+import Util ((<.>))+import I18n (__)++type MakeGnuPlotFile = FilePath -> String+type MakeDataFile = Controller (Either String String)++getTmpImageFilePath,getTmpDataFilePath,getTmpGpFilePath :: Controller FilePath+getTmpImageFilePath = liftIO $ tmpFilePath $ programName ++ ".gnuplot.png"+getTmpDataFilePath = liftIO $ tmpFilePath $ programName ++ ".gnuplot.data"+getTmpGpFilePath = liftIO $ tmpFilePath $ programName ++ ".gnuplot"++showPreview :: MakeGnuPlotFile -> MakeDataFile -> Controller Bool+showPreview makeGpFile makeDataFile = do+ tmpImageFilePath <- getTmpImageFilePath+ success <- plot makeGpFile makeDataFile tmpImageFilePath+ when success $ do + bitmap <- liftIO $ WXC.imageCreateFromFile tmpImageFilePath+ >>= WXC.imageConvertToBitmap+ previewImage bitmap+ return success++plot :: MakeGnuPlotFile -> MakeDataFile -> FilePath -> Controller Bool+plot makeGpFile makeDataFile imageFilePath = do+ dataFilePath <- getTmpDataFilePath+ gpFilePath <- getTmpGpFilePath++ makeDataFileContentResult <- makeDataFile++ case makeDataFileContentResult of+ Left msg -> warning msg >> return False+ Right dataFileContent ->+ let dataFile = (dataFilePath,dataFileContent)+ gpFileContent = unlines+ [ "set terminal png"+ , "set output \"" ++ imageFilePath ++ "\""+ , makeGpFile dataFilePath]+ gpFile = (gpFilePath,gpFileContent)+ in do+ result <- liftIO $ GP.run gpFile [dataFile]+ + case result of+ ExitSuccess -> return True+ ExitFailure _ -> + do warning $ concat [__ "Can not plot diagram",": ",show result]+ return False++getNumericColumns :: [Int] -> Controller (Either [String] [(String,[Double])])+getNumericColumns cols = do+ result <- forM cols $ \i -> do+ tmp <- onGridModel $ (getNumList <.> getColumnValues i)+ label <- onGridView $ getColumnLabel i+ case tmp of + Left _ -> return $ Left label+ Right nums -> return $ Right (label,nums)++ case partitionEithers result of+ (x:xs,_) -> return $ Left (x:xs)+ ([],columns) -> return $ Right columns+
+ src/Controller/Menu/File/Export/Image.hs view
@@ -0,0 +1,247 @@+module Controller.Menu.File.Export.Image (eventHandler)+ where++import Control.Monad (foldM,foldM_,when)+import Control.Monad.Trans (liftIO)+import Control.Applicative ((<$>))+import Foreign (Ptr,malloc,peek,nullPtr)+import qualified Graphics.UI.WXCore as WXC+import Config (imageExportParameters)+import Controller (Controller,onGridView,onView,getFromConfig,setOnConfig)+import View.Component.Grid (getColumnValues,numDataRows,numDataColumns+ ,getColumnLabel,getRowLabel)+import Controller.Menu.File.Export.ImageParameters + (Parameters(..),fileTypeList,fileTypeFromInt,intFromFileType)+import Controller.Dialog (saveFileDialog,previewImage,warning)+import View.Dialog.Complex (Layout (..),Widget (..),Modifier (..)+ ,showDialog,cancelButton)+import Util (justWhen)+import Util.FileType (fromFilePath,wildcard,bitmapType,unknownFileTypeIn)+import Util.Color (toWXColor)+import I18n (__)++data Action = Export | Preview++eventHandler :: Controller ()+eventHandler = getFromConfig imageExportParameters >>= eventHandlerWith++eventHandlerWith :: Parameters -> Controller ()+eventHandlerWith storedParams = do+ result <- onView $ showDialog (__ "Export") dialog storedParams+ justWhen result $ \(params,action) -> + case action of+ Export -> + do saveFile <- saveFileDialog (__ "Export") wildcard+ setOnConfig $ \c -> c {imageExportParameters = params}+ case saveFile of+ Nothing -> eventHandlerWith params+ Just file -> writeImage params file+ Preview -> + do showPreview params+ setOnConfig $ \c -> c {imageExportParameters = params}+ eventHandlerWith params++dialog :: Layout Parameters Action+dialog =+ let setFontSize p s = p {fontPointSize = s}+ setFontColor p c = p {fontColor = c}+ setBackgroundColor p c = p {backgroundColor = c}+ setPadding p pa = p {padding = pa}+ setRowLineWidth p w = p {rowLineWidth = w}+ setColLineWidth p w = p {colLineWidth = w}+ setFileType p f = p {fileType = fileTypeFromInt f}+ in+ Modifier Margin $ Column+ [ Grid [[ Label $ __ "Font size (pt)"+ , Widget $ Spinner 1 maxBound fontPointSize setFontSize]+ ,[ Label $ __ "Font color"+ , Widget $ ColorButton fontColor setFontColor]+ ,[ Label $ __ "Background color"+ , Widget $ ColorButton backgroundColor setBackgroundColor]+ ,[ Label $ __ "Padding (px)"+ , Widget $ Spinner 0 maxBound padding setPadding]+ ,[ Label $ __ "Line width (horizontal) (px)"+ , Widget $ Spinner 0 maxBound rowLineWidth setRowLineWidth]+ ,[ Label $ __ "Line width (vertical) (px)"+ , Widget $ Spinner 0 maxBound colLineWidth setColLineWidth]+ ,[ Label $ __ "File type"+ , Widget $ Choice fileTypeList + (intFromFileType . fileType) setFileType]+ ]+ , Modifier Center $ Row + [ Widget $ DefaultButton (__ "&Export") Export+ , Widget $ Button (__ "&Preview") Preview+ , cancelButton]+ ]+++showPreview :: Parameters -> Controller ()+showPreview params = do+ size <- getImageSize params+ (bitmap,dc) <- liftIO $ getBitmapAndDC params size+ exportTable params dc+ previewImage bitmap++writeImage :: Parameters -> FilePath -> Controller ()+writeImage params filepath = + case bmpType of+ Nothing -> warning $ unknownFileTypeIn filepath+ Just bmpType -> + do size <- getImageSize params+ (bitmap,dc) <- liftIO $ getBitmapAndDC params size+ exportTable params dc + image <- liftIO $ WXC.imageCreateFromBitmap bitmap + _ <- liftIO $ WXC.imageSaveFile image filepath bmpType+ return ()+ where+ bmpType = case fileType params of+ Nothing -> bitmapType <$> fromFilePath filepath+ Just ext -> Just $ bitmapType ext++getImageSize :: Parameters -> Controller (Int,Int)+getImageSize params = do+ (_,dc) <- liftIO $ getBitmapAndDC params (1,1)+ rows <- onGridView numDataRows+ cols <- onGridView numDataColumns++ colWidth <- sum <$> mapM (getColumnWidth params dc) [0..cols-1]+ rowHeight <- (*) rows <$> getRowHeight params dc++ colLabelHeight <- getRowHeight params dc+ rowLabelWidth <- exportRowLabels params dc++ let colLineWidth' = colLineWidth params * (cols - 1)+ rowLineHeigth = rowLineWidth params * (rows - 1)++ return ( rowLabelWidth + colWidth + colLineWidth'+ , colLabelHeight + rowHeight + rowLineHeigth)++exportTable :: Parameters -> WXC.DC a -> Controller ()+exportTable params dc = do+ cols <- onGridView numDataColumns+ + fstWidth <- exportRowLabels params dc+ foldM_ (\xOffset col -> do + x <- nextXOffset params dc xOffset col+ exportColumn params dc x col+ return x+ ) (padding params + fstWidth + (colLineWidth params)) [0..cols-1]++ withPenWidth dc (rowLineWidth params) $ drawRowLines params dc+ withPenWidth dc (colLineWidth params) $ drawColumnLines params dc fstWidth++exportRowLabels :: Parameters -> WXC.DC a -> Controller Int+exportRowLabels params dc = do+ rows <- onGridView numDataRows+ width <- foldM (\m row -> do+ label <- onGridView $ getRowLabel row+ (width,_) <- liftIO $ textExtent dc label+ y <- textYOffset params dc $ row + 1+ liftIO $ WXC.dcDrawText dc label $ WXC.pt (padding params) y+ return $ max m width+ ) 0 [0..rows-1]+ return $ width + (padding params * 2)++exportColumn :: Parameters -> WXC.DC a -> Int -> Int -> Controller ()+exportColumn params dc xOffset i = do+ values <- onGridView $ getColumnValues i+ rows <- onGridView numDataRows+ exportLabel++ mapM_ (\(row,value) -> do+ y <- textYOffset params dc $ row + 1+ liftIO $ WXC.dcDrawText dc value $ WXC.pt xOffset y+ ) $ zip [0..rows-1] values+ where+ exportLabel = do+ label <- onGridView $ getColumnLabel i + liftIO $ WXC.dcDrawText dc label $ WXC.pt xOffset $ padding params+ +drawRowLines :: Parameters -> WXC.DC a -> Controller ()+drawRowLines params dc = do+ dcWidth <- WXC.sizeW <$> (liftIO $ WXC.dcGetSize dc)+ rows <- onGridView numDataRows+ mapM_ (\row -> do + y <- lineYOffset params dc row+ liftIO $ WXC.dcDrawLine dc (WXC.pt 0 y) (WXC.pt dcWidth y)+ ) [1..rows]++drawColumnLines :: Parameters -> WXC.DC a -> Int -> Controller ()+drawColumnLines params dc xOffset = do+ dcHeight <- WXC.sizeH <$> (liftIO $ WXC.dcGetSize dc)+ cols <- onGridView numDataColumns+ foldM_ (\x col -> do + liftIO $ WXC.dcDrawLine dc (WXC.pt x 0) (WXC.pt x dcHeight)+ nextXOffset params dc x col+ ) (xOffset + (colLineWidth params `div` 2)) [1..cols]++textYOffset,lineYOffset :: Parameters -> WXC.DC a -> Int -> Controller Int+textYOffset params dc row = + liftIO $ do+ charHeight <- WXC.dcGetCharHeight dc+ let paddings = ((2 * row) + 1) * (padding params)+ lines = rowLineWidth params * row+ textHeights = row * charHeight+ return $ paddings + textHeights + lines++lineYOffset params dc row = do+ y <- textYOffset params dc row+ return $ y - (padding params) - (rowLineWidth params `div` 2)++nextXOffset :: Parameters -> WXC.DC a -> Int -> Int -> Controller Int+nextXOffset params dc x i = + if i == 0 then return x+ else do width <- getColumnWidth params dc $ i - 1+ return $ x + width + (colLineWidth params)++getRowHeight :: Parameters -> WXC.DC a -> Controller Int+getRowHeight params dc = do+ charHeight <- liftIO $ WXC.dcGetCharHeight dc+ return $ charHeight + (padding params * 2) ++getColumnWidth :: Parameters -> WXC.DC a -> Int -> Controller Int+getColumnWidth params dc i = do+ (labelWidth,_) <- onGridView (getColumnLabel i) >>= liftIO . textExtent dc+ values <- onGridView $ getColumnValues i+ maxWidth <- foldM (\m value -> + do (w,_) <- liftIO $ textExtent dc value+ return $ max m w) labelWidth values+ return $ (padding params * 2) + maxWidth++getWidthPtr,getHeightPtr :: IO (Ptr Int)+getWidthPtr = malloc+getHeightPtr = malloc++textExtent :: WXC.DC a -> String -> IO (Int,Int)+textExtent dc s = do+ font <- WXC.dcGetFont dc+ widthPtr <- getWidthPtr+ heightPtr <- getHeightPtr+ WXC.dcGetTextExtent dc s widthPtr heightPtr nullPtr nullPtr font+ width <- peek widthPtr+ height <- peek heightPtr+ return (width,height)++getBitmapAndDC :: Parameters -> (Int,Int) -> IO (WXC.Bitmap (),WXC.MemoryDC ())+getBitmapAndDC params (width,height) = do+ bitmap <- WXC.bitmapCreateEmpty (WXC.sz width height) (-1)+ dc <- WXC.memoryDCCreateWithBitmap bitmap+ font <- WXC.fontCreate (fontPointSize params) WXC.wxDEFAULT + WXC.wxNORMAL WXC.wxNORMAL False "" WXC.wxFONTENCODING_SYSTEM+ bgBrush <- WXC.brushCreateFromColour + (toWXColor $ backgroundColor params) WXC.wxSOLID+ WXC.dcSetBackground dc bgBrush+ WXC.dcSetFont dc font+ WXC.dcSetTextForeground dc $ toWXColor $ fontColor params+ WXC.dcClear dc+ return (bitmap,dc)++withPenWidth :: WXC.DC a -> Int -> Controller () -> Controller ()+withPenWidth dc width f = + when (width > 0) $ liftIO (setPenWidth dc width) + >> f >> liftIO (setPenWidth dc 1)+ where+ setPenWidth dc width = do+ pen <- WXC.dcGetPen dc+ WXC.penSetWidth pen width + WXC.dcSetPen dc pen
+ src/Controller/Menu/File/Export/ImageParameters.hs view
@@ -0,0 +1,83 @@+module Controller.Menu.File.Export.ImageParameters+ (Parameters (..),defaultParams,fileTypeList,fileTypeFromInt+ ,intFromFileType)+ where++import Util.Color (Color (..))+import Util.FileType (FileType(..),extension)+import Util.Sexp (Sexp(..))+import Util.Sexpable (Sexpable(..),PSE,unexpected,recordToSexp+ ,recordFromSexp)+import I18n (__)++data Parameters = Parameters { fontPointSize :: Int+ , fontColor :: Color+ , backgroundColor :: Color+ , padding :: Int+ , rowLineWidth :: Int+ , colLineWidth :: Int+ , fileType :: Maybe FileType+ }++defaultParams :: Parameters+defaultParams = Parameters 10 (Color (0,0,0)) (Color (255,255,255)) + 3 1 1 Nothing++instance Sexpable Parameters where+ toSexp = recordToSexp + [ ("fontPointSize", toSexp . fontPointSize)+ , ("fontColor", toSexp . fontColor)+ , ("backgroundColor", toSexp . backgroundColor)+ , ("padding", toSexp . padding)+ , ("rowLineWidth", toSexp . rowLineWidth)+ , ("colLineWidth", toSexp . colLineWidth)+ , ("fileType", toSexp . fileType)+ ]+ fromSexp = recordFromSexp setParam defaultParams ++setParam :: Parameters -> Sexp -> PSE Parameters+setParam p sexp =+ case sexp of+ List [Atom "fontPointSize",fps] -> do+ parameter <- fromSexp fps+ return $ p {fontPointSize = parameter}+ List [Atom "fontColor",color] -> do+ parameter <- fromSexp color+ return $ p {fontColor = parameter}+ List [Atom "backgroundColor",color] -> do+ parameter <- fromSexp color+ return $ p {backgroundColor = parameter}+ List [Atom "padding",padding] -> do+ parameter <- fromSexp padding+ return $ p {padding = parameter}+ List [Atom "rowLineWidth",width] -> do+ parameter <- fromSexp width+ return $ p {rowLineWidth = parameter}+ List [Atom "colLineWidth",width] -> do+ parameter <- fromSexp width+ return $ p {colLineWidth = parameter}+ List [Atom "fileType",fileType] -> do+ parameter <- fromSexp fileType+ return $ p {fileType = parameter}+ _ -> unexpected sexp++fileTypeList :: [String]+fileTypeList = [ (__ "By extension")+ , extension Bmp, extension Jpeg+ , extension Png, extension Tif]++fileTypeFromInt :: Int -> Maybe FileType+fileTypeFromInt i = case i of 0 -> Nothing+ 1 -> Just Bmp+ 2 -> Just Jpeg+ 3 -> Just Png+ 4 -> Just Tif++intFromFileType :: Maybe FileType -> Int+intFromFileType fileType = case fileType of+ Nothing -> 0+ Just Bmp -> 1+ Just Jpeg -> 2+ Just Png -> 3+ Just Tif -> 4+
+ src/Controller/Menu/File/Import/Csv.hs view
@@ -0,0 +1,100 @@+module Controller.Menu.File.Import.Csv (eventHandler) + where++import Control.Applicative ((<$>))+import Control.Monad (forM_)+import Control.Monad.Trans (liftIO)+import Controller (Controller,onView,getFromConfig,setOnConfig)+import Controller.Menu.File.Import.CsvParameters (Parameters(..))+import Controller.Dialog (openFileDialog)+import qualified Controller.Grid as Grid+import Controller.Canonical (addRow)+import Config (csvImportParameters)+import Util (justWhen,breakAt,maybeRead)+import qualified Util+import View.Dialog.Complex (Layout (..),Widget (..),Modifier (..)+ ,showSimpleDialog,cancelButton)+import I18n (__)++eventHandler :: Controller ()+eventHandler = getFromConfig csvImportParameters >>= eventHandlerWith++eventHandlerWith :: Parameters -> Controller ()+eventHandlerWith storedParams = do+ action <- onView $ showSimpleDialog (__ "Import") dialog storedParams+ justWhen action $ \params -> + do setOnConfig $ \c -> c {csvImportParameters = params}+ importFile <- openFileDialog (__ "Import") []+ case importFile of+ Nothing -> eventHandlerWith params+ Just file -> importCsvFile params file ++dialog :: Layout Parameters ()+dialog =+ let setColDelimiter p = maybe p (\d -> p {columnDelimiter = d}) . maybeRead+ setRowDelimiter p = maybe p (\d -> p {rowDelimiter = d}) . maybeRead+ setFirstCol p b = p {firstColumnContainsLabels = b}+ setFirstRow p b = p {firstRowContainsLabels = b}+ setStripWhitespace p b = p {stripWhitespace = b}+ setParseNumbers p b = p {tryParsingNumbers = b}+ in+ Modifier Margin $ Column+ [ Grid [[ Label $ __ "Column delimiter"+ , Widget $ TextEntry (show . columnDelimiter) setColDelimiter]+ ,[ Label $ __ "Row delimiter"+ , Widget $ TextEntry (show . rowDelimiter) setRowDelimiter]+ ,[ Label $ __ "First column contains labels"+ , Widget $ CheckBox "" firstColumnContainsLabels setFirstCol]+ ,[ Label $ __ "First row contains labels"+ , Widget $ CheckBox "" firstRowContainsLabels setFirstRow]+ ,[ Label $ __ "Strip whitespace"+ , Widget $ CheckBox "" stripWhitespace setStripWhitespace]+ ,[ Label $ __ "Try parsing numbers"+ , Widget $ CheckBox "" tryParsingNumbers setParseNumbers]]+ , Modifier Center $ Row + [ Widget $ DefaultButton (__ "&Import") ()+ , cancelButton]]++importCsvFile :: Parameters -> FilePath -> Controller ()+importCsvFile params filePath = do+ Grid.new Nothing+ rows <- breakAt (rowDelimiter params) <$> (liftIO $ readFile filePath)+ if firstRowContainsLabels params + then let labels = + let values = breakAt (columnDelimiter params) $ head rows+ in+ map (mayStripWhitespace params) values+ in do+ forM_ (tail rows) $ importRow params+ Grid.updateColumnLabels labels+ else forM_ rows $ importRow params+ +importRow :: Parameters -> String -> Controller ()+importRow params row = + let csvValues = breakAt (columnDelimiter params) row+ label = if firstColumnContainsLabels params+ then Just $ mayStripWhitespace params $ head csvValues+ else Nothing+ rowData = if firstColumnContainsLabels params+ then map (getCellInput params) $ tail csvValues+ else map (getCellInput params) csvValues+ in + addRow (label,rowData) ++mayStripWhitespace :: Parameters -> String -> String+mayStripWhitespace params = if stripWhitespace params + then Util.stripWhitespace else id++getCellInput :: Parameters -> String -> String+getCellInput params =+ let mayParseNumber x = + if tryParsingNumbers params+ then case maybeRead x :: Maybe Double of+ Just number -> show number+ Nothing -> show x+ else show x+ in+ mayParseNumber . mayStripWhitespace params+ + +
+ src/Controller/Menu/File/Import/CsvParameters.hs view
@@ -0,0 +1,55 @@+module Controller.Menu.File.Import.CsvParameters+ (Parameters(..),defaultParams)+ where++import Util.Sexp (Sexp(..))+import Util.Sexpable (Sexpable(..),PSE,unexpected,recordToSexp+ ,recordFromSexp)++data Parameters = Parameters { columnDelimiter :: Char+ , rowDelimiter :: Char+ , firstColumnContainsLabels :: Bool+ , firstRowContainsLabels :: Bool+ , stripWhitespace :: Bool+ , tryParsingNumbers :: Bool+ }++defaultParams :: Parameters+defaultParams = Parameters ' ' '\n' True True True True++instance Sexpable Parameters where+ toSexp = recordToSexp+ [ ("columnDelimiter", toSexp . columnDelimiter)+ , ("rowDelimiter", toSexp . rowDelimiter)+ , ("firstColumnContainsLabels", toSexp . firstColumnContainsLabels)+ , ("firstRowContainsLabels", toSexp . firstRowContainsLabels)+ , ("stripWhitespace", toSexp . stripWhitespace)+ , ("tryParsingNumbers", toSexp . tryParsingNumbers)+ ]+ fromSexp = recordFromSexp setParam defaultParams++setParam :: Parameters -> Sexp -> PSE Parameters+setParam p sexp =+ case sexp of+ List [Atom "columnDelimiter",cD] -> do+ parameter <- fromSexp cD+ return $ p {columnDelimiter = parameter}+ List [Atom "rowDelimiter",rD] -> do+ parameter <- fromSexp rD+ return $ p {rowDelimiter = parameter}+ List [Atom "firstColumnContainsLabels",containsLabels] -> do+ parameter <- fromSexp containsLabels+ return $ p {firstColumnContainsLabels = parameter}+ List [Atom "firstRowContainsLabels",containsLabels] -> do+ parameter <- fromSexp containsLabels+ return $ p {firstRowContainsLabels = parameter}+ List [Atom "stripWhitespace",strip] -> do+ parameter <- fromSexp strip+ return $ p {stripWhitespace = parameter}+ List [Atom "tryParsingNumbers",try] -> do+ parameter <- fromSexp try+ return $ p {tryParsingNumbers = parameter}+ _ -> unexpected sexp+++
+ src/Controller/Menu/File/NewClose.hs view
@@ -0,0 +1,34 @@+module Controller.Menu.File.NewClose + (newEventHandler,closeEventHandler,closeAllEventHandler)+ where++import Control.Monad (forM_,when)+import Controller (Controller,onView,onGridModel)+import qualified Controller.Grid as Grid+import qualified Controller.Menu.File.Save as Save+import qualified Controller.Menu.Table.Add as Add+import Model.Grid (getFilePath)+import View.Dialog.Simple (confirm)+import View (isGridOpen)+import I18n (__)++newEventHandler,closeEventHandler,closeAllEventHandler :: Controller ()+newEventHandler = do+ Grid.new Nothing+ forM_ [1..5] $ \_ -> Add.addRow Nothing+ forM_ [1..5] $ \_ -> Add.addColumn Nothing++closeEventHandler = do+ saveFirst <- + do filePath <- onGridModel getFilePath+ case filePath of+ Nothing -> onView $ confirm (__ "Save?") (__ "Save?")+ Just f -> onView $ confirm (__ "Save?") + $ unwords [__ "Save as",f,"?"]+ when saveFirst Save.saveEventHandler+ Grid.deleteCurrent++closeAllEventHandler = do+ closeEventHandler+ stillOpen <- onView isGridOpen+ when stillOpen closeAllEventHandler
+ src/Controller/Menu/File/Open.hs view
@@ -0,0 +1,59 @@+module Controller.Menu.File.Open (eventHandler)+ where++import Control.Monad (forM_)+import Control.Monad.Trans (liftIO)+import qualified Graphics.UI.WXCore as WXC+import Controller (Controller,onGridView,onGridModel)+import qualified Controller.Grid as Grid+import Controller.Menu.Table.Add (addRow,addColumn)+import Controller.Canonical (writeRowData)+import Controller.Dialog (openFileDialog,warning)+import Model.Grid (resizeTo)+import Util (justWhen)+import Util.Sexp (Sexp(..),ValidSexp(..),parseFile,validate)+import I18n (__)++eventHandler :: Controller ()+eventHandler = do+ result <- openFileDialog (__ "Open") [(__ "Tables",["*.table"])]+ justWhen result openFile++validTableSexp :: ValidSexp+validTableSexp = + ValidList [ ValidListOf ValidAtom+ , ValidListOf ValidAtom+ , ValidListOf $ ValidListOf ValidAtom+ ]++invalidFile :: FilePath -> String -> Controller ()+invalidFile filePath msg =+ warning $ unwords [__ "Invalid file",filePath ++ ":",msg]++openFile :: FilePath -> Controller ()+openFile filePath = do+ content <- liftIO $ parseFile filePath+ case content of+ Left error -> invalidFile filePath error + Right sexp -> if validate validTableSexp sexp == True+ then loadTable filePath sexp+ else invalidFile filePath $ __ "Invalid s-expression"++loadTable :: FilePath -> Sexp -> Controller ()+loadTable filePath (List [List rows,List cols,List cells]) = do+ Grid.new $ Just filePath+ onGridView WXC.gridBeginBatch+ onGridModel $ resizeTo (length rows,length cols)++ forM_ rows $ \(Atom a) -> addRow $ Just a+ forM_ cols $ \(Atom a) -> addColumn $ Just a+ loadCells cells+ + onGridView WXC.gridEndBatch+ Grid.markAsSaved+ +loadCells :: [Sexp] -> Controller ()+loadCells rows = do+ forM_ (zip [0..] rows) $ \(r,List row) ->+ writeRowData r $ map (\(Atom value) -> value) row+
+ src/Controller/Menu/File/Save.hs view
@@ -0,0 +1,52 @@+module Controller.Menu.File.Save (saveAsEventHandler,saveEventHandler)+ where++import Control.Monad (forM)+import Control.Monad.Trans (liftIO)+import Controller (Controller,onGridView,onGridModel)+import Controller.Grid (setFilePath,markAsSaved)+import Controller.Dialog (saveFileDialog)+import View.Component.Grid (getRowLabel,getColumnLabel+ ,numDataRows,numDataColumns)+import Util (justWhen)+import Util.Sexp (Sexp(..),prettyPrint)+import Model.Cell (input)+import Model.Grid (getCell,getFilePath)+import I18n (__)++saveAsEventHandler,saveEventHandler :: Controller ()+saveAsEventHandler = do+ result <- saveFileDialog (__ "Save") [(__ "Tables",["*.table"])]+ justWhen result saveAs++saveEventHandler = do+ result <- onGridModel getFilePath+ case result of+ Nothing -> saveAsEventHandler+ Just path -> saveAs path++saveAs :: FilePath -> Controller ()+saveAs filePath = do+ toSexp >>= liftIO . writeFile filePath . prettyPrint+ setFilePath filePath+ markAsSaved++toSexp :: Controller Sexp+toSexp = do+ maxRow <- do r <- onGridView numDataRows+ return $ r-1+ maxCol <- do c <- onGridView numDataColumns+ return $ c-1++ rows <- forM [0..maxRow] $ onGridView . getRowLabel+ cols <- forM [0..maxCol] $ onGridView . getColumnLabel++ rowList <- + forM [0..maxRow] $ \row -> + do cells <- forM [0..maxCol] $ \col -> + onGridModel $ getCell (row,col)+ return $ List $ map (Atom . input) cells++ return $ List [ List $ map Atom rows, List $ map Atom cols+ , List rowList]+
+ src/Controller/Menu/Statistics/Descriptive.hs view
@@ -0,0 +1,114 @@+module Controller.Menu.Statistics.Descriptive (eventHandler)+ where++import Prelude hiding (max,min)+import Control.Monad (foldM,forM_,when)+import Control.Applicative ((<$>))+import Data.Maybe (catMaybes)+import Controller (Controller,onView,onGridView,onGridModel)+import qualified Controller.Grid as Grid+import Controller.Dialog (nonNumericDataWarning)+import Controller.Menu.Table.Add (addColumn)+import Controller.Canonical (addRow)+import Util (justWhen)+import Model.Grid (getColumnValues)+import Model.CellExpression.Evaluator.Common (getNumList)+import qualified Model.CellExpression.Evaluator.Math as Math+import View.Component.Grid (getColumnLabel,getColumnLabels)+import View.Dialog.Complex (Layout (..),Widget (..),Modifier (..)+ ,showSimpleDialog,okButton,cancelButton)+import I18n (__)++data Parameters = Parameters { columns :: [Int]+ , calcMean :: Bool+ , calcMedian :: Bool+ , calcStdDeviation :: Bool+ , calcMax :: Bool+ , calcMin :: Bool+ }++data Descriptive = Descriptive { label :: String+ , mean :: Maybe Double+ , median :: Maybe Double+ , stdDeviation :: Maybe Double+ , max :: Maybe Double+ , min :: Maybe Double+ }++type Result = ([String],[Descriptive])++eventHandler :: Controller ()+eventHandler = do+ labels <- onGridView getColumnLabels+ params <- onView $ showSimpleDialog (__ "Descriptive Statistics")+ (dialog (__ "Columns") labels) $ Parameters [] True True True True True+ justWhen params calcDescriptiveTable++dialog :: String -> [String] -> Layout Parameters ()+dialog labelsCaption labels =+ let setColumns p cols = p {columns = cols}+ setCalcMean p b = p {calcMean = b}+ setCalcMedian p b = p {calcMedian = b}+ setCalcStdDev p b = p {calcStdDeviation = b}+ setCalcMax p b = p {calcMax = b}+ setCalcMin p b = p {calcMin = b}+ in+ Modifier Margin $ Column+ [ Row [ Grid+ [[ Label $ __ "Mean"+ , Widget $ CheckBox "" calcMean setCalcMean]+ ,[ Label $ __ "Median"+ , Widget $ CheckBox "" calcMean setCalcMedian]+ ,[ Label $ __ "Standard Deviation"+ , Widget $ CheckBox "" calcStdDeviation setCalcStdDev]+ ,[ Label $ __ "Maximum", Widget $ CheckBox "" calcMax setCalcMax]+ ,[ Label $ __ "Minimum", Widget $ CheckBox "" calcMin setCalcMin]]+ , Modifier (Boxed labelsCaption) + $ Widget $ MultiListBox labels columns setColumns]+ , Modifier Center $ Row [okButton (),cancelButton]]++calcDescriptiveTable :: Parameters -> Controller ()+calcDescriptiveTable params = do+ (invalidCols,result) <- foldM (descriptive params) ([],[]) + $ columns params++ when (not $ null result) $+ do Grid.new Nothing+ when (calcMean params) $ addColumn $ Just $ __ "Mean"+ when (calcMedian params) $ addColumn $ Just $ __ "Median"+ when (calcStdDeviation params) $ addColumn $ Just $ __ "Standard Deviation"+ when (calcMax params) $ addColumn $ Just $ __ "Maximum"+ when (calcMin params) $ addColumn $ Just $ __ "Minimum"++ forM_ (reverse result) $ \desc ->+ do addRow (Just $ label desc,descriptiveToRow desc)++ when (not $ null invalidCols) + $ nonNumericDataWarning $ reverse invalidCols++descriptive :: Parameters -> Result -> Int -> Controller Result+descriptive params (invalidCols,xs) col = do+ colLabel <- onGridView $ getColumnLabel col+ result <- getNumList <$> (onGridModel $ getColumnValues col)+ case (result) of+ Left _ -> return (colLabel : invalidCols,xs)+ Right values -> + let calculate condition v = if condition params + then Just v else Nothing+ mean = calculate calcMean $ Math.mean values+ median = calculate calcMedian $ Math.median values+ stdDev = calculate calcStdDeviation $ Math.stdDeviation values+ max = calculate calcMax $ maximum values+ min = calculate calcMax $ minimum values+ in+ return ( invalidCols+ , Descriptive colLabel mean median stdDev max min : xs+ )++descriptiveToRow :: Descriptive -> [String]+descriptiveToRow desc =+ map show $ catMaybes [ mean desc+ , median desc+ , stdDeviation desc+ , max desc, min desc+ ]
+ src/Controller/Menu/Statistics/FrequencyDistribution.hs view
@@ -0,0 +1,36 @@+module Controller.Menu.Statistics.FrequencyDistribution (eventHandler)+ where++import Control.Monad (forM_)+import Data.List (nub)+import Controller (Controller,onGridView)+import View.Component.Grid (getColumnValues)+import Util (justWhen)+import qualified Controller.Grid as Grid+import Controller.Canonical (addRow)+import Controller.Menu.Table.Add (addColumn)+import Controller.Dialog (chooseColumnDialog)+import I18n (__)++eventHandler :: Controller ()+eventHandler = do+ selection <- chooseColumnDialog $ __ "Frequency Distribution"+ justWhen selection $ \colIndex ->+ do values <- onGridView $ getColumnValues colIndex+ Grid.new Nothing+ addColumn $ Just $ __ "Frequency"+ addColumn $ Just $ __ "Percentage"++ forM_ (freqDist values) $ \(value,freq,perc) ->+ do addRow (Just value,[show freq, show perc])++freqDist :: [String] -> [(String,Int,Double)]+freqDist values =+ let unique = nub values+ frequency = map (\s -> length $ filter ((==) s) values) unique+ numValues = length values+ percentage = map (\f -> fromIntegral f / (fromIntegral numValues)) frequency+ in+ zip3 unique frequency percentage++
+ src/Controller/Menu/Statistics/Pivot.hs view
@@ -0,0 +1,146 @@+module Controller.Menu.Statistics.Pivot (eventHandler)+ where++import Control.Monad (when,forM_)+import Data.List (nub)+import Data.Maybe (mapMaybe)+import Controller (Controller,onView,onGridView,onGridModel)+import Controller.Canonical (addColumn,addRow)+import qualified Controller.Grid as Grid+import Controller.Dialog (warning)+import View.Component.Grid (getColumnLabels)+import View.Dialog.Complex (Layout (..),Widget (..),Modifier (..)+ ,showSimpleDialog,okButton,cancelButton)+import Model.Grid (getColumnValues)+import Model.CellContent (CellValue (NumberValue))+import qualified Model.CellExpression.Evaluator.Math as Math+import Model.CellExpression.Evaluator.Common (fLength,getNumList)+import Util (justWhen)+import I18n (__)++data Aggregator = Count+ | Numeric ([Double] -> Double)++data Parameters = Parameters { abscissaColumn :: Maybe Int+ , ordinateColumn :: Maybe Int+ , dataColumn :: Maybe Int+ , aggregatorIndex :: Int+ , makeTotals :: Bool+ }++defaultParams :: Parameters+defaultParams = Parameters Nothing Nothing Nothing 0 True++aggregatorList :: [String]+aggregatorList = [ __ "Sum",__ "Mean",__ "Median",__ "Maximum"+ , __ "Minimum",__ "Standard Deviation",__ "Count"]++aggregatorFromInt :: Int -> Aggregator+aggregatorFromInt i = + case i of 0 -> Numeric sum; 1 -> Numeric Math.mean+ 2 -> Numeric Math.median; 3 -> Numeric maximum+ 4 -> Numeric minimum; 5 -> Numeric Math.stdDeviation+ 6 -> Count++typecheck :: Aggregator -> [CellValue] -> Bool+typecheck aggregator values = + case aggregator of+ Numeric _ -> either (\_ -> False) (\_ -> True) $ getNumList values+ _ -> True++apply :: Aggregator -> [CellValue] -> CellValue+apply aggregator values =+ case aggregator of+ Count -> NumberValue $ fLength values+ Numeric f -> let Right vs = getNumList values+ in NumberValue $ f vs++eventHandler :: Controller ()+eventHandler = eventHandlerWith defaultParams++eventHandlerWith :: Parameters -> Controller ()+eventHandlerWith params = do+ labels <- onGridView getColumnLabels+ result <- onView $ showSimpleDialog (__ "Pivot table") + (dialog labels) params+ justWhen result $ \params' -> do success <- processParams params'+ when (not success) + $ eventHandlerWith params'++dialog :: [String] -> Layout Parameters ()+dialog labels =+ let setAbscissa p a = p {abscissaColumn = a}+ setOrdinate p a = p {ordinateColumn = a}+ setData p a = p {dataColumn = a}+ setAggregator p a = p {aggregatorIndex = a}+ setTotals p b = p {makeTotals = b}+ in+ Modifier Margin $ Column + [ Row [ Modifiers [Boxed $ __ "Abscissa column",HFill]+ $ Widget $ SingleListBox labels abscissaColumn setAbscissa+ , Modifiers [Boxed $ __ "Ordinate column",HFill]+ $ Widget $ SingleListBox labels ordinateColumn setOrdinate+ , Modifiers [Boxed $ __ "Data column",HFill]+ $ Widget $ SingleListBox labels dataColumn setData]+ , Grid [[ Label $ __ "Aggregator"+ , Widget $ Choice aggregatorList aggregatorIndex setAggregator]+ ,[ Label $ __ "Make totals"+ , Widget $ CheckBox "" makeTotals setTotals]]+ , Modifier Center $ Row [okButton (),cancelButton]]++processParams :: Parameters -> Controller Bool+processParams params = + case (abscissaColumn params,ordinateColumn params,dataColumn params) of+ (Nothing,_,_) -> warning (__ "No abscissa column selected") >> return False+ (_,Nothing,_) -> warning (__ "No ordinate column selected") >> return False+ (_,_,Nothing) -> warning (__ "No data column selected") >> return False+ (Just absCol,Just ordCol,Just dataCol) ->+ do abscissa <- onGridModel $ getColumnValues absCol+ ordinate <- onGridModel $ getColumnValues ordCol+ datas <- onGridModel $ getColumnValues dataCol+ calcPivotTable abscissa ordinate datas + (aggregatorFromInt $ aggregatorIndex params) + (makeTotals params)++calcPivotTable :: [CellValue] -> [CellValue] -> [CellValue] + -> Aggregator -> Bool -> Controller Bool+calcPivotTable abscissa ordinate datas aggregator makeTotals = + if not $ typecheck aggregator datas + then warning (__ "Can not apply aggregator to selected data column (e.g. because of non-numeric data).")+ >> return False+ else do+ Grid.new Nothing++ let association = zip3 abscissa ordinate datas+ nubOrdinate = nub ordinate+ nubAbscissa = nub abscissa+ aggregateSome select =+ let values = mapMaybe (\(a,o,value) -> if select a o + then Just value + else Nothing+ ) association+ in apply aggregator values++ aggregate abscissaValue ordinateValue =+ aggregateSome $ \a o -> a == abscissaValue && o == ordinateValue+ aggregateAbs abscissaValue =+ aggregateSome $ \a _ -> a == abscissaValue+ aggregateOrd ordinateValue =+ aggregateSome $ \_ o -> o == ordinateValue++ forM_ nubAbscissa $ \abscissaValue -> + let column = map (show . aggregate abscissaValue) nubOrdinate+ in+ addColumn (Just $ show abscissaValue,column)++ Grid.updateRowLabels $ map show nubOrdinate++ when makeTotals $ + let column = map (show . aggregateOrd) nubOrdinate+ row = map (show . aggregateAbs) nubAbscissa+ all = show $ aggregateSome (\_ _ -> True)+ in do+ addColumn (Just $ __ "Total",column)+ addRow (Just $ __ "Total",row ++ [all])+ return True+
+ src/Controller/Menu/Table/Add.hs view
@@ -0,0 +1,82 @@+module Controller.Menu.Table.Add (columnEventHandler,rowEventHandler+ ,addRow,addColumn)+ where++import Control.Monad (forM_)+import Data.Char (chr,ord)+import Data.List (intercalate)+import Controller (Controller,onView,onGridModel,onGridView)+import Controller.Cell (updateInView)+import qualified Model.Grid as GridM+import qualified View.Component.Grid as GridV+import View.Dialog.Complex (Layout (..),Widget (..),Modifier (..)+ ,showSimpleDialog,cancelButton)+import Util (breakAt)+import I18n (__)++data AddBy = N | Labels+data Parameters = Parameters { addBy :: AddBy+ , n :: Int+ , labels :: [String]}++columnEventHandler :: Controller ()+columnEventHandler = do+ result <- onView $ showSimpleDialog (__ "Add columns")+ (dialog $ __ "Add columns by") $ Parameters N 0 []+ case result of+ Nothing -> return ()+ Just (Parameters {addBy=N, n=n}) -> + forM_ [1..n] $ \_ -> addColumn Nothing+ Just (Parameters {addBy=Labels, labels=labels}) -> + forM_ labels $ addColumn . Just++rowEventHandler :: Controller ()+rowEventHandler = do+ result <- onView $ showSimpleDialog (__ "Add rows")+ (dialog $ __ "Add rows by") $ Parameters N 0 []+ case result of+ Nothing -> return ()+ Just (Parameters {addBy=N, n=n}) -> + forM_ [1..n] $ \_ -> addRow Nothing+ Just (Parameters {addBy=Labels, labels=labels}) -> + forM_ labels $ addRow . Just++dialog :: String -> Layout Parameters ()+dialog radioBoxCaption =+ let setLabels p labels = p {labels = breakAt ',' labels}+ getLabels = intercalate "," . labels+ setAddBy p 0 = p {addBy = N}+ setAddBy p 1 = p {addBy = Labels}+ getAddBy p = case addBy p of N -> 0; Labels -> 1+ setN p n = p {n = n}+ in+ Modifier Margin $ Column+ [ Row [ Widget $ RadioBox radioBoxCaption + [__ "Number \"n\"",__ "Labels \"label1[,label2,...]\""] + getAddBy setAddBy+ , Column [ Widget $ Spinner 0 maxBound n setN+ , Widget $ TextEntry getLabels setLabels]]+ , Modifier Center $ Row [ Widget $ DefaultButton (__ "&Add") ()+ , cancelButton]]++{- Reihenfolge wichtig: + 1. zu View hinzufügen+ 2. zu Model hinzufügen+-}+addRow,addColumn :: Maybe String -> Controller ()+addRow caption = do+ rows <- onGridView GridV.numDataRows+ let name = maybe (autoRowName rows) id caption+ onGridView $ GridV.addRow name+ onGridModel (GridM.addRow name) >>= updateInView+ where autoRowName i = show $ i+1++addColumn caption = do+ cols <- onGridView GridV.numDataColumns+ let name = maybe (autoColumnName cols) id caption+ onGridView $ GridV.addColumn name+ onGridModel (GridM.addColumn name) >>= updateInView+ where autoColumnName i =+ let number = (i `div` 26) + 1+ in+ replicate number $ chr $ (i `mod` 26) + ord 'A'
+ src/Controller/Menu/Table/Copy.hs view
@@ -0,0 +1,22 @@+module Controller.Menu.Table.Copy (eventHandler)+ where++import Control.Monad (forM_,forM)+import Controller (Controller)+import Controller.Dialog (chooseColumnsAndTableDialog)+import Controller.Grid (changePageTo)+import Controller.Canonical (getColumn,addColumn')+import Util (justWhen)+import I18n (__)++eventHandler :: Controller ()+eventHandler = do+ result <- chooseColumnsAndTableDialog $ __ "Copy"+ justWhen result $ \(cols,table) ->+ justWhen table $ copyColumnsTo cols++copyColumnsTo :: [Int] -> Int -> Controller ()+copyColumnsTo cols tableN = do+ columnData <- forM cols getColumn+ changePageTo tableN+ forM_ columnData addColumn'
+ src/Controller/Menu/Table/Delete.hs view
@@ -0,0 +1,28 @@+module Controller.Menu.Table.Delete (columnEventHandler,rowEventHandler+ ,deleteColumns,deleteRows)+ where++import Controller (Controller,onGridModel,onGridView)+import Controller.Dialog (chooseColumnsDialog,chooseRowsDialog)+import qualified Controller.Menu.Table.Reevaluate as Reevaluate+import qualified Model.Grid as GridM+import qualified View.Component.Grid as GridV+import I18n (__)++columnEventHandler :: Controller ()+columnEventHandler = chooseColumnsDialog (__ "Delete") >>= deleteColumns++deleteColumns :: [Int] -> Controller ()+deleteColumns cols = do+ onGridModel $ GridM.deleteColumns cols+ onGridView $ GridV.deleteColumns $ reverse cols+ Reevaluate.eventHandler++rowEventHandler :: Controller ()+rowEventHandler = chooseRowsDialog (__ "Delete") >>= deleteRows++deleteRows :: [Int] -> Controller ()+deleteRows rows = do+ onGridModel $ GridM.deleteRows rows+ onGridView $ GridV.deleteRows $ reverse rows+ Reevaluate.eventHandler
+ src/Controller/Menu/Table/Reevaluate.hs view
@@ -0,0 +1,18 @@+module Controller.Menu.Table.Reevaluate (eventHandler)+ where++import Control.Monad (forM_)+import Controller (Controller,onGridView,onGridModel)+import Controller.Cell (updateInView,showSelectedContent)+import View.Component.Grid (numDataRows,numDataColumns)+import Model.Grid (rewriteCell)++eventHandler :: Controller ()+eventHandler = do+ rows <- onGridView numDataRows+ cols <- onGridView numDataColumns++ forM_ [0..rows-1] $ \r -> forM_ [0..cols-1] $ \c -> + do changed <- onGridModel $ rewriteCell (r,c)+ updateInView changed+ showSelectedContent
+ src/Controller/Menu/Table/Transpose.hs view
@@ -0,0 +1,41 @@+module Controller.Menu.Table.Transpose (tableEventHandler+ ,columnEventHandler+ ,rowEventHandler)+ where++import Control.Monad (forM_,forM)+import Controller (Controller,onGridView)+import Controller.Dialog (chooseColumnsDialog,chooseRowsDialog)+import Controller.Canonical (getColumn,getRow,addRow',addColumn')+import qualified Controller.Grid as Grid+import View.Component.Grid (getRowLabels,getColumnLabels+ ,numDataColumns)+import I18n (__)++tableEventHandler :: Controller ()+tableEventHandler = do+ n <- onGridView numDataColumns+ transposeColumns [0..n-1]++columnEventHandler :: Controller ()+columnEventHandler = chooseColumnsDialog (__ "Transpose") >>= transposeColumns++rowEventHandler :: Controller ()+rowEventHandler = chooseRowsDialog (__ "Transpose") >>= transposeRows++transposeColumns :: [Int] -> Controller ()+transposeColumns cols = do+ columnData <- forM cols getColumn+ rowLabels <- onGridView getRowLabels+ Grid.new Nothing+ forM_ columnData addRow'+ Grid.updateColumnLabels rowLabels++transposeRows :: [Int] -> Controller ()+transposeRows rows = do+ rowData <- forM rows getRow+ colLabels <- onGridView getColumnLabels+ Grid.new Nothing+ forM_ rowData addColumn'+ Grid.updateRowLabels colLabels+
+ src/Controller/SaveConfig.hs view
@@ -0,0 +1,12 @@+module Controller.SaveConfig (configFilePath,eventHandler)+ where++import Control.Monad.Trans (liftIO)+import Controller (Controller,currentConfig)+import Config (save)+import Constants (configFilePath)++eventHandler :: Controller ()+eventHandler = do+ config <- currentConfig+ liftIO $ configFilePath >>= save `flip` config
+ src/I18n.hs view
@@ -0,0 +1,18 @@+module I18n (__,initI18n)+ where++import Text.I18N.GetText (getText,bindTextDomain,textDomain)+import System.Locale.SetLocale (setLocale,Category(LC_ALL))+import System.IO.Unsafe (unsafePerformIO)+import Codec.Binary.UTF8.String (decodeString)++__ :: String -> String+__ = decodeString . unsafePerformIO . getText++initI18n :: String -> IO ()+initI18n locale = do+ setLocale LC_ALL $ Just locale+ bindTextDomain __MESSAGE_CATALOG_DOMAIN__ $ Just __MESSAGE_CATALOG_DIR__+ textDomain $ Just __MESSAGE_CATALOG_DOMAIN__ + return ()+
+ src/Main.hs view
@@ -0,0 +1,14 @@+module Main+ where++import Graphics.UI.WX (start)+import Run (program)+import Config (load)+import Constants (configFilePath)++main :: IO ()+main = do+ result <- load =<< configFilePath+ case result of+ Left withError -> fail withError+ Right config -> start $ program config
+ src/Model.hs view
@@ -0,0 +1,48 @@+module Model (Model,new,onGrid,setCurrent,addGrid,deleteCurrent)+ where++import Control.Applicative ((<$>))+import Data.IORef+import Data.IntMap (IntMap,Key)+import qualified Data.IntMap as Map+import Model.Grid (Grid)+import qualified Model.Grid as G+import qualified View+import Util (justWhen)++type Current = (Key,Grid)++data Model = Model { grids :: IORef (IntMap Grid)+ , current :: IORef (Maybe Current)+ }++new :: IO Model+new = do+ grids <- newIORef $ Map.empty+ current <- newIORef Nothing+ return $ Model grids current++deleteCurrent :: Model -> IO ()+deleteCurrent model =+ onCurrent model $ \(key,_) -> + modifyIORef (grids model) $ Map.delete key++onGrid :: Model -> (Grid -> IO a) -> IO a+onGrid model f = onCurrent model $ f . snd++onCurrent :: Model -> (Current -> IO a) -> IO a+onCurrent model f = + readIORef (current model) >>= \maybeC ->+ case maybeC of Just c -> f c+ Nothing -> error "Model.onCurrent: Nothing"++setCurrent :: Key -> Model -> IO ()+setCurrent key model = do+ grid <- Map.lookup key <$> (readIORef $ grids model)+ justWhen grid $ \g -> writeIORef (current model) $ Just (key,g)++addGrid :: Key -> View.Info -> Model -> IO ()+addGrid key viewInfo model = do+ grid <- G.new viewInfo+ modifyIORef (grids model) $ Map.insert key grid+ setCurrent key model
+ src/Model/Cell.hs view
@@ -0,0 +1,46 @@+module Model.Cell (Cell(..),empty,showValue+ ,setExpression,setRuntimeErrorValue+ ,evaluate,addDependent,deleteDependent)+ where++import qualified Data.List as List+import Model.CellContent (CellExpr(EmptyExpr),CellValue(EmptyValue,Error)+ ,RuntimeReason,Reference)+import qualified Model.CellContent as CC+import qualified Model.CellExpression.Evaluator as E+import CellCoordinate (CellCoord)++data Cell = Cell { expression :: CellExpr+ , value :: CellValue+ , dependent :: [CellCoord]+ , dependencies :: [Reference]+ , input :: String+ } ++empty :: Cell+empty = Cell EmptyExpr EmptyValue [] [] ""++setExpression :: String -> CellExpr -> Cell -> Cell+setExpression input expr cell = + cell { input = input+ , expression = expr+ , dependencies = CC.dependencies expr}++setRuntimeErrorValue :: RuntimeReason -> Cell -> Cell+setRuntimeErrorValue reason cell = cell {value = Error reason}++showValue :: Cell -> String+showValue = show . value++evaluate :: E.Lookup -> Cell -> IO Cell+evaluate lookup cell = do+ value' <- E.evaluate lookup $ expression cell+ return $ cell {value = value'}++addDependent :: CellCoord -> Cell -> Cell+addDependent coords cell =+ cell {dependent = coords : (dependent cell)}++deleteDependent :: CellCoord -> Cell -> Cell+deleteDependent coords cell = + cell {dependent = List.delete coords $ dependent cell}
+ src/Model/CellContent.hs view
@@ -0,0 +1,99 @@+module Model.CellContent (NamedReference (..),Reference (..)+ ,CompileReason (..),RuntimeReason (..)+ ,CellExpr (..),CellValue (..)+ ,Symbol,dependencies)+ where++import Data.List (intercalate)+import CellCoordinate (CellCoord)+import I18n (__)++type Symbol = String++data NamedReference = NamedCell (String,String)+ | NamedRange ((String,String),(String,String))+ | NamedColumn String+ | NamedRow String+ deriving (Eq)++instance Show NamedReference where+ show (NamedCell (r,c)) = concat ["$",r,",",c,"$"]+ show (NamedRange (a,b)) = concat [ show $ NamedCell a+ , ":",show $ NamedCell b]+ show (NamedColumn a) = concat ["$_,",a,"$"]+ show (NamedRow a) = concat ["$",a,"_$"]++data Reference = Cell CellCoord+ | Range (CellCoord,CellCoord)+ | Column Int+ | Row Int+ deriving (Eq,Show)++data CompileReason = ParseError String+ | RefConversionError NamedReference+ | ReferenceLoop+ deriving (Eq)++instance Show CompileReason where+ show (ParseError a) = unwords [(__ "Parse error")++ ":",a]+ show (RefConversionError a) = unwords [(__ "Unknown reference") ++ ":",show a]+ show ReferenceLoop = __ "Reference loop"++data RuntimeReason = CompileError CompileReason+ | TypeError CellValue+ | Inherited CellValue RuntimeReason + | UnknownIdentifier Symbol+ deriving (Eq)++instance Show RuntimeReason where+ show (CompileError a) = unwords [(__ "Compile error") ++ ":",show a]+ show (TypeError a) = unwords [__ "Type error in",showValueInError a]+ show (Inherited a b) = unwords [__ "In",showValueInError a,":",show b]+ show (UnknownIdentifier a) = unwords [__ "Unknown identifier",a]++data CellExpr = EmptyExpr+ | NumberExpr Double+ | StringExpr String+ | ListExpr [CellExpr]+ | NamedReference NamedReference+ | Reference Reference+ | UnaryOp Symbol CellExpr+ | BinaryOp Symbol CellExpr CellExpr+ | Call Symbol CellExpr+ | Sub CellExpr+ | CompileErrorExpr CompileReason+ deriving (Eq,Show)++data CellValue = EmptyValue+ | NumberValue Double+ | StringValue String+ | ListValue [CellValue]+ | Error RuntimeReason+ deriving (Eq)++instance Show CellValue where+ show EmptyValue = ""+ show (NumberValue a) = show a+ show (StringValue a) = a+ show (ListValue a) = concat ["[",intercalate "," $ map show a,"]"]+ show (Error _) = __ "Error"++dependencies :: CellExpr -> [Reference]+dependencies expr =+ case expr of+ Reference ref -> [ref]+ ListExpr a -> concat $ map dependencies a+ UnaryOp _ a -> dependencies a+ BinaryOp _ a b -> (dependencies a) ++ (dependencies b)+ Call _ a -> dependencies a+ Sub a -> dependencies a+ _ -> []++showValueInError :: CellValue -> String+showValueInError value =+ case value of+ EmptyValue -> __ "Empty"+ ListValue a -> + concat ["[",intercalate "," $ map showValueInError a,"]"]+ _ -> show value+
+ src/Model/CellExpression/Evaluator.hs view
@@ -0,0 +1,50 @@+module Model.CellExpression.Evaluator (evaluate,Lookup)+ where++import Prelude hiding (lookup)+import Control.Applicative ((<$>))+import Control.Monad (forM)+import Model.CellContent (CellExpr (..),CellValue(..),RuntimeReason(..)+ ,Reference(..),Symbol)+import Model.CellExpression.Evaluator.Common + (withNum,withList,withNumList,getNumList,fLength)+import Model.CellExpression.Evaluator.Math (mean,median)++type Lookup = Reference -> IO CellValue++evaluate :: Lookup -> CellExpr -> IO CellValue+evaluate lookup e =+ let binary f = return . mathBinary f+ eval = evaluate lookup+ in+ case e of+ EmptyExpr -> return $ EmptyValue+ NumberExpr a -> return $ NumberValue a+ StringExpr a -> return $ StringValue a+ ListExpr a -> ListValue <$> forM a eval+ Reference a -> lookup a+ UnaryOp "-" a -> forM [NumberExpr 0,a] eval >>= binary (-)+ BinaryOp "+" a b -> forM [a,b] eval >>= binary (+)+ BinaryOp "-" a b -> forM [a,b] eval >>= binary (-)+ BinaryOp "*" a b -> forM [a,b] eval >>= binary (*)+ BinaryOp "/" a b -> forM [a,b] eval >>= binary (/)+ BinaryOp "^" a b -> forM [a,b] eval >>= binary (**)+ Call f arg -> evaluateFun f <$> eval arg+ Sub a -> eval a+ NamedReference _ -> error "Model.CellExpression.Evaluator.evaluate: NamedReference"+ CompileErrorExpr a -> return $ Error $ CompileError a++evaluateFun :: Symbol -> CellValue -> CellValue+evaluateFun f arg = + case f of+ "sum" -> withNumList arg $ NumberValue . sum+ "count" -> withList arg $ NumberValue . fLength+ "abs" -> withNum arg $ NumberValue . abs+ "mean" -> withNumList arg $ NumberValue . mean+ "median" -> withNumList arg $ NumberValue . median+ unknown -> Error $ UnknownIdentifier unknown++mathBinary :: (Double -> Double -> Double) -> [CellValue] -> CellValue+mathBinary f = + either Error (\[a,b] -> NumberValue $ f a b) . getNumList+
+ src/Model/CellExpression/Evaluator/Common.hs view
@@ -0,0 +1,43 @@+module Model.CellExpression.Evaluator.Common+ (withNum,withString,withList,withNumList,getNumList,fLength)+ where++import Model.CellContent (CellValue (..),RuntimeReason(..))++withNum :: CellValue -> (Double -> CellValue) -> CellValue+withNum value f = + case value of NumberValue a -> f a+ Error a -> Error $ Inherited value a+ a -> Error $ TypeError a++withString :: CellValue -> (String -> CellValue) -> CellValue+withString value f = + case value of StringValue a -> f a+ Error a -> Error $ Inherited value a+ a -> Error $ TypeError a++withList :: CellValue -> ([CellValue] -> CellValue) -> CellValue+withList value f =+ case value of ListValue a -> f a+ Error a -> Error $ Inherited value a+ a -> Error $ TypeError a++withNumList :: CellValue -> ([Double] -> CellValue) -> CellValue+withNumList value f =+ case value of+ ListValue a -> either (Error . Inherited value) f $ getNumList a+ Error a -> Error $ Inherited value a+ a -> Error $ TypeError a+ +getNumList :: [CellValue] -> Either RuntimeReason [Double]+getNumList =+ foldr (\value accu -> + case (value,accu) of+ (NumberValue n,Right list) -> Right $ n:list+ (Error a,Right _) -> Left $ Inherited value a+ (a,Right _) -> Left $ TypeError a+ (_,Left a) -> Left a+ ) (Right [])++fLength :: Num b => [a] -> b+fLength = fromIntegral . length
+ src/Model/CellExpression/Evaluator/Math.hs view
@@ -0,0 +1,47 @@+module Model.CellExpression.Evaluator.Math + (mean,median,medianSorted,stdDeviation+ ,firstQuartile,firstQuartileSorted+ ,thirdQuartile,thirdQuartileSorted)+ where++import Data.List (sort)+import Model.CellExpression.Evaluator.Common (fLength)++mean :: [Double] -> Double+mean values = sum values / (fLength values)++median,medianSorted :: [Double] -> Double+medianSorted values = + let l = length values+ in+ if odd l then values !! (l `div` 2)+ else + let x = values !! ((l - 1) `div` 2)+ y = values !! (l `div` 2)+ in+ (x + y) / 2+median = medianSorted . sort++firstQuartile,firstQuartileSorted :: [Double] -> Double+firstQuartileSorted values =+ let l = length values+ in+ if odd l then values !! (l `div` 4)+ else values !! ((l + 1) `div` 4)+firstQuartile = firstQuartileSorted . sort++thirdQuartile,thirdQuartileSorted :: [Double] -> Double+thirdQuartileSorted values =+ let l = length values+ in+ if odd l then values !! ((3 * l + 2) `div` 4)+ else values !! ((3 * l + 1) `div` 4)+thirdQuartile = thirdQuartileSorted . sort++stdDeviation :: [Double] -> Double+stdDeviation [_] = 0+stdDeviation values =+ let m = mean values+ in + sqrt $ (sum $ map (\v -> (v - m) ** 2) values) / (fLength values - 1)+
+ src/Model/CellExpression/Parser.hs view
@@ -0,0 +1,169 @@+module Model.CellExpression.Parser (parse)+ where++import Data.List (find,intercalate,nub)+import Control.Applicative ((<$>))+import Text.ParserCombinators.Parsec ((<|>),(<?>),Parser,try)+import qualified Text.ParserCombinators.Parsec as P+import qualified Text.ParserCombinators.Parsec.Error as Error+import qualified Text.ParserCombinators.Parsec.Expr as E+import qualified Text.ParserCombinators.Parsec.Token as T+import qualified Text.ParserCombinators.Parsec.Language as L+import Model.CellContent (CellExpr (..),NamedReference(..),CompileReason(..))+import qualified Model.CellContent as CC+import I18n (__)++program,topLevelFormula,topLevelLiteral :: Parser CellExpr+expression,term,functionCall :: Parser CellExpr+number,stringExpr,list,subExpression :: Parser CellExpr+reference,cellRef,cellRangeRef :: Parser CellExpr++program = topLevelFormula <|> topLevelLiteral++topLevelLiteral = (try topLevelNumber) + <|> (StringExpr <$> (P.many1 P.anyChar))++topLevelNumber = do+ whitespace+ n <- NumberExpr <$> signedDouble+ P.eof >> return n++topLevelFormula = do+ f <- P.char '=' >> whitespace >> expression+ P.eof >> return f++expression = E.buildExpressionParser table term <?> __ "formula"++table :: E.OperatorTable Char () CellExpr+table = [[ prefix "-" CC.UnaryOp]+ ,[ binary "^" CC.BinaryOp E.AssocRight]+ ,[ binary "*" CC.BinaryOp E.AssocLeft+ , binary "/" CC.BinaryOp E.AssocLeft]+ ,[ binary "+" CC.BinaryOp E.AssocLeft+ , binary "-" CC.BinaryOp E.AssocLeft]+ ] + where+ binary sign f = E.Infix $ do {reservedOp sign; return $ f sign}+ prefix sign f = E.Prefix $ do {reservedOp sign; return $ f sign}++term = subExpression <|> functionCall <|> number <|> list + <|> stringExpr <|> reference <?> __ "formula"++functionCall = do { name <- identifier+ ; arg <- number <|> stringExpr <|> list + <|> reference <|> subExpression <?> __ "argument"+ ; return $ Call name arg+ } <?> __ "function call"++number = (NumberExpr <$> double) <?> __ "number"++stringExpr = (StringExpr <$> stringLiteral) <?> __ "string"++list = do { symbol "["+ ; l <- P.sepBy1 expression $ symbol ","+ ; symbol "]"+ ; return $ ListExpr l+ } <?> "list"++reference = try cellRangeRef <|> try cellRef <?> __ "reference"++cellRef = do { string "$"+ ; row <- P.many1 (P.noneOf [','])+ ; string ","+ ; column <- P.many1 (P.noneOf ['$'])+ ; string "$"+ ; whitespace+ ; case (row,column) of+ ("_",c) -> return $ NamedReference $ NamedColumn c+ (r,"_") -> return $ NamedReference $ NamedRow r+ _ -> return $ NamedReference $ NamedCell (row,column)+ } <?> __ "cell reference"++cellRangeRef = do { NamedReference (NamedCell from) <- cellRef+ ; symbol ":"+ ; NamedReference (NamedCell to) <- cellRef+ ; return $ NamedReference $ NamedRange (from,to)+ } <?> __ "cell range reference"++subExpression = do { subExpr <- P.between (symbol "(") (symbol ")") expression+ ; return $ Sub subExpr+ } <?> __ "subexpression"++lexer :: T.TokenParser ()+lexer = T.makeTokenParser $ L.emptyDef + { L.opStart = P.oneOf "+-*/~^:"+ , L.opLetter = P.oneOf "+-*/~^:"+ , L.reservedOpNames = ["+","-","*","/","~","^",":"]+ , L.reservedNames = []}++double :: Parser Double+double = do+ f <- T.naturalOrFloat lexer+ case f of + Left i -> return $ realToFrac i+ Right d -> return d++signedDouble :: Parser Double+signedDouble = + let signed = do symbol "-"+ ((*) (-1)) <$> double+ in+ P.choice [signed,double]++reservedOp :: String -> Parser ()+reservedOp = T.reservedOp lexer++whitespace :: Parser ()+whitespace = T.whiteSpace lexer++symbol :: String -> Parser ()+symbol s = T.symbol lexer s >> return ()++identifier :: Parser String+identifier = T.identifier lexer++stringLiteral :: Parser String+stringLiteral = T.stringLiteral lexer++string :: String -> Parser ()+string s = P.string s >> return ()++parse :: String -> CellExpr+parse "" = EmptyExpr+parse input = + case (P.parse program "" input) of+ Left e -> CompileErrorExpr $ ParseError $ errorMessage e+ Right x -> x++errorMessage :: Error.ParseError -> String+errorMessage parseError =+ let errors = Error.errorMessages parseError+ Just unexpected = find (\e -> case e of+ Error.SysUnExpect _ -> True+ Error.UnExpect _ -> True+ _ -> False) errors+ expected = filter (\e -> case e of + Error.Expect "" -> False+ Error.Expect _ -> True+ _ -> False+ ) errors+ message e = + case e of+ Error.SysUnExpect "" -> __ "end of input"+ Error.SysUnExpect a -> a+ Error.UnExpect "" -> __ "end of input"+ Error.UnExpect a -> a+ a -> Error.messageString a++ unexpectedMessage = unwords [ (__ "unexpected") ++ ":"+ , message unexpected]+ expectedMessage = + case expected of+ [] -> ""+ _ -> (__ "expecting") ++ " " ++ + (intercalate " or " $ nub $ map message expected)+ in+ case expectedMessage of+ "" -> unexpectedMessage+ _ -> unexpectedMessage ++ ", " ++ expectedMessage+-- ++ "#####" ++ show parseError
+ src/Model/Grid.hs view
@@ -0,0 +1,248 @@+module Model.Grid (Grid,new,getCell,setCell,rewriteCell+ ,updateCell,updateCellIO,resizeTo+ ,addRow,addColumn,deleteColumns,deleteRows+ ,updateRowLabel,updateColumnLabel+ ,columnIndex,rowIndex+ ,setFilePath,getFilePath+ ,getColumnValues)+ where++import Control.Monad (forM,forM_)+import Control.Applicative ((<$>))+import Data.IORef+import Util.DynArray (DynArray)+import qualified Util.DynArray as Dyn+import Model.Cell (Cell)+import qualified Model.Cell as Cell+import Model.CellContent (CellExpr(..),CellValue(..),Reference(..)+ ,NamedReference(..),CompileReason(..))+import qualified Model.CellContent as CellContent+import Model.CellExpression.Parser (parse)+import Model.GridHeader (Header)+import qualified Model.GridHeader as Header+import CellCoordinate (CellCoord,coordRange)+import qualified View++data Grid = Grid { cells :: DynArray CellCoord Cell+ , rowHeader :: Header+ , columnHeader :: Header+ , filePath :: IORef (Maybe FilePath)+ , numViewGridRows :: IO Int+ , numViewGridCols :: IO Int+ }++new :: View.Info -> IO Grid+new viewInfo = do+ cells <- Dyn.newMatrix (1,1) Cell.empty+ rowHeader <- Header.new+ columnHeader <- Header.new+ filePath <- newIORef Nothing+ return $ Grid cells rowHeader columnHeader filePath+ (View.numDataRows viewInfo) (View.numDataColumns viewInfo)++getCell :: CellCoord -> Grid -> IO Cell+getCell coord = Dyn.read coord . cells++getCellValue :: CellCoord -> Grid -> IO CellValue+getCellValue coord grid = Cell.value <$> getCell coord grid++setCell :: CellCoord -> String -> Grid -> IO [CellCoord]+setCell coord string grid = do+ cell <- getCell coord grid+ deleteDependenciesOn cell+ cell' <- do expr <- compile (parse string,coord) grid+ return $ Cell.setExpression string expr cell+ addDependenciesOn cell'+ Dyn.write coord cell' $ cells grid+ evaluate coord grid+ where+ onCoords coords f = forM_ coords $ \c -> updateCell c f grid++ deleteDependenciesOn cell = + let onRef ref = + case ref of+ Column a -> Header.deleteDependent coord a $ columnHeader grid+ Row a -> Header.deleteDependent coord a $ rowHeader grid+ _ -> do cs <- referencedCoords ref grid+ onCoords cs $ Cell.deleteDependent coord+ in forM_ (Cell.dependencies cell) onRef++ addDependenciesOn cell = + let onRef ref = + case ref of+ Column a -> Header.addDependent coord a $ columnHeader grid+ Row a -> Header.addDependent coord a $ rowHeader grid+ _ -> do cs <- referencedCoords ref grid+ onCoords cs $ Cell.addDependent coord+ in forM_ (Cell.dependencies cell) onRef++rewriteCell :: CellCoord -> Grid -> IO [CellCoord]+rewriteCell coord grid = do+ input <- Cell.input <$> getCell coord grid+ setCell coord input grid++evaluate :: CellCoord -> Grid -> IO [CellCoord]+evaluate evalCoord grid = + let evaluateCell = Cell.evaluate $ flip referencedCellValue grid+ evalRec (coord@(row,col)) = do+ cell <- getCell coord grid+ updateCellIO coord evaluateCell grid+ changed1 <- forM (Cell.dependent cell) evalRec+ changed2 <- evaluateHeaderDeps row rowHeader grid+ changed3 <- evaluateHeaderDeps col columnHeader grid+ return $ coord : ((concat changed1) ++ changed2 ++ changed3)+ in+ evalRec evalCoord++evaluateHeaderDeps :: Int -> (Grid -> Header) -> Grid -> IO [CellCoord]+evaluateHeaderDeps i getHeader grid = do+ dependents <- Header.dependents i $ getHeader grid+ concat <$> (forM dependents $ \d -> evaluate d grid)++evaluateAllHeaderDeps :: (Grid -> Header) -> Grid -> IO [CellCoord]+evaluateAllHeaderDeps getHeader grid = do+ dependents <- Header.allDependents $ getHeader grid+ concat <$> (forM dependents $ \d -> evaluate d grid) ++referencedCoords :: Reference -> Grid -> IO [CellCoord]+referencedCoords ref grid =+ case ref of+ Cell coord -> return [coord]+ Range (from,to) -> return $ coordRange (from,to)+ Column c -> do rows <- numViewGridRows grid+ return $ coordRange ((0,c),(rows-1,c))+ Row r -> do cols <- numViewGridCols grid+ return $ coordRange ((r,0),(r,cols-1))++referencedCellValue :: Reference -> Grid -> IO CellValue+referencedCellValue ref grid = do+ coord <- referencedCoords ref grid+ case ref of+ (Cell _) -> getCellValue (head coord) grid+ _ -> ListValue <$> (forM coord $ flip getCellValue grid)+++updateCellIO :: CellCoord -> (Cell -> IO Cell) -> Grid -> IO ()+updateCellIO coord f = Dyn.updateIO coord f . cells++updateCell :: CellCoord -> (Cell -> Cell) -> Grid -> IO ()+updateCell coord f = Dyn.update coord f . cells++resizeTo :: CellCoord -> Grid -> IO ()+resizeTo (r,c) = Dyn.resizeTo ((0,0),(r-1,c-1)) . cells++addRow,addColumn :: String -> Grid -> IO [CellCoord]+addRow name grid = do+ i <- numViewGridRows grid+ Header.setLabel name (i-1) $ rowHeader grid+ evaluateAllHeaderDeps columnHeader grid++addColumn name grid = do+ i <- numViewGridCols grid+ Header.setLabel name (i-1) $ columnHeader grid+ evaluateAllHeaderDeps rowHeader grid++updateRowLabel,updateColumnLabel :: Int -> String -> Grid -> IO [CellCoord]+updateRowLabel i to grid = do+ Header.setLabel to i $ rowHeader grid+ changed1 <- do + cols <- numViewGridCols grid+ rowDeps <- forM [0..cols] $ \c -> Cell.dependent <$> (getCell (i,c) grid)+ forM (concat rowDeps) $ flip rewriteCell grid+ changed2 <- do+ headerDeps <- Header.dependents i $ rowHeader grid+ forM headerDeps $ flip rewriteCell grid + return $ (concat changed1) ++ (concat changed2)++updateColumnLabel i to grid = do+ Header.setLabel to i $ columnHeader grid+ changed1 <- do + rows <- numViewGridRows grid+ colDeps <- forM [0..rows] $ \r -> Cell.dependent <$> (getCell (r,i) grid)+ forM (concat colDeps) $ flip rewriteCell grid+ changed2 <- do+ headerDeps <- Header.dependents i $ columnHeader grid+ forM headerDeps $ flip rewriteCell grid + return $ (concat changed1) ++ (concat changed2)++rowIndex,columnIndex :: String -> Grid -> IO (Maybe Int)+rowIndex name grid = Header.index name $ rowHeader grid+columnIndex name grid = Header.index name $ columnHeader grid++labelsToCoord :: (String,String) -> Grid -> IO (Maybe CellCoord)+labelsToCoord (rowName,columnName) grid = do+ row <- rowIndex rowName grid+ col <- columnIndex columnName grid+ return $ case (row,col) of+ (Just r,Just c) -> Just (r,c)+ _ -> Nothing++getFilePath :: Grid -> IO (Maybe FilePath)+getFilePath = readIORef . filePath++setFilePath :: FilePath -> Grid -> IO ()+setFilePath path grid = writeIORef (filePath grid) $ Just path++deleteColumns :: [Int] -> Grid -> IO ()+deleteColumns cols grid = do+ Header.delete cols $ columnHeader grid+ Dyn.deleteColumnsInMatrix cols $ cells grid++deleteRows :: [Int] -> Grid -> IO ()+deleteRows rows grid = do+ Header.delete rows $ rowHeader grid+ Dyn.deleteRowsInMatrix rows $ cells grid++getColumnValues :: Int -> Grid -> IO [CellValue]+getColumnValues i grid = do+ rows <- numViewGridRows grid+ forM [0..rows-1] $ \row -> getCellValue (row,i) grid++compile :: (CellExpr,CellCoord) -> Grid -> IO CellExpr+compile (expr,coord) grid = do+ expr' <- referenceConversion expr grid+ referenceLoop <- checkReferenceLoop (expr',coord) grid+ if referenceLoop then return $ CompileErrorExpr ReferenceLoop+ else return expr'++checkReferenceLoop :: (CellExpr,CellCoord) -> Grid -> IO Bool+checkReferenceLoop (expr,at) grid =+ let searchOnDependencies deps = do+ refCoords <- do cs <- forM deps $ referencedCoords `flip` grid+ return $ concat cs+ or <$> forM refCoords depthFirstSearch++ depthFirstSearch coords | coords == at = return True+ depthFirstSearch coords = do+ deps <- Cell.dependencies <$> getCell coords grid+ searchOnDependencies deps + in+ searchOnDependencies $ CellContent.dependencies expr++referenceConversion :: CellExpr -> Grid -> IO CellExpr+referenceConversion expr grid =+ let convert (NamedCell a) = fmap Cell <$> labelsToCoord a grid+ convert (NamedRange (a,b)) = + do a' <- labelsToCoord a grid+ b' <- labelsToCoord b grid+ case (a',b') of+ (Just x,Just y) -> return $ Just $ Range (x,y) + _ -> return Nothing+ convert (NamedColumn a) = fmap Column <$> columnIndex a grid+ convert (NamedRow a) = fmap Row <$> rowIndex a grid+ refConversion = flip referenceConversion grid++ in case expr of+ NamedReference ref -> + do ref' <- convert ref+ case ref' of+ Just r -> return $ Reference r+ Nothing -> return $ CompileErrorExpr $ RefConversionError ref+ ListExpr a -> ListExpr <$> forM a refConversion+ UnaryOp s a -> UnaryOp s <$> refConversion a+ BinaryOp s a b -> do a' <- refConversion a+ b' <- refConversion b+ return $ BinaryOp s a' b'+ Call s a -> Call s <$> refConversion a+ Sub a -> Sub <$> refConversion a+ _ -> return expr
+ src/Model/GridHeader.hs view
@@ -0,0 +1,46 @@+module Model.GridHeader (Header,new,addDependent,deleteDependent+ ,dependents,allDependents,index,setLabel+ ,delete,label)+ where++import Control.Applicative ((<$>))+import Control.Monad (forM)+import qualified Data.List as List+import Data.Ix (range)+import Util.DynArray (DynArray)+import qualified Util.DynArray as DynArray+import CellCoordinate (CellCoord)++type Label = String+type Info = (Label,[CellCoord])+type Header = DynArray Int Info++new :: IO Header+new = DynArray.newLinearArray 0 ("",[])++setLabel :: Label -> Int -> Header -> IO ()+setLabel label to = DynArray.update to (\(_,deps) -> (label,deps)) ++addDependent :: CellCoord -> Int -> Header -> IO ()+addDependent dep to = DynArray.update to (\(l,deps) -> (l,dep:deps))++deleteDependent :: CellCoord -> Int -> Header -> IO ()+deleteDependent dep from = + DynArray.update from (\(l,deps) -> (l,List.delete dep deps))++label :: Int -> Header -> IO Label+label i header = fst <$> DynArray.read i header++dependents :: Int -> Header -> IO [CellCoord]+dependents i header = snd <$> DynArray.read i header++allDependents :: Header -> IO [CellCoord]+allDependents header = do+ b <- DynArray.bounds header+ concat <$> (forM (range b) $ \i -> dependents i header)++index :: String -> Header -> IO (Maybe Int)+index label = DynArray.indexBy (\(l,_) -> label == l)++delete :: [Int] -> Header -> IO ()+delete = DynArray.deleteInLinearArray
+ src/Run.hs view
@@ -0,0 +1,159 @@+module Run (program) + where++import Control.Monad (when)+import Control.Monad.Reader (runReaderT)+import Graphics.UI.WX (Prop ((:=)),on)+import qualified Graphics.UI.WX as WX+import qualified Graphics.UI.WXCore as WXC+import Config (Config,initI18n)+import I18n (__)++import qualified Model+import qualified View+import qualified Controller++import qualified View.Modes as VMode+import qualified View.Component.TextWindow as VTextWindow+import qualified View.Component.Notebook as VNotebook++import qualified Controller.Grid as CGrid+import qualified Controller.FormulaInput as CFormulaInput+import qualified Controller.SaveConfig as CSaveConfig++import qualified Controller.Menu.File.NewClose as CFileNewClose+import qualified Controller.Menu.File.Open as CFileOpen+import qualified Controller.Menu.File.Save as CFileSave+import qualified Controller.Menu.File.Import.Csv as CFileImportCsv+import qualified Controller.Menu.File.Export.Image as CFileExportImage+import qualified Controller.Menu.File.Export.Diagram.BarChart as CFileExportBarChart+import qualified Controller.Menu.File.Export.Diagram.BoxPlot as CFileExportBoxPlot++import qualified Controller.Menu.Table.Add as CAdd+import qualified Controller.Menu.Table.Copy as CCopy+import qualified Controller.Menu.Table.Delete as CDelete+import qualified Controller.Menu.Table.Transpose as CTranspose+import qualified Controller.Menu.Table.Reevaluate as CReevaluate++import qualified Controller.Menu.Statistics.FrequencyDistribution as CStatsFD+import qualified Controller.Menu.Statistics.Descriptive as CDescriptive+import qualified Controller.Menu.Statistics.Pivot as CPivot++program :: Config -> IO ()+program config = do+ initI18n config++ frame <- WX.frame [WX.text := "lhae"]+ formulaInput <- VTextWindow.newSingleLine frame []+ notebook <- VNotebook.new frame+ current <- WX.varCreate Nothing++ let view = View.View frame formulaInput notebook current+ model <- Model.new+ ctrl <- Controller.new config model view+ let ifGridOpen eventHandler = do+ opened <- View.isGridOpen view+ when opened $ runReaderT eventHandler ctrl+ call eventHandler = runReaderT eventHandler ctrl ++ windowEH <- WXC.windowGetEventHandler notebook --frame+ notebookId <- WXC.windowGetId notebook+ WXC.evtHandlerOnEvent windowEH notebookId notebookId + [WXC.wxEVT_COMMAND_NOTEBOOK_PAGE_CHANGED] () + (\_ -> return ()) (\_ -> call CGrid.changePageEventHandler)++ WX.set formulaInput + [ WX.processEnter := True+ , WX.enabled := False+ , on WX.command := + do call CFormulaInput.eventHandler+ VMode.gridMode view+ , on WX.focus := + \onFocus -> when (not onFocus) $ VMode.gridMode view+ ]++ fileMenu <- WX.menuPane [WX.text := "&" ++ (__ "File")]+ fileNew <- WX.menuItem fileMenu [WX.text := "&" ++ (__ "New") ++ "\tCtrl+N"]+ fileOpen <- WX.menuItem fileMenu [WX.text := "&" ++ (__ "Open") ++ " ...\tCtrl+O"]+ fileImport <- WX.menuPane [WX.text := "&" ++ (__ "Import")]+ _ <- WX.menuSub fileMenu fileImport []+ fileImportCsv <- WX.menuItem fileImport [WX.text := (__ "From CSV file") ++ " ..."]+ WX.menuLine fileMenu+ fileSaveAs <- WX.menuItem fileMenu [WX.text := (__ "Save as") ++ " ..."]+ fileSave <- WX.menuItem fileMenu [WX.text := "&" ++ (__ "Save") ++ "\tCtrl+S"]+ fileExport <- WX.menuPane [WX.text := "&" ++ (__ "Export")]+ _ <- WX.menuSub fileMenu fileExport []+ fileExportImage <- WX.menuItem fileExport [WX.text := (__ "As image") ++ " ..."]+ fileExportBarChart <- WX.menuItem fileExport [WX.text := (__ "As bar chart") ++ " ..."]+ fileExportBoxPlot <- WX.menuItem fileExport [WX.text := (__ "As box plot") ++ " ..."]+ WX.menuLine fileMenu+ fileClose <- WX.menuItem fileMenu [WX.text := "&" ++ (__ "Close") ++ "\tCtrl+C"]+ fileCloseAll <- WX.menuItem fileMenu [WX.text := (__ "Close all")]+ fileQuit <- WX.menuQuit fileMenu [WX.text := "&" ++ (__ "Quit") ++ "\tCtrl+Q"]++ tableMenu <- WX.menuPane [WX.text := "&" ++ (__ "Table")]+ tableAdd <- WX.menuPane [WX.text := (__ "Add")]+ _ <- WX.menuSub tableMenu tableAdd []+ tableAddColumn <- WX.menuItem tableAdd [WX.text := (__ "Columns") ++ " ..."]+ tableAddRow <- WX.menuItem tableAdd [WX.text := (__ "Rows") ++ " ..."]+ tableCopy <- WX.menuPane [WX.text := (__ "Copy")]+ _ <- WX.menuSub tableMenu tableCopy []+ tableCopyColumn <- WX.menuItem tableCopy [WX.text := (__ "Columns") ++ " ..."]+ tableDelete <- WX.menuPane [WX.text := (__ "Delete")]+ _ <- WX.menuSub tableMenu tableDelete []+ tableDeleteColumn <- WX.menuItem tableDelete [WX.text := (__ "Columns") ++ " ..."]+ tableDeleteRow <- WX.menuItem tableDelete [WX.text := (__ "Rows") ++ " ..."]+ tableTranspose <- WX.menuPane [WX.text := (__ "Transpose")]+ _ <- WX.menuSub tableMenu tableTranspose []+ tableTransposeTable <- WX.menuItem tableTranspose [WX.text := (__ "Table")]+ tableTransposeColumn <- WX.menuItem tableTranspose [WX.text := (__ "Columns") ++ " ..."]+ tableTransposeRow <- WX.menuItem tableTranspose [WX.text := (__ "Rows") ++ " ..."]+ tableReevalAll <- WX.menuItem tableMenu [WX.text := (__ "Reevaluate all cells")]++ statisticsMenu <- WX.menuPane [WX.text := "&" ++ (__ "Statistics")]+ statsFreqDist <- WX.menuItem statisticsMenu [WX.text := (__ "Frequency Distribution") ++ " ..."]+ statsDescriptive <- WX.menuItem statisticsMenu [WX.text := (__ "Descriptive") ++ " ..."]+ statsPivot <- WX.menuItem statisticsMenu [WX.text := (__ "Pivot table") ++ " ..."]++ WX.set frame + [ WX.menuBar := [fileMenu,tableMenu,statisticsMenu] + , WX.layout := WX.column 0 + [ WX.hfill $ WX.widget formulaInput+ , WX.fill $ WX.widget notebook+ ]+ , on (WX.menu fileNew) := call CFileNewClose.newEventHandler++ , on (WX.menu fileExportBoxPlot) := ifGridOpen CFileExportBoxPlot.eventHandler++ , on (WX.menu fileOpen) := call CFileOpen.eventHandler+ , on (WX.menu fileSaveAs) := ifGridOpen CFileSave.saveAsEventHandler+ , on (WX.menu fileSave) := ifGridOpen CFileSave.saveEventHandler+ , on (WX.menu fileImportCsv) := call CFileImportCsv.eventHandler + , on (WX.menu fileExportImage) := ifGridOpen CFileExportImage.eventHandler+ , on (WX.menu fileExportBarChart) := ifGridOpen CFileExportBarChart.eventHandler+ , on (WX.menu fileExportBoxPlot) := ifGridOpen CFileExportBoxPlot.eventHandler+ , on (WX.menu fileClose) := ifGridOpen CFileNewClose.closeEventHandler+ , on (WX.menu fileCloseAll) := ifGridOpen CFileNewClose.closeAllEventHandler+ , on (WX.menu fileQuit) := WX.close frame++ , on (WX.menu tableAddColumn) := ifGridOpen CAdd.columnEventHandler+ , on (WX.menu tableAddRow) := ifGridOpen CAdd.rowEventHandler+ , on (WX.menu tableCopyColumn) := ifGridOpen CCopy.eventHandler+ , on (WX.menu tableDeleteColumn) := ifGridOpen CDelete.columnEventHandler+ , on (WX.menu tableDeleteRow) := ifGridOpen CDelete.rowEventHandler+ , on (WX.menu tableTransposeColumn) := ifGridOpen CTranspose.columnEventHandler+ , on (WX.menu tableTransposeRow) := ifGridOpen CTranspose.rowEventHandler+ , on (WX.menu tableTransposeTable) := ifGridOpen CTranspose.tableEventHandler+ , on (WX.menu tableReevalAll) := ifGridOpen CReevaluate.eventHandler++ , on (WX.menu statsFreqDist) := ifGridOpen CStatsFD.eventHandler+ , on (WX.menu statsDescriptive) := ifGridOpen CDescriptive.eventHandler+ , on (WX.menu statsPivot) := ifGridOpen CPivot.eventHandler++ , on WX.closing := do ifGridOpen CFileNewClose.closeAllEventHandler+ call CSaveConfig.eventHandler+ WX.propagateEvent+++ ]+ return ()
+ src/Util.hs view
@@ -0,0 +1,46 @@+module Util (justWhen,whenNothing,(<.>),deleteByIndices,breakAt+ ,maybeRead,stripWhitespace)+ where++import Control.Applicative ((<$>))+import Control.Exception (assert)+import Data.List (sort)+import Data.Char (isSpace)++justWhen :: Monad m => Maybe a -> (a -> m ()) -> m ()+justWhen maybeThis doThis = case maybeThis of+ Nothing -> return ()+ Just a -> doThis a++whenNothing :: Monad m => Maybe a -> m () -> m ()+whenNothing maybeThis doThis = case maybeThis of+ Nothing -> doThis+ _ -> return ()+ +(<.>) :: Functor f => (a -> b) -> (c -> f a) -> c -> f b+(<.>) p q arg = p <$> (q arg)++deleteByIndices :: [Int] -> [a] -> [a]+deleteByIndices indices =+ let deleteByIndex (_,l) ([],list) = ([],l:list)+ deleteByIndex (lIndex,l) (i:is,list) =+ if lIndex == i then (is,list)+ else (i:is,l:list)+ in+ assert (indices == (reverse $ sort indices)) . + snd . foldr deleteByIndex (indices,[]) . zip [0..]++breakAt :: (Eq a) => a -> [a] -> [[a]]+breakAt partition list = + case dropWhile ((==) partition) list of+ [] -> []+ list' -> element : (breakAt partition list'')+ where (element,list'') = break ((==) partition) list'++maybeRead :: (Read a) => String -> Maybe a+maybeRead string = case reads string of+ [(a,[])] -> Just a+ _ -> Nothing++stripWhitespace :: String -> String+stripWhitespace = takeWhile (not . isSpace) . dropWhile isSpace
+ src/Util/Color.hs view
@@ -0,0 +1,32 @@+module Util.Color (Color (..),fromWXColor,toWXColor,toHexColor)+ where++import qualified Graphics.UI.WX as WX+import Util.Sexp (Sexp (..))+import Util.Sexpable (Sexpable (..), unexpected)+import Numeric (showHex)++newtype Color = Color (Int,Int,Int)++instance Sexpable Color where+ toSexp (Color (r,g,b)) = List [toSexp r,toSexp g,toSexp b]+ fromSexp (List [red,green,blue]) = + do r <- fromSexp red+ g <- fromSexp green+ b <- fromSexp blue+ return $ Color (r,g,b)+ fromSexp sexp = unexpected sexp++fromWXColor :: WX.Color -> Color+fromWXColor c = Color (WX.colorRed c, WX.colorGreen c, WX.colorBlue c)++toWXColor :: Color -> WX.Color+toWXColor (Color (r,g,b)) = WX.rgb r g b++toHexColor :: Color -> String+toHexColor (Color (r,g,b)) = + let hex x = case showHex x "" of+ [y] -> ['0',y]+ y -> y+ in+ concat ["#", hex r, hex g, hex b]
+ src/Util/DynArray.hs view
@@ -0,0 +1,172 @@+module Util.DynArray (DynArray,new,newMatrix,newLinearArray,read+ ,write,writes+ ,resizeTo,updateIO,update,bounds,index,indexBy+ ,elements,showDynArray+ ,deleteInLinearArray+ ,deleteColumnsInMatrix,deleteRowsInMatrix)+ where++import Prelude hiding (init,read)+import Control.Applicative ((<$>))+import Control.Monad (forM_,when,foldM)+import Control.Exception (assert)+import Data.Array.IO (IOArray,readArray,writeArray,getBounds,getElems)+import qualified Data.Array.IO as ArrayIO+import Data.Ix (Ix,range,inRange)+import Data.IORef+import Data.List (sort)++data DynArray i e = DynArray { ref :: IORef (IOArray i e)+ , grow :: (i,i) -> i -> (i,i)+ , init :: e+ }++defaultGrowingSize = 10++showDynArray :: (Ix i,Show e) => DynArray i e -> IO String+showDynArray dynArray = show <$> (readIORef (ref dynArray) >>= getElems)++new :: Ix i => (i,i) -> ((i,i) -> i -> (i,i)) -> e -> IO (DynArray i e)+new ix grow e = do+ array <- ArrayIO.newArray ix e+ ref <- newIORef array+ return $ DynArray ref grow e++newMatrix :: (Int,Int) -> e -> IO (DynArray (Int,Int) e)+newMatrix (initRows,initColumns) = + let grow (_,(r,c)) (i,j) = ( (0,0)+ , ( max r $ i + defaultGrowingSize - 1+ , max c $ j + defaultGrowingSize - 1))+ in+ new ((0,0),(initRows-1,initColumns-1)) grow++newLinearArray :: Int -> e -> IO (DynArray Int e)+newLinearArray numElements =+ let grow _ i = (0,i + defaultGrowingSize - 1)+ in+ new (0,numElements-1) grow++read :: Ix i => i -> DynArray i e -> IO e+read i dynArray = do+ array <- readIORef $ ref dynArray+ bounds <- getBounds array+ if inRange bounds i then readArray array i+ else return $ init dynArray++write :: Ix i => i -> e -> DynArray i e -> IO ()+write i e dynArray = do+ array <- readIORef $ ref dynArray+ bounds <- getBounds array+ if inRange bounds i + then writeArray array i e+ else do+ array' <- ArrayIO.newArray (grow dynArray bounds i) $ init dynArray+ copyUnsafe array array'+ writeArray array' i e+ writeIORef (ref dynArray) array'++writes :: (Ix i,Enum i) => i -> [e] -> DynArray i e -> IO ()+writes i es dynArray = + forM_ (zip [i..] es) $ \(pos,e) -> write pos e dynArray++copyUnsafe :: Ix i => IOArray i e -> IOArray i e -> IO ()+copyUnsafe from to = do+ r <- getBounds from+ forM_ (range r) $ \i -> readArray from i >>= writeArray to i++resizeTo :: Ix i => (i,i) -> DynArray i e -> IO ()+resizeTo range dynArray = + ArrayIO.newArray range (init dynArray) >>= writeIORef (ref dynArray)++updateIO :: Ix i => i -> (e -> IO e) -> DynArray i e -> IO ()+updateIO i f dynArray = + read i dynArray >>= f >>= \e -> write i e dynArray++update :: Ix i => i -> (e -> e) -> DynArray i e -> IO ()+update i f = updateIO i (return . f)+ +bounds :: Ix i => DynArray i e -> IO (i,i)+bounds dynArray = readIORef (ref dynArray) >>= getBounds++indexBy :: (Enum i, Ix i) => (e -> Bool) -> DynArray i e -> IO (Maybe i)+indexBy equals dynArray = do+ array <- readIORef $ ref dynArray+ bounds <- getBounds array+ let indexRec i = + if inRange bounds i + then do value <- readArray array i+ if equals value then return $ Just i+ else indexRec $ succ i+ else return Nothing+ indexRec $ fst bounds++index :: (Enum i, Ix i, Eq e) => e -> DynArray i e -> IO (Maybe i)+index e = indexBy ((==) e)++elements :: Ix i => DynArray i e -> IO [e]+elements dynArray = readIORef (ref dynArray) >>= getElems++deleteInLinearArray :: [Int] -> DynArray Int e -> IO ()+deleteInLinearArray is dynArray = do+ assert (is == sort is) $ return ()+ array <- readIORef $ ref dynArray+ (minB,maxB) <- getBounds array++ let deleteElement (x:xs,deleted) index | x == index = + return (xs,deleted+1)++ deleteElement (xs,deleted) index = do+ when (deleted > 0) $ + readArray array index >>= writeArray array (index - deleted)+ return (xs,deleted)++ (_,deleted) <- foldM deleteElement (is,0) [minB..maxB] + forM_ [maxB-deleted+1..maxB] $ \x -> writeArray array x $ init dynArray++deleteColumnsInMatrix :: [Int] -> DynArray (Int,Int) e -> IO ()+deleteColumnsInMatrix columns dynArray = do+ assert (columns == sort columns) $ return ()+ array <- readIORef $ ref dynArray+ ((minRow,minCol),(maxRow,maxCol)) <- getBounds array+ + let copyColumn from to =+ forM_ [minRow..maxRow] $ \r -> + readArray array (r,from) >>= writeArray array (r,to)++ deleteColumn (col:cols,deleted) index | col == index = + return (cols,deleted+1)++ deleteColumn (cols,deleted) index = do+ when (deleted > 0) $ copyColumn index $ index - deleted+ return (cols,deleted)++ resetColumnToInit col = + forM_ [minRow..maxRow] $ \r -> + writeArray array (r,col) $ init dynArray++ (_,deleted) <- foldM deleteColumn (columns,0) [minCol..maxCol] + forM_ [maxCol-deleted+1..maxCol] resetColumnToInit++deleteRowsInMatrix :: [Int] -> DynArray (Int,Int) e -> IO ()+deleteRowsInMatrix rowsToDelete dynArray = do+ assert (rowsToDelete == sort rowsToDelete) $ return ()+ array <- readIORef $ ref dynArray+ ((minRow,minCol),(maxRow,maxCol)) <- getBounds array+ + let copyRow from to =+ forM_ [minCol..maxCol] $ \c -> + readArray array (from,c) >>= writeArray array (to,c)++ deleteRow (row:rows,deleted) index | row == index = + return (rows,deleted+1)++ deleteRow (rows,deleted) index = do+ when (deleted > 0) $ copyRow index $ index - deleted+ return (rows,deleted)++ resetRowToInit row = + forM_ [minCol..maxCol] $ \c -> + writeArray array (row,c) $ init dynArray++ (_,deleted) <- foldM deleteRow (rowsToDelete,0) [minRow..maxRow] + forM_ [maxRow-deleted+1..maxRow] resetRowToInit
+ src/Util/FileType.hs view
@@ -0,0 +1,64 @@+module Util.FileType ( FileType (..),extension,fromExtension+ , fromFilePath,wildcard,bitmapType+ , unknownFileType,unknownFileTypeIn+ , justPngWildcard)+ where++import qualified Graphics.UI.WXCore as WXC+import Data.Char (toLower)+import System.FilePath (takeExtension)+import Util.Sexp (Sexp(Atom))+import Util.Sexpable (Sexpable (..),unexpected,unknownAtom)+import I18n(__)++data FileType = Bmp | Jpeg | Png | Tif deriving (Eq)++instance Sexpable FileType where+ toSexp = Atom . extension+ fromSexp (Atom ext) = + case fromExtension ext of+ Nothing -> unknownAtom ext+ Just ft -> return ft+ fromSexp sexp = unexpected sexp++extension :: FileType -> String+extension ext = + case ext of { Bmp -> "bmp"; Jpeg -> "jpeg"+ ; Png -> "png"; Tif -> "tif"}++fromExtension :: String -> Maybe FileType+fromExtension ext = + case map toLower ext of + "bmp" -> Just Bmp+ "jpg" -> Just Jpeg; "jpeg" -> Just Jpeg+ "png" -> Just Png; "tif" -> Just Tif+ _ -> Nothing++unknownFileType :: String -> String+unknownFileType ext = (__ "Unknown file type") ++ " '" ++ ext ++ "'"++unknownFileTypeIn :: FilePath -> String+unknownFileTypeIn filepath = + (__ "Unknown file type")++ " '" ++ (tail $ takeExtension filepath) ++ "'"++fromFilePath :: FilePath -> Maybe FileType+fromFilePath = fromExtension . tail . takeExtension++wildcard :: [(String,[String])]+wildcard = [ ("bmp",["*.bmp","*.BMP"])+ , ("jpeg",["*.jpg","*.JPG","*.jpeg","*.JPEG"])+ , ("png",["*.png","*.PNG"])+ , ("tif",["*.tif","*.TIF"])+ ]++justPngWildcard :: [(String,[String])]+justPngWildcard = [("png",["*.png","*.PNG"])]++bitmapType :: FileType -> Int+bitmapType ext = + case ext of+ Bmp -> WXC.wxBITMAP_TYPE_BMP+ Jpeg -> WXC.wxBITMAP_TYPE_JPEG+ Png -> WXC.wxBITMAP_TYPE_PNG+ Tif -> WXC.wxBITMAP_TYPE_TIF+
+ src/Util/Gnuplot.hs view
@@ -0,0 +1,12 @@+module Util.Gnuplot (run)+ where++import Control.Monad (forM_)+import System.Exit (ExitCode(..))+import System.Cmd (rawSystem)++run :: (FilePath,String) -> [(FilePath,String)] -> IO ExitCode+run (gnuplotFile,gnuplotData) dataFiles = do+ writeFile gnuplotFile gnuplotData+ forM_ dataFiles $ uncurry writeFile+ rawSystem "gnuplot" [gnuplotFile]
+ src/Util/Sexp.hs view
@@ -0,0 +1,83 @@+module Util.Sexp ( Sexp (..),ValidSexp (..),parse,parseFile,prettyPrint+ , validate)+ where++import Control.Applicative ((<$>))+import Text.ParserCombinators.Parsec ((<|>),(<?>),Parser)+import qualified Text.ParserCombinators.Parsec as P+import qualified Text.ParserCombinators.Parsec.Language as L+import qualified Text.ParserCombinators.Parsec.Token as T++data Sexp = Atom String+ | List [Sexp]++instance Show Sexp where+ show (Atom a) = show a+ show (List a) = "(" ++ (unwords $ map show a) ++ ")"++data ValidSexp = ValidAtom+ | ValidList [ValidSexp]+ | ValidListOf ValidSexp+ | ValidSexp++parse :: String -> Either String Sexp+parse = either (Left . show) Right . P.parse sexp ""++parseFile :: FilePath -> IO (Either String Sexp)+parseFile filePath = parse <$> readFile filePath++sexp,simpleSexp,atom,list :: Parser Sexp++sexp = do { T.whiteSpace lexer+ ; e <- simpleSexp+ ; P.eof+ ; return e+ } <?> "s-expression"++simpleSexp = atom <|> list <?> "simple s-expression"++atom = (Atom <$> string) <|> (Atom <$> identifier) <?> "atom"++list = List <$> (P.between (symbol "(") (symbol ")") + $ P.many simpleSexp) <?> "list"++symbol = T.symbol lexer+string = T.stringLiteral lexer+identifier = T.identifier lexer++lexer :: T.TokenParser ()+lexer = T.makeTokenParser $ L.emptyDef ++prettyPrint :: Sexp -> String+prettyPrint =+ let isAtomList (List l) = + let isAtom (Atom _) = True+ isAtom _ = False+ in+ and $ map isAtom l+ isAtomList _ = False++ ppList space (List l) =+ let lines = map (pp $ " " ++ space) l+ in+ unlines ([space ++ "("] ++ lines) ++ space ++ ")"++ pp space sexp = + if isAtomList sexp + then space ++ (show sexp)+ else case sexp of+ List _ -> ppList space sexp+ Atom a -> space ++ (show a)+ in+ pp ""++validate :: ValidSexp -> Sexp -> Bool+validate valid sexp =+ case (valid,sexp) of+ (ValidAtom,Atom _) -> True+ (ValidList v,List l) -> + if length v /= length l then False+ else and $ map (uncurry validate) $ zip v l+ (ValidListOf v,List l) -> and $ map (validate v) l+ (ValidSexp,_) -> True+ _ -> False
+ src/Util/Sexpable.hs view
@@ -0,0 +1,86 @@+{-# OPTIONS_GHC -XFlexibleInstances -XOverlappingInstances #-}+module Util.Sexpable ( Sexpable (..),ParseSexpError,PSE+ , unexpected,unknownAtom+ , recordToSexp,recordFromSexp)+ where++import Control.Monad.Error++import Util.Sexp (Sexp (..))++data ParseSexpError = Unexpected Sexp+ | UnknownAtom String+ | UnknownError String++type PSE = Either ParseSexpError++instance Show ParseSexpError where+ show (Unexpected sexp) = "Unexpected s-expression " ++ (show sexp)+ show (UnknownAtom atom) = "Unknown atom " ++ atom+ show (UnknownError msg) = "Unknown error: " ++ msg++instance Error ParseSexpError where+ noMsg = UnknownError "Unknown s-expression parsing error"+ strMsg msg = UnknownError msg++unexpected :: Sexp -> PSE a+unexpected = throwError . Unexpected++unknownAtom :: String -> PSE a+unknownAtom = throwError . UnknownAtom++class Sexpable a where+ toSexp :: a -> Sexp+ fromSexp :: Sexp -> PSE a++instance Sexpable Char where+ toSexp c = Atom [c]+ fromSexp (Atom (a:[])) = return a+ fromSexp (Atom a) = throwError $ UnknownError $ "Can not parse character from " ++ (show $ Atom a)+ fromSexp sexp = unexpected sexp++instance Sexpable [Char] where+ toSexp = Atom+ fromSexp (Atom a) = return a+ fromSexp sexp = unexpected sexp++instance (Sexpable a) => Sexpable [a] where+ toSexp = List . map toSexp+ fromSexp (List a) = forM a fromSexp+ fromSexp sexp = unexpected sexp++instance Sexpable Int where+ toSexp = Atom . show+ fromSexp (Atom a) = return $ read a+ fromSexp sexp = unexpected sexp++instance Sexpable Double where+ toSexp = Atom . show+ fromSexp (Atom a) = return $ read a+ fromSexp sexp = unexpected sexp++instance (Sexpable a) => Sexpable (Maybe a) where+ toSexp Nothing = List [Atom "optional"]+ toSexp (Just a) = List [Atom "optional",toSexp a]+ fromSexp (List [Atom "optional"]) = return Nothing+ fromSexp (List [Atom "optional",sexp]) = + fromSexp sexp >>= return . Just+ fromSexp sexp = unexpected sexp++instance Sexpable Bool where+ toSexp True = Atom "true"+ toSexp False = Atom "false"+ fromSexp (Atom "true") = return True+ fromSexp (Atom "false") = return False+ fromSexp sexp = unexpected sexp++recordToSexp :: [(String, b -> Sexp)] -> b -> Sexp+recordToSexp assocs record = + List $ (map (\(name, field) ->+ List [Atom name, field record]+ )+ ) assocs++recordFromSexp :: (a -> Sexp -> PSE a) -> a -> Sexp -> PSE a+recordFromSexp f init (List options) = foldM f init options+recordFromSexp _ _ sexp = unexpected sexp
+ src/View.hs view
@@ -0,0 +1,33 @@+module View (View(..),Info(..),onGrid,isGridOpen+ ,info)+ where++import qualified Graphics.UI.WX as WX+import qualified View.Component.Grid as Grid+import qualified View.Component.TextWindow as TextWindow+import qualified View.Component.Notebook as Notebook++data View = View { frame :: WX.Frame ()+ , formulaInput :: TextWindow.TextWindow+ , notebook :: Notebook.Notebook+ , current :: WX.Var (Maybe Grid.Grid)+ }++data Info = Info { numDataRows :: IO Int+ , numDataColumns :: IO Int+ }++onGrid :: View -> (Grid.Grid -> IO a) -> IO a+onGrid view f = WX.varGet (current view) >>= \(Just grid) -> f grid++isGridOpen :: View -> IO Bool+isGridOpen view = do+ current <- WX.varGet $ current view+ return $ case current of+ Nothing -> False+ _ -> True++info :: View -> Info+info view = Info { numDataRows = onGrid view Grid.numDataRows+ , numDataColumns = onGrid view Grid.numDataColumns+ }
+ src/View/Component/Grid.hs view
@@ -0,0 +1,124 @@+module View.Component.Grid (Grid,new,getCursorPos+ ,cycleCursor+ ,setValue,getValue+ ,getRowValues,getColumnValues+ ,getRowLabel,getColumnLabel+ ,getRowLabels,getColumnLabels+ ,setRowLabel,setColumnLabel+ ,addRow,addColumn,numDataRows,numDataColumns+ ,deleteColumns,deleteRows+ )+ where++import Control.Exception (assert)+import Control.Monad (forM,forM_)+import qualified Graphics.UI.WX as WX+import qualified Graphics.UI.WXCore as WXC+import Data.List (sort)+import CellCoordinate (CellCoord)+import Util (deleteByIndices)++type Grid = WXC.Grid ()++new :: WX.Window a -> [WX.Prop Grid] -> IO Grid+new parent props =+ let createFromParent id rect props flags = + do g <- WXC.gridCreate parent id rect flags+ WXC.gridCreateGrid g 0 0 0+ WX.set g props+ return g+ in do+ grid <- WX.initialWindow createFromParent props 0+ WXC.gridAppendRows grid 1 True+ WXC.gridAppendCols grid 1 True+ WXC.gridSetRowLabelValue grid 0 "..."+ WXC.gridSetColLabelValue grid 0 "..."+ return grid++cycleCursor :: Grid -> IO ()+cycleCursor grid = do + (row,col) <- getCursorPos grid+ rowCount <- WXC.gridGetNumberRows grid+ colCount <- WXC.gridGetNumberCols grid+ let (row',col') = + if row+1 >= rowCount + then if col+1 >= colCount then (0,0) else (0,col+1)+ else (row+1,col)+ WXC.gridSetGridCursor grid row' col'++getCursorPos :: Grid -> IO CellCoord+getCursorPos grid = do+ row <- WXC.gridGetGridCursorRow grid+ col <- WXC.gridGetGridCursorCol grid+ return (row,col)+ +getValue :: CellCoord -> Grid -> IO String+getValue (row,col) grid = WXC.gridGetCellValue grid row col++setValue :: CellCoord -> String -> Grid -> IO ()+setValue (row,col) s grid = WXC.gridSetCellValue grid row col s++numDataRows,numDataColumns :: Grid -> IO Int+numDataRows grid = WXC.gridGetNumberRows grid >>= \n -> return $ n-1+numDataColumns grid = WXC.gridGetNumberCols grid >>= \n -> return $ n-1++getRowLabel,getColumnLabel :: Int -> Grid -> IO String+getRowLabel = flip WXC.gridGetRowLabelValue+getColumnLabel = flip WXC.gridGetColLabelValue++getRowLabels,getColumnLabels :: Grid -> IO [String]+getRowLabels grid = do+ n <- numDataRows grid+ forM [0..n-1] $ flip getRowLabel grid+getColumnLabels grid = do+ n <- numDataColumns grid+ forM [0..n-1] $ flip getColumnLabel grid++setRowLabel,setColumnLabel :: Int -> String -> Grid -> IO ()+setRowLabel n s grid = WXC.gridSetRowLabelValue grid n s+setColumnLabel n s grid = WXC.gridSetColLabelValue grid n s++getRowValues,getColumnValues :: Int -> Grid -> IO [String]+getRowValues n grid = do+ cols <- numDataColumns grid+ forM [0..cols-1] $ \i -> getValue (n,i) grid+getColumnValues n grid = do+ rows <- numDataRows grid+ forM [0..rows-1] $ \i -> getValue (i,n) grid++addRow,addColumn :: String -> Grid -> IO ()+addRow name grid = do+ n <- numDataRows grid+ WXC.gridAppendRows grid 1 True+ setRowLabel n name grid+ setRowLabel (n+1) "..." grid+addColumn name grid = do+ n <- numDataColumns grid+ WXC.gridAppendCols grid 1 True+ setColumnLabel n name grid+ setColumnLabel (n+1) "..." grid++deleteColumns :: [Int] -> Grid -> IO ()+deleteColumns cols grid = do+ assert (reverse (sort cols) == cols) $ return ()+ labels <- getColumnLabels grid++ forM_ cols $ \i -> WXC.gridDeleteCols grid i 1 True++ -- Reset labels (because of buggy gridDeleteCols)+ forM (zip [0..] $ deleteByIndices cols labels) $ \(col,label) -> + setColumnLabel col label grid++ numDataColumns grid >>= \n -> setColumnLabel n "..." grid++deleteRows :: [Int] -> Grid -> IO ()+deleteRows rows grid = do+ assert (reverse (sort rows) == rows) $ return ()+ labels <- getRowLabels grid+ forM_ rows $ \i -> WXC.gridDeleteRows grid i 1 True++ -- Reset labels (because of buggy gridDeleteCols)+ forM (zip [0..] $ deleteByIndices rows labels) $ \(row,label) ->+ setRowLabel row label grid++ numDataRows grid >>= \n -> setRowLabel n "..." grid
+ src/View/Component/Notebook.hs view
@@ -0,0 +1,65 @@+module View.Component.Notebook + (Notebook,GridId,new,addGrid,deleteCurrentView+ ,currentSelectionNum,currentSelectionId,getGrid+ ,getCurrentCaption,setCurrentCaption,pageId,numPages)+ where++import Control.Applicative ((<$>))+import qualified Graphics.UI.WX as WX+import qualified Graphics.UI.WXCore as WXC+import View.Component.Grid (Grid)++type Notebook = WXC.Notebook ()+type GridId = Int++new :: WX.Window a -> IO Notebook+new parent =+ let createFromParent id rect props _ = do+ n <- WXC.notebookCreate parent id rect WXC.wxNB_BOTTOM+ WX.set n props+ return n+ in+ WX.initialWindow createFromParent [] 0++addGrid :: Grid -> String -> Notebook -> IO ()+addGrid grid caption notebook = do+ _ <- WXC.notebookAddPage notebook grid caption True (-1)+ return ()++deleteCurrentView :: Notebook -> IO ()+deleteCurrentView notebook = do+ _ <- WXC.notebookGetSelection notebook >>=+ WXC.notebookDeletePage notebook+ return ()++currentSelectionNum :: Notebook -> IO (Maybe Int)+currentSelectionNum notebook = do+ n <- numPages notebook+ if n == 0 then return Nothing+ else Just <$> WXC.notebookGetSelection notebook++currentSelectionId :: Notebook -> IO (Maybe GridId)+currentSelectionId notebook = do+ n <- numPages notebook+ if n == 0 then return Nothing+ else Just <$> (WXC.notebookGetSelection notebook >>=+ flip pageId notebook)++pageId :: Int -> Notebook -> IO GridId+pageId n notebook = WXC.notebookGetPage notebook n >>= WXC.windowGetId++getGrid :: Int -> Notebook -> IO Grid+getGrid n notebook = WX.objectCast <$> WXC.notebookGetPage notebook n++setCurrentCaption :: String -> Notebook -> IO ()+setCurrentCaption caption notebook = do+ current <- WXC.notebookGetSelection notebook+ _ <- WXC.notebookSetPageText notebook current caption+ return ()++getCurrentCaption :: Notebook -> IO String+getCurrentCaption notebook = WXC.notebookGetSelection notebook + >>= WXC.notebookGetPageText notebook++numPages :: Notebook -> IO Int+numPages = WXC.notebookGetPageCount
+ src/View/Component/TextWindow.hs view
@@ -0,0 +1,24 @@+module View.Component.TextWindow (TextWindow,new,newSingleLine+ ,printString,print,text)+ where++import Prelude hiding (print)+import qualified Graphics.UI.WX as WX+import qualified Graphics.UI.WXCore as WXC++type TextWindow = WX.TextCtrl ()++new :: WX.Window a -> [WX.Prop TextWindow] -> IO TextWindow+new = WX.textCtrl++newSingleLine :: WX.Window a -> [WX.Prop TextWindow] -> IO TextWindow+newSingleLine = WX.textEntry++printString :: String -> TextWindow -> IO ()+printString s win = WXC.textCtrlAppendText win $ s ++ "\n"+ +print :: Show a => a -> TextWindow -> IO ()+print s win = WXC.textCtrlAppendText win $ show s ++ "\n"++text :: TextWindow -> IO String+text = WXC.textCtrlGetValue
+ src/View/Dialog/Complex.hs view
@@ -0,0 +1,248 @@+module View.Dialog.Complex (Layout (..),Widget (..),Modifier (..)+ ,showDialog,showSimpleDialog+ ,okButton,cancelButton)+ where++import Control.Applicative ((<$>))+import Control.Monad (forM,forM_,foldM,when)+import Data.Char (isDigit)+import Graphics.UI.WX (Prop ((:=)),on)+import qualified Graphics.UI.WX as WX+import qualified Graphics.UI.WXCore as WXC+import View (View,frame)+import Util (justWhen,maybeRead)+import Util.Color (Color,fromWXColor,toWXColor)+import I18n (__)++data Widget a b = + ScrolledWindow (WX.Dialog () -> IO (WX.ScrolledWindow ()))+ | TextEntry (a -> String) (a -> String -> a)+ | NumberEntry (a -> Double) (a -> Double -> a)+ | Button String b+ | DefaultButton String b+ | TerminationButton String+ | ColorButton (a -> Color) (a -> Color -> a)+ | SingleListBox [String] (a -> Maybe Int) (a -> Maybe Int -> a)+ | MultiListBox [String] (a -> [Int]) (a -> [Int] -> a)+ | Spinner Int Int (a -> Int) (a -> Int -> a)+ | Choice [String] (a -> Int) (a -> Int -> a)+ | CheckBox String (a -> Bool) (a -> Bool -> a)+ | RadioBox String [String] (a -> Int) (a -> Int -> a)++data Modifier = HFill | Center | Margin | Boxed String ++data WXWidget a b = + WXScrolledWindow (WX.ScrolledWindow ())+ | WXTextEntry (WX.TextCtrl ()) (a -> String -> a)+ | WXNumberEntry (WX.TextCtrl ()) (a -> Double -> a)+ | WXButton (WX.Button ()) b+ | WXTerminationButton (WX.Button ())+ | WXColorButton (WX.Button ()) (a -> Color -> a)+ | WXSingleListBox (WX.SingleListBox ()) (a -> Maybe Int -> a)+ | WXMultiListBox (WX.MultiListBox ()) (a -> [Int] -> a)+ | WXSpinCtrl (WX.SpinCtrl ()) (a -> Int -> a)+ | WXChoice (WX.Choice ()) (a -> Int -> a)+ | WXCheckBox (WX.CheckBox ()) (a -> Bool -> a)+ | WXRadioBox (WX.RadioBox ()) (a -> Int -> a)++instance WX.Widget (WXWidget a b) where+ widget (WXScrolledWindow ctrl) = WX.widget ctrl+ widget (WXTextEntry ctrl _) = WX.widget ctrl+ widget (WXNumberEntry ctrl _) = WX.widget ctrl+ widget (WXButton ctrl _) = WX.widget ctrl+ widget (WXTerminationButton ctrl) = WX.widget ctrl+ widget (WXColorButton ctrl _) = WX.widget ctrl+ widget (WXSingleListBox ctrl _) = WX.widget ctrl+ widget (WXMultiListBox ctrl _) = WX.widget ctrl+ widget (WXSpinCtrl ctrl _) = WX.widget ctrl+ widget (WXChoice ctrl _) = WX.widget ctrl+ widget (WXCheckBox ctrl _) = WX.widget ctrl+ widget (WXRadioBox ctrl _) = WX.widget ctrl++data Layout a b = Row [Layout a b]+ | Column [Layout a b]+ | Grid [[Layout a b]]+ | Label String+ | Modifier Modifier (Layout a b)+ | Modifiers [Modifier] (Layout a b)+ | Widget (Widget a b)+ | FocusOn (Widget a b)+ | WXWidget (WXWidget a b)++showDialog :: String -> Layout a b -> a -> View -> IO (Maybe (a,b))+showDialog caption layout defaultRecord view = do+ dialog <- WX.dialog (frame view) [WX.text := caption]+ layout' <- buildWXWidgets dialog layout defaultRecord++ WX.set dialog [WX.layout := buildWXLayout layout']++ WX.showModal dialog + (\endWith -> setButtonActions endWith defaultRecord layout')++showSimpleDialog :: String -> Layout a () -> a -> View -> IO (Maybe a)+showSimpleDialog caption layout defaultRecord view = + fmap fst <$> showDialog caption layout defaultRecord view++buildWXWidgets :: WX.Dialog () -> Layout a b -> a -> IO (Layout a b)+buildWXWidgets dialog layout record =+ let recBuild layout' = buildWXWidgets dialog layout' record+ in+ case layout of+ Row a -> Row <$> forM a recBuild+ Column a -> Column <$> forM a recBuild+ Grid a -> Grid <$> mapM (mapM recBuild) a+ Label a -> return $ Label a+ Modifier a b -> Modifier a <$> recBuild b+ Modifiers a b -> Modifiers a <$> recBuild b+ FocusOn a -> do (WXWidget w) <- recBuild $ Widget a+ case w of WXTextEntry c _ -> WX.focusOn c+ WXNumberEntry c _ -> WX.focusOn c+ WXButton c _ -> WX.focusOn c+ return $ WXWidget w+ Widget (ScrolledWindow makeWindow) -> + (WXWidget . WXScrolledWindow) <$> makeWindow dialog+ Widget (TextEntry get set) ->+ do ctrl <- WX.textEntry dialog [WX.text := get record]+ return $ WXWidget $ WXTextEntry ctrl set+ Widget (NumberEntry get set) ->+ do ctrl <- WX.textEntry dialog [WX.text := show $ get record]+ WX.set ctrl [on WX.anyKey := numberEntryKeyHandler ctrl]+ return $ WXWidget $ WXNumberEntry ctrl set+ Widget (Button text action) -> + do ctrl <- WX.button dialog [WX.text := text]+ return $ WXWidget $ WXButton ctrl action+ Widget (DefaultButton text action) -> + do ctrl <- WX.button dialog [WX.text := text]+ WXC.buttonSetDefault ctrl+ return $ WXWidget $ WXButton ctrl action+ Widget (TerminationButton text) -> + do ctrl <- WX.button dialog [WX.text := text]+ return $ WXWidget $ WXTerminationButton ctrl+ Widget (ColorButton get set) -> + do ctrl <- WX.button dialog [ WX.bgcolor := toWXColor $ get record] + WX.set ctrl [on WX.command := do+ color <- WX.get ctrl WX.bgcolor >>= WX.colorDialog dialog+ justWhen color $ \c -> WX.set ctrl [WX.bgcolor := c]]+ return $ WXWidget $ WXColorButton ctrl set+ Widget (SingleListBox items get set) ->+ do ctrl <- WX.singleListBox dialog [WX.items := items]+ justWhen (get record) $ \selection -> + WX.set ctrl [WX.selection := selection]+ return $ WXWidget $ WXSingleListBox ctrl set+ Widget (MultiListBox items get set) ->+ do ctrl <- WX.multiListBox dialog [WX.items := items]+ when (not $ null $ get record) $+ WX.set ctrl [WX.selections := get record]+ return $ WXWidget $ WXMultiListBox ctrl set+ Widget (Spinner min max get set) ->+ do ctrl <- WX.spinCtrl dialog min max [WX.selection := get record]+ return $ WXWidget $ WXSpinCtrl ctrl set+ Widget (Choice items get set) ->+ do ctrl <- WX.choice dialog [ WX.items := items+ , WX.selection := get record]+ return $ WXWidget $ WXChoice ctrl set+ Widget (CheckBox text get set) ->+ do ctrl <- WX.checkBox dialog [ WX.text := text+ , WX.checked := get record]+ return $ WXWidget $ WXCheckBox ctrl set+ Widget (RadioBox text items get set) ->+ do ctrl <- WX.radioBox dialog WX.Vertical items+ [WX.text := text, WX.selection := get record]+ return $ WXWidget $ WXRadioBox ctrl set++buildWXLayout :: Layout a b -> WX.Layout+buildWXLayout layout = + case layout of+ Row a -> WX.row 5 $ map buildWXLayout a+ Column a -> WX.column 5 $ map buildWXLayout a+ Grid rows -> WX.grid 5 5 $ map (map buildWXLayout) rows+ Label a -> WX.label a+ Modifier HFill a -> WX.hfill $ buildWXLayout a+ Modifier Center a -> WX.centre $ buildWXLayout a+ Modifier Margin a -> WX.margin 10 $ buildWXLayout a+ Modifier (Boxed text) a -> WX.boxed text $ buildWXLayout a+ Modifiers xs a -> buildWXLayout $ foldr Modifier a xs+ WXWidget a -> WX.widget a++setButtonActions :: (Maybe (a,b) -> IO ()) -> a -> Layout a b -> IO ()+setButtonActions endWith record completeLayout = + let recSet layout =+ case layout of+ Row a -> forM_ a recSet+ Column a -> forM_ a recSet+ Grid rows -> mapM_ (mapM_ recSet) rows+ Label _ -> return ()+ Modifier _ a -> recSet a+ Modifiers _ a -> recSet a+ WXWidget (WXTerminationButton button) ->+ WX.set button [on WX.command := endWith Nothing]+ WXWidget (WXButton button action) ->+ WX.set button [on WX.command := + do result <- buildRecord record completeLayout+ endWith $ Just (result,action)]+ WXWidget _ -> return () + in+ recSet completeLayout++buildRecord :: a -> Layout a b -> IO a+buildRecord record layout = + case layout of+ Row a -> foldM buildRecord record a+ Column a -> foldM buildRecord record a+ Grid rows -> foldM (foldM buildRecord) record rows+ Label _ -> return record+ Modifier _ a -> buildRecord record a+ Modifiers _ a -> buildRecord record a+ WXWidget (WXScrolledWindow _) -> return record+ WXWidget (WXTextEntry ctrl set) -> + set record <$> WX.get ctrl WX.text+ WXWidget (WXNumberEntry ctrl set) -> do+ num <- maybeRead <$> WX.get ctrl WX.text+ case num of Just n -> return $ set record n+ Nothing -> return record+ WXWidget (WXButton _ _) -> return record+ WXWidget (WXTerminationButton _) -> return record+ WXWidget (WXColorButton ctrl set) -> + (set record . fromWXColor) <$> WX.get ctrl WX.bgcolor+ WXWidget (WXSingleListBox ctrl set) -> + set record <$> getMaybeSelection ctrl+ WXWidget (WXMultiListBox ctrl set) -> + set record <$> WX.get ctrl WX.selections+ WXWidget (WXSpinCtrl ctrl set) -> set record <$> WX.get ctrl WX.selection+ WXWidget (WXChoice ctrl set) -> set record <$> WX.get ctrl WX.selection+ WXWidget (WXCheckBox ctrl set) -> set record <$> WX.get ctrl WX.checked+ WXWidget (WXRadioBox ctrl set) -> set record <$> WX.get ctrl WX.selection++ where+ getMaybeSelection ctrl = do+ selection <- WX.get ctrl WX.selection+ return $ if selection == -1 then Nothing else Just selection++numberEntryKeyHandler :: (WX.TextCtrl ()) -> WX.Key -> IO ()+numberEntryKeyHandler ctrl key = do+ text <- WX.get ctrl WX.text+ let lastCharIsDigit = (not $ null text) && + (isDigit $ head $ reverse text)+ case key of+ WX.KeyChar '0' -> WX.propagateEvent+ WX.KeyChar '1' -> WX.propagateEvent+ WX.KeyChar '2' -> WX.propagateEvent+ WX.KeyChar '3' -> WX.propagateEvent+ WX.KeyChar '4' -> WX.propagateEvent+ WX.KeyChar '5' -> WX.propagateEvent+ WX.KeyChar '6' -> WX.propagateEvent+ WX.KeyChar '7' -> WX.propagateEvent+ WX.KeyChar '8' -> WX.propagateEvent+ WX.KeyChar '9' -> WX.propagateEvent+ WX.KeyChar '.' -> when (not (elem '.' text) && lastCharIsDigit) + WX.propagateEvent+ WX.KeyChar '-' -> when (null text) WX.propagateEvent+ WX.KeyChar _ -> return ()+ WX.KeySpace -> return ()+ _ -> WX.propagateEvent++okButton :: b -> Layout a b+okButton = Widget . DefaultButton (__ "&Ok")++cancelButton :: Layout a b+cancelButton = Widget $ TerminationButton (__ "&Cancel")
+ src/View/Dialog/Simple.hs view
@@ -0,0 +1,23 @@+module View.Dialog.Simple (input,warning,confirm)+ where++import qualified Graphics.UI.WX as WX+import View (View,frame)+import View.Dialog.Complex (Layout (..),Widget (..),Modifier (..)+ ,showSimpleDialog,okButton,cancelButton)+import I18n (__)++input :: String -> String -> View -> IO (Maybe String)+input caption =+ let dialog = Modifier Margin $ Column + [ FocusOn $ TextEntry id (\_ -> id)+ , Modifier Center + $ Row [okButton (), cancelButton]]+ in+ showSimpleDialog caption dialog++warning :: String -> View -> IO ()+warning text view = WX.errorDialog (frame view) (__ "Error") text++confirm :: String -> String -> View -> IO Bool+confirm caption s view = WX.confirmDialog (frame view) caption s True
+ src/View/FormulaInput.hs view
@@ -0,0 +1,16 @@+module View.FormulaInput (log,logString,formula)+ where++import Prelude hiding (log)+import qualified Graphics.UI.WXCore as WXC+import View (View,formulaInput)+import qualified View.Component.TextWindow as TW++logString :: String -> View -> IO ()+logString s view = WXC.textCtrlSetValue (formulaInput view) s++log :: Show a => a -> View -> IO ()+log some = logString $ show some++formula :: View -> IO String+formula = TW.text . formulaInput
+ src/View/GridPage.hs view
@@ -0,0 +1,96 @@+module View.GridPage (new,add,deleteCurrent,setCurrent+ ,getCurrentCaption,setCurrentCaption+ ,keyEventHandler,captions+ ,currentSelectionNum,currentSelectionId,pageId+ ,fireSelectionEvent)+ where++import Control.Applicative ((<$>))+import Control.Monad (forM)+import qualified Graphics.UI.WX as WX+import qualified Graphics.UI.WXCore as WXC+import View (View,notebook,current,onGrid)+import Util (justWhen)+import View.Component.Notebook (GridId)+import qualified View.Component.Notebook as Notebook+import View.Component.Grid (Grid)+import qualified View.Component.Grid as Grid+import View.Modes (gridMode)++new :: View -> IO (GridId,Grid)+new view = do+ grid <- Grid.new (notebook view) []+ id <- WXC.windowGetId grid+ return (id,grid)++add :: Maybe String -> Grid -> View -> IO ()+add maybeCaption grid view = + let nb = notebook view+ in do + caption <- case maybeCaption of+ Nothing -> + do id <- show . abs <$> WXC.windowGetId grid+ return $ unwords ["unnamed","[" ++ id ++ "]"]+ Just c -> return c+ Notebook.addGrid grid caption nb+ Notebook.currentSelectionNum nb >>= (\(Just n) -> setCurrent n view)++deleteCurrent :: View -> IO ()+deleteCurrent view = do+ currentStuff <- WX.varGet $ current view+ justWhen currentStuff $ \_ -> + do Notebook.deleteCurrentView $ notebook view+ next <- Notebook.currentSelectionNum $ notebook view+ case next of+ Nothing -> WX.varSet (current view) Nothing+ Just n -> setCurrent n view++setCurrent :: Int -> View -> IO ()+setCurrent n view = do+ grid <- Notebook.getGrid n $ notebook view+ WX.varSet (current view) $ Just grid+ gridMode view++fireSelectionEvent :: Int -> View -> IO ()+fireSelectionEvent n view = do+ _ <- WXC.notebookSetSelection (notebook view) n+ return ()++setCurrentCaption :: String -> View -> IO ()+setCurrentCaption c = Notebook.setCurrentCaption c . notebook++getCurrentCaption :: View -> IO String+getCurrentCaption = Notebook.getCurrentCaption . notebook++captions :: View -> IO [String]+captions view = do+ n <- WXC.notebookGetPageCount $ notebook view+ forM [0..n-1] $ WXC.notebookGetPageText $ notebook view++currentSelectionNum :: View -> IO (Maybe Int)+currentSelectionNum = Notebook.currentSelectionNum . notebook++currentSelectionId :: View -> IO (Maybe Int)+currentSelectionId = Notebook.currentSelectionId . notebook++pageId :: Int -> View -> IO Int+pageId n = Notebook.pageId n . notebook++keyEventHandler :: View -> WXC.EventKey -> IO ()+keyEventHandler view (WXC.EventKey key modifier _) = do+ case key of + WXC.KeyReturn -> + do isEnabled <- onGrid view WXC.gridIsCellEditControlEnabled+ onGrid view $ if isEnabled + then flip WXC.gridEnableCellEditControl False+ else flip WXC.gridEnableCellEditControl True+ WXC.KeyTab -> + case modifier of+ WXC.Modifiers {WXC.controlDown=True} ->+ WXC.notebookAdvanceSelection (notebook view) True+ _ -> onGrid view Grid.cycleCursor+ WXC.KeyLeft -> WXC.propagateEvent+ WXC.KeyRight -> WXC.propagateEvent+ WXC.KeyUp -> WXC.propagateEvent+ WXC.KeyDown -> WXC.propagateEvent+ _ -> return ()
+ src/View/Modes.hs view
@@ -0,0 +1,22 @@+module View.Modes (formulaMode,gridMode)+ where++import qualified Graphics.UI.WX as WX+import qualified Graphics.UI.WXCore as WXC+import View (View,onGrid,formulaInput)+import CellCoordinate (CellCoord)++formulaMode :: CellCoord -> String -> View -> IO ()+formulaMode (row,col) value view = + onGrid view $ \grid -> do+ WXC.textCtrlSetValue (formulaInput view) value+ WXC.gridSelectBlock grid row col row col False+ WXC.windowEnable $ formulaInput view+ WX.focusOn $ formulaInput view++gridMode :: View -> IO ()+gridMode view = + onGrid view $ \grid -> do+ WXC.gridSelectBlock grid (-1) (-1) (-1) (-1) False+ WXC.windowDisable $ formulaInput view+ WX.focusOn grid