Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/olpcgames/activity.py
diff options
context:
space:
mode:
authorManuel Kaufmann <humitos@gmail.com>2012-03-27 13:15:10 (GMT)
committer Rafael Ortiz <rafael@activitycentral.com>2012-03-28 18:05:22 (GMT)
commit335ad73456ba3ec8f56811abddcaca4650199db1 (patch)
treedb788baba57c7656c9bdb73eb9003a70dc87d59a /olpcgames/activity.py
parent6deeb3f569e6c9a1c02a32a011b7a96a58fa8443 (diff)
Save and restore state of the game
Ability to 'save' (when the user closes the Activity) and 'restore' (when the user launch it from the Journal or the Home without holding Alt) the state of the game. For this ability I had to upgrade 'olpcgames' to 1.6 because 'olpcgames.FILE_READ_REQUEST' and 'olpcgames.FILE_WRITE_REQUEST' events are added in that version and those events are needed for this. The data is saved (as JSON, with json module) in the 'event.metadata["state"]' and the timestamp state is saved in 'event.filename'. This commit solves ticket #2393: * http://bugs.sugarlabs.org/ticket/2393 Signed-off-by: Manuel Kaufmann <humitos@gmail.com> Signed-off-by: Rafael Ortiz <rafael@activitycentral.com>
Diffstat (limited to 'olpcgames/activity.py')
-rw-r--r--olpcgames/activity.py230
1 files changed, 128 insertions, 102 deletions
diff --git a/olpcgames/activity.py b/olpcgames/activity.py
index 77687cd..538ba13 100644
--- a/olpcgames/activity.py
+++ b/olpcgames/activity.py
@@ -1,54 +1,73 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-"""Embeds the Canvas widget into a Sugar-specific Activity environment"""
+"""Embeds the Canvas widget into a Sugar-specific Activity environment
+The olpcgames.activity module encapsulates creation of a Pygame activity.
+Your Activity should inherit from this class. Simply setting some class
+attributes is all you need to do in a class inheriting from
+olpcgames.activity.PygameActivity in order to get Pygame to work.
+
+(The skeleton builder script creates this file automatically for you).
+
+Note:
+ You should not import pygame into your activity file, as the olpcgames
+ wrapper needs to be initialized before pygame is imported the first time.
+
+Example usage:
+
+ class PygameActivity(activity.Activity):
+ game_name = None
+ game_title = 'Pygame Game'
+ game_size = (units.grid_to_pixels(16),
+ units.grid_to_pixels(11))
+ pygame_mode = 'SDL'
+"""
import logging
-logging.root.setLevel(logging.WARN)
-log = logging.getLogger('olpcgames.activity')
-#log.setLevel( logging.INFO )
+logging.root.setLevel( logging.WARN )
+log = logging.getLogger( 'olpcgames.activity' )
+##log.setLevel( logging.DEBUG )
import pygtk
pygtk.require('2.0')
import gtk
import gtk.gdk
+import os
from sugar.activity import activity
from sugar.graphics import style
-from olpcgames.canvas import PyGameCanvas
+from olpcgames.canvas import PygameCanvas
from olpcgames import mesh, util
-__all__ = ['PyGameActivity']
+__all__ = ['PygameActivity']
-
-class PyGameActivity(activity.Activity):
-
- """PyGame-specific activity type, provides boilerplate toolbar, creates canvas
+class PygameActivity(activity.Activity):
+ """Pygame-specific activity type, provides boilerplate toolbar, creates canvas
Subclass Overrides:
game_name -- specifies a fully-qualified name for the game's main-loop
format like so:
'package.module:main'
- if not function name is provided, "main" is assumed.
- game_handler -- alternate specification via direct
- reference to a main-loop
- function
+ if no function name is provided, "main" is assumed.
+
+ game_handler -- DEPRECATED. alternate specification via direct
+ reference to a main-loop function.
game_size -- two-value tuple specifying the size of the display in pixels,
this is currently static, so once the window is created it cannot be
changed.
- If None, use the bulk of the screen for the PyGame surface based on
+ If None, use the bulk of the screen for the Pygame surface based on
the values reported by the gtk.gdk functions. Note that None is
*not* the default value.
game_title -- title to be displayed in the Sugar Shell UI
pygame_mode -- chooses the rendering engine used for handling the
- PyGame drawing mode, 'SDL' chooses the standard PyGame renderer,
+ Pygame drawing mode, 'SDL' chooses the standard Pygame renderer,
'Cairo' chooses the experimental pygamecairo renderer.
+
+ Note: You likely do *not* want to use Cairo, it is no longer maintained.
- PYGAME_CANVAS_CLASS -- normally PyGameCanvas, but can be overridden
+ PYGAME_CANVAS_CLASS -- normally PygameCanvas, but can be overridden
if you want to provide a different canvas class, e.g. to provide a different
internal layout. Note: only used where pygame_mode == 'SDL'
@@ -60,138 +79,94 @@ class PyGameActivity(activity.Activity):
this super-class, with no easy way of overriding without completely rewriting
the __init__ method. We should allow for customising both the UI layout and
the toolbar contents/layout/connection.
-
+
XXX Note that if you change the title of your activity in the toolbar you may
see the same focus issues as we have patched around in the build_toolbar
method. If so, please report them to Mike Fletcher.
-
"""
-
game_name = None
- game_title = 'PyGame Game'
+ game_title = 'Pygame Game'
game_handler = None
- game_size = (16 * style.GRID_CELL_SIZE, 11 * style.GRID_CELL_SIZE)
+ game_size = (16 * style.GRID_CELL_SIZE,
+ 11 * style.GRID_CELL_SIZE)
pygame_mode = 'SDL'
def __init__(self, handle):
"""Initialise the Activity with the activity-description handle"""
-
- super(PyGameActivity, self).__init__(handle)
+ super(PygameActivity, self).__init__(handle)
self.make_global()
if self.game_size is None:
- (width, height) = (gtk.gdk.screen_width(),
- gtk.gdk.screen_height())
- log.info('Total screen size: %s %s', width, height)
-
+ width,height = gtk.gdk.screen_width(), gtk.gdk.screen_height()
+ log.info( 'Total screen size: %s %s', width,height)
# for now just fudge the toolbar size...
-
- self.game_size = (width, height - 1 * style.GRID_CELL_SIZE)
+ self.game_size = width, height - (1*style.GRID_CELL_SIZE)
self.set_title(self.game_title)
toolbar = self.build_toolbar()
- log.debug('Toolbar size: %s', toolbar.get_size_request())
+ log.debug( 'Toolbar size: %s', toolbar.get_size_request())
canvas = self.build_canvas()
+ self.connect( 'configure-event', canvas._translator.do_resize_event )
- def make_global(self):
+ def make_global( self ):
"""Hack to make olpcgames.ACTIVITY point to us
"""
+ import weakref, olpcgames
+ assert not olpcgames.ACTIVITY, """Activity.make_global called twice, have you created two Activity instances in a single process?"""
+ olpcgames.ACTIVITY = weakref.proxy( self )
- import weakref
- import olpcgames
- assert not olpcgames.ACTIVITY, \
- """Activity.make_global called twice, have you created two Activity instances in a single process?"""
- olpcgames.ACTIVITY = weakref.proxy(self)
-
- def build_toolbar(self):
+ def build_toolbar( self ):
"""Build our Activity toolbar for the Sugar system
This is a customisation point for those games which want to
provide custom toolbars when running under Sugar.
"""
-
- try:
- from sugar.graphics.toolbarbox import ToolbarBox, ToolbarButton
- from sugar.activity.widgets import ActivityToolbarButton, StopButton, \
- ShareButton
- from mybutton import MyActivityToolbarButton
-
- toolbar_box = ToolbarBox()
- activity_button = MyActivityToolbarButton(self)
- toolbar_box.toolbar.insert(activity_button, 0)
- activity_button.show()
-
- separator = gtk.SeparatorToolItem()
- separator.props.draw = False
- separator.set_expand(True)
- toolbar_box.toolbar.insert(separator, -1)
- separator.show()
-
- share_button = ShareButton(self)
- toolbar_box.toolbar.insert(share_button, -1)
- share_button.show()
-
- stop_button = StopButton(self)
- toolbar_box.toolbar.insert(stop_button, -1)
- stop_button.show()
-
- self.set_toolbar_box(toolbar_box)
- toolbar_box.show()
- toolbar=toolbar_box.toolbar
- except ImportError:
- toolbar = activity.ActivityToolbar(self)
- toolbar.show()
- self.set_toolbox(toolbar)
- toolbar.title.unset_flags(gtk.CAN_FOCUS)
-
+ toolbar = activity.ActivityToolbar(self)
+ toolbar.show()
+ self.set_toolbox(toolbar)
def shared_cb(*args, **kwargs):
- log.info('shared: %s, %s', args, kwargs)
+ log.info( 'shared: %s, %s', args, kwargs )
try:
mesh.activity_shared(self)
except Exception, err:
- log.error("""Failure signaling activity sharing to mesh module: %s"""
- , util.get_traceback(err))
+ log.error( """Failure signaling activity sharing to mesh module: %s""", util.get_traceback(err) )
else:
- log.info('mesh activity shared message sent, trying to grab focus'
- )
+ log.info( 'mesh activity shared message sent, trying to grab focus' )
try:
self._pgc.grab_focus()
except Exception, err:
- log.warn('Focus failed: %s', err)
+ log.warn( 'Focus failed: %s', err )
else:
- log.info('asserting focus')
- assert self._pgc.is_focus(), \
- """Did not successfully set pygame canvas focus"""
- log.info('callback finished')
-
+ log.info( 'asserting focus' )
+ assert self._pgc.is_focus(), """Did not successfully set pygame canvas focus"""
+ log.info( 'callback finished' )
+
def joined_cb(*args, **kwargs):
- log.info('joined: %s, %s', args, kwargs)
+ log.info( 'joined: %s, %s', args, kwargs )
mesh.activity_joined(self)
self._pgc.grab_focus()
-
- self.connect('shared', shared_cb)
- self.connect('joined', joined_cb)
+ self.connect("shared", shared_cb)
+ self.connect("joined", joined_cb)
if self.get_shared():
-
# if set at this point, it means we've already joined (i.e.,
# launched from Neighborhood)
-
joined_cb()
+ toolbar.title.unset_flags(gtk.CAN_FOCUS)
return toolbar
- PYGAME_CANVAS_CLASS = PyGameCanvas
-
- def build_canvas(self):
- """Construct the PyGame or PyGameCairo canvas for drawing"""
-
- assert self.game_handler or self.game_name, \
- 'You must specify a game_handler or game_name on your Activity (%r)' \
- % (self.game_handler or self.game_name)
+ PYGAME_CANVAS_CLASS = PygameCanvas
+ def build_canvas( self ):
+ """Construct the Pygame or PygameCairo canvas for drawing"""
+ assert self.game_handler or self.game_name, 'You must specify a game_handler or game_name on your Activity (%r)'%(
+ self.game_handler or self.game_name
+ )
if self.pygame_mode != 'Cairo':
self._pgc = self.PYGAME_CANVAS_CLASS(*self.game_size)
self.set_canvas(self._pgc)
self._pgc.grab_focus()
self._pgc.connect_game(self.game_handler or self.game_name)
+ # XXX Bad coder, do not hide in a widely subclassed operation
+ # map signal does not appear to show up on socket instances
gtk.gdk.threads_init()
return self._pgc
else:
@@ -209,7 +184,58 @@ class PyGameActivity(activity.Activity):
app = self.game_handler or self.game_name
if ':' not in app:
app += ':main'
- (mod_name, fn_name) = app.split(':')
+ mod_name, fn_name = app.split(':')
mod = __import__(mod_name, globals(), locals(), [])
fn = getattr(mod, fn_name)
fn()
+ def read_file(self, file_path):
+ """Handle request to read the given file on the Pygame side
+
+ This is complicated rather noticeably by the silly semantics of the Journal
+ where it unlinks the file as soon as this method returns. We either have to
+ handle the file-opening in PyGTK (not acceptable), block this thread until
+ the Pygame thread handles the event (which it may never do) or we have
+ to make the silly thing use a non-standard file-opening interface.
+ """
+ log.info( 'read_file: %s %s', file_path, self.metadata )
+ import olpcgames, pygame
+ from olpcgames import eventwrap
+ event = eventwrap.Event(
+ type = pygame.USEREVENT,
+ code = olpcgames.FILE_READ_REQUEST,
+ filename = file_path,
+ metadata = self.metadata,
+ )
+ eventwrap.post( event )
+ event.block()
+ def write_file( self, file_path ):
+ """Handle request to write to the given file on the Pygame side
+
+ This is rather complicated by the need to have the file complete by the
+ time the function returns. Very poor API, after all, if I have to write a
+ multi-hundred-megabyte file it might take many minutes to complete
+ writing.
+ """
+ log.info( 'write_file: %s %s', file_path, self.metadata )
+ if os.path.exists( file_path ):
+ self.read_file( file_path )
+ import olpcgames, pygame
+ from olpcgames import eventwrap
+ event = eventwrap.Event(
+ type = pygame.USEREVENT,
+ code = olpcgames.FILE_WRITE_REQUEST,
+ filename = file_path,
+ metadata = self.metadata,
+ )
+ eventwrap.post( event )
+ event.block()
+ if not os.path.exists( file_path ):
+ log.warn( '''No file created in %r''', file_path )
+ raise NotImplementedError( """Pygame Activity code did not produce a file for %s"""%( file_path, ))
+ else:
+ log.info( '''Stored file in %r''', file_path )
+
+
+import olpcgames
+olpcgames.PyGameActivity = PygameActivity
+PyGameActivity = PygameActivity