Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJuan 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)
commit429d3d501697d97869ac1ab38c90ed1d9d58c1ab (patch)
tree5aa147d9867475d0550cdb371753ec1dd481a806
parent5263c449bbfc2e165f3c3d403510d352d2aa8326 (diff)
Port to GTK3!
-rw-r--r--NEWS4
-rw-r--r--StoryActivity.py35
-rw-r--r--StoryActivity.pyobin0 -> 14802 bytes
-rw-r--r--StoryActivity.py~405
-rw-r--r--game.py27
-rw-r--r--game.pyobin0 -> 9460 bytes
-rw-r--r--game.py~268
-rw-r--r--grecord.py12
-rw-r--r--grecord.pyobin0 -> 7704 bytes
-rw-r--r--gtk2/StoryActivity.py404
-rw-r--r--gtk2/game.py265
-rw-r--r--gtk2/grecord.py244
-rw-r--r--gtk2/setup.py32
-rw-r--r--gtk2/sprites.py459
-rw-r--r--gtk2/toolbar_utils.py164
-rw-r--r--gtk2/toolbar_utils.py~165
-rw-r--r--gtk2/utils.py61
-rw-r--r--locale/en/LC_MESSAGES/org.sugarlabs.StoryActivity.mobin0 -> 964 bytes
-rw-r--r--locale/en/activity.linfo2
-rw-r--r--locale/en_GB/LC_MESSAGES/org.sugarlabs.StoryActivity.mobin0 -> 967 bytes
-rw-r--r--locale/en_GB/activity.linfo2
-rw-r--r--locale/en_US/LC_MESSAGES/org.sugarlabs.StoryActivity.mobin0 -> 967 bytes
-rw-r--r--locale/en_US/activity.linfo2
-rw-r--r--locale/es/LC_MESSAGES/org.sugarlabs.StoryActivity.mobin0 -> 994 bytes
-rw-r--r--locale/es/activity.linfo2
-rw-r--r--locale/hy/LC_MESSAGES/org.sugarlabs.StoryActivity.mobin0 -> 481 bytes
-rw-r--r--locale/hy/activity.linfo2
-rw-r--r--locale/pl/LC_MESSAGES/org.sugarlabs.StoryActivity.mobin0 -> 637 bytes
-rw-r--r--locale/pl/activity.linfo2
-rw-r--r--po/Story.pot39
-rw-r--r--po/cs.po63
-rw-r--r--po/da.po63
-rw-r--r--po/el.po64
-rw-r--r--po/es.po63
-rw-r--r--po/fr.po63
-rw-r--r--po/hus.po63
-rw-r--r--po/hy.po45
-rw-r--r--po/mi.po62
-rw-r--r--po/nl.po63
-rw-r--r--po/pl.po44
-rw-r--r--po/quz.po64
-rw-r--r--po/zh_CN.po64
-rw-r--r--[-rwxr-xr-x]setup.py0
-rw-r--r--sprites.py231
-rw-r--r--sprites.pyobin0 -> 13199 bytes
-rw-r--r--toolbar_utils.py35
-rw-r--r--toolbar_utils.pyobin0 -> 4848 bytes
-rw-r--r--toolbar_utils.py~436
-rw-r--r--utils.pyobin0 -> 1599 bytes
49 files changed, 3126 insertions, 893 deletions
diff --git a/NEWS b/NEWS
index de2b07b..5858989 100644
--- a/NEWS
+++ b/NEWS
@@ -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
new file mode 100644
index 0000000..9d8fa25
--- /dev/null
+++ b/StoryActivity.pyo
Binary files differ
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
diff --git a/game.py b/game.py
index c1bc398..7202d5d 100644
--- a/game.py
+++ b/game.py
@@ -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
new file mode 100644
index 0000000..c61fe58
--- /dev/null
+++ b/game.pyo
Binary files differ
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()
diff --git a/grecord.py b/grecord.py
index 02fcbd3..fd1a794 100644
--- a/grecord.py
+++ b/grecord.py
@@ -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
new file mode 100644
index 0000000..8293c3f
--- /dev/null
+++ b/grecord.pyo
Binary files differ
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
new file mode 100644
index 0000000..353b27c
--- /dev/null
+++ b/locale/en/LC_MESSAGES/org.sugarlabs.StoryActivity.mo
Binary files differ
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
new file mode 100644
index 0000000..29c74b6
--- /dev/null
+++ b/locale/en_GB/LC_MESSAGES/org.sugarlabs.StoryActivity.mo
Binary files differ
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
new file mode 100644
index 0000000..a00438e
--- /dev/null
+++ b/locale/en_US/LC_MESSAGES/org.sugarlabs.StoryActivity.mo
Binary files differ
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
new file mode 100644
index 0000000..26c7bad
--- /dev/null
+++ b/locale/es/LC_MESSAGES/org.sugarlabs.StoryActivity.mo
Binary files differ
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
new file mode 100644
index 0000000..61cd693
--- /dev/null
+++ b/locale/hy/LC_MESSAGES/org.sugarlabs.StoryActivity.mo
Binary files differ
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
new file mode 100644
index 0000000..ddb60a2
--- /dev/null
+++ b/locale/pl/LC_MESSAGES/org.sugarlabs.StoryActivity.mo
Binary files differ
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"
diff --git a/po/hy.po b/po/hy.po
index 693bac9..4a75548 100644
--- a/po/hy.po
+++ b/po/hy.po
@@ -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"
diff --git a/po/pl.po b/po/pl.po
index 0970f3d..220cad2 100644
--- a/po/pl.po
+++ b/po/pl.po
@@ -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 的录制备注"
diff --git a/setup.py b/setup.py
index e5026ee..e5026ee 100755..100644
--- a/setup.py
+++ b/setup.py
diff --git a/sprites.py b/sprites.py
index 2b8bb55..e35f67b 100644
--- a/sprites.py
+++ b/sprites.py
@@ -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
new file mode 100644
index 0000000..1132095
--- /dev/null
+++ b/sprites.pyo
Binary files differ
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
new file mode 100644
index 0000000..8bd3748
--- /dev/null
+++ b/toolbar_utils.pyo
Binary files differ
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
new file mode 100644
index 0000000..94b1def
--- /dev/null
+++ b/utils.pyo
Binary files differ