commit 128a49dddedb7525323ca55a683c9696fa3ee2fc Author: 依瑪貓 Date: Mon Nov 30 00:55:43 2020 +0800 Initial commit. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..595d31b --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +build +.idea +*.iml +calcmosaic.jar diff --git a/HISTORY b/HISTORY new file mode 100644 index 0000000..f08b072 --- /dev/null +++ b/HISTORY @@ -0,0 +1,69 @@ +The story of CalcMosaic: + +=================================================================== + Villeroy's work inspired me the idea to pass the color value to +the BASIC macros in order to run faster. + +http://forum.openoffice.org/en/forum/viewtopic.php?f=49&t=55857 + + But in the end I still cannot understand Villeroy's code. He +uses styles which I believe is faster. But it runs in a different +way than my Calc Mosaic. I cannot adapt his method. Also, I really +can not understand how the data work for his horse. He is so smart. + + Here is my own version. It is 8 times faster than the previous +version. Now I learned how to inject and run OpenOffice BASIC macros +with Java UNO. I also learned that each Java UNO command triggers a +display refresh, which is costly. OpenOffice BASIC macros does not +trigger refresh, which is why it is faster. + + Thanks for the people in the OpenOffice track of ApacheCon EU +2012, that I have a chance to review my code, rewrite it and +demostrate it. + +imacat ^_*' +imacat@mail.imacat.idv.tw +2012-10-08 + +=================================================================== + The origin of this is a video "Stop-Motion Excel" posted on +YouTube on 2012-08-09. + +http://www.youtube.com/watch?v=Fq9EV2fYF2E + + This video quickly caught the media discusussion. Not only +because it is cool, but also that viewers found the author is not +using Excel, but OpenOffice Calc. This draw a discussion in the +OpenOffice development list, too. After watching the video, I feel I +can do it programmatically. + +http://www.youtube.com/watch?v=AlA7HFwxDSU + + (The original Apple iPod Ad) + +http://www.youtube.com/watch?v=NlHUz99l-eo + + And then I presented it in the lightning talk in COSCUP 2012 +Taiwan a week after, on 2012-08-18. + +http://www.youtube.com/watch?v=tEhz0zNmTFQ + + The files used in the presentation is here for download. ^_*' + +http://people.apache.org/~imacat/stop-motion-calc.zip + + There is a hidden black button at A1 of the first sheet. Click +on the button to start the animation. + + It seems that the talk without talk and the fresh-typed +presentation is too cool that many people did not notice what I +presented. :p But it helps to fill up the room when I presented an +introduction to Apache OpenOffice 3.4 the next day. + + Welcome to download and try Calc Mosaic. + + Hope you like it! Your comment is welcome! ^_*' + +imacat ^_*' +imacat@mail.imacat.idv.tw +2012-08-24 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..3007bca --- /dev/null +++ b/LICENSE @@ -0,0 +1,611 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + + +APACHE HTTP SERVER SUBCOMPONENTS: + +The Apache HTTP Server includes a number of subcomponents with +separate copyright notices and license terms. Your use of the source +code for the these subcomponents is subject to the terms and +conditions of the following licenses. + +For the mod_mime_magic component: + +/* + * mod_mime_magic: MIME type lookup via file magic numbers + * Copyright (c) 1996-1997 Cisco Systems, Inc. + * + * This software was submitted by Cisco Systems to the Apache Group in July + * 1997. Future revisions and derivatives of this source code must + * acknowledge Cisco Systems as the original contributor of this module. + * All other licensing and usage conditions are those of the Apache Group. + * + * Some of this code is derived from the free version of the file command + * originally posted to comp.sources.unix. Copyright info for that program + * is included below as required. + * --------------------------------------------------------------------------- + * - Copyright (c) Ian F. Darwin, 1987. Written by Ian F. Darwin. + * + * This software is not subject to any license of the American Telephone and + * Telegraph Company or of the Regents of the University of California. + * + * Permission is granted to anyone to use this software for any purpose on any + * computer system, and to alter it and redistribute it freely, subject to + * the following restrictions: + * + * 1. The author is not responsible for the consequences of use of this + * software, no matter how awful, even if they arise from flaws in it. + * + * 2. The origin of this software must not be misrepresented, either by + * explicit claim or by omission. Since few users ever read sources, credits + * must appear in the documentation. + * + * 3. Altered versions must be plainly marked as such, and must not be + * misrepresented as being the original software. Since few users ever read + * sources, credits must appear in the documentation. + * + * 4. This notice may not be removed or altered. + * ------------------------------------------------------------------------- + * + */ + + +For the modules\mappers\mod_imagemap.c component: + + "macmartinized" polygon code copyright 1992 by Eric Haines, erich@eye.com + +For the server\util_md5.c component: + +/************************************************************************ + * NCSA HTTPd Server + * Software Development Group + * National Center for Supercomputing Applications + * University of Illinois at Urbana-Champaign + * 605 E. Springfield, Champaign, IL 61820 + * httpd@ncsa.uiuc.edu + * + * Copyright (C) 1995, Board of Trustees of the University of Illinois + * + ************************************************************************ + * + * md5.c: NCSA HTTPd code which uses the md5c.c RSA Code + * + * Original Code Copyright (C) 1994, Jeff Hostetler, Spyglass, Inc. + * Portions of Content-MD5 code Copyright (C) 1993, 1994 by Carnegie Mellon + * University (see Copyright below). + * Portions of Content-MD5 code Copyright (C) 1991 Bell Communications + * Research, Inc. (Bellcore) (see Copyright below). + * Portions extracted from mpack, John G. Myers - jgm+@cmu.edu + * Content-MD5 Code contributed by Martin Hamilton (martin@net.lut.ac.uk) + * + */ + + +/* these portions extracted from mpack, John G. Myers - jgm+@cmu.edu */ +/* (C) Copyright 1993,1994 by Carnegie Mellon University + * All Rights Reserved. + * + * Permission to use, copy, modify, distribute, and sell this software + * and its documentation for any purpose is hereby granted without + * fee, provided that the above copyright notice appear in all copies + * and that both that copyright notice and this permission notice + * appear in supporting documentation, and that the name of Carnegie + * Mellon University not be used in advertising or publicity + * pertaining to distribution of the software without specific, + * written prior permission. Carnegie Mellon University makes no + * representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied + * warranty. + * + * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO + * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE + * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS + * SOFTWARE. + */ + +/* + * Copyright (c) 1991 Bell Communications Research, Inc. (Bellcore) + * + * Permission to use, copy, modify, and distribute this material + * for any purpose and without fee is hereby granted, provided + * that the above copyright notice and this permission notice + * appear in all copies, and that the name of Bellcore not be + * used in advertising or publicity pertaining to this + * material without the specific, prior written permission + * of an authorized representative of Bellcore. BELLCORE + * MAKES NO REPRESENTATIONS ABOUT THE ACCURACY OR SUITABILITY + * OF THIS MATERIAL FOR ANY PURPOSE. IT IS PROVIDED "AS IS", + * WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. + */ + +For the srclib\apr\include\apr_md5.h component: +/* + * This is work is derived from material Copyright RSA Data Security, Inc. + * + * The RSA copyright statement and Licence for that original material is + * included below. This is followed by the Apache copyright statement and + * licence for the modifications made to that material. + */ + +/* Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All + rights reserved. + + License to copy and use this software is granted provided that it + is identified as the "RSA Data Security, Inc. MD5 Message-Digest + Algorithm" in all material mentioning or referencing this software + or this function. + + License is also granted to make and use derivative works provided + that such works are identified as "derived from the RSA Data + Security, Inc. MD5 Message-Digest Algorithm" in all material + mentioning or referencing the derived work. + + RSA Data Security, Inc. makes no representations concerning either + the merchantability of this software or the suitability of this + software for any particular purpose. It is provided "as is" + without express or implied warranty of any kind. + + These notices must be retained in any copies of any part of this + documentation and/or software. + */ + +For the srclib\apr\passwd\apr_md5.c component: + +/* + * This is work is derived from material Copyright RSA Data Security, Inc. + * + * The RSA copyright statement and Licence for that original material is + * included below. This is followed by the Apache copyright statement and + * licence for the modifications made to that material. + */ + +/* MD5C.C - RSA Data Security, Inc., MD5 message-digest algorithm + */ + +/* Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All + rights reserved. + + License to copy and use this software is granted provided that it + is identified as the "RSA Data Security, Inc. MD5 Message-Digest + Algorithm" in all material mentioning or referencing this software + or this function. + + License is also granted to make and use derivative works provided + that such works are identified as "derived from the RSA Data + Security, Inc. MD5 Message-Digest Algorithm" in all material + mentioning or referencing the derived work. + + RSA Data Security, Inc. makes no representations concerning either + the merchantability of this software or the suitability of this + software for any particular purpose. It is provided "as is" + without express or implied warranty of any kind. + + These notices must be retained in any copies of any part of this + documentation and/or software. + */ +/* + * The apr_md5_encode() routine uses much code obtained from the FreeBSD 3.0 + * MD5 crypt() function, which is licenced as follows: + * ---------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * wrote this file. As long as you retain this notice you + * can do whatever you want with this stuff. If we meet some day, and you think + * this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp + * ---------------------------------------------------------------------------- + */ + +For the srclib\apr-util\crypto\apr_md4.c component: + + * This is derived from material copyright RSA Data Security, Inc. + * Their notice is reproduced below in its entirety. + * + * Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All + * rights reserved. + * + * License to copy and use this software is granted provided that it + * is identified as the "RSA Data Security, Inc. MD4 Message-Digest + * Algorithm" in all material mentioning or referencing this software + * or this function. + * + * License is also granted to make and use derivative works provided + * that such works are identified as "derived from the RSA Data + * Security, Inc. MD4 Message-Digest Algorithm" in all material + * mentioning or referencing the derived work. + * + * RSA Data Security, Inc. makes no representations concerning either + * the merchantability of this software or the suitability of this + * software for any particular purpose. It is provided "as is" + * without express or implied warranty of any kind. + * + * These notices must be retained in any copies of any part of this + * documentation and/or software. + */ + +For the srclib\apr-util\include\apr_md4.h component: + + * + * This is derived from material copyright RSA Data Security, Inc. + * Their notice is reproduced below in its entirety. + * + * Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All + * rights reserved. + * + * License to copy and use this software is granted provided that it + * is identified as the "RSA Data Security, Inc. MD4 Message-Digest + * Algorithm" in all material mentioning or referencing this software + * or this function. + * + * License is also granted to make and use derivative works provided + * that such works are identified as "derived from the RSA Data + * Security, Inc. MD4 Message-Digest Algorithm" in all material + * mentioning or referencing the derived work. + * + * RSA Data Security, Inc. makes no representations concerning either + * the merchantability of this software or the suitability of this + * software for any particular purpose. It is provided "as is" + * without express or implied warranty of any kind. + * + * These notices must be retained in any copies of any part of this + * documentation and/or software. + */ + + +For the srclib\apr-util\test\testmd4.c component: + + * + * This is derived from material copyright RSA Data Security, Inc. + * Their notice is reproduced below in its entirety. + * + * Copyright (C) 1990-2, RSA Data Security, Inc. Created 1990. All + * rights reserved. + * + * RSA Data Security, Inc. makes no representations concerning either + * the merchantability of this software or the suitability of this + * software for any particular purpose. It is provided "as is" + * without express or implied warranty of any kind. + * + * These notices must be retained in any copies of any part of this + * documentation and/or software. + */ + +For the srclib\apr-util\xml\expat\conftools\install-sh component: + +# +# install - install a program, script, or datafile +# This comes from X11R5 (mit/util/scripts/install.sh). +# +# Copyright 1991 by the Massachusetts Institute of Technology +# +# Permission to use, copy, modify, distribute, and sell this software and its +# documentation for any purpose is hereby granted without fee, provided that +# the above copyright notice appear in all copies and that both that +# copyright notice and this permission notice appear in supporting +# documentation, and that the name of M.I.T. not be used in advertising or +# publicity pertaining to distribution of the software without specific, +# written prior permission. M.I.T. makes no representations about the +# suitability of this software for any purpose. It is provided "as is" +# without express or implied warranty. +# + +For the srclib\pcre\install-sh component: + +# +# Copyright 1991 by the Massachusetts Institute of Technology +# +# Permission to use, copy, modify, distribute, and sell this software and its +# documentation for any purpose is hereby granted without fee, provided that +# the above copyright notice appear in all copies and that both that +# copyright notice and this permission notice appear in supporting +# documentation, and that the name of M.I.T. not be used in advertising or +# publicity pertaining to distribution of the software without specific, +# written prior permission. M.I.T. makes no representations about the +# suitability of this software for any purpose. It is provided "as is" +# without express or implied warranty. + +For the pcre component: + +PCRE LICENCE +------------ + +PCRE is a library of functions to support regular expressions whose syntax +and semantics are as close as possible to those of the Perl 5 language. + +Release 5 of PCRE is distributed under the terms of the "BSD" licence, as +specified below. The documentation for PCRE, supplied in the "doc" +directory, is distributed under the same terms as the software itself. + +Written by: Philip Hazel + +University of Cambridge Computing Service, +Cambridge, England. Phone: +44 1223 334714. + +Copyright (c) 1997-2004 University of Cambridge +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of the University of Cambridge nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +End PCRE LICENCE + + +For the test\zb.c component: + +/* ZeusBench V1.01 + =============== + +This program is Copyright (C) Zeus Technology Limited 1996. + +This program may be used and copied freely providing this copyright notice +is not removed. + +This software is provided "as is" and any express or implied waranties, +including but not limited to, the implied warranties of merchantability and +fitness for a particular purpose are disclaimed. In no event shall +Zeus Technology Ltd. be liable for any direct, indirect, incidental, special, +exemplary, or consequential damaged (including, but not limited to, +procurement of substitute good or services; loss of use, data, or profits; +or business interruption) however caused and on theory of liability. Whether +in contract, strict liability or tort (including negligence or otherwise) +arising in any way out of the use of this software, even if advised of the +possibility of such damage. + + Written by Adam Twiss (adam@zeus.co.uk). March 1996 + +Thanks to the following people for their input: + Mike Belshe (mbelshe@netscape.com) + Michael Campanella (campanella@stevms.enet.dec.com) + +*/ + +For the expat xml parser component: + +Copyright (c) 1998, 1999, 2000 Thai Open Source Software Center Ltd + and Clark Cooper + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +==================================================================== diff --git a/NEWS b/NEWS new file mode 100644 index 0000000..63fdd47 --- /dev/null +++ b/NEWS @@ -0,0 +1,70 @@ +NEWS in calcmosaic + +2.1.0 + * Better user interface processing: Added an icon, shortcut keys, + button focus, enter key, the number of chosen files, etc. + * Added information and WinJava64.reg for the problem running on + MS Windows 64-bit with 64-bit Java JRE. See: + https://issues.apache.org/ooo/show_bug.cgi?id=121258 + +2.0.0 + * You can run calcmosaic.jar from the file manager. An + option dialog was added to obtain the options with GUI. + * You can now run in the 0.x mode that paint the mosaic sheet cell + by cell, for presentation, by using the -slow switch. + * Better localized in Traditional Chinese. + * Errors are better handled. Error messages are shown in either + STDERR or message box automatically. Errors reading from + non-image files are handled. + * The end-of-run message will not block for the user input when + running from console. + * An API to return mosaic color data is added. + * Many minor fixes. + +1.0.3 + * Fixed build.xml includeantruntime warning. + * Changed case of thisComponent to ThisComponent. + +1.0.2 + * Fixed Javadoc problems. + +1.0.1 + * Removed unused algorithms to calculate the mosaic cell color. + +1.0.0 + * Rewritten to be faster (about 8 times faster). Most of + program is now OpenOffice BASIC. The Java program actually + injects OpenOffice BASIC macros into the document and run it. + Unlike Java UNO, OpenOffice BASIC does not refresh the display + for each UNO command. The execution is now a LOT faster. + * Added the play script and the start buttons, to help playing + the spreadsheet stop-motion. + +0.2.1 + * Added a maximum check for the number of images used. This + users from errors after hours of work of conversion. + * Added HISTORY for the story behind Calc Mosaic. + * Added explanation for the FFMPEG command to use for grabbing + snapshots. + +0.2.0 + * Added the ability to convert multiple images. + * Added the ability to calculate the mosaic cell size + automatically. + * Added command line options and help. + * Changed the option mosaic cell size to maximum number of + rows to use, to make it easier (and more meaningful) to + the users. + * Changed the algorithm to calculate the color of the mosaic + cell, to have sharper result on the edges. + +0.1.2 + * 0.1.1 mistakenly packaged 0.1.0 along with 0.1.1, and this is + fixed. + +0.1.1 + * License changed from GPLv3 to Apache License v2. + +0.1.0 + * Initial release + diff --git a/README b/README new file mode 100644 index 0000000..813d41e --- /dev/null +++ b/README @@ -0,0 +1,66 @@ +Calc Mosaic README + + +INTRODUCTION + + Calc Mosaic is a program to create mosaic art from images with +OpenOffice Calc. This can be used to create cool stop motion movies. + + See HISTORY for the story behind Calc Mosaic. + + +COMPILATION + + You need JDK, Apache ANT and OpenOffice SDK in order to compile +this program. + + You need juh.jar, jurt.jar, ridl.jar and unoil.jar in order to +compile the source. They can be found in OpenOffice SDK. + +${OpenOffice Installation}/ure/share/java/juh.jar +${OpenOffice Installation}/ure/share/java/jurt.jar +${OpenOffice Installation}/ure/share/java/ridl.jar +${OpenOffice Installation}/basis3.4/program/classes/unoil.jar + + Just copy them into the lib/ directory and they will work. Also +You may want to review the property value of ooo.dir.sdk-classes in +build.xml. + + To compile, simply type "ant compile". Type "ant jar" will +create the jar file. Type "ant help" for more information. + + Alternatively, there is a pre-compiled jar file here within +the distribution. + + +USAGE + + Type "java -jar calcmosaic.jar -help" to display a simple help. + + The snapshots of a video can be grabbed using ffmpeg: + +ffmpeg -i video.mp4 -r -sameq -y -f image2 img%04d.jpg + + Note that a spreadsheet document can only contain at most 256 +spreadsheets. + + Save file first for the macro to work. + + Hit the start button at A1 of the first and last sheet to play +the sheets. + + You can set the break time (in milliseconds) between sheets at +B1 of the first sheet (default 100 ms). + + You can set the background music at C1 of the first sheet. This +only works on Linux and you must install mplayer. Relative file path +is relative to the document file. Play once before your live show to +avoid the I/O delay with cache. + + If you found problem running on Windows X64, see Windows-x64.txt. + + Hope you like it! ^_*' + +imacat ^_*' +imacat@mail.imacat.idv.tw +2012-08-24 diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..7ec1d6d --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +2.1.0 diff --git a/WinJava64.reg b/WinJava64.reg new file mode 100644 index 0000000..1fb338d Binary files /dev/null and b/WinJava64.reg differ diff --git a/Windows-x64.txt b/Windows-x64.txt new file mode 100644 index 0000000..e935a90 --- /dev/null +++ b/Windows-x64.txt @@ -0,0 +1,42 @@ +Information running on Windows x64 + + If you are running on MS Windows x64, you might not be able to +run calcmosaic.jar just by double-clicking it. If you go to the +command line and run: + +java -jar calcmosaic.jar + + You may see the following error message + +Exception in thread "main" java.lang.UnsatisfiedLinkError: +C:\Users\\AppData\Local\Temp\unowinreg1072712583236131558.dll: +Can't load IA 32-bit .dll on a AMD 64-bit platform + + (If you do not experience such problem, that is fine.) + + The reason behind this is that, the unowinreg.dll shipped with +OpenOffice SDK to find and run OpenOffice has only a 32-bit version, +which cannot be run with your 64-bit Java VM. Currently 64-bit +unowinreg.dll is not available yet. This has already been reported: + +https://issues.apache.org/ooo/show_bug.cgi?id=121258 + + You may try one of the following workarounds: + + 1. Install 32-bit Java JRE on top of your 64-bit Java JRE. + + 2. If you already have 32-bit Java JRE and 64-bit Java JRE installed + together, with 64-bit Java JRE on top of 32-bit Java JRE, you + may run from the command line with full path: + +"C:\Program Files (x86)\Java\jre7\bin\java.exe" -jar calcmosaic.jar + + Alternatively, you may install WinJava64.reg that come with + this package. This will change the file association so that + .jar files will be run with 32-bit Java JRE in the future. + + Thanks to 李振 goodser456@hotmail.com for helping me notice +this problem. + +imacat +2012/10/29 diff --git a/build.xml b/build.xml new file mode 100644 index 0000000..e8e4da5 --- /dev/null +++ b/build.xml @@ -0,0 +1,140 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/images/calcmosaic.png b/images/calcmosaic.png new file mode 100644 index 0000000..830d994 Binary files /dev/null and b/images/calcmosaic.png differ diff --git a/lib/juh.jar b/lib/juh.jar new file mode 100644 index 0000000..df71cda Binary files /dev/null and b/lib/juh.jar differ diff --git a/lib/jurt.jar b/lib/jurt.jar new file mode 100644 index 0000000..e281642 Binary files /dev/null and b/lib/jurt.jar differ diff --git a/lib/ridl.jar b/lib/ridl.jar new file mode 100644 index 0000000..bfec876 Binary files /dev/null and b/lib/ridl.jar differ diff --git a/lib/unoil.jar b/lib/unoil.jar new file mode 100644 index 0000000..7f27833 Binary files /dev/null and b/lib/unoil.jar differ diff --git a/src/tw/idv/imacat/calcmosaic/CalcMosaic.java b/src/tw/idv/imacat/calcmosaic/CalcMosaic.java new file mode 100644 index 0000000..09354c0 --- /dev/null +++ b/src/tw/idv/imacat/calcmosaic/CalcMosaic.java @@ -0,0 +1,877 @@ +/* Copyright (c) 2012 imacat + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * CalcMosaic + * + * Created on 2012-08-12 + * + * Copyright (c) 2012 imacat + */ + +package tw.idv.imacat.calcmosaic; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Image; +import java.awt.image.BufferedImage; +import java.awt.Toolkit; +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import java.util.ResourceBundle; +import javax.imageio.ImageIO; +import javax.swing.JFrame; +import javax.swing.JOptionPane; + +/** + * A program to create mosaic art from images with Calc. + * + * @author imacat + * @version 2.1.0 + */ +public class CalcMosaic { + + /** The title of the application. */ + public static final String APP_NAME = "Calc Moasic"; + + /** The title of the application. */ + private static final String ICON = "images/calcmosaic.png"; + + /** The version number. */ + private static final String VERSION = "2.1.0"; + + /** The name of the jar file. */ + private static final String JAR_NAME = "calcmosaic.jar"; + + /** The module name of the BASIC playing macros. */ + private static final String playModule = "_0Play"; + + /** The module name of the BASIC macros. */ + private static final String basicModule = "_1CalcMosaic"; + + /** The BASIC macros to play the mosaic art. */ + private static final String playMacros = +"' Copyright (c) 2012 imacat.\n" ++ "' \n" ++ "' Licensed under the Apache License, Version 2.0 (the \"License\");\n" ++ "' you may not use this file except in compliance with the License.\n" ++ "' You may obtain a copy of the License at\n" ++ "' \n" ++ "' http://www.apache.org/licenses/LICENSE-2.0\n" ++ "' \n" ++ "' Unless required by applicable law or agreed to in writing, software\n" ++ "' distributed under the License is distributed on an \"AS IS\" BASIS,\n" ++ "' WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n" ++ "' See the License for the specific language governing permissions and\n" ++ "' limitations under the License.\n" ++ "\n" ++ "' _0Play: The BASIC macros to play the spreadsheets\n" ++ "' Created with " + APP_NAME + " version " + VERSION + "\n" ++ "' By imacat , 2012/10/5\n" ++ "\n" ++ "Option Explicit\n" ++ "\n" ++ "' The default background music, related to the document file\n" ++ "Const sDefaultMusic = \"\"\n" ++ "' The default break time between sheets (ms)\n" ++ "Const nDefaultBreak = 100\n" ++ "\n" ++ "' subPlaySheets: Plays the spreadsheets.\n" ++ "' 1. Save file first for the macro to work.\n" ++ "' 2. Hit the start button at A1 of the first\n" ++ "' and last sheet to play the sheets.\n" ++ "' 3. You can set the break time (in\n" ++ "' milliseconds) between sheets at B1 of\n" ++ "' the first sheet (default 100 ms).\n" ++ "' 4. You can set the background music at C1\n" ++ "' of the first sheet. This only works on\n" ++ "' Linux and you must install mplayer.\n" ++ "' Relative file path is relative to the\n" ++ "' document file. Play once before your\n" ++ "' live show to avoid the I/O delay with cache.\n" ++ "Sub subPlaySheets\n" ++ "\tDim oController As Object, oSheets As Object\n" ++ "\tDim nBreak As Integer, nI As Integer\n" ++ "\tDim nCount As Integer, oSheet As Object\n" ++ "\t\n" ++ "\toController = ThisComponent.getCurrentController\n" ++ "\toSheets = ThisComponent.getSheets\n" ++ "\tnBreak = oSheets.getByIndex (0).getCellByPosition (1, 0).getValue\n" ++ "\tIf nBreak = 0 Then\n" ++ "\t\tnBreak = nDefaultBreak\n" ++ "\tEnd If\n" ++ "\tnCount = oSheets.getCount\n" ++ "\t\n" ++ "\tsubPlayMusic\n" ++ "\t\n" ++ "\tFor nI = 0 To nCount - 1\n" ++ "\t\toSheet = oSheets.getByIndex (nI)\n" ++ "\t\toController.setActiveSheet (oSheet)\n" ++ "\t\tWait nBreak\n" ++ "\tNext nI\n" ++ "End Sub\n" ++ "\n" ++ "' subPlayMusic: Plays the background music.\n" ++ "Sub subPlayMusic\n" ++ "\tDim oSheet As Object, sMusic As String\n" ++ "\tDim sFile As String, nPos As Integer\n" ++ "\t\n" ++ "\t' This only works on Linux and you must install mplayer.\n" ++ "\tIf Not FileExists (\"/usr/bin/mplayer\") Then\n" ++ "\t\tExit Sub\n" ++ "\tEnd if\n" ++ "\t\n" ++ "\toSheet = ThisComponent.getSheets.getByIndex (0)\n" ++ "\tsMusic = oSheet.getCellByPosition (2, 0).getString\n" ++ "\tIf sMusic = \"\" Then\n" ++ "\t\tsMusic = sDefaultMusic\n" ++ "\tEnd If\n" ++ "\tIf sMusic = \"\" Then\n" ++ "\t\tExit Sub\n" ++ "\tEnd If\n" ++ "\t\n" ++ "\t' Relative file path is relative to the document file.\n" ++ "\tIf Left (sMusic, 1) <> \"/\" Then\n" ++ "\t\tsFile = ConvertFromURL (ThisComponent.getLocation)\n" ++ "\t\tIf sFile = \"\" Then\n" ++ "\t\t\tExit Sub\n" ++ "\t\tEnd If\n" ++ "\t\tnPos = Len (sFile)\n" ++ "\t\tDo While Mid (sFile, nPos, 1) <> \"/\"\n" ++ "\t\t\tnPos = nPos - 1\n" ++ "\t\tLoop\n" ++ "\t\tsMusic = Left (sFile, nPos) & sMusic\n" ++ "\tEnd If\n" ++ "\t\n" ++ "\tShell \"mplayer\", 6, \"\"\"\" & sMusic & \"\"\"\", False\n" ++ "End Sub\n"; + + /** The BASIC macros to convert color numbers to mosaic art. */ + private static final String basicMacros = +"' Copyright (c) 2012 imacat\n" ++ "' \n" ++ "' Licensed under the Apache License, Version 2.0 (the \"License\");\n" ++ "' you may not use this file except in compliance with the License.\n" ++ "' You may obtain a copy of the License at\n" ++ "' \n" ++ "' http://www.apache.org/licenses/LICENSE-2.0\n" ++ "' \n" ++ "' Unless required by applicable law or agreed to in writing, software\n" ++ "' distributed under the License is distributed on an \"AS IS\" BASIS,\n" ++ "' WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n" ++ "' See the License for the specific language governing permissions and\n" ++ "' limitations under the License.\n" ++ "\n" ++ "' _1CalcMosaic: The BASIC macros of Calc Mosaic\n" ++ "' Created with " + APP_NAME + " version " + VERSION + "\n" ++ "' By imacat , 2012/10/5\n" ++ "\n" ++ "Option Explicit\n" ++ "\n" ++ "' subCreateMosaic: Creates the spreadsheets of mosaic art\n" ++ "Sub subCreateMosaic (mData () As Object)\n" ++ "\tDim mNames (UBound (mData (0))) As String\n" ++ "\tDim mColors (UBound (mData (1))) As Object\n" ++ "\tDim oSheet As Object, nI As Integer\n" ++ "\t\n" ++ "\t' Adds the spreadsheets and allocates memory first, to prevent\n" ++ "\t' memory corruption when the the number of sheets goes large.\n" ++ "\tmNames = mData (0)\n" ++ "\tmColors = mData (1)\n" ++ "\tsubInitializeSheets (mNames)\n" ++ "\tFor nI = 0 To UBound (mNames)\n" ++ "\t\tsubCreateMosaicSheet (mNames (nI), mColors (nI))\n" ++ "\tNext nI\n" ++ "\t\n" ++ "\tsubAddMacroTriggers\n" ++ "End Sub\n" ++ "\n" ++ "' subCreateMosaicSheet: Creates a spreadsheet of mosaic art with\n" ++ "' the color values\n" ++ "Sub subCreateMosaicSheet (sSheet As String, mColors () As Object)\n" ++ "\tDim oSheet As Object, oRange As Object, oCell As Object\n" ++ "\tDim nRow As Integer, nColumn As Integer, nWidth As Integer\n" ++ "\t\n" ++ "\toSheet = ThisComponent.getSheets.getByName (sSheet)\n" ++ "\toRange = oSheet.getCellRangeByPosition ( _\n" ++ "\t\t0, 0, UBound (mColors (0)), UBound (mColors))\n" ++ "\tFor nRow = 0 To UBound (mColors)\n" ++ "\t\tFor nColumn = 0 To UBound (mColors (0))\n" ++ "\t\t\toCell = oRange.getCellByPosition (nColumn, nRow)\n" ++ "\t\t\toCell.setPropertyValue (\"CellBackColor\", _\n" ++ "\t\t\t\tCLng (mColors (nRow)(nColumn)))\n" ++ "\t\tNext nColumn\n" ++ "\tNext nRow\n" ++ "\t\n" ++ "\t' Shortly displays the sheet. Removing these can save the\n" ++ "\t' display refreshing time and run slightly faster, with\n" ++ "\t' the cost of not seeing the progress and looking like hang.\n" ++ "\tnWidth = oRange.getRows.getPropertyValue (\"Height\")\n" ++ "\toRange.getColumns.setPropertyValue (\"Width\", nWidth)\n" ++ "\tThisComponent.getCurrentController.setActiveSheet (oSheet)\n" ++ "\tWait 1\n" ++ "End Sub\n" ++ "\n" ++ "' subInitializeSheets: Initialize the spreadsheets\n" ++ "Sub subInitializeSheets (mNames () As Object)\n" ++ "\tDim oSheets As Object, nI As Integer, mExisting () As String\n" ++ "\t\n" ++ "\toSheets = ThisComponent.getSheets\n" ++ "\tIf oSheets.hasByName (mNames (0)) Then\n" ++ "\t\tIf oSheets.getCount = 1 Then\n" ++ "\t\t\toSheets.insertNewByName (mNames (0) & 1, 0)\n" ++ "\t\tEnd If\n" ++ "\t\toSheets.removeByName (mNames (0))\n" ++ "\tEnd If\n" ++ "\tmExisting = oSheets.getElementNames\n" ++ "\toSheets.insertNewByName (mNames (0), 0)\n" ++ "\tFor nI = 0 To UBound (mExisting)\n" ++ "\t\toSheets.removeByName (mExisting (nI))\n" ++ "\tNext nI\n" ++ "\tFor nI = 1 To UBound (mNames)\n" ++ "\t\toSheets.insertNewByName (mNames (nI), nI)\n" ++ "\tNext nI\n" ++ "End Sub\n" ++ "\n" ++ "' fnGetEmptyStringArray: Returns an empty array of String.\n" ++ "Function fnGetEmptyStringArray (nUBound As Integer)\n" ++ "\tDim mArray (nUBound) As String\n" ++ "\t\n" ++ "\tfnGetEmptyStringArray = mArray\n" ++ "End Function\n" ++ "\n" ++ "' subAddMacroTriggers: Adds the macro trigggers to show the sheets\n" ++ "Sub subAddMacroTriggers\n" ++ "\tDim oSheets As Object, oSheet As Object\n" ++ "\tDim oCell As Object, nColor As Long\n" ++ "\t\n" ++ "\toSheets = ThisComponent.getSheets\n" ++ "\tsubAddStartButton (oSheets.getByIndex (0))\n" ++ "\t' Adds a start button at the end. Last time (2012/8/18 on\n" ++ "\t' COSCUP 2012 lightning talk) I tested playing and ended at\n" ++ "\t' the last sheet. When talk started I did not notice it and\n" ++ "\t' spent quite some time finding the button. Audience got\n" ++ "\t' anxious and even lost attention when the show begins.\n" ++ "\tsubAddStartButton (oSheets.getByIndex (oSheets.getCount - 1))\n" ++ "\t\n" ++ "\toSheet = ThisComponent.getSheets.getByIndex (0)\n" ++ "\tThisComponent.getCurrentController.setActiveSheet (oSheet)\n" ++ "\toCell = oSheet.getCellByPosition (1, 0)\n" ++ "\tnColor = oCell.getPropertyValue (\"CellBackColor\")\n" ++ "\toCell.setPropertyValue (\"CharColor\", nColor)\n" ++ "\toCell = oSheet.getCellByPosition (2, 0)\n" ++ "\tnColor = oCell.getPropertyValue (\"CellBackColor\")\n" ++ "\toCell.setPropertyValue (\"CharColor\", nColor)\n" ++ "End Sub\n" ++ "\n" ++ "' subAddStartButton: Adds a start button to show the sheets\n" ++ "Sub subAddStartButton (oSheet As Object)\n" ++ "\tDim oPage As Object, oShape As Object, oButton As Object\n" ++ "\tDim nSize As Integer, nColor As Long\n" ++ "\tDim aPos As New com.sun.star.awt.Point\n" ++ "\tDim aSize As New com.sun.star.awt.Size\n" ++ "\tDim oForm As Object, nIndex As Integer\n" ++ "\tDim aEvent As New com.sun.star.script.ScriptEventDescriptor\n" ++ "\t\n" ++ "\toPage = oSheet.getDrawPage\n" ++ "\tnSize = oSheet.getRows.getPropertyValue (\"Height\")\n" ++ "\t\n" ++ "\toShape = ThisComponent.createInstance ( _\n" ++ "\t\t\"com.sun.star.drawing.ControlShape\")\n" ++ "\taPos.X = 0\n" ++ "\taPos.Y = 0\n" ++ "\toShape.setPosition (aPos)\n" ++ "\taSize.Width = nSize\n" ++ "\taSize.Height = nSize\n" ++ "\toShape.setSize (aSize)\n" ++ "\t\n" ++ "\toButton = ThisComponent.createInstance ( _\n" ++ "\t\t\"com.sun.star.form.component.CommandButton\")\n" ++ "\tnColor = oSheet.getCellByPosition (0, 0).getPropertyValue ( _\n" ++ "\t\t\"CellBackColor\")\n" ++ "\toButton.setPropertyValue (\"BackgroundColor\", nColor)\n" ++ "\t\n" ++ "\toShape.setControl (oButton)\n" ++ "\toPage.add (oShape)\n" ++ "\t\n" ++ "\taEvent.ListenerType = \"XActionListener\"\n" ++ "\taEvent.EventMethod = \"actionPerformed\"\n" ++ "\taEvent.ScriptType = \"Script\"\n" ++ "\taEvent.ScriptCode = \"vnd.sun.star.script:\" _\n" ++ "\t\t& \"Standard." + playModule + ".subPlaySheets\" _\n" ++ "\t\t& \"?language=Basic&location=document\"\n" ++ "\toForm = oButton.getParent\n" ++ "\tnIndex = oForm.getCount - 1\n" ++ "\toForm.registerScriptEvent (nIndex, aEvent)\n" ++ "End Sub\n"; + + /** The localization resources. */ + private ResourceBundle l10n = null; + + /** The window icon. */ + protected static Image icon = null; + + /** The image files. */ + private File files[] = new File[0];; + + /** + * The number of rows to use, default to 0 (estimate it + * automatically). + */ + private int numRows = 0; + + /** Whether we should run in slow motion (for presentation). */ + private boolean isSlow = false; + + /** Should we uses GUI. */ + private boolean isGui = false; + + /** The spreadsheet document to work with. */ + private SpreadsheetDocument document = null; + + /** The available dimension of mosaic cells. */ + private Dimension availCells = null; + + /** + * Creates a new instance of CalcMosaic. + * + * @param args the command line arguments + * @throws IllegalArgumentException if the arguments are + * illegal + */ + private CalcMosaic(String args[]) + throws IllegalArgumentException { + // Obtains the localization resources + this.l10n = ResourceBundle.getBundle( + this.getClass().getPackage().getName() + ".res.L10n"); + this.parseArguments(args); + if (this.isGui) { + icon = Toolkit.getDefaultToolkit().getImage(ICON); + OptionDialog dialog = new OptionDialog( + this.files, this.numRows, this.isSlow); + Options options = dialog.getOptions(); + // Cancelled + if (options == null) { + System.exit(0); + } + this.files = options.files; + this.numRows = options.numRows; + this.isSlow = options.isSlow; + } + } + + /** + * Creates a new instance of CalcMosaic, with the parameters. + * + * @param files The image files + * @param numRows The number of rows to use, default to 0 (estimate it + * automatically) + * @param isSlow Should we create in slow motion (for presentation) + */ + public CalcMosaic(File files[], int numRows, boolean isSlow) { + // Obtains the localization resources + this.l10n = ResourceBundle.getBundle( + this.getClass().getPackage().getName() + ".res.L10n"); + this.files = files; + this.numRows = numRows; + this.isSlow = isSlow; + } + + /** + * Runs CalcMosaic from the command line. + * + * @param args the command line arguments + */ + public static void main(String args[]) { + CalcMosaic mosaic = null; + + try { + mosaic = new CalcMosaic(args); + } catch (IllegalArgumentException e) { + System.err.println(APP_NAME + ": " + e.getMessage()); + System.exit(1); + } + try { + mosaic.create(); + } catch (com.sun.star.comp.helper.BootstrapException e) { + mosaic.showErrorMessage(e.getMessage()); + System.exit(2); + } catch (java.io.IOException e) { + mosaic.showErrorMessage(e.getMessage()); + System.exit(2); + } catch (NotAnImageException e) { + mosaic.showErrorMessage(e.getMessage()); + System.exit(2); + } + mosaic.showMessage(mosaic._("finished")); + System.exit(0); + } + + /** + * Calculates the mosaic color matrix from an image file. + * + * @param image the image + * @param cellSize the size of the mosaic cell + * @return the values of the mosaic color matrix, with Alpha + * channel removed + */ + public static int[][] addMacroTriggers( + BufferedImage image, int cellSize) { + int colors[][] = new int + [(image.getHeight() - 1) / cellSize + 1] + [(image.getWidth() - 1) / cellSize + 1]; + + // Gets the color of each cell + for (int y0 = 0; y0 < image.getHeight(); y0 += cellSize) { + int y1 = y0 + cellSize - 1; + if (y1 >= image.getHeight()) { + y1 = image.getHeight() - 1; + } + for (int x0 = 0; x0 < image.getWidth(); x0 += cellSize) { + int x1 = x0 + cellSize - 1; + if (x1 >= image.getWidth()) { + x1 = image.getWidth() - 1; + } + Color color = getMosaicColor(image, x0, y0, x1, y1); + colors[y0 / cellSize][x0 / cellSize] + = color.getRGB() & 0x00FFFFFF; + } + } + + return colors; + } + + /** + * Obtain the mosaic color of a block in the image. + * + * @param image the image + * @param x0 the x-coordinate of the upper-left corner of the + * block in the image + * @param y0 the y-coordinate of the upper-left corner of the + * block in the image + * @param x1 the x-coordinate of the lower-right corner of the + * block in the image + * @param y1 the y-coordinate of the lower-right corner of the + * block in the image + * @return the mosaic color of the block in the image + */ + public static Color getMosaicColor(BufferedImage image, + int x0, int y0, int x1, int y1) { + /* + * Using linear distance weakening: When calculating the + * average color of this block, the weights of the colors at + * all the pixels are different: Colors of pixels closer to + * the center of the block have a larger weight that those + * colors of pixels far away from center. The weight + * decreases from the center in a linear speed. The resulting + * weights may look like a circled cone. This creates a + * mosaic effect that is more sharp, especially with images of + * only a few colors. Our eye focus stay mostly at the center + * of the block. The colors of the center area outweights the + * skirts area. + */ + + double totalWeight = 0; + double alpha = 0, red = 0, green = 0, blue = 0; + double centerX = (double) (x0 + x1) / 2; + double centerY = (double) (y0 + y1) / 2; + double radius = 1 + Math.sqrt( + (x0 - centerX) * (x0 - centerX) + + (y0 - centerY) * (y0 - centerY)); + + for (int x = x0; x <= x1; x++) { + for (int y = y0; y <= y1; y++) { + Color color = new Color(image.getRGB(x, y)); + double distance = Math.sqrt( + (x - centerX) * (x - centerX) + + (y - centerY) * (y - centerY)); + double weight = (radius - distance) / radius; + + alpha += color.getAlpha() * weight; + red += color.getRed() * weight; + green += color.getGreen() * weight; + blue += color.getBlue() * weight; + totalWeight += weight; + } + } + alpha /= totalWeight; + red /= totalWeight; + green /= totalWeight; + blue /= totalWeight; + + return new Color((int) red, (int) green, (int) blue, + (int) alpha); + } + + /** + * Parses the command line arguments. + * + * @param args the command line arguments + * @throws IllegalArgumentException if the arguments are + * illegal + */ + private void parseArguments(String args[]) + throws IllegalArgumentException { + List filesList = new ArrayList(); + + for (int i = 0; i < args.length; i++) { + if (args[i].equals("-r") || args[i].equals("-rows")) { + i++; + if (i >= args.length) { + throw new NumberFormatException( + _("err_rows_miss")); + } + try { + this.numRows = Integer.parseInt(args[i]); + } catch (NumberFormatException e) { + throw new NumberFormatException( + args[i - 1] + " \"" + args[i] + "\": " + + _("err_rows_not_pint")); + } + if (this.numRows <= 0) { + throw new NumberFormatException( + args[i - 1] + " \"" + args[i] + "\": " + + _("err_rows_not_pint")); + } + } else if (args[i].startsWith("-r=")) { + try { + this.numRows = Integer.parseInt(args[i].substring(3)); + } catch (NumberFormatException e) { + throw new NumberFormatException( + "\"" + args[i] + "\": " + + _("err_rows_not_pint")); + } + if (this.numRows <= 0) { + throw new NumberFormatException( + "\"" + args[i] + "\": " + + _("err_rows_not_pint")); + } + } else if (args[i].startsWith("-rows=")) { + try { + this.numRows = Integer.parseInt(args[i].substring(6)); + } catch (NumberFormatException e) { + throw new NumberFormatException( + "\"" + args[i] + "\": " + + _("err_rows_not_pint")); + } + if (this.numRows <= 0) { + throw new NumberFormatException( + "\"" + args[i] + "\": " + + _("err_rows_not_pint")); + } + } else if (args[i].equals("-s") || args[i].equals("-slow")) { + this.isSlow = true; + } else if (args[i].equals("-noslow")) { + this.isSlow = false; + } else if (args[i].equals("-h") || args[i].equals("-help")) { + System.out.println(String.format(_("help"), JAR_NAME)); + System.exit(0); + } else if (args[i].equals("-v") || args[i].equals("-version")) { + System.out.println(String.format(_("version"), + APP_NAME, VERSION, _("author"))); + System.exit(0); + } else if (args[i].startsWith("-")) { + throw new IllegalArgumentException( + args[i] + ": " + _("err_arg_unknown")); + } else { + filesList.add(args[i]); + } + } + + // Checks the files. + // This is processed last, after all the options, so that + // -help and -version takes effect before this. + this.files = new File[filesList.size()]; + for (int i = 0; i < this.files.length; i++) { + File file = new File(filesList.get(i)); + + if (!file.exists()) { + throw new IllegalArgumentException( + args[i] + ": " + _("err_file_not_exist")); + } + if (!file.isFile()) { + throw new IllegalArgumentException( + args[i] + ": " + _("err_file_not_a_file")); + } + this.files[i] = file; + } + // Checks the limits of the number of images/spreadsheets. + if (this.files.length > 256) { + throw new IllegalArgumentException( + _("err_file_too_many")); + } + + // Use GUI if no files are supplied. + if (this.files.length == 0) { + this.isGui = true; + } + } + + /** + * Creates mosaic art of images with Calc. + * + * @throws com.sun.star.comp.helper.BootstrapException if fails to + * create the initial component context + * @throws java.io.IOException if an error occurs during reading + * the image + * @throws NotAnImageException if the file read is not an image + */ + private void create() + throws com.sun.star.comp.helper.BootstrapException, + java.io.IOException, + NotAnImageException { + if (this.isSlow) { + this.createSlowly(); + } else { + this.createNormal(); + } + } + + /** + * Creates mosaic art of images with Calc with the normal speed. + * + * @throws com.sun.star.comp.helper.BootstrapException if fails to + * create the initial component context + * @throws java.io.IOException if an error occurs during reading + * the image + * @throws NotAnImageException if the file read is not an image + */ + private void createNormal() + throws com.sun.star.comp.helper.BootstrapException, + java.io.IOException, + NotAnImageException { + OpenOffice office = null; + Object param[] = new Object[1]; + Object data[][] = new Object[2][this.files.length]; + + try { + office = new OpenOffice(); + } catch (com.sun.star.comp.helper.BootstrapException e) { + throw e; + } + this.document = office.startNewSpreadsheetDocument(); + this.document.addBasicModule(playModule, playMacros); + this.document.addBasicModule(basicModule, basicMacros); + for (int i = 0; i < this.files.length; i++) { + data[0][i] = this.files[i].getName(); + try { + data[1][i] = this.addMacroTriggers(this.files[i]); + } catch (java.io.IOException e) { + throw e; + } catch (NotAnImageException e) { + throw e; + } + } + param[0] = data; + this.document.runBasicMacro( + basicModule, "subCreateMosaic", param); + } + + /** + * Creates mosaic art of images with Calc in slow motion. + * + * @throws com.sun.star.comp.helper.BootstrapException if fails to + * create the initial component context + * @throws java.io.IOException if an error occurs during reading + * the image + * @throws NotAnImageException if the file read is not an image + */ + private void createSlowly() + throws com.sun.star.comp.helper.BootstrapException, + java.io.IOException, + NotAnImageException { + OpenOffice office = null; + + try { + office = new OpenOffice(); + } catch (com.sun.star.comp.helper.BootstrapException e) { + throw e; + } + this.document = office.startNewSpreadsheetDocument(); + this.document.addBasicModule(playModule, playMacros); + for (int i = 0; i < this.files.length; i++) { + String name = this.files[i].getName(); + int colors[][] = null; + + try { + colors = this.addMacroTriggers(this.files[i]); + } catch (java.io.IOException e) { + throw e; + } catch (NotAnImageException e) { + throw e; + } + this.document.createMosaicSheet(name, i, colors); + } + this.document.addMacroTriggers(); + } + + /** + * Calculates the mosaic color matrix from an image file. + * + * @param file the source image file + * @return the values of the mosaic color matrix, with Alpha + * channel removed + * @throws java.io.IOException if an error occurs during reading + * the image + * @throws NotAnImageException if the file read is not an image + */ + private int[][] addMacroTriggers(File file) + throws java.io.IOException, + NotAnImageException { + BufferedImage image = null; + int cellSize = -1; + + try { + image = ImageIO.read(file); + } catch (java.io.IOException e) { + throw e; + } + if (image == null) { + throw new NotAnImageException( + file.getName() + ": " + _("err_file_not_an_image")); + } + + cellSize = this.getCellSize(image); + return addMacroTriggers(image, cellSize); + } + + /** + * Returns the mosaic cell size. + * + * @param image the source image + * @return the calculated mosaic cell size + */ + private int getCellSize(BufferedImage image) { + if (this.numRows != 0) { + return (image.getHeight() - 1) / this.numRows + 1; + } + + // Calculate the size that fits the whole image. + int width = 0, height = 0; + + this.estimateAvailableCells(); + width = (image.getWidth() - 1) / this.availCells.width + 1; + height = (image.getHeight() - 1) / this.availCells.height + 1; + return width > height? width: height; + } + + /** + * Estimates the number of rows to use automatically + * + * @param image the source image + * @return the number of rows to use + */ + private int estimateNumRows(BufferedImage image) { + // Calculate the size that fits the whole image. + int width = -1, height = -1, cellSize = -1; + + this.estimateAvailableCells(); + width = (image.getWidth() - 1) / this.availCells.width + 1; + height = (image.getHeight() - 1) / this.availCells.height + 1; + cellSize = width > height? width: height; + return Math.round((float) image.getHeight() / cellSize); + } + + /** + * Estimates the available dimension of the mosaic cells. + * + */ + private void estimateAvailableCells() { + if (this.availCells == null) { + Dimension window = this.document.getWindowSize(); + int rowHeight = this.document.getRowHeight(); + + // Only use the row height, because the height is always + // shorter than the width in newly-created sheets. + this.availCells = new Dimension( + window.width * 25 / rowHeight, + window.height * 25 / rowHeight); + } + } + + /** + * Shows a error message. + * + * @param message the error message to be shown + */ + private void showErrorMessage(String message) { + if (this.isGui) { + JFrame frame = new JFrame(APP_NAME); + Dimension screenSize + = Toolkit.getDefaultToolkit().getScreenSize(); + Dimension frameSize = frame.getSize(); + + frame.setIconImage(icon); + frame.setLocation( + (screenSize.width - frameSize.width) / 2, + (screenSize.height - frameSize.height) / 2 + ); + frame.setVisible(true); + JOptionPane.showMessageDialog(frame, message, + String.format(_("dialog_err_title"), + CalcMosaic.APP_NAME), + JOptionPane.ERROR_MESSAGE); + frame.setVisible(false); + } else { + System.err.println(APP_NAME + ": " + message); + } + } + + /** + * Shows a message. + * + * @param message the message to be shown + */ + private void showMessage(String message) { + if (this.isGui) { + JFrame frame = new JFrame(APP_NAME); + Dimension screenSize + = Toolkit.getDefaultToolkit().getScreenSize(); + Dimension frameSize = frame.getSize(); + + frame.setIconImage(icon); + frame.setLocation( + (screenSize.width - frameSize.width) / 2, + (screenSize.height - frameSize.height) / 2 + ); + frame.setVisible(true); + JOptionPane.showMessageDialog(frame, message, APP_NAME, + JOptionPane.INFORMATION_MESSAGE); + frame.setVisible(false); + } else { + System.out.println(APP_NAME + ": " + message); + } + } + + /** + * Gets a string for the given key from this resource bundle + * or one of its parents. If the key is missing, returns + * the key itself. + * + * @param key the key for the desired string + * @return the string for the given key + */ + private String _(String key) { + try { + return new String( + l10n.getString(key).getBytes("ISO-8859-1"), "UTF-8"); + } catch (java.io.UnsupportedEncodingException e) { + return l10n.getString(key); + } catch (java.util.MissingResourceException e) { + return key; + } + } +} diff --git a/src/tw/idv/imacat/calcmosaic/NotAnImageException.java b/src/tw/idv/imacat/calcmosaic/NotAnImageException.java new file mode 100644 index 0000000..c619418 --- /dev/null +++ b/src/tw/idv/imacat/calcmosaic/NotAnImageException.java @@ -0,0 +1,89 @@ +/* Copyright (c) 2012 imacat + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * NotAnImageException + * + * Created on 2012-10-27, rewritten from CalcMosaic + * + * Copyright (c) 2012 imacat + */ + +package tw.idv.imacat.calcmosaic; + +/** + * An exception thrown when the file read is not an image. + * + * @author imacat + * @version 0.0.1 + */ +public class NotAnImageException extends Exception { + + /** + * Constructs a NotAnImageException exception with + * null as its error detail message. + * + */ + public NotAnImageException() { + super(); + } + + /** + * Constructs a NotAnImageException with the + * specified detail message. + * + * @param message The detail message (which is saved for later + * retrieval by the Throwable.getMessage() method) + */ + public NotAnImageException(String message) { + super(message); + } + + /** + * Constructs an NotAnImageException with the + * specified detail message and cause. + *

