|
|
|
@ -1,6 +1,6 @@
|
|
|
|
|
"""Base class for activities written in Python
|
|
|
|
|
|
|
|
|
|
This is currently the only definitive reference for what an
|
|
|
|
|
This is currently the only definitive reference for what an
|
|
|
|
|
activity must do to participate in the Sugar desktop.
|
|
|
|
|
|
|
|
|
|
A Basic Activity
|
|
|
|
@ -10,11 +10,11 @@ The convention is to call it ActivitynameActivity, but this is not required as
|
|
|
|
|
the activity.info file associated with your activity will tell the sugar-shell
|
|
|
|
|
which class to start.
|
|
|
|
|
|
|
|
|
|
For example the most minimal Activity:
|
|
|
|
|
For example the most minimal Activity:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
from sugar.activity import activity
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ReadActivity(activity.Activity):
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
@ -61,7 +61,7 @@ import dbus
|
|
|
|
|
import dbus.service
|
|
|
|
|
import cjson
|
|
|
|
|
|
|
|
|
|
from sugar import util
|
|
|
|
|
from sugar import util
|
|
|
|
|
from sugar.presence import presenceservice
|
|
|
|
|
from sugar.activity.activityservice import ActivityService
|
|
|
|
|
from sugar.activity.namingalert import NamingAlert
|
|
|
|
@ -136,67 +136,67 @@ class _ActivitySession(gobject.GObject):
|
|
|
|
|
class Activity(Window, gtk.Container):
|
|
|
|
|
"""This is the base Activity class that all other Activities derive from.
|
|
|
|
|
This is where your activity starts.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
To get a working Activity:
|
|
|
|
|
0. Derive your Activity from this class:
|
|
|
|
|
class MyActivity(activity.Activity):
|
|
|
|
|
...
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1. implement an __init__() method for your Activity class.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Use your init method to create your own ActivityToolbar which will
|
|
|
|
|
contain some standard buttons:
|
|
|
|
|
contain some standard buttons:
|
|
|
|
|
toolbox = activity.ActivityToolbox(self)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Add extra Toolbars to your toolbox.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
You should setup Activity sharing here too.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Finaly, your Activity may need some resources which you can claim
|
|
|
|
|
here too.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
The __init__() method is also used to make the distinction between
|
|
|
|
|
being resumed from the Journal, or starting with a blank document.
|
|
|
|
|
|
|
|
|
|
being resumed from the Journal, or starting with a blank document.
|
|
|
|
|
|
|
|
|
|
2. Implement read_file() and write_file()
|
|
|
|
|
Most activities revolve around creating and storing Journal entries.
|
|
|
|
|
For example, Write: You create a document, it is saved to the Journal
|
|
|
|
|
and then later you resume working on the document.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
read_file() and write_file() will be called by sugar to tell your
|
|
|
|
|
Activity that it should load or save the document the user is working
|
|
|
|
|
on.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3. Implement our Activity Toolbars.
|
|
|
|
|
The Toolbars are added to your Activity in step 1 (the toolbox), but
|
|
|
|
|
you need to implement them somewhere. Now is a good time.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
There are a number of standard Toolbars. The most basic one, the one
|
|
|
|
|
your almost absolutely MUST have is the ActivityToolbar. Without
|
|
|
|
|
this, you're not really making a proper Sugar Activity (which may be
|
|
|
|
|
okay, but you should really stop and think about why not!) You do
|
|
|
|
|
this with the ActivityToolbox(self) call in step 1.
|
|
|
|
|
|
|
|
|
|
this with the ActivityToolbox(self) call in step 1.
|
|
|
|
|
|
|
|
|
|
Usually, you will also need the standard EditToolbar. This is the one
|
|
|
|
|
which has the standard copy and paste buttons. You need to derive
|
|
|
|
|
your own EditToolbar class from sugar.EditToolbar:
|
|
|
|
|
class EditToolbar(activity.EditToolbar):
|
|
|
|
|
...
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
See EditToolbar for the methods you should implement in your class.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Finaly, your Activity will very likely need some activity specific
|
|
|
|
|
buttons and options you can create your own toolbars by deriving a
|
|
|
|
|
class from gtk.Toolbar:
|
|
|
|
|
class MySpecialToolbar(gtk.Toolbar):
|
|
|
|
|
...
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
4. Use your creativity. Make your Activity something special and share
|
|
|
|
|
it with your friends!
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Read through the methods of the Activity class below, to learn more about
|
|
|
|
|
how to make an Activity work.
|
|
|
|
|
|
|
|
|
|
how to make an Activity work.
|
|
|
|
|
|
|
|
|
|
Hint: A good and simple Activity to learn from is the Read activity. To
|
|
|
|
|
create your own activity, you may want to copy it and use it as a template.
|
|
|
|
|
"""
|
|
|
|
@ -208,34 +208,34 @@ class Activity(Window, gtk.Container):
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
def __init__(self, handle, create_jobject=True):
|
|
|
|
|
"""Initialise the Activity
|
|
|
|
|
|
|
|
|
|
"""Initialise the Activity
|
|
|
|
|
|
|
|
|
|
handle -- sugar.activity.activityhandle.ActivityHandle
|
|
|
|
|
instance providing the activity id and access to the
|
|
|
|
|
presence service which *may* provide sharing for this
|
|
|
|
|
instance providing the activity id and access to the
|
|
|
|
|
presence service which *may* provide sharing for this
|
|
|
|
|
application
|
|
|
|
|
|
|
|
|
|
create_jobject -- boolean
|
|
|
|
|
define if it should create a journal object if we are
|
|
|
|
|
not resuming
|
|
|
|
|
|
|
|
|
|
Side effects:
|
|
|
|
|
|
|
|
|
|
Sets the gdk screen DPI setting (resolution) to the
|
|
|
|
|
Side effects:
|
|
|
|
|
|
|
|
|
|
Sets the gdk screen DPI setting (resolution) to the
|
|
|
|
|
Sugar screen resolution.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Connects our "destroy" message to our _destroy_cb
|
|
|
|
|
method.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Creates a base gtk.Window within this window.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Creates an ActivityService (self._bus) servicing
|
|
|
|
|
this application.
|
|
|
|
|
|
|
|
|
|
Usage:
|
|
|
|
|
|
|
|
|
|
Usage:
|
|
|
|
|
If your Activity implements __init__(), it should call
|
|
|
|
|
the base class __init()__ before doing Activity specific things.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
Window.__init__(self)
|
|
|
|
|
|
|
|
|
@ -280,9 +280,9 @@ class Activity(Window, gtk.Container):
|
|
|
|
|
share_scope = SCOPE_PRIVATE
|
|
|
|
|
|
|
|
|
|
if handle.object_id:
|
|
|
|
|
self._jobject = datastore.get(handle.object_id)
|
|
|
|
|
self._jobject = datastore.get(handle.object_id)
|
|
|
|
|
self.set_title(self._jobject.metadata['title'])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if self._jobject.metadata.has_key('share-scope'):
|
|
|
|
|
share_scope = self._jobject.metadata['share-scope']
|
|
|
|
|
|
|
|
|
@ -364,7 +364,7 @@ class Activity(Window, gtk.Container):
|
|
|
|
|
|
|
|
|
|
def get_id(self):
|
|
|
|
|
"""Returns the activity id of the current instance of your activity.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
The activity id is sort-of-like the unix process id (PID). However,
|
|
|
|
|
unlike PIDs it is only different for each new instance (with
|
|
|
|
|
create_jobject = True set) and stays the same everytime a user
|
|
|
|
@ -379,7 +379,7 @@ class Activity(Window, gtk.Container):
|
|
|
|
|
|
|
|
|
|
def set_canvas(self, canvas):
|
|
|
|
|
"""Sets the 'work area' of your activity with the canvas of your choice.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
One commonly used canvas is gtk.ScrolledWindow
|
|
|
|
|
"""
|
|
|
|
|
Window.set_canvas(self, canvas)
|
|
|
|
@ -412,21 +412,21 @@ class Activity(Window, gtk.Container):
|
|
|
|
|
logging.debug('Error creating activity datastore object: %s', err)
|
|
|
|
|
|
|
|
|
|
def get_activity_root(self):
|
|
|
|
|
""" FIXME: Deprecated. This part of the API has been moved
|
|
|
|
|
""" FIXME: Deprecated. This part of the API has been moved
|
|
|
|
|
out of this class to the module itself
|
|
|
|
|
|
|
|
|
|
Returns a path for saving Activity specific preferences, etc.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Returns a path to the location in the filesystem where the activity can
|
|
|
|
|
store activity related data that doesn't pertain to the current
|
|
|
|
|
execution of the activity and thus cannot go into the DataStore.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Currently, this will return something like
|
|
|
|
|
~/.sugar/default/MyActivityName/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Activities should ONLY save settings, user preferences and other data
|
|
|
|
|
which isn't specific to a journal item here. If (meta-)data is in anyway
|
|
|
|
|
specific to a journal entry, it MUST be stored in the DataStore.
|
|
|
|
|
specific to a journal entry, it MUST be stored in the DataStore.
|
|
|
|
|
"""
|
|
|
|
|
if os.environ.has_key('SUGAR_ACTIVITY_ROOT') and \
|
|
|
|
|
os.environ['SUGAR_ACTIVITY_ROOT']:
|
|
|
|
@ -438,17 +438,17 @@ class Activity(Window, gtk.Container):
|
|
|
|
|
"""
|
|
|
|
|
Subclasses implement this method if they support resuming objects from
|
|
|
|
|
the journal. 'file_path' is the file to read from.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
You should immediately open the file from the file_path, because the
|
|
|
|
|
file_name will be deleted immediately after returning from read_file().
|
|
|
|
|
Once the file has been opened, you do not have to read it immediately:
|
|
|
|
|
After you have opened it, the file will only be really gone when you
|
|
|
|
|
close it.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Although not required, this is also a good time to read all meta-data:
|
|
|
|
|
the file itself cannot be changed externally, but the title, description
|
|
|
|
|
and other metadata['tags'] may change. So if it is important for you to
|
|
|
|
|
notice changes, this is the time to record the originals.
|
|
|
|
|
notice changes, this is the time to record the originals.
|
|
|
|
|
"""
|
|
|
|
|
raise NotImplementedError
|
|
|
|
|
|
|
|
|
@ -456,17 +456,17 @@ class Activity(Window, gtk.Container):
|
|
|
|
|
"""
|
|
|
|
|
Subclasses implement this method if they support saving data to objects
|
|
|
|
|
in the journal. 'file_path' is the file to write to.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
If the user did make changes, you should create the file_path and save
|
|
|
|
|
all document data to it.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Additionally, you should also write any metadata needed to resume your
|
|
|
|
|
activity. For example, the Read activity saves the current page and zoom
|
|
|
|
|
level, so it can display the page.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Note: Currently, the file_path *WILL* be different from the one you
|
|
|
|
|
received in file_read(). Even if you kept the file_path from file_read()
|
|
|
|
|
open until now, you must still write the entire file to this file_path.
|
|
|
|
|
open until now, you must still write the entire file to this file_path.
|
|
|
|
|
"""
|
|
|
|
|
raise NotImplementedError
|
|
|
|
|
|
|
|
|
@ -539,11 +539,11 @@ class Activity(Window, gtk.Container):
|
|
|
|
|
|
|
|
|
|
def save(self):
|
|
|
|
|
"""Request that the activity is saved to the Journal.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
This method is called by the close() method below. In general,
|
|
|
|
|
activities should not override this method. This method is part of the
|
|
|
|
|
public API of an Acivity, and should behave in standard ways. Use your
|
|
|
|
|
own implementation of write_file() to save your Activity specific data.
|
|
|
|
|
own implementation of write_file() to save your Activity specific data.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
if self._jobject is None:
|
|
|
|
@ -590,7 +590,7 @@ class Activity(Window, gtk.Container):
|
|
|
|
|
def copy(self):
|
|
|
|
|
"""Request that the activity 'Keep in Journal' the current state
|
|
|
|
|
of the activity.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Activities should not override this method. Instead, like save() do any
|
|
|
|
|
copy work that needs to be done in write_file()
|
|
|
|
|
"""
|
|
|
|
@ -656,7 +656,7 @@ class Activity(Window, gtk.Container):
|
|
|
|
|
|
|
|
|
|
def _send_invites(self):
|
|
|
|
|
while self._invites_queue:
|
|
|
|
|
buddy_key = self._invites_queue.pop()
|
|
|
|
|
buddy_key = self._invites_queue.pop()
|
|
|
|
|
buddy = self._pservice.get_buddy(buddy_key)
|
|
|
|
|
if buddy:
|
|
|
|
|
self.shared_activity.invite(
|
|
|
|
@ -666,10 +666,10 @@ class Activity(Window, gtk.Container):
|
|
|
|
|
|
|
|
|
|
def invite(self, buddy_key):
|
|
|
|
|
"""Invite a buddy to join this Activity.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Side Effects:
|
|
|
|
|
Calls self.share(True) to privately share the activity if it wasn't
|
|
|
|
|
shared before.
|
|
|
|
|
shared before.
|
|
|
|
|
"""
|
|
|
|
|
self._invites_queue.append(buddy_key)
|
|
|
|
|
|
|
|
|
@ -681,7 +681,7 @@ class Activity(Window, gtk.Container):
|
|
|
|
|
|
|
|
|
|
def share(self, private=False):
|
|
|
|
|
"""Request that the activity be shared on the network.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private -- bool: True to share by invitation only,
|
|
|
|
|
False to advertise as shared to everyone.
|
|
|
|
|
|
|
|
|
@ -694,7 +694,7 @@ class Activity(Window, gtk.Container):
|
|
|
|
|
verb = private and 'private' or 'public'
|
|
|
|
|
logging.debug('Requesting %s share of activity %s.', verb,
|
|
|
|
|
self._activity_id)
|
|
|
|
|
self._share_id = self._pservice.connect("activity-shared",
|
|
|
|
|
self._share_id = self._pservice.connect("activity-shared",
|
|
|
|
|
self.__share_cb)
|
|
|
|
|
self._pservice.share_activity(self, private=private)
|
|
|
|
|
|
|
|
|
@ -752,7 +752,7 @@ class Activity(Window, gtk.Container):
|
|
|
|
|
|
|
|
|
|
def close(self, skip_save=False):
|
|
|
|
|
"""Request that the activity be stopped and saved to the Journal
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Activities should not override this method, but should implement
|
|
|
|
|
write_file() to do any state saving instead. If the application wants
|
|
|
|
|
to control wether it can close, it should override can_close().
|
|
|
|
@ -783,13 +783,13 @@ class Activity(Window, gtk.Container):
|
|
|
|
|
|
|
|
|
|
def get_metadata(self):
|
|
|
|
|
"""Returns the jobject metadata or None if there is no jobject.
|
|
|
|
|
|
|
|
|
|
Activities can set metadata in write_file() using:
|
|
|
|
|
|
|
|
|
|
Activities can set metadata in write_file() using:
|
|
|
|
|
self.metadata['MyKey'] = "Something"
|
|
|
|
|
|
|
|
|
|
and retrieve metadata in read_file() using:
|
|
|
|
|
|
|
|
|
|
and retrieve metadata in read_file() using:
|
|
|
|
|
self.metadata.get('MyKey', 'aDefaultValue')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Note: Make sure your activity works properly if one or more of the
|
|
|
|
|
metadata items is missing. Never assume they will all be present.
|
|
|
|
|
"""
|
|
|
|
@ -822,7 +822,7 @@ def _get_session():
|
|
|
|
|
def get_bundle_name():
|
|
|
|
|
"""Return the bundle name for the current process' bundle"""
|
|
|
|
|
return os.environ['SUGAR_BUNDLE_NAME']
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_bundle_path():
|
|
|
|
|
"""Return the bundle path for the current process' bundle"""
|
|
|
|
|
return os.environ['SUGAR_BUNDLE_PATH']
|
|
|
|
|