From 3d3c113d02936f990e314c53be20d1af76c43a7c Mon Sep 17 00:00:00 2001 From: Aleksey Lim Date: Mon, 09 Feb 2009 11:54:18 +0000 Subject: Merge screen_resolution_independent branch - total refactoring of code; - support various screen resolutions; - use jobjects for characters, backgrounds and sounds; - add collaboration code --- diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..0261dae --- /dev/null +++ b/AUTHORS @@ -0,0 +1,2 @@ +Ed Stoner +Aleksey Lim 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. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 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. + + , 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 @@ - - - - -]> - - - - - - - - - - - - - - - - - - - - - - - - - - - \ 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 @@ + + + + + + + + + + + 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 @@ + + + + + + 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 @@ + + + + + + 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 @@ + + + + + + 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 @@ + + + + + + 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 @@ + + + + + + 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 @@ + + + + + + + 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 @@ + + + + + + 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 @@ + + + + + + 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 , 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 \n" +"Language-Team: LANGUAGE \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) -- cgit v0.9.1