Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--AUTHORS2
-rw-r--r--COPYING340
-rw-r--r--MANIFEST98
-rw-r--r--TODO (renamed from todo.txt)6
-rw-r--r--activity.py164
-rw-r--r--[-rwxr-xr-x]activity/activity.info3
-rw-r--r--activity/old.activity-cartoonbuilder.svg35
-rwxr-xr-xcartoonbuilder.py1724
-rw-r--r--char.py149
-rw-r--r--config.backpics17
-rw-r--r--config.imgdirs3
-rw-r--r--config.sounds4
-rw-r--r--document.py106
-rw-r--r--filmstrip.pngbin1790 -> 0 bytes
-rw-r--r--ground.py99
-rw-r--r--gtkrc22
-rw-r--r--icons/filmstrip.pngbin1821 -> 1063 bytes
-rw-r--r--icons/pink_arrow.pngbin361 -> 347 bytes
-rw-r--r--icons/sl-reset.svg13
-rw-r--r--icons/sound_icon.pngbin3227 -> 0 bytes
-rw-r--r--icons/tempo1.svg8
-rw-r--r--icons/tempo2.svg9
-rw-r--r--icons/tempo3.svg8
-rw-r--r--icons/tempo4.svg9
-rw-r--r--icons/tempo5.svg9
-rw-r--r--icons/tempo6.svg11
-rw-r--r--icons/tempo7.svg9
-rw-r--r--icons/tempo8.svg10
-rw-r--r--icons/yellow_arrow.pngbin375 -> 357 bytes
-rw-r--r--images/backpics/bigbg01.gif (renamed from backpics/bigbg01.gif)bin2942 -> 2942 bytes
-rw-r--r--images/backpics/bigbg02.gif (renamed from backpics/bigbg02.gif)bin3860 -> 3860 bytes
-rw-r--r--images/backpics/bigbg03.gif (renamed from backpics/bigbg03.gif)bin3015 -> 3015 bytes
-rw-r--r--images/backpics/bigbg04.gif (renamed from backpics/bigbg04.gif)bin2185 -> 2185 bytes
-rw-r--r--images/backpics/bigbg05.gif (renamed from backpics/bigbg05.gif)bin2456 -> 2456 bytes
-rw-r--r--images/backpics/bigbg06.gif (renamed from backpics/bigbg06.gif)bin4966 -> 4966 bytes
-rw-r--r--images/backpics/bigbg07.gif (renamed from backpics/bigbg07.gif)bin3413 -> 3413 bytes
-rw-r--r--images/backpics/bigbg08.gif (renamed from backpics/bigbg08.gif)bin768 -> 768 bytes
-rw-r--r--images/backpics/bigbg09.gif (renamed from backpics/bigbg09.gif)bin2175 -> 2175 bytes
-rw-r--r--images/backpics/bigbg10.gif (renamed from backpics/bigbg10.gif)bin1894 -> 1894 bytes
-rw-r--r--images/backpics/bigbg11.gif (renamed from backpics/bigbg11.gif)bin4402 -> 4402 bytes
-rw-r--r--images/backpics/bigbg12.gif (renamed from backpics/bigbg12.gif)bin8529 -> 8529 bytes
-rw-r--r--images/backpics/bigbg13.gif (renamed from backpics/bigbg13.gif)bin1026 -> 1026 bytes
-rw-r--r--images/backpics/bigbg14.gif (renamed from backpics/bigbg14.gif)bin936 -> 936 bytes
-rw-r--r--images/backpics/bigbg15.gif (renamed from backpics/bigbg15.gif)bin1067 -> 1067 bytes
-rw-r--r--images/backpics/bigbg16.gif (renamed from backpics/bigbg16.gif)bin1848 -> 1848 bytes
-rw-r--r--images/backpics/bigbg17.gif (renamed from backpics/bigbg17.gif)bin2253 -> 2253 bytes
-rw-r--r--images/backpics/custom.pngbin0 -> 15518 bytes
-rw-r--r--images/pics/Elephant/bigelephant0.gif (renamed from pics/Elephant/bigelephant0.gif)bin3186 -> 3186 bytes
-rw-r--r--images/pics/Elephant/bigelephant1.gif (renamed from pics/Elephant/bigelephant1.gif)bin3160 -> 3160 bytes
-rw-r--r--images/pics/Elephant/bigelephant10.gif (renamed from pics/Elephant/bigelephant10.gif)bin2442 -> 2442 bytes
-rw-r--r--images/pics/Elephant/bigelephant11.gif (renamed from pics/Elephant/bigelephant11.gif)bin2852 -> 2852 bytes
-rw-r--r--images/pics/Elephant/bigelephant12.gif (renamed from pics/Elephant/bigelephant12.gif)bin2879 -> 2879 bytes
-rw-r--r--images/pics/Elephant/bigelephant13.gif (renamed from pics/Elephant/bigelephant13.gif)bin2595 -> 2595 bytes
-rw-r--r--images/pics/Elephant/bigelephant2.gif (renamed from pics/Elephant/bigelephant2.gif)bin2554 -> 2554 bytes
-rw-r--r--images/pics/Elephant/bigelephant3.gif (renamed from pics/Elephant/bigelephant3.gif)bin3029 -> 3029 bytes
-rw-r--r--images/pics/Elephant/bigelephant4.gif (renamed from pics/Elephant/bigelephant4.gif)bin2492 -> 2492 bytes
-rw-r--r--images/pics/Elephant/bigelephant5.gif (renamed from pics/Elephant/bigelephant5.gif)bin3138 -> 3138 bytes
-rw-r--r--images/pics/Elephant/bigelephant6.gif (renamed from pics/Elephant/bigelephant6.gif)bin2408 -> 2408 bytes
-rw-r--r--images/pics/Elephant/bigelephant7.gif (renamed from pics/Elephant/bigelephant7.gif)bin2311 -> 2311 bytes
-rw-r--r--images/pics/Elephant/bigelephant8.gif (renamed from pics/Elephant/bigelephant8.gif)bin2644 -> 2644 bytes
-rw-r--r--images/pics/Elephant/bigelephant9.gif (renamed from pics/Elephant/bigelephant9.gif)bin2681 -> 2681 bytes
-rw-r--r--images/pics/SpaceBlob/bigblob0.gif (renamed from pics/SpaceBlob/bigblob0.gif)bin2677 -> 2677 bytes
-rw-r--r--images/pics/SpaceBlob/bigblob1.gif (renamed from pics/SpaceBlob/bigblob1.gif)bin2834 -> 2834 bytes
-rw-r--r--images/pics/SpaceBlob/bigblob10.gif (renamed from pics/SpaceBlob/bigblob10.gif)bin2628 -> 2628 bytes
-rw-r--r--images/pics/SpaceBlob/bigblob11.gif (renamed from pics/SpaceBlob/bigblob11.gif)bin2112 -> 2112 bytes
-rw-r--r--images/pics/SpaceBlob/bigblob12.gif (renamed from pics/SpaceBlob/bigblob12.gif)bin2340 -> 2340 bytes
-rw-r--r--images/pics/SpaceBlob/bigblob13.gif (renamed from pics/SpaceBlob/bigblob13.gif)bin2397 -> 2397 bytes
-rw-r--r--images/pics/SpaceBlob/bigblob2.gif (renamed from pics/SpaceBlob/bigblob2.gif)bin2231 -> 2231 bytes
-rw-r--r--images/pics/SpaceBlob/bigblob3.gif (renamed from pics/SpaceBlob/bigblob3.gif)bin2362 -> 2362 bytes
-rw-r--r--images/pics/SpaceBlob/bigblob4.gif (renamed from pics/SpaceBlob/bigblob4.gif)bin2661 -> 2661 bytes
-rw-r--r--images/pics/SpaceBlob/bigblob5.gif (renamed from pics/SpaceBlob/bigblob5.gif)bin2248 -> 2248 bytes
-rw-r--r--images/pics/SpaceBlob/bigblob6.gif (renamed from pics/SpaceBlob/bigblob6.gif)bin2297 -> 2297 bytes
-rw-r--r--images/pics/SpaceBlob/bigblob7.gif (renamed from pics/SpaceBlob/bigblob7.gif)bin2217 -> 2217 bytes
-rw-r--r--images/pics/SpaceBlob/bigblob8.gif (renamed from pics/SpaceBlob/bigblob8.gif)bin2750 -> 2750 bytes
-rw-r--r--images/pics/SpaceBlob/bigblob9.gif (renamed from pics/SpaceBlob/bigblob9.gif)bin2482 -> 2482 bytes
-rw-r--r--images/pics/Turkey/bigturkey1.gif (renamed from pics/Turkey/bigturkey1.gif)bin1703 -> 1703 bytes
-rw-r--r--images/pics/Turkey/bigturkey10.gif (renamed from pics/Turkey/bigturkey10.gif)bin1600 -> 1600 bytes
-rw-r--r--images/pics/Turkey/bigturkey11.gif (renamed from pics/Turkey/bigturkey11.gif)bin1597 -> 1597 bytes
-rw-r--r--images/pics/Turkey/bigturkey12.gif (renamed from pics/Turkey/bigturkey12.gif)bin1589 -> 1589 bytes
-rw-r--r--images/pics/Turkey/bigturkey13.gif (renamed from pics/Turkey/bigturkey13.gif)bin1595 -> 1595 bytes
-rw-r--r--images/pics/Turkey/bigturkey14.gif (renamed from pics/Turkey/bigturkey14.gif)bin1550 -> 1550 bytes
-rw-r--r--images/pics/Turkey/bigturkey2.gif (renamed from pics/Turkey/bigturkey2.gif)bin1651 -> 1651 bytes
-rw-r--r--images/pics/Turkey/bigturkey3.gif (renamed from pics/Turkey/bigturkey3.gif)bin1527 -> 1527 bytes
-rw-r--r--images/pics/Turkey/bigturkey4.gif (renamed from pics/Turkey/bigturkey4.gif)bin1571 -> 1571 bytes
-rw-r--r--images/pics/Turkey/bigturkey5.gif (renamed from pics/Turkey/bigturkey5.gif)bin1610 -> 1610 bytes
-rw-r--r--images/pics/Turkey/bigturkey6.gif (renamed from pics/Turkey/bigturkey6.gif)bin1809 -> 1809 bytes
-rw-r--r--images/pics/Turkey/bigturkey7.gif (renamed from pics/Turkey/bigturkey7.gif)bin1570 -> 1570 bytes
-rw-r--r--images/pics/Turkey/bigturkey8.gif (renamed from pics/Turkey/bigturkey8.gif)bin1620 -> 1620 bytes
-rw-r--r--images/pics/Turkey/bigturkey9.gif (renamed from pics/Turkey/bigturkey9.gif)bin1581 -> 1581 bytes
-rw-r--r--images/pics/custom.pngbin0 -> 15518 bytes
-rw-r--r--images/pics/empty.pngbin0 -> 11525 bytes
-rw-r--r--images/sounds/custom.pngbin0 -> 15518 bytes
-rw-r--r--images/sounds/mute.pngbin0 -> 3012 bytes
-rw-r--r--images/sounds/speaker.pngbin0 -> 3046 bytes
-rw-r--r--lessons.py76
-rw-r--r--lessons/en/An_Overview.txt (renamed from lp-en/An_Overview.txt)0
-rw-r--r--lessons/en/Lesson_Plan_1.txt (renamed from lp-en/Lesson_Plan_1.txt)0
-rw-r--r--lessons/en/Lesson_Plan_2.txt (renamed from lp-en/Lesson_Plan_2.txt)0
-rw-r--r--lessons/en/Lesson_Plan_3.txt (renamed from lp-en/Lesson_Plan_3.txt)0
-rw-r--r--lessons/en/Lesson_Plan_4.txt (renamed from lp-en/Lesson_Plan_4.txt)0
-rw-r--r--lessons/en/Lesson_Plan_5.txt (renamed from lp-en/Lesson_Plan_5.txt)0
-rw-r--r--lessons/es/0Una_Descripcion.txt (renamed from lp-es/0Una_Descripcion.txt)0
-rw-r--r--lessons/es/Plan_1.txt (renamed from lp-es/Plan_1.txt)0
-rw-r--r--lessons/es/Plan_2.txt (renamed from lp-es/Plan_2.txt)0
-rw-r--r--lessons/es/Plan_3.txt (renamed from lp-es/Plan_3.txt)0
-rw-r--r--lessons/es/Plan_4.txt (renamed from lp-es/Plan_4.txt)0
-rw-r--r--lessons/es/Plan_5.txt (renamed from lp-es/Plan_5.txt)0
-rw-r--r--messenger.py284
-rw-r--r--montage.py428
-rw-r--r--po/CartoonBuilder.pot141
-rw-r--r--screen.py58
-rwxr-xr-xsetup.py19
-rw-r--r--shared.py130
-rw-r--r--sound.py143
-rw-r--r--theme.py148
-rw-r--r--utils.py296
116 files changed, 2696 insertions, 1885 deletions
diff --git a/AUTHORS b/AUTHORS
new file mode 100644
index 0000000..0261dae
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1,2 @@
+Ed Stoner <ed@whsd.net>
+Aleksey Lim <alsroot@member.fsf.org>
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..623b625
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,340 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Library General
+Public License instead of this License.
diff --git a/MANIFEST b/MANIFEST
deleted file mode 100644
index 03fc84a..0000000
--- a/MANIFEST
+++ /dev/null
@@ -1,98 +0,0 @@
-cartoonbuilder.py
-config.backpics
-config.imgdirs
-config.sounds
-filmstrip.png
-backpics/bigbg01.gif
-backpics/bigbg02.gif
-backpics/bigbg03.gif
-backpics/bigbg04.gif
-backpics/bigbg05.gif
-backpics/bigbg06.gif
-backpics/bigbg07.gif
-backpics/bigbg08.gif
-backpics/bigbg09.gif
-backpics/bigbg10.gif
-backpics/bigbg11.gif
-backpics/bigbg12.gif
-backpics/bigbg13.gif
-backpics/bigbg14.gif
-backpics/bigbg15.gif
-backpics/bigbg16.gif
-backpics/bigbg17.gif
-icons/50x50blank-trans.png
-icons/big_down_arrow.png
-icons/big_left_arrow.png
-icons/big_pause.png
-icons/big_right_arrow.png
-icons/big_up_arrow.png
-icons/clear.png
-icons/down_arrow.png
-icons/filmstrip.png
-icons/left_arrow.png
-icons/logo.png
-icons/oldfilmstrip.png
-icons/pause.png
-icons/pink_arrow.png
-icons/right_arrow.png
-icons/sound_icon.png
-icons/up_arrow.png
-icons/yellow_arrow.png
-lp-en/An_Overview.txt
-lp-en/Lesson_Plan_1.txt
-lp-en/Lesson_Plan_2.txt
-lp-en/Lesson_Plan_3.txt
-lp-en/Lesson_Plan_4.txt
-lp-en/Lesson_Plan_5.txt
-lp-es/0Una_Descripcion.txt
-lp-es/Plan_1.txt
-lp-es/Plan_2.txt
-lp-es/Plan_3.txt
-lp-es/Plan_4.txt
-lp-es/Plan_5.txt
-pics/Elephant/bigelephant0.gif
-pics/Elephant/bigelephant1.gif
-pics/Elephant/bigelephant2.gif
-pics/Elephant/bigelephant3.gif
-pics/Elephant/bigelephant4.gif
-pics/Elephant/bigelephant5.gif
-pics/Elephant/bigelephant6.gif
-pics/Elephant/bigelephant7.gif
-pics/Elephant/bigelephant8.gif
-pics/Elephant/bigelephant9.gif
-pics/Elephant/bigelephant10.gif
-pics/Elephant/bigelephant11.gif
-pics/Elephant/bigelephant12.gif
-pics/Elephant/bigelephant13.gif
-pics/SpaceBlob/bigblob0.gif
-pics/SpaceBlob/bigblob1.gif
-pics/SpaceBlob/bigblob2.gif
-pics/SpaceBlob/bigblob3.gif
-pics/SpaceBlob/bigblob4.gif
-pics/SpaceBlob/bigblob5.gif
-pics/SpaceBlob/bigblob6.gif
-pics/SpaceBlob/bigblob7.gif
-pics/SpaceBlob/bigblob8.gif
-pics/SpaceBlob/bigblob9.gif
-pics/SpaceBlob/bigblob10.gif
-pics/SpaceBlob/bigblob11.gif
-pics/SpaceBlob/bigblob12.gif
-pics/SpaceBlob/bigblob13.gif
-pics/Turkey/bigturkey1.gif
-pics/Turkey/bigturkey2.gif
-pics/Turkey/bigturkey3.gif
-pics/Turkey/bigturkey4.gif
-pics/Turkey/bigturkey5.gif
-pics/Turkey/bigturkey6.gif
-pics/Turkey/bigturkey7.gif
-pics/Turkey/bigturkey8.gif
-pics/Turkey/bigturkey9.gif
-pics/Turkey/bigturkey10.gif
-pics/Turkey/bigturkey11.gif
-pics/Turkey/bigturkey12.gif
-pics/Turkey/bigturkey13.gif
-pics/Turkey/bigturkey14.gif
-sounds/funk.wav
-sounds/giggle.wav
-sounds/gobble.wav
-sounds/jungle.wav
diff --git a/todo.txt b/TODO
index 59fe161..e3df077 100644
--- a/todo.txt
+++ b/TODO
@@ -6,5 +6,7 @@ d remove my character, my background, and my sound buttons
d replace file system integration with journal integration
d add sugar toolbar with activity, background, and sound menus
d insert background from background menu (from journal)
- x insert sound from sound menu (from journal)
-- make buttons bigger
+ d insert sound from sound menu (from journal)
+d make buttons bigger
+d make interface screen resolution independent
+d add tooltips
diff --git a/activity.py b/activity.py
new file mode 100644
index 0000000..7832114
--- /dev/null
+++ b/activity.py
@@ -0,0 +1,164 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import gtk
+from gettext import gettext as _
+
+from sugar.graphics.toolbutton import ToolButton
+from sugar.graphics.toggletoolbutton import ToggleToolButton
+from sugar.activity.activity import ActivityToolbox
+
+import montage
+import lessons
+import document
+import char
+import ground
+import sound
+from shared import SharedActivity
+from messenger import Messenger, SERVICE
+from utils import *
+
+class CartoonBuilderActivity(SharedActivity):
+ def __init__(self, handle):
+ self.notebook = gtk.Notebook()
+ SharedActivity.__init__(self, self.notebook, SERVICE, handle)
+
+ self.connect('init', self._init_cb)
+ self.connect('tube', self._tube_cb)
+
+ self.notebook.show()
+ self.notebook.props.show_border = False
+ self.notebook.props.show_tabs = False
+
+ self.montage = montage.View()
+ self.notebook.append_page(self.montage)
+ self.lessons = lessons.View()
+ self.lessons.show()
+ self.notebook.append_page(self.lessons)
+
+ toolbox = ActivityToolbox(self)
+ toolbox.show()
+ toolbox.connect('current-toolbar-changed', self._toolbar_changed_cb)
+ self.set_toolbox(toolbox)
+
+ montage_bar = MontageToolbar()
+ montage_bar.show()
+ toolbox.add_toolbar(_('Montage'), montage_bar)
+
+ lessons_bar = LessonsToolbar()
+ lessons_bar.show()
+ toolbox.add_toolbar(_('Lessons'), lessons_bar)
+
+ toolbox.set_current_toolbar(1)
+
+ def read_file(self, filepath):
+ document.load(filepath)
+ char.load()
+ ground.load()
+ sound.load()
+
+ def write_file(self, filepath):
+ document.save(filepath)
+
+ def _init_cb(self, widget):
+ self.montage.restore()
+
+ def _tube_cb(self, activity, tube_conn, initiating):
+ self.messenger = Messenger(tube_conn, initiating, self.montage)
+
+ def _toolbar_changed_cb(self, widget, index):
+ if index == 2:
+ self.notebook.set_current_page(1)
+ else:
+ self.notebook.set_current_page(0)
+
+class MontageToolbar(gtk.Toolbar):
+ def __init__(self):
+ gtk.Toolbar.__init__(self)
+
+ self.playButton = ToggleToolButton('media-playback-start')
+ self.playButton.connect('toggled', self._play_cb)
+ self.insert(self.playButton, -1)
+ self.playButton.set_tooltip(_('Play / Pause'))
+
+ # Play button Image
+ self.playButtonImg = gtk.Image()
+ self.playButtonImg.show()
+ self.playButtonImg.set_from_icon_name('media-playback-start', gtk.ICON_SIZE_LARGE_TOOLBAR)
+
+ # Pause button Image
+ self.pauseButtonImg = gtk.Image()
+ self.pauseButtonImg.show()
+ self.pauseButtonImg.set_from_icon_name('media-playback-pause', gtk.ICON_SIZE_LARGE_TOOLBAR)
+
+ tempo = TempoSlider(0, 10)
+ tempo.adjustment.connect("value-changed", self._tempo_cb)
+ tempo.set_size_request(250, -1)
+ tempo.set_value(5)
+ tempo_item = gtk.ToolItem()
+ tempo_item.add(tempo)
+ self.insert(tempo_item, -1)
+
+ separator = gtk.SeparatorToolItem()
+ self.insert(separator,-1)
+
+ clear_tape = ToolButton('sl-reset')
+ clear_tape.connect('clicked', self._clear_tape_cb)
+ clear_tape.set_tooltip(_('Reset'))
+ self.insert(clear_tape, -1)
+
+ self.show_all()
+
+ def _clear_tape_cb(self, widget):
+ montage.clear_tape()
+
+ def _tempo_cb(self, widget):
+ montage.set_tempo(widget.value)
+
+ def _play_cb(self, widget):
+ if widget.get_active():
+ widget.set_icon_widget(self.pauseButtonImg)
+ sound.play()
+ montage.play()
+ else:
+ widget.set_icon_widget(self.playButtonImg)
+ sound.stop()
+ montage.stop()
+
+class LessonsToolbar(gtk.Toolbar):
+ def __init__(self):
+ gtk.Toolbar.__init__(self)
+ self._mask = False
+
+ for lesson in lessons.THEMES:
+ button = gtk.ToggleToolButton()
+ button.set_label(lesson.name)
+ button.connect('clicked', self._lessons_cb, lesson)
+ self.insert(button, -1)
+
+ self.get_nth_item(0).set_active(True)
+ self.show_all()
+
+ def _lessons_cb(self, widget, lesson):
+ if self._mask:
+ return
+ self._mask = True
+
+ for i, j in enumerate(lessons.THEMES):
+ if j != lesson:
+ self.get_nth_item(i).set_active(False)
+
+ widget.props.active = True
+ lesson.change()
+ self._mask = False
diff --git a/activity/activity.info b/activity/activity.info
index 5f7797a..a754f9a 100755..100644
--- a/activity/activity.info
+++ b/activity/activity.info
@@ -1,7 +1,8 @@
[Activity]
name = CartoonBuilder
service_name = com.ywwg.CartoonBuilderActivity
-class = cartoonbuilder.cartoonbuilderActivity
+class = activity.CartoonBuilderActivity
icon = activity-cartoonbuilder
activity_version = 1
show_launcher = yes
+license = GPLv2+
diff --git a/activity/old.activity-cartoonbuilder.svg b/activity/old.activity-cartoonbuilder.svg
deleted file mode 100644
index 1a762a3..0000000
--- a/activity/old.activity-cartoonbuilder.svg
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 12.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 51448) -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
- <!ENTITY ns_svg "http://www.w3.org/2000/svg">
- <!ENTITY ns_xlink "http://www.w3.org/1999/xlink">
-]>
-<svg version="1.1" id="Layer_1" xmlns="&ns_svg;" xmlns:xlink="&ns_xlink;" width="60.75" height="56.5" viewBox="0 0 60.75 56.5"
- overflow="visible" enable-background="new 0 0 60.75 56.5" xml:space="preserve">
-<rect x="1.75" y="1.75" fill="#FFFFFF" stroke="#000000" stroke-width="3.5" width="57.25" height="53"/>
-<line fill="none" stroke="#000000" stroke-width="3.5" x1="1.5" y1="10.5" x2="59" y2="10.5"/>
-<line fill="none" stroke="#000000" stroke-width="3.5" x1="1.5" y1="46.625" x2="59" y2="46.625"/>
-<line fill="none" stroke="#000000" stroke-width="3.5" x1="9.125" y1="1.5" x2="9.125" y2="10.5"/>
-<line fill="none" stroke="#000000" stroke-width="3.5" x1="16.229" y1="1.5" x2="16.229" y2="10.5"/>
-<line fill="none" stroke="#000000" stroke-width="3.5" x1="23.333" y1="1.5" x2="23.333" y2="10.5"/>
-<line fill="none" stroke="#000000" stroke-width="3.5" x1="30.438" y1="1.5" x2="30.438" y2="10.5"/>
-<line fill="none" stroke="#000000" stroke-width="3.5" x1="37.542" y1="1.5" x2="37.542" y2="10.5"/>
-<line fill="none" stroke="#000000" stroke-width="3.5" x1="44.646" y1="1.5" x2="44.646" y2="10.5"/>
-<line fill="none" stroke="#000000" stroke-width="3.5" x1="51.75" y1="1.5" x2="51.75" y2="10.5"/>
-<line fill="none" stroke="#000000" stroke-width="3.5" x1="9.125" y1="45.875" x2="9.125" y2="54.875"/>
-<line fill="none" stroke="#000000" stroke-width="3.5" x1="16.229" y1="45.875" x2="16.229" y2="54.875"/>
-<line fill="none" stroke="#000000" stroke-width="3.5" x1="23.333" y1="45.875" x2="23.333" y2="54.875"/>
-<line fill="none" stroke="#000000" stroke-width="3.5" x1="30.438" y1="45.875" x2="30.438" y2="54.875"/>
-<line fill="none" stroke="#000000" stroke-width="3.5" x1="37.542" y1="45.875" x2="37.542" y2="54.875"/>
-<line fill="none" stroke="#000000" stroke-width="3.5" x1="44.646" y1="45.875" x2="44.646" y2="54.875"/>
-<line fill="none" stroke="#000000" stroke-width="3.5" x1="51.75" y1="45.875" x2="51.75" y2="54.875"/>
-<line fill="none" stroke="#000000" stroke-width="3.5" x1="37.375" y1="10.375" x2="37.375" y2="47.375"/>
-<ellipse fill="none" stroke="#000000" stroke-width="3.5" cx="19.125" cy="28.188" rx="12.25" ry="12.312"/>
-<ellipse fill="none" stroke="#000000" cx="23.833" cy="24.125" rx="2.5" ry="2.417"/>
-<ellipse fill="none" stroke="#000000" cx="14.25" cy="24.125" rx="2.5" ry="2.417"/>
-<path fill="none" stroke="#000000" stroke-width="2" d="M13.042,30.667c2.108,2.886,7.219,6.563,13.083,0"/>
-<path fill="none" stroke="#000000" stroke-width="3.5" d="M57.03,40.069c-6.766,0-12.25-5.513-12.25-12.312
- s5.484-12.312,12.25-12.312"/>
-<ellipse fill="none" stroke="#000000" cx="52.156" cy="23.694" rx="2.5" ry="2.417"/>
-<path fill="none" stroke="#000000" stroke-width="2" d="M50.573,30.174c1.251,1.713,3.561,3.705,6.457,3.669"/>
-</svg> \ No newline at end of file
diff --git a/cartoonbuilder.py b/cartoonbuilder.py
deleted file mode 100755
index 4b16027..0000000
--- a/cartoonbuilder.py
+++ /dev/null
@@ -1,1724 +0,0 @@
-#!/usr/bin/env python
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
-#
-
-
-### cartoonbuilder
-###
-### author: Ed Stoner (ed@whsd.net)
-### (c) 2007 World Wide Workshop Foundation
-
-import time
-import StringIO
-import pygtk
-pygtk.require('2.0')
-import gtk
-import gobject
-import gettext
-import os
-import zipfile
-import textwrap
-import pickle
-# should really put a try in front of this
-# in case there is no sound support
-import gst
-
-_ = gettext.lgettext
-
-SERVICE = 'org.freedesktop.Telepathy.Tube.Connect'
-IFACE = SERVICE
-PATH = '/org/freedesktop/Telepathy/Tube/Connect'
-
-TRANSIMG = '50x50blank-trans.png'
-BGHEIGHT = 425
-BGWIDTH = 425
-IMGHEIGHT = 100
-IMGWIDTH = 100
-
-BORDER_LEFT = 1
-BORDER_RIGHT = 2
-BORDER_TOP = 4
-BORDER_BOTTOM = 8
-BORDER_VERTICAL = BORDER_TOP | BORDER_BOTTOM
-BORDER_HORIZONTAL = BORDER_LEFT | BORDER_RIGHT
-BORDER_ALL = BORDER_VERTICAL | BORDER_HORIZONTAL
-BORDER_ALL_BUT_BOTTOM = BORDER_HORIZONTAL | BORDER_TOP
-BORDER_ALL_BUT_LEFT = BORDER_VERTICAL | BORDER_RIGHT
-
-SLICE_BTN_WIDTH = 40
-
-# Colors from the Rich's UI design
-
-GRAY = "#B7B7B7" # gray
-PINK = "#FF0099" # pink
-YELLOW = "#FFFF00" # yellow
-WHITE = "#FFFFFF"
-BLACK = "#000000"
-BACKGROUND = "#66CC00" # light green
-BUTTON_FOREGROUND = "#CCFB99" # very light green
-BUTTON_BACKGROUND = "#027F01" # dark green
-COLOR_FG_BUTTONS = (
- (gtk.STATE_NORMAL,"#CCFF99"),
- (gtk.STATE_ACTIVE,"#CCFF99"),
- (gtk.STATE_PRELIGHT,"#CCFF99"),
- (gtk.STATE_SELECTED,"#CCFF99"),
- (gtk.STATE_INSENSITIVE,"#CCFF99"),
- ) # very light green
-COLOR_BG_BUTTONS = (
- (gtk.STATE_NORMAL,"#027F01"),
- (gtk.STATE_ACTIVE,"#CCFF99"),
- (gtk.STATE_PRELIGHT,"#016D01"),
- (gtk.STATE_SELECTED,"#CCFF99"),
- (gtk.STATE_INSENSITIVE,"#027F01"),
- )
-OLD_COLOR_BG_BUTTONS = (
- (gtk.STATE_NORMAL,"#027F01"),
- (gtk.STATE_ACTIVE,"#014D01"),
- (gtk.STATE_PRELIGHT,"#016D01"),
- (gtk.STATE_SELECTED,"#027F01"),
- (gtk.STATE_INSENSITIVE,"#027F01"),
- )
-
-SPANISH = u'Espa\xf1ol'
-#SPANISH = 'Espanol'
-LANGLIST = ['English',SPANISH]
-LANG = {'English':{'character':'My Character',
- 'sound':'My Sound',
- 'background':'My Background',
- 'lessonplan':'Lesson Plans',
- 'lpdir':'lp-en'},
- SPANISH:{'character':u'Mi car\xe1cter',
- 'sound':'Mi sonido',
- 'background':'Mi fondo',
- 'lessonplan':u'Planes de la lecci\xf3n',
- 'lpdir':'lp-es'}}
-
-def getwrappedfile(filepath,linelength):
- text = []
- f = file(filepath)
- for line in f:
- if line == '\n':
- text.append(line)
- else:
- for wline in textwrap.wrap(line.strip()):
- text.append('%s\n' % wline)
- return ''.join(text)
-
-def prepare_btn(btn, w=-1, h=-1):
- for state, color in COLOR_BG_BUTTONS:
- btn.modify_bg(state, gtk.gdk.color_parse(color))
- c = btn.get_child()
- if c is not None:
- for state, color in COLOR_FG_BUTTONS:
- c.modify_fg(state, gtk.gdk.color_parse(color))
- else:
- for state, color in COLOR_FG_BUTTONS:
- btn.modify_fg(state, gtk.gdk.color_parse(color))
- if w>0 or h>0:
- btn.set_size_request(w, h)
- return btn
-
-class FrameWidget(gtk.DrawingArea):
- def __init__(self,bgpixbuf,fgpixbuf):
- gtk.DrawingArea.__init__(self)
- self.gc = None # initialized in realize-event handler
- self.width = 0 # updated in size-allocate handler
- self.height = 0 # idem
- self.bgpixbuf = bgpixbuf
- self.fgpixbuf = fgpixbuf
- self.connect('size-allocate', self.on_size_allocate)
- self.connect('expose-event', self.on_expose_event)
- self.connect('realize', self.on_realize)
-
- def on_realize(self, widget):
- self.gc = widget.window.new_gc()
-
- def on_size_allocate(self, widget, allocation):
- self.width = allocation.width
- self.height = allocation.height
-
- def on_expose_event(self, widget, event):
- # This is where the drawing takes place
- if self.bgpixbuf:
- #bgpixbuf = gtk.gdk.pixbuf_new_from_file(self.bgimgpath)
- widget.window.draw_pixbuf(self.gc,self.bgpixbuf,0,0,0,0,-1,-1,0,0)
- if self.fgpixbuf:
- #fgpixbuf = gtk.gdk.pixbuf_new_from_file(self.fgimgpath)
- #widget.window.draw_pixbuf(self.gc,fgpixbuf,0,0,75,75,-1,-1,0,0)
- widget.window.draw_pixbuf(self.gc,self.fgpixbuf,0,0,0,0,-1,-1,0,0)
-
-
-class cartoonbuilder:
- def delete_event(self, widget, event, data=None):
- return False
-
- def destroy(self, widget, data=None):
- gtk.main_quit()
-
- def on_gstmessage(self, bus, message):
- t = message.type
- if t == gst.MESSAGE_EOS:
- # END OF SOUND FILE
- self.player.set_state(gst.STATE_NULL)
- self.player.set_state(gst.STATE_PLAYING)
- elif t == gst.MESSAGE_ERROR:
- self.player.set_state(gst.STATE_NULL)
-
- def clearframe(self, widget, data=None):
- transpixbuf = self.gettranspixbuf(IMGWIDTH,IMGHEIGHT)
- self.frameimgs[self.frame_selected].set_from_pixbuf(transpixbuf)
- self.fgpixbufs[self.frame_selected] = self.gettranspixbuf(BGWIDTH,BGHEIGHT)
- self.fgpixbuf = self.gettranspixbuf(BGWIDTH,BGHEIGHT)
- self.drawmain()
-
- def clearall(self, widget, data=None):
- for i in range(6):
- transpixbuf = self.gettranspixbuf(IMGWIDTH,IMGHEIGHT)
- self.frameimgs[i].set_from_pixbuf(transpixbuf)
- self.fgpixbufs[i] = self.gettranspixbuf(BGWIDTH,BGHEIGHT)
- self.fgpixbuf = self.gettranspixbuf(BGWIDTH,BGHEIGHT)
-
- def selectframe(self, widget, event, data=None):
- if data:
- i = data-1
- self.framebuttons[i].modify_bg(gtk.STATE_NORMAL,gtk.gdk.color_parse(YELLOW))
- self.framebuttons[i].modify_bg(gtk.STATE_PRELIGHT,gtk.gdk.color_parse(YELLOW))
- if self.frame_selected != i:
- self.framebuttons[self.frame_selected].modify_bg(gtk.STATE_NORMAL,gtk.gdk.color_parse(BLACK))
- self.framebuttons[self.frame_selected].modify_bg(gtk.STATE_PRELIGHT,gtk.gdk.color_parse(BLACK))
- #self.framebuttons[self.frame_selected].set_style(self.fbstyle)
- self.frame_selected = i
- self.fgpixbuf = self.fgpixbufs[self.frame_selected]
- self.drawmain()
-
- def pickimage(self, widget, event, data=None):
- if data:
- pixbuf = self.posepixbufs[data-1]
- scaled_buf = pixbuf.scale_simple(IMGWIDTH,IMGHEIGHT,gtk.gdk.INTERP_BILINEAR)
- self.frameimgs[self.frame_selected].set_from_pixbuf(scaled_buf)
- fgpixbuf = pixbuf.scale_simple(BGWIDTH,BGHEIGHT,gtk.gdk.INTERP_BILINEAR)
- self.fgpixbufs[self.frame_selected] = fgpixbuf
- self.fgpixbufpaths[self.frame_selected] = self.poseimgpaths[data-1]
- self.fgpixbuf = fgpixbuf
- self.drawmain()
-
- def lastback(self, widget, data=None):
- if self.backnum == 0:
- self.backnum = len(self.backpicpaths)-1
- else:
- self.backnum -= 1
- bgimgpath = self.backpicpaths[self.backnum]
- self.setback(bgimgpath)
-
- def nextback(self, widget, data=None):
- if self.backnum == (len(self.backpicpaths)-1):
- self.backnum = 0
- else:
- self.backnum += 1
- bgimgpath = self.backpicpaths[self.backnum]
- self.setback(bgimgpath)
-
- def setback(self,imgpath):
- #self.mfdraw.queue_draw()
- #pixbuf = gtk.gdk.pixbuf_new_from_file(self.bgimgpath)
- pixbuf = gtk.gdk.pixbuf_new_from_file(imgpath)
- self.bgpixbuf = pixbuf.scale_simple(BGWIDTH,BGHEIGHT,gtk.gdk.INTERP_BILINEAR)
- scaled_buf = pixbuf.scale_simple(IMGWIDTH,IMGHEIGHT,gtk.gdk.INTERP_BILINEAR)
- self.bgsmall.set_from_pixbuf(scaled_buf)
- self.drawmain()
-
- def setcharacter(self):
- pics = self.getpics(self.imgdir)
- pixbuf = gtk.gdk.pixbuf_new_from_file(pics[self.imgstartindex])
- scaled_buf = pixbuf.scale_simple(IMGWIDTH,IMGHEIGHT,gtk.gdk.INTERP_BILINEAR)
- self.ccismall.set_from_pixbuf(scaled_buf)
- self.charlabel.set_label(os.path.split(self.imgdir)[1])
-
- def lastcharacter(self, widget, data=None):
- if self.imgdirindex == 0:
- self.imgdirindex = (len(self.imgdirs)-1)
- else:
- self.imgdirindex -= 1
- self.imgstartindex = 0
- self.imgdir = self.imgdirs[self.imgdirindex]
- self.loadimages()
- self.setcharacter()
- self.drawmain()
-
- def nextcharacter(self, widget, data=None):
- if self.imgdirindex == (len(self.imgdirs)-1):
- self.imgdirindex = 0
- else:
- self.imgdirindex += 1
- self.imgstartindex = 0
- self.imgdir = self.imgdirs[self.imgdirindex]
- self.loadimages()
- self.setcharacter()
- self.drawmain()
-
- def changesound(self):
- if self.soundfile:
- soundname = os.path.splitext(os.path.split(self.soundfile)[1])[0]
- self.player.set_property('uri', 'file://' + self.soundfile)
- if self.playing:
- self.player.set_state(gst.STATE_NULL)
- self.player.set_state(gst.STATE_PLAYING)
- else:
- soundname = 'No Sound'
- if self.playing:
- self.player.set_state(gst.STATE_NULL)
- self.soundlabel.set_text(soundname.capitalize())
-
- def lastsound(self, widget, data=None):
- if self.soundindex == 0:
- self.soundindex = (len(self.sounds)-1)
- else:
- self.soundindex -= 1
- self.soundfile = self.sounds[self.soundindex]
- self.changesound()
-
- def nextsound(self, widget, data=None):
- if self.soundindex == (len(self.sounds)-1):
- self.soundindex = 0
- else:
- self.soundindex += 1
- self.soundfile = self.sounds[self.soundindex]
- self.changesound()
-
- def go(self, widget, data=None):
- self.playframenum = 0
- if self.playing:
- if self.soundfile:
- self.player.set_state(gst.STATE_NULL)
- #widget.set_label('GO!')
- playimg = gtk.Image()
- #playimg.set_from_stock(gtk.STOCK_MEDIA_PLAY,gtk.ICON_SIZE_BUTTON)
- playimg.set_from_file(os.path.join(self.iconsdir,'big_right_arrow.png'))
- playimg.show()
- widget.set_image(playimg)
- self.playing = False
- else:
- if self.soundfile:
- #self.player.set_property('uri', 'file://' + self.soundfile)
- self.player.set_state(gst.STATE_PLAYING)
- #widget.set_label('STOP')
- stopimg = gtk.Image()
- #stopimg.set_from_stock(gtk.STOCK_MEDIA_STOP,gtk.ICON_SIZE_BUTTON)
- stopimg.set_from_file(os.path.join(self.iconsdir,'big_pause.png'))
- stopimg.show()
- widget.set_image(stopimg)
- self.playing = gobject.timeout_add(self.waittime, self.playframe)
-
- def oldplayframe(self):
- self.mfdraw.fgimgpath = self.frameimgpaths[self.playframenum]
- self.mfdraw.queue_draw()
- self.playframenum += 1
- if self.playframenum == 6:
- self.playframenum = 0
- if self.playing:
- return True
- else:
- return False
-
- def playframe(self):
- self.fgpixbuf = self.fgpixbufs[self.playframenum]
- self.drawmain()
- self.playframenum += 1
- if self.playframenum == 6:
- self.playframenum = 0
- # SOUND HANDLING
- #if self.bus.have_pending:
- # print 'PENDING ITEMS ON SOUND BUS'
- # END OF SOUND HANDLING
- if self.playing:
- return True
- else:
- return False
-
- def drawmain(self):
- #if not self.fgimgpath:
- # pixbuf2 = gtk.gdk.pixbuf_new_from_file(self.bgimgpath)
- # sbuf2 = pixbuf2.scale_simple(BGHEIGHT,BGWIDTH,gtk.gdk.INTERP_BILINEAR)
- # self.mainimage.set_from_pixbuf(sbuf2)
- # return
-
- # COMPOSITING FROM FILE PATHS
- #pixbuf = gtk.gdk.pixbuf_new_from_file(self.fgimgpath)
- #sbuf = pixbuf.scale_simple(BGHEIGHT,BGWIDTH,gtk.gdk.INTERP_BILINEAR)
- #pixbuf2 = gtk.gdk.pixbuf_new_from_file(self.bgimgpath)
- #sbuf2 = pixbuf2.scale_simple(BGHEIGHT,BGWIDTH,gtk.gdk.INTERP_BILINEAR)
- #sbuf.composite(sbuf2,0,0,sbuf.props.width,sbuf.props.height,
- # 0,0,1.0,1.0,gtk.gdk.INTERP_HYPER,255)
-
- # COMPOSITING FROM PIXBUFS
- #sbuf = self.fgpixbuf.copy()
- #sbuf2 = self.bgpixbuf.copy()
- #sbuf.composite(sbuf2,0,0,sbuf.props.width,sbuf.props.height,
- # 0,0,1.0,1.0,gtk.gdk.INTERP_HYPER,255)
- #self.mainimage.set_from_pixbuf(sbuf2)
-
- # USING DRAWING AREA
- self.mfdraw.fgpixbuf = self.fgpixbuf
- self.mfdraw.bgpixbuf = self.bgpixbuf
- self.mfdraw.queue_draw()
-
- def getbackgroundfile(self, widget, data=None):
- dialog = gtk.FileChooserDialog(title="Open..",
- action=gtk.FILE_CHOOSER_ACTION_OPEN,
- buttons=(gtk.STOCK_CANCEL,
- gtk.RESPONSE_CANCEL,
- gtk.STOCK_OPEN, gtk.RESPONSE_OK))
- if self.insugar:
- dialog.set_current_folder('/home/olpc')
- dialog.set_default_response(gtk.RESPONSE_OK)
-
- #filter = gtk.FileFilter()
- #filter.set_name("All files")
- #filter.add_pattern("*")
- #dialog.add_filter(filter)
-
- filter = gtk.FileFilter()
- filter.set_name("Images")
- filter.add_mime_type("image/png")
- filter.add_mime_type("image/jpeg")
- filter.add_mime_type("image/gif")
- filter.add_pattern("*.png")
- filter.add_pattern("*.jpg")
- filter.add_pattern("*.gif")
- dialog.add_filter(filter)
-
- response = dialog.run()
- if response == gtk.RESPONSE_OK:
- #print dialog.get_filename(), 'selected'
- bgimgpath = dialog.get_filename()
- self.backpicpaths.append(bgimgpath)
- self.backnum = self.backpicpaths.index(bgimgpath)
- self.setback(bgimgpath)
- f = file(os.path.join(self.mdirpath,'config.backpics'),'a')
- f.write('%s\n' % bgimgpath)
- f.close()
- elif response == gtk.RESPONSE_CANCEL:
- # print 'Closed, no files selected'
- pass
- dialog.destroy()
-
- def getimgdir(self, widget, data=None):
- daction = gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER
- dialog = gtk.FileChooserDialog(title='Select Folder',
- action=daction,
- buttons=(gtk.STOCK_CANCEL,
- gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN,
- gtk.RESPONSE_OK))
- if self.insugar:
- dialog.set_current_folder('/home/olpc')
- dialog.set_default_response(gtk.RESPONSE_OK)
- response = dialog.run()
- if response == gtk.RESPONSE_OK:
- #print dialog.get_filename(), 'selected'
- imgdir = dialog.get_filename()
- pics = self.getpics(imgdir)
- if pics:
- self.imgdir = imgdir
- self.imgdirs.append(imgdir)
- self.imgdirindex = self.imgdirs.index(imgdir)
- self.loadimages()
- self.setcharacter()
- self.drawmain()
- f = file(os.path.join(self.mdirpath,'config.imgdirs'),'a')
- f.write('%s\n' % imgdir)
- f.close()
- elif response == gtk.RESPONSE_CANCEL:
- pass
- dialog.destroy()
-
- def getsoundfile(self, widget, data=None):
- dialog = gtk.FileChooserDialog(title="Open..",
- action=gtk.FILE_CHOOSER_ACTION_OPEN,
- buttons=(gtk.STOCK_CANCEL,
- gtk.RESPONSE_CANCEL,
- gtk.STOCK_OPEN, gtk.RESPONSE_OK))
- if self.insugar:
- dialog.set_current_folder('/home/olpc')
- dialog.set_default_response(gtk.RESPONSE_OK)
-
- filter = gtk.FileFilter()
- filter.set_name("Sounds")
- #filter.add_mime_type("image/png")
- filter.add_pattern('*.wav')
- filter.add_pattern('*.mp3')
- filter.add_pattern('*.ogg')
- dialog.add_filter(filter)
-
- response = dialog.run()
- if response == gtk.RESPONSE_OK:
- soundfilepath = dialog.get_filename()
- self.sounds.append(soundfilepath)
- self.soundfile = soundfilepath
- self.soundindex = self.sounds.index(soundfilepath)
- self.changesound()
- f = file(os.path.join(self.mdirpath,'config.sounds'),'a')
- f.write('%s\n' % soundfilepath)
- f.close()
- elif response == gtk.RESPONSE_CANCEL:
- # print 'Closed, no files selected'
- pass
- dialog.destroy()
-
- def getsdata(self):
- #self.lessonplans.set_label('getting sdata')
- # THE BELOW SHOULD WORK BUT DOESN'T
- #zf = StringIO.StringIO()
- #self.savetozip(zf)
- #zf.seek(0)
- #sdata = zf.read()
- #zf.close()
- # END OF STUFF THAT DOESN'T WORK
- sdd = {}
- tmpimgdir = os.path.join(self.mdirpath,'tmpimg')
- tmpbgpath = os.path.join(tmpimgdir,'back.png')
- self.bgpixbuf.save(tmpbgpath,'png')
- sdd['pngdata'] = file(tmpbgpath).read()
- os.remove(tmpbgpath)
- sdd['fgpixbufpaths'] = self.fgpixbufpaths
- #sdd['fgpixbufs'] = []
- #count = 1
- #for pixbuf in self.fgpixbufs:
- # filename = '%02d.png' % count
- # filepath = os.path.join(tmpimgdir,filename)
- # pixbuf.save(filepath,'png')
- # sdd['fgpixbufs'].append(file(filepath).read())
- # os.remove(filepath)
- # count += 1
- return pickle.dumps(sdd)
-
- def restore(self, sdata):
- # THE BELOW SHOULD WORK BUT DOESN'T
- #zf = StringIO.StringIO(sdata)
- #self.loadfromzip(zf)
- # END OF STUFF THAT DOESN'T WORK
- sdd = pickle.loads(sdata)
- tmpimgdir = os.path.join(self.mdirpath,'tmpimg')
- tmpbgpath = os.path.join(tmpimgdir,'back.png')
- f = file(tmpbgpath,'w')
- f.write(sdd['pngdata'])
- f.close()
- self.setback(tmpbgpath)
- os.remove(tmpbgpath)
- transimgpath = os.path.join(self.iconsdir,TRANSIMG)
- for i in range(len(sdd['fgpixbufpaths'])):
- filepath = sdd['fgpixbufpaths'][i]
- if filepath == transimgpath:
- continue
- pixbuf = gtk.gdk.pixbuf_new_from_file(filepath)
- fgpixbuf = pixbuf.scale_simple(BGWIDTH,BGHEIGHT,gtk.gdk.INTERP_BILINEAR)
- self.fgpixbufs[i] = fgpixbuf
- if i == 0:
- self.fgpixbuf = fgpixbuf
- self.drawmain()
- scaled_buf = pixbuf.scale_simple(IMGWIDTH,IMGHEIGHT,gtk.gdk.INTERP_BILINEAR)
- self.frameimgs[i].set_from_pixbuf(scaled_buf)
-
- def savefile(self, widget, data=None):
- daction = gtk.FILE_CHOOSER_ACTION_SAVE
- dialog = gtk.FileChooserDialog(title='Save Animation',
- action=daction,
- buttons=(gtk.STOCK_CANCEL,
- gtk.RESPONSE_CANCEL, gtk.STOCK_SAVE,
- gtk.RESPONSE_OK))
- if self.insugar:
- dialog.set_current_folder('/home/olpc')
- dialog.set_current_name('cartoon.zip')
- dialog.set_default_response(gtk.RESPONSE_OK)
- response = dialog.run()
- if response == gtk.RESPONSE_OK:
- filepath = dialog.get_filename()
- zf = file(filepath,'w')
- self.savetozip(zf)
- elif response == gtk.RESPONSE_CANCEL:
- pass
- dialog.destroy()
-
- def savetozip(self, f):
- # print filepath
- #zf = zipfile.ZipFile(filepath,'w')
- zf = zipfile.ZipFile(f,'w')
- # add the background file
- tmpimgdir = os.path.join(self.mdirpath,'tmpimg')
- tmpbgpath = os.path.join(tmpimgdir,'back.png')
- self.bgpixbuf.save(tmpbgpath,'png')
- zf.write(tmpbgpath)
- os.remove(tmpbgpath)
- # add the frames
- count = 1
- for pixbuf in self.fgpixbufs:
- filename = '%02d.png' % count
- filepath = os.path.join(tmpimgdir,filename)
- pixbuf.save(filepath,'png')
- zf.write(filepath)
- os.remove(filepath)
- count += 1
- zf.close()
-
- def loadfile(self, widget, data=None):
- daction = gtk.FILE_CHOOSER_ACTION_OPEN
- dialog = gtk.FileChooserDialog(title='Select File',
- action=daction,
- buttons=(gtk.STOCK_CANCEL,
- gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN,
- gtk.RESPONSE_OK))
- if self.insugar:
- dialog.set_current_folder('/home/olpc')
- dialog.set_default_response(gtk.RESPONSE_OK)
- filter = gtk.FileFilter()
- filter.set_name("Zipfiles")
- #filter.add_mime_type("image/gif")
- filter.add_pattern("*.zip")
- dialog.add_filter(filter)
- response = dialog.run()
- if response == gtk.RESPONSE_OK:
- filepath = dialog.get_filename()
- zf = file(filepath,'r')
- self.loadfromzip(zf)
- elif response == gtk.RESPONSE_CANCEL:
- pass
- dialog.destroy()
-
- def loadfromzip(self, f):
- # print filepath
- #zf = zipfile.ZipFile(filepath,'r')
- zf = zipfile.ZipFile(f)
- fnames = zf.namelist()
- framenames = []
- for fname in fnames:
- if fname[-8:] == 'back.png':
- backname = fname
- else:
- framenames.append(fname)
- framenames.sort()
- # set the background
- tmpimgdir = os.path.join(self.mdirpath,'tmpimg')
- tmpbgpath = os.path.join(tmpimgdir,'back.png')
- f = file(tmpbgpath,'w')
- f.write(zf.read(backname))
- f.close()
- self.setback(tmpbgpath)
- os.remove(tmpbgpath)
- self.imgdir = tmpimgdir
- for filepath in framenames:
- fname = os.path.split(filepath)[1]
- tmpfilepath = os.path.join(tmpimgdir,fname)
- f = file(tmpfilepath,'w')
- f.write(zf.read(filepath))
- f.close()
- zf.close()
- self.loadimages()
- #self.setcharacter()
- # setup the filmstrip frames
- pics = self.getpics(self.imgdir)
- count = 0
- for imgpath in pics:
- pixbuf = gtk.gdk.pixbuf_new_from_file(imgpath)
- fgpixbuf = pixbuf.scale_simple(BGWIDTH,BGHEIGHT,gtk.gdk.INTERP_BILINEAR)
- self.fgpixbufs[count] = fgpixbuf
- if count == 0:
- self.fgpixbuf = fgpixbuf
- self.drawmain()
- scaled_buf = pixbuf.scale_simple(IMGWIDTH,IMGHEIGHT,gtk.gdk.INTERP_BILINEAR)
- self.frameimgs[count].set_from_pixbuf(scaled_buf)
- count += 1
- entries = os.listdir(tmpimgdir)
- for entry in entries:
- entrypath = os.path.join(tmpimgdir,entry)
- os.remove(entrypath)
-
- def setplayspeed(self,adj):
- self.waittime = int((6-adj.value)*150)
- if self.playing:
- gobject.source_remove(self.playing)
- self.playing = gobject.timeout_add(self.waittime, self.playframe)
-
- def loadimages(self):
- self.posepixbufs = []
- self.poseimgpaths = []
- pics = self.getpics(self.imgdir)
- count = 0
- for imgpath in pics[self.imgstartindex:self.imgstartindex+10]:
- pixbuf = gtk.gdk.pixbuf_new_from_file(imgpath)
- scaled_buf = pixbuf.scale_simple(IMGWIDTH,IMGHEIGHT,gtk.gdk.INTERP_BILINEAR)
- self.posepixbufs.append(pixbuf)
- self.poseimgpaths.append(imgpath)
- self.images[count].set_from_pixbuf(scaled_buf)
- count += 1
- for i in range(count,10):
- transpixbuf = self.gettranspixbuf(IMGWIDTH,IMGHEIGHT)
- imgpath = os.path.join(self.iconsdir,TRANSIMG)
- img = gtk.Image()
- img.set_from_pixbuf(transpixbuf)
- img.show()
- self.posepixbufs.append(pixbuf)
- self.poseimgpaths.append(imgpath)
- self.images[i].set_from_pixbuf(transpixbuf)
-
- def getpics(self, dirpath):
- pics = []
- entries = os.listdir(dirpath)
- entries.sort()
- for entry in entries:
- if entry[-4:].lower() in ['.png','.gif','.jpg']:
- filepath = os.path.join(dirpath,entry)
- pics.append(filepath)
- return pics
-
-
- def imgup(self, widget, data=None):
- pics = self.getpics(self.imgdir)
- if self.imgstartindex > 0:
- self.imgstartindex -= 2
- self.loadimages()
- self.setcharacter()
- self.drawmain()
-
- def imgdown(self, widget, data=None):
- pics = self.getpics(self.imgdir)
- if len(pics[self.imgstartindex:]) > 10:
- self.imgstartindex += 2
- self.loadimages()
- self.setcharacter()
- self.drawmain()
-
- def gettranspixbuf(self, width=50, height=50):
- transimgpath = os.path.join(self.iconsdir,TRANSIMG)
- pixbuf = gtk.gdk.pixbuf_new_from_file(transimgpath)
- if width == 50 and height == 50:
- return pixbuf
- scaled_buf = pixbuf.scale_simple(width,height,gtk.gdk.INTERP_BILINEAR)
- return scaled_buf
-
- def showlessonplans(self, widget, data=None):
- dia = gtk.Dialog(title='Lesson Plans',
- parent=None,
- flags=0,
- buttons=None)
- dia.set_default_size(500,500)
- dia.show()
-
- #dia.vbox.pack_start(scrolled_window, True, True, 0)
- notebook = gtk.Notebook()
- # uncomment below to highlight tabs
- notebook.modify_bg(gtk.STATE_NORMAL,gtk.gdk.color_parse(WHITE))
- notebook.set_tab_pos(gtk.POS_TOP)
- #notebook.set_default_size(400,400)
- notebook.show()
- lessonplans = {}
- lpdir = os.path.join(self.mdirpath,LANG[self.language]['lpdir'])
- lpentries = os.listdir(lpdir)
- for entry in lpentries:
- fpath = os.path.join(lpdir,entry)
- lessonplans[entry] = getwrappedfile(fpath,80)
- lpkeys = lessonplans.keys()
- lpkeys.sort()
- for lpkey in lpkeys:
- lpname = lpkey.replace('_',' ').replace('0','')[:-4]
- label = gtk.Label(lessonplans[lpkey])
- #if self.insugar:
- # label.modify_fg(gtk.STATE_NORMAL,gtk.gdk.color_parse(WHITE))
- eb = gtk.EventBox()
- eb.modify_bg(gtk.STATE_NORMAL,gtk.gdk.color_parse(WHITE))
- #label.set_line_wrap(True)
- label.show()
- eb.add(label)
- eb.show()
- #tlabel = gtk.Label('Lesson Plan %s' % str(i+1))
- tlabel = gtk.Label(lpname)
- tlabel.show()
- scrolled_window = gtk.ScrolledWindow()
- scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_ALWAYS)
- scrolled_window.show()
- scrolled_window.add_with_viewport(eb)
- notebook.append_page(scrolled_window, tlabel)
- #dia.action_area.pack_start(notebook, True, True, 0)
- dia.vbox.pack_start(notebook, True, True, 0)
- result = dia.run()
- dia.destroy()
-
- def changed_cb(self, combobox):
- model = combobox.get_model()
- index = combobox.get_active()
- if index:
- lang = model[index][0]
- if lang == 'Espa\xc3\xb1ol':
- lang = SPANISH
- if lang in LANG:
- self.lessonplans.set_label(LANG[lang]['lessonplan'])
- prepare_btn(self.lessonplans)
- if not self.insugar:
- self.character.set_label(LANG[lang]['character'])
- prepare_btn(self.character)
- self.bgbutton.set_label(LANG[lang]['background'])
- prepare_btn(self.bgbutton)
- self.soundbutton.set_label(LANG[lang]['sound'])
- prepare_btn(self.soundbutton)
- else:
- print repr(lang)
- return
-
- def changebuttonlang(self):
- self.lessonplans.set_label(LANG[self.language]['lessonplan'])
- prepare_btn(self.lessonplans)
- self.lang.set_label(self.language)
- prepare_btn(self.lang)
- self.character.set_label(LANG[self.language]['character'])
- prepare_btn(self.character)
- self.bgbutton.set_label(LANG[self.language]['background'])
- prepare_btn(self.bgbutton)
- self.soundbutton.set_label(LANG[self.language]['sound'])
- prepare_btn(self.soundbutton)
-
- def setlastlanguage(self, widget, data=None):
- li = LANGLIST.index(self.language)
- if li == 0:
- self.language = LANGLIST[len(LANGLIST)-1]
- else:
- self.language = LANGLIST[li-1]
- self.changebuttonlang()
-
- def setnextlanguage(self, widget, data=None):
- li = LANGLIST.index(self.language)
- if li == (len(LANGLIST)-1):
- self.language = LANGLIST[0]
- else:
- self.language = LANGLIST[li+1]
- self.changebuttonlang()
-
- def getdefaultlang(self):
- return 'English'
-
- def __init__(self,insugar,toplevel_window,mdirpath):
- self.mdirpath = mdirpath
- tmpimgdir = os.path.join(self.mdirpath,'tmpimg')
- if not os.path.isdir(tmpimgdir):
- os.mkdir(tmpimgdir)
- self.iconsdir = os.path.join(self.mdirpath,'icons')
- self.playing = False
- self.backnum = 0
- self.backpicpaths = []
- bpfile = file(os.path.join(self.mdirpath,'config.backpics'))
- for line in bpfile:
- bpfilepath = line.strip()
- if bpfilepath[0] != '/':
- bpfilepath = os.path.join(self.mdirpath,line.strip())
- if os.path.isfile(bpfilepath):
- self.backpicpaths.append(bpfilepath)
- bpfile.close()
- self.waittime = 3*150
- self.insugar = insugar
- self.language = self.getdefaultlang()
- self.imgdirs = []
- imgdirfile = file(os.path.join(self.mdirpath,'config.imgdirs'))
- for line in imgdirfile:
- imgdirpath = line.strip()
- if imgdirpath[0] != '/':
- imgdirpath = os.path.join(self.mdirpath,line.strip())
- if os.path.isdir(imgdirpath):
- self.imgdirs.append(imgdirpath)
- imgdirfile.close()
- self.imgdirindex = 0
- self.imgstartindex = 0
- self.sounds = ['']
- soundfile = file(os.path.join(self.mdirpath,'config.sounds'))
- for line in soundfile:
- soundfilepath = line.strip()
- if soundfilepath[0] != '/':
- soundfilepath = os.path.join(self.mdirpath,line.strip())
- if os.path.isfile(soundfilepath):
- self.sounds.append(soundfilepath)
- soundfile.close()
- self.soundindex = 0
- self.soundfile = self.sounds[self.soundindex]
- # START GSTREAMER STUFF
- self.player = gst.element_factory_make("playbin", "player")
- fakesink = gst.element_factory_make('fakesink', "my-fakesink")
- self.player.set_property("video-sink", fakesink)
- self.bus = self.player.get_bus()
- self.bus.add_signal_watch()
- self.bus.connect('message', self.on_gstmessage)
- # END GSTREAMER STUFF
- self.fgpixbuf = self.gettranspixbuf(BGWIDTH,BGHEIGHT)
-
- self.mpbox = gtk.VBox()
-
- self.main = gtk.EventBox()
- self.main.show()
- self.main.modify_bg(gtk.STATE_NORMAL,gtk.gdk.color_parse(BUTTON_BACKGROUND))
- self.mainbox = gtk.EventBox()
- self.mainbox.modify_bg(gtk.STATE_NORMAL,gtk.gdk.color_parse(BACKGROUND))
- self.mainbox.set_border_width(5)
- self.mainbox.show()
- self.main.add(self.mainbox)
- self.mpbox.show()
- self.logobox = gtk.HBox(False,0)
- self.logobox.show()
- self.logo = gtk.Image()
- self.logo.show()
- self.logo.set_from_file(os.path.join(self.iconsdir,'logo.png'))
- self.logobox.pack_start(self.logo,False,False,0)
- self.lessonplans = gtk.Button('Lesson Plans')
- self.lessonplans.connect('clicked',self.showlessonplans, None)
- prepare_btn(self.lessonplans)
- self.lessonplans.show()
- self.lpvbox = gtk.VBox()
- self.lpvbox.show()
- self.lpvbox.pack_start(self.lessonplans,True,False,0)
- self.lpoframe = gtk.EventBox()
- self.lpoframe.modify_bg(gtk.STATE_NORMAL,gtk.gdk.color_parse(YELLOW))
- self.lpoframe.show()
- self.lpframe = gtk.EventBox()
- self.lpframe.modify_bg(gtk.STATE_NORMAL,gtk.gdk.color_parse(BACKGROUND))
- self.lpframe.show()
- self.lpalign = gtk.Alignment(1.0,1.0,1.0,1.0)
- self.lpalign.add(self.lpframe)
- self.lpalign.set_padding(5,0,5,0)
- self.lpalign.show()
- self.lpoframe.add(self.lpalign)
- self.lphbox = gtk.HBox()
- self.lphbox.show()
- self.lphbox.pack_start(self.lpvbox,True,False,0)
- self.lpframe.add(self.lphbox)
- self.logobox.pack_start(self.lpoframe,True,True,0)
- self.langdd = gtk.combo_box_new_text()
- self.langdd.append_text('Language')
- self.langdd.append_text('English')
- self.langdd.append_text(SPANISH)
- self.langdd.connect('changed', self.changed_cb)
- self.langdd.set_active(0)
- self.langdd.show()
- self.langddvbox = gtk.VBox()
- self.langddvbox.show()
- self.langddvbox.pack_start(self.langdd,True,False,0)
- #vvvv LANGUAGE BUTTONS vvvv
- #self.lastlang = gtk.Button()
- #self.lastlang.connect('clicked', self.setlastlanguage, None)
- #llla = gtk.Image()
- #llla.set_from_file(os.path.join(self.iconsdir,'left_arrow.png'))
- #llla.show()
- #self.lastlang.add(llla)
- #prepare_btn(self.lastlang)
- #self.lastlang.show()
- #self.llvbox = gtk.VBox()
- #self.llvbox.show()
- #self.llvbox.pack_start(self.lastlang,True,False,0)
- #self.lang = gtk.Button(self.language)
- #prepare_btn(self.lang)
- #self.lang.show()
- #self.nextlang = gtk.Button()
- #self.nextlang.connect('clicked', self.setnextlanguage, None)
- #nlra = gtk.Image()
- #nlra.set_from_file(os.path.join(self.iconsdir,'right_arrow.png'))
- #nlra.show()
- #self.nextlang.add(nlra)
- #prepare_btn(self.nextlang)
- #self.nextlang.show()
- #self.nlvbox = gtk.VBox()
- #self.nlvbox.show()
- #self.nlvbox.pack_start(self.nextlang,True,False,0)
- #self.langvbox = gtk.VBox()
- #self.langvbox.show()
- #self.langvbox.pack_start(self.lang,True,False,0)
- #^^^^ LANGUAGE BUTTONS^^^^
- self.langoframe = gtk.EventBox()
- self.langoframe.modify_bg(gtk.STATE_NORMAL,gtk.gdk.color_parse(YELLOW))
- self.langoframe.show()
- self.langframe = gtk.EventBox()
- self.langframe.modify_bg(gtk.STATE_NORMAL,gtk.gdk.color_parse(BACKGROUND))
- self.langframe.show()
- self.langalign = gtk.Alignment(1.0,1.0,1.0,1.0)
- self.langalign.add(self.langframe)
- if not self.insugar:
- self.langalign.set_padding(5,0,5,0)
- else:
- self.langalign.set_padding(5,0,5,5)
- self.langalign.show()
- self.langoframe.add(self.langalign)
- self.langhbox = gtk.HBox()
- self.langhbox.show()
- #self.langhbox.pack_start(self.llvbox,True,False,0)
- #self.langhbox.pack_start(self.langvbox,True,False,0)
- #self.langhbox.pack_start(self.nlvbox,True,False,0)
- self.langhbox.pack_start(self.langddvbox,True,False,0)
- self.langframe.add(self.langhbox)
- self.logobox.pack_start(self.langoframe,True,True,0)
-
- if not self.insugar:
- self.sooframe = gtk.EventBox()
- self.sooframe.modify_bg(gtk.STATE_NORMAL,gtk.gdk.color_parse(YELLOW))
- self.sooframe.show()
- self.soframe = gtk.EventBox()
- self.soframe.modify_bg(gtk.STATE_NORMAL,gtk.gdk.color_parse(BACKGROUND))
- self.soframe.show()
- self.soalign = gtk.Alignment(1.0,1.0,1.0,1.0)
- self.soalign.add(self.soframe)
- self.soalign.set_padding(5,0,5,5)
- self.soalign.show()
- self.sooframe.add(self.soalign)
- self.fsvbox = gtk.VBox()
- self.fsvbox.show()
- self.fileopen = gtk.Button()
- openimg = gtk.Image()
- openimg.set_from_stock(gtk.STOCK_OPEN,gtk.ICON_SIZE_BUTTON)
- openimg.show()
- prepare_btn(self.fileopen)
- self.fileopen.set_label('')
- self.fileopen.set_image(openimg)
- self.fileopen.connect('clicked',self.loadfile, None)
- self.fileopen.show()
- self.fovbox = gtk.VBox()
- self.fovbox.show()
- self.fovbox.pack_start(self.fileopen,True,False,0)
- self.fohbox = gtk.HBox()
- self.fohbox.show()
- self.fohbox.pack_start(self.fovbox,True,False,0)
- self.filesave = gtk.Button()
- saveimg = gtk.Image()
- saveimg.set_from_stock(gtk.STOCK_SAVE,gtk.ICON_SIZE_BUTTON)
- saveimg.show()
- prepare_btn(self.filesave)
- self.filesave.set_label('')
- self.filesave.set_image(saveimg)
- self.filesave.connect('clicked',self.savefile, None)
- self.filesave.show()
- self.fsvbox.pack_start(self.filesave,True,False,0)
- self.fohbox.pack_start(self.fsvbox,True,False,0)
- self.soframe.add(self.fohbox)
- self.logobox.pack_start(self.sooframe,True,True,0)
-
- self.mpbox.pack_start(self.logobox,False,False,0)
-
- self.centerframeborder = gtk.EventBox()
- self.centerframeborder.modify_bg(gtk.STATE_NORMAL,gtk.gdk.color_parse(YELLOW))
- self.centerframeborder.show()
- self.ocenterframe = gtk.EventBox()
- self.ocenterframe.modify_bg(gtk.STATE_NORMAL,gtk.gdk.color_parse(BACKGROUND))
- self.ocenterframe.set_border_width(5)
- self.ocenterframe.show()
- self.centerframeborder.add(self.ocenterframe)
- self.centerframe = gtk.EventBox()
- self.centerframe.modify_bg(gtk.STATE_NORMAL,gtk.gdk.color_parse(BACKGROUND))
- self.centerframe.set_border_width(5)
- self.centerframe.show()
- self.ocenterframe.add(self.centerframe)
- self.hbox = gtk.HBox()
- self.hbox.show()
- self.mainbox.add(self.mpbox)
- self.centerframe.add(self.hbox)
- self.mpbox.pack_start(self.centerframeborder,True,True,0)
-
-
- self.tvbox = gtk.VBox()
- self.tvbox.show()
- # flow arrows
- flowbox = gtk.HBox()
- flowbox.show()
- yellow_arrow = gtk.Image()
- yellow_arrow.set_from_file(os.path.join(self.iconsdir, 'yellow_arrow.png'))
- yellow_arrow.show()
- flowbox.pack_end(yellow_arrow,True,False,0)
- topspace = gtk.EventBox()
- topspace.modify_bg(gtk.STATE_NORMAL,gtk.gdk.color_parse(BACKGROUND))
- topspace.show()
- topspace.set_border_width(15)
- self.tvbox.pack_start(topspace,False,False,0)
- self.tvbox.pack_start(flowbox,False,False,0)
- self.table = gtk.Table(rows=7, columns=2, homogeneous=False)
-
- self.imgbuttons = []
- self.images = []
- # POSE CHOOSER BUTTONS
- for i in range(1,11):
- ib = gtk.EventBox()
- #ib = gtk.Button()
- #ib.connect('clicked', self.pickimage, i)
- ib.set_events(gtk.gdk.BUTTON_PRESS_MASK)
- ib.connect('button_press_event', self.pickimage, i)
- ib.set_border_width(1)
- #ib.add(img)
- ib.modify_bg(gtk.STATE_NORMAL,gtk.gdk.color_parse(BLACK))
- ib.modify_bg(gtk.STATE_PRELIGHT,gtk.gdk.color_parse(BLACK))
- ib.show()
- img = gtk.Image()
- img.show()
- #ib.set_label('')
- #ib.set_image(img)
- ib.add(img)
- self.imgbuttons.append(ib)
- self.images.append(img)
-
- self.imgupbutton = gtk.Button()
- self.imgupbutton.connect('clicked', self.imgup, None)
- self.imgupbutton.show()
- #upa = gtk.Arrow(gtk.ARROW_UP,gtk.SHADOW_OUT)
- #upa.show()
- upa = gtk.Image()
- upa.set_from_file(os.path.join(self.iconsdir,'big_up_arrow.png'))
- #upapixbuf = gtk.gdk.pixbuf_new_from_file(os.path.join(self.iconsdir,'up_arrow.png'))
- #scaled_upapixbuf = upapixbuf.scale_simple(42,34,gtk.gdk.INTERP_BILINEAR)
- #upa.set_from_pixbuf(scaled_upapixbuf)
- upa.show()
-
- self.imgupbutton.add(upa)
- prepare_btn(self.imgupbutton)
- self.iubhbox = gtk.HBox()
- self.iubhbox.show()
- self.iubhbox.pack_start(self.imgupbutton,True,True,150)
- self.tvbox.pack_start(self.iubhbox,False,False,0)
-
- self.table.attach(self.imgbuttons[0],0,1,0,1)
- self.table.attach(self.imgbuttons[1],1,2,0,1)
- self.table.attach(self.imgbuttons[2],0,1,1,2)
- self.table.attach(self.imgbuttons[3],1,2,1,2)
- self.table.attach(self.imgbuttons[4],0,1,2,3)
- self.table.attach(self.imgbuttons[5],1,2,2,3)
- self.table.attach(self.imgbuttons[6],0,1,3,4)
- self.table.attach(self.imgbuttons[7],1,2,3,4)
- self.table.attach(self.imgbuttons[8],0,1,4,5)
- self.table.attach(self.imgbuttons[9],1,2,4,5)
- self.table.show()
-
- self.tableframeborder = gtk.EventBox()
- self.tableframeborder.modify_bg(gtk.STATE_NORMAL,gtk.gdk.color_parse(YELLOW))
- self.tableframeborder.show()
- self.tableframe = gtk.EventBox()
- self.tableframe.modify_bg(gtk.STATE_NORMAL,gtk.gdk.color_parse(BACKGROUND))
- self.tableframe.show()
- self.tableframe.set_border_width(5)
- self.tableframeborder.add(self.tableframe)
- self.tableframe.add(self.table)
-
- self.tfhbox = gtk.HBox()
- self.tfhbox.show()
- self.tfhbox.pack_start(self.tableframeborder,True,False,20)
- self.tvbox.pack_start(self.tfhbox,False,False,0)
-
- self.imgdownbutton = gtk.Button()
- self.imgdownbutton.connect('clicked', self.imgdown, None)
- self.imgdownbutton.show()
- #downa = gtk.Arrow(gtk.ARROW_DOWN,gtk.SHADOW_OUT)
- #downa.show()
- downa = gtk.Image()
- downa.set_from_file(os.path.join(self.iconsdir,'big_down_arrow.png'))
- downa.show()
- self.imgdownbutton.add(downa)
- prepare_btn(self.imgdownbutton)
- self.idbhbox = gtk.HBox()
- self.idbhbox.show()
- self.idbhbox.pack_start(self.imgdownbutton,True,True,150)
- self.tvbox.pack_start(self.idbhbox,False,False,0)
- self.hbox.pack_start(self.tvbox,True,True,0)
-
- self.imgdir = self.imgdirs[self.imgdirindex]
- self.loadimages()
-
- self.rightbox = gtk.VBox()
- self.rightbox.show()
-
- # ANIMATION FRAMES / FILMSTRIP
- self.tophbox = gtk.HBox()
- self.tophbox.show()
- # animation frames
- self.animhbox = gtk.HBox()
- self.animhbox.show()
- self.framebuttons = []
- self.frameimgs = []
- self.fgpixbufs = []
- self.fgpixbufpaths = []
- transimgpath = os.path.join(self.iconsdir,TRANSIMG)
- for i in range(6):
- #fb = gtk.Button()
- #fb.connect('clicked', self.selectframe, i+1)
- fb = gtk.EventBox()
- fb.set_events(gtk.gdk.BUTTON_PRESS_MASK)
- fb.connect('button_press_event', self.selectframe, i+1)
- fb.modify_bg(gtk.STATE_NORMAL,gtk.gdk.color_parse(BLACK))
- fb.modify_bg(gtk.STATE_PRELIGHT,gtk.gdk.color_parse(BLACK))
- fb.show()
- self.framebuttons.append(fb)
- tpixbuf = self.gettranspixbuf(BGWIDTH,BGHEIGHT)
- self.fgpixbufs.append(tpixbuf)
- self.fgpixbufpaths.append(transimgpath)
- #fb.set_label('')
- transimg = gtk.Image()
- transimg.set_from_pixbuf(self.gettranspixbuf(IMGWIDTH,IMGHEIGHT))
- transimg.show()
- self.frameimgs.append(transimg)
- #fb.set_image(transimg)
- fb.add(transimg)
- self.animhbox.pack_start(fb,True,True,2)
- #if i != 5:
- # ra = gtk.Arrow(gtk.ARROW_RIGHT,gtk.SHADOW_OUT)
- # ra.show()
- # self.tophbox.pack_start(ra,True,True,0)
-
-
- self.animborder = gtk.EventBox()
- self.animborder.modify_bg(gtk.STATE_NORMAL,gtk.gdk.color_parse(PINK))
- self.animborder.show()
- self.animframe = gtk.EventBox()
- self.animframe.modify_bg(gtk.STATE_NORMAL,gtk.gdk.color_parse(BACKGROUND))
- self.animframe.set_border_width(5)
- self.animframe.show()
- self.animborder.add(self.animframe)
- self.animfilmstrip = gtk.VBox()
- self.animfilmstrip.show()
- self.filmstriptopimg = gtk.Image()
- self.filmstriptopimg.set_from_file(os.path.join(self.iconsdir,'filmstrip.png'))
- self.filmstriptopimg.show()
- self.animfilmstrip.pack_start(self.filmstriptopimg,False,False,0)
- self.animfilmstrip.pack_start(self.animhbox,False,False,0)
- self.filmstripbottomimg = gtk.Image()
- self.filmstripbottomimg.set_from_file(os.path.join(self.iconsdir,'filmstrip.png'))
- self.filmstripbottomimg.show()
- self.animfilmstrip.pack_start(self.filmstripbottomimg,False,False,0)
- self.animframe.add(self.animfilmstrip)
- self.afvbox = gtk.VBox()
- self.afvbox.show()
- self.afvbox.pack_start(self.animborder,False,False,0)
- self.tophbox.pack_start(self.afvbox,False,False,0)
- #self.clrframe = gtk.Button('CLEAR FRAME')
- cancelimg = gtk.Image()
- #cancelimg.set_from_stock(gtk.STOCK_CANCEL,gtk.ICON_SIZE_BUTTON)
- cancelimg.set_from_file(os.path.join(self.iconsdir,'clear.png'))
- cancelimg.show()
- self.clrframe = gtk.Button()
- self.clrframe.set_label('')
- self.clrframe.set_image(cancelimg)
- self.clrframe.connect('clicked', self.clearall, None)
- prepare_btn(self.clrframe)
- self.clrframe.show()
-
- #self.cfbox.pack_start(self.clrframe,True,True,0)
- #self.clrall = gtk.Button('CLEAR ALL')
- #self.clrall.connect('clicked', self.clearall, None)
- #self.clrall.show()
- #self.cfbox.pack_start(self.clrall,True,True,0)
- #self.controlbox.pack_start(self.cfbox,True,True,0)
- self.cfvbox = gtk.VBox()
- self.cfvbox.show()
- self.cfvbox.pack_start(self.clrframe,True,False,0)
- self.tophbox.pack_start(self.cfvbox,False,False,5)
-
- pink_arrow = gtk.Image()
- pink_arrow.set_from_file(os.path.join(self.iconsdir, 'pink_arrow.png'))
- pink_arrow.show()
- self.pahbox = gtk.HBox()
- self.pahbox.show()
- self.pahbox.pack_start(pink_arrow,False,False,150)
- self.topvbox = gtk.VBox()
- self.topvbox.show()
- self.topvbox.pack_start(self.tophbox,False,False,0)
- self.topvbox.pack_start(self.pahbox,False,False,0)
-
- self.rightbox.pack_start(self.topvbox,False,False,5)
- self.frame_selected = 0
- self.fbstyle = self.framebuttons[0].get_style()
- self.framebuttons[0].modify_bg(gtk.STATE_NORMAL,gtk.gdk.color_parse(YELLOW))
- self.framebuttons[0].modify_bg(gtk.STATE_PRELIGHT,gtk.gdk.color_parse(YELLOW))
-
- self.bottomhbox = gtk.HBox()
- self.bottomhbox.show()
-
- self.centervbox = gtk.VBox()
- self.centervbox.show()
- # MAIN IMAGE
- self.mfdraw = FrameWidget(None,self.fgpixbuf)
- self.mfdraw.set_size_request(BGWIDTH,BGHEIGHT)
- self.mfdraw.show()
- self.mfdrawborder = gtk.EventBox()
- self.mfdrawborder.modify_bg(gtk.STATE_NORMAL,gtk.gdk.color_parse(PINK))
- self.mfdrawborder.show()
- self.mfdrawbox = gtk.EventBox()
- self.mfdrawbox.set_border_width(5)
- self.mfdrawbox.show()
- self.mfdrawbox.add(self.mfdraw)
- self.mfdrawborder.add(self.mfdrawbox)
- self.centervbox.pack_start(self.mfdrawborder,False,False,0)
-
- self.bcontrolbox = gtk.HBox()
- self.bcontrolbox.set_border_width(5)
- self.bcontrolbox.show()
- # GO BUTTON
- playimg = gtk.Image()
- #playimg.set_from_stock(gtk.STOCK_MEDIA_PLAY,gtk.ICON_SIZE_BUTTON)
- playimg.set_from_file(os.path.join(self.iconsdir,'big_right_arrow.png'))
- playimg.show()
- self.gobutton = gtk.Button()
- self.gobutton.set_label('')
- self.gobutton.set_image(playimg)
- self.gobutton.connect('clicked', self.go, None)
- prepare_btn(self.gobutton)
- self.gobutton.show()
- self.bcontrolbox.pack_start(self.gobutton,True,True,5)
-
- # SPEED CONTROLS
- self.sbox = gtk.VBox()
- self.sbox.show()
- adj = gtk.Adjustment(2.5,1,5,.5,1)
- adj.connect('value_changed',self.setplayspeed)
- self.playspeed = gtk.HScale(adj)
- self.playspeed.set_draw_value(False)
- for state, color in COLOR_BG_BUTTONS:
- self.playspeed.modify_bg(state, gtk.gdk.color_parse(color))
- self.playspeed.show()
- self.sbox.pack_start(self.playspeed,True,True,0)
- #self.pslabel = gtk.Label('Speed')
- #self.pslabel.show()
- #self.sbox.pack_start(self.pslabel,True,True,0)
- self.bcontrolbox.pack_start(self.sbox,True,True,5)
- self.centervbox.pack_start(self.bcontrolbox,False,False,0)
- self.bottomhbox.pack_start(self.centervbox,False,False,0)
-
- self.controlbox = gtk.VBox()
- self.controlbox.show()
-
- # CHARACTER CONTROLS
- self.ccbox = gtk.VBox()
- self.ccbox.show()
- self.cchbox = gtk.HBox()
- self.cchbox.show()
- self.cclbutton = gtk.Button()
- self.cclbutton.connect('clicked',self.lastcharacter,None)
- self.cclbutton.show()
- ccla = gtk.Image()
- ccla.set_from_file(os.path.join(self.iconsdir,'big_left_arrow.png'))
- ccla.show()
- prepare_btn(self.cclbutton)
- self.cclbutton.add(ccla)
- self.cclbvbox = gtk.VBox()
- self.cclbvbox.show()
- self.cclbvbox.pack_start(self.cclbutton,True,False,0)
- self.cchbox.pack_start(self.cclbvbox,True,True,5)
- self.ccibutton = gtk.Button()
- self.ccibutton.show()
- self.ccismall = gtk.Image()
- self.ccismall.show()
- self.cciebox = gtk.EventBox()
- self.cciebox.modify_bg(gtk.STATE_NORMAL,gtk.gdk.color_parse(BLACK))
- self.cciebox.show()
- self.cciebox.add(self.ccismall)
- self.ccibvbox = gtk.VBox()
- self.ccibvbox.show()
- self.ccibvbox.pack_start(self.cciebox,True,False,0)
- self.cchbox.pack_start(self.ccibvbox,False,False,0)
- self.ccrbutton = gtk.Button()
- self.ccrbutton.connect('clicked',self.nextcharacter,None)
- self.ccrbutton.show()
- ccra = gtk.Image()
- ccra.set_from_file(os.path.join(self.iconsdir,'big_right_arrow.png'))
- ccra.show()
- self.ccrbutton.add(ccra)
- prepare_btn(self.ccrbutton)
- self.ccrbvbox = gtk.VBox()
- self.ccrbvbox.show()
- self.ccrbvbox.pack_start(self.ccrbutton,True,False,0)
- self.cchbox.pack_start(self.ccrbvbox,True,True,5)
- self.ccbox.pack_start(self.cchbox,True,True,0)
- self.charlabel = gtk.Label('')
- self.charlabel.show()
- self.charlabelhbox = gtk.HBox()
- self.charlabelhbox.show()
- self.charlabelhbox.pack_start(self.charlabel,True,False,0)
- self.ccbox.pack_start(self.charlabelhbox,False,False,0)
- if not self.insugar:
- self.character = gtk.Button('My Character')
- self.character.connect('clicked',self.getimgdir,None)
- prepare_btn(self.character)
- self.character.show()
- self.characterhbox = gtk.HBox()
- self.characterhbox.show()
- self.characterhbox.pack_start(self.character,True,False,0)
- self.ccbox.pack_start(self.characterhbox,False,False,5)
- self.setcharacter()
-
-
- self.controlbox.pack_start(self.ccbox,False,False,5)
-
- # BACKGROUND CONTROLS
- self.bgbox = gtk.VBox()
- self.bgbox.show()
- self.bghbox = gtk.HBox()
- self.bghbox.show()
- self.blbutton = gtk.Button()
- self.blbutton.connect('clicked',self.lastback,None)
- self.blbutton.show()
- bla = gtk.Image()
- bla.set_from_file(os.path.join(self.iconsdir,'big_left_arrow.png'))
- bla.show()
- self.blbutton.add(bla)
- prepare_btn(self.blbutton)
- self.blbvbox = gtk.VBox()
- self.blbvbox.show()
- self.blbvbox.pack_start(self.blbutton,True,False,0)
- self.bghbox.pack_start(self.blbvbox,True,True,5)
- self.bgsmall = gtk.Image()
- bgimgpath = os.path.join(self.mdirpath,'backpics/bigbg01.gif')
- self.setback(bgimgpath)
- self.bgsmall.show()
- self.bghbox.pack_start(self.bgsmall,False,False,0)
- self.brbutton = gtk.Button()
- self.brbutton.connect('clicked',self.nextback,None)
- self.brbutton.show()
- bra = gtk.Image()
- bra.set_from_file(os.path.join(self.iconsdir,'big_right_arrow.png'))
- bra.show()
- self.brbutton.add(bra)
- prepare_btn(self.brbutton)
- self.brbvbox = gtk.VBox()
- self.brbvbox.show()
- self.brbvbox.pack_start(self.brbutton,True,False,0)
- self.bghbox.pack_start(self.brbvbox,True,True,5)
- self.bgbox.pack_start(self.bghbox,True,True,0)
- if not self.insugar:
- self.bgbutton = gtk.Button('My Background')
- self.bgbutton.connect('clicked',self.getbackgroundfile,None)
- prepare_btn(self.bgbutton)
- self.bgbutton.show()
- self.bgbuttonhbox = gtk.HBox()
- self.bgbuttonhbox.show()
- self.bgbuttonhbox.pack_start(self.bgbutton,True,False,0)
- self.bgbox.pack_start(self.bgbuttonhbox,False,False,5)
- self.controlbox.pack_start(self.bgbox,False,False,5)
-
- # SOUND CONTROLS
- self.soundbox = gtk.VBox()
- self.soundbox.show()
- self.soundhbox = gtk.HBox()
- self.soundhbox.show()
- self.slbutton = gtk.Button()
- self.slbutton.connect('clicked',self.lastsound,None)
- self.slbutton.show()
- sla = gtk.Image()
- sla.set_from_file(os.path.join(self.iconsdir,'big_left_arrow.png'))
- sla.show()
- self.slbutton.add(sla)
- prepare_btn(self.slbutton)
- self.slbvbox = gtk.VBox()
- self.slbvbox.show()
- self.slbvbox.pack_start(self.slbutton,True,False,0)
- self.soundhbox.pack_start(self.slbvbox,True,True,5)
- self.soundimg = gtk.Image()
- #self.soundimg.set_from_file(os.path.join(self.iconsdir,'sound_icon.png'))
- soundimgpath = os.path.join(self.iconsdir,'sound_icon.png')
- sipixbuf = gtk.gdk.pixbuf_new_from_file(soundimgpath)
- si_scaled_buf = sipixbuf.scale_simple(IMGWIDTH,IMGHEIGHT,gtk.gdk.INTERP_BILINEAR)
- self.soundimg.set_from_pixbuf(si_scaled_buf)
- self.soundimg.show()
- self.soundhbox.pack_start(self.soundimg,False,False,0)
- self.srbutton = gtk.Button()
- self.srbutton.connect('clicked',self.nextsound,None)
- self.srbutton.show()
- sra = gtk.Image()
- sra.set_from_file(os.path.join(self.iconsdir,'big_right_arrow.png'))
- sra.show()
- self.srbutton.add(sra)
- prepare_btn(self.srbutton)
- self.srbvbox = gtk.VBox()
- self.srbvbox.show()
- self.srbvbox.pack_start(self.srbutton,True,False,0)
- self.soundhbox.pack_start(self.srbvbox,True,True,5)
- self.soundbox.pack_start(self.soundhbox,True,True,0)
- self.soundlabel = gtk.Label('No Sound')
- self.soundlabel.show()
- self.soundlabelhbox = gtk.HBox()
- self.soundlabelhbox.show()
- self.soundlabelhbox.pack_start(self.soundlabel,True,False,0)
- self.soundbox.pack_start(self.soundlabelhbox,False,False,0)
- if not self.insugar:
- self.soundbutton = gtk.Button('My Sound')
- self.soundbutton.connect('clicked',self.getsoundfile,None)
- prepare_btn(self.soundbutton)
- self.soundbutton.show()
- self.soundbuttonhbox = gtk.HBox()
- self.soundbuttonhbox.show()
- self.soundbuttonhbox.pack_start(self.soundbutton,True,False,0)
- self.soundbox.pack_start(self.soundbuttonhbox,False,False,5)
- self.controlbox.pack_start(self.soundbox,False,False,5)
-
- # FINISHING DETAILS
- self.bottomhbox.pack_start(self.controlbox,True,True,10)
- self.rightbox.pack_start(self.bottomhbox,True,True,10)
- self.hbox.pack_start(self.rightbox,True,True,0)
-
- def main(self):
- gtk.main()
-
-try:
- from sugar.activity import activity
- from sugar.graphics.toolbutton import ToolButton
- from sugar.graphics.objectchooser import ObjectChooser
- from sugar.presence import presenceservice
- from sugar.presence.tubeconn import TubeConnection
- import telepathy
- import telepathy.client
- from dbus import Interface
- from dbus.service import method, signal
- from dbus.gobject_service import ExportedGObject
-
- class BGToolbar(gtk.Toolbar):
- def __init__(self,sactivity,app):
- gtk.Toolbar.__init__(self)
- self.sactivity = sactivity
- self.app = app
- self.image = ToolButton('insert-image')
- self.image.set_tooltip('Insert Image')
- self.imageid = self.image.connect('clicked',self.image_cb)
- self.insert(self.image,-1)
- self.image.show()
-
- def image_cb(self, button):
- chooser = ObjectChooser('Choose Image',self.sactivity,
- gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT)
- try:
- result = chooser.run()
- if result == gtk.RESPONSE_ACCEPT:
- jobject = chooser.get_selected_object()
- if jobject and jobject.file_path:
- self.app.setback(jobject.file_path)
- finally:
- chooser.destroy()
- del chooser
-
- class cartoonbuilderActivity(activity.Activity):
- def __init__(self, handle):
- activity.Activity.__init__(self,handle)
- self.connect("destroy",self.destroy_cb)
- #app = cartoonbuilder(self,'/home/olpc/Activities/CartoonBuilder.activity')
- bundle_path = activity.get_bundle_path()
- os.chdir(bundle_path)
- self.app = cartoonbuilder(True,self, bundle_path)
- self.set_title('CartoonBuilder')
- toolbox = activity.ActivityToolbox(self)
- bgtoolbar = BGToolbar(self,self.app)
- toolbox.add_toolbar(_('Background'),bgtoolbar)
- bgtoolbar.show()
- self.set_toolbox(toolbox)
- toolbox.show()
- if hasattr(self, '_jobject'):
- self._jobject.metadata['title'] = 'CartoonBuilder'
- title_widget = toolbox._activity_toolbar.title
- title_widget.set_size_request(title_widget.get_layout().get_pixel_size()[0] + 20, -1)
- outerframe = gtk.EventBox()
- outerframe.modify_bg(gtk.STATE_NORMAL,gtk.gdk.color_parse(BUTTON_BACKGROUND))
- outerframe.show()
- innerframe = gtk.EventBox()
- innerframe.show()
- ifalign = gtk.Alignment(1.0,1.0,1.0,1.0)
- ifalign.add(innerframe)
- ifalign.set_padding(10,10,30,30) # top,bottom,left,right
- ifalign.show()
- #innerframe.set_border_width(150)
- outerframe.add(ifalign)
- innerframe.add(self.app.main)
- self.set_canvas(outerframe)
-
- # mesh stuff
- self.pservice = presenceservice.get_instance()
- owner = self.pservice.get_owner()
- self.owner = owner
- try:
- name, path = self.pservice.get_preferred_connection()
- self.tp_conn_name = name
- self.tp_conn_path = path
- self.conn = telepathy.client.Connection(name, path)
- except TypeError:
- pass
- self.initiating = None
-
- #sharing stuff
- self.game = None
- self.connect('shared', self._shared_cb)
- if self._shared_activity:
- # we are joining the activity
- self.connect('joined', self._joined_cb)
- if self.get_shared():
- # oh, OK, we've already joined
- self._joined_cb()
- else:
- # we are creating the activity
- pass
-
-
- def destroy_cb(self, data=None):
- return True
-
- def read_file(self, filepath):
- zf = file(filepath,'r')
- self.app.loadfromzip(zf)
-
- def write_file(self, filepath):
- zf = file(filepath,'w')
- self.app.savetozip(zf)
-
- def _shared_cb(self,activity):
- self.initiating = True
- self._setup()
- id = self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].OfferDBusTube(
- SERVICE, {})
- #self.app.export.set_label('Shared Me')
-
- def _joined_cb(self,activity):
- if self.game is not None:
- return
-
- if not self._shared_activity:
- return
-
- #for buddy in self._shared_activity.get_joined_buddies():
- # self.buddies_panel.add_watcher(buddy)
-
- #logger.debug('Joined an existing Connect game')
- #self.app.export.set_label('Joined You')
- self.initiating = False
- self._setup()
-
- #logger.debug('This is not my activity: waiting for a tube...')
- self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].ListTubes(
- reply_handler=self._list_tubes_reply_cb,
- error_handler=self._list_tubes_error_cb)
-
- def _setup(self):
- if self._shared_activity is None:
- return
-
- bus_name, conn_path, channel_paths = self._shared_activity.get_channels()
-
- # Work out what our room is called and whether we have Tubes already
- room = None
- tubes_chan = None
- text_chan = None
- for channel_path in channel_paths:
- channel = telepathy.client.Channel(bus_name, channel_path)
- htype, handle = channel.GetHandle()
- if htype == telepathy.HANDLE_TYPE_ROOM:
- #logger.debug('Found our room: it has handle#%d "%s"',
- # handle, self.conn.InspectHandles(htype, [handle])[0])
- room = handle
- ctype = channel.GetChannelType()
- if ctype == telepathy.CHANNEL_TYPE_TUBES:
- #logger.debug('Found our Tubes channel at %s', channel_path)
- tubes_chan = channel
- elif ctype == telepathy.CHANNEL_TYPE_TEXT:
- #logger.debug('Found our Text channel at %s', channel_path)
- text_chan = channel
-
- if room is None:
- #logger.error("Presence service didn't create a room")
- return
- if text_chan is None:
- #logger.error("Presence service didn't create a text channel")
- return
-
- # Make sure we have a Tubes channel - PS doesn't yet provide one
- if tubes_chan is None:
- #logger.debug("Didn't find our Tubes channel, requesting one...")
- tubes_chan = self.conn.request_channel(telepathy.CHANNEL_TYPE_TUBES,
- telepathy.HANDLE_TYPE_ROOM, room, True)
-
- self.tubes_chan = tubes_chan
- self.text_chan = text_chan
-
- tubes_chan[telepathy.CHANNEL_TYPE_TUBES].connect_to_signal('NewTube',
- self._new_tube_cb)
-
- def _list_tubes_reply_cb(self, tubes):
- for tube_info in tubes:
- self._new_tube_cb(*tube_info)
-
- def _list_tubes_error_cb(self, e):
- #logger.error('ListTubes() failed: %s', e)
- pass
-
- def _new_tube_cb(self, id, initiator, type, service, params, state):
- #logger.debug('New tube: ID=%d initator=%d type=%d service=%s '
- # 'params=%r state=%d', id, initiator, type, service,
- # params, state)
-
- if (self.game is None and type == telepathy.TUBE_TYPE_DBUS and
- service == SERVICE):
- if state == telepathy.TUBE_STATE_LOCAL_PENDING:
- self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].AcceptDBusTube(id)
-
- tube_conn = TubeConnection(self.conn,
- self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES],
- id, group_iface=self.text_chan[telepathy.CHANNEL_INTERFACE_GROUP])
- self.game = ConnectGame(tube_conn, self.initiating, self)
-
- class ConnectGame(ExportedGObject):
- def __init__(self,tube, is_initiator, activity):
- super(ConnectGame,self).__init__(tube,PATH)
- self.tube = tube
- self.is_initiator = is_initiator
- self.entered = False
- self.activity = activity
-
- self.ordered_bus_names=[]
- self.tube.watch_participants(self.participant_change_cb)
-
- def participant_change_cb(self, added, removed):
- if not self.entered:
- if self.is_initiator:
- self.add_hello_handler()
- else:
- self.Hello()
- self.entered = True
-
- @signal(dbus_interface=IFACE,signature='')
- def Hello(self):
- """Request that this player's Welcome method is called to bring it
- up to date with the game state.
- """
-
- @method(dbus_interface=IFACE, in_signature='s', out_signature='')
- def Welcome(self, sdata):
- #sdata is the zip file contents
- #self.activity.app.lessonplans.set_label('got data to restore')
- self.activity.app.restore(str(sdata))
-
- def add_hello_handler(self):
- self.tube.add_signal_receiver(self.hello_cb, 'Hello', IFACE,
- path=PATH, sender_keyword='sender')
-
- def hello_cb(self, sender=None):
- self.tube.get_object(sender, PATH).Welcome(self.activity.app.getsdata(),dbus_interface=IFACE)
-
-except ImportError:
- pass
-
-if __name__ == "__main__":
- # have to do toplevel window stuff here because Sugar does it on the OLPC
- toplevel_window = gtk.Window(gtk.WINDOW_TOPLEVEL)
- #mdirpath = '.'
- mdirpath = os.path.abspath(os.curdir)
- app = cartoonbuilder(False,toplevel_window,mdirpath)
- toplevel_window.add(app.main)
- toplevel_window.set_title('Cartoon Builder')
- # FULLSCREEN
-
- #toplevel_window.set_decorated(False)
- #toplevel_window.fullscreen()
-
- toplevel_window.connect("delete_event", app.delete_event)
- toplevel_window.connect("destroy", app.destroy)
- #toplevel_window.set_border_width(10)
- toplevel_window.show()
- gtk.main()
diff --git a/char.py b/char.py
new file mode 100644
index 0000000..c91c9b9
--- /dev/null
+++ b/char.py
@@ -0,0 +1,149 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import os
+import gtk
+import glob
+from gettext import gettext as _
+
+import theme
+from utils import pixbuf, pixbuf2str
+
+def load():
+ from document import Document
+
+ custom = THEMES[-1]
+ for i, f in enumerate(
+ [i for i in set(Document.tape) if not i.empty() and i.custom()]):
+ custom.frames[i] = f
+
+class Frame:
+ def __init__(self, id):
+ self.id = id
+ self.name = ''
+ self._thumb = None
+ self._orig = None
+
+ def serialize(self):
+ if self._orig:
+ return pixbuf2str(self._orig)
+ else:
+ return ''
+
+ def empty(self):
+ return False
+
+ def custom(self):
+ return True
+
+ def thumb(self):
+ if self._thumb == None:
+ self._thumb = theme.scale(self.orig())
+ return self._thumb
+
+ def orig(self):
+ return self._orig
+
+ def select(self):
+ return True;
+
+class PreinstalledFrame(Frame):
+ def __init__(self, filename):
+ Frame.__init__(self, filename)
+ self._filename = filename
+
+ def custom(self):
+ return False
+
+ def orig(self):
+ if self._orig == None:
+ self._orig = theme.pixbuf(self._filename)
+ return self._orig
+
+class EmptyFrame(Frame):
+ def __init__(self):
+ Frame.__init__(self, None)
+ self._thumb = theme.EMPTY_THUMB
+ self._orig = theme.EMPTY_ORIG
+
+ def custom(self):
+ return False
+
+ def empty(self):
+ return True
+
+class RestoredFrame(Frame):
+ def __init__(self, id, data):
+ Frame.__init__(self, id)
+ self._orig = theme.str2pixbuf(data)
+
+class CustomFrame(Frame):
+ def __init__(self):
+ Frame.__init__(self, None)
+ self._thumb = theme.CUSTOM_FRAME_THUMB
+
+ def orig(self):
+ if self._orig == None:
+ return theme.EMPTY_ORIG
+ return self._orig
+
+ def select(self):
+ if self._orig:
+ return True;
+ self.name, self.id, self._orig = theme.choose(lambda jobject:
+ (jobject.metadata['title'], jobject.object_id,
+ theme.pixbuf(jobject.file_path)),
+ (None, None, None))
+ if self.name:
+ self._thumb = theme.scale(self._orig)
+ return True
+ else:
+ return False
+
+class Char:
+ def __init__(self, name, thumbfile, dir):
+ self.name = name
+ self.frames = []
+
+ if dir:
+ for i in sorted(glob.glob(theme.path(dir, '*'))):
+ self.frames.append(PreinstalledFrame(
+ os.path.join(dir, os.path.basename(i))))
+ self._thumb = theme.pixbuf(thumbfile, theme.THUMB_SIZE)
+ self._custom = False
+ else:
+ for i in range(0, theme.FRAME_ROWS*theme.FRAME_COLS):
+ self.frames.append(CustomFrame())
+ self._thumb = theme.CUSTOM_FRAME_THUMB
+ self._custom = True
+
+ def custom(self):
+ return self._custom
+
+ def thumb(self):
+ return self._thumb
+
+ def clean(self, index):
+ if self.frames[index].custom():
+ self.frames[index] = CustomFrame()
+
+THEMES = (
+ Char(_('Elephant'), 'images/pics/Elephant/bigelephant0.gif',
+ 'images/pics/Elephant'),
+ Char(_('Space Blob'), 'images/pics/SpaceBlob/bigblob8.gif',
+ 'images/pics/SpaceBlob'),
+ Char(_('Turkey'), 'images/pics/Turkey/bigturkey1.gif',
+ 'images/pics/Turkey'),
+ None,
+ Char(_('Custom'), None, None))
diff --git a/config.backpics b/config.backpics
deleted file mode 100644
index 8c601a8..0000000
--- a/config.backpics
+++ /dev/null
@@ -1,17 +0,0 @@
-backpics/bigbg01.gif
-backpics/bigbg02.gif
-backpics/bigbg03.gif
-backpics/bigbg04.gif
-backpics/bigbg05.gif
-backpics/bigbg06.gif
-backpics/bigbg07.gif
-backpics/bigbg08.gif
-backpics/bigbg09.gif
-backpics/bigbg10.gif
-backpics/bigbg11.gif
-backpics/bigbg12.gif
-backpics/bigbg13.gif
-backpics/bigbg14.gif
-backpics/bigbg15.gif
-backpics/bigbg16.gif
-backpics/bigbg17.gif
diff --git a/config.imgdirs b/config.imgdirs
deleted file mode 100644
index 249246c..0000000
--- a/config.imgdirs
+++ /dev/null
@@ -1,3 +0,0 @@
-pics/SpaceBlob
-pics/Elephant
-pics/Turkey
diff --git a/config.sounds b/config.sounds
deleted file mode 100644
index 872147a..0000000
--- a/config.sounds
+++ /dev/null
@@ -1,4 +0,0 @@
-sounds/gobble.wav
-sounds/funk.wav
-sounds/giggle.wav
-sounds/jungle.wav
diff --git a/document.py b/document.py
new file mode 100644
index 0000000..9273f2c
--- /dev/null
+++ b/document.py
@@ -0,0 +1,106 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import os
+import gtk
+import cjson
+from zipfile import ZipFile
+
+import theme
+from sound import *
+from ground import *
+from utils import *
+from char import *
+
+class Document:
+ tape = []
+ ground = None
+ sound = None
+
+ for i in range(theme.TAPE_COUNT):
+ tape.append(EmptyFrame())
+
+def clean(index):
+ from char import Frame
+ Document.tape[index] = EmptyFrame()
+
+def save(filepath):
+ zip = ZipFile(filepath, 'w')
+
+ cfg = { 'ground': {},
+ 'sound' : {},
+ 'frames': {},
+ 'tape' : [] }
+
+ def _save(node, arcname, value):
+ if value.custom():
+ node['custom'] = True
+ node['filename'] = arcname
+ zip.writestr(arcname, value.serialize())
+ else:
+ node['custom'] = False
+ node['name'] = unicode(value.name)
+ node['id'] = value.id
+
+ _save(cfg['ground'], 'ground.png', Document.ground)
+ _save(cfg['sound'], 'sound', Document.sound)
+
+ for i, frame in enumerate(
+ [i for i in set(Document.tape) if not i.empty() and i.custom()]):
+ arcname = 'frame%03d.png' % i
+ cfg['frames'][frame.id] = arcname
+ zip.writestr(arcname, frame.serialize())
+
+ for i, frame in enumerate(Document.tape):
+ if not frame.empty():
+ node = {}
+ node['custom'] = frame.custom()
+ node['id'] = frame.id
+ node['index'] = i
+ cfg['tape'].append(node)
+
+ zip.writestr('MANIFEST', cjson.encode(cfg))
+ zip.close()
+
+ import shutil
+ shutil.copy(filepath, '/tmp/foo.zip')
+
+def load(filepath):
+ zip = ZipFile(filepath, 'r')
+ cfg = cjson.decode(zip.read('MANIFEST'))
+
+ def _load(node, restored_class, preinstalled_class):
+ if node['custom']:
+ return restored_class(node['name'], node['id'],
+ zip.read(node['filename']))
+ else:
+ return preinstalled_class(node['name'], node['id'])
+
+ Document.ground = _load(cfg['ground'], RestoredGround, PreinstalledGround)
+ Document.sound = _load(cfg['sound'], RestoredSound, PreinstalledSound)
+
+ frames = {}
+
+ for id, arcname in cfg['frames'].items():
+ frames[id] = RestoredFrame(id, zip.read(arcname))
+
+ for node in cfg['tape']:
+ i = node['index']
+ if i < theme.TAPE_COUNT:
+ if node['custom']:
+ Document.tape[i] = frames[node['id']]
+ else:
+ Document.tape[i] = PreinstalledFrame(node['id'])
+
+ zip.close()
diff --git a/filmstrip.png b/filmstrip.png
deleted file mode 100644
index f21bd93..0000000
--- a/filmstrip.png
+++ /dev/null
Binary files differ
diff --git a/ground.py b/ground.py
new file mode 100644
index 0000000..1f97514
--- /dev/null
+++ b/ground.py
@@ -0,0 +1,99 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import os
+import gtk
+from gettext import gettext as _
+
+import theme
+
+def load():
+ from document import Document
+
+ if Document.ground and Document.ground.custom():
+ THEMES.append(Document.ground)
+
+class Ground:
+ def __init__(self, name, id):
+ self.name = name
+ self.id = id
+ self._thumb = None
+
+ def custom(self):
+ return True
+
+ def serialize(self):
+ return theme.pixbuf2str(self._orig)
+
+ def thumb(self):
+ if not self._thumb:
+ self._thumb = theme.scale(self._orig)
+ return self._thumb
+
+ def orig(self):
+ return self._orig
+
+ def select(self):
+ return self
+
+class PreinstalledGround(Ground):
+ def __init__(self, name, filename):
+ Ground.__init__(self, name, filename)
+ self._orig = theme.pixbuf(filename)
+
+ def custom(self):
+ return False
+
+class CustomGround(Ground):
+ def __init__(self, name, filename):
+ Ground.__init__(self, name, None)
+ self._orig = theme.pixbuf(filename)
+
+ def select(self):
+ try:
+ return theme.choose(lambda jobject: JournalGround(jobject))
+ except:
+ return None
+
+class RestoredGround(Ground):
+ def __init__(self, name, id, data):
+ Ground.__init__(self, name, id)
+ self._orig = theme.str2pixbuf(data)
+
+class JournalGround(Ground):
+ def __init__(self, jobject):
+ Ground.__init__(self, jobject.metadata['title'], jobject.object_id)
+ self._orig = theme.pixbuf(jobject.file_path)
+ THEMES.append(self)
+
+THEMES = [
+ PreinstalledGround(_('Saturn'), 'images/backpics/bigbg01.gif'),
+ PreinstalledGround(_('Snowflakes'), 'images/backpics/bigbg02.gif'),
+ PreinstalledGround(_('Eye'), 'images/backpics/bigbg03.gif'),
+ PreinstalledGround(_('Blobs'), 'images/backpics/bigbg04.gif'),
+ PreinstalledGround(_('Star Night'), 'images/backpics/bigbg05.gif'),
+ PreinstalledGround(_('Forest'), 'images/backpics/bigbg06.gif'),
+ PreinstalledGround(_('Spiral'), 'images/backpics/bigbg07.gif'),
+ PreinstalledGround(_('Beam'), 'images/backpics/bigbg08.gif'),
+ PreinstalledGround(_('Cloth'), 'images/backpics/bigbg09.gif'),
+ PreinstalledGround(_('Faces'), 'images/backpics/bigbg10.gif'),
+ PreinstalledGround(_('Leaves'), 'images/backpics/bigbg11.gif'),
+ PreinstalledGround(_('Vegetables'), 'images/backpics/bigbg12.gif'),
+ PreinstalledGround(_('Spotlight'), 'images/backpics/bigbg13.gif'),
+ PreinstalledGround(_('Strips'), 'images/backpics/bigbg14.gif'),
+ PreinstalledGround(_('Scene'), 'images/backpics/bigbg15.gif'),
+ PreinstalledGround(_('Rhombs'), 'images/backpics/bigbg16.gif'),
+ PreinstalledGround(_('Milky Way'), 'images/backpics/bigbg17.gif'),
+ None,
+ CustomGround(_('Custom'), 'images/backpics/custom.png')]
diff --git a/gtkrc b/gtkrc
new file mode 100644
index 0000000..57110c6
--- /dev/null
+++ b/gtkrc
@@ -0,0 +1,22 @@
+style "combobox"
+{
+ color["focus_line"] = "#027F01"
+ bg[NORMAL] = "#027F01"
+ bg[ACTIVE] = "#026002"
+ bg[PRELIGHT] = "#027F01"
+}
+
+style "scrollbar"
+{
+ bg[NORMAL] = "#027F01"
+}
+
+style "fixframe"
+{
+ color["focus_line"] = "#FFFFFF"
+ bg[NORMAL] = "#808080"
+ bg[ACTIVE] = "#808080"
+ bg[PRELIGHT] = "#808080"
+}
+
+widget "*ComboBox*" style "fixframe"
diff --git a/icons/filmstrip.png b/icons/filmstrip.png
index 37e3ca6..fa78873 100644
--- a/icons/filmstrip.png
+++ b/icons/filmstrip.png
Binary files differ
diff --git a/icons/pink_arrow.png b/icons/pink_arrow.png
index bb0ac73..d26e106 100644
--- a/icons/pink_arrow.png
+++ b/icons/pink_arrow.png
Binary files differ
diff --git a/icons/sl-reset.svg b/icons/sl-reset.svg
new file mode 100644
index 0000000..833e85b
--- /dev/null
+++ b/icons/sl-reset.svg
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 13.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 14576) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ width="55px" height="55px" viewBox="0 0 55 55" enable-background="new 0 0 55 55" xml:space="preserve">
+<polygon fill="none" stroke="#FFFFFF" stroke-width="2" points="36.668,46.959 17.384,46.671 13.751,19.147 40.626,19.147 "/>
+<path fill="none" stroke="#FFFFFF" stroke-width="2" d="M13.751,17.312c18.998-2.13,21.875-2.374,25.436-3.021
+ c3.561-0.647-8.347-2.808-12.054-2.375c-1.729,0.201-9.713,1.618-13.275,5.396"/>
+<path fill="none" stroke="#FFFFFF" stroke-width="2" d="M22.709,11.557l6.691-1.476c0,0-4.209-3.13-6.691,1.296"/>
+<line fill="none" stroke="#FFFFFF" x1="27.457" y1="24.652" x2="27.457" y2="39.763"/>
+<line fill="none" stroke="#FFFFFF" x1="35.229" y1="25.084" x2="33.502" y2="39.763"/>
+<line fill="none" stroke="#FFFFFF" x1="19.038" y1="25.731" x2="20.55" y2="39.763"/>
+</svg>
diff --git a/icons/sound_icon.png b/icons/sound_icon.png
deleted file mode 100644
index e953394..0000000
--- a/icons/sound_icon.png
+++ /dev/null
Binary files differ
diff --git a/icons/tempo1.svg b/icons/tempo1.svg
new file mode 100644
index 0000000..bb9aeec
--- /dev/null
+++ b/icons/tempo1.svg
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 13.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 14576) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ width="50px" height="50px" viewBox="0 0 50 50" enable-background="new 0 0 50 50" xml:space="preserve">
+<path fill-rule="evenodd" clip-rule="evenodd" fill="#FFFFFF" d="M23.5,6.5c3,3,7,7,9,11c-7,5-4,6-3,26c-1,1-8,1-9,0c0,0,2,1,2-1
+ c0-3-2-7-2-11c0-2,1-4,1-6c0-3-2-1-2-3c0-3,3-8,3-11c0-2-1-1-2-2v-3H23.5z"/>
+</svg>
diff --git a/icons/tempo2.svg b/icons/tempo2.svg
new file mode 100644
index 0000000..4a98310
--- /dev/null
+++ b/icons/tempo2.svg
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 13.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 14576) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ width="50px" height="50px" viewBox="0 0 50 50" enable-background="new 0 0 50 50" xml:space="preserve">
+<path fill-rule="evenodd" clip-rule="evenodd" fill="#FFFFFF" d="M27.5,44.5v-3C28.5,42.5,28.5,43.5,27.5,44.5z M26.5,10.5
+ c2,2,2,6,2,8c0,4-3,11-3,13s4,7,7,10c-2,2-4,3-5,5h-6c1-1,2-3,2-5c0-3-2-9-3-14c0,0,0-1-1,0v-6c0-3,3-8,3-11c0-1-2-2-2-6h3
+ C23.5,5.5,26.5,9.5,26.5,10.5z"/>
+</svg>
diff --git a/icons/tempo3.svg b/icons/tempo3.svg
new file mode 100644
index 0000000..bd893bd
--- /dev/null
+++ b/icons/tempo3.svg
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 13.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 14576) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ width="50px" height="50px" viewBox="0 0 50 50" enable-background="new 0 0 50 50" xml:space="preserve">
+<path fill-rule="evenodd" clip-rule="evenodd" fill="#FFFFFF" d="M30.5,17.5c0,3-2,2-2,4c0,3,4,14,7,21c-1,0-3,1-5,1c1-1,2,0,2-3
+ c0-2-4-7-6-10c-3,3-5,8-7,13c-1,0-3-1-4-1c3-3,7-14,7-18s-1-3-4-4c3-2,4-8,4-14h3C23.5,9.5,30.5,14.5,30.5,17.5z"/>
+</svg>
diff --git a/icons/tempo4.svg b/icons/tempo4.svg
new file mode 100644
index 0000000..6fa5afa
--- /dev/null
+++ b/icons/tempo4.svg
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 13.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 14576) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ width="50px" height="50px" viewBox="0 0 50 50" enable-background="new 0 0 50 50" xml:space="preserve">
+<path fill-rule="evenodd" clip-rule="evenodd" fill="#FFFFFF" d="M34.5,22.5c-1-1-2-4-5-6c-1,2,0,3,0,6c0,2-3,4-3,7c0,2,4,2,4,4
+ c0,3-1,4-2,5c0-1,0-3-1-4c-1,3-2,7-3,10c-4-3,0-6,0-9s-3-11-4-17l-4,4c1-5,8.25-11.12,7.25-16.12c0.68,0.68,3.029,0,2.87,2.12
+ C26.5,10.25,33.62,17.75,34.5,22.5z"/>
+</svg>
diff --git a/icons/tempo5.svg b/icons/tempo5.svg
new file mode 100644
index 0000000..9500e7e
--- /dev/null
+++ b/icons/tempo5.svg
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 13.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 14576) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ width="50px" height="50px" viewBox="0 0 50 50" enable-background="new 0 0 50 50" xml:space="preserve">
+<path fill-rule="evenodd" clip-rule="evenodd" fill="#FFFFFF" d="M24.5,13.5c2,1,5,3,5,6c0,2-2,3-2,5c0,9,11,4,11,13c-1,0-3-2-4-3
+ c-3-1-9,1-10-3c-2,3-5,7-7,11c-3,0-3-1-4-1c0-2,3-3,4-6s4-8,4-10c0-3-1-3-2-5c-1,0-2,1-3,2c0-1,2-3,2-4c1-2,3-5,2-8c0,0,1-1,4-2
+ C25.5,9.5,25.5,11.5,24.5,13.5z"/>
+</svg>
diff --git a/icons/tempo6.svg b/icons/tempo6.svg
new file mode 100644
index 0000000..9844fd6
--- /dev/null
+++ b/icons/tempo6.svg
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 13.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 14576) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ width="50px" height="50px" viewBox="0 0 50 50" enable-background="new 0 0 50 50" xml:space="preserve">
+<path fill-rule="evenodd" clip-rule="evenodd" fill="#FFFFFF" d="M22.5,10.5c3,2,7,5,7,7c0,3-4,8-4,10c0,3,1,3,1,5h5l2-2l2,2v4
+ c-1,0-3-2-5-2c-3,0-5,1-8,1c-1,3-2,7-2,10h-5c1-1,3-3,3-4c1-5,1-11,1-18l-1-1c-1,1-1.75,2.88-2.75,2.88c0,0-0.25-0.63-0.25-1.63
+ c4-4,2-8.25,2-13.25c0-1,0.25-2.5,0.38-5.38L22.5,5.5C23.12,6.5,22.5,8.5,22.5,10.5z"/>
+<polygon fill-rule="evenodd" clip-rule="evenodd" fill="#333333" stroke="#333333" stroke-linecap="round" stroke-linejoin="round" points="
+ 25,20 25.25,16.75 26.5,17.88 "/>
+</svg>
diff --git a/icons/tempo7.svg b/icons/tempo7.svg
new file mode 100644
index 0000000..54bed80
--- /dev/null
+++ b/icons/tempo7.svg
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 13.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 14576) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ width="50px" height="50px" viewBox="0 0 50 50" enable-background="new 0 0 50 50" xml:space="preserve">
+<path fill-rule="evenodd" clip-rule="evenodd" fill="#FFFFFF" d="M20.5,7.5c1,1,1,3,1,4c10,4,8,6,8,14c0,2,6,9,10,13c-1,2-2,4-4,5
+ c1.62-8.88-8.75-13.88-12-15c-1,1-1,0-1,2c0,3,2,5,3,7c-1,1-3,2-6,2c0-1,2-1,2-4c0-2-4-4-4-6c0-3,3-4,5-6c-3-8-8-2-11-6h6
+ c0-1,1,0,1-3c0-2-1-1-2-2l1-5H20.5z"/>
+</svg>
diff --git a/icons/tempo8.svg b/icons/tempo8.svg
new file mode 100644
index 0000000..2c0154f
--- /dev/null
+++ b/icons/tempo8.svg
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 13.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 14576) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ width="50px" height="50px" viewBox="0 0 50 50" enable-background="new 0 0 50 50" xml:space="preserve">
+<path fill-rule="evenodd" clip-rule="evenodd" fill="#FFFFFF" d="M20.5,12.5c0.67,0.4,0.4,1.9,1.75,2.25s1.05-0.38,1.5-0.37
+ c4.971,0,10.95-0.88,11.75,7.12c-1-2-3-4-5-5l-4,1c1,2,4,4,5,7c1,1,1,4,1,6c3,3,8-1,11,6c-2.88-0.82-4.25-2.62-12.75-2.75
+ c-1.561-0.02-2.34-1.561-3.75-1.87c-3.42-0.76-4.67-0.38-5.5-0.38c-3,0-8,7-11,7c-2,0-3-1-3-2c4,2,8-4,9-7c2-1,5-1,8-3c-2-4-6-5-8-3
+ l-6-6l2-2c1,1,1,2,1,4c1,0,4.12,0.38,6.12-0.62L16.5,17.5v-5H20.5z"/>
+</svg>
diff --git a/icons/yellow_arrow.png b/icons/yellow_arrow.png
index f3a35f2..600ca8c 100644
--- a/icons/yellow_arrow.png
+++ b/icons/yellow_arrow.png
Binary files differ
diff --git a/backpics/bigbg01.gif b/images/backpics/bigbg01.gif
index dd3b442..dd3b442 100644
--- a/backpics/bigbg01.gif
+++ b/images/backpics/bigbg01.gif
Binary files differ
diff --git a/backpics/bigbg02.gif b/images/backpics/bigbg02.gif
index 45f6cbd..45f6cbd 100644
--- a/backpics/bigbg02.gif
+++ b/images/backpics/bigbg02.gif
Binary files differ
diff --git a/backpics/bigbg03.gif b/images/backpics/bigbg03.gif
index 284c53c..284c53c 100644
--- a/backpics/bigbg03.gif
+++ b/images/backpics/bigbg03.gif
Binary files differ
diff --git a/backpics/bigbg04.gif b/images/backpics/bigbg04.gif
index 728cb29..728cb29 100644
--- a/backpics/bigbg04.gif
+++ b/images/backpics/bigbg04.gif
Binary files differ
diff --git a/backpics/bigbg05.gif b/images/backpics/bigbg05.gif
index 2ce3a01..2ce3a01 100644
--- a/backpics/bigbg05.gif
+++ b/images/backpics/bigbg05.gif
Binary files differ
diff --git a/backpics/bigbg06.gif b/images/backpics/bigbg06.gif
index 19ef057..19ef057 100644
--- a/backpics/bigbg06.gif
+++ b/images/backpics/bigbg06.gif
Binary files differ
diff --git a/backpics/bigbg07.gif b/images/backpics/bigbg07.gif
index 63c08fb..63c08fb 100644
--- a/backpics/bigbg07.gif
+++ b/images/backpics/bigbg07.gif
Binary files differ
diff --git a/backpics/bigbg08.gif b/images/backpics/bigbg08.gif
index 28975b9..28975b9 100644
--- a/backpics/bigbg08.gif
+++ b/images/backpics/bigbg08.gif
Binary files differ
diff --git a/backpics/bigbg09.gif b/images/backpics/bigbg09.gif
index 7081235..7081235 100644
--- a/backpics/bigbg09.gif
+++ b/images/backpics/bigbg09.gif
Binary files differ
diff --git a/backpics/bigbg10.gif b/images/backpics/bigbg10.gif
index 8472741..8472741 100644
--- a/backpics/bigbg10.gif
+++ b/images/backpics/bigbg10.gif
Binary files differ
diff --git a/backpics/bigbg11.gif b/images/backpics/bigbg11.gif
index 94e53d3..94e53d3 100644
--- a/backpics/bigbg11.gif
+++ b/images/backpics/bigbg11.gif
Binary files differ
diff --git a/backpics/bigbg12.gif b/images/backpics/bigbg12.gif
index d2f2235..d2f2235 100644
--- a/backpics/bigbg12.gif
+++ b/images/backpics/bigbg12.gif
Binary files differ
diff --git a/backpics/bigbg13.gif b/images/backpics/bigbg13.gif
index d631e2d..d631e2d 100644
--- a/backpics/bigbg13.gif
+++ b/images/backpics/bigbg13.gif
Binary files differ
diff --git a/backpics/bigbg14.gif b/images/backpics/bigbg14.gif
index c90ff31..c90ff31 100644
--- a/backpics/bigbg14.gif
+++ b/images/backpics/bigbg14.gif
Binary files differ
diff --git a/backpics/bigbg15.gif b/images/backpics/bigbg15.gif
index cfdd076..cfdd076 100644
--- a/backpics/bigbg15.gif
+++ b/images/backpics/bigbg15.gif
Binary files differ
diff --git a/backpics/bigbg16.gif b/images/backpics/bigbg16.gif
index f358416..f358416 100644
--- a/backpics/bigbg16.gif
+++ b/images/backpics/bigbg16.gif
Binary files differ
diff --git a/backpics/bigbg17.gif b/images/backpics/bigbg17.gif
index 49ca132..49ca132 100644
--- a/backpics/bigbg17.gif
+++ b/images/backpics/bigbg17.gif
Binary files differ
diff --git a/images/backpics/custom.png b/images/backpics/custom.png
new file mode 100644
index 0000000..9d2c610
--- /dev/null
+++ b/images/backpics/custom.png
Binary files differ
diff --git a/pics/Elephant/bigelephant0.gif b/images/pics/Elephant/bigelephant0.gif
index 32e2998..32e2998 100644
--- a/pics/Elephant/bigelephant0.gif
+++ b/images/pics/Elephant/bigelephant0.gif
Binary files differ
diff --git a/pics/Elephant/bigelephant1.gif b/images/pics/Elephant/bigelephant1.gif
index aa4b639..aa4b639 100644
--- a/pics/Elephant/bigelephant1.gif
+++ b/images/pics/Elephant/bigelephant1.gif
Binary files differ
diff --git a/pics/Elephant/bigelephant10.gif b/images/pics/Elephant/bigelephant10.gif
index 5719a27..5719a27 100644
--- a/pics/Elephant/bigelephant10.gif
+++ b/images/pics/Elephant/bigelephant10.gif
Binary files differ
diff --git a/pics/Elephant/bigelephant11.gif b/images/pics/Elephant/bigelephant11.gif
index 0832e05..0832e05 100644
--- a/pics/Elephant/bigelephant11.gif
+++ b/images/pics/Elephant/bigelephant11.gif
Binary files differ
diff --git a/pics/Elephant/bigelephant12.gif b/images/pics/Elephant/bigelephant12.gif
index cc2950d..cc2950d 100644
--- a/pics/Elephant/bigelephant12.gif
+++ b/images/pics/Elephant/bigelephant12.gif
Binary files differ
diff --git a/pics/Elephant/bigelephant13.gif b/images/pics/Elephant/bigelephant13.gif
index 3395865..3395865 100644
--- a/pics/Elephant/bigelephant13.gif
+++ b/images/pics/Elephant/bigelephant13.gif
Binary files differ
diff --git a/pics/Elephant/bigelephant2.gif b/images/pics/Elephant/bigelephant2.gif
index 5d03ccc..5d03ccc 100644
--- a/pics/Elephant/bigelephant2.gif
+++ b/images/pics/Elephant/bigelephant2.gif
Binary files differ
diff --git a/pics/Elephant/bigelephant3.gif b/images/pics/Elephant/bigelephant3.gif
index 7fd718a..7fd718a 100644
--- a/pics/Elephant/bigelephant3.gif
+++ b/images/pics/Elephant/bigelephant3.gif
Binary files differ
diff --git a/pics/Elephant/bigelephant4.gif b/images/pics/Elephant/bigelephant4.gif
index 0df7c12..0df7c12 100644
--- a/pics/Elephant/bigelephant4.gif
+++ b/images/pics/Elephant/bigelephant4.gif
Binary files differ
diff --git a/pics/Elephant/bigelephant5.gif b/images/pics/Elephant/bigelephant5.gif
index 35ce9a5..35ce9a5 100644
--- a/pics/Elephant/bigelephant5.gif
+++ b/images/pics/Elephant/bigelephant5.gif
Binary files differ
diff --git a/pics/Elephant/bigelephant6.gif b/images/pics/Elephant/bigelephant6.gif
index a91695b..a91695b 100644
--- a/pics/Elephant/bigelephant6.gif
+++ b/images/pics/Elephant/bigelephant6.gif
Binary files differ
diff --git a/pics/Elephant/bigelephant7.gif b/images/pics/Elephant/bigelephant7.gif
index bcf485a..bcf485a 100644
--- a/pics/Elephant/bigelephant7.gif
+++ b/images/pics/Elephant/bigelephant7.gif
Binary files differ
diff --git a/pics/Elephant/bigelephant8.gif b/images/pics/Elephant/bigelephant8.gif
index 429cc51..429cc51 100644
--- a/pics/Elephant/bigelephant8.gif
+++ b/images/pics/Elephant/bigelephant8.gif
Binary files differ
diff --git a/pics/Elephant/bigelephant9.gif b/images/pics/Elephant/bigelephant9.gif
index dc682da..dc682da 100644
--- a/pics/Elephant/bigelephant9.gif
+++ b/images/pics/Elephant/bigelephant9.gif
Binary files differ
diff --git a/pics/SpaceBlob/bigblob0.gif b/images/pics/SpaceBlob/bigblob0.gif
index 5af8edd..5af8edd 100644
--- a/pics/SpaceBlob/bigblob0.gif
+++ b/images/pics/SpaceBlob/bigblob0.gif
Binary files differ
diff --git a/pics/SpaceBlob/bigblob1.gif b/images/pics/SpaceBlob/bigblob1.gif
index 1e2e9ae..1e2e9ae 100644
--- a/pics/SpaceBlob/bigblob1.gif
+++ b/images/pics/SpaceBlob/bigblob1.gif
Binary files differ
diff --git a/pics/SpaceBlob/bigblob10.gif b/images/pics/SpaceBlob/bigblob10.gif
index 0f538f9..0f538f9 100644
--- a/pics/SpaceBlob/bigblob10.gif
+++ b/images/pics/SpaceBlob/bigblob10.gif
Binary files differ
diff --git a/pics/SpaceBlob/bigblob11.gif b/images/pics/SpaceBlob/bigblob11.gif
index 3f912e5..3f912e5 100644
--- a/pics/SpaceBlob/bigblob11.gif
+++ b/images/pics/SpaceBlob/bigblob11.gif
Binary files differ
diff --git a/pics/SpaceBlob/bigblob12.gif b/images/pics/SpaceBlob/bigblob12.gif
index e5308d9..e5308d9 100644
--- a/pics/SpaceBlob/bigblob12.gif
+++ b/images/pics/SpaceBlob/bigblob12.gif
Binary files differ
diff --git a/pics/SpaceBlob/bigblob13.gif b/images/pics/SpaceBlob/bigblob13.gif
index 7348e6b..7348e6b 100644
--- a/pics/SpaceBlob/bigblob13.gif
+++ b/images/pics/SpaceBlob/bigblob13.gif
Binary files differ
diff --git a/pics/SpaceBlob/bigblob2.gif b/images/pics/SpaceBlob/bigblob2.gif
index 9eee50a..9eee50a 100644
--- a/pics/SpaceBlob/bigblob2.gif
+++ b/images/pics/SpaceBlob/bigblob2.gif
Binary files differ
diff --git a/pics/SpaceBlob/bigblob3.gif b/images/pics/SpaceBlob/bigblob3.gif
index 30f6250..30f6250 100644
--- a/pics/SpaceBlob/bigblob3.gif
+++ b/images/pics/SpaceBlob/bigblob3.gif
Binary files differ
diff --git a/pics/SpaceBlob/bigblob4.gif b/images/pics/SpaceBlob/bigblob4.gif
index 1dd33f0..1dd33f0 100644
--- a/pics/SpaceBlob/bigblob4.gif
+++ b/images/pics/SpaceBlob/bigblob4.gif
Binary files differ
diff --git a/pics/SpaceBlob/bigblob5.gif b/images/pics/SpaceBlob/bigblob5.gif
index bd6f791..bd6f791 100644
--- a/pics/SpaceBlob/bigblob5.gif
+++ b/images/pics/SpaceBlob/bigblob5.gif
Binary files differ
diff --git a/pics/SpaceBlob/bigblob6.gif b/images/pics/SpaceBlob/bigblob6.gif
index e7d17e5..e7d17e5 100644
--- a/pics/SpaceBlob/bigblob6.gif
+++ b/images/pics/SpaceBlob/bigblob6.gif
Binary files differ
diff --git a/pics/SpaceBlob/bigblob7.gif b/images/pics/SpaceBlob/bigblob7.gif
index 9073b5d..9073b5d 100644
--- a/pics/SpaceBlob/bigblob7.gif
+++ b/images/pics/SpaceBlob/bigblob7.gif
Binary files differ
diff --git a/pics/SpaceBlob/bigblob8.gif b/images/pics/SpaceBlob/bigblob8.gif
index f8faf8d..f8faf8d 100644
--- a/pics/SpaceBlob/bigblob8.gif
+++ b/images/pics/SpaceBlob/bigblob8.gif
Binary files differ
diff --git a/pics/SpaceBlob/bigblob9.gif b/images/pics/SpaceBlob/bigblob9.gif
index a627d6f..a627d6f 100644
--- a/pics/SpaceBlob/bigblob9.gif
+++ b/images/pics/SpaceBlob/bigblob9.gif
Binary files differ
diff --git a/pics/Turkey/bigturkey1.gif b/images/pics/Turkey/bigturkey1.gif
index f49b34f..f49b34f 100644
--- a/pics/Turkey/bigturkey1.gif
+++ b/images/pics/Turkey/bigturkey1.gif
Binary files differ
diff --git a/pics/Turkey/bigturkey10.gif b/images/pics/Turkey/bigturkey10.gif
index 709d92e..709d92e 100644
--- a/pics/Turkey/bigturkey10.gif
+++ b/images/pics/Turkey/bigturkey10.gif
Binary files differ
diff --git a/pics/Turkey/bigturkey11.gif b/images/pics/Turkey/bigturkey11.gif
index b80765f..b80765f 100644
--- a/pics/Turkey/bigturkey11.gif
+++ b/images/pics/Turkey/bigturkey11.gif
Binary files differ
diff --git a/pics/Turkey/bigturkey12.gif b/images/pics/Turkey/bigturkey12.gif
index dd043f7..dd043f7 100644
--- a/pics/Turkey/bigturkey12.gif
+++ b/images/pics/Turkey/bigturkey12.gif
Binary files differ
diff --git a/pics/Turkey/bigturkey13.gif b/images/pics/Turkey/bigturkey13.gif
index 91a5bd7..91a5bd7 100644
--- a/pics/Turkey/bigturkey13.gif
+++ b/images/pics/Turkey/bigturkey13.gif
Binary files differ
diff --git a/pics/Turkey/bigturkey14.gif b/images/pics/Turkey/bigturkey14.gif
index c777887..c777887 100644
--- a/pics/Turkey/bigturkey14.gif
+++ b/images/pics/Turkey/bigturkey14.gif
Binary files differ
diff --git a/pics/Turkey/bigturkey2.gif b/images/pics/Turkey/bigturkey2.gif
index 195d1c6..195d1c6 100644
--- a/pics/Turkey/bigturkey2.gif
+++ b/images/pics/Turkey/bigturkey2.gif
Binary files differ
diff --git a/pics/Turkey/bigturkey3.gif b/images/pics/Turkey/bigturkey3.gif
index c0f219d..c0f219d 100644
--- a/pics/Turkey/bigturkey3.gif
+++ b/images/pics/Turkey/bigturkey3.gif
Binary files differ
diff --git a/pics/Turkey/bigturkey4.gif b/images/pics/Turkey/bigturkey4.gif
index 8b93529..8b93529 100644
--- a/pics/Turkey/bigturkey4.gif
+++ b/images/pics/Turkey/bigturkey4.gif
Binary files differ
diff --git a/pics/Turkey/bigturkey5.gif b/images/pics/Turkey/bigturkey5.gif
index 6ecedba..6ecedba 100644
--- a/pics/Turkey/bigturkey5.gif
+++ b/images/pics/Turkey/bigturkey5.gif
Binary files differ
diff --git a/pics/Turkey/bigturkey6.gif b/images/pics/Turkey/bigturkey6.gif
index ed56a65..ed56a65 100644
--- a/pics/Turkey/bigturkey6.gif
+++ b/images/pics/Turkey/bigturkey6.gif
Binary files differ
diff --git a/pics/Turkey/bigturkey7.gif b/images/pics/Turkey/bigturkey7.gif
index f6fc8f8..f6fc8f8 100644
--- a/pics/Turkey/bigturkey7.gif
+++ b/images/pics/Turkey/bigturkey7.gif
Binary files differ
diff --git a/pics/Turkey/bigturkey8.gif b/images/pics/Turkey/bigturkey8.gif
index 41115ed..41115ed 100644
--- a/pics/Turkey/bigturkey8.gif
+++ b/images/pics/Turkey/bigturkey8.gif
Binary files differ
diff --git a/pics/Turkey/bigturkey9.gif b/images/pics/Turkey/bigturkey9.gif
index 4f43698..4f43698 100644
--- a/pics/Turkey/bigturkey9.gif
+++ b/images/pics/Turkey/bigturkey9.gif
Binary files differ
diff --git a/images/pics/custom.png b/images/pics/custom.png
new file mode 100644
index 0000000..9d2c610
--- /dev/null
+++ b/images/pics/custom.png
Binary files differ
diff --git a/images/pics/empty.png b/images/pics/empty.png
new file mode 100644
index 0000000..c14e8c5
--- /dev/null
+++ b/images/pics/empty.png
Binary files differ
diff --git a/images/sounds/custom.png b/images/sounds/custom.png
new file mode 100644
index 0000000..9d2c610
--- /dev/null
+++ b/images/sounds/custom.png
Binary files differ
diff --git a/images/sounds/mute.png b/images/sounds/mute.png
new file mode 100644
index 0000000..aa5cc60
--- /dev/null
+++ b/images/sounds/mute.png
Binary files differ
diff --git a/images/sounds/speaker.png b/images/sounds/speaker.png
new file mode 100644
index 0000000..9283e50
--- /dev/null
+++ b/images/sounds/speaker.png
Binary files differ
diff --git a/lessons.py b/lessons.py
new file mode 100644
index 0000000..2766e0b
--- /dev/null
+++ b/lessons.py
@@ -0,0 +1,76 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import os
+import gtk
+import locale
+import logging
+from glob import glob
+
+import theme
+
+THEMES = []
+
+class Lesson:
+ def __init__(self, index, filename):
+ self.index = index
+ self.name = os.path.splitext(os.path.basename(filename).lstrip(
+ '.-_1234567890').replace('_', ' '))[0]
+ self.text = file(filename, 'r').read()
+
+ def change(self):
+ View.notebook.set_current_page(self.index)
+
+class View(gtk.EventBox):
+ notebook = None
+
+ def __init__(self):
+ gtk.EventBox.__init__(self)
+
+ View.notebook = gtk.Notebook()
+ View.notebook.props.show_border = False
+ View.notebook.props.show_tabs = False
+ self.add(View.notebook)
+
+ for i in THEMES:
+ view = gtk.TextView()
+ view.get_buffer().set_text(i.text)
+ view.set_wrap_mode(gtk.WRAP_WORD)
+ view.set_editable(False)
+
+ view_box = gtk.EventBox()
+ view_box.add(view)
+ view_box.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse(theme.WHITE))
+ view_box.props.border_width = 10
+
+ border_box = gtk.EventBox()
+ border_box.add(view_box)
+ border_box.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse(theme.WHITE))
+
+ scrolled_window = gtk.ScrolledWindow()
+ scrolled_window.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
+ scrolled_window.add_with_viewport(border_box)
+
+ View.notebook.append_page(scrolled_window)
+
+ self.show_all()
+
+_lang = locale.getdefaultlocale()[0].split('_')[0]
+
+if not os.path.isdir(theme.path('lessons', _lang)):
+ logging.info('Cannot find lessons for language %s, thus use en' % _lang)
+ _lang = 'en'
+
+for i, filename in enumerate(sorted(glob(theme.path('lessons', _lang, '*')))):
+ THEMES.append(Lesson(i, filename))
diff --git a/lp-en/An_Overview.txt b/lessons/en/An_Overview.txt
index de375e3..de375e3 100644
--- a/lp-en/An_Overview.txt
+++ b/lessons/en/An_Overview.txt
diff --git a/lp-en/Lesson_Plan_1.txt b/lessons/en/Lesson_Plan_1.txt
index 8c3a14b..8c3a14b 100644
--- a/lp-en/Lesson_Plan_1.txt
+++ b/lessons/en/Lesson_Plan_1.txt
diff --git a/lp-en/Lesson_Plan_2.txt b/lessons/en/Lesson_Plan_2.txt
index 22d76f1..22d76f1 100644
--- a/lp-en/Lesson_Plan_2.txt
+++ b/lessons/en/Lesson_Plan_2.txt
diff --git a/lp-en/Lesson_Plan_3.txt b/lessons/en/Lesson_Plan_3.txt
index e167c56..e167c56 100644
--- a/lp-en/Lesson_Plan_3.txt
+++ b/lessons/en/Lesson_Plan_3.txt
diff --git a/lp-en/Lesson_Plan_4.txt b/lessons/en/Lesson_Plan_4.txt
index 6f685f0..6f685f0 100644
--- a/lp-en/Lesson_Plan_4.txt
+++ b/lessons/en/Lesson_Plan_4.txt
diff --git a/lp-en/Lesson_Plan_5.txt b/lessons/en/Lesson_Plan_5.txt
index ced6437..ced6437 100644
--- a/lp-en/Lesson_Plan_5.txt
+++ b/lessons/en/Lesson_Plan_5.txt
diff --git a/lp-es/0Una_Descripcion.txt b/lessons/es/0Una_Descripcion.txt
index de375e3..de375e3 100644
--- a/lp-es/0Una_Descripcion.txt
+++ b/lessons/es/0Una_Descripcion.txt
diff --git a/lp-es/Plan_1.txt b/lessons/es/Plan_1.txt
index 8c3a14b..8c3a14b 100644
--- a/lp-es/Plan_1.txt
+++ b/lessons/es/Plan_1.txt
diff --git a/lp-es/Plan_2.txt b/lessons/es/Plan_2.txt
index 22d76f1..22d76f1 100644
--- a/lp-es/Plan_2.txt
+++ b/lessons/es/Plan_2.txt
diff --git a/lp-es/Plan_3.txt b/lessons/es/Plan_3.txt
index e167c56..e167c56 100644
--- a/lp-es/Plan_3.txt
+++ b/lessons/es/Plan_3.txt
diff --git a/lp-es/Plan_4.txt b/lessons/es/Plan_4.txt
index 6f685f0..6f685f0 100644
--- a/lp-es/Plan_4.txt
+++ b/lessons/es/Plan_4.txt
diff --git a/lp-es/Plan_5.txt b/lessons/es/Plan_5.txt
index ced6437..ced6437 100644
--- a/lp-es/Plan_5.txt
+++ b/lessons/es/Plan_5.txt
diff --git a/messenger.py b/messenger.py
new file mode 100644
index 0000000..f034ee8
--- /dev/null
+++ b/messenger.py
@@ -0,0 +1,284 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import cjson
+import logging
+import dbus
+from dbus.gobject_service import ExportedGObject
+from dbus.service import method, signal
+
+from sugar.presence import presenceservice
+
+import char
+import ground
+import sound
+from document import Document
+
+logger = logging.getLogger('cartoon-builder')
+
+SERVICE = 'org.sugarlabs.CartoonBuilder'
+IFACE = SERVICE
+PATH = '/org/sugarlabs/CartoonBuilder'
+
+class Slot:
+ def __init__(self, sender=None, raw=None):
+ if sender:
+ data = cjson.decode(raw)
+ self.seqno = data['seqno']
+ self.oid = data['oid']
+ self.sender = sender
+ else:
+ self.seqno = -1
+ self.oid = None
+ self.sender = None
+
+ def serialize(self):
+ return cjson.encode({
+ 'seqno': self.seqno,
+ 'oid' : self.oid})
+
+class Messenger(ExportedGObject):
+ def __init__(self, tube, initiator, view):
+ ExportedGObject.__init__(self, tube, PATH)
+
+ self.initiator = initiator
+ self._tube = tube
+ self._entered = False
+ self._slots = {}
+ self._view = view
+
+ self._view.connect('frame-changed', self._frame_changed_cb)
+ self._view.connect('ground-changed', self._ground_changed_cb)
+ self._view.connect('sound-changed', self._sound_changed_cb)
+ self._tube.watch_participants(self._participant_change_cb)
+
+ def _participant_change_cb(self, added, removed):
+ if not self._entered and added:
+ self.me = self._tube.get_unique_name()
+
+ slots = [('%s:%d' % (FRAME, i), f) \
+ for i, f in enumerate(Document.tape)] + \
+ [(GROUND, Document.ground), (SOUND, Document.sound)]
+ for i in slots:
+ self._slots[i[0]] = Slot()
+
+ if self.initiator:
+ self._tube.add_signal_receiver(self._ping_cb, '_ping', IFACE,
+ path=PATH, sender_keyword='sender')
+ for i in slots:
+ slot = self._slots[i[0]]
+ slot.seqno = 0
+ slot.oid = i[1].id
+ slot.sender = self.me
+ else:
+ self._pong_handle = self._tube.add_signal_receiver(
+ self._pong_cb, '_pong', IFACE, path=PATH,
+ sender_keyword='sender')
+ self._ping()
+
+
+ self._tube.add_signal_receiver(self._notify_cb, '_notify', IFACE,
+ path=PATH, sender_keyword='sender')
+ self._entered = True
+
+ # incomers' signal to retrieve initial snapshot
+ @signal(IFACE, signature='')
+ def _ping(self):
+ logger.debug('send ping')
+ pass
+
+ # object is ready to post snapshot to incomer
+ @signal(IFACE, signature='')
+ def _pong(self):
+ logger.debug('send pong')
+ pass
+
+ # slot was changed
+ @signal(IFACE, signature='ss')
+ def _notify(self, slot, raw):
+ pass
+
+ # the whole list of slots for incomers
+ @method(dbus_interface=IFACE, in_signature='', out_signature='a{ss}',
+ sender_keyword='sender')
+ def _snapshot(self, sender=None):
+ logger.debug('_snapshot requested from %s' % sender)
+ out = {}
+
+ for i, slot in self._slots.items():
+ out[i] = slot.serialize()
+
+ return out
+
+ # fetch content of specified object
+ @method(dbus_interface=IFACE, in_signature='ss', out_signature='say',
+ sender_keyword='sender', byte_arrays=True)
+ def _fetch(self, type, oid, sender=None):
+ logger.debug('_fetch requested from %s type=%s oid=%s' \
+ % (sender, type, oid))
+ return object_serialize(type, oid)
+
+ def _ping_cb(self, sender=None):
+ if sender == self.me:
+ return
+ logger.debug('_ping received from %s' % sender)
+ self._pong()
+
+ def _pong_cb(self, sender=None):
+ if sender == self.me:
+ return
+ logger.debug('_pong sent from %s' % sender)
+
+ # we've got source for _snapshot and don't need _pong anymore
+ self._tube.remove_signal_receiver(self._pong_handle)
+ self._pong_handle = None
+
+ remote = self._tube.get_object(sender, PATH)
+ rawlist = remote._snapshot()
+
+ logger.debug('snapshot received len=%d' % len(rawlist))
+
+ for slot, raw in rawlist.items():
+ self._receive(slot, raw, sender)
+
+ # we are ready to receive _snapshot requests
+ self._tube.add_signal_receiver(self._ping_cb, '_ping', IFACE,
+ path=PATH, sender_keyword='sender')
+
+ def _notify_cb(self, slot, raw, sender=None):
+ if sender == self.me:
+ return
+ logger.debug('_notify requested from %s' % sender)
+ self._receive(slot, raw, sender)
+
+ def _receive(self, slot, raw, sender):
+ cur = self._slots[slot]
+ new = Slot(sender, raw)
+
+ logger.debug('object received slot=%s seqno=%d sender=%s oid=%s from %s'
+ % (slot, new.seqno, new.sender, new.oid, sender))
+
+ if cur.seqno > new.seqno:
+ logger.debug('trying to rewrite newer value by older one')
+ return
+ elif cur.seqno == new.seqno:
+ # arrived value was sent at the same time as current one
+ if cur.sender > sender:
+ logger.debug('current value is higher ranked then arrived')
+ return
+ if cur.sender == self.me:
+ # we sent current and arrived value rewrites it
+ logger.debug('resend current with higher seqno')
+ self._send(slot, cur.oid)
+ return
+ else:
+ logger.debug('just discard low rank')
+ return
+ else:
+ logger.debug('accept higher seqno')
+
+ if new.oid and not object_find(slot, new.oid):
+ remote = self._tube.get_object(sender, PATH)
+ name, raw = remote._fetch(slot, new.oid, byte_arrays=True)
+ object_new(slot, new.oid, name, raw)
+
+ object_select(self._view, slot, new.oid)
+ self._slots[slot] = new
+
+ def _send(self, slot_num, oid):
+ slot = self._slots[slot_num]
+ slot.seqno += 1
+ slot.sender = self.me
+ slot.oid = oid
+ self._notify(slot_num, slot.serialize())
+
+ logger.debug('_send slot=%s oid=%s seqno=%d'
+ % (slot_num, oid, slot.seqno))
+
+ def _frame_changed_cb(self, sender, index, frame):
+ self._send('%s:%d' % (FRAME, index), frame and frame.id)
+
+ def _ground_changed_cb(self, sender, ground):
+ self._send(GROUND, ground.id)
+
+ def _sound_changed_cb(self, sender, sound):
+ self._send(SOUND, sound.id)
+
+FRAME = 'frame'
+GROUND = 'ground'
+SOUND = 'sound'
+
+OBJECTS = {
+ FRAME : char.THEMES[-1].frames,
+ GROUND : ground.THEMES,
+ SOUND : sound.THEMES }
+
+def object_find(type, oid):
+ if type.startswith(FRAME):
+ for c in char.THEMES:
+ if not c:
+ continue
+ for i in c.frames:
+ if i.id == oid:
+ return i
+ else:
+ for i in OBJECTS[type.split(':')[0]]:
+ if i and i.id == oid:
+ return i
+ return None
+
+def object_new(type, oid, name, raw):
+ logger.debug('add new object type=%s oid=%s' % (type, oid))
+
+ if type.startswith(FRAME):
+ object = char.RestoredFrame(oid, raw)
+ for i, frame in enumerate(OBJECTS[FRAME]):
+ if not frame.id:
+ OBJECTS[FRAME][i] = object
+ return
+ elif type.startswith(GROUND):
+ object = ground.RestoredGround(name, oid, raw)
+ elif type.startswith(SOUND):
+ object = sound.RestoredSound(name, oid, raw)
+ else:
+ logger.error('cannot create object of type %s' % type)
+ return
+
+ OBJECTS[type.split(':')[0]].append(object)
+
+def object_serialize(type, oid):
+ object = object_find(type, oid)
+
+ if object:
+ return (object.name, object.serialize())
+ else:
+ logger.error('cannot find object to serialize type=%s oid=%s' \
+ % (type, oid))
+ return ('', '')
+
+def object_select(view, type, oid):
+ if oid:
+ object = object_find(type, oid)
+ else:
+ object = None
+
+ if type.startswith(FRAME):
+ index = int(type.split(':')[1])
+ view.props.frame = (index, object)
+ elif type.startswith(GROUND):
+ view.props.ground = object
+ elif type.startswith(SOUND):
+ view.props.sound = object
+ else:
+ logger.error('cannot find object to select type=%s oid=%s' % (type, oid))
diff --git a/montage.py b/montage.py
new file mode 100644
index 0000000..a92f0d9
--- /dev/null
+++ b/montage.py
@@ -0,0 +1,428 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+### cartoonbuilder
+###
+### author: Ed Stoner (ed@whsd.net)
+### (c) 2007 World Wide Workshop Foundation
+
+import gtk
+import gobject
+import logging
+from gobject import SIGNAL_RUN_FIRST, TYPE_PYOBJECT
+
+import theme
+import char
+import ground
+import sound
+from document import Document, clean
+from screen import Screen
+from utils import *
+
+logger = logging.getLogger('cartoon-builder')
+
+def play():
+ View.play_tape_num = 0
+ View.playing = gobject.timeout_add(View.delay, _play_tape)
+
+def stop():
+ View.playing = None
+ View.screen.fgpixbuf = Document.tape[View.tape_selected].orig()
+ View.screen.draw()
+
+def set_tempo(tempo):
+ View.delay = 10 + (10-int(tempo)) * 100
+ if View.playing:
+ gobject.source_remove(View.playing)
+ View.playing = gobject.timeout_add(View.delay, _play_tape)
+
+def clear_tape():
+ for i in range(TAPE_COUNT):
+ clean(i)
+ View.tape[i].child.set_from_pixbuf(theme.EMPTY_THUMB)
+
+ View.screen.fgpixbuf = Document.tape[View.tape_selected].orig()
+ View.screen.draw()
+
+def _play_tape():
+ if not View.playing:
+ return False
+
+ View.screen.fgpixbuf = Document.tape[View.play_tape_num].orig()
+ View.screen.draw()
+
+ for i in range(theme.TAPE_COUNT):
+ View.play_tape_num += 1
+ if View.play_tape_num == TAPE_COUNT:
+ View.play_tape_num = 0
+ if Document.tape[View.play_tape_num].empty():
+ continue
+ return True
+
+ return True
+
+class View(gtk.EventBox):
+ __gsignals__ = {
+ 'frame-changed' : (SIGNAL_RUN_FIRST, None, 2*[TYPE_PYOBJECT]),
+ 'ground-changed': (SIGNAL_RUN_FIRST, None, [TYPE_PYOBJECT]),
+ 'sound-changed' : (SIGNAL_RUN_FIRST, None, [TYPE_PYOBJECT]) }
+
+ screen = Screen()
+ play_tape_num = 0
+ playing = None
+ delay = 3*150
+ tape_selected = -1
+ tape = []
+
+ def set_frame(self, value):
+ tape_num, frame = value
+
+ if frame == None:
+ clean(tape_num)
+ View.tape[tape_num].child.set_from_pixbuf(theme.EMPTY_THUMB)
+ else:
+ if not frame.select():
+ return False
+
+ Document.tape[tape_num] = frame
+ View.tape[tape_num].child.set_from_pixbuf(frame.thumb())
+
+ if frame.custom():
+ index = [i for i, f in enumerate(char.THEMES[-1].frames)
+ if f == frame][0]
+ if index >= len(self._frames):
+ first = index / theme.FRAME_COLS * theme.FRAME_COLS
+ for i in range(first, first + theme.FRAME_COLS):
+ self._add_frame(i)
+
+ if self.char.custom():
+ self._frames[index].set_from_pixbuf(frame.thumb())
+
+ if View.tape_selected == tape_num:
+ self._tape_cb(None, None, tape_num)
+
+ return True
+
+ def set_ground(self, value):
+ self._set_combo(self._ground_combo, value)
+
+ def set_sound(self, value):
+ self._set_combo(self._sound_combo, value)
+
+ def _set_combo(self, combo, value):
+ try:
+ self._stop_emission = True
+ pos = -1
+
+ for i, item in enumerate(combo.get_model()):
+ if item[0] == value:
+ pos = i
+ break
+
+ if pos == -1:
+ combo.append_item(value, text = value.name,
+ size = (theme.THUMB_SIZE, theme.THUMB_SIZE),
+ pixbuf = value.thumb())
+ pos = len(combo.get_model())-1
+
+ combo.set_active(pos)
+ finally:
+ self._stop_emission = False
+
+ frame = gobject.property(type=object, getter=None, setter=set_frame)
+ ground = gobject.property(type=object, getter=None, setter=set_ground)
+ sound = gobject.property(type=object, getter=None, setter=set_sound)
+
+ def restore(self):
+ def new_combo(themes, cb, object = None, closure = None):
+ combo = ComboBox()
+ sel = 0
+
+ for i, o in enumerate(themes):
+ if o:
+ combo.append_item(o, text = o.name,
+ size = (theme.THUMB_SIZE, theme.THUMB_SIZE),
+ pixbuf = o.thumb())
+ if object and o.name == object.name:
+ sel = i
+ else:
+ combo.append_separator()
+
+ combo.connect('changed', cb, closure)
+ combo.set_active(sel)
+ combo.show()
+
+ return combo
+
+ self.controlbox.pack_start(new_combo(char.THEMES, self._char_cb),
+ False, False)
+ self._ground_combo = new_combo(ground.THEMES, self._combo_cb,
+ Document.ground, self._ground_cb)
+ self.controlbox.pack_start(self._ground_combo, False, False)
+ self._sound_combo = new_combo(sound.THEMES, self._combo_cb,
+ Document.sound, self._sound_cb)
+ self.controlbox.pack_start(self._sound_combo, False, False)
+
+ for i in range(theme.TAPE_COUNT):
+ View.tape[i].child.set_from_pixbuf(Document.tape[i].thumb())
+ self._tape_cb(None, None, 0)
+
+ return False
+
+ def __init__(self):
+ gtk.EventBox.__init__(self)
+
+ self.char = None
+ self._frames = []
+ self._prev_combo_selected = {}
+ self._stop_emission = False
+
+ # frames table
+
+ self.table = gtk.Table(#theme.FRAME_ROWS, columns=theme.FRAME_COLS,
+ homogeneous=False)
+
+ for i in range(theme.FRAME_ROWS * theme.FRAME_COLS):
+ self._add_frame(i)
+
+ # frames box
+
+ table_scroll = VScrolledBox()
+ table_scroll.set_viewport(self.table)
+ table_scroll.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse(BUTTON_BACKGROUND))
+
+ yellow_frames = gtk.EventBox()
+ yellow_frames.modify_bg(gtk.STATE_NORMAL,gtk.gdk.color_parse(YELLOW))
+ table_frames = gtk.EventBox()
+ table_frames.modify_bg(gtk.STATE_NORMAL,gtk.gdk.color_parse(BACKGROUND))
+ table_frames.set_border_width(5)
+ table_frames.add(table_scroll)
+ yellow_frames.add(table_frames)
+
+ yelow_arrow = gtk.Image()
+ yelow_arrow.set_from_file(theme.path('icons', 'yellow_arrow.png'))
+
+ frames_box = gtk.VBox()
+ frames_box.pack_start(yellow_frames, True, True)
+ frames_box.pack_start(yelow_arrow, False, False)
+ frames_box.props.border_width = theme.BORDER_WIDTH
+
+ # screen
+
+ screen_pink = gtk.EventBox()
+ screen_pink.modify_bg(gtk.STATE_NORMAL,gtk.gdk.color_parse(PINK))
+ screen_box = gtk.EventBox()
+ screen_box.set_border_width(5)
+ screen_box.add(View.screen)
+ screen_pink.add(screen_box)
+ screen_pink.props.border_width = theme.BORDER_WIDTH
+
+ # tape
+
+ tape = gtk.HBox()
+
+ for i in range(TAPE_COUNT):
+ frame_box = gtk.VBox()
+
+ filmstrip_pixbuf = gtk.gdk.pixbuf_new_from_file_at_scale(
+ theme.path('icons', 'filmstrip.png'), THUMB_SIZE, -1, False)
+
+ filmstrip = gtk.Image()
+ filmstrip.set_from_pixbuf(filmstrip_pixbuf);
+ frame_box.pack_start(filmstrip, False, False)
+
+ frame = gtk.EventBox()
+ frame.set_events(gtk.gdk.BUTTON_PRESS_MASK)
+ frame.connect('button_press_event', self._tape_cb, i)
+ frame.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse(BLACK))
+ frame.modify_bg(gtk.STATE_PRELIGHT, gtk.gdk.color_parse(BLACK))
+ frame.props.border_width = 2
+ frame.set_size_request(theme.THUMB_SIZE, theme.THUMB_SIZE)
+ frame_box.pack_start(frame)
+ View.tape.append(frame)
+
+ frame_image = gtk.Image()
+ frame_image.set_from_pixbuf(theme.EMPTY_THUMB)
+ frame.add(frame_image)
+
+ filmstrip = gtk.Image()
+ filmstrip.set_from_pixbuf(filmstrip_pixbuf);
+ frame_box.pack_start(filmstrip, False, False)
+
+ tape.pack_start(frame_box, False, False)
+
+ # left control box
+
+ self.controlbox = gtk.VBox()
+ self.controlbox.props.border_width = theme.BORDER_WIDTH
+ self.controlbox.props.spacing = theme.BORDER_WIDTH
+
+ leftbox = gtk.VBox()
+ logo = gtk.Image()
+ logo.set_from_file(theme.path('icons', 'logo.png'))
+ leftbox.set_size_request(logo.props.pixbuf.get_width(), -1)
+ leftbox.pack_start(logo, False, False)
+ leftbox.pack_start(self.controlbox, True, True)
+
+ # screen box
+
+ screen_alignment = gtk.Alignment(0.5, 0.5, 0, 0)
+ screen_alignment.add(screen_pink)
+ screen_alignment.connect('size-allocate', self._screen_size_cb)
+
+ cetralbox = gtk.HBox()
+ cetralbox.pack_start(screen_alignment, True, True)
+ cetralbox.pack_start(frames_box, True, False)
+
+ hdesktop = gtk.HBox()
+ hdesktop.pack_start(leftbox,False,True,0)
+ hdesktop.pack_start(cetralbox,True,True,0)
+
+ # tape box
+
+ arrow = gtk.Image()
+ arrow.set_from_file(theme.path('icons', 'pink_arrow.png'))
+ tape_pink = gtk.EventBox()
+ tape_pink.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse(PINK))
+ tape_bg = gtk.EventBox()
+ tape_bg.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse(BACKGROUND))
+ tape_bg.set_border_width(5)
+ tape_bg.add(tape)
+ tape_pink.add(tape_bg)
+
+ tape_hbox = gtk.HBox()
+ tape_hbox.pack_start(tape_pink, True, False)
+
+ tape_box = gtk.VBox()
+ tape_box.props.border_width = theme.BORDER_WIDTH
+ tape_box.pack_start(arrow, False, False)
+ tape_box.pack_start(tape_hbox)
+
+ desktop = gtk.VBox()
+ desktop.pack_start(hdesktop,True,True,0)
+ desktop.pack_start(tape_box, False, False, 0)
+
+ greenbox = gtk.EventBox()
+ greenbox.modify_bg(gtk.STATE_NORMAL,gtk.gdk.color_parse(BACKGROUND))
+ greenbox.set_border_width(5)
+ greenbox.add(desktop)
+
+ self.modify_bg(gtk.STATE_NORMAL,gtk.gdk.color_parse(YELLOW))
+ self.add(greenbox)
+ self.show_all()
+
+ def _add_frame(self, index):
+ y = index / theme.FRAME_COLS
+ x = index - y*theme.FRAME_COLS
+ logger.debug('add new frame x=%d y=%d index=%d' % (x, y, index))
+
+ image = gtk.Image()
+ image.show()
+ image.set_from_pixbuf(theme.EMPTY_THUMB)
+ self._frames.append(image)
+
+ image_box = gtk.EventBox()
+ image_box.set_events(gtk.gdk.BUTTON_PRESS_MASK)
+ image_box.connect('button_press_event', self._frame_cb, index)
+ image_box.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse(BLACK))
+ image_box.modify_bg(gtk.STATE_PRELIGHT, gtk.gdk.color_parse(BLACK))
+ image_box.props.border_width = 2
+ image_box.set_size_request(theme.THUMB_SIZE, theme.THUMB_SIZE)
+ image_box.add(image)
+
+ if self.char and self.char.custom():
+ image_box.show()
+
+ self.table.attach(image_box, x, x+1, y, y+1)
+
+ return image
+
+ def _tape_cb(self, widget, event, index):
+ if event and event.button == 3:
+ self.set_frame((index, None))
+ self.emit('frame-changed', index, None)
+ return
+
+ tape = View.tape[index]
+ tape.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse(YELLOW))
+ tape.modify_bg(gtk.STATE_PRELIGHT, gtk.gdk.color_parse(YELLOW))
+
+ if View.tape_selected != index:
+ if View.tape_selected != -1:
+ old_tape = View.tape[View.tape_selected]
+ old_tape.modify_bg(gtk.STATE_NORMAL,
+ gtk.gdk.color_parse(BLACK))
+ old_tape.modify_bg(gtk.STATE_PRELIGHT,
+ gtk.gdk.color_parse(BLACK))
+
+ View.tape_selected = index
+ View.screen.fgpixbuf = Document.tape[index].orig()
+ View.screen.draw()
+
+ def _frame_cb(self, widget, event, i):
+ if event.button == 3:
+ self.char.clean(i)
+ self._frames[i].set_from_pixbuf(self.char.frames[i].thumb())
+ else:
+ if i < len(self.char.frames):
+ frame = self.char.frames[i]
+ if not self.set_frame((View.tape_selected, frame)):
+ return
+ else:
+ frame = None
+ self.set_frame((View.tape_selected, None))
+
+ self.emit('frame-changed', View.tape_selected, frame)
+
+ def _char_cb(self, widget, closure):
+ self.char = widget.props.value
+ for i in range(len(self._frames)):
+ if i < len(self.char.frames):
+ self._frames[i].set_from_pixbuf(self.char.frames[i].thumb())
+ self._frames[i].parent.show()
+ else:
+ self._frames[i].parent.hide()
+
+ def _combo_cb(self, widget, cb):
+ choice = widget.props.value.select()
+
+ if not choice:
+ widget.set_active(self._prev_combo_selected[widget])
+ return
+
+ if id(choice) != id(widget.props.value):
+ widget.append_item(choice, text = choice.name,
+ size = (theme.THUMB_SIZE, theme.THUMB_SIZE),
+ pixbuf = choice.thumb())
+ widget.set_active(len(widget.get_model())-1)
+
+ self._prev_combo_selected[widget] = widget.get_active()
+ cb(choice)
+
+ def _ground_cb(self, choice):
+ View.screen.bgpixbuf = choice.orig()
+ View.screen.draw()
+ Document.ground = choice
+ if not self._stop_emission:
+ self.emit('ground-changed', choice)
+
+ def _sound_cb(self, choice):
+ Document.sound = choice
+ if not self._stop_emission:
+ self.emit('sound-changed', choice)
+
+ def _screen_size_cb(self, widget, aloc):
+ size = min(aloc.width, aloc.height)
+ widget.child.set_size_request(size, size)
diff --git a/po/CartoonBuilder.pot b/po/CartoonBuilder.pot
new file mode 100644
index 0000000..1abd706
--- /dev/null
+++ b/po/CartoonBuilder.pot
@@ -0,0 +1,141 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2009-02-09 11:12+0000\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=CHARSET\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: activity/activity.info:2
+msgid "CartoonBuilder"
+msgstr ""
+
+#: ground.py:81
+msgid "Saturn"
+msgstr ""
+
+#: ground.py:82
+msgid "Snowflakes"
+msgstr ""
+
+#: ground.py:83
+msgid "Eye"
+msgstr ""
+
+#: ground.py:84
+msgid "Blobs"
+msgstr ""
+
+#: ground.py:85
+msgid "Star Night"
+msgstr ""
+
+#: ground.py:86
+msgid "Forest"
+msgstr ""
+
+#: ground.py:87
+msgid "Spiral"
+msgstr ""
+
+#: ground.py:88
+msgid "Beam"
+msgstr ""
+
+#: ground.py:89
+msgid "Cloth"
+msgstr ""
+
+#: ground.py:90
+msgid "Faces"
+msgstr ""
+
+#: ground.py:91
+msgid "Leaves"
+msgstr ""
+
+#: ground.py:92
+msgid "Vegetables"
+msgstr ""
+
+#: ground.py:93
+msgid "Spotlight"
+msgstr ""
+
+#: ground.py:94
+msgid "Strips"
+msgstr ""
+
+#: ground.py:95
+msgid "Scene"
+msgstr ""
+
+#: ground.py:96
+msgid "Rhombs"
+msgstr ""
+
+#: ground.py:97
+msgid "Milky Way"
+msgstr ""
+
+#: ground.py:99 char.py:149 sound.py:113
+msgid "Custom"
+msgstr ""
+
+#: char.py:142
+msgid "Elephant"
+msgstr ""
+
+#: char.py:144
+msgid "Space Blob"
+msgstr ""
+
+#: char.py:146
+msgid "Turkey"
+msgstr ""
+
+#: sound.py:107
+msgid "Gobble"
+msgstr ""
+
+#: sound.py:108
+msgid "Funk"
+msgstr ""
+
+#: sound.py:109
+msgid "Giggle"
+msgstr ""
+
+#: sound.py:110
+msgid "Jungle"
+msgstr ""
+
+#: sound.py:111
+msgid "Mute"
+msgstr ""
+
+#: activity.py:57
+msgid "Montage"
+msgstr ""
+
+#: activity.py:61
+msgid "Lessons"
+msgstr ""
+
+#: activity.py:93
+msgid "Play / Pause"
+msgstr ""
+
+#: activity.py:118
+msgid "Reset"
+msgstr ""
diff --git a/screen.py b/screen.py
new file mode 100644
index 0000000..29ed2b7
--- /dev/null
+++ b/screen.py
@@ -0,0 +1,58 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+### cartoonbuilder
+###
+### author: Ed Stoner (ed@whsd.net)
+### (c) 2007 World Wide Workshop Foundation
+
+import gtk
+
+import theme
+
+class Screen(gtk.DrawingArea):
+ def __init__(self):
+ gtk.DrawingArea.__init__(self)
+ self.gc = None # initialized in realize-event handler
+ self.width = 0 # updated in size-allocate handler
+ self.height = 0 # idem
+ self.bgpixbuf = None
+ self.fgpixbuf = None
+ self.connect('size-allocate', self.on_size_allocate)
+ self.connect('expose-event', self.on_expose_event)
+ self.connect('realize', self.on_realize)
+
+ def on_realize(self, widget):
+ self.gc = widget.window.new_gc()
+
+ def on_size_allocate(self, widget, allocation):
+ self.height = self.width = min(allocation.width, allocation.height)
+
+ def on_expose_event(self, widget, event):
+ # This is where the drawing takes place
+ if self.bgpixbuf:
+ pixbuf = self.bgpixbuf
+ if pixbuf.get_width != self.width:
+ pixbuf = theme.scale(pixbuf, self.width)
+ widget.window.draw_pixbuf(self.gc, pixbuf, 0, 0, 0, 0, -1, -1, 0, 0)
+
+ if self.fgpixbuf:
+ pixbuf = self.fgpixbuf
+ if pixbuf.get_width != self.width:
+ pixbuf = theme.scale(pixbuf, self.width)
+ widget.window.draw_pixbuf(self.gc, pixbuf, 0, 0, 0, 0, -1, -1, 0, 0)
+
+ def draw(self):
+ self.queue_draw()
diff --git a/setup.py b/setup.py
index ed6ded8..019d64e 100755
--- a/setup.py
+++ b/setup.py
@@ -1,3 +1,20 @@
#!/usr/bin/python
+
+# Copyright (C) 2006, Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
from sugar.activity import bundlebuilder
-bundlebuilder.start('cartoon-builder')
+bundlebuilder.start()
diff --git a/shared.py b/shared.py
new file mode 100644
index 0000000..98fb965
--- /dev/null
+++ b/shared.py
@@ -0,0 +1,130 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import logging
+import telepathy
+from gobject import property, SIGNAL_RUN_FIRST, TYPE_PYOBJECT
+
+from sugar.activity.activity import Activity
+from sugar.presence.sugartubeconn import SugarTubeConnection
+
+logger = logging.getLogger('cartoon-builder')
+
+class CanvasActivity(Activity):
+ __gsignals__ = {
+ 'init' : (SIGNAL_RUN_FIRST, None, []) }
+
+ def __init__(self, canvas, *args):
+ Activity.__init__(self, *args)
+
+ self._inited = False
+
+ # XXX do it after(possible) read_file() invoking
+ # have to rely on calling read_file() from map_cb in sugar-toolkit
+ canvas.connect_after('map', self._map_cb)
+ self.set_canvas(canvas)
+
+ def get_inited(self):
+ return self._inited
+
+ inited = property(type=bool, default=False, getter=get_inited, setter=None)
+
+ def _map_cb(self, widget):
+ self._inited = True
+ self.emit('init')
+
+class SharedActivity(CanvasActivity):
+ __gsignals__ = {
+ 'tube' : (SIGNAL_RUN_FIRST, None, 2*[TYPE_PYOBJECT]) }
+
+ def __init__(self, canvas, service, *args):
+ CanvasActivity.__init__(self, canvas, *args)
+
+ self.service = service
+ self._postpone_tubes = []
+
+ self.connect('init', self._init_sharedactivity_cb)
+ self.connect('shared', self._shared_cb)
+
+ # Owner.props.key
+ if self._shared_activity:
+ # We are joining the activity
+ self.connect('joined', self._joined_cb)
+ if self.get_shared():
+ # We've already joined
+ self._joined_cb()
+
+ def _init_sharedactivity_cb(self):
+ for i in self._postpone_tubes:
+ self.emit('tube', i, self._initiating)
+ self._postpone_tubes = []
+
+ def _shared_cb(self, activity):
+ logger.debug('My activity was shared')
+ self._initiating = True
+ self._sharing_setup()
+
+ logger.debug('This is my activity: making a tube...')
+ id = self._tubes_chan[telepathy.CHANNEL_TYPE_TUBES].OfferDBusTube(
+ self.service, {})
+
+ def _joined_cb(self, activity):
+ if not self._shared_activity:
+ return
+
+ logger.debug('Joined an existing shared activity')
+
+ self._initiating = False
+ self._sharing_setup()
+
+ logger.debug('This is not my activity: waiting for a tube...')
+ self._tubes_chan[telepathy.CHANNEL_TYPE_TUBES].ListTubes(
+ reply_handler=self._list_tubes_reply_cb,
+ error_handler=self._list_tubes_error_cb)
+
+ def _sharing_setup(self):
+ if self._shared_activity is None:
+ logger.error('Failed to share or join activity')
+ return
+ self._conn = self._shared_activity.telepathy_conn
+ self._tubes_chan = self._shared_activity.telepathy_tubes_chan
+ self._text_chan = self._shared_activity.telepathy_text_chan
+
+ self._tubes_chan[telepathy.CHANNEL_TYPE_TUBES].connect_to_signal('NewTube', self._new_tube_cb)
+
+ def _list_tubes_reply_cb(self, tubes):
+ for tube_info in tubes:
+ self._new_tube_cb(*tube_info)
+
+ def _list_tubes_error_cb(self, e):
+ logger.error('ListTubes() failed: %s', e)
+
+ def _new_tube_cb(self, id, initiator, type, service, params, state):
+ logger.debug('New tube: ID=%d initator=%d type=%d service=%s '
+ 'params=%r state=%d', id, initiator, type, service,
+ params, state)
+
+ if (type == telepathy.TUBE_TYPE_DBUS and
+ service == self.service):
+ if state == telepathy.TUBE_STATE_LOCAL_PENDING:
+ self._tubes_chan[telepathy.CHANNEL_TYPE_TUBES].AcceptDBusTube(id)
+
+ tube_conn = SugarTubeConnection(self._conn,
+ self._tubes_chan[telepathy.CHANNEL_TYPE_TUBES],
+ id, group_iface=self._text_chan[telepathy.CHANNEL_INTERFACE_GROUP])
+
+ if self.get_inited():
+ self.emit('tube', tube_conn, self._initiating)
+ else:
+ self._postpone_tubes.append(tube_conn)
diff --git a/sound.py b/sound.py
new file mode 100644
index 0000000..7819b93
--- /dev/null
+++ b/sound.py
@@ -0,0 +1,143 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import os
+import gtk
+import gst
+import shutil
+from glob import glob
+from gettext import gettext as _
+
+import theme
+from utils import *
+from sugar.activity.activity import get_bundle_path
+
+def load():
+ from document import Document
+
+ if Document.sound and Document.sound.custom():
+ THEMES.append(Document.sound)
+
+class Sound:
+ playing = False
+ current = None
+ player = None
+
+ def __init__(self, name, id, soundfile, thumb):
+ self.name = name
+ self.id = id
+ self._soundfile = soundfile
+ self._thumb = theme.pixbuf(thumb, theme.THUMB_SIZE)
+
+ def custom(self):
+ return True
+
+ def serialize(self):
+ return file(self._soundfile, 'r').read()
+
+ def thumb(self):
+ return self._thumb
+
+ def select(self):
+ Sound.current = self
+ if Sound.playing:
+ Sound.player.set_state(gst.STATE_NULL)
+ Sound.player.set_property('uri',
+ 'file://' + theme.path(self._soundfile))
+ Sound.player.set_state(gst.STATE_PLAYING)
+ return self
+
+class PreinstalledSound(Sound):
+ def __init__(self, name, filename):
+ Sound.__init__(self, name, filename, filename, theme.SOUND_SPEAKER)
+
+ def custom(self):
+ return False
+
+class MuteSound(Sound):
+ def __init__(self, name):
+ Sound.__init__(self, name, 'mute', None, theme.SOUND_MUTE)
+
+ def custom(self):
+ return False
+
+ def serialize(self):
+ return ''
+
+ def select(self):
+ Sound.player.set_state(gst.STATE_NULL)
+ return self
+
+class CustomSound(Sound):
+ def __init__(self, name):
+ Sound.__init__(self, name, None, None, theme.SOUND_CUSTOM)
+
+ def select(self):
+ sound = theme.choose(lambda jobject: JournalSound(jobject))
+ if sound:
+ sound.select()
+ return sound
+
+class RestoredSound(Sound):
+ def __init__(self, name, id, data):
+ soundfile = os.path.join(theme.SESSION_PATH, id)
+ Sound.__init__(self, name, id, soundfile, theme.SOUND_CUSTOM)
+ file(soundfile, 'w').write(data)
+
+class JournalSound(Sound):
+ def __init__(self, jobject):
+ soundfile = os.path.join(theme.SESSION_PATH, jobject.object_id)
+ Sound.__init__(self, jobject.metadata['title'],
+ jobject.object_id, soundfile, theme.SOUND_CUSTOM)
+ shutil.copy(jobject.file_path, soundfile)
+ THEMES.append(self)
+
+THEMES = [
+ PreinstalledSound(_('Gobble'), 'sounds/gobble.wav'),
+ PreinstalledSound(_('Funk'), 'sounds/funk.wav'),
+ PreinstalledSound(_('Giggle'), 'sounds/giggle.wav'),
+ PreinstalledSound(_('Jungle'), 'sounds/jungle.wav'),
+ MuteSound(_('Mute')),
+ None,
+ CustomSound(_('Custom')) ]
+
+Sound.current = THEMES[0]
+
+def play():
+ Sound.playing = True
+ Sound.current.select()
+
+def stop():
+ Sound.playing = False
+ Sound.player.set_state(gst.STATE_NULL)
+
+# GSTREAMER STUFF
+
+def _gstmessage_cb(bus, message):
+ type = message.type
+
+ if type == gst.MESSAGE_EOS:
+ # END OF SOUND FILE
+ Sound.player.set_state(gst.STATE_NULL)
+ Sound.player.set_state(gst.STATE_PLAYING)
+ elif type == gst.MESSAGE_ERROR:
+ Sound.player.set_state(gst.STATE_NULL)
+
+Sound.player = gst.element_factory_make("playbin", "player")
+fakesink = gst.element_factory_make('fakesink', "my-fakesink")
+Sound.player.set_property("video-sink", fakesink)
+
+bus = Sound.player.get_bus()
+bus.add_signal_watch()
+bus.connect('message', _gstmessage_cb)
diff --git a/theme.py b/theme.py
new file mode 100644
index 0000000..6021cb6
--- /dev/null
+++ b/theme.py
@@ -0,0 +1,148 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import os
+import gtk
+import shutil
+from math import ceil
+
+from sugar.activity.activity import get_bundle_path, get_activity_root
+from sugar.graphics import style
+
+SOUND_SPEAKER = 'images/sounds/speaker.png'
+SOUND_MUTE = 'images/sounds/mute.png'
+SOUND_CUSTOM = 'images/sounds/custom.png'
+
+LOGO_WIDTH = 275
+TAPE_COUNT = 11
+FRAME_COUNT = 14
+
+DESKTOP_WIDTH = gtk.gdk.screen_width()
+DESKTOP_HEIGHT = gtk.gdk.screen_height() - style.LARGE_ICON_SIZE
+
+THUMB_SIZE = min(100, DESKTOP_WIDTH / (TAPE_COUNT+1))
+
+FRAME_COLS = max(1, ((DESKTOP_WIDTH-LOGO_WIDTH) -
+ min(DESKTOP_HEIGHT-THUMB_SIZE-THUMB_SIZE/2, DESKTOP_WIDTH-LOGO_WIDTH))
+ / THUMB_SIZE)
+
+FRAME_ROWS = max((DESKTOP_HEIGHT - THUMB_SIZE*3) / THUMB_SIZE,
+ int(ceil(float(FRAME_COUNT) / FRAME_COLS)))
+
+BORDER_WIDTH = 10
+
+# Colors from the Rich's UI design
+
+GRAY = "#B7B7B7" # gray
+PINK = "#FF0099" # pink
+YELLOW = "#FFFF00" # yellow
+WHITE = "#FFFFFF"
+BLACK = "#000000"
+BACKGROUND = "#66CC00" # light green
+BUTTON_FOREGROUND = "#CCFB99" # very light green
+BUTTON_BACKGROUND = "#027F01" # dark green
+COLOR_FG_BUTTONS = (
+ (gtk.STATE_NORMAL,"#CCFF99"),
+ (gtk.STATE_ACTIVE,"#CCFF99"),
+ (gtk.STATE_PRELIGHT,"#CCFF99"),
+ (gtk.STATE_SELECTED,"#CCFF99"),
+ (gtk.STATE_INSENSITIVE,"#CCFF99"),
+ ) # very light green
+COLOR_BG_BUTTONS = (
+ (gtk.STATE_NORMAL,"#027F01"),
+ (gtk.STATE_ACTIVE,"#CCFF99"),
+ (gtk.STATE_PRELIGHT,"#016D01"),
+ (gtk.STATE_SELECTED,"#CCFF99"),
+ (gtk.STATE_INSENSITIVE,"#027F01"),
+ )
+OLD_COLOR_BG_BUTTONS = (
+ (gtk.STATE_NORMAL,"#027F01"),
+ (gtk.STATE_ACTIVE,"#014D01"),
+ (gtk.STATE_PRELIGHT,"#016D01"),
+ (gtk.STATE_SELECTED,"#027F01"),
+ (gtk.STATE_INSENSITIVE,"#027F01"),
+ )
+
+SESSION_PATH = os.path.join(get_activity_root(), 'tmp', '.session')
+if os.path.isdir(SESSION_PATH):
+ shutil.rmtree(SESSION_PATH)
+os.mkdir(SESSION_PATH)
+
+def path(*args):
+ file = os.path.join(*args)
+
+ if os.path.isabs(file):
+ return file
+ else:
+ return os.path.join(get_bundle_path(), file)
+
+def pixbuf(file, size = None):
+ if size:
+ out = gtk.gdk.pixbuf_new_from_file_at_size(path(file), size, size)
+ else:
+ out = gtk.gdk.pixbuf_new_from_file(path(file))
+ return out
+
+def scale(pixbuf, size = THUMB_SIZE):
+ return pixbuf.scale_simple(size, size, gtk.gdk.INTERP_BILINEAR)
+
+EMPTY_FILENAME = 'images/pics/empty.png'
+EMPTY_ORIG = pixbuf(EMPTY_FILENAME)
+EMPTY_THUMB = scale(EMPTY_ORIG)
+
+CUSTOM_FRAME_ORIG = pixbuf('images/pics/custom.png')
+CUSTOM_FRAME_THUMB = scale(CUSTOM_FRAME_ORIG)
+
+def choose(out_fun, default=None):
+ from sugar.graphics.objectchooser import ObjectChooser
+
+ chooser = ObjectChooser()
+ jobject = None
+
+ try:
+ result = chooser.run()
+
+ if result == gtk.RESPONSE_ACCEPT:
+ jobject = chooser.get_selected_object()
+ if jobject and jobject.file_path:
+ return out_fun(jobject)
+ finally:
+ if jobject: jobject.destroy()
+ chooser.destroy()
+ del chooser
+
+ return default
+
+def pixbuf2str(pixbuf):
+ def push(data, buffer):
+ buffer.write(data)
+
+ import cStringIO
+ buffer = cStringIO.StringIO()
+ pixbuf.save_to_callback(push, 'png', user_data=buffer)
+ return buffer.getvalue()
+
+def str2pixbuf(data):
+ tmpfile = os.path.join(SESSION_PATH, '.tmp.png')
+ file(tmpfile, 'w').write(data)
+ out = pixbuf(tmpfile)
+ os.unlink(tmpfile)
+ return out
+
+# customize theme
+gtkrc = os.path.join(get_bundle_path(), 'gtkrc')
+gtk.rc_add_default_file(gtkrc)
+settings = gtk.settings_get_default()
+gtk.rc_reset_styles(settings)
+gtk.rc_reparse_all_for_settings(settings, True)
diff --git a/utils.py b/utils.py
new file mode 100644
index 0000000..bd219f0
--- /dev/null
+++ b/utils.py
@@ -0,0 +1,296 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import os
+import gtk
+import pango
+
+import sugar
+from sugar.graphics import style
+from sugar.graphics.icon import Icon
+
+from theme import *
+
+class ComboBox(sugar.graphics.combobox.ComboBox):
+ def __init__(self):
+ sugar.graphics.combobox.ComboBox.__init__(self)
+ self.set_name('we-really-need-it-to-use-custom-combobox-colors')
+
+ def append_item(self, action_id, text = None, icon_name = None, size = None,
+ pixbuf = None, position = None):
+
+ if not self._icon_renderer and (icon_name or pixbuf):
+ self._icon_renderer = gtk.CellRendererPixbuf()
+
+ settings = self.get_settings()
+ w, h = gtk.icon_size_lookup_for_settings(settings, gtk.ICON_SIZE_MENU)
+ self._icon_renderer.props.stock_size = w
+
+ self._icon_renderer.props.xpad = 4
+ self._icon_renderer.props.ypad = 4
+
+ self.pack_start(self._icon_renderer, False)
+ self.add_attribute(self._icon_renderer, 'pixbuf', 2)
+
+ if not self._text_renderer and text:
+ self._text_renderer = gtk.CellRendererText()
+ self._text_renderer.props.ellipsize = pango.ELLIPSIZE_END
+ self.pack_end(self._text_renderer, True)
+ self.add_attribute(self._text_renderer, 'text', 1)
+
+ if not pixbuf:
+ if icon_name:
+ if not size:
+ size = gtk.ICON_SIZE_LARGE_TOOLBAR
+ width, height = gtk.icon_size_lookup(size)
+ else:
+ width, height = size
+ pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(icon_name,
+ width, height)
+ else:
+ pixbuf = None
+
+ if position:
+ self._model.insert(position, [action_id, text, pixbuf, False])
+ else:
+ self._model.append([action_id, text, pixbuf, False])
+
+def map_range(value, ilower, iupper, olower, oupper):
+ if value == iupper:
+ return oupper
+ return olower + int((oupper-olower+1) * (value-ilower) /
+ float(iupper-ilower))
+
+class TempoSlider(gtk.HBox):
+ def __init__(self, min_value, max_value):
+ gtk.HBox.__init__(self)
+
+ self._pixbuf = [None] * 8
+ self._image = gtk.Image()
+ self._image.show()
+
+ # used to store tempo updates while the slider is active
+ self._delayed = 0
+ self._active = False
+
+ self.adjustment = gtk.Adjustment(min_value, min_value, max_value,
+ (max_value - min_value) / 8, (max_value - min_value) / 8, 0)
+ self._adjustment_h = self.adjustment.connect('value-changed',
+ self._changed_cb)
+
+ slider = gtk.HScale(adjustment = self.adjustment)
+ slider.show()
+ slider.set_draw_value(False)
+ slider.connect("button-press-event", self._press_cb)
+ slider.connect("button-release-event", self._release_cb)
+
+ self.pack_start(slider, True, True)
+ self.pack_end(self._image, False, False)
+
+ def set_value(self, tempo, quiet = False):
+ if self._active:
+ self._delayed = tempo
+ elif quiet:
+ self.adjustment.handler_block(self._adjustment_h)
+ self.adjustment.set_value(tempo)
+ self._update(tempo)
+ self.adjustment.handler_unblock(self._adjustment_h)
+ else:
+ self.adjustment.set_value(tempo)
+
+ def _changed_cb(self, widget):
+ self._update(widget.get_value())
+
+ def _update(self, tempo):
+ img = map_range(tempo, self.adjustment.lower,
+ self.adjustment.upper, 0, 7)
+
+ if not self._pixbuf[img]:
+ self._pixbuf[img] = gtk.gdk.pixbuf_new_from_file_at_size(
+ os.path.join(get_bundle_path(), 'icons/tempo' +
+ str(img+1) + '.svg'),
+ style.STANDARD_ICON_SIZE, style.STANDARD_ICON_SIZE)
+
+ self._image.set_from_pixbuf(self._pixbuf[img])
+
+ def _press_cb(self, widget, event):
+ self._active = True
+
+ def _release_cb(self, widget, event):
+ self._active = False
+ if self._delayed != 0:
+ self.set_value(self._delayed, True)
+ self._delayed = 0
+
+class ScrollButton(gtk.ToolButton):
+ def __init__(self, icon_name):
+ gtk.ToolButton.__init__(self)
+
+ icon = Icon(icon_name = icon_name,
+ icon_size=gtk.ICON_SIZE_SMALL_TOOLBAR)
+ # The alignment is a hack to work around gtk.ToolButton code
+ # that sets the icon_size when the icon_widget is a gtk.Image
+ alignment = gtk.Alignment(0.5, 0.5)
+ alignment.add(icon)
+ self.set_icon_widget(alignment)
+
+class ScrolledBox(gtk.EventBox):
+ def __init__(self, orientation, arrows_policy = gtk.POLICY_AUTOMATIC):
+ gtk.EventBox.__init__(self)
+ self.orientation = orientation
+ self._viewport = None
+ self._abox = None
+ self._aviewport = None
+ self._aviewport_sig = None
+ self._arrows_policy = arrows_policy
+ self._left = None
+ self._right = None
+
+ if orientation == gtk.ORIENTATION_HORIZONTAL:
+ box = gtk.HBox()
+ else:
+ box = gtk.VBox()
+ if self._arrows_policy == gtk.POLICY_AUTOMATIC:
+ box.connect("size-allocate", self._box_allocate_cb)
+ self.add(box)
+
+ if self._arrows_policy != gtk.POLICY_NEVER:
+ if orientation == gtk.ORIENTATION_HORIZONTAL:
+ self._left = ScrollButton('go-left')
+ else:
+ self._left = ScrollButton('go-up')
+ self._left.connect('clicked', self._scroll_cb, 'left')
+ box.pack_start(self._left, False, False, 0)
+
+ self._scrolled = gtk.ScrolledWindow()
+ if orientation == gtk.ORIENTATION_HORIZONTAL:
+ self._scrolled.set_policy(arrows_policy, gtk.POLICY_NEVER)
+ else:
+ self._scrolled.set_policy(gtk.POLICY_NEVER, arrows_policy)
+ self._scrolled.connect('scroll-event', self._scroll_event_cb)
+ box.pack_start(self._scrolled, True, True, 0)
+
+ if orientation == gtk.ORIENTATION_HORIZONTAL:
+ self._adj = self._scrolled.get_hadjustment()
+ else:
+ self._adj = self._scrolled.get_vadjustment()
+ self._adj.connect('changed', self._scroll_changed_cb)
+ self._adj.connect('value-changed', self._scroll_changed_cb)
+
+ if self._arrows_policy != gtk.POLICY_NEVER:
+ if orientation == gtk.ORIENTATION_HORIZONTAL:
+ self._right = ScrollButton('go-right')
+ else:
+ self._right = ScrollButton('go-down')
+ self._right.connect('clicked', self._scroll_cb, 'right')
+ box.pack_start(self._right, False, False, 0)
+
+ def modify_fg(self, state, bg):
+ gtk.EventBox.modify_fg(self, state, bg)
+ self._viewport.get_parent().modify_fg(state, bg)
+
+ def modify_bg(self, state, bg):
+ gtk.EventBox.modify_bg(self, state, bg)
+ self._viewport.get_parent().modify_bg(state, bg)
+
+ def set_viewport(self, widget):
+ if widget == self._viewport: return
+ if self._viewport and self._aviewport_sig:
+ self._viewport.disconnect(self._aviewport_sig)
+ self._viewport = widget
+
+ if self._arrows_policy == gtk.POLICY_AUTOMATIC:
+ self._aviewport_sig = self._viewport.connect('size-allocate',
+ self._viewport_allocate_cb)
+
+ self._scrolled.add_with_viewport(widget)
+
+ def get_viewport_allocation(self):
+ alloc = self._scrolled.get_allocation()
+ alloc.x -= self._adj.get_value()
+ return alloc
+
+ def get_adjustment(self):
+ return self._adj
+
+ def _box_allocate_cb(self, w, a):
+ self._abox = a
+ self._update_arrows()
+
+ def _viewport_allocate_cb(self, w, a):
+ self._aviewport = a
+ self._update_arrows()
+
+ def _update_arrows(self):
+ if not self._abox or not self._aviewport: return
+
+ if self.orientation == gtk.ORIENTATION_HORIZONTAL:
+ show_flag = self._abox.width < self._aviewport.width
+ else:
+ show_flag = self._abox.height < self._aviewport.height
+
+ if show_flag:
+ self._left.show()
+ self._right.show()
+ else:
+ self._left.hide()
+ self._right.hide()
+
+ def _scroll_event_cb(self, widget, event):
+ if self.orientation == gtk.ORIENTATION_HORIZONTAL:
+ if event.direction == gtk.gdk.SCROLL_UP:
+ event.direction = gtk.gdk.SCROLL_LEFT
+ if event.direction == gtk.gdk.SCROLL_DOWN:
+ event.direction = gtk.gdk.SCROLL_RIGHT
+ else:
+ if event.direction == gtk.gdk.SCROLL_LEFT:
+ event.direction = gtk.gdk.SCROLL_UP
+ if event.direction == gtk.gdk.SCROLL_RIGHT:
+ event.direction = gtk.gdk.SCROLL_DOWN
+ return False
+
+ def _scroll_cb(self, widget, data):
+ if data == 'left':
+ val = max(self._adj.get_property('lower'), self._adj.get_value()
+ - self._adj.get_property('page_increment'))
+ else:
+ val = min(self._adj.get_property('upper')
+ - self._adj.get_property('page_size'),
+ self._adj.get_value()
+ + self._adj.get_property('page_increment'))
+
+ self._adj.set_value(val)
+
+ def _scroll_changed_cb(self, widget):
+ val = self._adj.get_value()
+ if self._left:
+ if val == 0:
+ self._left.set_sensitive(False)
+ else:
+ self._left.set_sensitive(True)
+
+ if self._right:
+ if val >= self._adj.get_property('upper') - \
+ self._adj.get_property('page_size'):
+ self._right.set_sensitive(False)
+ else:
+ self._right.set_sensitive(True)
+
+class HScrolledBox(ScrolledBox):
+ def __init__(self, *args):
+ ScrolledBox.__init__(self, gtk.ORIENTATION_HORIZONTAL, *args)
+
+class VScrolledBox(ScrolledBox):
+ def __init__(self, *args):
+ ScrolledBox.__init__(self, gtk.ORIENTATION_VERTICAL, *args)