diff options
author | Juan Ignacio Rodríguez <ignacio@Ignacio-Magallanes.(none)> | 2012-10-12 22:12:17 (GMT) |
---|---|---|
committer | Juan Ignacio Rodríguez <ignacio@Ignacio-Magallanes.(none)> | 2012-10-12 22:12:17 (GMT) |
commit | 429d3d501697d97869ac1ab38c90ed1d9d58c1ab (patch) | |
tree | 5aa147d9867475d0550cdb371753ec1dd481a806 | |
parent | 5263c449bbfc2e165f3c3d403510d352d2aa8326 (diff) |
Port to GTK3!
49 files changed, 3126 insertions, 893 deletions
@@ -1,3 +1,7 @@ +6 +ENHANCEMENTS: +* Ignacio Rodriguez port to GTK3 + 5 ENHANCEMENTS: * New translations diff --git a/StoryActivity.py b/StoryActivity.py index ef5e37d..50c217d 100644 --- a/StoryActivity.py +++ b/StoryActivity.py @@ -10,27 +10,28 @@ # Foundation, 51 Franklin Street, Suite 500 Boston, MA 02110-1335 USA -import gtk -import gobject +from gi.repository import Gdk +from gi.repository import Gtk +from gi.repository import GObject import subprocess import cairo import os import time -from sugar.activity import activity -from sugar import profile -from sugar.datastore import datastore -try: - from sugar.graphics.toolbarbox import ToolbarBox +from sugar3.activity import activity +from sugar3 import profile +from sugar3.datastore import datastore +try: # Intente , siempre da TRUE, En Sugar 3 ya esta.. + from sugar3.graphics.toolbarbox import ToolbarBox _have_toolbox = True -except ImportError: +except ImportError: # Si hay error _have_toolbox = False if _have_toolbox: - from sugar.activity.widgets import ActivityToolbarButton - from sugar.activity.widgets import StopButton + from sugar3.activity.widgets import ActivityToolbarButton + from sugar3.activity.widgets import StopButton -from sugar.graphics.alert import Alert +from sugar3.graphics.alert import Alert from toolbar_utils import button_factory, label_factory, separator_factory from utils import json_load, json_dump, play_audio_from_file @@ -40,8 +41,8 @@ import telepathy import dbus from dbus.service import signal from dbus.gobject_service import ExportedGObject -from sugar.presence import presenceservice -from sugar.presence.tubeconn import TubeConnection +from sugar3.presence import presenceservice +from sugar3.presence.tubeconn import TubeConnection from gettext import gettext as _ @@ -81,9 +82,9 @@ class StoryActivity(activity.Activity): self._setup_dispatch_table() # Create a canvas - canvas = gtk.DrawingArea() - canvas.set_size_request(gtk.gdk.screen_width(), \ - gtk.gdk.screen_height()) + canvas = Gtk.DrawingArea() + canvas.set_size_request(Gdk.Screen.width(), \ + Gdk.Screen.height()) self.set_canvas(canvas) canvas.show() self.show_all() @@ -117,7 +118,7 @@ class StoryActivity(activity.Activity): else: # Use pre-0.86 toolbar design - games_toolbar = gtk.Toolbar() + games_toolbar = Gtk.Toolbar() toolbox = activity.ActivityToolbox(self) self.set_toolbox(toolbox) toolbox.add_toolbar(_('Game'), games_toolbar) diff --git a/StoryActivity.pyo b/StoryActivity.pyo Binary files differnew file mode 100644 index 0000000..9d8fa25 --- /dev/null +++ b/StoryActivity.pyo diff --git a/StoryActivity.py~ b/StoryActivity.py~ new file mode 100644 index 0000000..8c94029 --- /dev/null +++ b/StoryActivity.py~ @@ -0,0 +1,405 @@ +#Copyright (c) 2012 Walter Bender + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# You should have received a copy of the GNU General Public License +# along with this library; if not, write to the Free Software +# Foundation, 51 Franklin Street, Suite 500 Boston, MA 02110-1335 USA + + +from gi.repository import Gdk +from gi.repository import Gtk +from gi.repository import GObject +import subprocess +import cairo +import os +import time + +from sugar3.activity import activity +from sugar3 import profile +from sugar3.datastore import datastore +try: # Intente , siempre da TRUE, En Sugar 3 ya esta.. + from sugar3.graphics.toolbarbox import ToolbarBox + _have_toolbox = True +except ImportError: # Si hay error + _have_toolbox = False + +if _have_toolbox: + from sugar3.activity.widgets import ActivityToolbarButton + from sugar3.activity.widgets import StopButton + +from sugar3.graphics.alert import Alert + +from toolbar_utils import button_factory, label_factory, separator_factory +from utils import json_load, json_dump, play_audio_from_file +from grecord import Grecord + +import telepathy +import dbus +from dbus.service import signal +from dbus.gobject_service import ExportedGObject +from sugar3.presence import presenceservice +from sugar3.presence.tubeconn import TubeConnection + +from gettext import gettext as _ + +from game import Game + +import logging +_logger = logging.getLogger('story-activity') + + +SERVICE = 'org.sugarlabs.StoryActivity' +IFACE = SERVICE + + +class StoryActivity(activity.Activity): + """ Storytelling game """ + + def __init__(self, handle): + """ Initialize the toolbars and the game board """ + try: + super(StoryActivity, self).__init__(handle) + except dbus.exceptions.DBusException, e: + _logger.error(str(e)) + + self.path = activity.get_bundle_path() + + self.nick = profile.get_nick_name() + if profile.get_color() is not None: + self.colors = profile.get_color().to_string().split(',') + else: + self.colors = ['#A0FFA0', '#FF8080'] + + self._recording = False + self._grecord = None + self._alert = None + + self._setup_toolbars(_have_toolbox) + self._setup_dispatch_table() + + # Create a canvas + canvas = Gtk.DrawingArea() + canvas.set_size_request(Gdk.screen_width(), \ + Gdk.screen_height()) + self.set_canvas(canvas) + canvas.show() + self.show_all() + + self._game = Game(canvas, parent=self, path=self.path, + colors=self.colors) + self._setup_presence_service() + + if 'dotlist' in self.metadata: + self._restore() + else: + self._game.new_game() + + def _setup_toolbars(self, have_toolbox): + """ Setup the toolbars. """ + + self.max_participants = 9 + + if have_toolbox: + toolbox = ToolbarBox() + + # Activity toolbar + activity_button = ActivityToolbarButton(self) + + toolbox.toolbar.insert(activity_button, 0) + activity_button.show() + + self.set_toolbar_box(toolbox) + toolbox.show() + self.toolbar = toolbox.toolbar + + else: + # Use pre-0.86 toolbar design + games_toolbar = Gtk.Toolbar() + toolbox = activity.ActivityToolbox(self) + self.set_toolbox(toolbox) + toolbox.add_toolbar(_('Game'), games_toolbar) + toolbox.show() + toolbox.set_current_toolbar(1) + self.toolbar = games_toolbar + + self._new_game_button_h = button_factory( + 'view-refresh', self.toolbar, self._new_game_cb, + tooltip=_('Load new images.')) + + separator_factory(self.toolbar) + + self.save_as_image = button_factory( + 'image-saveoff', self.toolbar, self._do_save_as_image_cb, + tooltip=_('Save as image')) + + separator_factory(self.toolbar) + + self._record_button = button_factory( + 'media-record', self.toolbar, + self._record_cb, tooltip=_('Start recording')) + + self._playback_button = button_factory( + 'media-playback-start-insensitive', self.toolbar, + self._playback_recording_cb, tooltip=_('Nothing to play')) + + if _have_toolbox: + separator_factory(toolbox.toolbar, True, False) + + if _have_toolbox: + stop_button = StopButton(self) + stop_button.props.accelerator = '<Ctrl>q' + toolbox.toolbar.insert(stop_button, -1) + stop_button.show() + + def _new_game_cb(self, button=None): + ''' Start a new game. ''' + self._game.new_game() + + def write_file(self, file_path): + """ Write the grid status to the Journal """ + dot_list = self._game.save_game() + self.metadata['dotlist'] = '' + for dot in dot_list: + self.metadata['dotlist'] += str(dot) + if dot_list.index(dot) < len(dot_list) - 1: + self.metadata['dotlist'] += ' ' + + def _restore(self): + """ Restore the game state from metadata """ + dot_list = [] + dots = self.metadata['dotlist'].split() + for dot in dots: + dot_list.append(int(dot)) + self._game.restore_game(dot_list) + + def _do_save_as_image_cb(self, button=None): + """ Grab the current canvas and save it to the Journal. """ + self._notify_successful_save(title=_('Save as image')) + file_path = os.path.join(activity.get_activity_root(), + 'instance', 'story.png') + png_surface = self._game.export() + png_surface.write_to_png(file_path) + + dsobject = datastore.create() + dsobject.metadata['title'] = "%s %s" % \ + (self.metadata['title'], _("image")) + dsobject.metadata['icon-color'] = profile.get_color().to_string() + dsobject.metadata['mime_type'] = 'image/png' + dsobject.set_file_path(file_path) + datastore.write(dsobject) + dsobject.destroy() + os.remove(file_path) + if self._alert is not None: + self.remove_alert(self._alert) + self._alert = None + + def _record_cb(self, button=None): + ''' Start/stop audio recording ''' + if self._grecord is None: + _logger.debug('setting up grecord') + self._grecord = Grecord(self) + if self._recording: # Was recording, so stop (and save?) + _logger.debug('recording...True. Preparing to save.') + self._grecord.stop_recording_audio() + self._recording = False + self._record_button.set_icon('media-record') + self._record_button.set_tooltip(_('Start recording')) + self._playback_button.set_icon('media-playback-start') + self._playback_button.set_tooltip(_('Play recording')) + self._notify_successful_save(title=_('Save recording')) + gobject.timeout_add(100, self._wait_for_transcoding_to_finish) + else: # Wasn't recording, so start + _logger.debug('recording...False. Start recording.') + self._grecord.record_audio() + self._recording = True + self._record_button.set_icon('media-recording') + self._record_button.set_tooltip(_('Stop recording')) + + def _wait_for_transcoding_to_finish(self, button=None): + while not self._grecord.transcoding_complete(): + time.sleep(1) + if self._alert is not None: + self.remove_alert(self._alert) + self._alert = None + self._save_recording() + + def _playback_recording_cb(self, button=None): + ''' Play back current recording ''' + _logger.debug('Playback current recording from output.ogg...') + play_audio_from_file(os.path.join(activity.get_activity_root(), + 'instance', 'output.ogg')) + return + + def _save_recording(self): + if os.path.exists(os.path.join(activity.get_activity_root(), + 'instance', 'output.ogg')): + _logger.debug('Saving recording to Journal...') + dsobject = datastore.create() + dsobject.metadata['title'] = _('audio note for %s') % \ + (self.metadata['title']) + dsobject.metadata['icon-color'] = profile.get_color().to_string() + dsobject.metadata['mime_type'] = 'audio/ogg' + _logger.debug('setting file path to %s' % ( + os.path.join(activity.get_activity_root(), + 'instance', 'output.ogg'))) + dsobject.set_file_path(os.path.join(activity.get_activity_root(), + 'instance', 'output.ogg')) + datastore.write(dsobject) + dsobject.destroy() + # Always save an image with the recording. + self._do_save_as_image_cb() + else: + _logger.debug('Nothing to save...') + return + + def _notify_successful_save(self, title='', msg=''): + ''' Notify user when saves are completed ''' + self._alert = Alert() + self._alert.props.title = title + self._alert.props.msg = msg + self.add_alert(self._alert) + self._alert.show() + + # Collaboration-related methods + + def _setup_presence_service(self): + """ Setup the Presence Service. """ + self.pservice = presenceservice.get_instance() + self.initiating = None # sharing (True) or joining (False) + + owner = self.pservice.get_owner() + self.owner = owner + self._share = "" + self.connect('shared', self._shared_cb) + self.connect('joined', self._joined_cb) + + def _shared_cb(self, activity): + """ Either set up initial share...""" + self._new_tube_common(True) + + def _joined_cb(self, activity): + """ ...or join an exisiting share. """ + self._new_tube_common(False) + + def _new_tube_common(self, sharer): + """ Joining and sharing are mostly the same... """ + if self._shared_activity is None: + _logger.debug("Error: Failed to share or join activity ... \ + _shared_activity is null in _shared_cb()") + return + + self.initiating = sharer + self.waiting_for_hand = not sharer + + 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) + + if sharer: + _logger.debug('This is my activity: making a tube...') + id = self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].OfferDBusTube( + SERVICE, {}) + else: + _logger.debug('I am joining an 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) + self._game.set_sharing(True) + + def _list_tubes_reply_cb(self, tubes): + """ Reply to a list request. """ + for tube_info in tubes: + self._new_tube_cb(*tube_info) + + def _list_tubes_error_cb(self, e): + """ Log errors. """ + _logger.debug('Error: ListTubes() failed: %s' % (e)) + + def _new_tube_cb(self, id, initiator, type, service, params, state): + """ Create a new tube. """ + _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 == 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.chattube = ChatTube(tube_conn, self.initiating, \ + self.event_received_cb) + + def _setup_dispatch_table(self): + ''' Associate tokens with commands. ''' + self._processing_methods = { + 'n': [self._receive_new_game, 'get a new game grid'], + 'p': [self._receive_dot_click, 'get a dot click'], + } + + def event_received_cb(self, event_message): + ''' Data from a tube has arrived. ''' + if len(event_message) == 0: + return + try: + command, payload = event_message.split('|', 2) + except ValueError: + _logger.debug('Could not split event message %s' % (event_message)) + return + self._processing_methods[command][0](payload) + + def send_new_game(self): + ''' Send a new grid to all players ''' + self.send_event('n|%s' % (json_dump(self._game.save_game()))) + + def _receive_new_game(self, payload): + ''' Sharer can start a new game. ''' + dot_list = json_load(payload) + self._game.restore_game(dot_list) + + def send_dot_click(self, dot, color): + ''' Send a dot click to all the players ''' + self.send_event('p|%s' % (json_dump([dot, color]))) + + def _receive_dot_click(self, payload): + ''' When a dot is clicked, everyone should change its color. ''' + (dot, color) = json_load(payload) + self._game.remote_button_press(dot, color) + + def send_event(self, entry): + """ Send event through the tube. """ + if hasattr(self, 'chattube') and self.chattube is not None: + self.chattube.SendText(entry) + + +class ChatTube(ExportedGObject): + """ Class for setting up tube for sharing """ + + def __init__(self, tube, is_initiator, stack_received_cb): + super(ChatTube, self).__init__(tube, PATH) + self.tube = tube + self.is_initiator = is_initiator # Are we sharing or joining activity? + self.stack_received_cb = stack_received_cb + self.stack = '' + + self.tube.add_signal_receiver(self.send_stack_cb, 'SendText', IFACE, + path=PATH, sender_keyword='sender') + + def send_stack_cb(self, text, sender=None): + if sender == self.tube.get_unique_name(): + return + self.stack = text + self.stack_received_cb(text) + + @signal(dbus_interface=IFACE, signature='s') + def SendText(self, text): + self.stack = text @@ -1,5 +1,7 @@ # -*- coding: utf-8 -*- #Copyright (c) 2012 Walter Bender +# Port to GTK3: +# Ignacio Rodriguez <ignaciorodriguez@sugarlabs.org> # 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 @@ -10,9 +12,7 @@ # along with this library; if not, write to the Free Software # Foundation, 51 Franklin Street, Suite 500 Boston, MA 02110-1335 USA - -import gtk -import gobject +from gi.repository import Gdk,GdkPixbuf,Gtk,GObject import cairo import os import glob @@ -24,7 +24,7 @@ import logging _logger = logging.getLogger('search-activity') try: - from sugar.graphics import style + from sugar3.graphics import style GRID_CELL_SIZE = style.GRID_CELL_SIZE except ImportError: GRID_CELL_SIZE = 0 @@ -49,11 +49,11 @@ class Game(): self._colors.append(colors[0]) self._colors.append(colors[1]) - self._canvas.set_flags(gtk.CAN_FOCUS) - self._canvas.connect("expose-event", self._expose_cb) + # self._canvas.set_flags(Gtk.CAN_FOCUS) + # self._canvas.connect("expose-event", self._expose_cb) - self._width = gtk.gdk.screen_width() - self._height = gtk.gdk.screen_height() - (GRID_CELL_SIZE * 1.5) + self._width = Gdk.Screen.width() + self._height = Gdk.Screen.height() - (GRID_CELL_SIZE * 1.5) self._scale = self._height / (3 * DOT_SIZE * 1.2) self._scale /= 1.5 self._dot_size = int(DOT_SIZE * self._scale) @@ -165,7 +165,7 @@ class Game(): self._sprites.redraw_sprites(cr=cr) def _destroy_cb(self, win, event): - gtk.main_quit() + Gtk.main_quit() def export(self): ''' Write dot to cairo surface. ''' @@ -179,7 +179,7 @@ class Game(): y = self._space + int(i / 3.) * (self._dot_size + self._space) x = self._space + (i % 3) * (self._dot_size + self._space) cr.save() - cr = gtk.gdk.CairoContext(cr) + cr = Gdk.CairoContext(cr) cr.set_source_surface(self._dots[i].cached_surfaces[0], x, y) cr.rectangle(x, y, self._dot_size, self._dot_size) cr.fill() @@ -199,7 +199,7 @@ class Game(): pixbuf = svg_str_to_pixbuf(svg_string, w=self._dot_size, h = self._dot_size) ''' - pixbuf = gtk.gdk.pixbuf_new_from_file_at_size( + pixbuf = Gdk.pixbuf_new_from_file_at_size( os.path.join(self._path, self._PATHS[image]), self._dot_size, self._dot_size) ''' @@ -220,7 +220,7 @@ class Game(): surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, self._svg_width, self._svg_height) context = cairo.Context(surface) - context = gtk.gdk.CairoContext(context) + context = Gdk.CairoContext(context) context.set_source_pixbuf(pixbuf, 0, 0) context.rectangle(0, 0, self._svg_width, self._svg_height) context.fill() @@ -257,7 +257,8 @@ class Game(): def svg_str_to_pixbuf(svg_string, w=None, h=None): ''' Load pixbuf from SVG string ''' - pl = gtk.gdk.PixbufLoader('svg') + # Admito que fue la parte mas dificil.. + pl = GdkPixbuf.PixbufLoader.new_with_type('svg') if w is not None: pl.set_size(w, h) pl.write(svg_string) diff --git a/game.pyo b/game.pyo Binary files differnew file mode 100644 index 0000000..c61fe58 --- /dev/null +++ b/game.pyo diff --git a/game.py~ b/game.py~ new file mode 100644 index 0000000..c9b2467 --- /dev/null +++ b/game.py~ @@ -0,0 +1,268 @@ +# -*- coding: utf-8 -*- +#Copyright (c) 2012 Walter Bender +# Port to GTK3: +# Ignacio Rodriguez <ignaciorodriguez@sugarlabs.org> + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# You should have received a copy of the GNU General Public License +# along with this library; if not, write to the Free Software +# Foundation, 51 Franklin Street, Suite 500 Boston, MA 02110-1335 USA + +from gi.repository import Gdk,GdkPixbuf +from gi.repository import Gtk +from gi.repository import GObject +import cairo +import os +import glob +from random import uniform + +from gettext import gettext as _ + +import logging +_logger = logging.getLogger('search-activity') + +try: + from sugar3.graphics import style + GRID_CELL_SIZE = style.GRID_CELL_SIZE +except ImportError: + GRID_CELL_SIZE = 0 + +from sprites import Sprites, Sprite + + +DOT_SIZE = 40 +COLORS = ['#000000', '#a00000', '#907000', '#009000', '#0000ff', '#9000a0'] + + +class Game(): + + def __init__(self, canvas, parent=None, path=None, + colors=['#A0FFA0', '#FF8080']): + self._canvas = canvas + self._parent = parent + self._parent.show_all() + self._path = path + + self._colors = ['#FFFFFF'] + self._colors.append(colors[0]) + self._colors.append(colors[1]) + + # self._canvas.set_flags(Gtk.CAN_FOCUS) + # self._canvas.connect("expose-event", self._expose_cb) + + self._width = Gdk.Screen.width() + self._height = Gdk.Screen.height() - (GRID_CELL_SIZE * 1.5) + self._scale = self._height / (3 * DOT_SIZE * 1.2) + self._scale /= 1.5 + self._dot_size = int(DOT_SIZE * self._scale) + self._space = int(self._dot_size / 5.) + self.we_are_sharing = False + + self._start_time = 0 + self._timeout_id = None + + # Find the image files + self._PATHS = glob.glob(os.path.join(self._path, 'images', '*.svg')) + + # Generate the sprites we'll need... + self._sprites = Sprites(self._canvas) + self._dots = [] + yoffset = self._space * 2 # int(self._space / 2.) + for y in range(3): + for x in range(3): + xoffset = int((self._width - 3 * self._dot_size - \ + 2 * self._space) / 2.) + self._dots.append( + Sprite(self._sprites, + xoffset + x * (self._dot_size + self._space), + y * (self._dot_size + self._space) + yoffset, + self._new_dot_surface(color=self._colors[0]))) + self._dots[-1].type = -1 # No image + self._dots[-1].set_label_attributes(72) + + def _all_clear(self): + ''' Things to reinitialize when starting up a new game. ''' + if self._timeout_id is not None: + gobject.source_remove(self._timeout_id) + + for dot in self._dots: + if dot.type != -1: + dot.type = -1 + dot.set_shape(self._new_dot_surface( + self._colors[abs(dot.type)])) + dot.set_label('?') + self._dance_counter = 0 + self._dance_step() + + def _dance_step(self): + ''' Short animation before loading new game ''' + for dot in self._dots: + dot.set_shape(self._new_dot_surface( + self._colors[int(uniform(0, 3))])) + self._dance_counter += 1 + if self._dance_counter < 10: + self._timeout_id = gobject.timeout_add(500, self._dance_step) + else: + self._new_game() + + def new_game(self): + ''' Start a new game. ''' + self._all_clear() + + def _new_game(self): + ''' Select pictures at random ''' + for i in range(3 * 3): + self._dots[i].set_label('') + self._dots[i].type = int(uniform(0, len(self._PATHS))) + _logger.debug(self._dots[i].type) + self._dots[i].set_shape(self._new_dot_surface( + image=self._dots[i].type)) + + if self.we_are_sharing: + _logger.debug('sending a new game') + self._parent.send_new_game() + + def restore_game(self, dot_list): + ''' Restore a game from the Journal or share ''' + for i, dot in enumerate(dot_list): + self._dots[i].type = dot + self._dots[i].set_shape(self._new_dot_surface( + image=self._dots[i].type)) + + def save_game(self): + ''' Return dot list for saving to Journal or + sharing ''' + dot_list = [] + for dot in self._dots: + dot_list.append(dot.type) + return dot_list + + def set_sharing(self, share=True): + _logger.debug('enabling sharing') + self.we_are_sharing = share + + def _grid_to_dot(self, pos): + ''' calculate the dot index from a column and row in the grid ''' + return pos[0] + pos[1] * 3 + + def _dot_to_grid(self, dot): + ''' calculate the grid column and row for a dot ''' + return [dot % 3, int(dot / 3)] + + def _expose_cb(self, win, event): + self.do_expose_event(event) + + def do_expose_event(self, event): + ''' Handle the expose-event by drawing ''' + # Restrict Cairo to the exposed area + cr = self._canvas.window.cairo_create() + cr.rectangle(event.area.x, event.area.y, + event.area.width, event.area.height) + cr.clip() + # Refresh sprite list + self._sprites.redraw_sprites(cr=cr) + + def _destroy_cb(self, win, event): + Gtk.main_quit() + + def export(self): + ''' Write dot to cairo surface. ''' + w = h = int(4 * self._space + 3 * self._dot_size) + png_surface = cairo.ImageSurface(cairo.FORMAT_RGB24, w, h) + cr = cairo.Context(png_surface) + cr.set_source_rgb(192, 192, 192) + cr.rectangle(0, 0, w, h) + cr.fill() + for i in range(9): + y = self._space + int(i / 3.) * (self._dot_size + self._space) + x = self._space + (i % 3) * (self._dot_size + self._space) + cr.save() + cr = Gdk.CairoContext(cr) + cr.set_source_surface(self._dots[i].cached_surfaces[0], x, y) + cr.rectangle(x, y, self._dot_size, self._dot_size) + cr.fill() + cr.restore() + return png_surface + + def _new_dot_surface(self, color='#000000', image=None): + ''' generate a dot of a color color ''' + self._dot_cache = {} + if image is not None: + color = COLORS[int(uniform(0, 6))] + fd = open(os.path.join(self._path, self._PATHS[image]), 'r') + svg_string = '' + for line in fd: + svg_string += line.replace('#000000', color) + fd.close() + pixbuf = svg_str_to_pixbuf(svg_string, w=self._dot_size, + h = self._dot_size) + ''' + pixbuf = Gdk.pixbuf_new_from_file_at_size( + os.path.join(self._path, self._PATHS[image]), + self._dot_size, self._dot_size) + ''' + else: + if color in self._dot_cache: + return self._dot_cache[color] + self._stroke = color + self._fill = color + self._svg_width = self._dot_size + self._svg_height = self._dot_size + + i = self._colors.index(color) + pixbuf = svg_str_to_pixbuf( + self._header() + \ + self._circle(self._dot_size / 2., self._dot_size / 2., + self._dot_size / 2.) + \ + self._footer()) + surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, + self._svg_width, self._svg_height) + context = cairo.Context(surface) + context = Gdk.CairoContext(context) + context.set_source_pixbuf(pixbuf, 0, 0) + context.rectangle(0, 0, self._svg_width, self._svg_height) + context.fill() + if image is None: + self._dot_cache[color] = surface + return surface + + def _header(self): + return '<svg\n' + 'xmlns:svg="http://www.w3.org/2000/svg"\n' + \ + 'xmlns="http://www.w3.org/2000/svg"\n' + \ + 'xmlns:xlink="http://www.w3.org/1999/xlink"\n' + \ + 'version="1.1"\n' + 'width="' + str(self._svg_width) + '"\n' + \ + 'height="' + str(self._svg_height) + '">\n' + + def _rect(self, w, h, x, y): + svg_string = ' <rect\n' + svg_string += ' width="%f"\n' % (w) + svg_string += ' height="%f"\n' % (h) + svg_string += ' rx="%f"\n' % (0) + svg_string += ' ry="%f"\n' % (0) + svg_string += ' x="%f"\n' % (x) + svg_string += ' y="%f"\n' % (y) + svg_string += 'style="fill:#000000;stroke:#000000;"/>\n' + return svg_string + + def _circle(self, r, cx, cy): + return '<circle style="fill:' + str(self._fill) + ';stroke:' + \ + str(self._stroke) + ';" r="' + str(r - 0.5) + '" cx="' + \ + str(cx) + '" cy="' + str(cy) + '" />\n' + + def _footer(self): + return '</svg>\n' + + +def svg_str_to_pixbuf(svg_string, w=None, h=None): + ''' Load pixbuf from SVG string ''' + # Admito que fue la parte mas dificil.. + pl = GdkPixbuf.PixbufLoader.new_with_type('svg') + if w is not None: + pl.set_size(w, h) + pl.write(svg_string) + pl.close() + return pl.get_pixbuf() @@ -22,14 +22,14 @@ import os import time -import gtk +from gi.repository import Gtk import gst import logging _logger = logging.getLogger("portfolio-activity") -import gobject -gobject.threads_init() +from gi.repository import GObject +GObject.threads_init() class Grecord: @@ -150,7 +150,7 @@ filesink name=audioFilesink' audioBus.add_signal_watch() self._audio_transcode_handler = audioBus.connect( 'message', self._onMuxedAudioMessageCb, self._audioline) - self._transcode_id = gobject.timeout_add(200, self._transcodeUpdateCb, + self._transcode_id = GObject.timeout_add(200, self._transcodeUpdateCb, self._audioline) self._audiopos = 0 self._audioline.set_state(gst.STATE_PLAYING) @@ -216,9 +216,9 @@ filesink name=audioFilesink' return False def _clean_up_transcoding_pipeline(self, pipe): - gobject.source_remove(self._audio_transcode_handler) + GObject.source_remove(self._audio_transcode_handler) self._audio_transcode_handler = None - gobject.source_remove(self._transcode_id) + GObject.source_remove(self._transcode_id) self._transcode_id = None pipe.set_state(gst.STATE_NULL) pipe.get_bus().remove_signal_watch() diff --git a/grecord.pyo b/grecord.pyo Binary files differnew file mode 100644 index 0000000..8293c3f --- /dev/null +++ b/grecord.pyo diff --git a/gtk2/StoryActivity.py b/gtk2/StoryActivity.py new file mode 100644 index 0000000..ef5e37d --- /dev/null +++ b/gtk2/StoryActivity.py @@ -0,0 +1,404 @@ +#Copyright (c) 2012 Walter Bender + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# You should have received a copy of the GNU General Public License +# along with this library; if not, write to the Free Software +# Foundation, 51 Franklin Street, Suite 500 Boston, MA 02110-1335 USA + + +import gtk +import gobject +import subprocess +import cairo +import os +import time + +from sugar.activity import activity +from sugar import profile +from sugar.datastore import datastore +try: + from sugar.graphics.toolbarbox import ToolbarBox + _have_toolbox = True +except ImportError: + _have_toolbox = False + +if _have_toolbox: + from sugar.activity.widgets import ActivityToolbarButton + from sugar.activity.widgets import StopButton + +from sugar.graphics.alert import Alert + +from toolbar_utils import button_factory, label_factory, separator_factory +from utils import json_load, json_dump, play_audio_from_file +from grecord import Grecord + +import telepathy +import dbus +from dbus.service import signal +from dbus.gobject_service import ExportedGObject +from sugar.presence import presenceservice +from sugar.presence.tubeconn import TubeConnection + +from gettext import gettext as _ + +from game import Game + +import logging +_logger = logging.getLogger('story-activity') + + +SERVICE = 'org.sugarlabs.StoryActivity' +IFACE = SERVICE + + +class StoryActivity(activity.Activity): + """ Storytelling game """ + + def __init__(self, handle): + """ Initialize the toolbars and the game board """ + try: + super(StoryActivity, self).__init__(handle) + except dbus.exceptions.DBusException, e: + _logger.error(str(e)) + + self.path = activity.get_bundle_path() + + self.nick = profile.get_nick_name() + if profile.get_color() is not None: + self.colors = profile.get_color().to_string().split(',') + else: + self.colors = ['#A0FFA0', '#FF8080'] + + self._recording = False + self._grecord = None + self._alert = None + + self._setup_toolbars(_have_toolbox) + self._setup_dispatch_table() + + # Create a canvas + canvas = gtk.DrawingArea() + canvas.set_size_request(gtk.gdk.screen_width(), \ + gtk.gdk.screen_height()) + self.set_canvas(canvas) + canvas.show() + self.show_all() + + self._game = Game(canvas, parent=self, path=self.path, + colors=self.colors) + self._setup_presence_service() + + if 'dotlist' in self.metadata: + self._restore() + else: + self._game.new_game() + + def _setup_toolbars(self, have_toolbox): + """ Setup the toolbars. """ + + self.max_participants = 9 + + if have_toolbox: + toolbox = ToolbarBox() + + # Activity toolbar + activity_button = ActivityToolbarButton(self) + + toolbox.toolbar.insert(activity_button, 0) + activity_button.show() + + self.set_toolbar_box(toolbox) + toolbox.show() + self.toolbar = toolbox.toolbar + + else: + # Use pre-0.86 toolbar design + games_toolbar = gtk.Toolbar() + toolbox = activity.ActivityToolbox(self) + self.set_toolbox(toolbox) + toolbox.add_toolbar(_('Game'), games_toolbar) + toolbox.show() + toolbox.set_current_toolbar(1) + self.toolbar = games_toolbar + + self._new_game_button_h = button_factory( + 'view-refresh', self.toolbar, self._new_game_cb, + tooltip=_('Load new images.')) + + separator_factory(self.toolbar) + + self.save_as_image = button_factory( + 'image-saveoff', self.toolbar, self._do_save_as_image_cb, + tooltip=_('Save as image')) + + separator_factory(self.toolbar) + + self._record_button = button_factory( + 'media-record', self.toolbar, + self._record_cb, tooltip=_('Start recording')) + + self._playback_button = button_factory( + 'media-playback-start-insensitive', self.toolbar, + self._playback_recording_cb, tooltip=_('Nothing to play')) + + if _have_toolbox: + separator_factory(toolbox.toolbar, True, False) + + if _have_toolbox: + stop_button = StopButton(self) + stop_button.props.accelerator = '<Ctrl>q' + toolbox.toolbar.insert(stop_button, -1) + stop_button.show() + + def _new_game_cb(self, button=None): + ''' Start a new game. ''' + self._game.new_game() + + def write_file(self, file_path): + """ Write the grid status to the Journal """ + dot_list = self._game.save_game() + self.metadata['dotlist'] = '' + for dot in dot_list: + self.metadata['dotlist'] += str(dot) + if dot_list.index(dot) < len(dot_list) - 1: + self.metadata['dotlist'] += ' ' + + def _restore(self): + """ Restore the game state from metadata """ + dot_list = [] + dots = self.metadata['dotlist'].split() + for dot in dots: + dot_list.append(int(dot)) + self._game.restore_game(dot_list) + + def _do_save_as_image_cb(self, button=None): + """ Grab the current canvas and save it to the Journal. """ + self._notify_successful_save(title=_('Save as image')) + file_path = os.path.join(activity.get_activity_root(), + 'instance', 'story.png') + png_surface = self._game.export() + png_surface.write_to_png(file_path) + + dsobject = datastore.create() + dsobject.metadata['title'] = "%s %s" % \ + (self.metadata['title'], _("image")) + dsobject.metadata['icon-color'] = profile.get_color().to_string() + dsobject.metadata['mime_type'] = 'image/png' + dsobject.set_file_path(file_path) + datastore.write(dsobject) + dsobject.destroy() + os.remove(file_path) + if self._alert is not None: + self.remove_alert(self._alert) + self._alert = None + + def _record_cb(self, button=None): + ''' Start/stop audio recording ''' + if self._grecord is None: + _logger.debug('setting up grecord') + self._grecord = Grecord(self) + if self._recording: # Was recording, so stop (and save?) + _logger.debug('recording...True. Preparing to save.') + self._grecord.stop_recording_audio() + self._recording = False + self._record_button.set_icon('media-record') + self._record_button.set_tooltip(_('Start recording')) + self._playback_button.set_icon('media-playback-start') + self._playback_button.set_tooltip(_('Play recording')) + self._notify_successful_save(title=_('Save recording')) + gobject.timeout_add(100, self._wait_for_transcoding_to_finish) + else: # Wasn't recording, so start + _logger.debug('recording...False. Start recording.') + self._grecord.record_audio() + self._recording = True + self._record_button.set_icon('media-recording') + self._record_button.set_tooltip(_('Stop recording')) + + def _wait_for_transcoding_to_finish(self, button=None): + while not self._grecord.transcoding_complete(): + time.sleep(1) + if self._alert is not None: + self.remove_alert(self._alert) + self._alert = None + self._save_recording() + + def _playback_recording_cb(self, button=None): + ''' Play back current recording ''' + _logger.debug('Playback current recording from output.ogg...') + play_audio_from_file(os.path.join(activity.get_activity_root(), + 'instance', 'output.ogg')) + return + + def _save_recording(self): + if os.path.exists(os.path.join(activity.get_activity_root(), + 'instance', 'output.ogg')): + _logger.debug('Saving recording to Journal...') + dsobject = datastore.create() + dsobject.metadata['title'] = _('audio note for %s') % \ + (self.metadata['title']) + dsobject.metadata['icon-color'] = profile.get_color().to_string() + dsobject.metadata['mime_type'] = 'audio/ogg' + _logger.debug('setting file path to %s' % ( + os.path.join(activity.get_activity_root(), + 'instance', 'output.ogg'))) + dsobject.set_file_path(os.path.join(activity.get_activity_root(), + 'instance', 'output.ogg')) + datastore.write(dsobject) + dsobject.destroy() + # Always save an image with the recording. + self._do_save_as_image_cb() + else: + _logger.debug('Nothing to save...') + return + + def _notify_successful_save(self, title='', msg=''): + ''' Notify user when saves are completed ''' + self._alert = Alert() + self._alert.props.title = title + self._alert.props.msg = msg + self.add_alert(self._alert) + self._alert.show() + + # Collaboration-related methods + + def _setup_presence_service(self): + """ Setup the Presence Service. """ + self.pservice = presenceservice.get_instance() + self.initiating = None # sharing (True) or joining (False) + + owner = self.pservice.get_owner() + self.owner = owner + self._share = "" + self.connect('shared', self._shared_cb) + self.connect('joined', self._joined_cb) + + def _shared_cb(self, activity): + """ Either set up initial share...""" + self._new_tube_common(True) + + def _joined_cb(self, activity): + """ ...or join an exisiting share. """ + self._new_tube_common(False) + + def _new_tube_common(self, sharer): + """ Joining and sharing are mostly the same... """ + if self._shared_activity is None: + _logger.debug("Error: Failed to share or join activity ... \ + _shared_activity is null in _shared_cb()") + return + + self.initiating = sharer + self.waiting_for_hand = not sharer + + 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) + + if sharer: + _logger.debug('This is my activity: making a tube...') + id = self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].OfferDBusTube( + SERVICE, {}) + else: + _logger.debug('I am joining an 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) + self._game.set_sharing(True) + + def _list_tubes_reply_cb(self, tubes): + """ Reply to a list request. """ + for tube_info in tubes: + self._new_tube_cb(*tube_info) + + def _list_tubes_error_cb(self, e): + """ Log errors. """ + _logger.debug('Error: ListTubes() failed: %s' % (e)) + + def _new_tube_cb(self, id, initiator, type, service, params, state): + """ Create a new tube. """ + _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 == 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.chattube = ChatTube(tube_conn, self.initiating, \ + self.event_received_cb) + + def _setup_dispatch_table(self): + ''' Associate tokens with commands. ''' + self._processing_methods = { + 'n': [self._receive_new_game, 'get a new game grid'], + 'p': [self._receive_dot_click, 'get a dot click'], + } + + def event_received_cb(self, event_message): + ''' Data from a tube has arrived. ''' + if len(event_message) == 0: + return + try: + command, payload = event_message.split('|', 2) + except ValueError: + _logger.debug('Could not split event message %s' % (event_message)) + return + self._processing_methods[command][0](payload) + + def send_new_game(self): + ''' Send a new grid to all players ''' + self.send_event('n|%s' % (json_dump(self._game.save_game()))) + + def _receive_new_game(self, payload): + ''' Sharer can start a new game. ''' + dot_list = json_load(payload) + self._game.restore_game(dot_list) + + def send_dot_click(self, dot, color): + ''' Send a dot click to all the players ''' + self.send_event('p|%s' % (json_dump([dot, color]))) + + def _receive_dot_click(self, payload): + ''' When a dot is clicked, everyone should change its color. ''' + (dot, color) = json_load(payload) + self._game.remote_button_press(dot, color) + + def send_event(self, entry): + """ Send event through the tube. """ + if hasattr(self, 'chattube') and self.chattube is not None: + self.chattube.SendText(entry) + + +class ChatTube(ExportedGObject): + """ Class for setting up tube for sharing """ + + def __init__(self, tube, is_initiator, stack_received_cb): + super(ChatTube, self).__init__(tube, PATH) + self.tube = tube + self.is_initiator = is_initiator # Are we sharing or joining activity? + self.stack_received_cb = stack_received_cb + self.stack = '' + + self.tube.add_signal_receiver(self.send_stack_cb, 'SendText', IFACE, + path=PATH, sender_keyword='sender') + + def send_stack_cb(self, text, sender=None): + if sender == self.tube.get_unique_name(): + return + self.stack = text + self.stack_received_cb(text) + + @signal(dbus_interface=IFACE, signature='s') + def SendText(self, text): + self.stack = text diff --git a/gtk2/game.py b/gtk2/game.py new file mode 100644 index 0000000..c1bc398 --- /dev/null +++ b/gtk2/game.py @@ -0,0 +1,265 @@ +# -*- coding: utf-8 -*- +#Copyright (c) 2012 Walter Bender + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# You should have received a copy of the GNU General Public License +# along with this library; if not, write to the Free Software +# Foundation, 51 Franklin Street, Suite 500 Boston, MA 02110-1335 USA + + +import gtk +import gobject +import cairo +import os +import glob +from random import uniform + +from gettext import gettext as _ + +import logging +_logger = logging.getLogger('search-activity') + +try: + from sugar.graphics import style + GRID_CELL_SIZE = style.GRID_CELL_SIZE +except ImportError: + GRID_CELL_SIZE = 0 + +from sprites import Sprites, Sprite + + +DOT_SIZE = 40 +COLORS = ['#000000', '#a00000', '#907000', '#009000', '#0000ff', '#9000a0'] + + +class Game(): + + def __init__(self, canvas, parent=None, path=None, + colors=['#A0FFA0', '#FF8080']): + self._canvas = canvas + self._parent = parent + self._parent.show_all() + self._path = path + + self._colors = ['#FFFFFF'] + self._colors.append(colors[0]) + self._colors.append(colors[1]) + + self._canvas.set_flags(gtk.CAN_FOCUS) + self._canvas.connect("expose-event", self._expose_cb) + + self._width = gtk.gdk.screen_width() + self._height = gtk.gdk.screen_height() - (GRID_CELL_SIZE * 1.5) + self._scale = self._height / (3 * DOT_SIZE * 1.2) + self._scale /= 1.5 + self._dot_size = int(DOT_SIZE * self._scale) + self._space = int(self._dot_size / 5.) + self.we_are_sharing = False + + self._start_time = 0 + self._timeout_id = None + + # Find the image files + self._PATHS = glob.glob(os.path.join(self._path, 'images', '*.svg')) + + # Generate the sprites we'll need... + self._sprites = Sprites(self._canvas) + self._dots = [] + yoffset = self._space * 2 # int(self._space / 2.) + for y in range(3): + for x in range(3): + xoffset = int((self._width - 3 * self._dot_size - \ + 2 * self._space) / 2.) + self._dots.append( + Sprite(self._sprites, + xoffset + x * (self._dot_size + self._space), + y * (self._dot_size + self._space) + yoffset, + self._new_dot_surface(color=self._colors[0]))) + self._dots[-1].type = -1 # No image + self._dots[-1].set_label_attributes(72) + + def _all_clear(self): + ''' Things to reinitialize when starting up a new game. ''' + if self._timeout_id is not None: + gobject.source_remove(self._timeout_id) + + for dot in self._dots: + if dot.type != -1: + dot.type = -1 + dot.set_shape(self._new_dot_surface( + self._colors[abs(dot.type)])) + dot.set_label('?') + self._dance_counter = 0 + self._dance_step() + + def _dance_step(self): + ''' Short animation before loading new game ''' + for dot in self._dots: + dot.set_shape(self._new_dot_surface( + self._colors[int(uniform(0, 3))])) + self._dance_counter += 1 + if self._dance_counter < 10: + self._timeout_id = gobject.timeout_add(500, self._dance_step) + else: + self._new_game() + + def new_game(self): + ''' Start a new game. ''' + self._all_clear() + + def _new_game(self): + ''' Select pictures at random ''' + for i in range(3 * 3): + self._dots[i].set_label('') + self._dots[i].type = int(uniform(0, len(self._PATHS))) + _logger.debug(self._dots[i].type) + self._dots[i].set_shape(self._new_dot_surface( + image=self._dots[i].type)) + + if self.we_are_sharing: + _logger.debug('sending a new game') + self._parent.send_new_game() + + def restore_game(self, dot_list): + ''' Restore a game from the Journal or share ''' + for i, dot in enumerate(dot_list): + self._dots[i].type = dot + self._dots[i].set_shape(self._new_dot_surface( + image=self._dots[i].type)) + + def save_game(self): + ''' Return dot list for saving to Journal or + sharing ''' + dot_list = [] + for dot in self._dots: + dot_list.append(dot.type) + return dot_list + + def set_sharing(self, share=True): + _logger.debug('enabling sharing') + self.we_are_sharing = share + + def _grid_to_dot(self, pos): + ''' calculate the dot index from a column and row in the grid ''' + return pos[0] + pos[1] * 3 + + def _dot_to_grid(self, dot): + ''' calculate the grid column and row for a dot ''' + return [dot % 3, int(dot / 3)] + + def _expose_cb(self, win, event): + self.do_expose_event(event) + + def do_expose_event(self, event): + ''' Handle the expose-event by drawing ''' + # Restrict Cairo to the exposed area + cr = self._canvas.window.cairo_create() + cr.rectangle(event.area.x, event.area.y, + event.area.width, event.area.height) + cr.clip() + # Refresh sprite list + self._sprites.redraw_sprites(cr=cr) + + def _destroy_cb(self, win, event): + gtk.main_quit() + + def export(self): + ''' Write dot to cairo surface. ''' + w = h = int(4 * self._space + 3 * self._dot_size) + png_surface = cairo.ImageSurface(cairo.FORMAT_RGB24, w, h) + cr = cairo.Context(png_surface) + cr.set_source_rgb(192, 192, 192) + cr.rectangle(0, 0, w, h) + cr.fill() + for i in range(9): + y = self._space + int(i / 3.) * (self._dot_size + self._space) + x = self._space + (i % 3) * (self._dot_size + self._space) + cr.save() + cr = gtk.gdk.CairoContext(cr) + cr.set_source_surface(self._dots[i].cached_surfaces[0], x, y) + cr.rectangle(x, y, self._dot_size, self._dot_size) + cr.fill() + cr.restore() + return png_surface + + def _new_dot_surface(self, color='#000000', image=None): + ''' generate a dot of a color color ''' + self._dot_cache = {} + if image is not None: + color = COLORS[int(uniform(0, 6))] + fd = open(os.path.join(self._path, self._PATHS[image]), 'r') + svg_string = '' + for line in fd: + svg_string += line.replace('#000000', color) + fd.close() + pixbuf = svg_str_to_pixbuf(svg_string, w=self._dot_size, + h = self._dot_size) + ''' + pixbuf = gtk.gdk.pixbuf_new_from_file_at_size( + os.path.join(self._path, self._PATHS[image]), + self._dot_size, self._dot_size) + ''' + else: + if color in self._dot_cache: + return self._dot_cache[color] + self._stroke = color + self._fill = color + self._svg_width = self._dot_size + self._svg_height = self._dot_size + + i = self._colors.index(color) + pixbuf = svg_str_to_pixbuf( + self._header() + \ + self._circle(self._dot_size / 2., self._dot_size / 2., + self._dot_size / 2.) + \ + self._footer()) + surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, + self._svg_width, self._svg_height) + context = cairo.Context(surface) + context = gtk.gdk.CairoContext(context) + context.set_source_pixbuf(pixbuf, 0, 0) + context.rectangle(0, 0, self._svg_width, self._svg_height) + context.fill() + if image is None: + self._dot_cache[color] = surface + return surface + + def _header(self): + return '<svg\n' + 'xmlns:svg="http://www.w3.org/2000/svg"\n' + \ + 'xmlns="http://www.w3.org/2000/svg"\n' + \ + 'xmlns:xlink="http://www.w3.org/1999/xlink"\n' + \ + 'version="1.1"\n' + 'width="' + str(self._svg_width) + '"\n' + \ + 'height="' + str(self._svg_height) + '">\n' + + def _rect(self, w, h, x, y): + svg_string = ' <rect\n' + svg_string += ' width="%f"\n' % (w) + svg_string += ' height="%f"\n' % (h) + svg_string += ' rx="%f"\n' % (0) + svg_string += ' ry="%f"\n' % (0) + svg_string += ' x="%f"\n' % (x) + svg_string += ' y="%f"\n' % (y) + svg_string += 'style="fill:#000000;stroke:#000000;"/>\n' + return svg_string + + def _circle(self, r, cx, cy): + return '<circle style="fill:' + str(self._fill) + ';stroke:' + \ + str(self._stroke) + ';" r="' + str(r - 0.5) + '" cx="' + \ + str(cx) + '" cy="' + str(cy) + '" />\n' + + def _footer(self): + return '</svg>\n' + + +def svg_str_to_pixbuf(svg_string, w=None, h=None): + ''' Load pixbuf from SVG string ''' + pl = gtk.gdk.PixbufLoader('svg') + if w is not None: + pl.set_size(w, h) + pl.write(svg_string) + pl.close() + return pl.get_pixbuf() diff --git a/gtk2/grecord.py b/gtk2/grecord.py new file mode 100644 index 0000000..02fcbd3 --- /dev/null +++ b/gtk2/grecord.py @@ -0,0 +1,244 @@ +#Copyright (c) 2008, Media Modifications Ltd. +#Copyright (c) 2011-12, Walter Bender + +#Permission is hereby granted, free of charge, to any person obtaining a copy +#of this software and associated documentation files (the "Software"), to deal +#in the Software without restriction, including without limitation the rights +#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +#copies of the Software, and to permit persons to whom the Software is +#furnished to do so, subject to the following conditions: + +#The above copyright notice and this permission notice shall be included in +#all copies or substantial portions of the Software. + +#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +#THE SOFTWARE. + +import os +import time + +import gtk +import gst + +import logging +_logger = logging.getLogger("portfolio-activity") + +import gobject +gobject.threads_init() + + +class Grecord: + + def __init__(self, parent): + self._activity = parent + self._eos_cb = None + + self._can_limit_framerate = False + self._playing = False + + self._audio_transcode_handler = None + self._transcode_id = None + + self._pipeline = gst.Pipeline("Record") + self._create_audiobin() + + bus = self._pipeline.get_bus() + bus.add_signal_watch() + bus.connect('message', self._bus_message_handler) + + def _create_audiobin(self): + src = gst.element_factory_make("alsasrc", "absrc") + + # attempt to use direct access to the 0,0 device, solving some A/V + # sync issues + src.set_property("device", "plughw:0,0") + hwdev_available = src.set_state(gst.STATE_PAUSED) != \ + gst.STATE_CHANGE_FAILURE + src.set_state(gst.STATE_NULL) + if not hwdev_available: + src.set_property("device", "default") + + srccaps = gst.Caps("audio/x-raw-int,rate=16000,channels=1,depth=16") + + # Guarantee perfect stream, important for A/V sync + rate = gst.element_factory_make("audiorate") + + # Without a buffer here, gstreamer struggles at the start of the + # recording and then the A/V sync is bad for the whole video + # (possibly a gstreamer/ALSA bug -- even if it gets caught up, it + # should be able to resync without problem). + queue = gst.element_factory_make("queue", "audioqueue") + queue.set_property("leaky", True) # prefer fresh data + queue.set_property("max-size-time", 5000000000) # 5 seconds + queue.set_property("max-size-buffers", 500) + queue.connect("overrun", self._log_queue_overrun) + + enc = gst.element_factory_make("wavenc", "abenc") + + sink = gst.element_factory_make("filesink", "absink") + sink.set_property("location", + os.path.join(self._activity.get_activity_root(), + 'instance', 'output.wav')) + + self._audiobin = gst.Bin("audiobin") + self._audiobin.add(src, rate, queue, enc, sink) + + src.link(rate, srccaps) + gst.element_link_many(rate, queue, enc, sink) + + def _log_queue_overrun(self, queue): + cbuffers = queue.get_property("current-level-buffers") + cbytes = queue.get_property("current-level-bytes") + ctime = queue.get_property("current-level-time") + + def play(self): + if self._get_state() == gst.STATE_PLAYING: + return + + self._pipeline.set_state(gst.STATE_PLAYING) + self._playing = True + + def pause(self): + self._pipeline.set_state(gst.STATE_PAUSED) + self._playing = False + + def stop(self): + self._pipeline.set_state(gst.STATE_NULL) + self._playing = False + + def is_playing(self): + return self._playing + + def _get_state(self): + return self._pipeline.get_state()[1] + + def stop_recording_audio(self): + # We should be able to simply pause and remove the audiobin, but + # this seems to cause a gstreamer segfault. So we stop the whole + # pipeline while manipulating it. + # http://dev.laptop.org/ticket/10183 + self._pipeline.set_state(gst.STATE_NULL) + self._pipeline.remove(self._audiobin) + self.play() + + audio_path = os.path.join(self._activity.get_activity_root(), + 'instance', 'output.wav') + if not os.path.exists(audio_path) or os.path.getsize(audio_path) <= 0: + # FIXME: inform model of failure? + _logger.error('output.wav does not exist or is empty') + return + + line = 'filesrc location=' + audio_path + ' name=audioFilesrc ! \ +wavparse name=audioWavparse ! audioconvert name=audioAudioconvert ! \ +vorbisenc name=audioVorbisenc ! oggmux name=audioOggmux ! \ +filesink name=audioFilesink' + self._audioline = gst.parse_launch(line) + + vorbis_enc = self._audioline.get_by_name('audioVorbisenc') + + audioFilesink = self._audioline.get_by_name('audioFilesink') + audioOggFilepath = os.path.join(self._activity.get_activity_root(), + 'instance', 'output.ogg') + audioFilesink.set_property("location", audioOggFilepath) + + audioBus = self._audioline.get_bus() + audioBus.add_signal_watch() + self._audio_transcode_handler = audioBus.connect( + 'message', self._onMuxedAudioMessageCb, self._audioline) + self._transcode_id = gobject.timeout_add(200, self._transcodeUpdateCb, + self._audioline) + self._audiopos = 0 + self._audioline.set_state(gst.STATE_PLAYING) + + def transcoding_complete(self): + # The EOS message is sometimes either not sent or not received. + # So if the position in the stream is not advancing, assume EOS. + if self._transcode_id is None: + _logger.debug('EOS.... transcoding finished') + return True + else: + position, duration = self._query_position(self._audioline) + # _logger.debug('position: %s, duration: %s' % (str(position), + # str(duration))) + if position == duration: + _logger.debug('We are done, even though we did not see EOS') + self._clean_up_transcoding_pipeline(self._audioline) + return True + elif position == self._audiopos: + _logger.debug('No progess, so assume we are done') + self._clean_up_transcoding_pipeline(self._audioline) + return True + self._audiopos = position + return False + + def blockedCb(self, x, y, z): + pass + + def record_audio(self): + # We should be able to add the audiobin on the fly, but unfortunately + # this results in several seconds of silence being added at the start + # of the recording. So we stop the whole pipeline while adjusting it. + # SL#2040 + self._pipeline.set_state(gst.STATE_NULL) + self._pipeline.add(self._audiobin) + self.play() + + def _transcodeUpdateCb(self, pipe): + position, duration = self._query_position(pipe) + if position != gst.CLOCK_TIME_NONE: + value = position * 100.0 / duration + value = value / 100.0 + return True + + def _query_position(self, pipe): + try: + position, format = pipe.query_position(gst.FORMAT_TIME) + except: + position = gst.CLOCK_TIME_NONE + + try: + duration, format = pipe.query_duration(gst.FORMAT_TIME) + except: + duration = gst.CLOCK_TIME_NONE + + return (position, duration) + + def _onMuxedAudioMessageCb(self, bus, message, pipe): + # _logger.debug(message.type) + if message.type != gst.MESSAGE_EOS: + return True + self._clean_up_transcoding_pipeline(pipe) + return False + + def _clean_up_transcoding_pipeline(self, pipe): + gobject.source_remove(self._audio_transcode_handler) + self._audio_transcode_handler = None + gobject.source_remove(self._transcode_id) + self._transcode_id = None + pipe.set_state(gst.STATE_NULL) + pipe.get_bus().remove_signal_watch() + pipe.get_bus().disable_sync_message_emission() + + wavFilepath = os.path.join(self._activity.get_activity_root(), + 'instance', 'output.wav') + os.remove(wavFilepath) + return + + def _bus_message_handler(self, bus, message): + t = message.type + if t == gst.MESSAGE_EOS: + if self._eos_cb: + cb = self._eos_cb + self._eos_cb = None + cb() + elif t == gst.MESSAGE_ERROR: + # TODO: if we come out of suspend/resume with errors, then + # get us back up and running... TODO: handle "No space + # left on the resource.gstfilesink.c" err, debug = + # message.parse_error() + pass diff --git a/gtk2/setup.py b/gtk2/setup.py new file mode 100644 index 0000000..e5026ee --- /dev/null +++ b/gtk2/setup.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python +import os +import sys + +if len(sys.argv) > 1 and '--no-sugar' == sys.argv[1]: + # Remove the argument from the stack so we don't cause problems + # for distutils + sys.argv.pop(1) + + import glob, os.path, string + from distutils.core import setup + + DATA_FILES = [ + ('icons', glob.glob('icons/*')), + ('images', glob.glob('images/*')), + ('/usr/share/applications', ['turtleart.desktop']) + ] + + setup (name = 'Turtle Art', + description = "A LOGO-like tool for teaching programming", + author = "Walter Bender", + author_email = "walter.bender@gmail.com", + version = '0.9.4', + packages = ['TurtleArt'], + scripts = ['turtleart'], + data_files = DATA_FILES, + ) +else: + from sugar.activity import bundlebuilder + + if __name__ == "__main__": + bundlebuilder.start() diff --git a/gtk2/sprites.py b/gtk2/sprites.py new file mode 100644 index 0000000..2b8bb55 --- /dev/null +++ b/gtk2/sprites.py @@ -0,0 +1,459 @@ +# -*- coding: utf-8 -*- + +#Copyright (c) 2007-8, Playful Invention Company. +#Copyright (c) 2008-11 Walter Bender + +#Permission is hereby granted, free of charge, to any person obtaining a copy +#of this software and associated documentation files (the "Software"), to deal +#in the Software without restriction, including without limitation the rights +#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +#copies of the Software, and to permit persons to whom the Software is +#furnished to do so, subject to the following conditions: + +#The above copyright notice and this permission notice shall be included in +#all copies or substantial portions of the Software. + +#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +#THE SOFTWARE. + +''' + +sprites.py is a simple sprites library for managing graphics objects, +'sprites', on a gtk.DrawingArea. It manages multiple sprites with +methods such as move, hide, set_layer, etc. + +There are two classes: + +class Sprites maintains a collection of sprites +class Sprite manages individual sprites within the collection. + +Example usage: + # Import the classes into your program. + from sprites import Sprites Sprite + + # Create a new sprite collection associated with your widget + self.sprite_list = Sprites(widget) + + # Create a "pixbuf" (in this example, from SVG). + my_pixbuf = svg_str_to_pixbuf("<svg>...some svg code...</svg>") + + # Create a sprite at position x1, y1. + my_sprite = sprites.Sprite(self.sprite_list, x1, y1, my_pixbuf) + + # Move the sprite to a new position. + my_sprite.move((x1+dx, y1+dy)) + + # Create another "pixbuf". + your_pixbuf = svg_str_to_pixbuf("<svg>...some svg code...</svg>") + + # Create a sprite at position x2, y2. + your_sprite = sprites.Sprite(self.sprite_list, x2, y2, my_pixbuf) + + # Assign the sprites to layers. + # In this example, your_sprite will be on top of my_sprite. + my_sprite.set_layer(100) + your_sprite.set_layer(200) + + # Now put my_sprite on top of your_sprite. + my_sprite.set_layer(300) + + cr = self.window.cairo_create() + # In your activity's do_expose_event, put in a call to redraw_sprites + self.sprites.redraw_sprites(event.area, cairo_context) + +# method for converting SVG to a gtk pixbuf +def svg_str_to_pixbuf(svg_string): + pl = gtk.gdk.PixbufLoader('svg') + pl.write(svg_string) + pl.close() + pixbuf = pl.get_pixbuf() + return pixbuf + +''' + +import pygtk +pygtk.require('2.0') +import gtk +import pango +import pangocairo +import cairo + +class Sprites: + ''' A class for the list of sprites and everything they share in common ''' + + def __init__(self, widget): + ''' Initialize an empty array of sprites ''' + self.widget = widget + self.list = [] + self.cr = None + + def set_cairo_context(self, cr): + ''' Cairo context may be set or reset after __init__ ''' + self.cr = cr + + def get_sprite(self, i): + ''' Return a sprint from the array ''' + if i < 0 or i > len(self.list) - 1: + return(None) + else: + return(self.list[i]) + + def length_of_list(self): + ''' How many sprites are there? ''' + return(len(self.list)) + + def append_to_list(self, spr): + ''' Append a new sprite to the end of the list. ''' + self.list.append(spr) + + def insert_in_list(self, spr, i): + ''' Insert a sprite at position i. ''' + if i < 0: + self.list.insert(0, spr) + elif i > len(self.list) - 1: + self.list.append(spr) + else: + self.list.insert(i, spr) + + def remove_from_list(self, spr): + ''' Remove a sprite from the list. ''' + if spr in self.list: + self.list.remove(spr) + + def find_sprite(self, pos, inverse=False): + ''' Search based on (x, y) position. Return the 'top/first' one. ''' + list = self.list[:] + if not inverse: + list.reverse() + for spr in list: + if spr.hit(pos): + return spr + return None + + def redraw_sprites(self, area=None, cr=None): + ''' Redraw the sprites that intersect area. ''' + # I think I need to do this to save Cairo some work + if cr is None: + cr = self.cr + else: + self.cr = cr + if cr is None: + print 'sprites.redraw_sprites: no Cairo context' + return + for spr in self.list: + if area == None: + spr.draw(cr=cr) + else: + intersection = spr.rect.intersect(area) + if intersection.width > 0 or intersection.height > 0: + spr.draw(cr=cr) + + +class Sprite: + ''' A class for the individual sprites ''' + + def __init__(self, sprites, x, y, image): + ''' Initialize an individual sprite ''' + self._sprites = sprites + self.save_xy = (x, y) # remember initial (x, y) position + self.rect = gtk.gdk.Rectangle(int(x), int(y), 0, 0) + self._scale = [12] + self._rescale = [True] + self._horiz_align = ["center"] + self._vert_align = ["middle"] + self._fd = None + self._bold = False + self._italic = False + self._color = None + self._margins = [0, 0, 0, 0] + self.layer = 100 + self.labels = [] + self.cached_surfaces = [] + self._dx = [] # image offsets + self._dy = [] + self.type = None + self.set_image(image) + self._sprites.append_to_list(self) + + def set_image(self, image, i=0, dx=0, dy=0): + ''' Add an image to the sprite. ''' + while len(self.cached_surfaces) < i + 1: + self.cached_surfaces.append(None) + self._dx.append(0) + self._dy.append(0) + self._dx[i] = dx + self._dy[i] = dy + if isinstance(image, gtk.gdk.Pixbuf) or \ + isinstance(image, cairo.ImageSurface): + w = image.get_width() + h = image.get_height() + else: + w, h = image.get_size() + if i == 0: # Always reset width and height when base image changes. + self.rect.width = w + dx + self.rect.height = h + dy + else: + if w + dx > self.rect.width: + self.rect.width = w + dx + if h + dy > self.rect.height: + self.rect.height = h + dy + if isinstance(image, cairo.ImageSurface): + self.cached_surfaces[i] = image + else: # Convert to Cairo surface + surface = cairo.ImageSurface( + cairo.FORMAT_ARGB32, self.rect.width, self.rect.height) + context = cairo.Context(surface) + context = gtk.gdk.CairoContext(context) + context.set_source_pixbuf(image, 0, 0) + context.rectangle(0, 0, self.rect.width, self.rect.height) + context.fill() + self.cached_surfaces[i] = surface + + def move(self, pos): + ''' Move to new (x, y) position ''' + self.inval() + self.rect.x, self.rect.y = int(pos[0]), int(pos[1]) + self.inval() + + def move_relative(self, pos): + ''' Move to new (x+dx, y+dy) position ''' + self.inval() + self.rect.x += int(pos[0]) + self.rect.y += int(pos[1]) + self.inval() + + def get_xy(self): + ''' Return current (x, y) position ''' + return (self.rect.x, self.rect.y) + + def get_dimensions(self): + ''' Return current size ''' + return (self.rect.width, self.rect.height) + + def get_layer(self): + ''' Return current layer ''' + return self.layer + + def set_shape(self, image, i=0): + ''' Set the current image associated with the sprite ''' + self.inval() + self.set_image(image, i) + self.inval() + + def set_layer(self, layer=None): + ''' Set the layer for a sprite ''' + self._sprites.remove_from_list(self) + if layer is not None: + self.layer = layer + for i in range(self._sprites.length_of_list()): + if layer < self._sprites.get_sprite(i).layer: + self._sprites.insert_in_list(self, i) + self.inval() + return + self._sprites.append_to_list(self) + self.inval() + + def set_label(self, new_label, i=0): + ''' Set the label drawn on the sprite ''' + self._extend_labels_array(i) + if type(new_label) is str or type(new_label) is unicode: + # pango doesn't like nulls + self.labels[i] = new_label.replace("\0", " ") + else: + self.labels[i] = str(new_label) + self.inval() + + def set_margins(self, l=0, t=0, r=0, b=0): + ''' Set the margins for drawing the label ''' + self._margins = [l, t, r, b] + + def _extend_labels_array(self, i): + ''' Append to the labels attribute list ''' + if self._fd is None: + self.set_font('Sans') + if self._color is None: + self._color = (0., 0., 0.) + while len(self.labels) < i + 1: + self.labels.append(" ") + self._scale.append(self._scale[0]) + self._rescale.append(self._rescale[0]) + self._horiz_align.append(self._horiz_align[0]) + self._vert_align.append(self._vert_align[0]) + + def set_font(self, font): + ''' Set the font for a label ''' + self._fd = pango.FontDescription(font) + + def set_label_color(self, rgb): + ''' Set the font color for a label ''' + COLORTABLE = {'black': '#000000', 'white': '#FFFFFF', + 'red': '#FF0000', 'yellow': '#FFFF00', + 'green': '#00FF00', 'cyan': '#00FFFF', + 'blue': '#0000FF', 'purple': '#FF00FF', + 'gray': '#808080'} + if rgb.lower() in COLORTABLE: + rgb = COLORTABLE[rgb.lower()] + # Convert from '#RRGGBB' to floats + self._color = (int('0x' + rgb[1:3], 16) / 256., + int('0x' + rgb[3:5], 16) / 256., + int('0x' + rgb[5:7], 16) / 256.) + return + + def set_label_attributes(self, scale, rescale=True, horiz_align="center", + vert_align="middle", i=0): + ''' Set the various label attributes ''' + self._extend_labels_array(i) + self._scale[i] = scale + self._rescale[i] = rescale + self._horiz_align[i] = horiz_align + self._vert_align[i] = vert_align + + def hide(self): + ''' Hide a sprite ''' + self.inval() + self._sprites.remove_from_list(self) + + def restore(self): + ''' Restore a hidden sprite ''' + self.set_layer() + + def inval(self): + ''' Invalidate a region for gtk ''' + self._sprites.widget.queue_draw_area(self.rect.x, + self.rect.y, + self.rect.width, + self.rect.height) + + def draw(self, cr=None): + ''' Draw the sprite (and label) ''' + if cr is None: + print 'sprite.draw: no Cairo context.' + return + for i, surface in enumerate(self.cached_surfaces): + cr.set_source_surface(surface, + self.rect.x + self._dx[i], + self.rect.y + self._dy[i]) + cr.rectangle(self.rect.x + self._dx[i], + self.rect.y + self._dy[i], + self.rect.width, + self.rect.height) + cr.fill() + if len(self.labels) > 0: + self.draw_label(cr) + + def hit(self, pos): + ''' Is (x, y) on top of the sprite? ''' + x, y = pos + if x < self.rect.x: + return False + if x > self.rect.x + self.rect.width: + return False + if y < self.rect.y: + return False + if y > self.rect.y + self.rect.height: + return False + return True + + def draw_label(self, cr): + ''' Draw the label based on its attributes ''' + # Create a pangocairo context + cr = pangocairo.CairoContext(cr) + my_width = self.rect.width - self._margins[0] - self._margins[2] + if my_width < 0: + my_width = 0 + my_height = self.rect.height - self._margins[1] - self._margins[3] + for i in range(len(self.labels)): + pl = cr.create_layout() + pl.set_text(str(self.labels[i])) + self._fd.set_size(int(self._scale[i] * pango.SCALE)) + pl.set_font_description(self._fd) + w = pl.get_size()[0] / pango.SCALE + if w > my_width: + if self._rescale[i]: + self._fd.set_size( + int(self._scale[i] * pango.SCALE * my_width / w)) + pl.set_font_description(self._fd) + w = pl.get_size()[0] / pango.SCALE + else: + j = len(self.labels[i]) - 1 + while(w > my_width and j > 0): + pl.set_text( + "…" + self.labels[i][len(self.labels[i]) - j:]) + self._fd.set_size(int(self._scale[i] * pango.SCALE)) + pl.set_font_description(self._fd) + w = pl.get_size()[0] / pango.SCALE + j -= 1 + if self._horiz_align[i] == "center": + x = int(self.rect.x + self._margins[0] + (my_width - w) / 2) + elif self._horiz_align[i] == 'left': + x = int(self.rect.x + self._margins[0]) + else: # right + x = int(self.rect.x + self.rect.width - w - self._margins[2]) + h = pl.get_size()[1] / pango.SCALE + if self._vert_align[i] == "middle": + y = int(self.rect.y + self._margins[1] + (my_height - h) / 2) + elif self._vert_align[i] == "top": + y = int(self.rect.y + self._margins[1]) + else: # bottom + y = int(self.rect.y + self.rect.height - h - self._margins[3]) + cr.save() + cr.translate(x, y) + cr.set_source_rgb(self._color[0], self._color[1], self._color[2]) + cr.update_layout(pl) + cr.show_layout(pl) + cr.restore() + + def label_width(self): + ''' Calculate the width of a label ''' + cr = pangocairo.CairoContext(self._sprites.cr) + if cr is not None: + max = 0 + for i in range(len(self.labels)): + pl = cr.create_layout() + pl.set_text(self.labels[i]) + self._fd.set_size(int(self._scale[i] * pango.SCALE)) + pl.set_font_description(self._fd) + w = pl.get_size()[0] / pango.SCALE + if w > max: + max = w + return max + else: + return self.rect.width + + def label_safe_width(self): + ''' Return maximum width for a label ''' + return self.rect.width - self._margins[0] - self._margins[2] + + def label_safe_height(self): + ''' Return maximum height for a label ''' + return self.rect.height - self._margins[1] - self._margins[3] + + def label_left_top(self): + ''' Return the upper-left corner of the label safe zone ''' + return(self._margins[0], self._margins[1]) + + def get_pixel(self, pos, i=0): + ''' Return the pixel at (x, y) ''' + x = int(pos[0] - self.rect.x) + y = int(pos[1] - self.rect.y) + if x < 0 or x > (self.rect.width - 1) or \ + y < 0 or y > (self.rect.height - 1): + return(-1, -1, -1, -1) + + # create a new 1x1 cairo surface + cs = cairo.ImageSurface(cairo.FORMAT_RGB24, 1, 1); + cr = cairo.Context(cs) + cr.set_source_surface(self.cached_surfaces[i], -x, -y) + cr.rectangle(0,0,1,1) + cr.set_operator(cairo.OPERATOR_SOURCE) + cr.fill() + cs.flush() # ensure all writing is done + # Read the pixel + pixels = cs.get_data() + return (ord(pixels[2]), ord(pixels[1]), ord(pixels[0]), 0) + diff --git a/gtk2/toolbar_utils.py b/gtk2/toolbar_utils.py new file mode 100644 index 0000000..94e6883 --- /dev/null +++ b/gtk2/toolbar_utils.py @@ -0,0 +1,164 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2011, Walter Bender + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# You should have received a copy of the GNU General Public License +# along with this library; if not, write to the Free Software +# Foundation, 51 Franklin Street, Suite 500 Boston, MA 02110-1335 USA + + +import gtk + +from sugar.graphics.radiotoolbutton import RadioToolButton +from sugar.graphics.toolbutton import ToolButton +from sugar.graphics.combobox import ComboBox +from sugar.graphics.toolcombobox import ToolComboBox + + +def combo_factory(combo_array, toolbar, callback, cb_arg=None, + tooltip=None, default=None): + '''Factory for making a toolbar combo box''' + combo = ComboBox() + if tooltip is not None and hasattr(combo, 'set_tooltip_text'): + combo.set_tooltip_text(tooltip) + if cb_arg is not None: + combo.connect('changed', callback, cb_arg) + else: + combo.connect('changed', callback) + for i, selection in enumerate(combo_array): + combo.append_item(i, selection, None) + combo.show() + toolitem = gtk.ToolItem() + toolitem.add(combo) + if hasattr(toolbar, 'insert'): # the main toolbar + toolbar.insert(toolitem, -1) + else: # or a secondary toolbar + toolbar.props.page.insert(toolitem, -1) + toolitem.show() + if default is not None: + combo.set_active(combo_array.index(default)) + return combo + + +def entry_factory(default_string, toolbar, tooltip=None, max=3): + ''' Factory for adding a text box to a toolbar ''' + entry = gtk.Entry() + entry.set_text(default_string) + if tooltip is not None and hasattr(entry, 'set_tooltip_text'): + entry.set_tooltip_text(tooltip) + entry.set_width_chars(max) + entry.show() + toolitem = gtk.ToolItem() + toolitem.add(entry) + if hasattr(toolbar, 'insert'): # the main toolbar + toolbar.insert(toolitem, -1) + else: # or a secondary toolbar + toolbar.props.page.insert(toolitem, -1) + toolitem.show() + return entry + + +def button_factory(icon_name, toolbar, callback, cb_arg=None, tooltip=None, + accelerator=None): + '''Factory for making tooplbar buttons''' + button = ToolButton(icon_name) + if tooltip is not None: + button.set_tooltip(tooltip) + button.props.sensitive = True + if accelerator is not None: + button.props.accelerator = accelerator + if cb_arg is not None: + button.connect('clicked', callback, cb_arg) + else: + button.connect('clicked', callback) + if hasattr(toolbar, 'insert'): # the main toolbar + toolbar.insert(button, -1) + else: # or a secondary toolbar + toolbar.props.page.insert(button, -1) + button.show() + return button + + +def radio_factory(name, toolbar, callback, cb_arg=None, tooltip=None, + group=None): + ''' Add a radio button to a toolbar ''' + button = RadioToolButton(group=group) + button.set_named_icon(name) + if callback is not None: + if cb_arg is None: + button.connect('clicked', callback) + else: + button.connect('clicked', callback, cb_arg) + if hasattr(toolbar, 'insert'): # Add button to the main toolbar... + toolbar.insert(button, -1) + else: # ...or a secondary toolbar. + toolbar.props.page.insert(button, -1) + button.show() + if tooltip is not None: + button.set_tooltip(tooltip) + return button + + +def label_factory(toolbar, label_text, width=None): + ''' Factory for adding a label to a toolbar ''' + label = gtk.Label(label_text) + label.set_line_wrap(True) + if width is not None: + label.set_size_request(width, -1) # doesn't work on XOs + label.show() + toolitem = gtk.ToolItem() + toolitem.add(label) + if hasattr(toolbar, 'insert'): # the main toolbar + toolbar.insert(toolitem, -1) + else: # or a secondary toolbar + toolbar.props.page.insert(toolitem, -1) + toolitem.show() + return label + + +def separator_factory(toolbar, expand=False, visible=True): + ''' add a separator to a toolbar ''' + separator = gtk.SeparatorToolItem() + separator.props.draw = visible + separator.set_expand(expand) + if hasattr(toolbar, 'insert'): # the main toolbar + toolbar.insert(separator, -1) + else: # or a secondary toolbar + toolbar.props.page.insert(separator, -1) + separator.show() + + +def image_factory(image, toolbar, tooltip=None): + ''' Add an image to the toolbar ''' + img = gtk.Image() + img.set_from_pixbuf(image) + img_tool = gtk.ToolItem() + img_tool.add(img) + if tooltip is not None: + img.set_tooltip_text(tooltip) + if hasattr(toolbar, 'insert'): # the main toolbar + toolbar.insert(img_tool, -1) + else: # or a secondary toolbar + toolbar.props.page.insert(img_tool, -1) + img_tool.show() + return img + + +def spin_factory(default, min, max, callback, toolbar): + spin_adj = gtk.Adjustment(default, min, max, 1, 32, 0) + spin = gtk.SpinButton(spin_adj, 0, 0) + spin_id = spin.connect('value-changed', callback) + spin.set_numeric(True) + spin.show() + toolitem = gtk.ToolItem() + toolitem.add(spin) + if hasattr(toolbar, 'insert'): # the main toolbar + toolbar.insert(toolitem, -1) + else: + toolbar.props.page.insert(toolitem, -1) + toolitem.show() + return spin diff --git a/gtk2/toolbar_utils.py~ b/gtk2/toolbar_utils.py~ new file mode 100644 index 0000000..ef2382d --- /dev/null +++ b/gtk2/toolbar_utils.py~ @@ -0,0 +1,165 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2011, Walter Bender +# Port To GTK3: +# Ignacio Rodriguez <ignaciorodriguez@sugarlabs.org> +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# You should have received a copy of the GNU General Public License +# along with this library; if not, write to the Free Software +# Foundation, 51 Franklin Street, Suite 500 Boston, MA 02110-1335 USA + + +from gi.repository import Gtk + +from sugar3.graphics.radiotoolbutton import RadioToolButton +from sugar3.graphics.toolbutton import ToolButton +from sugar3.graphics.combobox import ComboBox +from sugar3.graphics.toolcombobox import ToolComboBox + + +def combo_factory(combo_array, toolbar, callback, cb_arg=None, + tooltip=None, default=None): + '''Factory for making a toolbar combo box''' + combo = ComboBox() + if tooltip is not None and hasattr(combo, 'set_tooltip_text'): + combo.set_tooltip_text(tooltip) + if cb_arg is not None: + combo.connect('changed', callback, cb_arg) + else: + combo.connect('changed', callback) + for i, selection in enumerate(combo_array): + combo.append_item(i, selection, None) + combo.show() + toolitem = Gtk.ToolItem() + toolitem.add(combo) + if hasattr(toolbar, 'insert'): # the main toolbar + toolbar.insert(toolitem, -1) + else: # or a secondary toolbar + toolbar.props.page.insert(toolitem, -1) + toolitem.show() + if default is not None: + combo.set_active(combo_array.index(default)) + return combo + + +def entry_factory(default_string, toolbar, tooltip=None, max=3): + ''' Factory for adding a text box to a toolbar ''' + entry = Gtk.Entry() + entry.set_text(default_string) + if tooltip is not None and hasattr(entry, 'set_tooltip_text'): + entry.set_tooltip_text(tooltip) + entry.set_width_chars(max) + entry.show() + toolitem = Gtk.ToolItem() + toolitem.add(entry) + if hasattr(toolbar, 'insert'): # the main toolbar + toolbar.insert(toolitem, -1) + else: # or a secondary toolbar + toolbar.props.page.insert(toolitem, -1) + toolitem.show() + return entry + + +def button_factory(icon_name, toolbar, callback, cb_arg=None, tooltip=None, + accelerator=None): + '''Factory for making tooplbar buttons''' + button = ToolButton(icon_name) + if tooltip is not None: + button.set_tooltip(tooltip) + button.props.sensitive = True + if accelerator is not None: + button.props.accelerator = accelerator + if cb_arg is not None: + button.connect('clicked', callback, cb_arg) + else: + button.connect('clicked', callback) + if hasattr(toolbar, 'insert'): # the main toolbar + toolbar.insert(button, -1) + else: # or a secondary toolbar + toolbar.props.page.insert(button, -1) + button.show() + return button + + +def radio_factory(name, toolbar, callback, cb_arg=None, tooltip=None, + group=None): + ''' Add a radio button to a toolbar ''' + button = RadioToolButton(group=group) + button.set_named_icon(name) + if callback is not None: + if cb_arg is None: + button.connect('clicked', callback) + else: + button.connect('clicked', callback, cb_arg) + if hasattr(toolbar, 'insert'): # Add button to the main toolbar... + toolbar.insert(button, -1) + else: # ...or a secondary toolbar. + toolbar.props.page.insert(button, -1) + button.show() + if tooltip is not None: + button.set_tooltip(tooltip) + return button + + +def label_factory(toolbar, label_text, width=None): + ''' Factory for adding a label to a toolbar ''' + label = Gtk.Label(label_text) + label.set_line_wrap(True) + if width is not None: + label.set_size_request(width, -1) # doesn't work on XOs + label.show() + toolitem = Gtk.ToolItem() + toolitem.add(label) + if hasattr(toolbar, 'insert'): # the main toolbar + toolbar.insert(toolitem, -1) + else: # or a secondary toolbar + toolbar.props.page.insert(toolitem, -1) + toolitem.show() + return label + + +def separator_factory(toolbar, expand=False, visible=True): + ''' add a separator to a toolbar ''' + separator = Gtk.SeparatorToolItem() + separator.props.draw = visible + separator.set_expand(expand) + if hasattr(toolbar, 'insert'): # the main toolbar + toolbar.insert(separator, -1) + else: # or a secondary toolbar + toolbar.props.page.insert(separator, -1) + separator.show() + + +def image_factory(image, toolbar, tooltip=None): + ''' Add an image to the toolbar ''' + img = Gtk.Image() + img.set_from_pixbuf(image) + img_tool = Gtk.ToolItem() + img_tool.add(img) + if tooltip is not None: + img.set_tooltip_text(tooltip) + if hasattr(toolbar, 'insert'): # the main toolbar + toolbar.insert(img_tool, -1) + else: # or a secondary toolbar + toolbar.props.page.insert(img_tool, -1) + img_tool.show() + return img + + +def spin_factory(default, min, max, callback, toolbar): + spin_adj = Gtk.Adjustment(default, min, max, 1, 32, 0) + spin = Gtk.SpinButton(spin_adj, 0, 0) + spin_id = spin.connect('value-changed', callback) + spin.set_numeric(True) + spin.show() + toolitem = Gtk.ToolItem() + toolitem.add(spin) + if hasattr(toolbar, 'insert'): # the main toolbar + toolbar.insert(toolitem, -1) + else: + toolbar.props.page.insert(toolitem, -1) + toolitem.show() + return spin diff --git a/gtk2/utils.py b/gtk2/utils.py new file mode 100644 index 0000000..230ab45 --- /dev/null +++ b/gtk2/utils.py @@ -0,0 +1,61 @@ +#Copyright (c) 2011 Walter Bender + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# You should have received a copy of the GNU General Public License +# along with this library; if not, write to the Free Software +# Foundation, 51 Franklin Street, Suite 500 Boston, MA 02110-1335 USA + +import subprocess +from StringIO import StringIO +try: + OLD_SUGAR_SYSTEM = False + import json + json.dumps + from json import load as jload + from json import dump as jdump +except (ImportError, AttributeError): + try: + import simplejson as json + from simplejson import load as jload + from simplejson import dump as jdump + except: + OLD_SUGAR_SYSTEM = True + + +def json_load(text): + """ Load JSON data using what ever resources are available. """ + if OLD_SUGAR_SYSTEM is True: + listdata = json.read(text) + else: + # strip out leading and trailing whitespace, nulls, and newlines + io = StringIO(text) + try: + listdata = jload(io) + except ValueError: + # assume that text is ascii list + listdata = text.split() + for i, value in enumerate(listdata): + listdata[i] = int(value) + return listdata + + +def json_dump(data): + """ Save data using available JSON tools. """ + if OLD_SUGAR_SYSTEM is True: + return json.write(data) + else: + _io = StringIO() + jdump(data, _io) + return _io.getvalue() + + +def play_audio_from_file(file_path): + """ Audio media """ + command_line = ['gst-launch', 'filesrc', 'location=' + file_path, + '! oggdemux', '! vorbisdec', '! audioconvert', + '! alsasink'] + subprocess.call(command_line) diff --git a/locale/en/LC_MESSAGES/org.sugarlabs.StoryActivity.mo b/locale/en/LC_MESSAGES/org.sugarlabs.StoryActivity.mo Binary files differnew file mode 100644 index 0000000..353b27c --- /dev/null +++ b/locale/en/LC_MESSAGES/org.sugarlabs.StoryActivity.mo diff --git a/locale/en/activity.linfo b/locale/en/activity.linfo new file mode 100644 index 0000000..a006122 --- /dev/null +++ b/locale/en/activity.linfo @@ -0,0 +1,2 @@ +[Activity] +name = Story diff --git a/locale/en_GB/LC_MESSAGES/org.sugarlabs.StoryActivity.mo b/locale/en_GB/LC_MESSAGES/org.sugarlabs.StoryActivity.mo Binary files differnew file mode 100644 index 0000000..29c74b6 --- /dev/null +++ b/locale/en_GB/LC_MESSAGES/org.sugarlabs.StoryActivity.mo diff --git a/locale/en_GB/activity.linfo b/locale/en_GB/activity.linfo new file mode 100644 index 0000000..a006122 --- /dev/null +++ b/locale/en_GB/activity.linfo @@ -0,0 +1,2 @@ +[Activity] +name = Story diff --git a/locale/en_US/LC_MESSAGES/org.sugarlabs.StoryActivity.mo b/locale/en_US/LC_MESSAGES/org.sugarlabs.StoryActivity.mo Binary files differnew file mode 100644 index 0000000..a00438e --- /dev/null +++ b/locale/en_US/LC_MESSAGES/org.sugarlabs.StoryActivity.mo diff --git a/locale/en_US/activity.linfo b/locale/en_US/activity.linfo new file mode 100644 index 0000000..a006122 --- /dev/null +++ b/locale/en_US/activity.linfo @@ -0,0 +1,2 @@ +[Activity] +name = Story diff --git a/locale/es/LC_MESSAGES/org.sugarlabs.StoryActivity.mo b/locale/es/LC_MESSAGES/org.sugarlabs.StoryActivity.mo Binary files differnew file mode 100644 index 0000000..26c7bad --- /dev/null +++ b/locale/es/LC_MESSAGES/org.sugarlabs.StoryActivity.mo diff --git a/locale/es/activity.linfo b/locale/es/activity.linfo new file mode 100644 index 0000000..51f29be --- /dev/null +++ b/locale/es/activity.linfo @@ -0,0 +1,2 @@ +[Activity] +name = Crear historias diff --git a/locale/hy/LC_MESSAGES/org.sugarlabs.StoryActivity.mo b/locale/hy/LC_MESSAGES/org.sugarlabs.StoryActivity.mo Binary files differnew file mode 100644 index 0000000..61cd693 --- /dev/null +++ b/locale/hy/LC_MESSAGES/org.sugarlabs.StoryActivity.mo diff --git a/locale/hy/activity.linfo b/locale/hy/activity.linfo new file mode 100644 index 0000000..a006122 --- /dev/null +++ b/locale/hy/activity.linfo @@ -0,0 +1,2 @@ +[Activity] +name = Story diff --git a/locale/pl/LC_MESSAGES/org.sugarlabs.StoryActivity.mo b/locale/pl/LC_MESSAGES/org.sugarlabs.StoryActivity.mo Binary files differnew file mode 100644 index 0000000..ddb60a2 --- /dev/null +++ b/locale/pl/LC_MESSAGES/org.sugarlabs.StoryActivity.mo diff --git a/locale/pl/activity.linfo b/locale/pl/activity.linfo new file mode 100644 index 0000000..bf9b6ff --- /dev/null +++ b/locale/pl/activity.linfo @@ -0,0 +1,2 @@ +[Activity] +name = Opowieść diff --git a/po/Story.pot b/po/Story.pot index 3c6d975..fdb35ad 100644 --- a/po/Story.pot +++ b/po/Story.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2012-05-09 16:52-0400\n" +"POT-Creation-Date: 2012-05-15 09:32-0400\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Language-Team: LANGUAGE <LL@li.org>\n" @@ -21,10 +21,43 @@ msgstr "" msgid "Story" msgstr "" -#: StoryActivity.py:110 +#: StoryActivity.py:119 msgid "Game" msgstr "" -#: StoryActivity.py:117 +#: StoryActivity.py:126 msgid "Load new images." msgstr "" + +#: StoryActivity.py:132 StoryActivity.py:190 +msgid "Save as image" +msgstr "" + +#: StoryActivity.py:138 StoryActivity.py:202 +msgid "Start recording" +msgstr "" + +#: StoryActivity.py:142 +msgid "Nothing to play" +msgstr "" + +#: StoryActivity.py:183 +msgid "image" +msgstr "" + +#: StoryActivity.py:204 +msgid "Play recording" +msgstr "" + +#: StoryActivity.py:207 +msgid "Save recording" +msgstr "" + +#: StoryActivity.py:213 +msgid "Stop recording" +msgstr "" + +#: StoryActivity.py:227 +#, python-format +msgid "audio note for %s" +msgstr "" diff --git a/po/cs.po b/po/cs.po deleted file mode 100644 index 6d77e19..0000000 --- a/po/cs.po +++ /dev/null @@ -1,63 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. -msgid "" -msgstr "" -"Project-Id-Version: PACKAGE VERSION\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2012-05-27 00:34-0400\n" -"PO-Revision-Date: 2012-07-04 15:42+0200\n" -"Last-Translator: jui <appukonrad@gmail.com>\n" -"Language-Team: LANGUAGE <LL@li.org>\n" -"Language: cs\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n" -"X-Generator: Pootle 2.0.5\n" - -#: activity/activity.info:2 -msgid "Story" -msgstr "Příběh" - -#: StoryActivity.py:121 -msgid "Game" -msgstr "Hra" - -#: StoryActivity.py:128 -msgid "Load new images." -msgstr "Nahrát nové obrázky." - -#: StoryActivity.py:134 StoryActivity.py:192 -msgid "Save as image" -msgstr "Uložit jako obrázek" - -#: StoryActivity.py:140 StoryActivity.py:204 -msgid "Start recording" -msgstr "Spustit záznam" - -#: StoryActivity.py:144 -msgid "Nothing to play" -msgstr "Nic k přehrávání" - -#: StoryActivity.py:185 -msgid "image" -msgstr "obrázek" - -#: StoryActivity.py:206 -msgid "Play recording" -msgstr "Přehrát záznam" - -#: StoryActivity.py:207 -msgid "Save recording" -msgstr "Uložit záznam" - -#: StoryActivity.py:220 -msgid "Stop recording" -msgstr "Zastavit záznam" - -#: StoryActivity.py:234 -#, python-format -msgid "audio note for %s" -msgstr "zvuková poznámka pro %s" diff --git a/po/da.po b/po/da.po deleted file mode 100644 index f953260..0000000 --- a/po/da.po +++ /dev/null @@ -1,63 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. -msgid "" -msgstr "" -"Project-Id-Version: PACKAGE VERSION\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2012-05-27 00:34-0400\n" -"PO-Revision-Date: 2012-06-08 04:34+0200\n" -"Last-Translator: Aputsiaq Niels <aj@isit.gl>\n" -"Language-Team: LANGUAGE <LL@li.org>\n" -"Language: da\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" -"X-Generator: Pootle 2.0.5\n" - -#: activity/activity.info:2 -msgid "Story" -msgstr "Historie" - -#: StoryActivity.py:121 -msgid "Game" -msgstr "Spil" - -#: StoryActivity.py:128 -msgid "Load new images." -msgstr "Indlæs nye billeder." - -#: StoryActivity.py:134 StoryActivity.py:192 -msgid "Save as image" -msgstr "Gem som billede" - -#: StoryActivity.py:140 StoryActivity.py:204 -msgid "Start recording" -msgstr "Start optagelse" - -#: StoryActivity.py:144 -msgid "Nothing to play" -msgstr "Intet af afspille" - -#: StoryActivity.py:185 -msgid "image" -msgstr "billede" - -#: StoryActivity.py:206 -msgid "Play recording" -msgstr "Afspil optagelse" - -#: StoryActivity.py:207 -msgid "Save recording" -msgstr "Gem optagelse" - -#: StoryActivity.py:220 -msgid "Stop recording" -msgstr "Stop optagelse" - -#: StoryActivity.py:234 -#, python-format -msgid "audio note for %s" -msgstr "lydnote til %s" diff --git a/po/el.po b/po/el.po deleted file mode 100644 index 7a39d67..0000000 --- a/po/el.po +++ /dev/null @@ -1,64 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. -msgid "" -msgstr "" -"Project-Id-Version: PACKAGE VERSION\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2012-05-27 00:34-0400\n" -"PO-Revision-Date: 2012-09-07 19:30+0200\n" -"Last-Translator: Yannis <kiolalis@gmail.com>\n" -"Language-Team: LANGUAGE <LL@li.org>\n" -"Language: el\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" -"X-Generator: Pootle 2.0.5\n" - -#: activity/activity.info:2 -msgid "Story" -msgstr "Πες μου μια ιστορία" - -#: StoryActivity.py:121 -msgid "Game" -msgstr "Παιχνίδι" - -#: StoryActivity.py:128 -msgid "Load new images." -msgstr "Φόρτωση νέων εικόνων" - -#: StoryActivity.py:134 StoryActivity.py:192 -msgid "Save as image" -msgstr "Αποθήκευση ως εικόνα" - -#: StoryActivity.py:140 StoryActivity.py:204 -msgid "Start recording" -msgstr "Έναρξη εγγραφής" - -#: StoryActivity.py:144 -msgid "Nothing to play" -msgstr "Τίποτε για αναπαραγωγή" - -#: StoryActivity.py:185 -msgid "image" -msgstr "εικόνα" - -#: StoryActivity.py:206 -msgid "Play recording" -msgstr "Αναπαραγωγή εγγραφής" - -#: StoryActivity.py:207 -msgid "Save recording" -msgstr "Αποθήκευση εγγραφής" - -#: StoryActivity.py:220 -msgid "Stop recording" -msgstr "Διακοπή εγγραφής" - -#: StoryActivity.py:234 -#, python-format -#, python-format, -msgid "audio note for %s" -msgstr "ηχητική σημειώση για %s" diff --git a/po/es.po b/po/es.po deleted file mode 100644 index ebb1dd4..0000000 --- a/po/es.po +++ /dev/null @@ -1,63 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. -msgid "" -msgstr "" -"Project-Id-Version: PACKAGE VERSION\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2012-05-27 00:34-0400\n" -"PO-Revision-Date: 2012-08-06 22:21+0200\n" -"Last-Translator: AlanJAS <alanjas@hotmail.com>\n" -"Language-Team: LANGUAGE <LL@li.org>\n" -"Language: es\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" -"X-Generator: Pootle 2.0.5\n" - -#: activity/activity.info:2 -msgid "Story" -msgstr "Relatar" - -#: StoryActivity.py:121 -msgid "Game" -msgstr "Juego" - -#: StoryActivity.py:128 -msgid "Load new images." -msgstr "Cargar nuevas imágenes." - -#: StoryActivity.py:134 StoryActivity.py:192 -msgid "Save as image" -msgstr "Guardar como imagen" - -#: StoryActivity.py:140 StoryActivity.py:204 -msgid "Start recording" -msgstr "Iniciar grabación" - -#: StoryActivity.py:144 -msgid "Nothing to play" -msgstr "Nada que reproducir" - -#: StoryActivity.py:185 -msgid "image" -msgstr "imagen" - -#: StoryActivity.py:206 -msgid "Play recording" -msgstr "Reproducir grabación" - -#: StoryActivity.py:207 -msgid "Save recording" -msgstr "Guardar grabación" - -#: StoryActivity.py:220 -msgid "Stop recording" -msgstr "Parar grabación" - -#: StoryActivity.py:234 -#, python-format -msgid "audio note for %s" -msgstr "nota de audio por %s" diff --git a/po/fr.po b/po/fr.po deleted file mode 100644 index 804f3a2..0000000 --- a/po/fr.po +++ /dev/null @@ -1,63 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. -#, fuzzy -msgid "" -msgstr "" -"Project-Id-Version: PACKAGE VERSION\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2012-05-27 00:34-0400\n" -"PO-Revision-Date: 2012-07-15 12:10+0200\n" -"Last-Translator: Bastien Guerry <bzg@laptop.org>\n" -"Language-Team: OLPC France <contact@olpc-france.org>\n" -"Language: french\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"X-Generator: Translate Toolkit 1.7.0\n" - -#: activity/activity.info:2 -msgid "Story" -msgstr "Histoire" - -#: StoryActivity.py:121 -msgid "Game" -msgstr "Jeu" - -#: StoryActivity.py:128 -msgid "Load new images." -msgstr "Charger de nouvelles images" - -#: StoryActivity.py:134 StoryActivity.py:192 -msgid "Save as image" -msgstr "Sauver en tant qu'image" - -#: StoryActivity.py:140 StoryActivity.py:204 -msgid "Start recording" -msgstr "Démarrer l'enregistrement" - -#: StoryActivity.py:144 -msgid "Nothing to play" -msgstr "Rien à jouer" - -#: StoryActivity.py:185 -msgid "image" -msgstr "image" - -#: StoryActivity.py:206 -msgid "Play recording" -msgstr "Jouer l'enregistrement" - -#: StoryActivity.py:207 -msgid "Save recording" -msgstr "Sauvegarder l'enregistrement" - -#: StoryActivity.py:220 -msgid "Stop recording" -msgstr "Arrêter l'enregistrement" - -#: StoryActivity.py:234 -#, python-format -msgid "audio note for %s" -msgstr "note audio pour %s" diff --git a/po/hus.po b/po/hus.po deleted file mode 100644 index 3ba16ae..0000000 --- a/po/hus.po +++ /dev/null @@ -1,63 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. -msgid "" -msgstr "" -"Project-Id-Version: PACKAGE VERSION\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2012-05-27 00:34-0400\n" -"PO-Revision-Date: 2012-10-07 22:50+0200\n" -"Last-Translator: Chris <cjl@laptop.org>\n" -"Language-Team: LANGUAGE <LL@li.org>\n" -"Language: hus\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" -"X-Generator: Pootle 2.0.5\n" - -#: activity/activity.info:2 -msgid "Story" -msgstr "t'ilmaxtalab" - -#: StoryActivity.py:121 -msgid "Game" -msgstr "ubat'intalab" - -#: StoryActivity.py:128 -msgid "Load new images." -msgstr "" - -#: StoryActivity.py:134 StoryActivity.py:192 -msgid "Save as image" -msgstr "ka dhay'a itil juni k'ot'bixtaláb" - -#: StoryActivity.py:140 StoryActivity.py:204 -msgid "Start recording" -msgstr "ka tujchij an t's'at'baxtalab" - -#: StoryActivity.py:144 -msgid "Nothing to play" -msgstr "yab jant'oj ki wat'ba'" - -#: StoryActivity.py:185 -msgid "image" -msgstr "k'ot'bixtaláb" - -#: StoryActivity.py:206 -msgid "Play recording" -msgstr "w'atba' axi ts'at'bame" - -#: StoryActivity.py:207 -msgid "Save recording" -msgstr "dhaya an ts'atbaxtalab" - -#: StoryActivity.py:220 -msgid "Stop recording" -msgstr "ka kuba' an ts'at'baxtalab" - -#: StoryActivity.py:234 -#, python-format, fuzzy -msgid "audio note for %s" -msgstr "dhúchadh ots'omtalab %s" @@ -6,8 +6,8 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2012-05-27 00:34-0400\n" -"PO-Revision-Date: 2012-08-03 07:39+0200\n" +"POT-Creation-Date: 2012-05-09 16:52-0400\n" +"PO-Revision-Date: 2012-05-28 08:35+0200\n" "Last-Translator: Chris <cjl@laptop.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n" "Language: hy\n" @@ -19,45 +19,12 @@ msgstr "" #: activity/activity.info:2 msgid "Story" -msgstr "Պատմություն" +msgstr "" -#: StoryActivity.py:121 +#: StoryActivity.py:110 msgid "Game" msgstr "Խաղ" -#: StoryActivity.py:128 +#: StoryActivity.py:117 msgid "Load new images." -msgstr "Բեռնել նոր պատկերներ" - -#: StoryActivity.py:134 StoryActivity.py:192 -msgid "Save as image" -msgstr "Պահպանել որպես պատկեր" - -#: StoryActivity.py:140 StoryActivity.py:204 -msgid "Start recording" -msgstr "Սկսել ձայնագրումը" - -#: StoryActivity.py:144 -msgid "Nothing to play" -msgstr "Նվագարկելու ոչինչ չկա" - -#: StoryActivity.py:185 -msgid "image" -msgstr "պատկեր" - -#: StoryActivity.py:206 -msgid "Play recording" -msgstr "Նվագարկել ձայնագրումը" - -#: StoryActivity.py:207 -msgid "Save recording" -msgstr "Պահպանել ձայնագրումը" - -#: StoryActivity.py:220 -msgid "Stop recording" -msgstr "Դադարեցնել ձայնագրումը" - -#: StoryActivity.py:234 -#, python-format -msgid "audio note for %s" -msgstr "աուդիո նոտա %s համար" +msgstr "" diff --git a/po/mi.po b/po/mi.po deleted file mode 100644 index a7d4dd3..0000000 --- a/po/mi.po +++ /dev/null @@ -1,62 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. -#, fuzzy -msgid "" -msgstr "" -"Project-Id-Version: PACKAGE VERSION\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2012-05-27 00:34-0400\n" -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" -"Language-Team: LANGUAGE <LL@li.org>\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"X-Generator: Translate Toolkit 1.7.0\n" - -#: activity/activity.info:2 -msgid "Story" -msgstr "" - -#: StoryActivity.py:121 -msgid "Game" -msgstr "" - -#: StoryActivity.py:128 -msgid "Load new images." -msgstr "" - -#: StoryActivity.py:134 StoryActivity.py:192 -msgid "Save as image" -msgstr "" - -#: StoryActivity.py:140 StoryActivity.py:204 -msgid "Start recording" -msgstr "" - -#: StoryActivity.py:144 -msgid "Nothing to play" -msgstr "" - -#: StoryActivity.py:185 -msgid "image" -msgstr "" - -#: StoryActivity.py:206 -msgid "Play recording" -msgstr "" - -#: StoryActivity.py:207 -msgid "Save recording" -msgstr "" - -#: StoryActivity.py:220 -msgid "Stop recording" -msgstr "" - -#: StoryActivity.py:234 -#, python-format -msgid "audio note for %s" -msgstr "" diff --git a/po/nl.po b/po/nl.po deleted file mode 100644 index 219013c..0000000 --- a/po/nl.po +++ /dev/null @@ -1,63 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. -msgid "" -msgstr "" -"Project-Id-Version: PACKAGE VERSION\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2012-05-27 00:34-0400\n" -"PO-Revision-Date: 2012-08-20 21:27+0200\n" -"Last-Translator: whe <heppew@yahoo.com>\n" -"Language-Team: LANGUAGE <LL@li.org>\n" -"Language: nl\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" -"X-Generator: Pootle 2.0.5\n" - -#: activity/activity.info:2 -msgid "Story" -msgstr "Verhaal" - -#: StoryActivity.py:121 -msgid "Game" -msgstr "Spel" - -#: StoryActivity.py:128 -msgid "Load new images." -msgstr "Laad nieuwe afbeeldingen." - -#: StoryActivity.py:134 StoryActivity.py:192 -msgid "Save as image" -msgstr "Bewaar als afbeelding" - -#: StoryActivity.py:140 StoryActivity.py:204 -msgid "Start recording" -msgstr "Start opnemen" - -#: StoryActivity.py:144 -msgid "Nothing to play" -msgstr "Niets af te spelen" - -#: StoryActivity.py:185 -msgid "image" -msgstr "afbeelding" - -#: StoryActivity.py:206 -msgid "Play recording" -msgstr "Speel opname" - -#: StoryActivity.py:207 -msgid "Save recording" -msgstr "Bewaar opname" - -#: StoryActivity.py:220 -msgid "Stop recording" -msgstr "Stop opname" - -#: StoryActivity.py:234 -#, python-format -msgid "audio note for %s" -msgstr "audio notitie voor %s" @@ -6,9 +6,9 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2012-05-27 00:34-0400\n" -"PO-Revision-Date: 2012-09-12 20:25+0200\n" -"Last-Translator: Kamila <kamilafilipowska@yahoo.com>\n" +"POT-Creation-Date: 2012-05-09 16:52-0400\n" +"PO-Revision-Date: 2012-05-28 15:32+0200\n" +"Last-Translator: Marcin <ulinski.marcin@gmail.com>\n" "Language-Team: LANGUAGE <LL@li.org>\n" "Language: pl\n" "MIME-Version: 1.0\n" @@ -22,44 +22,10 @@ msgstr "" msgid "Story" msgstr "Opowieść" -#: StoryActivity.py:121 +#: StoryActivity.py:110 msgid "Game" msgstr "Gra" -#: StoryActivity.py:128 +#: StoryActivity.py:117 msgid "Load new images." msgstr "Załaduj nowe obrazki." - -#: StoryActivity.py:134 StoryActivity.py:192 -msgid "Save as image" -msgstr "Zapisz jako obrazek" - -#: StoryActivity.py:140 StoryActivity.py:204 -msgid "Start recording" -msgstr "Zacznij nagrywanie" - -#: StoryActivity.py:144 -msgid "Nothing to play" -msgstr "Brak nagrań" - -#: StoryActivity.py:185 -msgid "image" -msgstr "obrazek" - -#: StoryActivity.py:206 -msgid "Play recording" -msgstr "Odtwórz nagranie" - -#: StoryActivity.py:207 -msgid "Save recording" -msgstr "Zapisz nagranie" - -#: StoryActivity.py:220 -msgid "Stop recording" -msgstr "Zatrzymaj nagrywanie" - -#: StoryActivity.py:234 -#, python-format -#, python-format, fuzzy -msgid "audio note for %s" -msgstr "notatka dźwiękowa dla %s" diff --git a/po/quz.po b/po/quz.po deleted file mode 100644 index d4b18ca..0000000 --- a/po/quz.po +++ /dev/null @@ -1,64 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. -msgid "" -msgstr "" -"Project-Id-Version: PACKAGE VERSION\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2012-05-27 00:34-0400\n" -"PO-Revision-Date: 2012-07-23 07:31+0200\n" -"Last-Translator: Chris <cjl@laptop.org>\n" -"Language-Team: LANGUAGE <LL@li.org>\n" -"Language: quz\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=2; plural=(n % 10 == 1 && n % 100 != 11) ? 0 : 1;\n" -"X-Generator: Pootle 2.0.5\n" - -#: activity/activity.info:2 -msgid "Story" -msgstr "" - -#: StoryActivity.py:121 -msgid "Game" -msgstr "" - -#: StoryActivity.py:128 -msgid "Load new images." -msgstr "" - -#: StoryActivity.py:134 StoryActivity.py:192 -msgid "Save as image" -msgstr "" - -#: StoryActivity.py:140 StoryActivity.py:204 -msgid "Start recording" -msgstr "" - -#: StoryActivity.py:144 -msgid "Nothing to play" -msgstr "" - -#: StoryActivity.py:185 -#, fuzzy -msgid "image" -msgstr "wanki" - -#: StoryActivity.py:206 -msgid "Play recording" -msgstr "" - -#: StoryActivity.py:207 -msgid "Save recording" -msgstr "" - -#: StoryActivity.py:220 -msgid "Stop recording" -msgstr "" - -#: StoryActivity.py:234 -#, python-format -msgid "audio note for %s" -msgstr "" diff --git a/po/zh_CN.po b/po/zh_CN.po deleted file mode 100644 index f78a721..0000000 --- a/po/zh_CN.po +++ /dev/null @@ -1,64 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. -msgid "" -msgstr "" -"Project-Id-Version: PACKAGE VERSION\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2012-05-27 00:34-0400\n" -"PO-Revision-Date: 2012-07-03 13:07+0200\n" -"Last-Translator: athurg <feng@jianbo.de>\n" -"Language-Team: LANGUAGE <LL@li.org>\n" -"Language: zh_CN\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=1; plural=0;\n" -"X-Generator: Pootle 2.0.5\n" - -#: activity/activity.info:2 -msgid "Story" -msgstr "故事" - -#: StoryActivity.py:121 -msgid "Game" -msgstr "游戏" - -#: StoryActivity.py:128 -msgid "Load new images." -msgstr "加载新图像。" - -#: StoryActivity.py:134 StoryActivity.py:192 -msgid "Save as image" -msgstr "保存为图像" - -#: StoryActivity.py:140 StoryActivity.py:204 -msgid "Start recording" -msgstr "开始录制" - -#: StoryActivity.py:144 -msgid "Nothing to play" -msgstr "没有可播放对象" - -#: StoryActivity.py:185 -msgid "image" -msgstr "图像" - -# 或许为播放录音? -#: StoryActivity.py:206 -msgid "Play recording" -msgstr "播放录音" - -#: StoryActivity.py:207 -msgid "Save recording" -msgstr "保存录音" - -#: StoryActivity.py:220 -msgid "Stop recording" -msgstr "停止录制" - -#: StoryActivity.py:234 -#, python-format -msgid "audio note for %s" -msgstr "%s 的录制备注" @@ -24,7 +24,7 @@ ''' sprites.py is a simple sprites library for managing graphics objects, -'sprites', on a gtk.DrawingArea. It manages multiple sprites with +'sprites', on a Gtk.DrawingArea. It manages multiple sprites with methods such as move, hide, set_layer, etc. There are two classes: @@ -68,7 +68,7 @@ Example usage: # method for converting SVG to a gtk pixbuf def svg_str_to_pixbuf(svg_string): - pl = gtk.gdk.PixbufLoader('svg') + pl = GdkPixbuf.PixbufLoader('svg') pl.write(svg_string) pl.close() pixbuf = pl.get_pixbuf() @@ -76,12 +76,10 @@ def svg_str_to_pixbuf(svg_string): ''' -import pygtk -pygtk.require('2.0') -import gtk -import pango -import pangocairo -import cairo +import gi +from gi.repository import Gtk, GdkPixbuf, Gdk +from gi.repository import Pango, PangoCairo + class Sprites: ''' A class for the list of sprites and everything they share in common ''' @@ -90,7 +88,6 @@ class Sprites: ''' Initialize an empty array of sprites ''' self.widget = widget self.list = [] - self.cr = None def set_cairo_context(self, cr): ''' Cairo context may be set or reset after __init__ ''' @@ -98,7 +95,7 @@ class Sprites: def get_sprite(self, i): ''' Return a sprint from the array ''' - if i < 0 or i > len(self.list) - 1: + if i < 0 or i > len(self.list)-1: return(None) else: return(self.list[i]) @@ -125,11 +122,10 @@ class Sprites: if spr in self.list: self.list.remove(spr) - def find_sprite(self, pos, inverse=False): + def find_sprite(self, pos): ''' Search based on (x, y) position. Return the 'top/first' one. ''' list = self.list[:] - if not inverse: - list.reverse() + list.reverse() for spr in list: if spr.hit(pos): return spr @@ -160,8 +156,7 @@ class Sprite: def __init__(self, sprites, x, y, image): ''' Initialize an individual sprite ''' self._sprites = sprites - self.save_xy = (x, y) # remember initial (x, y) position - self.rect = gtk.gdk.Rectangle(int(x), int(y), 0, 0) + self.rect = [int(x), int(y), 0, 0] self._scale = [12] self._rescale = [True] self._horiz_align = ["center"] @@ -173,7 +168,7 @@ class Sprite: self._margins = [0, 0, 0, 0] self.layer = 100 self.labels = [] - self.cached_surfaces = [] + self.images = [] self._dx = [] # image offsets self._dy = [] self.type = None @@ -182,58 +177,47 @@ class Sprite: def set_image(self, image, i=0, dx=0, dy=0): ''' Add an image to the sprite. ''' - while len(self.cached_surfaces) < i + 1: - self.cached_surfaces.append(None) + while len(self.images) < i + 1: + self.images.append(None) self._dx.append(0) self._dy.append(0) + self.images[i] = image self._dx[i] = dx self._dy[i] = dy - if isinstance(image, gtk.gdk.Pixbuf) or \ - isinstance(image, cairo.ImageSurface): - w = image.get_width() - h = image.get_height() + if isinstance(self.images[i], GdkPixbuf.Pixbuf): + w = self.images[i].get_width() + h = self.images[i].get_height() else: - w, h = image.get_size() + w, h = self.images[i].get_size() if i == 0: # Always reset width and height when base image changes. - self.rect.width = w + dx - self.rect.height = h + dy + self.rect[2] = w + dx + self.rect[3] = h + dy else: - if w + dx > self.rect.width: - self.rect.width = w + dx - if h + dy > self.rect.height: - self.rect.height = h + dy - if isinstance(image, cairo.ImageSurface): - self.cached_surfaces[i] = image - else: # Convert to Cairo surface - surface = cairo.ImageSurface( - cairo.FORMAT_ARGB32, self.rect.width, self.rect.height) - context = cairo.Context(surface) - context = gtk.gdk.CairoContext(context) - context.set_source_pixbuf(image, 0, 0) - context.rectangle(0, 0, self.rect.width, self.rect.height) - context.fill() - self.cached_surfaces[i] = surface + if w + dx > self.rect[2]: + self.rect[2] = w + dx + if h + dy > self.rect[3]: + self.rect[3] = h + dy def move(self, pos): ''' Move to new (x, y) position ''' self.inval() - self.rect.x, self.rect.y = int(pos[0]), int(pos[1]) + self.rect[0], self.rect[1] = int(pos[0]), int(pos[1]) self.inval() def move_relative(self, pos): ''' Move to new (x+dx, y+dy) position ''' self.inval() - self.rect.x += int(pos[0]) - self.rect.y += int(pos[1]) + self.rect[0] += int(pos[0]) + self.rect[1] += int(pos[1]) self.inval() def get_xy(self): ''' Return current (x, y) position ''' - return (self.rect.x, self.rect.y) + return (self.rect[0], self.rect[1]) def get_dimensions(self): ''' Return current size ''' - return (self.rect.width, self.rect.height) + return (self.rect[2], self.rect[3]) def get_layer(self): ''' Return current layer ''' @@ -245,11 +229,10 @@ class Sprite: self.set_image(image, i) self.inval() - def set_layer(self, layer=None): + def set_layer(self, layer): ''' Set the layer for a sprite ''' self._sprites.remove_from_list(self) - if layer is not None: - self.layer = layer + self.layer = layer for i in range(self._sprites.length_of_list()): if layer < self._sprites.get_sprite(i).layer: self._sprites.insert_in_list(self, i) @@ -277,7 +260,7 @@ class Sprite: if self._fd is None: self.set_font('Sans') if self._color is None: - self._color = (0., 0., 0.) + self._color = (0.5, 0.5, 0.5) while len(self.labels) < i + 1: self.labels.append(" ") self._scale.append(self._scale[0]) @@ -287,7 +270,7 @@ class Sprite: def set_font(self, font): ''' Set the font for a label ''' - self._fd = pango.FontDescription(font) + self._fd = Pango.FontDescription(font) def set_label_color(self, rgb): ''' Set the font color for a label ''' @@ -318,142 +301,136 @@ class Sprite: self.inval() self._sprites.remove_from_list(self) - def restore(self): - ''' Restore a hidden sprite ''' - self.set_layer() - def inval(self): ''' Invalidate a region for gtk ''' - self._sprites.widget.queue_draw_area(self.rect.x, - self.rect.y, - self.rect.width, - self.rect.height) + # self._sprites.window.invalidate_rect(self.rect, False) + self._sprites.widget.queue_draw_area(self.rect[0], + self.rect[1], + self.rect[2], + self.rect[3]) def draw(self, cr=None): ''' Draw the sprite (and label) ''' if cr is None: + cr = self._sprites.cr + if cr is None: print 'sprite.draw: no Cairo context.' return - for i, surface in enumerate(self.cached_surfaces): - cr.set_source_surface(surface, - self.rect.x + self._dx[i], - self.rect.y + self._dy[i]) - cr.rectangle(self.rect.x + self._dx[i], - self.rect.y + self._dy[i], - self.rect.width, - self.rect.height) - cr.fill() + for i, img in enumerate(self.images): + if isinstance(img, GdkPixbuf.Pixbuf): + Gdk.cairo_set_source_pixbuf(cr, img, + self.rect[0] + self._dx[i], + self.rect[1] + self._dy[i]) + cr.rectangle(self.rect[0] + self._dx[i], + self.rect[1] + self._dy[i], + self.rect[2], + self.rect[3]) + cr.fill() + else: + print 'sprite.draw: source not a pixbuf (%s)' % (type(img)) if len(self.labels) > 0: self.draw_label(cr) def hit(self, pos): ''' Is (x, y) on top of the sprite? ''' x, y = pos - if x < self.rect.x: + if x < self.rect[0]: return False - if x > self.rect.x + self.rect.width: + if x > self.rect[0] + self.rect[2]: return False - if y < self.rect.y: + if y < self.rect[1]: return False - if y > self.rect.y + self.rect.height: + if y > self.rect[1] + self.rect[3]: return False return True def draw_label(self, cr): ''' Draw the label based on its attributes ''' - # Create a pangocairo context - cr = pangocairo.CairoContext(cr) - my_width = self.rect.width - self._margins[0] - self._margins[2] + my_width = self.rect[2] - self._margins[0] - self._margins[2] if my_width < 0: my_width = 0 - my_height = self.rect.height - self._margins[1] - self._margins[3] + my_height = self.rect[3] - self._margins[1] - self._margins[3] for i in range(len(self.labels)): - pl = cr.create_layout() - pl.set_text(str(self.labels[i])) - self._fd.set_size(int(self._scale[i] * pango.SCALE)) + pl = PangoCairo.create_layout(cr) + pl.set_text(str(self.labels[i]), -1) + self._fd.set_size(int(self._scale[i] * Pango.SCALE)) pl.set_font_description(self._fd) - w = pl.get_size()[0] / pango.SCALE + w = pl.get_size()[0] / Pango.SCALE if w > my_width: if self._rescale[i]: self._fd.set_size( - int(self._scale[i] * pango.SCALE * my_width / w)) + int(self._scale[i] * Pango.SCALE * my_width / w)) pl.set_font_description(self._fd) - w = pl.get_size()[0] / pango.SCALE + w = pl.get_size()[0] / Pango.SCALE else: j = len(self.labels[i]) - 1 while(w > my_width and j > 0): pl.set_text( - "…" + self.labels[i][len(self.labels[i]) - j:]) - self._fd.set_size(int(self._scale[i] * pango.SCALE)) + "…" + self.labels[i][len(self.labels[i]) - j:], -1) + self._fd.set_size(int(self._scale[i] * Pango.SCALE)) pl.set_font_description(self._fd) - w = pl.get_size()[0] / pango.SCALE + w = pl.get_size()[0] / Pango.SCALE j -= 1 if self._horiz_align[i] == "center": - x = int(self.rect.x + self._margins[0] + (my_width - w) / 2) + x = int(self.rect[0] + self._margins[0] + (my_width - w) / 2) elif self._horiz_align[i] == 'left': - x = int(self.rect.x + self._margins[0]) + x = int(self.rect[0] + self._margins[0]) else: # right - x = int(self.rect.x + self.rect.width - w - self._margins[2]) - h = pl.get_size()[1] / pango.SCALE + x = int(self.rect[0] + self.rect[2] - w - self._margins[2]) + h = pl.get_size()[1] / Pango.SCALE if self._vert_align[i] == "middle": - y = int(self.rect.y + self._margins[1] + (my_height - h) / 2) + y = int(self.rect[1] + self._margins[1] + (my_height - h) / 2) elif self._vert_align[i] == "top": - y = int(self.rect.y + self._margins[1]) + y = int(self.rect[1] + self._margins[1]) else: # bottom - y = int(self.rect.y + self.rect.height - h - self._margins[3]) + y = int(self.rect[1] + self.rect[3] - h - self._margins[3]) cr.save() cr.translate(x, y) cr.set_source_rgb(self._color[0], self._color[1], self._color[2]) - cr.update_layout(pl) - cr.show_layout(pl) + PangoCairo.update_layout(cr, pl) + PangoCairo.show_layout(cr, pl) cr.restore() def label_width(self): ''' Calculate the width of a label ''' - cr = pangocairo.CairoContext(self._sprites.cr) - if cr is not None: - max = 0 - for i in range(len(self.labels)): - pl = cr.create_layout() - pl.set_text(self.labels[i]) - self._fd.set_size(int(self._scale[i] * pango.SCALE)) - pl.set_font_description(self._fd) - w = pl.get_size()[0] / pango.SCALE - if w > max: - max = w - return max - else: - return self.rect.width + max = 0 + for i in range(len(self.labels)): + pl = self._sprites.canvas.create_pango_layout(self.labels[i]) + self._fd.set_size(int(self._scale[i] * Pango.SCALE)) + pl.set_font_description(self._fd) + w = pl.get_size()[0] / Pango.SCALE + if w > max: + max = w + return max def label_safe_width(self): ''' Return maximum width for a label ''' - return self.rect.width - self._margins[0] - self._margins[2] + return self.rect[2] - self._margins[0] - self._margins[2] def label_safe_height(self): ''' Return maximum height for a label ''' - return self.rect.height - self._margins[1] - self._margins[3] + return self.rect[3] - self._margins[1] - self._margins[3] def label_left_top(self): ''' Return the upper-left corner of the label safe zone ''' return(self._margins[0], self._margins[1]) def get_pixel(self, pos, i=0): - ''' Return the pixel at (x, y) ''' - x = int(pos[0] - self.rect.x) - y = int(pos[1] - self.rect.y) - if x < 0 or x > (self.rect.width - 1) or \ - y < 0 or y > (self.rect.height - 1): + ''' Return the pixl at (x, y) ''' + x, y = pos + x = x - self.rect[0] + y = y - self.rect[1] + if y > self.images[i].get_height() - 1: + return(-1, -1, -1, -1) + try: + array = self.images[i].get_pixels() + if array is not None: + offset = (y * self.images[i].get_width() + x) * 4 + r, g, b, a = ord(array[offset]), ord(array[offset + 1]),\ + ord(array[offset + 2]), ord(array[offset + 3]) + return(r, g, b, a) + else: + return(-1, -1, -1, -1) + except IndexError: + print "Index Error: %d %d" % (len(array), offset) return(-1, -1, -1, -1) - - # create a new 1x1 cairo surface - cs = cairo.ImageSurface(cairo.FORMAT_RGB24, 1, 1); - cr = cairo.Context(cs) - cr.set_source_surface(self.cached_surfaces[i], -x, -y) - cr.rectangle(0,0,1,1) - cr.set_operator(cairo.OPERATOR_SOURCE) - cr.fill() - cs.flush() # ensure all writing is done - # Read the pixel - pixels = cs.get_data() - return (ord(pixels[2]), ord(pixels[1]), ord(pixels[0]), 0) - diff --git a/sprites.pyo b/sprites.pyo Binary files differnew file mode 100644 index 0000000..1132095 --- /dev/null +++ b/sprites.pyo diff --git a/toolbar_utils.py b/toolbar_utils.py index 94e6883..ef2382d 100644 --- a/toolbar_utils.py +++ b/toolbar_utils.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- # Copyright (c) 2011, Walter Bender - +# Port To GTK3: +# Ignacio Rodriguez <ignaciorodriguez@sugarlabs.org> # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or @@ -11,12 +12,12 @@ # Foundation, 51 Franklin Street, Suite 500 Boston, MA 02110-1335 USA -import gtk +from gi.repository import Gtk -from sugar.graphics.radiotoolbutton import RadioToolButton -from sugar.graphics.toolbutton import ToolButton -from sugar.graphics.combobox import ComboBox -from sugar.graphics.toolcombobox import ToolComboBox +from sugar3.graphics.radiotoolbutton import RadioToolButton +from sugar3.graphics.toolbutton import ToolButton +from sugar3.graphics.combobox import ComboBox +from sugar3.graphics.toolcombobox import ToolComboBox def combo_factory(combo_array, toolbar, callback, cb_arg=None, @@ -32,7 +33,7 @@ def combo_factory(combo_array, toolbar, callback, cb_arg=None, for i, selection in enumerate(combo_array): combo.append_item(i, selection, None) combo.show() - toolitem = gtk.ToolItem() + toolitem = Gtk.ToolItem() toolitem.add(combo) if hasattr(toolbar, 'insert'): # the main toolbar toolbar.insert(toolitem, -1) @@ -46,13 +47,13 @@ def combo_factory(combo_array, toolbar, callback, cb_arg=None, def entry_factory(default_string, toolbar, tooltip=None, max=3): ''' Factory for adding a text box to a toolbar ''' - entry = gtk.Entry() + entry = Gtk.Entry() entry.set_text(default_string) if tooltip is not None and hasattr(entry, 'set_tooltip_text'): entry.set_tooltip_text(tooltip) entry.set_width_chars(max) entry.show() - toolitem = gtk.ToolItem() + toolitem = Gtk.ToolItem() toolitem.add(entry) if hasattr(toolbar, 'insert'): # the main toolbar toolbar.insert(toolitem, -1) @@ -105,12 +106,12 @@ def radio_factory(name, toolbar, callback, cb_arg=None, tooltip=None, def label_factory(toolbar, label_text, width=None): ''' Factory for adding a label to a toolbar ''' - label = gtk.Label(label_text) + label = Gtk.Label(label_text) label.set_line_wrap(True) if width is not None: label.set_size_request(width, -1) # doesn't work on XOs label.show() - toolitem = gtk.ToolItem() + toolitem = Gtk.ToolItem() toolitem.add(label) if hasattr(toolbar, 'insert'): # the main toolbar toolbar.insert(toolitem, -1) @@ -122,7 +123,7 @@ def label_factory(toolbar, label_text, width=None): def separator_factory(toolbar, expand=False, visible=True): ''' add a separator to a toolbar ''' - separator = gtk.SeparatorToolItem() + separator = Gtk.SeparatorToolItem() separator.props.draw = visible separator.set_expand(expand) if hasattr(toolbar, 'insert'): # the main toolbar @@ -134,9 +135,9 @@ def separator_factory(toolbar, expand=False, visible=True): def image_factory(image, toolbar, tooltip=None): ''' Add an image to the toolbar ''' - img = gtk.Image() + img = Gtk.Image() img.set_from_pixbuf(image) - img_tool = gtk.ToolItem() + img_tool = Gtk.ToolItem() img_tool.add(img) if tooltip is not None: img.set_tooltip_text(tooltip) @@ -149,12 +150,12 @@ def image_factory(image, toolbar, tooltip=None): def spin_factory(default, min, max, callback, toolbar): - spin_adj = gtk.Adjustment(default, min, max, 1, 32, 0) - spin = gtk.SpinButton(spin_adj, 0, 0) + spin_adj = Gtk.Adjustment(default, min, max, 1, 32, 0) + spin = Gtk.SpinButton(spin_adj, 0, 0) spin_id = spin.connect('value-changed', callback) spin.set_numeric(True) spin.show() - toolitem = gtk.ToolItem() + toolitem = Gtk.ToolItem() toolitem.add(spin) if hasattr(toolbar, 'insert'): # the main toolbar toolbar.insert(toolitem, -1) diff --git a/toolbar_utils.pyo b/toolbar_utils.pyo Binary files differnew file mode 100644 index 0000000..8bd3748 --- /dev/null +++ b/toolbar_utils.pyo diff --git a/toolbar_utils.py~ b/toolbar_utils.py~ new file mode 100644 index 0000000..e35f67b --- /dev/null +++ b/toolbar_utils.py~ @@ -0,0 +1,436 @@ +# -*- coding: utf-8 -*- + +#Copyright (c) 2007-8, Playful Invention Company. +#Copyright (c) 2008-11 Walter Bender + +#Permission is hereby granted, free of charge, to any person obtaining a copy +#of this software and associated documentation files (the "Software"), to deal +#in the Software without restriction, including without limitation the rights +#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +#copies of the Software, and to permit persons to whom the Software is +#furnished to do so, subject to the following conditions: + +#The above copyright notice and this permission notice shall be included in +#all copies or substantial portions of the Software. + +#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +#THE SOFTWARE. + +''' + +sprites.py is a simple sprites library for managing graphics objects, +'sprites', on a Gtk.DrawingArea. It manages multiple sprites with +methods such as move, hide, set_layer, etc. + +There are two classes: + +class Sprites maintains a collection of sprites +class Sprite manages individual sprites within the collection. + +Example usage: + # Import the classes into your program. + from sprites import Sprites Sprite + + # Create a new sprite collection associated with your widget + self.sprite_list = Sprites(widget) + + # Create a "pixbuf" (in this example, from SVG). + my_pixbuf = svg_str_to_pixbuf("<svg>...some svg code...</svg>") + + # Create a sprite at position x1, y1. + my_sprite = sprites.Sprite(self.sprite_list, x1, y1, my_pixbuf) + + # Move the sprite to a new position. + my_sprite.move((x1+dx, y1+dy)) + + # Create another "pixbuf". + your_pixbuf = svg_str_to_pixbuf("<svg>...some svg code...</svg>") + + # Create a sprite at position x2, y2. + your_sprite = sprites.Sprite(self.sprite_list, x2, y2, my_pixbuf) + + # Assign the sprites to layers. + # In this example, your_sprite will be on top of my_sprite. + my_sprite.set_layer(100) + your_sprite.set_layer(200) + + # Now put my_sprite on top of your_sprite. + my_sprite.set_layer(300) + + cr = self.window.cairo_create() + # In your activity's do_expose_event, put in a call to redraw_sprites + self.sprites.redraw_sprites(event.area, cairo_context) + +# method for converting SVG to a gtk pixbuf +def svg_str_to_pixbuf(svg_string): + pl = GdkPixbuf.PixbufLoader('svg') + pl.write(svg_string) + pl.close() + pixbuf = pl.get_pixbuf() + return pixbuf + +''' + +import gi +from gi.repository import Gtk, GdkPixbuf, Gdk +from gi.repository import Pango, PangoCairo + + +class Sprites: + ''' A class for the list of sprites and everything they share in common ''' + + def __init__(self, widget): + ''' Initialize an empty array of sprites ''' + self.widget = widget + self.list = [] + + def set_cairo_context(self, cr): + ''' Cairo context may be set or reset after __init__ ''' + self.cr = cr + + def get_sprite(self, i): + ''' Return a sprint from the array ''' + if i < 0 or i > len(self.list)-1: + return(None) + else: + return(self.list[i]) + + def length_of_list(self): + ''' How many sprites are there? ''' + return(len(self.list)) + + def append_to_list(self, spr): + ''' Append a new sprite to the end of the list. ''' + self.list.append(spr) + + def insert_in_list(self, spr, i): + ''' Insert a sprite at position i. ''' + if i < 0: + self.list.insert(0, spr) + elif i > len(self.list) - 1: + self.list.append(spr) + else: + self.list.insert(i, spr) + + def remove_from_list(self, spr): + ''' Remove a sprite from the list. ''' + if spr in self.list: + self.list.remove(spr) + + def find_sprite(self, pos): + ''' Search based on (x, y) position. Return the 'top/first' one. ''' + list = self.list[:] + list.reverse() + for spr in list: + if spr.hit(pos): + return spr + return None + + def redraw_sprites(self, area=None, cr=None): + ''' Redraw the sprites that intersect area. ''' + # I think I need to do this to save Cairo some work + if cr is None: + cr = self.cr + else: + self.cr = cr + if cr is None: + print 'sprites.redraw_sprites: no Cairo context' + return + for spr in self.list: + if area == None: + spr.draw(cr=cr) + else: + intersection = spr.rect.intersect(area) + if intersection.width > 0 or intersection.height > 0: + spr.draw(cr=cr) + + +class Sprite: + ''' A class for the individual sprites ''' + + def __init__(self, sprites, x, y, image): + ''' Initialize an individual sprite ''' + self._sprites = sprites + self.rect = [int(x), int(y), 0, 0] + self._scale = [12] + self._rescale = [True] + self._horiz_align = ["center"] + self._vert_align = ["middle"] + self._fd = None + self._bold = False + self._italic = False + self._color = None + self._margins = [0, 0, 0, 0] + self.layer = 100 + self.labels = [] + self.images = [] + self._dx = [] # image offsets + self._dy = [] + self.type = None + self.set_image(image) + self._sprites.append_to_list(self) + + def set_image(self, image, i=0, dx=0, dy=0): + ''' Add an image to the sprite. ''' + while len(self.images) < i + 1: + self.images.append(None) + self._dx.append(0) + self._dy.append(0) + self.images[i] = image + self._dx[i] = dx + self._dy[i] = dy + if isinstance(self.images[i], GdkPixbuf.Pixbuf): + w = self.images[i].get_width() + h = self.images[i].get_height() + else: + w, h = self.images[i].get_size() + if i == 0: # Always reset width and height when base image changes. + self.rect[2] = w + dx + self.rect[3] = h + dy + else: + if w + dx > self.rect[2]: + self.rect[2] = w + dx + if h + dy > self.rect[3]: + self.rect[3] = h + dy + + def move(self, pos): + ''' Move to new (x, y) position ''' + self.inval() + self.rect[0], self.rect[1] = int(pos[0]), int(pos[1]) + self.inval() + + def move_relative(self, pos): + ''' Move to new (x+dx, y+dy) position ''' + self.inval() + self.rect[0] += int(pos[0]) + self.rect[1] += int(pos[1]) + self.inval() + + def get_xy(self): + ''' Return current (x, y) position ''' + return (self.rect[0], self.rect[1]) + + def get_dimensions(self): + ''' Return current size ''' + return (self.rect[2], self.rect[3]) + + def get_layer(self): + ''' Return current layer ''' + return self.layer + + def set_shape(self, image, i=0): + ''' Set the current image associated with the sprite ''' + self.inval() + self.set_image(image, i) + self.inval() + + def set_layer(self, layer): + ''' Set the layer for a sprite ''' + self._sprites.remove_from_list(self) + self.layer = layer + for i in range(self._sprites.length_of_list()): + if layer < self._sprites.get_sprite(i).layer: + self._sprites.insert_in_list(self, i) + self.inval() + return + self._sprites.append_to_list(self) + self.inval() + + def set_label(self, new_label, i=0): + ''' Set the label drawn on the sprite ''' + self._extend_labels_array(i) + if type(new_label) is str or type(new_label) is unicode: + # pango doesn't like nulls + self.labels[i] = new_label.replace("\0", " ") + else: + self.labels[i] = str(new_label) + self.inval() + + def set_margins(self, l=0, t=0, r=0, b=0): + ''' Set the margins for drawing the label ''' + self._margins = [l, t, r, b] + + def _extend_labels_array(self, i): + ''' Append to the labels attribute list ''' + if self._fd is None: + self.set_font('Sans') + if self._color is None: + self._color = (0.5, 0.5, 0.5) + while len(self.labels) < i + 1: + self.labels.append(" ") + self._scale.append(self._scale[0]) + self._rescale.append(self._rescale[0]) + self._horiz_align.append(self._horiz_align[0]) + self._vert_align.append(self._vert_align[0]) + + def set_font(self, font): + ''' Set the font for a label ''' + self._fd = Pango.FontDescription(font) + + def set_label_color(self, rgb): + ''' Set the font color for a label ''' + COLORTABLE = {'black': '#000000', 'white': '#FFFFFF', + 'red': '#FF0000', 'yellow': '#FFFF00', + 'green': '#00FF00', 'cyan': '#00FFFF', + 'blue': '#0000FF', 'purple': '#FF00FF', + 'gray': '#808080'} + if rgb.lower() in COLORTABLE: + rgb = COLORTABLE[rgb.lower()] + # Convert from '#RRGGBB' to floats + self._color = (int('0x' + rgb[1:3], 16) / 256., + int('0x' + rgb[3:5], 16) / 256., + int('0x' + rgb[5:7], 16) / 256.) + return + + def set_label_attributes(self, scale, rescale=True, horiz_align="center", + vert_align="middle", i=0): + ''' Set the various label attributes ''' + self._extend_labels_array(i) + self._scale[i] = scale + self._rescale[i] = rescale + self._horiz_align[i] = horiz_align + self._vert_align[i] = vert_align + + def hide(self): + ''' Hide a sprite ''' + self.inval() + self._sprites.remove_from_list(self) + + def inval(self): + ''' Invalidate a region for gtk ''' + # self._sprites.window.invalidate_rect(self.rect, False) + self._sprites.widget.queue_draw_area(self.rect[0], + self.rect[1], + self.rect[2], + self.rect[3]) + + def draw(self, cr=None): + ''' Draw the sprite (and label) ''' + if cr is None: + cr = self._sprites.cr + if cr is None: + print 'sprite.draw: no Cairo context.' + return + for i, img in enumerate(self.images): + if isinstance(img, GdkPixbuf.Pixbuf): + Gdk.cairo_set_source_pixbuf(cr, img, + self.rect[0] + self._dx[i], + self.rect[1] + self._dy[i]) + cr.rectangle(self.rect[0] + self._dx[i], + self.rect[1] + self._dy[i], + self.rect[2], + self.rect[3]) + cr.fill() + else: + print 'sprite.draw: source not a pixbuf (%s)' % (type(img)) + if len(self.labels) > 0: + self.draw_label(cr) + + def hit(self, pos): + ''' Is (x, y) on top of the sprite? ''' + x, y = pos + if x < self.rect[0]: + return False + if x > self.rect[0] + self.rect[2]: + return False + if y < self.rect[1]: + return False + if y > self.rect[1] + self.rect[3]: + return False + return True + + def draw_label(self, cr): + ''' Draw the label based on its attributes ''' + my_width = self.rect[2] - self._margins[0] - self._margins[2] + if my_width < 0: + my_width = 0 + my_height = self.rect[3] - self._margins[1] - self._margins[3] + for i in range(len(self.labels)): + pl = PangoCairo.create_layout(cr) + pl.set_text(str(self.labels[i]), -1) + self._fd.set_size(int(self._scale[i] * Pango.SCALE)) + pl.set_font_description(self._fd) + w = pl.get_size()[0] / Pango.SCALE + if w > my_width: + if self._rescale[i]: + self._fd.set_size( + int(self._scale[i] * Pango.SCALE * my_width / w)) + pl.set_font_description(self._fd) + w = pl.get_size()[0] / Pango.SCALE + else: + j = len(self.labels[i]) - 1 + while(w > my_width and j > 0): + pl.set_text( + "…" + self.labels[i][len(self.labels[i]) - j:], -1) + self._fd.set_size(int(self._scale[i] * Pango.SCALE)) + pl.set_font_description(self._fd) + w = pl.get_size()[0] / Pango.SCALE + j -= 1 + if self._horiz_align[i] == "center": + x = int(self.rect[0] + self._margins[0] + (my_width - w) / 2) + elif self._horiz_align[i] == 'left': + x = int(self.rect[0] + self._margins[0]) + else: # right + x = int(self.rect[0] + self.rect[2] - w - self._margins[2]) + h = pl.get_size()[1] / Pango.SCALE + if self._vert_align[i] == "middle": + y = int(self.rect[1] + self._margins[1] + (my_height - h) / 2) + elif self._vert_align[i] == "top": + y = int(self.rect[1] + self._margins[1]) + else: # bottom + y = int(self.rect[1] + self.rect[3] - h - self._margins[3]) + cr.save() + cr.translate(x, y) + cr.set_source_rgb(self._color[0], self._color[1], self._color[2]) + PangoCairo.update_layout(cr, pl) + PangoCairo.show_layout(cr, pl) + cr.restore() + + def label_width(self): + ''' Calculate the width of a label ''' + max = 0 + for i in range(len(self.labels)): + pl = self._sprites.canvas.create_pango_layout(self.labels[i]) + self._fd.set_size(int(self._scale[i] * Pango.SCALE)) + pl.set_font_description(self._fd) + w = pl.get_size()[0] / Pango.SCALE + if w > max: + max = w + return max + + def label_safe_width(self): + ''' Return maximum width for a label ''' + return self.rect[2] - self._margins[0] - self._margins[2] + + def label_safe_height(self): + ''' Return maximum height for a label ''' + return self.rect[3] - self._margins[1] - self._margins[3] + + def label_left_top(self): + ''' Return the upper-left corner of the label safe zone ''' + return(self._margins[0], self._margins[1]) + + def get_pixel(self, pos, i=0): + ''' Return the pixl at (x, y) ''' + x, y = pos + x = x - self.rect[0] + y = y - self.rect[1] + if y > self.images[i].get_height() - 1: + return(-1, -1, -1, -1) + try: + array = self.images[i].get_pixels() + if array is not None: + offset = (y * self.images[i].get_width() + x) * 4 + r, g, b, a = ord(array[offset]), ord(array[offset + 1]),\ + ord(array[offset + 2]), ord(array[offset + 3]) + return(r, g, b, a) + else: + return(-1, -1, -1, -1) + except IndexError: + print "Index Error: %d %d" % (len(array), offset) + return(-1, -1, -1, -1) diff --git a/utils.pyo b/utils.pyo Binary files differnew file mode 100644 index 0000000..94b1def --- /dev/null +++ b/utils.pyo |