+ * Note that the detail message associated with cause + * is not automatically incorporated into this + * exception's detail message. + * + * @param message The detail message (which is saved for later + * retrieval by the Throwable.getMessage() method) + * @param cause The cause (which is saved for later retrieval + * by the Throwable.getCause() method). (A null + * value is permitted, and indicates that the + * cause is nonexistent or unknown.) + */ + public NotAnImageException(String message, Throwable cause) { + super(message, cause); + } + + /** + * Constructs an NotAnImageException with the + * specified cause and a detail message of (cause==null + * ? null : cause.toString()) (which typically contains + * the class and detail message of cause). This + * constructor is useful for IO exceptions that are little more + * than wrappers for other throwables. + * + * @param cause The cause (which is saved for later retrieval + * by the Throwable.getCause() method). (A null + * value is permitted, and indicates that the + * cause is nonexistent or unknown.) + */ + public NotAnImageException(Throwable cause) { + super(cause); + } +} diff --git a/src/tw/idv/imacat/calcmosaic/OpenOffice.java b/src/tw/idv/imacat/calcmosaic/OpenOffice.java new file mode 100644 index 0000000..8dd9847 --- /dev/null +++ b/src/tw/idv/imacat/calcmosaic/OpenOffice.java @@ -0,0 +1,189 @@ +/* Copyright (c) 2012 imacat + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * OpenOffice + * + * Created on 2012-08-16, rewritten from CalcMosaic + * + * Copyright (c) 2012 imacat + */ + +package tw.idv.imacat.calcmosaic; + +import java.util.ResourceBundle; + +import com.sun.star.beans.PropertyValue; +import com.sun.star.beans.XPropertySet; +import com.sun.star.comp.helper.Bootstrap; +import com.sun.star.bridge.XUnoUrlResolver; +import com.sun.star.frame.XComponentLoader; +import com.sun.star.frame.XDesktop; +import com.sun.star.lang.XComponent; +import com.sun.star.lang.XMultiComponentFactory; +import com.sun.star.uno.UnoRuntime; +import com.sun.star.uno.XComponentContext; + +/** + * An OpenOffice instance. + * + * @author imacat + * @version 2.0.0 + */ +public class OpenOffice { + + /** The localization resources. */ + private ResourceBundle l10n = null; + + /** The desktop service. */ + private XDesktop desktop = null; + + /** The bootstrap context. */ + private XComponentContext bootstrapContext = null; + + /** The registry service manager. */ + private XMultiComponentFactory serviceManager = null; + + /** + * Creates a new instance of Office. + * + * @throws com.sun.star.comp.helper.BootstrapException if fails to + * create the initial component context + */ + public OpenOffice() + throws com.sun.star.comp.helper.BootstrapException { + // Obtains the localization resources + this.l10n = ResourceBundle.getBundle( + this.getClass().getPackage().getName() + ".res.L10n"); + try { + this.connect(); + } catch (com.sun.star.comp.helper.BootstrapException e) { + throw e; + } + } + + /** + * Connects to the OpenOffice program. + * + * @throws com.sun.star.comp.helper.BootstrapException if fails to + * create the initial component context + */ + private void connect() + throws com.sun.star.comp.helper.BootstrapException { + XMultiComponentFactory localServiceManager = null; + Object unoUrlResolver = null; + XUnoUrlResolver xUnoUrlResolver = null; + Object service = null; + + if (this.isConnected()) { + return; + } + + // Obtains the local context + try { + this.bootstrapContext = Bootstrap.bootstrap(); + } catch (java.lang.Exception e) { + throw new com.sun.star.comp.helper.BootstrapException(e); + } + if (this.bootstrapContext == null) { + throw new com.sun.star.comp.helper.BootstrapException( + this._("err_ooo_no_lcc")); + } + + // Obtains the local service manager + this.serviceManager = this.bootstrapContext.getServiceManager(); + + // Obtain the desktop service + try { + service = this.serviceManager.createInstanceWithContext( + "com.sun.star.frame.Desktop", this.bootstrapContext); + } catch (com.sun.star.uno.Exception e) { + throw new java.lang.UnsupportedOperationException(e); + } + this.desktop = (XDesktop) UnoRuntime.queryInterface( + XDesktop.class, service); + + // Obtains the service manager + this.serviceManager = this.bootstrapContext.getServiceManager(); + return; + } + + /** + * Returns whether the connection is on and alive. + * + * @return true if the connection is alive, false otherwise + */ + private boolean isConnected() { + if (this.serviceManager == null) { + return false; + } + try { + UnoRuntime.queryInterface( + XPropertySet.class, this.serviceManager); + } catch (com.sun.star.lang.DisposedException e) { + this.serviceManager = null; + return false; + } + return true; + } + + /** + * Starts a new spreadsheet document. + * + * @return the new spreadsheet document + */ + public SpreadsheetDocument startNewSpreadsheetDocument() { + XComponentLoader xComponentLoader = (XComponentLoader) + UnoRuntime.queryInterface( + XComponentLoader.class, this.desktop); + PropertyValue props[] = new PropertyValue[0]; + final String url = "private:factory/scalc"; + XComponent xComponent = null; + + // Load a new document + try { + xComponent = xComponentLoader.loadComponentFromURL(url, + "_default", 0, props); + } catch (com.sun.star.io.IOException e) { + throw new java.lang.RuntimeException( + String.format(_("err_open"), url), e); + } catch (com.sun.star.lang.IllegalArgumentException e) { + throw new java.lang.RuntimeException( + String.format(_("err_open"), url), e); + } + + return new SpreadsheetDocument(xComponent); + } + + /** + * Gets a string for the given key from this resource bundle + * or one of its parents. If the key is missing, returns + * the key itself. + * + * @param key the key for the desired string + * @return the string for the given key + */ + private String _(String key) { + try { + return new String( + this.l10n.getString(key).getBytes("ISO-8859-1"), + "UTF-8"); + } catch (java.io.UnsupportedEncodingException e) { + return this.l10n.getString(key); + } catch (java.util.MissingResourceException e) { + return key; + } + } +} diff --git a/src/tw/idv/imacat/calcmosaic/OptionDialog.java b/src/tw/idv/imacat/calcmosaic/OptionDialog.java new file mode 100644 index 0000000..c67f5b7 --- /dev/null +++ b/src/tw/idv/imacat/calcmosaic/OptionDialog.java @@ -0,0 +1,272 @@ +/* Copyright (c) 2012 imacat + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * OptionDialog + * + * Created on 2012-10-29 + * + * Copyright (c) 2012 imacat + */ + +package tw.idv.imacat.calcmosaic; + +import java.io.File; +import java.util.ResourceBundle; + +import java.awt.Dialog.ModalityType; +import java.awt.Dimension; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.KeyEvent; +import java.awt.GridLayout; +import java.awt.Toolkit; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JDialog; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JSpinner; +import javax.swing.SpinnerNumberModel; +import javax.swing.UIManager; + +import javax.swing.JFileChooser; +import javax.swing.filechooser.FileNameExtensionFilter; + +/** + * An dialog to obtain the options. + * + * @author imacat + * @version 2.1.0 + */ +public class OptionDialog { + + /** The localization resources. */ + private ResourceBundle l10n = null; + + /** The option set. */ + private Options options = null; + + /** The dialog. */ + private JDialog dialog = new JDialog( + null, ModalityType.TOOLKIT_MODAL); + + /** The image files. */ + public File files[] = new File[0];; + + /** The number of rows to use. */ + public int numRows = 0; + + /** Whether we should run in slow motion (for presentation). */ + public boolean isSlow = false; + + /** The current directory. */ + private File currentDirectory = null; + + /** The file chooser button. */ + private JButton filesButton = new JButton(); + + /** The spinner of the number of rows. */ + private JSpinner numRowsSpinner = new JSpinner(); + + /** The check box of whether we should run in slow motion. */ + private JCheckBox isSlowCheckBox = new JCheckBox(); + + /** The OK button. */ + private JButton okButton = new JButton(); + + /** + * Creates a new option dialog. + * + * @param files the image files + * @param numRows the number of rows to use + * @param isSlow whether we should run in slow motion (for + * presentation) + */ + public OptionDialog(File files[], int numRows, boolean isSlow) { + // Obtains the localization resources + this.l10n = ResourceBundle.getBundle( + this.getClass().getPackage().getName() + ".res.L10n"); + this.files = files; + this.numRows = numRows; + this.isSlow = isSlow; + } + + /** + * Asks the user and returns the options. + * + * @return the options set by the user + */ + public Options getOptions() { + JPanel optionPanel = new JPanel(); + JLabel label = null; + JButton cancelButton = new JButton(_("dialog_btn_cancel")); + SpinnerNumberModel spinnerModel = new SpinnerNumberModel( + this.numRows, 0, 32767, 1); + Dimension screenSize + = Toolkit.getDefaultToolkit().getScreenSize(); + Dimension frameSize = null; + + // Makes "enter" acts as "space" that activates the buttons + UIManager.put("Button.defaultButtonFollowsFocus", true); + + this.dialog.setIconImage(CalcMosaic.icon); + this.dialog.setTitle(String.format(_("dialog_opts_title"), + CalcMosaic.APP_NAME)); + this.dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE); + optionPanel.setLayout(new GridLayout(4, 2, 10, 5)); + + // Reserves the space for a 3-digit number + this.filesButton.setText( + String.format(_("dialog_btn_files"), 256)); + this.filesButton.setMnemonic(KeyEvent.VK_F); + this.filesButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + chooseFiles(); + } + }); + label = new JLabel(_("dialog_label_files")); + label.setLabelFor(this.filesButton); + optionPanel.add(label); + optionPanel.add(this.filesButton); + + this.numRowsSpinner.setModel(spinnerModel); + label = new JLabel(_("dialog_label_numrows")); + label.setLabelFor(this.numRowsSpinner); + optionPanel.add(label); + optionPanel.add(this.numRowsSpinner); + + this.isSlowCheckBox.setText(_("dialog_checkbox_isslow")); + this.isSlowCheckBox.setSelected(this.isSlow); + label = new JLabel(_("dialog_label_isslow")); + label.setLabelFor(this.isSlowCheckBox); + optionPanel.add(label); + optionPanel.add(this.isSlowCheckBox); + + this.okButton.setText(_("dialog_btn_ok")); + this.okButton.setMnemonic(KeyEvent.VK_O); + this.okButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + options = new Options(files, + (Integer) numRowsSpinner.getValue(), + isSlowCheckBox.isSelected()); + dialog.dispose(); + } + }); + if (this.files.length == 0) { + this.okButton.setEnabled(false); + } else { + this.okButton.setEnabled(true); + } + optionPanel.add(this.okButton); + cancelButton = new JButton(_("dialog_btn_cancel")); + cancelButton.setMnemonic(KeyEvent.VK_C); + cancelButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + options = null; + dialog.dispose(); + } + }); + optionPanel.add(cancelButton); + + this.options = null; + this.dialog.add(optionPanel); + this.dialog.pack(); + frameSize = this.dialog.getSize(); + this.dialog.setLocation( + (screenSize.width - frameSize.width) / 2, + (screenSize.height - frameSize.height) / 2 + ); + this.filesButton.setText( + String.format(_("dialog_btn_files"), this.files.length)); + this.dialog.setVisible(true); + + return this.options; + } + + /** + * Chooses the image files. + * + */ + private void chooseFiles() { + File chosen[] = new File[0]; + + while (true) { + JFileChooser chooser = new JFileChooser( + this.currentDirectory); + FileNameExtensionFilter filter = new FileNameExtensionFilter( + _("dialog_file_chooser_image_type"), "jpg", "png", "gif"); + int result = -1; + + chooser.setDialogTitle(_("dialog_file_chooser_title")); + chooser.setFileFilter(filter); + chooser.setMultiSelectionEnabled(true); + result = chooser.showOpenDialog(this.dialog); + this.currentDirectory = chooser.getCurrentDirectory(); + + if (result == JFileChooser.CANCEL_OPTION) { + return; + + } else if (result == JFileChooser.ERROR_OPTION) { + JOptionPane.showMessageDialog(this.dialog, + _("err_file_chooser_failed")); + return; + } + + chosen = chooser.getSelectedFiles(); + if (chosen.length > 256) { + JOptionPane.showMessageDialog(this.dialog, + _("err_file_too_many"), + String.format(_("dialog_err_title"), + CalcMosaic.APP_NAME), + JOptionPane.ERROR_MESSAGE); + continue; + } + break; + } + + this.files = chosen; + this.filesButton.setText( + String.format(_("dialog_btn_files"), this.files.length)); + if (this.files.length == 0) { + this.okButton.setEnabled(false); + } else { + this.okButton.setEnabled(true); + this.okButton.grabFocus(); + } + return; + } + + /** + * Gets a string for the given key from this resource bundle + * or one of its parents. If the key is missing, returns + * the key itself. + * + * @param key the key for the desired string + * @return the string for the given key + */ + private String _(String key) { + try { + return new String( + this.l10n.getString(key).getBytes("ISO-8859-1"), + "UTF-8"); + } catch (java.io.UnsupportedEncodingException e) { + return this.l10n.getString(key); + } catch (java.util.MissingResourceException e) { + return key; + } + } +} diff --git a/src/tw/idv/imacat/calcmosaic/Options.java b/src/tw/idv/imacat/calcmosaic/Options.java new file mode 100644 index 0000000..989ef34 --- /dev/null +++ b/src/tw/idv/imacat/calcmosaic/Options.java @@ -0,0 +1,58 @@ +/* Copyright (c) 2012 imacat + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Options + * + * Created on 2012-10-29 + * + * Copyright (c) 2012 imacat + */ + +package tw.idv.imacat.calcmosaic; + +import java.io.File; + +/** + * The options of Calc Mosaic. + * + * @author imacat + * @version 0.0.1 + */ +public class Options { + + /** The image files. */ + public File files[] = new File[0];; + + /** The number of rows to use. */ + public int numRows = 0; + + /** Whether we should run in slow motion (for presentation). */ + public boolean isSlow = false; + + /** + * Creates a new set of options. + * + * @param files the image files + * @param numRows the number of rows to use + * @param isSlow whether we should run in slow motion (for + * presentation) + */ + public Options(File files[], int numRows, boolean isSlow) { + this.files = files; + this.numRows = numRows; + this.isSlow = isSlow; + } +} diff --git a/src/tw/idv/imacat/calcmosaic/SpreadsheetDocument.java b/src/tw/idv/imacat/calcmosaic/SpreadsheetDocument.java new file mode 100644 index 0000000..2012129 --- /dev/null +++ b/src/tw/idv/imacat/calcmosaic/SpreadsheetDocument.java @@ -0,0 +1,655 @@ +/* Copyright (c) 2012 imacat + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * SpreadsheetDocument + * + * Created on 2012-08-16, rewritten from CalcMosaic + * + * Copyright (c) 2012 imacat + */ + +package tw.idv.imacat.calcmosaic; + +import java.awt.Dimension; +import java.util.ResourceBundle; + +import com.sun.star.awt.Size; +import com.sun.star.awt.XView; +import com.sun.star.awt.XWindow; +import com.sun.star.beans.XPropertySet; +import com.sun.star.container.XIndexAccess; +import com.sun.star.container.XNameContainer; +import com.sun.star.frame.XController; +import com.sun.star.frame.XFrame; +import com.sun.star.frame.XModel; +import com.sun.star.lang.XComponent; +import com.sun.star.script.XLibraryContainer; +import com.sun.star.script.provider.XScript; +import com.sun.star.script.provider.XScriptProvider; +import com.sun.star.script.provider.XScriptProviderSupplier; +import com.sun.star.sheet.XSpreadsheetDocument; +import com.sun.star.table.XColumnRowRange; +import com.sun.star.table.XTableRows; +import com.sun.star.uno.UnoRuntime; + +// Used in slow-motion mode. +import com.sun.star.awt.Point; +import com.sun.star.awt.Size; +import com.sun.star.awt.XControlModel; +import com.sun.star.container.XIndexContainer; +import com.sun.star.drawing.XControlShape; +import com.sun.star.drawing.XDrawPage; +import com.sun.star.drawing.XDrawPageSupplier; +import com.sun.star.drawing.XShape; +import com.sun.star.form.XFormComponent; +import com.sun.star.lang.XMultiServiceFactory; +import com.sun.star.script.ScriptEventDescriptor; +import com.sun.star.script.XEventAttacherManager; +import com.sun.star.sheet.XSpreadsheet; +import com.sun.star.sheet.XSpreadsheets; +import com.sun.star.sheet.XSpreadsheetView; +import com.sun.star.table.XCell; +import com.sun.star.table.XCellRange; +import com.sun.star.table.XColumnRowRange; +import com.sun.star.table.XTableColumns; +import com.sun.star.table.XTableRows; + +/** + * A spreadsheet document. + * + * @author imacat + * @version 2.1.0 + */ +public class SpreadsheetDocument { + + /** The BASIC library name. */ + private static final String libraryName = "Standard"; + + /** The localization resources. */ + private ResourceBundle l10n = null; + + /** The spreadsheet document. */ + private XSpreadsheetDocument document = null; + + /** The spreadsheet collection. */ + private XSpreadsheets sheets = null; + + /** The document controller. */ + private XController xController = null; + + /** The spreadsheet view. */ + private XSpreadsheetView xSpreadsheetView = null; + + /** The row height, to be cached. */ + private int rowHeight = -1; + + /** + * Creates a new instance of SpreadsheetDocument. + * + * @param document the document component + */ + public SpreadsheetDocument(XComponent document) { + XModel xModel = null; + + // Obtains the localization resources + this.l10n = ResourceBundle.getBundle( + this.getClass().getPackage().getName() + ".res.L10n"); + + this.document = (XSpreadsheetDocument) + UnoRuntime.queryInterface( + XSpreadsheetDocument.class, document); + + this.sheets = this.document.getSheets(); + + // Obtains the document controller + xModel = (XModel) UnoRuntime.queryInterface( + XModel.class, this.document); + this.xController = xModel.getCurrentController(); + this.xSpreadsheetView = (XSpreadsheetView) + UnoRuntime.queryInterface( + XSpreadsheetView.class, this.xController); + } + + /** + * Adds a module of BASIC macros to the document. + * + * @param module the name of the module. + * @param macros the content of the module + */ + public void addBasicModule(String module, String macros) { + XPropertySet xProps = null; + Object libraries = null; + XLibraryContainer libraryContainer = null; + Object library = null; + XNameContainer moduleContainer = null; + + xProps = (XPropertySet) UnoRuntime.queryInterface( + XPropertySet.class, this.document); + try { + libraries = xProps.getPropertyValue("BasicLibraries"); + } catch (com.sun.star.beans.UnknownPropertyException e) { + throw new java.lang.RuntimeException(e); + } catch (com.sun.star.lang.WrappedTargetException e) { + throw new java.lang.RuntimeException(e); + } + libraryContainer = (XLibraryContainer) + UnoRuntime.queryInterface( + XLibraryContainer.class, libraries); + if (!libraryContainer.hasByName(libraryName)) { + try { + libraryContainer.createLibrary(libraryName); + } catch (com.sun.star.lang.IllegalArgumentException e) { + throw new java.lang.IllegalArgumentException(e); + } catch (com.sun.star.container.ElementExistException e) { + throw new java.lang.RuntimeException(e); + } + } + try { + library = libraryContainer.getByName(libraryName); + } catch (com.sun.star.container.NoSuchElementException e) { + throw new java.lang.RuntimeException(e); + } catch (com.sun.star.lang.WrappedTargetException e) { + throw new java.lang.RuntimeException(e); + } + moduleContainer = (XNameContainer) UnoRuntime.queryInterface( + XNameContainer.class, library); + if (moduleContainer.hasByName(module)) { + try { + moduleContainer.removeByName(module); + } catch (com.sun.star.container.NoSuchElementException e) { + throw new java.lang.RuntimeException(e); + } catch (com.sun.star.lang.WrappedTargetException e) { + throw new java.lang.RuntimeException(e); + } + } + try { + moduleContainer.insertByName(module, macros); + } catch (com.sun.star.lang.IllegalArgumentException e) { + throw new java.lang.IllegalArgumentException(e); + } catch (com.sun.star.container.ElementExistException e) { + throw new java.lang.RuntimeException(e); + } catch (com.sun.star.lang.WrappedTargetException e) { + throw new java.lang.RuntimeException(e); + } + } + + /** + * Runs a BASIC macro of the document. + * + * @param module the name of the module of the macro. + * @param macro the name of the macro. + * @param param the parameters for the macro + */ + public void runBasicMacro(String module, String macro, Object param[]) { + String scriptUri = String.format( + "vnd.sun.star.script:%s.%s.%s?language=Basic&location=document", + libraryName, module, macro); + XScriptProviderSupplier xSupplier = (XScriptProviderSupplier) + UnoRuntime.queryInterface( + XScriptProviderSupplier.class, document); + XScriptProvider xProvider = xSupplier.getScriptProvider(); + XScript xScript = null; + short outParamIndex[][] = new short[0][0]; + Object outParam[][] = new Object[0][0]; + + try { + xScript = xProvider.getScript(scriptUri); + } catch (com.sun.star.script.provider.ScriptFrameworkErrorException e) { + throw new java.lang.IllegalArgumentException(e); + } + try { + xScript.invoke(param, outParamIndex, outParam); + } catch (com.sun.star.script.provider.ScriptFrameworkErrorException e) { + throw new java.lang.RuntimeException(e); + } catch (com.sun.star.reflection.InvocationTargetException e) { + throw new java.lang.RuntimeException(e); + } catch (com.sun.star.lang.DisposedException e) { + } + } + + /** + * Returns the size of the window. + * + * @return the size of the window + */ + public Dimension getWindowSize() { + XFrame frame = this.xController.getFrame(); + XWindow window = frame.getComponentWindow(); + XView xView = (XView) UnoRuntime.queryInterface( + XView.class, window); + Size size = xView.getSize(); + return new Dimension(size.Width - 55, size.Height - 72); + } + + /** + * Returns the height of the rows. We do not need + * the width, since the height is always shorter in new + * spreadsheets. + * + * @return the height of the rows. + */ + public int getRowHeight() { + if (this.rowHeight != -1) { + return this.rowHeight; + } + + XIndexAccess sheetIndexAccess = (XIndexAccess) + UnoRuntime.queryInterface( + XIndexAccess.class, this.sheets); + Object sheet = null; + XColumnRowRange xColumnRowRange = null; + XTableRows rows = null; + XPropertySet xProps = null; + + try { + sheet = sheetIndexAccess.getByIndex(0); + } catch (com.sun.star.lang.IndexOutOfBoundsException e) { + throw new java.lang.IndexOutOfBoundsException( + e.getMessage()); + } catch (com.sun.star.lang.WrappedTargetException e) { + throw new java.lang.RuntimeException(e); + } + + // Obtains the height of the rows. + xColumnRowRange = (XColumnRowRange) UnoRuntime.queryInterface( + XColumnRowRange.class, sheet); + rows = xColumnRowRange.getRows(); + xProps = (XPropertySet) UnoRuntime.queryInterface( + XPropertySet.class, rows); + try { + this.rowHeight = (Integer) + xProps.getPropertyValue("Height"); + } catch (com.sun.star.beans.UnknownPropertyException e) { + throw new java.lang.RuntimeException(e); + } catch (com.sun.star.lang.WrappedTargetException e) { + throw new java.lang.RuntimeException(e); + } + + return this.rowHeight; + } + + /** + * Creates a mosaic spreadsheet. + * + * @param name the spreadsheet name + * @param index the spreadsheet index + * @param colors the color values matrix + */ + public void createMosaicSheet(String name, int index, + int colors[][]) { + XSpreadsheet sheet = this.initializeMosaicSheet(name, index); + + this.xSpreadsheetView.setActiveSheet(sheet); + this.setColumnWidth(sheet, colors.length, colors[0].length); + + // Paints the colors. + for (int row = 0; row < colors.length; row++) { + for (int column = 0; column < colors[row].length; column++) { + XCell cell = null; + XPropertySet xProps = null; + + try { + cell = sheet.getCellByPosition(column, row); + } catch (com.sun.star.lang.IndexOutOfBoundsException e) { + throw new java.lang.IndexOutOfBoundsException( + e.getMessage()); + } + xProps = (XPropertySet) UnoRuntime.queryInterface( + XPropertySet.class, cell); + try { + xProps.setPropertyValue( + "CellBackColor", colors[row][column]); + xProps.setPropertyValue( + "IsCellBackgroundTransparent", false); + } catch (com.sun.star.beans.UnknownPropertyException e) { + throw new java.lang.RuntimeException(e); + } catch (com.sun.star.beans.PropertyVetoException e) { + throw new java.lang.RuntimeException(e); + } catch (com.sun.star.lang.IllegalArgumentException e) { + throw new java.lang.IllegalArgumentException(e); + } catch (com.sun.star.lang.WrappedTargetException e) { + throw new java.lang.RuntimeException(e); + } + } + } + return; + } + + /** + * Initializes a mosaic spreadsheet. + * + * @param name the spreadsheet name + * @param index the spreadsheet index + * @return the spreadsheet + */ + private XSpreadsheet initializeMosaicSheet( + String name, int index) { + XIndexAccess sheetIndexAccess = (XIndexAccess) + UnoRuntime.queryInterface( + XIndexAccess.class, this.sheets); + Object sheet = null; + XSpreadsheet xSheet = null; + + // Adds our spreadsheet. + if (this.sheets.hasByName(name)) { + if (sheetIndexAccess.getCount() == 1) { + this.sheets.insertNewByName(name + 1, (short) 0); + try { + this.sheets.removeByName(name); + } catch (com.sun.star.container.NoSuchElementException e) { + throw new java.lang.RuntimeException(e); + } catch (com.sun.star.lang.WrappedTargetException e) { + throw new java.lang.RuntimeException(e); + } + this.sheets.insertNewByName(name, (short) index); + try { + this.sheets.removeByName(name + 1); + } catch (com.sun.star.container.NoSuchElementException e) { + throw new java.lang.RuntimeException(e); + } catch (com.sun.star.lang.WrappedTargetException e) { + throw new java.lang.RuntimeException(e); + } + } else { + try { + this.sheets.removeByName(name); + } catch (com.sun.star.container.NoSuchElementException e) { + throw new java.lang.RuntimeException(e); + } catch (com.sun.star.lang.WrappedTargetException e) { + throw new java.lang.RuntimeException(e); + } + this.sheets.insertNewByName(name, (short) index); + } + } else { + this.sheets.insertNewByName(name, (short) index); + } + // Cleans-up + if (index == 0) { + String names[] = this.sheets.getElementNames(); + for (int i = 0; i < names.length; i++) { + if (!names[i].equals(name)) { + try { + this.sheets.removeByName(names[i]); + } catch (com.sun.star.container.NoSuchElementException e) { + throw new java.lang.RuntimeException(e); + } catch (com.sun.star.lang.WrappedTargetException e) { + throw new java.lang.RuntimeException(e); + } + } + } + } + + // Obtains the speadsheet. + try { + sheet = sheets.getByName(name); + } catch (com.sun.star.container.NoSuchElementException e) { + throw new java.lang.RuntimeException(e); + } catch (com.sun.star.lang.WrappedTargetException e) { + throw new java.lang.RuntimeException(e); + } + xSheet = (XSpreadsheet) UnoRuntime.queryInterface( + XSpreadsheet.class, sheet); + return xSheet; + } + + /** + * Sets the width of the columns. + * + * @param sheet the spreadsheet + * @param rows the number of rows in the cell range + * @param columns the number of columns in the cell range + */ + private void setColumnWidth(XSpreadsheet sheet, + int rows, int columns) { + XCellRange range = null; + XColumnRowRange xColumnRowRange = null; + XTableColumns xColumns = null; + int height = -1; + XPropertySet xProps = null; + + try { + range = sheet.getCellRangeByPosition( + 0, 0, columns - 1, rows - 1); + } catch (com.sun.star.lang.IndexOutOfBoundsException e) { + throw new java.lang.IndexOutOfBoundsException( + e.getMessage()); + } + xColumnRowRange = (XColumnRowRange) UnoRuntime.queryInterface( + XColumnRowRange.class, range); + xColumns = xColumnRowRange.getColumns(); + xProps = (XPropertySet) UnoRuntime.queryInterface( + XPropertySet.class, xColumns); + try { + xProps.setPropertyValue("Width", this.getRowHeight()); + } catch (com.sun.star.beans.UnknownPropertyException e) { + throw new java.lang.RuntimeException(e); + } catch (com.sun.star.beans.PropertyVetoException e) { + throw new java.lang.RuntimeException(e); + } catch (com.sun.star.lang.IllegalArgumentException e) { + throw new java.lang.IllegalArgumentException(e); + } catch (com.sun.star.lang.WrappedTargetException e) { + throw new java.lang.RuntimeException(e); + } + } + + /** + * Adds the macro triggers to show the sheets. + * + */ + public void addMacroTriggers() { + XIndexAccess sheetIndexAccess = (XIndexAccess) + UnoRuntime.queryInterface( + XIndexAccess.class, this.sheets); + int count = sheetIndexAccess.getCount(); + Object firstSheet = null, lastSheet = null; + XSpreadsheet xFirstSheet = null, xLastSheet; + XCell breaksCell = null, musicCell = null; + XPropertySet breaksCellProps = null, musicCellProps = null; + int breaksColor = -1, musicColor = -1; + + // Adds start buttons on the first and the last sheets. + try { + firstSheet = sheetIndexAccess.getByIndex(0); + lastSheet = sheetIndexAccess.getByIndex(count - 1); + } catch (com.sun.star.lang.IndexOutOfBoundsException e) { + throw new java.lang.IndexOutOfBoundsException( + e.getMessage()); + } catch (com.sun.star.lang.WrappedTargetException e) { + throw new java.lang.RuntimeException(e); + } + xFirstSheet = (XSpreadsheet) UnoRuntime.queryInterface( + XSpreadsheet.class, firstSheet); + xLastSheet = (XSpreadsheet) UnoRuntime.queryInterface( + XSpreadsheet.class, lastSheet); + this.addStartButton(xFirstSheet); + this.addStartButton(xLastSheet); + + // Sets the text color to be the same as the background color + // at B1 and C1 of the first sheet, where are the breaks + // between sheets and the music files, so that users can + // hide these two pieces of information. + this.xSpreadsheetView.setActiveSheet(xFirstSheet); + try { + breaksCell = xFirstSheet.getCellByPosition(1, 0); + musicCell = xFirstSheet.getCellByPosition(2, 0); + } catch (com.sun.star.lang.IndexOutOfBoundsException e) { + throw new java.lang.IndexOutOfBoundsException( + e.getMessage()); + } + breaksCellProps = (XPropertySet) UnoRuntime.queryInterface( + XPropertySet.class, breaksCell); + musicCellProps = (XPropertySet) UnoRuntime.queryInterface( + XPropertySet.class, musicCell); + try { + breaksColor = (Integer) breaksCellProps.getPropertyValue( + "CellBackColor"); + musicColor = (Integer) musicCellProps.getPropertyValue( + "CellBackColor"); + } catch (com.sun.star.beans.UnknownPropertyException e) { + throw new java.lang.RuntimeException(e); + } catch (com.sun.star.lang.WrappedTargetException e) { + throw new java.lang.RuntimeException(e); + } + try { + breaksCellProps.setPropertyValue( + "CharColor", breaksColor); + musicCellProps.setPropertyValue( + "CharColor", musicColor); + } catch (com.sun.star.beans.UnknownPropertyException e) { + throw new java.lang.RuntimeException(e); + } catch (com.sun.star.beans.PropertyVetoException e) { + throw new java.lang.RuntimeException(e); + } catch (com.sun.star.lang.IllegalArgumentException e) { + throw new java.lang.IllegalArgumentException(e); + } catch (com.sun.star.lang.WrappedTargetException e) { + throw new java.lang.RuntimeException(e); + } + } + + /** + * Adds a start button to show the sheets. + * + * @param sheet the spreadsheet to add a start button unto + */ + private void addStartButton(XSpreadsheet sheet) { + XMultiServiceFactory serviceManager = (XMultiServiceFactory) + UnoRuntime.queryInterface( + XMultiServiceFactory.class, this.document); + Object shape = null; + XControlShape xShape = null; + Point position = new Point(); + Size size = new Size(); + Object button = null; + XCell cell = null; + XPropertySet xProps = null; + int color = -1; + XControlModel buttonModel = null; + XDrawPageSupplier xDrawPageSupplier = null; + XDrawPage drawPage = null; + ScriptEventDescriptor event = new ScriptEventDescriptor(); + XFormComponent xFormComponent = null; + Object form = null; + XIndexContainer xContainer = null; + XEventAttacherManager xEventAttacherManager = null; + + // Creates the shape for the button, as a control shape. + try { + shape = serviceManager.createInstance( + "com.sun.star.drawing.ControlShape"); + } catch (com.sun.star.uno.Exception e) { + throw new java.lang.RuntimeException(e); + } + xShape = (XControlShape) UnoRuntime.queryInterface( + XControlShape.class, shape); + position.X = 0; + position.Y = 0; + xShape.setPosition(position); + size.Width = this.getRowHeight(); + size.Height = size.Width; + try { + xShape.setSize(size); + } catch (com.sun.star.beans.PropertyVetoException e) { + throw new java.lang.RuntimeException(e); + } + + // Creates the button. + try { + button = serviceManager.createInstance( + "com.sun.star.form.component.CommandButton"); + } catch (com.sun.star.uno.Exception e) { + throw new java.lang.RuntimeException(e); + } + try { + cell = sheet.getCellByPosition(0, 0); + } catch (com.sun.star.lang.IndexOutOfBoundsException e) { + throw new java.lang.IndexOutOfBoundsException( + e.getMessage()); + } + xProps = (XPropertySet) UnoRuntime.queryInterface( + XPropertySet.class, cell); + try { + color = (Integer) xProps.getPropertyValue( + "CellBackColor"); + } catch (com.sun.star.beans.UnknownPropertyException e) { + throw new java.lang.RuntimeException(e); + } catch (com.sun.star.lang.WrappedTargetException e) { + throw new java.lang.RuntimeException(e); + } + xProps = (XPropertySet) UnoRuntime.queryInterface( + XPropertySet.class, button); + try { + xProps.setPropertyValue("BackgroundColor", color); + } catch (com.sun.star.beans.UnknownPropertyException e) { + throw new java.lang.RuntimeException(e); + } catch (com.sun.star.beans.PropertyVetoException e) { + throw new java.lang.RuntimeException(e); + } catch (com.sun.star.lang.IllegalArgumentException e) { + throw new java.lang.IllegalArgumentException(e); + } catch (com.sun.star.lang.WrappedTargetException e) { + throw new java.lang.RuntimeException(e); + } + + // Adds the button to the page. + buttonModel = (XControlModel) UnoRuntime.queryInterface( + XControlModel.class, button); + xShape.setControl(buttonModel); + xDrawPageSupplier = (XDrawPageSupplier) UnoRuntime.queryInterface( + XDrawPageSupplier.class, sheet); + drawPage = xDrawPageSupplier.getDrawPage(); + drawPage.add(xShape); + + // Sets the action of the button + event.ListenerType = "XActionListener"; + event.EventMethod = "actionPerformed"; + event.ScriptType = "Script"; + event.ScriptCode = "vnd.sun.star.script:" + + "Standard._0Play.subPlaySheets" + + "?language=Basic&location=document"; + xFormComponent = (XFormComponent) UnoRuntime.queryInterface( + XFormComponent.class, button); + form = xFormComponent.getParent(); + xContainer = (XIndexContainer) UnoRuntime.queryInterface( + XIndexContainer.class, form); + xEventAttacherManager = (XEventAttacherManager) + UnoRuntime.queryInterface( + XEventAttacherManager.class, form); + try { + xEventAttacherManager.registerScriptEvent( + xContainer.getCount() - 1, event); + } catch (com.sun.star.lang.IllegalArgumentException e) { + throw new java.lang.IllegalArgumentException(e); + } + } + + /** + * Gets a string for the given key from this resource bundle + * or one of its parents. If the key is missing, returns + * the key itself. + * + * @param key the key for the desired string + * @return the string for the given key + */ + private String _(String key) { + try { + return new String( + this.l10n.getString(key).getBytes("ISO-8859-1"), + "UTF-8"); + } catch (java.io.UnsupportedEncodingException e) { + return this.l10n.getString(key); + } catch (java.util.MissingResourceException e) { + return key; + } + } +} diff --git a/src/tw/idv/imacat/calcmosaic/package-info.java b/src/tw/idv/imacat/calcmosaic/package-info.java new file mode 100644 index 0000000..f609f17 --- /dev/null +++ b/src/tw/idv/imacat/calcmosaic/package-info.java @@ -0,0 +1,8 @@ +/** + * A program to create mosaic art from images with Calc. + * + * @author imacat + * @version 0.1.1 + */ + +package tw.idv.imacat.calcmosaic; diff --git a/src/tw/idv/imacat/calcmosaic/res/L10n.properties b/src/tw/idv/imacat/calcmosaic/res/L10n.properties new file mode 100644 index 0000000..edf9d40 --- /dev/null +++ b/src/tw/idv/imacat/calcmosaic/res/L10n.properties @@ -0,0 +1,53 @@ +# The default resource properties +author = imacat +err_rows_miss = You did not specify the number of rows to use. +err_rows_not_pint = The number of rows is not a positive integer. +err_file_not_exist = File does not exist. +err_file_not_a_file = Not a file. +err_file_too_many = Too many images (256 max). A spreadsheet document can only contain at most 256 spreadsheets. +err_file_not_an_image = Not an image. +err_file_chooser_failed = Failed to select the images. +err_arg_unknown = Unrecognized switch. +err_ooo_no_lcc = Failed to obtain the local component context. +err_open = Failed to open “%s”. +dialog_opts_title = %s Options +dialog_btn_files = %d file(s) +dialog_label_files = Image files: +dialog_label_numrows = Number of rows: +dialog_checkbox_isslow = Run in slow motion +dialog_label_isslow = Slow? +dialog_btn_ok = OK +dialog_btn_cancel = Cancel +dialog_file_chooser_title = Select Image Files +dialog_file_chooser_image_type = Image files (JPG, PNG, GIF) +dialog_err_title = %s Error +help = \ +Usage: java -jar %s [options] image1 [image2...]\n\ +Create mosaic art from images with Calc.\n\ +\n \ + -r,-rows num The number of rows to use (default to\n \ + estimate it automatically).\n \ + -s,-slow Run in slow motion (for presentation).\n \ + -noslow Run in the normal speed (default).\n \ + -h,-help Display this help.\n \ + -v,-version Display version number.\n \ + image1, image2... The image files.\n\ +\n\ +The snapshots of a video can be grabbed using ffmpeg:\n\ +ffmpeg -i video.mp4 -r -sameq -y -f image2 img%%04d.jpg\n\ +Note that a spreadsheet document can only contain at most 256 spreadsheets. +version = %s v%s by %s +finished = \ +Done. Notice:\n\ +1. Save file first for the macro to work.\n\ +2. Hit the start button at A1 of the first\n \ + and last sheet to play the sheets.\n\ +3. You can set the break time (in\n \ + milliseconds) between sheets at B1 of\n \ + the first sheet (default 100 ms).\n\ +4. You can set the background music at C1\n \ + of the first sheet. This only works on\n \ + Linux and you must install mplayer.\n \ + Relative file path is relative to the\n \ + document file. Play once before your\n \ + live show to avoid the I/O delay with cache. diff --git a/src/tw/idv/imacat/calcmosaic/res/L10n_zh_TW.properties b/src/tw/idv/imacat/calcmosaic/res/L10n_zh_TW.properties new file mode 100644 index 0000000..961c0cc --- /dev/null +++ b/src/tw/idv/imacat/calcmosaic/res/L10n_zh_TW.properties @@ -0,0 +1,50 @@ +# The Chinese-Taiwan resource properties +author = 依瑪貓 +err_rows_miss = 沒有設定要使用幾個橫列。 +err_rows_not_pint = 橫列數不是正整數。 +err_file_not_exist = 查無此檔。 +err_file_not_a_file = 不是檔案。 +err_file_too_many = 圖檔太多(最多 256 個圖)。試算表文件最多只能有 256 張試算表。 +err_file_not_an_image = 不是圖檔。 +err_file_chooser_failed = 選擇圖檔失敗。 +err_arg_unknown = 參數不明。 +err_ooo_no_lcc = 無法取得local component context。 +err_open = 無法開啟「%s」。 +dialog_opts_title = %s 選項 +dialog_btn_files = %d 個圖檔(F) +dialog_label_files = 圖檔: +dialog_label_numrows = 橫列數: +dialog_checkbox_isslow = 以慢動作執行。 +dialog_label_isslow = 慢動作? +dialog_btn_ok = 確定(O) +dialog_btn_cancel = 取消(C) +dialog_file_chooser_title = 請選擇圖檔 +dialog_file_chooser_image_type = 圖檔(JPG、PNG、GIF) +dialog_err_title = %s 錯誤 +help = \ +用法: java -jar %s [選項] 圖檔1 [圖檔2...]\n\ +用 Calc 製作馬賽克拼貼圖。\n\ +\n \ + -r,-rows 列數 要用的橫列數(預設自動估算)。\n \ + -s,-slow 以慢動作執行(簡報用)。\n \ + -noslow 以正常速度執行(預設)。\n \ + -h,-help 顯示這個說明。\n \ + -v,-version 顯示版號。\n \ + 圖檔1, 圖檔2... 圖檔。\n\ +\n\ +可以用 ffmpeg 來抓影片截圖:\n\ +ffmpeg -i 影片檔.mp4 -r <影格率> -sameq -y -f image2 img%%04d.jpg\n\ +註:一個試算表檔,最多只能有 256 張試算表。 +version = %s %s 版,作者 %s +finished = \ +執行結束。請注意:\n\ +1. 要先存檔,才能執行巨集。\n\ +2. 第一張和最後一張試算表的 A1 有隱藏按鈕,按\n \ + 下去會播放試算表。\n\ +3. 在第一張試算表的 B1 ,可以設定每張試算表播\n \ + 放的間隔時間(單位毫秒)。預設 100 毫秒。\n\ +4. 在第一張試算表的 C1 ,可以設定背景音樂檔。\n \ + 背景音樂只支援 Linux ,需安裝 mplayer 。\n \ + 相對路徑相對於試算表檔案本身。為避免音樂延\n \ + 遲,實際表演前,請先執行過一遍,以快取硬碟\n \ + 資料。 diff --git a/src/tw/idv/imacat/calcmosaic/res/package-info.java b/src/tw/idv/imacat/calcmosaic/res/package-info.java new file mode 100644 index 0000000..6491c40 --- /dev/null +++ b/src/tw/idv/imacat/calcmosaic/res/package-info.java @@ -0,0 +1,12 @@ +/** + * The localization resource bundles for the mosaic art creator. + * The creation of a seperate package space is to prevent name + * clashing with the localization resource bundles of the client + * applications. Do not use anything in this package unless you + * know what you are doing. + * + * @author imacat + * @version 0.01 + */ + +package tw.idv.imacat.calcmosaic.res;